下面是一个基于 Vue3 + OpenLayers + 天地图 API 的地图组件实现,支持添加多个坐标点和点击弹窗功能。这个组件封装了地图初始化、点位添加和交互逻辑,方便在项目中直接使用。
<template>
<div class="map-container">
<!-- 地图容器 -->
<div id="map" class="map"></div>
<!-- 弹窗组件 -->
<div
v-if="popupVisible"
:style="{
position: 'absolute',
left: popupPosition.x + 'px',
top: popupPosition.y + 'px',
zIndex: 1000
}"
class="popup"
>
<div class="popup-content">
<div class="popup-header">
<span>{{ popupData.name }}</span>
<button class="popup-close" @click="closePopup">×</button>
</div>
<div class="popup-body">
<p>坐标: {{ popupData.lng }}, {{ popupData.lat }}</p>
<p v-if="popupData.desc">{{ popupData.desc }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import Map from 'ol/Map';
import View from 'ol/View';
import Tile from 'ol/layer/Tile';
import { OSM, XYZ } from 'ol/source';
import { fromLonLat } from 'ol/proj';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import { Style, Icon } from 'ol/style';
import { Click } from 'ol/interaction';
// 弹窗状态
const popupVisible = ref(false);
const popupPosition = ref({ x: 0, y: 0 });
const popupData = ref({});
// 地图实例
let map = null;
// 矢量图层
let vectorLayer = null;
// 组件参数 - 可通过props传入
const props = defineProps({
// 中心点坐标 [经度, 纬度]
center: {
type: Array,
default: () => [116.39748, 39.90882] // 默认北京
},
// 缩放级别
zoom: {
type: Number,
default: 12
},
// 坐标点数据
points: {
type: Array,
default: () => [
{ id: 1, name: '点位1', lng: 116.39748, lat: 39.90882, desc: '这是第一个点位的描述信息' },
{ id: 2, name: '点位2', lng: 116.41748, lat: 39.91882, desc: '这是第二个点位的描述信息' },
{ id: 3, name: '点位3', lng: 116.43748, lat: 39.92882 }
]
},
// 天地图密钥 - 请替换为自己的密钥
tdtKey: {
type: String,
default: '您的天地图密钥' // 需自行申请
}
});
// 初始化地图
const initMap = () => {
// 创建天地图图层 - 矢量底图
const tdtVectorLayer = new Tile({
source: new XYZ({
url: `http://t0.tianditu.gov.cn/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={z}&TileRow={y}&TileCol={x}&style=default&format=tiles&tk=${props.tdtKey}`
})
});
// 创建天地图图层 - 矢量注记
const tdtLabelLayer = new Tile({
source: new XYZ({
url: `http://t0.tianditu.gov.cn/cva_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cva&tileMatrixSet=w&TileMatrix={z}&TileRow={y}&TileCol={x}&style=default&format=tiles&tk=${props.tdtKey}`
})
});
// 创建矢量图层用于显示点位
vectorLayer = new VectorLayer({
source: new VectorSource()
});
// 初始化地图
map = new Map({
layers: [tdtVectorLayer, tdtLabelLayer, vectorLayer],
target: 'map',
view: new View({
center: fromLonLat(props.center),
zoom: props.zoom
})
});
// 添加点击交互
addClickInteraction();
// 添加点位
addPointsToMap();
};
// 添加点位到地图
const addPointsToMap = () => {
if (!vectorLayer || !props.points.length) return;
const source = vectorLayer.getSource();
source.clear(); // 清空现有点位
// 遍历添加所有点位
props.points.forEach(point => {
// 创建要素
const feature = new Feature({
geometry: new Point(fromLonLat([point.lng, point.lat])),
data: point // 存储自定义数据
});
// 设置样式 - 使用自定义图标
feature.setStyle(new Style({
image: new Icon({
src: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMjU2IDhDMTE5IDggOCAxMTkgOCAyNTZzMTExIDI0OCAyNDggMjQ4IDI0OC0xMTEgMjQ4LTI0OFMzOTMgOCAyNTYgOHptMCA0MzJoLTMyVjM4NGgtNjR2LTQ4aDY0VjE5MmgyMnYxOTJoNjRWNDQwaC02NHY0OEgyNTZ6bTMyLTEyOEg1NlYzMkg4OFYxOTJINThWMThINTEyVjMyMEgzODRWMzIwem0tMjMyIDBoLTY0VjE5MmgyMnYzMkg4OFYzMjBIMTVWMTkySDU2VjB6Ii8+PC9zdmc+',
anchor: [0.5, 1], // 图标锚点
scale: 0.08 // 图标大小
})
}));
source.addFeature(feature);
});
};
// 添加点击交互
const addClickInteraction = () => {
const click = new Click({
handleSingleClick: true
});
map.addInteraction(click);
// 监听点击事件
click.on('singleclick', (evt) => {
const features = [];
map.forEachFeatureAtPixel(evt.pixel, (feature) => {
features.push(feature);
return true;
});
if (features.length > 0) {
// 显示弹窗
const feature = features[0];
const data = feature.get('data');
showPopup(evt.pixel, data);
} else {
// 点击空白处关闭弹窗
closePopup();
}
});
};
// 显示弹窗
const showPopup = (pixel, data) => {
popupData.value = data;
popupVisible.value = true;
// 计算弹窗位置,避免超出地图边界
const mapEl = document.getElementById('map');
const mapRect = mapEl.getBoundingClientRect();
const popupWidth = 280;
const popupHeight = 150;
// 转换像素坐标为相对于地图容器的坐标
let x = pixel[0];
let y = pixel[1];
// 调整弹窗位置,避免右侧溢出
if (x + popupWidth > mapRect.width) {
x = x - popupWidth;
}
// 调整弹窗位置,避免底部溢出
if (y + popupHeight > mapRect.height) {
y = y - popupHeight;
}
popupPosition.value = { x, y };
};
// 关闭弹窗
const closePopup = () => {
popupVisible.value = false;
};
// 组件挂载时初始化地图
onMounted(() => {
nextTick(() => {
initMap();
});
});
// 组件卸载时销毁地图
onUnmounted(() => {
if (map) {
map.dispose();
}
});
// 监听点位数据变化,重新渲染点位
watch(() => props.points, (newVal) => {
if (newVal && newVal.length) {
addPointsToMap();
}
});
</script>
<style scoped>
.map-container {
position: relative;
width: 100%;
height: 100%;
}
.map {
width: 100%;
height: 100%;
}
.popup {
width: 280px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.popup-content {
padding: 0;
}
.popup-header {
padding: 12px 16px;
background-color: #f5f5f5;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
}
.popup-close {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #666;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.2s;
}
.popup-close:hover {
background-color: #e0e0e0;
}
.popup-body {
padding: 16px;
font-size: 14px;
}
.popup-body p {
margin: 6px 0;
color: #333;
}
</style>
使用说明:
- 首先需要安装必要的依赖:
npm install ol
- 在父组件中使用该地图组件:
<template>
<div class="page-container">
<MapComponent
:center="[116.39748, 39.90882]"
:zoom="13"
:points="mapPoints"
tdtKey="您的天地图密钥"
/>
</div>
</template>
<script setup>
import MapComponent from './MapComponent.vue';
import { ref } from 'vue';
// 定义点位数据
const mapPoints = ref([
{ id: 1, name: '天安门', lng: 116.39748, lat: 39.90882, desc: '北京市中心地标' },
{ id: 2, name: '故宫', lng: 116.39128, lat: 39.91658, desc: '中国明清两代皇家宫殿' },
{ id: 3, name: '颐和园', lng: 116.27557, lat: 39.99909, desc: '著名皇家园林' }
]);
</script>
<style>
.page-container {
width: 100vw;
height: 100vh;
}
</style>
- 天地图密钥申请:
- 访问天地图官网(https://www.tianditu.gov.cn/)
- 注册账号并申请开发者密钥
- 将申请到的密钥替换到组件中的
tdtKey属性
组件特点:
- 封装了完整的地图初始化逻辑,支持自定义中心点和缩放级别
- 使用天地图作为底图,包含矢量图和注记层
- 支持通过 props 动态传入多个坐标点数据
- 点击坐标点显示弹窗,展示点位详细信息
- 弹窗会自动调整位置,避免超出地图边界
- 点击地图空白处会关闭弹窗
- 当点位数据变化时,会自动更新地图上的点位
可以根据实际需求进一步扩展功能,如添加点位拖拽、编辑功能,或者自定义点位图标样式等。
3135

被折叠的 条评论
为什么被折叠?



