From 3aefe29db582b9d94817145175e0284b9c94539f Mon Sep 17 00:00:00 2001 From: luoxiao Date: Wed, 20 Nov 2024 16:25:19 +0800 Subject: [PATCH] =?UTF-8?q?[fix]=E7=82=B9=E9=80=89=E6=94=AF=E6=8C=81l7?= =?UTF-8?q?=E5=9B=BE=E5=B1=82=EF=BC=8C3d=E5=A1=AB=E5=85=85=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LayerHighlightViewModel.ts | 113 ++++++++++++++++-- .../__tests__/LayerHighlightViewModel.spec.js | 81 ++++++++++++- 2 files changed, 177 insertions(+), 17 deletions(-) diff --git a/src/mapboxgl/layer-highlight/LayerHighlightViewModel.ts b/src/mapboxgl/layer-highlight/LayerHighlightViewModel.ts index f94db0ff..5d1acbd7 100644 --- a/src/mapboxgl/layer-highlight/LayerHighlightViewModel.ts +++ b/src/mapboxgl/layer-highlight/LayerHighlightViewModel.ts @@ -8,6 +8,7 @@ interface HighlightStyle { circle: InstanceType; line: InstanceType; fill: InstanceType; + fillExtrusion?: any; strokeLine?: InstanceType; stokeLine?: InstanceType; } @@ -97,6 +98,8 @@ interface CreateRelatedDatasParams { isMultiple?: boolean; } +type mapboxEnhanceLayer = mapboxglTypes.Layer & { l7layer?: any }; + const HIGHLIGHT_COLOR = '#01ffff'; const PAINT_BASIC_ATTRS: BasicStyleAttrs = { @@ -141,6 +144,15 @@ const LAYER_DEFAULT_STYLE = { visibility: 'visible' } }, + 'fill-extrusion': { + paint: { + 'fill-extrusion-color': HIGHLIGHT_COLOR, + 'fill-extrusion-opacity': 0.6 + }, + layout: { + visibility: 'visible' + } + }, symbol: { layout: { 'icon-size': 5 @@ -192,8 +204,9 @@ export default class HighlightLayer extends mapboxgl.Evented { }; } - setMap({ map }: MapLoadInfo) { + setMap({ map, webmap }: MapLoadInfo) { this.map = map; + this.webmap = webmap; this.registerMapClick(); this.setTargetLayers(this.highlightOptions.layerIds); } @@ -273,8 +286,54 @@ export default class HighlightLayer extends mapboxgl.Evented { this.map.off('mouseleave', layerId, this.handleMapMouseLeave); }); } + highlightL7Layer({ layer, features, filter }) { + const { type, id, paint } = layer; + const nextPaint = Object.assign({}, paint); + let styleType = type; + const highlightLayerStyle: HighlightStyle = JSON.parse(JSON.stringify(this.highlightOptions.style)); + switch (type) { + case 'line-extrusion': + styleType = 'line'; + break; + case 'radar': + case 'point-extrusion': + styleType = 'circle'; + break; + default: + styleType = highlightLayerStyle[type] ? type : 'fill'; + break; + } + const paintKeys = Object.keys(paint); + const { paint: paintStyle } = highlightLayerStyle[styleType]; + for (const key in paintStyle) { + const matchKey = paintKeys.find(item => item.replace(`${type}-`, '') === key.replace(`${styleType}-`, '')); + if (matchKey) { + nextPaint[matchKey] = key.match(/-(radius|width)/) + ? Math.max(paintStyle[key], nextPaint[matchKey]) + : paintStyle[key]; + } + } + this.webmap.copyLayer(id, { id: `${id}-${this.highlightOptions.name}-SM-highlighted`, filter, paint: nextPaint }); + this.setL7Filter(layer, features); + } + + setL7Filter(layer, features) { + layer.setSelectedDatas(features); + const layerFilter = this.map.getFilter(layer.id); + this.map.setFilter(layer.id, layerFilter); + } + + addHighlightLayers(layer: mapboxEnhanceLayer, filter: any, features) { + const { l7layer } = layer; + if (l7layer) { + this.highlightL7Layer({ layer, features, filter }); + return; + } else { + this.addNormalHighlightLayers(layer, filter); + } + } - addHighlightLayers(layer: mapboxglTypes.Layer, filter: any) { + addNormalHighlightLayers(layer: mapboxglTypes.Layer, filter: any) { let type = layer.type as unknown as StyleTypes[number]; let paint = layer.paint; const id = layer.id; @@ -288,7 +347,7 @@ export default class HighlightLayer extends mapboxgl.Evented { types.push('strokeLine'); } const layerHighlightStyle = this.createLayerHighlightStyle(types, id); - if (['circle', 'line', 'fill'].includes(type)) { + if (['circle', 'line', 'fill', 'fill-extrusion'].includes(type)) { const layerStyle = layerHighlightStyle[type]; const highlightLayer = Object.assign({}, layer, { id: this.createHightlightLayerId(id), @@ -330,6 +389,13 @@ export default class HighlightLayer extends mapboxgl.Evented { if (!this.map) { return; } + this.highlightOptions.layerIds.forEach(layerId =>{ + const layer = this.map.getLayer(layerId); + // @ts-ignore + if (layer?.l7layer) { + this.setL7Filter(layer, []); + } + }) const layersToRemove = this.getHighlightLayerIds(this.highlightOptions.layerIds); layersToRemove.forEach(layerId => { if (this.map.getLayer(layerId)) { @@ -399,15 +465,28 @@ export default class HighlightLayer extends mapboxgl.Evented { fields = this.highlightOptions.featureFieldsMap?.[targetId] }: CreateFilterExpParams) { // 高亮过滤(所有字段) - const filterKeys = ['smx', 'smy', 'lon', 'lat', 'longitude', 'latitude', 'x', 'y', 'usestyle', 'featureinfo']; + const filterKeys = ['smx', 'smy', 'lon', 'lat', 'longitude', 'latitude', 'x', 'y', 'usestyle', 'featureinfo', '_id', 'id', 'smgeometry']; const isBasicType = (item: any) => { return typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean'; }; + const UNIQUE_FIELD = ['SMID', 'SMPID']; + const properties = feature.properties || {}; + let uniqueId; + for (const name of UNIQUE_FIELD) { + for (const attr in properties) { + if (attr.toUpperCase() === name) { + uniqueId = attr; + break; + } + } + } const filter: any[] = ['all']; - const featureKeys: string[] = fields || feature._vectorTileFeature?._keys || Object.keys(feature.properties); + const keys: string[] = fields || feature._vectorTileFeature?._keys || Object.keys(feature.properties); + const featureKeys = uniqueId ? [uniqueId] : keys; + return featureKeys.reduce((exp, key) => { if (filterKeys.indexOf(key.toLowerCase()) === -1 && isBasicType(feature.properties[key])) { - exp.push(['==', key, feature.properties[key]]); + exp.push(['==', ['get', key], feature.properties[key]]); } return exp; }, filter); @@ -433,6 +512,13 @@ export default class HighlightLayer extends mapboxgl.Evented { } }); }); + // 3d填充面的样式用普通面的配置项 + highlightStyle['fill-extrusion'] = { + paint: { + 'fill-extrusion-color': highlightStyle.fill.paint['fill-color'], + 'fill-extrusion-opacity': highlightStyle.fill.paint['fill-opacity'], + } + }; return highlightStyle; } @@ -450,7 +536,12 @@ export default class HighlightLayer extends mapboxgl.Evented { return layerIds.reduce((idList, layerId) => { const highlightLayerId = this.createHightlightLayerId(layerId); const highlightStrokeLayerId = this.createHighlightStrokeLayerId(layerId); - idList.push(highlightLayerId, highlightStrokeLayerId); + if (this.map.getLayer(highlightLayerId)) { + idList.push(highlightLayerId); + } + if (this.map.getLayer(highlightStrokeLayerId)) { + idList.push(highlightStrokeLayerId); + } return idList; }, []); } @@ -510,7 +601,7 @@ export default class HighlightLayer extends mapboxgl.Evented { }; const filterExps = this.createFilterExps(params); popupDatas = this.createPopupDatas(params); - this.addHighlightLayers(activeTargetLayer, filterExps); + this.addHighlightLayers(activeTargetLayer as mapboxEnhanceLayer, filterExps, this.resultFeatures); } const emitData: MapSelectionChangedEmit = { features, @@ -565,11 +656,7 @@ export default class HighlightLayer extends mapboxgl.Evented { return features.reduce( (filterExps: any[], feature) => { const filterExp = this.createFilterExp({ feature, targetId }); - if (isMultiple) { - filterExps.push(filterExp); - } else { - filterExps = filterExp; - } + filterExps.push(filterExp); return filterExps; }, ['any'] diff --git a/src/mapboxgl/layer-highlight/__tests__/LayerHighlightViewModel.spec.js b/src/mapboxgl/layer-highlight/__tests__/LayerHighlightViewModel.spec.js index a679d33a..8886cf3c 100644 --- a/src/mapboxgl/layer-highlight/__tests__/LayerHighlightViewModel.spec.js +++ b/src/mapboxgl/layer-highlight/__tests__/LayerHighlightViewModel.spec.js @@ -39,14 +39,17 @@ describe('LayerHighlightViewModel', () => { let map; const uniqueName = 'Test'; const mockLayerName = 'China'; + const copyLayerSpy = jest.fn(); let viewModel; beforeEach(() => { map = new Map({ style: { center: [0, 0], zoom: 1, layers: [], sources: {} } }); + viewModel = new LayerHighlightViewModel({ name: uniqueName, style: highlightStyle }); - viewModel.setMap({ map }); + const webmap = { copyLayer: copyLayerSpy }; + viewModel.setMap({ map, webmap }); }); afterEach(() => { @@ -71,6 +74,76 @@ describe('LayerHighlightViewModel', () => { done(); }); + it('map click l7 animate marker', done => { + const setSelectedDatas = jest.fn(); + jest.spyOn(map, 'queryRenderedFeatures').mockImplementation(() => [ + { + type: 'Feature', + properties: { + smpid: 7, + type: '分类5', + _id: 177554 + }, + layer: { paint: {}, l7layer: {}, setSelectedDatas }, + geometry: { + type: 'Point', + coordinates: [122.20916748046851, 31.332525032307764] + } + } + ]); + const viewModel = new LayerHighlightViewModel({ name: uniqueName, style: highlightStyle, layerIds: ['动画点'] }); + const webmap = { copyLayer: copyLayerSpy }; + viewModel.setMap({ map, webmap }); + + viewModel.once('mapselectionchanged', ({ features }) => { + expect(features.length).toBeGreaterThan(0); + expect(copyLayerSpy).toBeCalled() + expect(setSelectedDatas).toBeCalled() + viewModel.removeHighlightLayers(); + expect(setSelectedDatas).toBeCalled() + viewModel.unregisterMapClick(); + done(); + }); + expect(viewModel.featureFields).toBeUndefined(); + const featureFieldsMap = { [mockLayerName]: ['type'] }; + viewModel.setFeatureFieldsMap(featureFieldsMap); + expect(viewModel.highlightOptions.featureFieldsMap).toEqual(featureFieldsMap); + viewModel.map.fire('click', { target: map, point: { x: 10, y: 5 } }); + }); + it('map click ms fill extrusion', done => { + jest.spyOn(map, 'queryRenderedFeatures').mockImplementation(() => [ + { + type: 'Feature', + properties: { + type: '分类5', + _id: 177554 + }, + layer: {id:'3d填充面', type: 'fill-extrusion', paint: {} }, + geometry: { + type: 'Polygon', + coordinates: [[[122.20916748046851, 31.332525032307764]]] + } + } + ]); + const viewModel = new LayerHighlightViewModel({ name: uniqueName, style: highlightStyle, layerIds: ['3d填充面'] }); + const webmap = { copyLayer: copyLayerSpy }; + viewModel.setMap({ map, webmap }); + + viewModel.once('mapselectionchanged', () => { + const layers = map.getStyle().layers; + expect(layers.length).toBe(1); + expect(layers[0].id).toBe(`3d填充面-${uniqueName}-SM-highlighted`); + expect(layers[0].paint).toEqual({"fill-extrusion-color": "#01ffff", "fill-extrusion-opacity": 0.6}); + viewModel.unregisterMapClick(); + done(); + }); + expect(viewModel.featureFields).toBeUndefined(); + const featureFieldsMap = { '3d填充面': ['type'] }; + viewModel.setFeatureFieldsMap(featureFieldsMap); + expect(viewModel.highlightOptions.featureFieldsMap).toEqual(featureFieldsMap); + viewModel.map.fire('click', { target: map, point: { x: 10, y: 5 } }); + }); + it('map click target layer by specified featureFields', done => { viewModel.once('mapselectionchanged', ({ features }) => { expect(features.length).toBeGreaterThan(0); @@ -78,8 +151,8 @@ describe('LayerHighlightViewModel', () => { expect(layers.length).toBe(2); expect(layers[0].id).toBe(`${mockLayerName}-${uniqueName}-SM-highlighted`); expect(layers[1].id).toBe(`${mockLayerName}-${uniqueName}-SM-highlighted-StrokeLine`); - expect(layers[0].filter[0]).toBe('all'); - expect(layers[1].filter[0]).toBe('all'); + expect(layers[0].filter[0]).toBe('any'); + expect(layers[1].filter[0]).toBe('any'); expect(viewModel.highlightOptions.layerIds).toEqual([mockLayerName]); viewModel.removeHighlightLayers(); expect(map.getStyle().layers.length).toBe(0); @@ -170,7 +243,7 @@ describe('LayerHighlightViewModel', () => { expect(keyboardEvents.keyup).not.toBeUndefined(); keyboardEvents.keydown({ ctrlKey: 'Control' }); }); - + it('map click same feature by geometry', done => { viewModel.setTargetLayers(['layer1']); const keyboardEvents = {};