Explorar el Código

地图

master
张泽亮 hace 5 días
padre
commit
7320217c63
Se han modificado 3 ficheros con 223 adiciones y 346 borrados
  1. +219
    -342
      src/components/gis/MapField.vue
  2. +2
    -2
      src/views/app/map-bf.vue
  3. +2
    -2
      src/views/app/map.vue

+ 219
- 342
src/components/gis/MapField.vue Ver fichero

@@ -1,390 +1,268 @@
<template>
<div class="land-map-container">
<!-- 地图容器 -->
<div id="mapWrap" class="map-wrap"></div>
<!-- 地图操作按钮 -->
<div id="land-btn-wrap" class="map-btn-wrap">
<div>
<div :id="'map-element' + elementId" class="map-element" style="width: 100%; height: 100%;"></div>
<div :id="'draw-toolbar' + elementId" class='draw-toolbar' v-show="allowDraw">
<el-row>
<input id="drawPolygon" class="ant-btn ant-btn-red" type="button" value="画图" click="handleDrawPolygon"/>&nbsp;&nbsp;
<input id="drawReset" type="button" class="ant-btn ant-btn-red" value="还原" @click="handleDrawReset"/>
<input :id="'drawPolygon' + elementId" class="ant-btn ant-btn-red" type="button" value="画图"/>&nbsp;&nbsp;
<input :id="'drawRemove' + elementId" type="button" class="ant-btn ant-btn-red" value="取消画图"/>&nbsp;&nbsp;
<input :id="'drawReset' + elementId" type="button" class="ant-btn ant-btn-red" value="重置画图"/>
</el-row>
</div>
<div :id="'mark-toolbar' + elementId" class='draw-toolbar' v-show="allowMark">
<el-row>
<input :id="'markPoint' + elementId" class="ant-btn ant-btn-red" type="button" value="标记"/>&nbsp;&nbsp;
<input :id="'markReset' + elementId" type="button" class="ant-btn ant-btn-red" value="重置标记"/>
</el-row>
</div>
</div>
</template>

<script>
import ol from 'ol' // 确保项目已引入OpenLayers
import $ from 'jquery'
import $ from "jquery";
import {olMap} from "@/utils/ol_map";

const TYPE_NONE = 0; // 无交互 默认
const TYPE_DRAW = 1; // 绘制多边形 v-model为MultiPolygon的JSON
const TYPE_SELECT = 2; // 选择, 必须指定list属性 v-model为list索引, 未选中为-1
const TYPE_MARK = 3; // 标记点 v-model为[经度, 维度]数组

export default {
name: 'LandMapComponent',
name: "MapField",
props: {
// 地图配置参数
mapConfig: {
type: Object,
required: true,
default: () => ({
geoServerUrl: '', // GeoServer地址
landLayerName: '', // 地块图层名
villageBorderLayerName: '', // 村边界图层名
centerLng: 115.452752, // 默认中心点经度
centerLat: 31.789033, // 默认中心点纬度
zoom: 17.8, // 默认缩放级别
maxZoom: 18.3, // 最大缩放级别
minZoom: 0 // 最小缩放级别
})
minMapZoom: {
type: Number,
default: 16,
},
maxMapZoom: {
type: Number,
default: 18.9,
},
// 目标地块数据(用于定位和高亮)
targetLand: {
type: Object,
default: () => ({
fid: '',
theGeom: '', // 地块几何信息
importCode: '' // 用于筛选村边界的编码
})
allowDraw: {
type: Boolean,
default: false,
},
// 其他地块数据(用于加载已有地块图层)
otherLands: {
type: {
type: [Number, String], // 0 原始 1 绘制 2 选择
default: 0,
},
coord: {
type: Array,
default: () => []
}
default: function() {
return [115.452752, 31.789033];
},
},
value: {
type: [String, Array, Number],
},
list: {
type: Array,
default: function() {
return [];
},
},
allowSelect: {
type: Boolean,
default: false,
},
allowMark: {
type: Boolean,
default: false,
},
},
data() {
return {
map: null, // 地图实例
draw: null, // 绘制实例
vectorDrawingLayer: null, // 绘制图层
targetLandLayer: null, // 目标地块高亮图层
otherLandsLayer: null, // 其他地块图层
villageBorderLayer: null, // 村边界图层
drawResult: null // 绘制结果(几何坐标)
}
isInited: false,
mapObject: null,
mapResult: null,
elementId: ('_' + Math.random()).replaceAll('.', ''),
internalValue: null, // string only
};
},
created() {
},
mounted() {
this.Init();
this.SetupCoord();
if(this.list)
this.mapObject.SetLayers(this.list);
if(this.value)
this.SetValue(this.value);
},
watch: {
// 监听地块数据变化,重新加载图层
targetLand: {
deep: true,
handler(newVal) {
this.initTargetLandLayer(newVal)
this.initVillageBorderLayer(newVal.importCode)
}
value: function(newVal, oldVal) {
this.SetValue(newVal);
},
otherLands: {
coord: {
handler: function(newVal, oldVal) {
this.SetupCoord();
},
deep: true,
handler(newVal) {
this.initOtherLandsLayer(newVal)
}
},
// 监听地图配置变化,重新初始化地图
mapConfig: {
list: {
handler: function(newVal, oldVal) {
if(this.type == TYPE_SELECT)
{
this.mapObject.ClearSelection();
this.mapObject.SetLayers(newVal);
if(this.internalValue >= 0 && this.internalValue < this.list.length)
this.mapObject.SetSelection(this.list[this.internalValue].name);
}
},
deep: true,
handler(newVal) {
this.destroyMap() // 销毁旧地图
this.initMap(newVal) // 初始化新地图
}
}
},
mounted() {
// 组件挂载后初始化地图
this.initMap(this.mapConfig)
},
beforeUnmount() {
// 组件卸载前销毁地图资源
this.destroyMap()
},
},
methods: {
/**
* 初始化地图核心实例
* @param {Object} config - 地图配置
*/
initMap(config) {
// 1. 定义投影(EPSG:3857)
const projection = new ol.proj.Projection({
code: 'EPSG:3857',
units: 'degrees'
})

// 2. 加载底图(天地图卫星影像+标注)
const aerialLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: `http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=cc4aba6e967096098249efa069733067`
}),
name: '卫星影像图'
})

const labelLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: `https://t0.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=cc4aba6e967096098249efa069733067`
}),
name: '文字标注层'
})

// 3. 创建地图实例
this.map = new ol.Map({
controls: ol.control.defaults({
attribution: false, // 隐藏版权信息
zoom: false, // 隐藏缩放按钮
rotate: false // 隐藏旋转按钮
}),
layers: [aerialLayer, labelLayer],
projection: projection,
target: 'mapWrap',
view: new ol.View({
center: ol.proj.fromLonLat([config.centerLng, config.centerLat]),
zoom: config.zoom,
minZoom: config.minZoom,
maxZoom: config.maxZoom
})
})

// 4. 初始化附属图层
this.initVillageBorderLayer(this.targetLand.importCode)
this.initOtherLandsLayer(this.otherLands)
this.initTargetLandLayer(this.targetLand)
SetValue(val) {
if(this.internalValue === val)
return;
this.internalValue = val;
this.UpdateValueToMap();
},

/**
* 初始化村边界图层
* @param {String} importCode - 区域编码(用于筛选边界)
*/
initVillageBorderLayer(importCode) {
// 销毁旧图层
if (this.villageBorderLayer) {
this.map.removeLayer(this.villageBorderLayer)
}

if (!importCode || !this.mapConfig.geoServerUrl) return

// 创建新的村边界图层(WMS服务)
this.villageBorderLayer = new ol.layer.Image({
source: new ol.source.ImageWMS({
url: `${this.mapConfig.geoServerUrl}/wms`,
params: {
LAYERS: this.mapConfig.villageBorderLayerName,
cql_filter: `import_code = '${importCode}'`,
SRID: 3857
}
}),
name: 'villageBorderLayer'
})

this.map.addLayer(this.villageBorderLayer)
},

/**
* 初始化其他地块图层(青色显示)
* @param {Array} lands - 其他地块数据列表
*/
initOtherLandsLayer(lands) {
// 销毁旧图层
if (this.otherLandsLayer) {
this.map.removeLayer(this.otherLandsLayer)
UpdateValueToMap() {
this.SetupCoord();
if(this.type == TYPE_SELECT)
{
this.mapObject.ClearSelection();
if(this.internalValue >= 0 && this.internalValue < this.list.length)
this.mapObject.SetSelection(this.list[this.internalValue].name);
}

if (!lands.length) return

// 创建矢量数据源
const vectorSource = new ol.source.Vector()

// 遍历地块数据,添加到数据源
lands.forEach(land => {
if (land.theGeom && land.fid !== this.targetLand.fid) {
const feature = new ol.Feature({
geometry: new ol.geom.MultiPolygon(JSON.parse(land.theGeom).coordinates)
})
vectorSource.addFeature(feature)
else if(this.type == TYPE_MARK)
{
this.mapObject.RemoveDrawLayer();
if(this.internalValue && this.internalValue.length > 0)
{
let json = olMap.gen_polygon([this.internalValue]);
this.mapObject.SetLayer(olMap.MAP_FIELD_INIT_LAYER_NAME, json, null, null, {
'style.stroke.width': 10,
'style.stroke.color': '#FF0000',
});
}
})

// 创建其他地块图层
this.otherLandsLayer = new ol.layer.Vector({
source: vectorSource,
style: this.getOtherLandStyle() // 青色样式
})

this.map.addLayer(this.otherLandsLayer)
},

/**
* 初始化目标地块图层(黄色高亮)
* @param {Object} land - 目标地块数据
*/
initTargetLandLayer(land) {
// 销毁旧图层
if (this.targetLandLayer) {
this.map.removeLayer(this.targetLandLayer)
else
this.mapObject.PopLayer(olMap.MAP_FIELD_INIT_LAYER_NAME);
}

if (!land.theGeom) return

// 创建目标地块数据源
const vectorSource = new ol.source.Vector({
features: new ol.format.GeoJSON().readFeatures({
type: 'Feature',
geometry: JSON.parse(land.theGeom)
})
})

// 创建目标地块图层(黄色高亮)
this.targetLandLayer = new ol.layer.Vector({
source: vectorSource,
style: this.getTargetLandStyle() // 黄色样式
})

this.map.addLayer(this.targetLandLayer)

// 定位到目标地块
this.locateToTargetLand(vectorSource)
},

/**
* 定位到目标地块
* @param {ol.source.Vector} source - 目标地块数据源
*/
locateToTargetLand(source) {
const features = source.getFeatures()
if (!features.length) return

// 获取地块边界范围
const extent = source.getExtent()
// 地图居中到地块范围
this.map.getView().animate({
center: ol.extent.getCenter(extent),
zoom: this.mapConfig.zoom,
duration: 1000
})
},

/**
* 处理「画图」按钮点击事件
*/
handleDrawPolygon() {
// 移除旧的绘制图层和交互
this.removeDrawInteraction()
if (this.vectorDrawingLayer) {
this.map.removeLayer(this.vectorDrawingLayer)
else
{
this.mapObject.RemoveDrawLayer();
this.mapObject.SetLayer(olMap.MAP_FIELD_INIT_LAYER_NAME, this.internalValue);
}

// 创建新的绘制数据源和图层
const drawSource = new ol.source.Vector({ wrapX: false })
this.vectorDrawingLayer = new ol.layer.Vector({ source: drawSource })
this.map.addLayer(this.vectorDrawingLayer)

// 创建绘制交互(多边形)
this.draw = new ol.interaction.Draw({
source: drawSource,
type: 'Polygon'
})

// 绘制结束事件:保存结果并通知父组件
this.draw.on('drawend', (evt) => {
const geometry = evt.feature.getGeometry()
this.drawResult = geometry.getCoordinates()
// 通知父组件绘制结果
this.$emit('draw-complete', this.drawResult)
// 移除绘制交互(防止重复绘制)
this.map.removeInteraction(this.draw)
})

this.map.addInteraction(this.draw)
},

/**
* 处理「还原」按钮点击事件
*/
handleDrawReset() {
// 移除绘制交互和图层
this.removeDrawInteraction()
if (this.vectorDrawingLayer) {
this.map.removeLayer(this.vectorDrawingLayer)
Init() {
if(this.isInited)
return;
this.mapObject = new olMap('map-element' + this.elementId);
if(this.type == TYPE_DRAW)
{
this.mapObject.SetAllowDraw(this.allowDraw);
let self = this;
$("#drawPolygon" + this.elementId).click(function () {
self.mapObject.StartDraw();
});
//清除画图鼠标点击事件
$("#drawRemove" + this.elementId).click(function () {
self.mapObject.ClearDraw();
});
//还原之前图层
$("#drawReset" + this.elementId).click(function () {
self.mapObject.ResetDraw();
});
this.mapObject.SetCallback("draw::start", () => {
this.mapResult = null;
});
this.mapObject.SetCallback("draw::remove", () => {
});
this.mapObject.SetCallback("draw::end", (res) => {
this.mapResult = res;
this.SetInternalValue();
});
this.mapObject.SetCallback("draw::reset", () => {
this.mapResult = null;
});
}

// 清空绘制结果并通知父组件
this.drawResult = null
this.$emit('draw-reset')

// 重新加载目标地块图层(恢复高亮)
this.initTargetLandLayer(this.targetLand)
},

/**
* 移除绘制交互
*/
removeDrawInteraction() {
if (this.draw && this.map) {
this.map.removeInteraction(this.draw)
this.draw = null
else if(this.type == TYPE_SELECT)
{
this.mapObject.SetAllowSelect(this.allowSelect);
this.mapObject.SetCallback("interactive::select", (name) => {
console.log('select -> ' + name);
this.mapResult = name;
this.SetInternalValue();
this.$emit('select', name);
});
}
else if(this.type == TYPE_MARK)
{
this.mapObject.SetAllowMark(this.allowMark);
let self = this;
$("#markPoint" + this.elementId).click(function () {
self.mapObject.StartMark();
});
//还原之前图层
$("#markReset" + this.elementId).click(function () {
self.mapObject.ResetMark();
});
this.mapObject.SetCallback("mark::start", () => {
this.mapResult = null;
});
this.mapObject.SetCallback("mark::end", (res) => {
this.mapResult = res;
this.SetInternalValue();
});
this.mapObject.SetCallback("mark::reset", () => {
this.mapResult = null;
});
}
this.isInited = true;
if(this.internalValue)
this.UpdateValueToMap();
},

/**
* 获取目标地块样式(黄色高亮)
* @returns {ol.style.Style} 样式对象
*/
getTargetLandStyle() {
return new ol.style.Style({
fill: new ol.style.Fill({ color: 'yellow' }),
stroke: new ol.style.Stroke({ color: 'yellow', width: 3 })
})
DestroyMap() {
if(!this.mapObject)
return;
//delete this.mapObject;
this.mapObject.DestroyMap();
this.mapObject = null;
},

/**
* 获取其他地块样式(青色半透明)
* @returns {ol.style.Style} 样式对象
*/
getOtherLandStyle() {
return new ol.style.Style({
fill: new ol.style.Fill({ color: 'rgba(0, 218, 255, 0.3)' }),
stroke: new ol.style.Stroke({ color: '#00DAFF', width: 3 })
})
},

/**
* 销毁地图实例和资源
*/
destroyMap() {
if (this.map) {
// 移除所有图层
const layers = this.map.getLayers().getArray()
layers.forEach(layer => this.map.removeLayer(layer))
// 移除所有交互
const interactions = this.map.getInteractions().getArray()
interactions.forEach(interaction => this.map.removeInteraction(interaction))
// 清空地图容器
document.getElementById('mapWrap').innerHTML = ''
this.map = null
SetInternalValue() {
if(this.type == TYPE_SELECT)
{
this.internalValue = this.mapResult;
if(this.mapResult)
this.internalValue = this.list.findIndex((x) => x.name == this.mapResult);
else
this.internalValue = -1;
this.$emit('input', this.internalValue);
}

// 重置状态
this.draw = null
this.vectorDrawingLayer = null
this.targetLandLayer = null
this.otherLandsLayer = null
this.villageBorderLayer = null
this.drawResult = null
else if(this.type == TYPE_MARK)
{
this.internalValue = this.mapResult ? this.mapResult : [null, null];
this.$emit('input', this.internalValue);
}
else
{
this.internalValue = JSON.stringify(this.mapResult);
this.$emit('input', this.internalValue);
}
//console.log(this.type, this.mapResult, this.internalValue);
},

/**
* 对外暴露:清除所有图层(供父组件调用)
*/
clearAllLayers() {
this.removeDrawInteraction()
if (this.vectorDrawingLayer) this.map.removeLayer(this.vectorDrawingLayer)
if (this.targetLandLayer) this.map.removeLayer(this.targetLandLayer)
if (this.otherLandsLayer) this.map.removeLayer(this.otherLandsLayer)
if (this.villageBorderLayer) this.map.removeLayer(this.villageBorderLayer)
Update() {
if(this.mapObject)
this.mapObject.Update();
},
SetupCoord() {
if(this.mapObject && this.coord && this.coord.length >= 2 && this.coord[0] && this.coord[1])
this.mapObject.SetCoord(this.coord[0], this.coord[1]);
}
}
},
}
</script>

<style scoped>
.map-wrap {

.map-element {
width: 100%;
height: 100%;
}

.map-btn-wrap {
.draw-toolbar {
position: relative;
width: 40%;
left: 60%;
@@ -404,6 +282,5 @@
text-decoration: none;
text-indent: 0;
line-height: 20px;
right: -36%;
}
</style>

+ 2
- 2
src/views/app/map-bf.vue Ver fichero

@@ -183,13 +183,13 @@
import { deptTreeSelect } from "@/api/system/user"
import {getConfigKey} from "@/api/system/config";
import {getDept,getInfoByImportCode} from "@/api/system/dept";
import MapField from "@/components/house/MapField";
import $ from "jquery";


export default {
name: "contractedVillageContractor",
components: { Treeselect, Splitpanes, Pane,MapField },
components: { Treeselect, Splitpanes, Pane },
data() {
return {
form: {},


+ 2
- 2
src/views/app/map.vue Ver fichero

@@ -180,13 +180,13 @@
import { deptTreeSelect } from "@/api/system/user"
import {getConfigKey} from "@/api/system/config";
import {getDept,getInfoByImportCode} from "@/api/system/dept";
import MapField from "@/components/house/MapField";
import $ from "jquery";


export default {
name: "contractedVillageContractor",
components: { Treeselect, Splitpanes, Pane,MapField },
components: { Treeselect, Splitpanes, Pane },
data() {
return {
fform: {}, // 地块信息表单参数


Cargando…
Cancelar
Guardar