From e792d4d46e406da8d0a4ae8ad97db34a95f1c66a Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期一, 10 二月 2025 17:31:08 +0800 Subject: [PATCH] 高亮问题 --- src/model/map/OLMap.ts | 551 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 521 insertions(+), 30 deletions(-) diff --git a/src/model/map/OLMap.ts b/src/model/map/OLMap.ts index 2385085..db0bbea 100644 --- a/src/model/map/OLMap.ts +++ b/src/model/map/OLMap.ts @@ -1,19 +1,34 @@ import { defaultsDeep } from 'lodash-es'; -import type { Overlay } from 'ol'; +import type { Feature, Overlay } from 'ol'; import { Map as OpenLayerMap, View } from 'ol'; -import { ZoomSlider } from 'ol/control'; import type { Extent } from 'ol/extent'; import { getTopLeft, getWidth } from 'ol/extent'; +import type { FeatureLike } from 'ol/Feature'; +import MVT from 'ol/format/MVT.js'; +import type Geometry from 'ol/geom/Geometry'; +import { defaults as olDefaults } from 'ol/interaction'; +import type Layer from 'ol/layer/Layer'; import Tile from 'ol/layer/Tile'; +import VectorLayer from 'ol/layer/Vector'; +import VectorTileLayer from 'ol/layer/VectorTile'; import { fromLonLat, get as getProjection } from 'ol/proj'; +import type LayerRenderer from 'ol/renderer/Layer'; +import type { Source } from 'ol/source'; import { XYZ } from 'ol/source'; +import VectorSource from 'ol/source/Vector'; +import VectorTileSource from 'ol/source/VectorTile'; import WMTS from 'ol/source/WMTS'; +import { Circle, Fill, Stroke, Style } from 'ol/style'; import WMTSTileGrid from 'ol/tilegrid/WMTS'; import type { ViewOptions } from 'ol/View'; - -import type { Ref } from 'vue'; -import { ref } from 'vue'; +import type { Ref, ShallowRef } from 'vue'; +import { markRaw, ref } from 'vue'; import { MarkerOverlay } from './overlay/marker'; +import { Text } from 'ol/style'; +import { Select } from 'ol/interaction'; +import { click } from 'ol/events/condition'; +import { Polygon } from 'ol/geom'; + export type LangType = 'zh_cn' | 'en'; export const enum GaoDeSourceType { /** @description 榛樿鍦板浘 */ @@ -31,10 +46,11 @@ export const MARKER_OVERLAY_CLASS_NAME = 'marker-overlay'; export const gaoDeSourceTypeMap = { - [GaoDeSourceType.Default]: '榛樿鍦板浘', - [GaoDeSourceType.Satellite]: '褰卞儚鍦板浘', - [GaoDeSourceType.Vector]: '鐭㈤噺鍦板浘', - [GaoDeSourceType.SatelliteRoad]: '褰卞儚璺綉', + // [GaoDeSourceType.Default]: '榛樿鍦板浘', + [GaoDeSourceType.Vector]: '鏍囧噯鍦板浘', + + [GaoDeSourceType.Satellite]: '鍗槦鍦板浘', + [GaoDeSourceType.SatelliteRoad]: '璺綉鍦板浘', }; export const getGaoDeSourceUrl = (type: GaoDeSourceType, lang: LangType = 'zh_cn') => { @@ -48,21 +64,39 @@ }; type MapConfig = { sourceType: GaoDeSourceType; - markerIsVisible: boolean; }; -type OLEventType = 'blackClick'; +type OLEventType = 'blackClick' | 'featureChange' | 'featureHoverChange'; type OLMapOptions = { container: HTMLDivElement; view?: ViewOptions; } & MapConfig; + +const resolutions = []; +for (let i = 0; i <= 8; ++i) { + resolutions.push(156543.03392804097 / Math.pow(2, i * 2)); +} export class OLMap { map: OpenLayerMap; source: XYZ; private eventMap: Map<OLEventType, Function[]>; + /** @description 鎺掗櫎鏈仛鐨刲ayer锛屽悗缁叏閮ㄥ仛濂斤紝鍙互涓嶅仛鍒ゆ柇 */ + unsupportedLayers = []; + + /** @description 鍥惧眰鎺у埗淇℃伅 */ + layerInfo = ref([] as any[]); + + drawStyles = ref([] as any[]); + + /** @description 涓婚淇℃伅 */ + themeInfo = ref([] as any[]); + activeSourceType: Ref<GaoDeSourceType> = ref(GaoDeSourceType.Vector); - markerIsVisible: Ref<boolean> = ref(true); + interactLayer: VectorLayer<VectorSource<Feature<Geometry>>, Feature<Geometry>> = null; + + /** @description 褰撳墠婵�娲荤姸鎬� feature */ + activeFeature: ShallowRef<FeatureLike> = ref(null); private emit(eventName: OLEventType, ...args: any[]) { const handlers = this.eventMap?.get(eventName); if (handlers) { @@ -93,13 +127,12 @@ } constructor(options: OLMapOptions) { - const { container, view, sourceType, markerIsVisible } = defaultsDeep(options, { + const { container, view, sourceType } = defaultsDeep(options, { view: { center: [13247019.404399557, 4721671.572580107], zoom: 8, }, sourceType: GaoDeSourceType.Vector, - markerIsVisible: true, } as OLMapOptions) as OLMapOptions; this.source = new XYZ({ crossOrigin: 'anonymous', @@ -117,17 +150,236 @@ target: container, // 缁戝畾 DOM 瀹瑰櫒 layers: [layer], // 娣诲姞鍥惧眰 view: new View(view), + interactions: olDefaults({ doubleClickZoom: false }), }); this.activeSourceType.value = sourceType; - this.markerIsVisible.value = markerIsVisible; - this.setSourceUrl(this.activeSourceType.value); + this.applySourceType(this.activeSourceType.value); this.listenMapClick(); this.addBasicControl(); } - setSourceUrl(type: GaoDeSourceType = GaoDeSourceType.Vector) { + hightLightColor: '0000FFFF'; + sizeRate: 0; + + getPointHighLightStyle(feature) { + if (!feature) return null; + let pSize = feature.get('psize') ?? 15; + pSize += pSize * this.sizeRate; + + const pcolor = this.hightLightColor; + const pstyle = feature.get('pstyle'); + + return this.getPointStyles({ color: pcolor, size: pSize, style: pstyle }); + } + + getLineHighLightStyle(feature) { + if (!feature) return null; + + const lcolor = this.hightLightColor; + let lsize = feature.get('lsize') ?? 15; + lsize += lsize * this.sizeRate; + const lstyle = feature.get('lstyle'); + return this.getLineStyles({ lsize: lsize, lcolor: lcolor, lstyle: lstyle }); + } + + getPolygonHighLightStyle(feature) { + if (!feature) return null; + + const lcolor = this.hightLightColor; + // return this.getPolygonStyles({ pcolor: lcolor, lstyle: 'default', lsize: 2 }); + return new Style({ + stroke: new Stroke({ + color: lcolor, + width: 5, + }), + // fill: new Fill({ + // color: lcolor, + // }), + }); + } + + // 淇濆瓨鐐规牱寮忕紦瀛� + pointStyeMap: Record<string, Style> = {}; + + /** + * 鏍规嵁鐐圭殑閰嶇疆鑾峰彇鏍峰紡 + * @param config + * @returns + */ + getPointStyles(config: { size; color; style }) { + const pSize = config.size; + + const pcolor = config.color; + const pstyle = config.style; + + const pkey = `${pstyle}_${pSize}_${pcolor}`; + + if (!this.pointStyeMap[pkey]) { + const drawStyle = this.drawStyles.value.find((item) => item.id === pstyle + ''); + const drawStyleType = drawStyle?.type; + const config = drawStyle?.config; + + const drawStyleConfigType = drawStyle?.config?.type; + if (drawStyleType === 'POINT') { + if (drawStyleConfigType === 'circle') { + this.pointStyeMap[pkey] = new Style({ + image: new Circle({ + radius: pSize, + fill: new Fill({ color: `#${pcolor}` }), + }), + }); + } else if (drawStyleConfigType === 'font') { + this.pointStyeMap[pkey] = new Style({ + text: new Text({ + font: `${pSize}px "ywifont"`, + + text: config?.text ?? '', + fill: new Fill({ color: `#${pcolor}` }), + }), + }); + } else { + this.pointStyeMap[pkey] = new Style({ + image: new Circle({ + radius: pSize, + fill: new Fill({ color: `#${pcolor}` }), + }), + }); + } + } else { + this.pointStyeMap[pkey] = new Style({ + image: new Circle({ + radius: pSize, + fill: new Fill({ color: `#${pcolor}` }), + }), + }); + } + } + return this.pointStyeMap[pkey]; + } + + // 淇濆瓨绾挎牱寮忕紦瀛� + lineStyleMap: Record<string, Style> = {}; + + /** + * 鏍规嵁绾跨殑閰嶇疆鑾峰彇鏍峰紡 + * @param config + */ + getLineStyles(config: { lsize; lcolor; lstyle; psize?; pcolor?; pstyle? }) { + const lSize = config.lsize; + if (!lSize) return null; + const lColor = config.lcolor; + const lstyle = config.lstyle; + const styles = []; + const lKey = `${lstyle}_${lSize}_${lColor}`; + if (!this.lineStyleMap[lKey]) { + const drawStyle = this.drawStyles.value.find((item) => item.id === lstyle + ''); + const drawStyleType = drawStyle?.type; + const config = drawStyle?.config; + this.lineStyleMap[lKey] = new Style({ + // image: new Circle({ + // radius: lsize, + // fill: new Fill({ color: `#${color}` }), + // }), + stroke: new Stroke({ + color: `#${lColor}`, + width: lSize, + }), + }); + } + styles.push(this.lineStyleMap[lKey]); + + const hasPoint = Object.keys(config).some((key) => key === 'psize' && !!config[key]); + if (hasPoint) { + const psize = config.psize; + const pcolor = config.pcolor; + const pstyle = config.pstyle; + const pointStyle = this.getPointStyles({ size: psize, color: pcolor, style: pstyle }); + styles.push(pointStyle); + } + return styles; + } + polygonStyleMap: Record<string, Style> = {}; + getPolygonStyles(config: { pcolor; lstyle; lsize; lcolor }) { + const styles = []; + const pColor = config.pcolor; + const key = pColor; + if (!this.polygonStyleMap[key]) { + const polygonStyle = new Style({ + fill: new Fill({ color: `#${pColor}` }), + }); + styles.push(polygonStyle); + } + + const lineStyle = this.getLineStyles({ + lsize: config.lsize, + lcolor: config.lcolor, + lstyle: config.lstyle, + }); + lineStyle && styles.push(...lineStyle); + return styles; + } + + private maxTextResolution = 1; + getText(textContent, resolution) { + let text = `${textContent}`; + if (resolution > this.maxTextResolution) { + text = ''; + } + + // else if (type == 'hide') { + // text = ''; + // } else if (type == 'shorten') { + // text = text.trunc(12); + // } else if (type == 'wrap' && (!dom.placement || dom.placement.value != 'line')) { + // text = stringDivider(text, 16, '\n'); + // } + + return text; + } + + createTextStyle(textContent, resolution) { + return new Style({ + text: new Text({ + text: this.getText(textContent, resolution), + font: '14px Arial', + fill: new Fill({ color: '#000' }), + stroke: new Stroke({ color: '#fff', width: 2 }), + offsetX: 10, // 鏂囧瓧鐩稿浜庡嚑浣曚腑蹇冪殑姘村钩鍋忕Щ + offsetY: 20, // 鏂囧瓧鐩稿浜庡嚑浣曚腑蹇冪殑鍨傜洿鍋忕Щ + }), + }); + } + + // 淇濆瓨鏍囩鏍峰紡缂撳瓨 + labelStyleMap: Record<string, Style> = {}; + + /** + * 鏍规嵁鏍囩鐨勯厤缃幏鍙栨牱寮� + * @param config + */ + getLabelStyles(config: { textContent; resolution }) { + const textContent = config.textContent; + if (!textContent) return null; + const resolution = config.resolution; + if (!textContent) return null; + if (!this.labelStyleMap[textContent]) { + this.labelStyleMap[textContent] = this.createTextStyle(textContent, resolution); + } + return this.labelStyleMap[textContent]; + } + + setDrawStyles(styles: any[]) { + this.drawStyles.value = styles; + } + + applySourceType(type: GaoDeSourceType = GaoDeSourceType.Vector) { const url = getGaoDeSourceUrl(type); this.source.setUrl(url); + } + + setSourceType(type: GaoDeSourceType = GaoDeSourceType.Vector) { + this.activeSourceType.value = type; + this.applySourceType(type); } addMarkerLayer(dataList: any[], options: any) { const { markerOpt } = options; @@ -144,6 +396,22 @@ this.adjustViewToOverlays(markers); } + checkEquipIsShow() { + for (const item of this.layerInfo.value) { + if (item.id === 'equip') { + return item.isVisible; + } + } + return false; + } + + getEquipOverlay() { + for (const item of this.layerInfo.value) { + if (item.type === 'equip') { + return item; + } + } + } createEleOverlay(dom: string | HTMLElement, position = [0, 0]) { const ele = typeof dom === 'string' ? (document.querySelector(dom) as HTMLElement) : dom; if (!ele) return; @@ -154,7 +422,7 @@ stopEvent: false, className: 'z-[999]', }); - eleOverlay.setVisible(this.markerIsVisible.value); + eleOverlay.setVisible(this.checkEquipIsShow()); return eleOverlay; } @@ -193,6 +461,16 @@ return overlay; } + adjustViewToMarkers() { + const overlays = this.map.getOverlays().getArray(); + + const filteredOverlays = overlays.filter((overlay) => { + const type = overlay.get('type'); + return type === OverlayType.Marker; + }); + this.adjustViewToOverlays(filteredOverlays); + } + adjustViewToOverlays(overlays: Overlay[]) { if (overlays.length === 0) return; const extent = overlays.reduce<number[] | null>((ext, item) => { @@ -217,16 +495,129 @@ }); } /** - * 鐩戝惉鍦板浘绌虹櫧鍖哄煙鐐瑰嚮 + * 鐩戝惉鍦板浘鐐瑰嚮 */ listenMapClick() { this.map.on('click', (event) => { - const feature = this.map.forEachFeatureAtPixel(event.pixel, (feature) => feature); - - if (!feature) { - // overlay 鏃犳硶鍒ゆ柇 - this.emit('blackClick', event); + // const features = this.map.getFeaturesAtPixel(event.pixel); + const features = []; + const layers: Layer<Source, LayerRenderer<any>>[] = []; + this.map.forEachFeatureAtPixel(event.pixel, (feature, layer) => { + features.push(feature); + layers.push(layer); + }); + const feature = features[0]; + const layer = layers[0]; + if (feature !== this.activeFeature.value) { + this.emit('featureChange', feature, layer); } + if (feature) { + console.log('馃殌 ~ feature:', feature); + const otype = feature.get('otype'); + console.log('馃殌 ~ otype:', otype); + const oname = feature.get('oname'); + console.log('馃殌 ~ oname:', oname); + const geometryType = feature.getGeometry().getType(); + console.log('馃殌 ~ geometryType:', geometryType); + const properties = feature.getProperties(); + console.log('馃殌 ~ properties:', properties); + } + this.displayFeatureInfo(feature); + this.activeFeature.value = feature; + }); + } + + setAllThemes(themeInfo) { + this.themeInfo.value = themeInfo; + } + + getAllLayers() { + const allLayers = this.layerInfo.value.reduce((preVal, curVal) => { + if (curVal.children && curVal.children.length > 0) { + return preVal.concat(curVal.children.map((item) => item)); + } else { + return preVal; + } + }, []); + return allLayers; + } + + getAllLayerModels() { + console.log('馃殌 ~ this.layerInfo.value:', this.layerInfo.value); + return this.layerInfo.value.reduce((preVal, curVal) => { + if (curVal.children && curVal.children.length > 0) { + return preVal.concat(curVal.children.map((item) => item.model)); + } else { + return preVal; + } + }, []); + } + + /** @description 璁板綍鎵�鏈夊浘灞� */ + setAllLayers(layerModels: Layer[], layers: any[], layerGroup: any[]) { + this.layerInfo.value = layerGroup.reduce((preVal, curVal) => { + const group = curVal.group; + const groupId = `group-${group}`; + let mapGroupItem = preVal.find((item) => item.id === groupId); + if (!mapGroupItem) { + mapGroupItem = { id: groupId, title: group, type: 'layer-group' }; + preVal.push(mapGroupItem); + } + if (!mapGroupItem.children) { + mapGroupItem.children = []; + } + const layerId = curVal.layer_id; + const foundIndex = layers.findIndex((item) => item.id === layerId); + if (foundIndex === -1) { + return preVal; + } + const layer = layerModels[foundIndex]; + const layerData = layers[foundIndex]; + const data = { + id: layerData.id, + title: layerData.title, + model: markRaw(layer), + get isVisible() { + return layer.getVisible(); + }, + set isVisible(val) { + layer.setVisible(val); + }, + type: 'layer', + }; + mapGroupItem.children.push(data); + return preVal; + }, []); + const that = this; + this.layerInfo.value.push({ + id: 'equip', + title: '鐩戞祴璁惧', + children: [], + _isVisible: true, + get isVisible() { + return this._isVisible; + }, + set isVisible(val) { + this._isVisible = val; + that.toggleMarkerOverlayVisible(val); + }, + type: 'equip', + }); + } + + /** + * + * 鐩戝惉鍦板浘瑕佺礌hover + */ + listenMapFeatureHover() { + let activeHover: FeatureLike = null; + this.map.on('pointermove', (event) => { + const features = this.map.getFeaturesAtPixel(event.pixel); + const feature = features[0]; + if (feature !== activeHover) { + this.emit('featureHoverChange', feature); + } + activeHover = feature; }); } @@ -240,26 +631,126 @@ } } }); - this.markerIsVisible.value = visible; } getConfig(): MapConfig { return { sourceType: this.activeSourceType.value, - markerIsVisible: this.markerIsVisible.value, }; } setConfig(config: MapConfig) { this.activeSourceType.value = config.sourceType; - this.markerIsVisible.value = config.markerIsVisible; - this.setSourceUrl(this.activeSourceType.value); - this.toggleMarkerOverlayVisible(this.markerIsVisible.value); + this.applySourceType(this.activeSourceType.value); } private addBasicControl() { - this.map.addControl(new ZoomSlider()); + // this.map.addControl(new ZoomSlider()); // this.map.addControl(new FullScreen()); + const container = this.map.getViewport(); + if (!container) return; + const olZoom = container.querySelector('.ol-zoom') as HTMLElement; + if (!olZoom) return; + olZoom.style.display = 'none'; + } + + /** + * 鏀惧ぇ鍦板浘 + */ + zoomIn() { + const view = this.map.getView(); + const zoom = view.getZoom(); + view.setZoom(zoom + 1); + } + + /** + * 缂╁皬鍦板浘 + */ + zoomOut() { + const view = this.map.getView(); + const zoom = view.getZoom(); + view.setZoom(zoom - 1); + } + + private tileUrlFunction(url) { + return (tileCoord) => + url + .replace('{z}', String(tileCoord[0] * 2 - 1)) + .replace('{x}', String(tileCoord[1])) + .replace('{y}', String(tileCoord[2])) + .replace('{a-d}', 'abcd'.substr(((tileCoord[1] << tileCoord[0]) + tileCoord[2]) % 4, 1)); + } + + addCustomLayer(url: string, style?: any) { + const vectorTileExtent = [13270414.528705932, 2994644.904997596, 13295641.139349712, 3018305.0256410106]; + + const vectorTileLayer = new VectorTileLayer({ + source: new VectorTileSource({ + format: new MVT(), + url: url, + }), + extent: vectorTileExtent, + style: style, + }); + this.map.addLayer(vectorTileLayer); + return vectorTileLayer; + } + + // highlightFeature = null; + displayFeatureInfo(feature) { + const highlightStyle = { + Point: new Style({ + // image: new Circle({ + // radius: 5, + // fill: new Fill({ color: `blue` }), + // }), + image: new Circle({ + radius: 13, + stroke: new Stroke({ + color: `blue`, + width: 3, + }), + }), + }), + LineString: new Style({ + stroke: new Stroke({ + color: `blue`, + width: 5, + }), + }), + Polygon: new Style({ + stroke: new Stroke({ + color: `blue`, + width: 5, + }), + // fill: new Fill({ + // color: `rgba(0, 0, 255, 0.1)`, + // }), + }), + }; + // 鍒涘缓楂樹寒鍥惧眰 + if (!this.interactLayer) { + this.interactLayer = new VectorLayer({ + source: new VectorSource(), + map: this.map, + style: (feature) => { + const type = feature.getGeometry().getType(); + return highlightStyle[type]; + }, + }); + } + + // 璁剧疆楂樹寒瑕佺礌 + if (feature !== this.activeFeature.value) { + if (this.activeFeature.value) { + console.log('remove feature:', this.activeFeature.value); + this.interactLayer.getSource().removeFeature(this.activeFeature.value as any); + } + if (feature) { + this.interactLayer.getSource().addFeature(feature); + } + // this.highlightFeature = feature; + } } getWMTS = () => { -- Gitblit v1.9.3