From b92acbac4811bf1e634cbd3a09199f9cfe089f24 Mon Sep 17 00:00:00 2001 From: szdot Date: Wed, 18 Jun 2025 03:59:14 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E7=B1=BB=20=20=E5=9E=8B=E3=80=91?= =?UTF-8?q?=EF=BC=9Afeat=20=E3=80=90=E5=8E=9F=20=20=E5=9B=A0=E3=80=91?= =?UTF-8?q?=EF=BC=9A=E9=A3=9E=E6=9C=BA=E6=8E=A7=E5=88=B6=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=20=E7=A6=81=E9=A3=9E=E5=8C=BA=E9=99=90?= =?UTF-8?q?=E9=A3=9E=E5=8C=BA=20=E6=98=BE=E7=A4=BA=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E7=9A=84=E6=8E=A7=E4=BB=B6=20=E3=80=90=E8=BF=87=20=20=E7=A8=8B?= =?UTF-8?q?=E3=80=91=EF=BC=9A=20=E3=80=90=E5=BD=B1=20=20=E5=93=8D=E3=80=91?= =?UTF-8?q?=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 类型 包含: # feat:新功能(feature) # fix:修补bug # docs:文档(documentation) # style: 格式(不影响代码运行的变动) # refactor:重构(即不是新增功能,也不是修改bug的代码变动) # test:增加测试 # chore:构建过程或辅助工具的变动 --- src/components/MapBox.vue | 206 ++++++++------- src/styles/myIcon.scss | 2 +- src/utils/mapboxgl_plugs/index.js | 240 +++++------------- .../layout/components/main/nofly/set.vue | 4 +- .../layout/components/main/planes/index.vue | 21 +- 5 files changed, 196 insertions(+), 277 deletions(-) diff --git a/src/components/MapBox.vue b/src/components/MapBox.vue index 6f2ca7b..1d87b9f 100644 --- a/src/components/MapBox.vue +++ b/src/components/MapBox.vue @@ -172,7 +172,7 @@ export default { type: Boolean, default: false }, - showNofly: { // 显示 禁飞区 组件 + enableShowNofly: { // 显示 禁飞区 限飞区 组件 type: Boolean, default: false } @@ -366,17 +366,41 @@ export default { } // 显示禁飞区 限飞区控件 - if (this.showNofly) { + if (this.enableShowNofly) { const noflyPolygons = this.$store.state.noflyData[0] || [] const restrictflyPolygons = this.$store.state.noflyData[1] || [] this.map.addControl(new PolygonToggleControl({ - defaultIconClass: 'iconfont icon-polygon-default f-s-20', - activeIconClass: 'iconfont icon-polygon-active f-s-20 brandFontColor', - noflyPolygons, - restrictflyPolygons, - onDraw: (active) => { - console.log('禁飞/限飞区域控件状态:', active) + defaultIconClass: 'iconfont icon-jinfeiqu_weidianji f-s-20 seatFontColor', + activeIconClass: 'iconfont icon-jinfeiqu f-s-20 brandFontColor', + + onToggle: (isActive) => { + if (isActive) { + // 激活时绘制禁飞区 + noflyPolygons.forEach((coords, i) => { + this.drawPolygonWithLabel(coords, `nofly-polygon-${i}`, '#F33', '禁飞区') + }) + // 绘制限飞区 + restrictflyPolygons.forEach((coords, i) => { + this.drawPolygonWithLabel(coords, `restrict-polygon-${i}`, '#F83', '限飞区') + }) + } else { + // 关闭时清除禁飞区和限飞区相关图层和数据源 + const layerIds = [] + const sourceIds = [] + + noflyPolygons.forEach((_, i) => { + layerIds.push(`nofly-polygon-${i}`, `nofly-polygon-${i}-outline`, `nofly-polygon-${i}-label-layer`) + sourceIds.push(`nofly-polygon-${i}`, `nofly-polygon-${i}-label`) + }) + + restrictflyPolygons.forEach((_, i) => { + layerIds.push(`restrict-polygon-${i}`, `restrict-polygon-${i}-outline`, `restrict-polygon-${i}-label-layer`) + sourceIds.push(`restrict-polygon-${i}`, `restrict-polygon-${i}-label`) + }) + + this.clearMapElements(layerIds, sourceIds) + } } }), 'top-right') } @@ -717,104 +741,90 @@ export default { } }, /** - * @description: 绘制一个多边形区域,并在中心显示文字 - * @param {Array} coordinatesArray 多边形坐标数组(格式为 [ [lng, lat], ... ],首尾闭合) - * @param {String} polygonId 图层 ID - * @param {String} fillColor 填充颜色(默认红色) - * @param {String} labelText 中心显示的文字(为空则不显示) + * @description:在 Mapbox 地图上绘制一个带轮廓线的多边形区域,并可选添加标签文字。 + * + * @param {Array>} coords - 多边形的经纬度坐标数组,格式为 [[lng, lat], [lng, lat], ...] + * @param {string} id - 图层和数据源的唯一标识符前缀,用于创建 source 和 layer 的 ID + * @param {string} fillColor - 多边形的填充颜色(如 '#F00' 表示红色) + * @param {string} labelText - 要显示的标签文字(传空则不显示) */ - drawPolygonWithLabel (coordinatesArray, polygonId = 'polygon-area', fillColor = '#F33', labelText = '') { - // GeoJSON 数据 - const polygonGeoJSON = { - type: 'Feature', - geometry: { - type: 'Polygon', - coordinates: [coordinatesArray] - } - } - - // 内部函数:计算中心点 - const calculateCenter = (coords) => { - let lngSum = 0 - let latSum = 0 - coords.forEach(([lng, lat]) => { - lngSum += lng - latSum += lat - }) - return [lngSum / coords.length, latSum / coords.length] - } - - const center = calculateCenter(coordinatesArray) - - const labelGeoJSON = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: center - }, - properties: { - title: labelText - } - } - - // 添加或更新多边形图层 - if (this.map.getLayer(polygonId)) { - this.map.getSource(polygonId).setData(polygonGeoJSON) - } else { - this.map.addSource(polygonId, { - type: 'geojson', - data: polygonGeoJSON - }) - - this.map.addLayer({ - id: polygonId, - type: 'fill', - source: polygonId, - paint: { - 'fill-color': fillColor, - 'fill-opacity': 0.4 + drawPolygonWithLabel (coords, id, fillColor, labelText) { + // 添加 GeoJSON 数据源(多边形) + this.map.addSource(id, { + type: 'geojson', + data: { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [coords] } - }) + } + }) - this.map.addLayer({ - id: `${polygonId}-outline`, - type: 'line', - source: polygonId, - paint: { - 'line-color': fillColor, - 'line-width': 2 - } - }) - } + // 添加填充图层(多边形区域) + this.map.addLayer({ + id: id, + type: 'fill', + source: id, + paint: { + 'fill-color': fillColor, + 'fill-opacity': 0.3 + } + }) - // 添加或更新文字标签 + // 添加轮廓图层(边框线) + this.map.addLayer({ + id: `${id}-outline`, + type: 'line', + source: id, + paint: { + 'line-color': fillColor, + 'line-width': 2 + } + }) + + // 如果传入了标签文字,则添加文字图层 if (labelText) { - const labelSourceId = `${polygonId}-label` - const labelLayerId = `${polygonId}-label-layer` + // 简单方式计算中心点(可替换为更复杂的质心算法) + const center = coords.reduce((acc, cur) => { + acc[0] += cur[0] + acc[1] += cur[1] + return acc + }, [0, 0]).map(v => v / coords.length) - if (this.map.getLayer(labelLayerId)) { - this.map.getSource(labelSourceId).setData(labelGeoJSON) - } else { - this.map.addSource(labelSourceId, { - type: 'geojson', - data: labelGeoJSON - }) - - this.map.addLayer({ - id: labelLayerId, - type: 'symbol', - source: labelSourceId, - layout: { - 'text-field': ['get', 'title'], - 'text-size': 14, - 'text-offset': [0, 0.5], - 'text-anchor': 'top' + // 添加文字专用数据源 + this.map.addSource(`${id}-label`, { + type: 'geojson', + data: { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: center }, - paint: { - 'text-color': '#000000' + properties: { + label: labelText } - }) - } + } + }) + + // 添加文字图层 + this.map.addLayer({ + id: `${id}-label-layer`, + type: 'symbol', + source: `${id}-label`, + layout: { + 'text-field': ['get', 'label'], + 'text-size': 14, + 'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'], + 'text-offset': [0, 0.6], + 'text-anchor': 'top' + }, + paint: { + 'text-color': fillColor, + 'text-halo-color': '#fff', + 'text-halo-width': 1.5 + } + }) } }, /** diff --git a/src/styles/myIcon.scss b/src/styles/myIcon.scss index 47ba736..8791a8c 100644 --- a/src/styles/myIcon.scss +++ b/src/styles/myIcon.scss @@ -1 +1 @@ -@import 'https://at.alicdn.com/t/c/font_3703467_49fidfokplk.css'; //iconfont阿里巴巴 \ No newline at end of file +@import 'https://at.alicdn.com/t/c/font_3703467_1z89u99sr5w.css'; //iconfont阿里巴巴 \ No newline at end of file diff --git a/src/utils/mapboxgl_plugs/index.js b/src/utils/mapboxgl_plugs/index.js index d021b84..b11cca6 100644 --- a/src/utils/mapboxgl_plugs/index.js +++ b/src/utils/mapboxgl_plugs/index.js @@ -353,6 +353,7 @@ export class NoFlyControl { f.properties.type = type }) + // 重新添加以确保类型被写入并触发样式 this._draw.delete(features.map(f => f.id)) this._draw.add({ type: 'FeatureCollection', @@ -401,49 +402,64 @@ export class NoFlyControl { } _getDrawStyles () { + const noflyColor = '#ff3333' + const limitColor = '#ff8833' + return [ + // 区域填充样式 { - id: 'gl-draw-polygon-fill', + id: 'custom-polygon-fill', type: 'fill', - filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']], + filter: ['all', ['==', '$type', 'Polygon']], paint: { - 'fill-color': ['case', - ['==', ['get', 'type'], 'nofly'], '#F33', - ['==', ['get', 'type'], 'limit'], '#F83', - '#F83' + 'fill-color': [ + 'case', + ['==', ['get', 'type'], 'nofly'], noflyColor, + ['==', ['get', 'type'], 'limit'], limitColor, + limitColor // 默认颜色 ], 'fill-opacity': 0.3 } }, + // 区域边框线(不区分 static / active) { - id: 'gl-draw-polygon-stroke-active', + id: 'custom-polygon-stroke', type: 'line', - filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']], + filter: ['all', ['==', '$type', 'Polygon']], paint: { - 'line-color': ['case', - ['==', ['get', 'type'], 'nofly'], '#F33', - ['==', ['get', 'type'], 'limit'], '#F83', - '#F83' + 'line-color': [ + 'case', + ['==', ['get', 'type'], 'nofly'], noflyColor, + ['==', ['get', 'type'], 'limit'], limitColor, + limitColor ], - 'line-width': 2 + 'line-width': 2, + 'line-dasharray': [2, 2] } }, + // 顶点圆点 { - id: 'gl-draw-polygon-and-line-vertex-halo-active', - type: 'circle', - filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']], - paint: { - 'circle-radius': 7, - 'circle-color': '#FFF' - } - }, - { - id: 'gl-draw-polygon-and-line-vertex-active', + id: 'custom-vertex-point', type: 'circle', filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']], paint: { 'circle-radius': 5, - 'circle-color': '#F83' + 'circle-color': [ + 'case', + ['==', ['get', 'type'], 'nofly'], noflyColor, + ['==', ['get', 'type'], 'limit'], limitColor, + limitColor + ] + } + }, + // 顶点外光环 + { + id: 'custom-vertex-halo', + type: 'circle', + filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']], + paint: { + 'circle-radius': 7, + 'circle-color': '#FFFFFF' } } ] @@ -452,24 +468,16 @@ export class NoFlyControl { // 显示禁飞区 限飞区 export class PolygonToggleControl { - /** - * - * @param {{ - * defaultIconClass: string, - * activeIconClass: string, - * onDraw?: Function, // 激活时回调,传入激活状态 - * noflyPolygons: Array, // 禁飞区多边形数据,数组里是多边形坐标数组 - * restrictflyPolygons: Array // 限飞区多边形数据 - * }} options - */ - constructor ({ defaultIconClass, activeIconClass, onDraw, noflyPolygons, restrictflyPolygons }) { - this._defaultIconClass = defaultIconClass - this._activeIconClass = activeIconClass - this._onDraw = onDraw - this._noflyPolygons = noflyPolygons - this._restrictflyPolygons = restrictflyPolygons - this._isActive = false + constructor (options = {}) { this._map = null + this._container = null + this._active = false + + this.defaultIconClass = options.defaultIconClass + this.activeIconClass = options.activeIconClass + + // 外部传入的切换回调 + this.onToggle = options.onToggle || (() => {}) } onAdd (map) { @@ -478,144 +486,30 @@ export class PolygonToggleControl { this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group' this._button = document.createElement('button') - this._button.className = this._defaultIconClass - this._button.onclick = this._handleClick.bind(this) - + this._button.type = 'button' + this._button.className = this.defaultIconClass this._container.appendChild(this._button) + + this._button.addEventListener('click', () => { + this._active = !this._active + this._update() + this.onToggle(this._active) // 通知外部状态切换 + }) + return this._container } - _handleClick () { - this._isActive = !this._isActive - this._button.className = this._isActive ? this._activeIconClass : this._defaultIconClass - - if (this._isActive) { - // 激活时绘制所有禁飞区和限飞区多边形 - this._drawPolygons() - } else { - // 取消激活时移除所有相关图层 - this._removePolygons() - } - - if (this._onDraw) this._onDraw(this._isActive) - } - - _drawPolygons () { - // 禁飞区用颜色 #FF3333,id 用 nofly-0、nofly-1 ... - this._noflyPolygons.forEach((coords, idx) => { - this._drawPolygonWithLabel(coords, `nofly-${idx}`, '#FF3333', '禁飞区') - }) - - // 限飞区用颜色 #FF8833,id 用 restrictfly-0、restrictfly-1 ... - this._restrictflyPolygons.forEach((coords, idx) => { - this._drawPolygonWithLabel(coords, `restrictfly-${idx}`, '#FF8833', '限飞区') - }) - } - - _removePolygons () { - // 卸载禁飞区相关图层和source - this._noflyPolygons.forEach((_, idx) => { - this._removePolygonLayers(`nofly-${idx}`) - }) - // 卸载限飞区相关图层和source - this._restrictflyPolygons.forEach((_, idx) => { - this._removePolygonLayers(`restrictfly-${idx}`) - }) - } - - _removePolygonLayers (baseId) { - const outlineId = `${baseId}-outline` - const labelLayerId = `${baseId}-label-layer` - const labelSourceId = `${baseId}-label` - - if (this._map.getLayer(labelLayerId)) this._map.removeLayer(labelLayerId) - if (this._map.getSource(labelSourceId)) this._map.removeSource(labelSourceId) - - if (this._map.getLayer(outlineId)) this._map.removeLayer(outlineId) - if (this._map.getLayer(baseId)) this._map.removeLayer(baseId) - if (this._map.getSource(baseId)) this._map.removeSource(baseId) - } - - _drawPolygonWithLabel (coordinatesArray, polygonId, fillColor, labelText) { - // 计算中心点 - const calculateCenter = (coords) => { - let lngSum = 0; let latSum = 0 - coords.forEach(([lng, lat]) => { lngSum += lng; latSum += lat }) - return [lngSum / coords.length, latSum / coords.length] - } - - const polygonGeoJSON = { - type: 'Feature', - geometry: { - type: 'Polygon', - coordinates: [coordinatesArray] - } - } - - const center = calculateCenter(coordinatesArray) - - const labelGeoJSON = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: center - }, - properties: { - title: labelText - } - } - - if (this._map.getLayer(polygonId)) { - this._map.getSource(polygonId).setData(polygonGeoJSON) - } else { - this._map.addSource(polygonId, { type: 'geojson', data: polygonGeoJSON }) - - this._map.addLayer({ - id: polygonId, - type: 'fill', - source: polygonId, - paint: { 'fill-color': fillColor, 'fill-opacity': 0.4 } - }) - - this._map.addLayer({ - id: `${polygonId}-outline`, - type: 'line', - source: polygonId, - paint: { 'line-color': fillColor, 'line-width': 2 } - }) - } - - if (labelText) { - const labelSourceId = `${polygonId}-label` - const labelLayerId = `${polygonId}-label-layer` - - if (this._map.getLayer(labelLayerId)) { - this._map.getSource(labelSourceId).setData(labelGeoJSON) - } else { - this._map.addSource(labelSourceId, { type: 'geojson', data: labelGeoJSON }) - - this._map.addLayer({ - id: labelLayerId, - type: 'symbol', - source: labelSourceId, - layout: { - 'text-field': ['get', 'title'], - 'text-size': 14, - 'text-offset': [0, 0.5], - 'text-anchor': 'top' - }, - paint: { - 'text-color': '#000000' - } - }) - } - } - } - onRemove () { - this._removePolygons() this._container.parentNode.removeChild(this._container) - this._map = undefined + this._map = null + } + + _update () { + if (this._active) { + this._button.className = this.activeIconClass + } else { + this._button.className = this.defaultIconClass + } } } diff --git a/src/views/layout/components/main/nofly/set.vue b/src/views/layout/components/main/nofly/set.vue index 2c82f63..60ffc95 100644 --- a/src/views/layout/components/main/nofly/set.vue +++ b/src/views/layout/components/main/nofly/set.vue @@ -14,8 +14,8 @@ export default { }, data () { return { - showMapbox: true, - mapboxKey: 0 + showMapbox: true, // 数据更新的时候 用于刷新地图组件 + mapboxKey: 0 // 初始化一个变量用于控制map-box组件的重新渲染 } }, computed: { diff --git a/src/views/layout/components/main/planes/index.vue b/src/views/layout/components/main/planes/index.vue index 6a249e8..c3ce0d7 100644 --- a/src/views/layout/components/main/planes/index.vue +++ b/src/views/layout/components/main/planes/index.vue @@ -1,8 +1,8 @@