项目演示了如何使用 Turf.js 的
turf.mask()
方法与 OpenLayers 实现地图遮罩布挖洞效果,支持一键生成洞口区域、遮罩布、以及最终合成的遮罩布挖洞图形。
📸效果预览
(上图为遮罩布在地图上挖出洞后的样式,仅供参考)
🧩技术栈
-
Vue3 + Composition API
-
OpenLayers(地图库)
-
Turf.js(地理计算库)
-
Element Plus(UI 控件)
-
Autonavi 高德地图瓦片源
✨功能介绍
按钮名称 | 功能描述 |
---|---|
绘制小洞 | 使用 turf.polygon 创建一个小洞区域(Polygon) |
绘制遮罩布 | 使用 turf.polygon 创建整个大遮罩布区域 |
合力遮罩打洞 | 使用 turf.mask(洞, 遮罩布) 将遮罩布打上洞 |
清除图层 | 清除当前地图上的所有遮罩图形 |
🧠关键逻辑详解
1️⃣ turf.mask 实现遮罩减洞
const maskResult = () => {
turfSource.clear()
const hole = turf.polygon(holeData)
const mask = turf.polygon(maskData)
const masked = turf.mask(hole, JSON.parse(JSON.stringify(mask)))
show(masked)
}
-
turf.polygon()
:构建多边形 -
turf.mask()
:将mask
多边形“剪掉”hole
区域,返回一个挖好洞的多边形
2️⃣ GeoJSON 与 OpenLayers 特征同步
const show = (geojsonData) => {
const features = new GeoJSON().readFeatures(geojsonData, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857',
})
turfSource.addFeatures(features)
}
-
使用
GeoJSON
读取并转换为 OpenLayers 能识别的Feature
实体,投影从 WGS84 转成 EPSG:3857
3️⃣ 样式定义:遮罩区域 + 多边形点位标注
const vectorLayer = new VectorLayer({
source: turfSource,
style: [
new Style({
fill: new Fill({ color: 'rgba(255,0,0,0.2)' }),
stroke: new Stroke({ width: 2, color: 'blue' })
}),
new Style({
image: new RegularShape({
radius: 6,
points: 4,
rotation: Math.PI / 4,
fill: new Fill({ color: 'white' }),
stroke: new Stroke({ color: 'red', width: 2 })
}),
geometry: function (feature) {
const coordinates = feature.getGeometry().getCoordinates()[0]
return new MultiPoint(coordinates)
}
})
]
})
-
外层填充透明红色,描边蓝色
-
顶点采用红白相间的矩形菱形样式标注(辅助观察)
🧪完整示例运行步骤
✅ 1. 安装依赖
npm install ol @turf/turf
✅ 2. 页面引入组件
将源码内容放置于 Vue 页面中(推荐 *.vue
文件中)
📦完整代码
<!--
* @Author: 彭麒
* @Date: 2025/7/14
* @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+Turf 实现遮罩布挖洞效果
</div>
</div>
<div class="flex gap-2 mb-4 w-full justify-center">
<el-button type="primary" size="small" @click="drawHole">绘制小洞</el-button>
<el-button type="primary" size="small" @click="drawMask">绘制遮罩布</el-button>
<el-button type="primary" size="small" @click="maskResult">合力遮罩打洞</el-button>
<el-button type="danger" size="small" @click="clearSource">清除图层</el-button>
</div>
<div id="vue-openlayers" ref="mapContainer"></div>
</div>
</template>
<script setup>
import 'ol/ol.css'
import { ref, onMounted } from 'vue'
import Map from 'ol/Map'
import View from 'ol/View'
import TileLayer from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import VectorSource from 'ol/source/Vector'
import VectorLayer from 'ol/layer/Vector'
import GeoJSON from 'ol/format/GeoJSON'
import { Fill, Stroke, Style, RegularShape } from 'ol/style'
import { fromLonLat } from 'ol/proj'
import { MultiPoint } from 'ol/geom'
import * as turf from '@turf/turf'
const map = ref(null)
const mapContainer = ref(null)
const turfSource = new VectorSource({ wrapX: false })
const holeData = [
[
[112, -21],
[116, -36],
[146, -39],
[153, -24],
[133, -10],
[112, -21]
]
]
const maskData = [
[
[90, -55],
[170, -55],
[170, 10],
[90, 10],
[90, -55]
]
]
const show = (geojsonData) => {
const features = new GeoJSON().readFeatures(geojsonData, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857',
})
turfSource.addFeatures(features)
}
const clearSource = () => {
turfSource.clear()
}
const drawHole = () => {
const hole = turf.polygon(holeData)
show(hole)
}
const drawMask = () => {
const mask = turf.polygon(maskData)
show(mask)
}
const maskResult = () => {
turfSource.clear()
const hole = turf.polygon(holeData)
const mask = turf.polygon(maskData)
const masked = turf.mask(hole, JSON.parse(JSON.stringify(mask)))
show(masked)
}
const initMap = () => {
const baseLayer = new TileLayer({
source: new XYZ({
url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=en&size=1&scl=1&style=7'
})
})
const vectorLayer = new VectorLayer({
source: turfSource,
style: [
new Style({
fill: new Fill({ color: 'rgba(255,0,0,0.2)' }),
stroke: new Stroke({ width: 2, color: 'blue' })
}),
new Style({
image: new RegularShape({
radius: 6,
points: 4,
rotation: Math.PI / 4,
fill: new Fill({ color: 'white' }),
stroke: new Stroke({ color: 'red', width: 2 })
}),
geometry: function (feature) {
const coordinates = feature.getGeometry().getCoordinates()[0]
return new MultiPoint(coordinates)
}
})
]
})
map.value = new Map({
target: mapContainer.value,
layers: [baseLayer, vectorLayer],
view: new View({
projection: 'EPSG:3857',
center: fromLonLat([146, -39]),
zoom: 2
})
})
}
onMounted(() => {
initMap()
})
</script>
<style scoped>
.container {
width: 840px;
height: 570px;
margin: 50px auto;
border: 1px solid #42B983;
}
#vue-openlayers {
width: 800px;
height: 400px;
margin: 0 auto;
border: 1px solid #42B983;
position: relative;
}
</style>
🧭适用场景
-
土地规划中的“保留区挖空”
-
可视区域剔除遮罩高亮
-
城市管理中对敏感区域进行裁剪遮罩
-
GIS 应用中实现地理图层 Mask 操作
✍️结语
本篇展示了如何通过 OpenLayers 与 Turf.js 组合实现地理遮罩布打洞功能。遮罩效果不仅直观,也非常适合拓展复杂图层控制、互动分析等 GIS 需求。
如果你觉得文章对你有帮助,欢迎点赞、评论、收藏!
你也可以关注我,查看更多 Vue3 + OpenLayers + Turf + Cesium 的三维地理可视化实战内容!