🖼️ UI效果图参考
👇 演示图如下:
🎯 效果:
单个要素展示图文信息(名称、地址、头像)
多个重叠要素显示名称列表
点击空白处清除弹窗
🎯 前言
在地图应用中,用户点击地图时,如果该位置有多个图层的要素重叠,理想体验是能获取 所有要素的信息,而不是只获取最顶层的一个。
本文将带你使用 Vue3 + OpenLayers
实现这个功能 👇
📦 项目环境说明
-
Vue 版本:Vue 3 +
<script setup>
-
地图引擎:OpenLayers v7.x+
-
样式:Tailwind CSS、Element Plus(用于按钮)
-
坐标系:EPSG:4326
🧩 实现思路概览
-
使用
VectorLayer
添加多个 Feature(Polygon) -
每个 Feature 携带自定义信息(如公司名、地址、头像)
-
绑定地图
click
事件 -
使用
getFeaturesAtPixel()
获取所有命中的要素 -
展示弹窗(单个展示图文,多个展示名称列表)
📋 完整组件代码
可封装为
PolygonClickMap.vue
,支持复制粘贴到项目中
<!--
* @Author: 彭麒
* @Date: 2025/5/21
* @Email: 1062470959@qq.com
* @Description: 此源码版权归吉檀迦俐所有,可供学习和借鉴或商用。
-->
<template>
<div class="container">
<div class="w-full flex justify-center flex-wrap">
<div class="font-bold text-[24px]">在Vue3中使用OpenLayers单击某点,获取多层重叠位置下的所有features信息</div>
</div>
<h4>
<el-button type="primary" size="small" @click="showPolygon(city1)">显示多边形1</el-button>
<el-button type="primary" size="small" @click="showPolygon(city2)">显示多边形2</el-button>
<el-button type="primary" size="small" @click="clearLayer">清除图层</el-button>
</h4>
<div id="vue-openlayers"></div>
<div id="popup-box" class="ol-popup">
<div id="popup-content" v-if="isshow === 1">
<div class="left"><img :src="cimgurl" /></div>
<div class="right">
<div class="name">{{ cname }}</div>
<div class="address">{{ caddress }}</div>
</div>
</div>
<div id="popup-content2" v-if="isshow === 2">
<div v-for="(item, index) in list" :key="index">{{ item }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import 'ol/ol.css'
import { Map, View } from 'ol'
import TileLayer from 'ol/layer/Tile'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import OSM from 'ol/source/OSM'
import Feature from 'ol/Feature'
import { Polygon } from 'ol/geom'
import Style from 'ol/style/Style'
import Fill from 'ol/style/Fill'
import Stroke from 'ol/style/Stroke'
import Overlay from 'ol/Overlay'
// refs 和响应式变量
const map = ref(null)
const dataSource = new VectorSource({ wrapX: false })
const list = ref([])
const isshow = ref(0)
const cimgurl = ref('')
const cname = ref('')
const caddress = ref('')
let overlayer = null
import person1 from '@/assets/OpenLayers/person1.jpg';
import person2 from '@/assets/OpenLayers/person2.jpg';
// 多边形数据
const city1 = {
name: '吉檀迦俐股份公司',
address: '大连市XX豪车路48号',
polygonData: [
[
[126.86864, 24.99068],
[126.55518, 16.2742],
[142.85507, 16.49975],
[142.93343, 24.84854],
[126.86864, 24.99068]
]
],
imgurl: person1
}
const city2 = {
name: 'PQ航空科技集团',
address: '北京市XXX科技路96号',
polygonData: [
[
[121.6182, 24.56377],
[121.53983, 15.89772],
[136.89934, 15.82234],
[136.66425, 24.27836],
[121.6182, 24.56377]
]
],
imgurl: person2
}
// 样式函数
function featureStyle() {
return new Style({
fill: new Fill({ color: 'rgba(0,0,0,0.1)' }),
stroke: new Stroke({ width: 1, color: 'blue' })
})
}
// 清除图层
function clearLayer() {
dataSource.clear()
}
// 显示多边形
function showPolygon(data) {
const polygonFeature = new Feature({
geometry: new Polygon(data.polygonData),
infoData: data
})
dataSource.addFeature(polygonFeature)
}
// 地图点击事件处理
function clickPoint() {
const box = document.getElementById('popup-box')
overlayer = new Overlay({
element: box,
autoPan: false
})
map.value.addOverlay(overlayer)
map.value.on('click', (evt) => {
if (evt.dragging) return
const pixel = map.value.getEventPixel(evt.originalEvent)
// 添加一个参数,确保检查所有图层
const features = map.value.getFeaturesAtPixel(pixel, {
layerFilter: () => true,
hitTolerance: 5
})
console.log('Features found:', features)
if (features && features.length === 1) {
const info = features[0].get('infoData')
if (info) { // 确保info存在
cname.value = info.name
cimgurl.value = info.imgurl
caddress.value = info.address
isshow.value = 1
overlayer.setPosition(evt.coordinate)
console.log('Single feature popup set at', evt.coordinate)
}
} else if (features && features.length > 1) {
list.value = features.map((f) => f.get('infoData')?.name).filter(Boolean)
isshow.value = 2
overlayer.setPosition(evt.coordinate)
console.log('Multiple features popup set at', evt.coordinate)
} else {
isshow.value = 0
overlayer.setPosition(undefined)
}
})
}
// 初始化地图
function initMap() {
const OSM_Layer = new TileLayer({ source: new OSM() })
const feature_Layer = new VectorLayer({
source: dataSource,
style: featureStyle()
})
map.value = new Map({
target: 'vue-openlayers',
layers: [OSM_Layer, feature_Layer],
view: new View({
projection: 'EPSG:4326',
center: [121.6182, 24.56377],
zoom: 4
})
})
}
// 生命周期挂载
onMounted(() => {
initMap()
clickPoint()
// 可以默认显示一个多边形用于测试
showPolygon(city1)
})
</script>
<style scoped>
.container {
width: 840px;
height: 590px;
margin: 50px auto;
border: 1px solid #42b983;
}
#vue-openlayers {
width: 800px;
height: 470px;
margin: 0 auto;
border: 1px solid #42b983;
position: relative;
}
.ol-popup {
/* 现有样式 */
display: block !important; /* 强制显示 */
z-index: 1000; /* 确保在地图上层 */
position: absolute;
background-color: rgba(255, 0, 255, 0.8);
padding: 5px;
border-radius: 5px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
color: #ffffff;
min-width: 200px;
}
.ol-popup:after,
.ol-popup:before {
top: 100%;
border: solid transparent;
content: ' ';
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: rgba(255, 0, 255, 0.8);
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
#popup-content {
width: 270px;
height: 80px;
border-radius: 10px;
border: 1px solid #fff;
}
#popup-content .left {
width: 80px;
height: 80px;
float: left;
}
#popup-content .left img {
width: 80px;
height: 80px;
}
#popup-content .right {
width: 170px;
height: 80px;
float: right;
text-align: left;
}
#popup-content .right .name {
line-height: 40px;
font-size: 18px;
}
#popup-content .right .address {
line-height: 40px;
font-size: 12px;
}
</style>
🧠 拓展建议
功能方向 | 可行扩展 |
---|---|
支持图层筛选 | 使用 layerFilter 过滤 |
多样式高亮 | 被选中要素高亮边框或图标 |
自定义弹窗 | 使用 Element 弹窗或第三方浮窗组件 |
后端接入 | 点击后获取服务端详情 |
组合封装 | 提取为 useFeatureClick() 组合式函数 |
✅ 总结
本文介绍了如何在 Vue3 + OpenLayers 项目中,实现 点击获取所有重叠位置的 Feature 信息 的功能。无论是城市划分、设备管理还是图层叠加展示,都非常实用。