import { defaultsDeep } from 'lodash-es';
|
import type { Feature, Overlay } from 'ol';
|
import { Map as OpenLayerMap, View } from 'ol';
|
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, ShallowRef } from 'vue';
|
import { markRaw, ref } from 'vue';
|
import { MarkerOverlay } from './overlay/marker';
|
export type LangType = 'zh_cn' | 'en';
|
export const enum GaoDeSourceType {
|
/** @description 默认地图 */
|
Default = 0,
|
/** @description 影像地图 */
|
Satellite = 1,
|
/** @description 矢量地图 */
|
Vector = 2,
|
/** @description 影像路网 */
|
SatelliteRoad = 3,
|
}
|
export const enum OverlayType {
|
Marker = 'marker',
|
}
|
export const MARKER_OVERLAY_CLASS_NAME = 'marker-overlay';
|
|
export const gaoDeSourceTypeMap = {
|
// [GaoDeSourceType.Default]: '默认地图',
|
[GaoDeSourceType.Vector]: '标准地图',
|
|
[GaoDeSourceType.Satellite]: '卫星地图',
|
[GaoDeSourceType.SatelliteRoad]: '路网地图',
|
};
|
|
export const getGaoDeSourceUrl = (type: GaoDeSourceType, lang: LangType = 'zh_cn') => {
|
const urlMap = {
|
[GaoDeSourceType.Satellite]: `http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=${lang}&size=1&scl=1&style=6`,
|
[GaoDeSourceType.Vector]: `http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=${lang}&size=1&scl=1&style=7`,
|
[GaoDeSourceType.SatelliteRoad]: `http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=${lang}&size=1&scl=1&style=8`,
|
[GaoDeSourceType.Default]: `http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=${lang}&size=1&scl=2&style=7`,
|
};
|
return urlMap[type];
|
};
|
type MapConfig = {
|
sourceType: GaoDeSourceType;
|
markerIsVisible: boolean;
|
};
|
|
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 图层控制信息 */
|
layerInfo = 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) {
|
handlers.forEach((handler) => handler(...args));
|
}
|
}
|
|
on(eventName: OLEventType, handler: Function) {
|
if (!this.eventMap) {
|
this.eventMap = new Map();
|
}
|
|
if (!this.eventMap.has(eventName)) {
|
this.eventMap.set(eventName, []);
|
}
|
|
this.eventMap.get(eventName).push(handler);
|
}
|
|
off(eventName: OLEventType, handler: Function) {
|
const handlers = this.eventMap.get(eventName);
|
if (handlers) {
|
const index = handlers.indexOf(handler);
|
if (index > -1) {
|
handlers.splice(index, 1);
|
}
|
}
|
}
|
|
constructor(options: OLMapOptions) {
|
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',
|
/** @description 高德地图最大支持18级, 超过会显示空白 */
|
maxZoom: 18,
|
/** @description 高德地图最小支持3级, 小于3级会显示空白 */
|
minZoom: 3,
|
});
|
|
// this.source = this.getWMTS();
|
const layer = new Tile({
|
source: this.source,
|
});
|
this.map = new OpenLayerMap({
|
target: container, // 绑定 DOM 容器
|
layers: [layer], // 添加图层
|
view: new View(view),
|
interactions: olDefaults({ doubleClickZoom: false }),
|
});
|
this.activeSourceType.value = sourceType;
|
this.markerIsVisible.value = markerIsVisible;
|
this.applySourceType(this.activeSourceType.value);
|
this.listenMapClick();
|
this.addBasicControl();
|
}
|
|
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, index) => {
|
const marker = this.createMarker(`marker-${index}`, item, markerOpt);
|
markers.push(marker);
|
this.map.addOverlay(marker);
|
});
|
|
// 计算并调整视图范围
|
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 MarkerOverlay({
|
element: ele,
|
position: position,
|
positioning: 'top-left',
|
stopEvent: false,
|
className: 'z-[999]',
|
});
|
eleOverlay.setVisible(this.markerIsVisible.value);
|
|
return eleOverlay;
|
}
|
|
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 MarkerOverlay({
|
id,
|
className: MARKER_OVERLAY_CLASS_NAME,
|
element: markerImg,
|
position: position,
|
positioning: 'center-center',
|
stopEvent: false,
|
});
|
|
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.extData, position);
|
});
|
|
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) => {
|
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);
|
}
|
}
|
|
private fitExtend(extent: Extent) {
|
this.map.getView().fit(extent, {
|
padding: [50, 50, 50, 50], // 设置边距,使标记点不会太靠近地图边缘
|
duration: 0, // 动画持续时间(毫秒)
|
maxZoom: 15, // 限制最大缩放级别,防止单个设备时缩放过大
|
});
|
}
|
/**
|
* 监听地图点击
|
*/
|
listenMapClick() {
|
this.map.on('click', (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.activeFeature.value = feature;
|
// this.displayFeatureInfo(feature);
|
});
|
}
|
|
setAllThemes(themeInfo) {
|
this.themeInfo.value = themeInfo;
|
}
|
|
/** @description 记录所有图层 */
|
setAllLayers(layerModels: Layer[], layers: any[], layerGroup: any[]) {
|
// this.layerInfo.value = layerModels.map((layer, index) => {
|
// const layerData = layers[index];
|
|
// return {
|
// id: layerData.id,
|
// title: layerData.title,
|
// model: markRaw(layer),
|
// get isVisible() {
|
// return layer.getVisible();
|
// },
|
// set isVisible(val) {
|
// layer.setVisible(val);
|
// },
|
// };
|
// });
|
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;
|
}, []);
|
}
|
|
/**
|
*
|
* 监听地图要素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;
|
});
|
}
|
|
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;
|
}
|
|
// highlightFeature = null;
|
displayFeatureInfo(feature) {
|
const highlightStyle = {
|
Point: new Style({
|
image: new Circle({
|
radius: 5,
|
fill: new Fill({ color: `blue` }),
|
}),
|
}),
|
LineString: new Style({
|
stroke: new Stroke({
|
color: `blue`,
|
width: 5,
|
}),
|
}),
|
};
|
// 创建高亮图层
|
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) {
|
this.interactLayer.getSource().removeFeature(this.activeFeature.value as any);
|
}
|
if (feature) {
|
this.interactLayer.getSource().addFeature(feature);
|
}
|
// this.highlightFeature = feature;
|
}
|
}
|
|
getWMTS = () => {
|
const projection = getProjection('EPSG:3857');
|
const projectionExtent = projection.getExtent();
|
const size = getWidth(projectionExtent) / 256;
|
const resolutions = new Array(19);
|
const matrixIds = new Array(19);
|
for (let z = 0; z < 19; ++z) {
|
const pow = Math.pow(2, z);
|
resolutions[z] = size / pow;
|
matrixIds[z] = z;
|
}
|
return new WMTS({
|
url: 'https://wmts-service.pre-fc.alibaba-inc.com/amap/service/wmts', //WMTS 服务的 url 地址
|
layer: 'map:shanghai',
|
matrixSet: 'GoogleMapsCompatible',
|
format: 'image/png',
|
projection: projection,
|
tileGrid: new WMTSTileGrid({
|
origin: getTopLeft(projectionExtent),
|
resolutions: resolutions,
|
matrixIds: matrixIds,
|
}),
|
style: 'default',
|
wrapX: true,
|
});
|
};
|
}
|