<template>
|
<div class="relative bg-white">
|
<div ref="containerRef" class="h-full"></div>
|
<div ref="infoWindowRef" v-show="infoWindowIsShow">
|
<div
|
class="bg-white rounded-md text-nowrap flex flex-col w-fit border border-solid border-blue-600 relative z-[1]"
|
style="padding: 12px"
|
>
|
<span
|
@click="closeInfoWindow"
|
class="guanbi absolute ywifont ywicon-guanbi right-[4px] top-[-2px] font-bold text-[#c3c3c3] cursor-pointer"
|
></span>
|
|
<div class="space-x-4 flex w-full">
|
<div class="flex flex-col w-1/2 key-list">
|
<span
|
v-for="(col, index) in colsArray"
|
:key="index"
|
class="flex-items-center justify-start text-gray-600"
|
style="height: 20px"
|
>{{ col.title ?? '' }}</span
|
>
|
</div>
|
|
<div class="flex flex-col w-1/2 value-list" v-if="infoWindowMapRow">
|
<span
|
v-for="(col, index) in colsArray"
|
:key="index"
|
class="flex-items-center justify-end text-black"
|
style="height: 20px"
|
>{{ infoWindowMapRow[index] ?? '' }}</span
|
>
|
</div>
|
</div>
|
</div>
|
</div>
|
<LayerControl v-if="olMap" :olMap="olMap" class="absolute top-3 left-3 z-10" />
|
|
<!-- <PropertyPanel class="absolute top-14 left-10 z-14"></PropertyPanel> -->
|
<PanelTool
|
v-if="olMap"
|
ref="panelToolRef"
|
:propertyMap="propertyMap"
|
:propertyConfigMap="propertyConfigMap"
|
class="absolute top-0 right-2 z-14 h-full"
|
:olMap="olMap"
|
:isFullscreen="isFullscreen"
|
@toggleFullScreen="toggleFullScreen"
|
></PanelTool>
|
<!-- <el-button class="absolute top-3 right-4" @click="changeTheme" type="primary">切换主题</el-button> -->
|
</div>
|
</template>
|
|
<script setup lang="ts" name="BasicMap">
|
import { reverse } from 'lodash-es';
|
import type { Feature, Overlay } from 'ol';
|
import type { FeatureLike } from 'ol/Feature';
|
import type Layer from 'ol/layer/Layer';
|
import type VectorTileLayer from 'ol/layer/VectorTile';
|
import 'ol/ol.css';
|
import type LayerRenderer from 'ol/renderer/Layer';
|
import type Source from 'ol/source/Source';
|
import { Text } from 'ol/style';
|
import Circle from 'ol/style/Circle';
|
import Fill from 'ol/style/Fill';
|
import Stroke from 'ol/style/Stroke';
|
import Style from 'ol/style/Style';
|
import { computed, onMounted, ref, shallowRef } from 'vue';
|
import equipSelectPic from './img/equip-select.svg';
|
import equipPic from './img/equip.svg';
|
import LayerControl from './LayerControl.vue';
|
import PanelTool from './panelTool/index.vue';
|
import {
|
getMapDrawStyles,
|
getMapLayerGroupList,
|
getMapLayersByPost,
|
getMapThemeList,
|
getMapVpropsByPost,
|
getMapVpropsConfigByPost,
|
switchMapTheme,
|
} from '/@/api/map';
|
import { MAIN_URL } from '/@/constants';
|
import { GaoDeSourceType, OLMap } from '/@/model/map/OLMap';
|
import { MarkerOverlay } from '/@/model/map/overlay/marker';
|
import { useCompRef } from '/@/utils/types';
|
import { formatDate } from '/@/utils/formatTime';
|
import { MapPanelTool } from './types';
|
|
const panelToolRef = useCompRef(PanelTool);
|
|
const props = withDefaults(
|
defineProps<{
|
data: any;
|
config?: {
|
sourceType: GaoDeSourceType;
|
markerIsVisible: boolean;
|
};
|
isFullscreen: boolean;
|
}>(),
|
{
|
config: () => ({
|
sourceType: GaoDeSourceType.Vector,
|
markerIsVisible: true,
|
}),
|
}
|
);
|
|
const colsArray = computed(() => {
|
return props.data.cols ?? [];
|
});
|
const emit = defineEmits(['markerClick', 'closeInfoWindow','toggleFullScreen']);
|
|
const toggleFullScreen = () => {
|
emit('toggleFullScreen');
|
};
|
|
const containerRef = ref<HTMLDivElement>(null);
|
const infoWindowRef = ref<HTMLDivElement>(null);
|
const infoWindowIsShow = ref(false);
|
const infoWindowMapRow = ref(null);
|
const olMap = shallowRef<OLMap>();
|
let infoWindowOverlay: Overlay;
|
let lastOverlay: Overlay;
|
const setMarkerIcon = (overlay: Overlay, icon: string) => {
|
if (!overlay) return;
|
const ele = overlay.getElement() as HTMLImageElement;
|
if (!ele) return;
|
ele.src = icon;
|
};
|
const showInfoWindow = (overlay: Overlay) => {
|
const position = overlay.getPosition();
|
const row = overlay.get('extData')?.value;
|
if (!row) return;
|
lastOverlay && setMarkerIcon(lastOverlay, equipPic);
|
infoWindowIsShow.value = true;
|
infoWindowOverlay.setPosition(position);
|
infoWindowMapRow.value = row;
|
setMarkerIcon(overlay, equipSelectPic);
|
lastOverlay = overlay;
|
emit('markerClick', row);
|
};
|
|
const closeInfoWindow = () => {
|
infoWindowIsShow.value = false;
|
infoWindowMapRow.value = null;
|
setMarkerIcon(lastOverlay, equipPic);
|
emit('closeInfoWindow');
|
};
|
const addMarkerLayer = () => {
|
const map = props.data.map;
|
if (map.pos_x == null && map.pos_y == null) return;
|
const dataList = (props.data?.values ?? []).map((item) => {
|
const x = item[map.pos_x];
|
const y = item[map.pos_y];
|
|
return {
|
position: [x, y],
|
// textColor: item.color,
|
extData: {
|
value: item,
|
recordSetTable: props.data,
|
},
|
};
|
});
|
olMap.value.addMarkerLayer(dataList, {
|
markerOpt: {
|
icon: {
|
url: equipPic,
|
size: 30,
|
selectUrl: equipSelectPic,
|
},
|
click(e, label, extData, position) {
|
showInfoWindow(label);
|
},
|
},
|
layerOpt: {
|
// allowCollision:false
|
},
|
});
|
};
|
|
const hoverStyleMap = {};
|
|
const resetLastActiveStyle = () => {
|
if (!olMap.value.activeFeature.value) return;
|
const originStyle = olMap.value.activeFeature.value?.get('originStyle');
|
|
(olMap.value.activeFeature.value as Feature).setStyle(originStyle);
|
};
|
|
const setActiveStyle = (feature: FeatureLike, layer: Layer<Source, LayerRenderer<any>>) => {
|
resetLastActiveStyle();
|
|
const geometryType = feature.getGeometry().getType();
|
const otype = feature.get('otype');
|
const key = `${geometryType}_${otype}`;
|
const style = hoverStyleMap[key];
|
const source = layer.getSource();
|
|
// const isFeatureLike = feature instanceof FeatureLike;
|
const curStyle = feature?.getStyle();
|
if (curStyle) {
|
feature.set('originStyle', curStyle);
|
}
|
if (style) {
|
feature?.setStyle(style);
|
} else {
|
let style = null;
|
if (geometryType === 'Point') {
|
style = new Style({
|
image: new Circle({
|
radius: 5,
|
fill: new Fill({ color: 'yellow' }),
|
}),
|
});
|
} else if (geometryType === 'LineString') {
|
style = new Style({
|
stroke: new Stroke({
|
color: 'yellow',
|
width: 2,
|
}),
|
});
|
}
|
if (style) {
|
feature?.setStyle(style);
|
hoverStyleMap[key] = style;
|
}
|
}
|
};
|
|
const overlayLabelMap = {};
|
|
const renderFeatureLabel = new Set();
|
|
const setOverlayLabel = (feature: FeatureLike, resolution: any) => {
|
const oname = feature.get('oname');
|
const otype = feature.get('otype');
|
const key = `${oname}_${otype}`;
|
if (resolution > 30) {
|
const overlay = overlayLabelMap[key];
|
if (overlay) {
|
(overlay as MarkerOverlay).setVisible(false);
|
}
|
return;
|
}
|
|
if (overlayLabelMap.hasOwnProperty(key)) {
|
return;
|
}
|
|
const position = (feature as any).getFlatCoordinates();
|
const div = document.createElement('div');
|
|
div.innerHTML = `first-${oname[0]}`;
|
const overlay = new MarkerOverlay({
|
position,
|
element: div,
|
stopEvent: false,
|
});
|
// overlayLabelMap[key] = overlay;
|
// renderFeatureLabel.add(olUid);
|
renderFeatureLabel.add(key);
|
overlayLabelMap[key] = overlay;
|
|
olMap.value.map.addOverlay(overlay);
|
};
|
|
const handleVectorTileClick = (feature, layer) => {
|
panelToolRef.value.featureClick(feature);
|
feature && panelToolRef.value.setActivePanel(MapPanelTool.Property);
|
// setActiveStyle(feature,layer);
|
};
|
const propertyMap = ref({});
|
const propertyConfigMap = ref({});
|
const getPropertyList = async () => {
|
const res = await getMapVpropsByPost();
|
const otypes = res?.otypes ?? {};
|
propertyMap.value = otypes;
|
|
const res1 = await getMapVpropsConfigByPost();
|
propertyConfigMap.value = res1?.values ?? {};
|
};
|
|
const maxResolution = 1;
|
|
const getText = function (textContent, resolution) {
|
let text = `${textContent}`;
|
if (resolution > maxResolution) {
|
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;
|
};
|
const createTextStyle = (textContent, resolution) => {
|
return new Style({
|
text: new Text({
|
text: getText(textContent, resolution),
|
font: '14px Arial',
|
fill: new Fill({ color: '#000' }),
|
stroke: new Stroke({ color: '#fff', width: 2 }),
|
offsetX: 10, // 文字相对于几何中心的水平偏移
|
offsetY: 20, // 文字相对于几何中心的垂直偏移
|
}),
|
});
|
};
|
|
const styleMap: Record<string, Record<string, Style>> = {
|
point: {},
|
line: {},
|
polygon: {},
|
};
|
|
const unsupportedLayers = ['dma1', 'dma2', 'dma3'];
|
const layerStyleFunc = function (feature, resolution) {
|
const shape = feature.getGeometry().getType();
|
const styles = [];
|
switch (shape) {
|
case 'Point':
|
const pSize = feature.get('psize');
|
|
const pcolor = feature.get('pcolor');
|
const pstyle = feature.get('pstyle');
|
const pointStyle = olMap.value.getPointStyles({
|
size: pSize,
|
color: pcolor,
|
style: pstyle,
|
});
|
|
styles.push(pointStyle);
|
break;
|
case 'LineString':
|
const lSize = feature.get('lsize');
|
const lColor = feature.get('lcolor');
|
const lstyle = feature.get('lstyle');
|
const pSize1 = feature.get('psize');
|
const pcolor1 = feature.get('pcolor');
|
const pStyle1 = feature.get('pstyle');
|
const lineStyle = olMap.value.getLineStyles({
|
lsize: lSize,
|
lcolor: lColor,
|
lstyle: lstyle,
|
psize: pSize1,
|
pcolor: pcolor1,
|
pstyle: pStyle1,
|
});
|
styles.push(...lineStyle);
|
break;
|
case 'Polygon':
|
const pColor = feature.get('pcolor');
|
const lSize1 = feature.get('lsize');
|
const lColor1 = feature.get('lcolor');
|
const lstyle1 = feature.get('lstyle');
|
const polygonStyle = olMap.value.getPolygonStyles({
|
pcolor: pColor,
|
lsize: lSize1,
|
lcolor: lColor1,
|
lstyle: lstyle1,
|
});
|
styles.push(...polygonStyle);
|
break;
|
default:
|
break;
|
}
|
|
// switch (otype) {
|
// case 'WDM_JUNCTIONS':
|
// const textStyle = createTextStyle(feature, resolution);
|
// styles.push(textStyle);
|
|
// case 'WDM_PIPES':
|
// break;
|
// }
|
//#region ====================== 添加标注 ======================
|
const tString = feature.get('tstring');
|
|
const tStringStyle = olMap.value.getLabelStyles({
|
textContent: tString,
|
resolution,
|
});
|
if (tStringStyle) {
|
styles.push(tStringStyle);
|
}
|
//#endregion
|
|
return styles;
|
};
|
|
const getGroupList = async () => {
|
const res = await getMapLayerGroupList();
|
const groupList = res?.values ?? [];
|
return groupList;
|
};
|
|
const getDrawStyles = async () => {
|
const res = await getMapDrawStyles();
|
const styleList = res?.values ?? [];
|
// console.log("🚀 ~ styleList:", styleList)
|
olMap.value.setDrawStyles(styleList);
|
};
|
|
const getThemeList = async () => {
|
const res = await getMapThemeList();
|
const themeList = res?.values ?? [];
|
const themeTreeData = themeList.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: 'theme-group' };
|
preVal.push(mapGroupItem);
|
}
|
if (!mapGroupItem.children) {
|
mapGroupItem.children = [];
|
}
|
const id = curVal.id;
|
const data = {
|
id,
|
title: curVal.title,
|
type: 'theme',
|
activeTheme: null,
|
legends: null,
|
};
|
mapGroupItem.children.push(data);
|
return preVal;
|
}, []);
|
olMap.value.setAllThemes(themeTreeData);
|
};
|
const initVectorTileLayer = async () => {
|
await getDrawStyles();
|
const res = await getMapLayersByPost();
|
const layers = reverse(res?.layers ?? []);
|
if (layers.length === 0) return;
|
const layerModels = [];
|
for (const item of layers) {
|
const styleFunc = olMap.value.unsupportedLayers.includes(item.id) ? undefined : layerStyleFunc;
|
const layer = olMap.value.addCustomLayer(`${MAIN_URL}map/get_vector_tile?x={x}&y={y}&z={z}&layer_id=${item.id}`, styleFunc);
|
|
layer.set('originStyle', styleFunc);
|
layerModels.push(layer);
|
}
|
getGroupList().then((groupList) => {
|
olMap.value.setAllLayers(layerModels, layers, groupList);
|
});
|
getThemeList();
|
getPropertyList();
|
olMap.value.on('featureChange', handleVectorTileClick);
|
};
|
const initMap = () => {
|
olMap.value = new OLMap({
|
container: containerRef.value,
|
sourceType: props.config?.sourceType,
|
markerIsVisible: props.config?.markerIsVisible,
|
});
|
|
addMarkerLayer();
|
infoWindowOverlay = olMap.value.createEleOverlay(infoWindowRef.value);
|
olMap.value.map.addOverlay(infoWindowOverlay);
|
initVectorTileLayer();
|
};
|
|
defineExpose({
|
olMap: olMap,
|
});
|
|
onMounted(() => {
|
initMap();
|
// window.olMap = olMap.value;
|
// window.map = olMap.value.map;
|
});
|
</script>
|
<style scoped lang="scss"></style>
|