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
也能工作,但会触发客户端重投影,在性能敏感场景建议:-
将
View.projection
改为EPSG:3857
; -
所有经纬度统一用
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
,分别是Polygon
和Circle
,绘制点后分别判断并合并结果即可。
9. 进阶扩展
-
支持 连续绘制多个点(
drawend
回调里不要移除交互,改为按钮切换) -
绘制 多边形/矩形/圆形 并将其作为电子围栏
-
用
ol/interaction/Modify
支持围栏 拖拽/编辑 -
大数据量下用 R-Tree 或空间索引加速点面关系计算
-
添加 GeoJSON 导入/导出,将围栏与打点结果持久化
10. 结语
以上就是 Vue3 + OpenLayers 实现“绘制点并判断是否在电子围栏内”的完整流程与坑位总结。你可以把本文的组件直接放进自己的项目,按需裁剪。若需要完整演示项目或想加上更多交互(如多围栏、多点批量判定),欢迎在评论区交流~
转载/引用请注明出处;如需商用授权或定制开发,可私信联系。