129.在 Vue3 中使用 OpenLayers 实现点击获取重叠要素信息(支持多 Feature)

🖼️ UI效果图参考

👇 演示图如下:

🎯 效果:

  • 单个要素展示图文信息(名称、地址、头像)

  • 多个重叠要素显示名称列表

  • 点击空白处清除弹窗


🎯 前言

在地图应用中,用户点击地图时,如果该位置有多个图层的要素重叠,理想体验是能获取 所有要素的信息,而不是只获取最顶层的一个。

本文将带你使用 Vue3 + OpenLayers 实现这个功能 👇


📦 项目环境说明

  • Vue 版本:Vue 3 + <script setup>

  • 地图引擎:OpenLayers v7.x+

  • 样式:Tailwind CSS、Element Plus(用于按钮)

  • 坐标系:EPSG:4326


🧩 实现思路概览

  1. 使用 VectorLayer 添加多个 Feature(Polygon)

  2. 每个 Feature 携带自定义信息(如公司名、地址、头像)

  3. 绑定地图 click 事件

  4. 使用 getFeaturesAtPixel() 获取所有命中的要素

  5. 展示弹窗(单个展示图文,多个展示名称列表)


📋 完整组件代码

可封装为 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 信息 的功能。无论是城市划分、设备管理还是图层叠加展示,都非常实用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吉檀迦俐

你的鼓励奖是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值