166.在Vue3中使用OpenLayers绘制点并判断是否在电子围栏内

1. 你将学到什么

  • Vue3 Composition API 集成 OpenLayers

  • 绘制 点(Point) 并接入 Draw 交互

  • Polygon#intersectsCoordinate 判断点是否位于电子围栏(多边形)内

  • 常见投影/坐标坑位与排错建议


2. 效果预览


3. 环境与依赖

  • Vue:3.x(Composition API)

  • Vite:任意(示例使用 5.x 亦可)

  • TypeScript:可选但推荐

  • UI:Element Plus(用于消息提示)

  • 地图:OpenLayers(简称 ol

安装依赖:

# OpenLayers + Element Plus
npm i ol element-plus

若使用按需引入 Element Plus,请配置 unplugin-vue-components;本文演示直接在脚本内使用 ElMessage


4. 核心思路 & 关键 API

  • 底图Tile + OSM

  • 数据图层:两个 Vector 图层

    • dataSource:存放电子围栏 Polygon

    • source:存放用户绘制的 Point

  • 交互Draw(type = 'Point')

  • 命中判断

    const isInside = polygonGeometry.intersectsCoordinate(coord)
    

    其中 coord 为点坐标(与地图 View.projection 一致)。

小贴士:若数据量较大,可先用 polygon.getExtent() + ol/extent 做一次 AABB 粗判,再进行精准面内判断以提速。


5. 目录结构(示例)

src/
  components/
    FenceDemo.vue   # 本文示例组件
  main.ts

6. 完整代码(Composition API)

可直接复制到 src/components/FenceDemo.vue 运行。

<!--
* @Author: 彭麒
* @Date: 2025/09/03
* @Email: 1062470959@qq.com
* @Description: Vue3 + OpenLayers 绘制点并判断是否在电子围栏内 Composition API
-->
<template>
  <div class="container">
    <div class="w-full flex justify-center flex-wrap">
      <div class="font-bold text-[24px]">vue3+openlayers: 绘制点,判断它是否在电子围栏内</div>
    </div>
    <h4>
      <el-button type="primary" size="small" @click="drawImage">绘制点</el-button>
    </h4>
    <div id="vue-openlayers"></div>
  </div>
</template>

<script setup lang="ts">
import 'ol/ol.css'
import { ref, onMounted } from 'vue'
import { Map, View } from 'ol'
import Tile from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'
import LayerVector from 'ol/layer/Vector'
import SourceVector from 'ol/source/Vector'
import Fill from 'ol/style/Fill'
import Feature from 'ol/Feature'
import { Point, Polygon } from 'ol/geom'
import Stroke from 'ol/style/Stroke'
import Style from 'ol/style/Style'
import CircleStyle from 'ol/style/Circle'
import Draw from 'ol/interaction/Draw'
import { ElMessage } from 'element-plus'

// 地图对象
const map = ref<Map | null>(null)
const draw = ref<Draw | null>(null)

// 数据源
const source = new SourceVector({ wrapX: false })
const dataSource = new SourceVector({ wrapX: false })

// 多边形数据(电子围栏)
const polygonData = [
  [
    [116.005, 39.005],
    [115.006, 40.008],
    [112.008, 39.008],
    [116.005, 39.005],
  ],
]

// 显示多边形
const showPolygon = () => {
  dataSource.clear()
  const polygonFeature = new Feature({
    geometry: new Polygon(polygonData),
  })
  dataSource.addFeature(polygonFeature)
}

// 初始化地图
const initMap = () => {
  const mapLayer = new Tile({ source: new OSM() })

  const pointLayer = new LayerVector({
    source: source,
    style: new Style({
      fill: new Fill({ color: 'orange' }),
      stroke: new Stroke({ width: 2, color: 'darkgreen' }),
      image: new CircleStyle({ radius: 5, fill: new Fill({ color: '#ff0000' }) }),
    }),
  })

  const fenceLayer = new LayerVector({
    source: dataSource,
    style: new Style({
      fill: new Fill({ color: 'transparent' }),
      stroke: new Stroke({ width: 2, color: 'blue' }),
    }),
  })

  map.value = new Map({
    target: 'vue-openlayers',
    layers: [mapLayer, fenceLayer, pointLayer],
    view: new View({
      projection: 'EPSG:4326',
      center: [115.006, 39.508],
      zoom: 8,
    }),
  })
}

// 绘制点并判断是否在电子围栏内
const drawImage = () => {
  source.clear()
  if (draw.value && map.value) {
    map.value.removeInteraction(draw.value)
  }

  draw.value = new Draw({ source: source, type: 'Point' })
  map.value?.addInteraction(draw.value)

  draw.value.on('drawend', (e: any) => {
    map.value?.removeInteraction(draw.value!)
    const coord = (e.feature.getGeometry() as Point).getCoordinates()
    const polygonGeometry = dataSource.getFeatures()[0].getGeometry() as Polygon
    const inside = polygonGeometry.intersectsCoordinate(coord)

    if (inside) {
      ElMessage.success({ message: '在电子围栏内', duration: 1000 })
    } else {
      ElMessage.error({ message: '在电子围栏外', duration: 1000 })
    }
  })
}

onMounted(() => {
  initMap()
  showPolygon()
})
</script>

<style scoped>
.container {
  width: 840px;
  height: 600px;
  margin: 50px auto;
  border: 1px solid #42b983;
}
#vue-openlayers {
  width: 800px;
  height: 430px;
  margin: 0 auto;
  border: 1px solid #42b983;
  position: relative;
}
</style>

7. 坑位与优化(强烈建议阅读)

7.1 关于投影(projection)

  • OSM 瓦片 原生是 EPSG:3857。本文示例将 View.projection 设置为 EPSG:4326 也能工作,但会触发客户端重投影,在性能敏感场景建议:

    1. View.projection 改为 EPSG:3857

    2. 所有经纬度统一用 fromLonLat([lon, lat]) 转换;

示例(3857 写法):

import { fromLonLat } from 'ol/proj'

view: new View({
  projection: 'EPSG:3857',
  center: fromLonLat([115.006, 39.508]),
  zoom: 8,
})

// polygon ring in lonlat -> map projection
const ring = [
  [116.005, 39.005],
  [115.006, 40.008],
  [112.008, 39.008],
  [116.005, 39.005],
].map(fromLonLat)
const polygon = new Polygon([ring])

记住:点坐标、面坐标、地图视图投影必须一致

7.2 边界点判定

部分业务认为“在边界上也算在内”。intersectsCoordinate 在边界附近的表现与版本实现有关,若需严格区分,可自行实现射线法或用 pointInPolygon 算法库,并统一规则。

7.3 重复添加 Feature

showPolygon() 前已调用 dataSource.clear(),避免多次点击叠加同一围栏。

7.4 容器尺寸与 updateSize

若地图容器在 Tab/抽屉中初始为隐藏,显示后应调用 map.updateSize() 防止白屏或偏移。


8. 常见问题(FAQ)

Q1:点击没有反应?

  • 检查 #vue-openlayers 是否有固定尺寸;

  • 检查是否成功 addInteraction(draw)

  • 控制台是否有 Element Plus 导入错误。

Q2:地图偏移或模糊?

  • 多为投影不一致或容器 CSS 受影响,参考 7.1 与 7.4。

Q3:怎么改为“多边形 + 圆形”混合电子围栏?

  • dataSource 允许放入多个 Feature,分别是 PolygonCircle,绘制点后分别判断并合并结果即可。


9. 进阶扩展

  • 支持 连续绘制多个点drawend 回调里不要移除交互,改为按钮切换)

  • 绘制 多边形/矩形/圆形 并将其作为电子围栏

  • ol/interaction/Modify 支持围栏 拖拽/编辑

  • 大数据量下用 R-Tree 或空间索引加速点面关系计算

  • 添加 GeoJSON 导入/导出,将围栏与打点结果持久化


10. 结语

以上就是 Vue3 + OpenLayers 实现“绘制点并判断是否在电子围栏内”的完整流程与坑位总结。你可以把本文的组件直接放进自己的项目,按需裁剪。若需要完整演示项目或想加上更多交互(如多围栏、多点批量判定),欢迎在评论区交流~

转载/引用请注明出处;如需商用授权或定制开发,可私信联系。

<think>我们正在处理一个关于在Vue2项目中集成OpenLayers的问题。根据引用内容,我们可以找到一些相关的信息。引用[2]提到在vue2框架中集成OpenLayers插件渲染2D地图,展示了在main.js中挂载的示例。引用[4]展示了如何添加图层和标记到地图中,使用了VectorLayer、VectorSource、Feature、Point等OpenLayers的类。因此,我们可以结合这些信息来回答用户的问题。步骤:1.创建一个Vue2项目(如果已有则跳过)2.安装OpenLayers3.在组件中引入OpenLayers创建地图注意:根据引用[2],我们需要在main.js中引入Vue和根组件,但OpenLayers的引入通常在具体组件中进行。具体步骤:第一步:创建Vue2项目(如果已有项目可跳过)使用VueCLI创建项目:vuecreatemy-project第二步:安装OpenLayers在项目目录下运行:npminstallol第三步:在Vue组件中使用OpenLayers例如,在App.vue中:<template><divid="map"class="map"></div></template><script>import&#39;ol/ol.css&#39;;//引入OpenLayers的样式importMapfrom&#39;ol/Map&#39;;importViewfrom&#39;ol/View&#39;;importTileLayerfrom&#39;ol/layer/Tile&#39;;importOSMfrom&#39;ol/source/OSM&#39;;exportdefault{name:&#39;App&#39;,mounted(){//在挂载后初始化地图this.initMap();},methods:{initMap(){newMap({target:&#39;map&#39;,//指向div的idlayers:[newTileLayer({source:newOSM(),//使用OpenStreetMap底图}),],view:newView({center:[0,0],//中心坐标zoom:2,//缩放级别}),});},},};</script><style>.map{width:100%;height:400px;}</style>这样就完成了一个简单的地图创建。另外,引用[4]展示了如何添加标记,我们可以在此基础上扩展://首先引入需要的模块importVectorLayerfrom&#39;ol/layer/Vector&#39;;importVectorSourcefrom&#39;ol/source/Vector&#39;;importFeaturefrom&#39;ol/Feature&#39;;importPointfrom&#39;ol/geom/Point&#39;;import{Style,Icon}from&#39;ol/style&#39;;//在initMap方法中创建标记//1.创建矢量图层constvectorLayer=newVectorLayer({source:newVectorSource(),});//2.创建要素constmarker=newFeature({geometry:newPoint([0,0]),//坐标});//3.设置样式(图标)marker.setStyle(newStyle({image:newIcon({anchor:[0.5,0.5],opacity:1,scale:0.4,cursor:&#39;pointer&#39;,src:&#39;path/to/icon.png&#39;,//图标的路径}),}));//4.将要素添加到矢量图层的源中vectorLayer.getSource().addFeature(marker);//5.将矢量图层添加到地图中//在创建地图时,将矢量图层添加到layers数组中newMap({target:&#39;map&#39;,layers:[newTileLayer({source:newOSM()}),vectorLayer,//添加矢量图层(标记)],view:newView({center:[0,0],zoom:2,}),});注意:标记的图标需要提供正确的路径。引用[2]还提到了在main.js中挂载Vue实例,这是Vue项目的标准步骤,通常在使用VueCLI创建项目时已经生成。总结:在Vue2中集成OpenLayers,主要是通过npm安装ol包,然后在组件中引入所需的类,在mounted生命周期中初始化地图。根据引用内容,我们还可以知道,OpenLayers6入门教程中有更多关于地图初始化设置(如缩放级别、中心、投影等)的细节,但这里我们只给出了基本示例。相关问题:1.如何更改OpenLayers地图的投影?2.如何在OpenLayers地图上添加多个标记3.OpenLayers地图事件如何处理(如击标记弹出信息)?注意:在生成相关问题的时候,我们参考了引用[1]中提到的入门教程目录,其中包含了设置默认缩放级别、中心、投影等内容。</think>### 在 Vue 2 中集成和使用 OpenLayers 的步骤 #### 1. **创建 Vue 2 项目** ```bash vue create vue2-openlayers-demo # 选择 Vue 2 模板 cd vue2-openlayers-demo ``` #### 2. **安装 OpenLayers 依赖** ```bash npm install ol # OpenLayers 核心库 ``` #### 3. **初始化地图组件** 在 `src/components/OpenLayersMap.vue` 中: ```vue <template> <div id="map" class="map-container"></div> </template> <script> import &#39;ol/ol.css&#39;; // 引入 OpenLayers 样式 import { Map, View } from &#39;ol&#39;; import TileLayer from &#39;ol/layer/Tile&#39;; import OSM from &#39;ol/source/OSM&#39;; // 使用 OpenStreetMap 底图 import VectorLayer from &#39;ol/layer/Vector&#39;; import VectorSource from &#39;ol/source/Vector&#39;; import { Feature, Point } from &#39;ol&#39;; import { Style, Icon } from &#39;ol/style&#39;; export default { name: &#39;OpenLayersMap&#39;, mounted() { this.initMap(); }, methods: { initMap() { // 1. 创建地图实例 const map = new Map({ target: &#39;map&#39;, // 绑定 DOM 元素 layers: [ new TileLayer({ source: new OSM() }), // 底图 new VectorLayer({ source: new VectorSource() }), // 矢量图层(用于标记) ], view: new View({ center: [116.4, 39.9], // 默认中心(北京) zoom: 10, // 默认缩放级别 projection: &#39;EPSG:4326&#39;, // 坐标系 }), }); // 2. 添加标记 this.addMarker(map); }, addMarker(map) { const layer = map.getLayers().getArray()[1]; // 获取矢量图层 const marker = new Feature({ geometry: new Point([116.4, 39.9]), // 坐标 }); marker.setStyle( new Style({ image: new Icon({ anchor: [0.5, 1], // 图标锚 src: &#39;https://openlayers.org/en/latest/examples/data/icon.png&#39;, // 图标路径 }), }) ); layer.getSource().addFeature(marker); }, }, }; </script> <style> .map-container { width: 100%; height: 500px; } </style> ``` #### 4. **在 App.vue使用组件** ```vue <template> <div id="app"> <OpenLayersMap /> </div> </template> <script> import OpenLayersMap from &#39;./components/OpenLayersMap.vue&#39;; export default { components: { OpenLayersMap }, }; </script> ``` #### 5. **挂载到 main.js** ```javascript import Vue from &#39;vue&#39;; import App from &#39;./App.vue&#39;; Vue.config.productionTip = false; new Vue({ render: h => h(App) }).$mount(&#39;#app&#39;); ``` #### 关键配置说明: 1. **坐标系**:通过 `View` 的 `projection` 设置(默认为 `EPSG:3857`,示例使用 `EPSG:4326`)。 2. **标记**:通过 `VectorLayer` 和 `Feature` 实现,支持自定义图标样式[^4]。 3. **图层管理**:`TileLayer` 用于瓦片底图,`VectorLayer` 用于矢量要素。 #### 运行项目 ```bash npm run serve ``` 访问 `http://localhost:8080` 即可看到集成的地图。 --- ### 常见问题解决 - **坐标系偏移**:确保坐标值与 `projection` 一致(`[经度, 纬度]` 对应 `EPSG:4326`)。 - **图标不显示**:检查 `Icon` 的 `src` 路径是否正确,或使用 Base64 编码图片。 - **销毁地图**:在组件 `beforeDestroy` 中调用 `map.setTarget(null)` 释放资源[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吉檀迦俐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值