vue3 openLayers渲染坐标点集合,点击点显示对应信息弹框

74 阅读2分钟
  • 展示

image.png

  • 封装组件
<template>
	<!-- 使用 OpenLayersOverlay 弹窗 -->
	<div ref="popupEl" class="ol_log_popup_container">
		<a class="popup-closer" @click="closePopup">×</a>
		<div class="popup-content">
			<slot name="popup" :feature="currentFeature">
				<p>{{ dialogInfo.file_name }}</p>
			</slot>
		</div>
	</div>
</template>

<script setup>
import { ref, reactive, toRefs, onMounted, onUnmounted, watch,nextTick } from 'vue';
import { Map, View, Overlay } from 'ol';
import OSM from 'ol/source/OSM';
import TileLayer from 'ol/layer/Tile';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { Point } from 'ol/geom';
import { fromLonLat } from 'ol/proj';
import Feature from 'ol/Feature.js';
import { Style, Circle, Fill, Stroke } from 'ol/style';

import { storeToRefs } from 'pinia';
import { useUavInfoStore } from '@/stores/uavInfo';
const uavInfo = useUavInfoStore();
const { logInfoList } = storeToRefs(uavInfo);

const state = reactive({
	dialogInfo: {},
	layers: [], // 图层
});
const { dialogInfo, layers } = toRefs(state);
// DOM 引用
const popupEl = ref(null);
const map = ref(null);
const overlay = ref(null);
const vectorSource = ref(new VectorSource());

// 当前选中要素
const currentFeature = ref(null);
// 在组件挂载时创建一次性监听
let clickHandler = null;
// 初始化
const initDot = () => {
	map.value = window.viewer2D;
	layers.value = new VectorLayer({
		source: vectorSource.value,
		style: new Style({
			image: new Circle({
				radius: 10,
				fill: new Fill({ color: 'red' }),
				stroke: new Stroke({ color: 'white', width: 2 }),
			}),
		}),
        zIndex: 1000,
	});
	map.value.addLayer(layers.value);
	// 初始化 Overlay
	overlay.value = new Overlay({
		element: popupEl.value,
		autoPan: true,
		positioning: 'center-center', // 核心定位配置
		offset: [0, 0], // 微调偏移量
		autoPanAnimation: {
			duration: 250,
		},
	});
	map.value.addOverlay(overlay.value);
	// 绑定点击事件
	clickHandler = map.value?.addEventListener('click', handleMapClick);
};

// 处理地图点击
const handleMapClick = (evt) => {
	const feature = map.value.forEachFeatureAtPixel(evt.pixel, (f) => f);

	if (feature && feature.values_?.info?.featureType == 'dotModule') {
		currentFeature.value = feature;
		console.log(feature.getGeometry().getCoordinates(), 'setPosition');
		overlay.value.setPosition(feature.getGeometry().getCoordinates());
		console.log(currentFeature.value.values_.info, 'feature');
		dialogInfo.value = feature.values_?.info;
	} else {
		overlay.value.setPosition(undefined);
		dialogInfo.value = {};
	}
};

// 关闭弹窗
const closePopup = () => {
	overlay.value.setPosition(undefined);
	currentFeature.value = null;
};

// 更新要素
const updateFeatures = () => {
    	// 先移除旧监听
	if (clickHandler) {
		console.log('先移除旧监听');
		unByKey(clickHandler);
	}
    // 清除要素
	vectorSource.value.clear();
	logInfoList.value.forEach((point) => {
		let temp = [point?.metadata?.shootPosition?.lng, point?.metadata?.shootPosition?.lat];
		const feature = new Feature({
			geometry: new Point(temp),
			info: { ...point, featureType: 'dotModule' },
		});
		vectorSource.value.addFeature(feature);
	});
};

// 生命周期
onMounted(() => {
	nextTick(() => {
		initDot();
	});
});
onUnmounted(() => {
	closePopup();
	vectorSource.value.clear();
	dialogInfo.value = {};
});
watch(
	logInfoList,
	(val) => {
		if (val.length > 0) {
			updateFeatures();
		} else {
			closePopup();
			vectorSource.value.clear();
			dialogInfo.value = {};
		}
	},
	{ deep: true }
);
</script>

<style>
.ol_log_popup_container {
	position: absolute;
    background: linear-gradient(133deg, rgba(17, 64, 158, 0.87) 2%, rgba(5, 16, 38, 0.86) 100%);
    color:#fff;
	padding: 15px;
	/* border-radius: 8px; */
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
	min-width: 300px;
	/* 居中定位修正 */
	transform: translateX(-50%);
	left: 0;
	top: -80px;
}

.popup-closer {
	position: absolute;
	top: 5px;
	right: 8px;
	cursor: pointer;
	color: #666;
    /* background: linear-gradient(133deg, rgba(17, 64, 158, 0.87) 2%, rgba(5, 16, 38, 0.86) 100%);
	border-bottom: 1px solid;
	border-image: linear-gradient(90deg, rgba(31, 111, 250, 0), rgb(16, 97, 255), rgba(31, 111, 250, 0)) 1 1 ; */
}
</style>


  • 使用
<template>

<div class="map_main">
    <div id="mapContainer" ></div>
    <DotModule />
</div>

</template>
import AddDotModule from './component/addDotModule.vue';