| | |
| | | import { defaultsDeep } from 'lodash-es'; |
| | | import { Map as OpenLayerMap, Overlay, View } from 'ol'; |
| | | import type { Overlay } from 'ol'; |
| | | import { Map as OpenLayerMap, View } from 'ol'; |
| | | import type { Extent } from 'ol/extent'; |
| | | import { getTopLeft, getWidth } from 'ol/extent'; |
| | | import Tile from 'ol/layer/Tile'; |
| | |
| | | import WMTS from 'ol/source/WMTS'; |
| | | import WMTSTileGrid from 'ol/tilegrid/WMTS'; |
| | | import type { ViewOptions } from 'ol/View'; |
| | | import MVT from 'ol/format/MVT.js'; |
| | | import VectorTileLayer from 'ol/layer/VectorTile'; |
| | | import VectorTileSource from 'ol/source/VectorTile'; |
| | | import { defaults as olDefaults } from 'ol/interaction'; |
| | | import type { Ref } from 'vue'; |
| | | import { ref } from 'vue'; |
| | | import { MarkerOverlay } from './overlay/marker'; |
| | | import { TileGrid } from 'ol/tilegrid'; |
| | | import Style from 'ol/style/Style'; |
| | | import Fill from 'ol/style/Fill'; |
| | | import Stroke from 'ol/style/Stroke'; |
| | | import Circle from 'ol/style/Circle'; |
| | | |
| | | export type LangType = 'zh_cn' | 'en'; |
| | | export const enum GaoDeSourceType { |
| | |
| | | /** @description 影像路网 */ |
| | | SatelliteRoad = 3, |
| | | } |
| | | export const enum OverlayType { |
| | | Marker = 'marker', |
| | | } |
| | | export const MARKER_OVERLAY_CLASS_NAME = 'marker-overlay'; |
| | | |
| | | export const GaoDeSourceTypeMap = { |
| | | [GaoDeSourceType.Default]: '默认地图', |
| | | [GaoDeSourceType.Satellite]: '影像地图', |
| | | [GaoDeSourceType.Vector]: '矢量地图', |
| | | [GaoDeSourceType.SatelliteRoad]: '影像路网', |
| | | export const gaoDeSourceTypeMap = { |
| | | // [GaoDeSourceType.Default]: '默认地图', |
| | | [GaoDeSourceType.Vector]: '标准地图', |
| | | |
| | | [GaoDeSourceType.Satellite]: '卫星地图', |
| | | [GaoDeSourceType.SatelliteRoad]: '路网地图', |
| | | }; |
| | | |
| | | export const getGaoDeSourceUrl = (type: GaoDeSourceType, lang: LangType = 'zh_cn') => { |
| | |
| | | }; |
| | | return urlMap[type]; |
| | | }; |
| | | type MapConfig = { |
| | | sourceType: GaoDeSourceType; |
| | | markerIsVisible: boolean; |
| | | }; |
| | | |
| | | type OLEventType = 'blackClick'; |
| | | 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[]>; |
| | | |
| | | activeSourceType: Ref<GaoDeSourceType> = ref(GaoDeSourceType.Vector); |
| | | markerIsVisible: Ref<boolean> = ref(true); |
| | | private emit(eventName: OLEventType, ...args: any[]) { |
| | | const handlers = this.eventMap?.get(eventName); |
| | | if (handlers) { |
| | |
| | | } |
| | | |
| | | constructor(options: OLMapOptions) { |
| | | const { container, view } = defaultsDeep(options, { |
| | | const { container, view, sourceType, markerIsVisible } = 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', |
| | |
| | | target: container, // 绑定 DOM 容器 |
| | | layers: [layer], // 添加图层 |
| | | view: new View(view), |
| | | interactions: olDefaults({ doubleClickZoom: false }), |
| | | }); |
| | | this.setSourceUrl(); |
| | | this.activeSourceType.value = sourceType; |
| | | this.markerIsVisible.value = markerIsVisible; |
| | | this.applySourceType(this.activeSourceType.value); |
| | | this.listenMapClick(); |
| | | this.addBasicControl(); |
| | | } |
| | | |
| | | setSourceUrl(type: GaoDeSourceType = GaoDeSourceType.Vector) { |
| | | 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; |
| | | const markers: Overlay[] = []; |
| | | |
| | | // 创建标记点 |
| | | dataList.forEach((item) => { |
| | | const marker = this.createMarker(item, markerOpt); |
| | | dataList.forEach((item, index) => { |
| | | const marker = this.createMarker(`marker-${index}`, item, markerOpt); |
| | | markers.push(marker); |
| | | this.map.addOverlay(marker); |
| | | }); |
| | | |
| | | // 计算并调整视图范围 |
| | | this.adjustViewToMarkers(dataList); |
| | | this.adjustViewToOverlays(markers); |
| | | } |
| | | |
| | | createEleOverlay(dom: string | HTMLElement, position = [0, 0]) { |
| | | const ele = typeof dom === 'string' ? (document.querySelector(dom) as HTMLElement) : dom; |
| | | if (!ele) return; |
| | | const eleOverlay = new Overlay({ |
| | | const eleOverlay = new MarkerOverlay({ |
| | | element: ele, |
| | | position: position, |
| | | positioning: 'top-left', |
| | | stopEvent: false, |
| | | className: 'z-[999]', |
| | | }); |
| | | // // 由于 setVisible 是受保护的方法,改用 set('visible', true) 来设置可见性 |
| | | // eleOverlay.set('visible', true); |
| | | eleOverlay.setVisible(this.markerIsVisible.value); |
| | | |
| | | return eleOverlay; |
| | | } |
| | | |
| | | private createMarker(item: any, markerOpt: any): Overlay { |
| | | private createMarker(id: string, item: any, markerOpt: any): Overlay { |
| | | // 创建图片元素 |
| | | const markerImg = document.createElement('img'); |
| | | markerImg.src = markerOpt.icon.url; |
| | | markerImg.style.width = `${markerOpt.icon.size}px`; |
| | | markerImg.style.height = `${markerOpt.icon.size}px`; |
| | | markerImg.style.cursor = 'pointer'; |
| | | markerImg.style.userSelect = 'none'; |
| | | |
| | | const position = fromLonLat(item.position); |
| | | |
| | | // 创建 Overlay |
| | | const overlay = new Overlay({ |
| | | const overlay = new MarkerOverlay({ |
| | | id, |
| | | className: MARKER_OVERLAY_CLASS_NAME, |
| | | element: markerImg, |
| | | position: position, |
| | | positioning: 'center-center', |
| | |
| | | }); |
| | | |
| | | overlay.set('extData', item.extData); |
| | | overlay.set('type', OverlayType.Marker); |
| | | // 添加点击事件 |
| | | markerImg.addEventListener('click', (event) => { |
| | | if (markerOpt.icon.selectUrl) { |
| | | markerImg.src = markerOpt.icon.selectUrl; |
| | | } |
| | | markerOpt.click?.(event, overlay, item, position); |
| | | markerOpt.click?.(event, overlay, item.extData, position); |
| | | }); |
| | | |
| | | return overlay; |
| | | } |
| | | |
| | | private adjustViewToMarkers(dataList: any[]) { |
| | | // 计算所有点的范围 |
| | | const extent = dataList.reduce((ext, item) => { |
| | | const coord = fromLonLat(item.position); |
| | | 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) => { |
| | | const coord = item.getPosition(); |
| | | |
| | | if (!ext) { |
| | | return [coord[0], coord[1], coord[0], coord[1]]; |
| | | } |
| | | return [Math.min(ext[0], coord[0]), Math.min(ext[1], coord[1]), Math.max(ext[2], coord[0]), Math.max(ext[3], coord[1])]; |
| | | }, null); |
| | | |
| | | // 调整视图以适应标记点范围 |
| | | if (extent) { |
| | | this.fitExtend(extent); |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | toggleMarkerOverlayVisible(visible: boolean) { |
| | | const overlays = this.map.getOverlays(); |
| | | overlays.forEach((overlay) => { |
| | | if (overlay instanceof MarkerOverlay) { |
| | | const overlayElement = overlay.getElement(); |
| | | if (overlayElement) { |
| | | overlayElement.style.visibility = visible ? 'visible' : 'hidden'; |
| | | } |
| | | } |
| | | }); |
| | | 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.applySourceType(this.activeSourceType.value); |
| | | this.toggleMarkerOverlayVisible(this.markerIsVisible.value); |
| | | } |
| | | |
| | | private addBasicControl() { |
| | | // 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; |
| | | } |
| | | |
| | | getWMTS = () => { |
| | | const projection = getProjection('EPSG:3857'); |
| | | const projectionExtent = projection.getExtent(); |