🌰在 OpenLayers 中实现图层裁切
2026年1月17日 19:31
💬 前言
做地图的时候,常会给地图套个“遮罩”——把不关心的地方调暗,把想看的地方突出出来。但有时我们并不想动整张地图的视觉效果,只想把某一张图层按不规则形状展示出来,比如只在某块多边形里显示指定图层。
🎯 最终效果如下
给定一个多边形(polygonCoords),对单一 WMTS/瓦片图层进行裁切:图层仅在该多边形内部可见,其他图层不受影响。
![]()
🧭 实现思路
通过监听图层的 prerender 事件 和 postrender事件 实现裁切
- 在图层开始绘制之前(prerender)准备裁切:
- 回调里会拿到两个东西:OpenLayers 的矢量渲染上下文和原生的 Canvas 上下文(ctx)。
- 先用 ctx.save() 保存当前 canvas 状态。
- 用
drawFeature(feature, style)把多边形画到当前画布上(这会在 canvas 上生成路径)。 - 然后调用
ctx.clip(),把这条路径设为裁切区,将当前图层进行裁切。
- 在图层绘制结束后(postrender)恢复状态:
- 在 postrender 回调里调用
ctx.restore(),把之前保存的 canvas 状态还原,移除裁切,确保别的图层不受影响。
- 在 postrender 回调里调用
⚙️ 初始化项目
这里我设置地图的坐标系是3857,你可以根据自己的需要设置对应的坐标系
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>openlayer图层裁切</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="./ol.js"></script>
<script>
const projection = ol.proj.get('EPSG:3857');
const map = new ol.Map({
layers: [
// 矢量底图
new ol.layer.Tile({
source: new ol.source.XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=2aedde694311cf1e8ac3feca2da4fd3e`,
}),
}),
// 矢量注记
new ol.layer.Tile({
source: new ol.source.XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=2aedde694311cf1e8ac3feca2da4fd3e`,
}),
}),
],
target: 'map',
view: new ol.View({
zoom: 5,
maxZoom: 17,
minZoom: 1,
projection: projection,
center: [116.406393, 39.909006],
}),
})
</script>
</body>
</html>
效果如下
![]()
📐 多边形预览
前面实现思路说了,我们需要一个多边形裁切图层,我这里准备了一个
const polygonCoords =
[
[
[
911245.7835522988,
1672839.829528198
],
[
1517290.255658307,
2962709.5078420187
],
[
2862581.9534774087,
3011639.670051078
],
[
3492423.0665472616,
2736407.507625122
],
[
3675871.934431684,
2161478.101668681
],
[
3131640.2930412292,
1647711.398473564
],
[
2141016.406465345,
999386.7492035348
],
[
2581293.6893879604,
63597.39695528569
],
[
2165476.2555166017,
-523564.5495534199
],
[
3107180.4439899744,
-982284.8202633462
],
[
2232740.8404075587,
-1379842.3882119493
],
[
1609014.6896005198,
-1300330.874622228
],
[
1040323.1991588091,
-1104610.2257859926
],
[
471631.7087170966,
-450169.3062398317
],
[
465516.74645428266,
240969.23496312322
],
[
324872.6144095585,
913758.9653376816
],
[
593930.953973379,
1757804.2634439464
],
[
911245.7835522988,
1672839.829528198
]
]
]
![]()
后面就会用这个多边形来对图层进行裁切
🗺️ 添加一张影像图
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>openlayer图层裁切</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="./ol.js"></script>
<script>
const projection = ol.proj.get('EPSG:3857');
const projectionExtent = projection.getExtent();
const size = ol.extent.getWidth(projectionExtent) / 256;
const resolutions = [];
for (let z = 2; z < 19; ++z) {
resolutions[z] = size / Math.pow(2, z);
}
const matrixIds = ['0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18']
const map = new ol.Map({
layers: [
// 矢量底图
new ol.layer.Tile({
source: new ol.source.XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=2aedde694311cf1e8ac3feca2da4fd3e`,
}),
}),
// 矢量注记
new ol.layer.Tile({
source: new ol.source.XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=2aedde694311cf1e8ac3feca2da4fd3e`,
}),
}),
],
target: 'map',
view: new ol.View({
zoom: 5,
maxZoom: 17,
minZoom: 1,
projection: projection,
center: [116.406393, 39.909006],
}),
})
const wmtsLayer = new ol.layer.Tile({
source: new ol.source.WMTS({
url: `http://t{0-6}.tianditu.gov.cn/img_c/wmts?tk=2aedde694311cf1e8ac3feca2da4fd3e`,
layer: 'img',
matrixSet: 'c',
style: 'default',
crossOrigin: 'anonymous',
format: 'tiles',
wrapX: true,
crossOrigin: 'anonymous',
tileGrid: new ol.tilegrid.WMTS({
origin: ol.extent.getTopLeft(projectionExtent),
resolutions: resolutions,
matrixIds: matrixIds
})
})
})
map.addLayer(wmtsLayer);
</script>
</body>
</html>
效果如下
![]()
🔔 监听图层事件
前面已经将这个影像图添加到地图上面去了, 接下来就可以监听这个图层事件,然后用多边形去裁切这张图层
// wmtsLayer 就是前面添加的影像图
wmtsLayer.on('prerender', function (evt) {
const layerContent = ol.render.getVectorContext(evt)
const ctx = evt.context;
ctx.save();
layerContent.drawFeature(polygon, polygonStyle)
ctx.clip();
});
// 渲染后恢复 canvas 状态,移除裁切
wmtsLayer.on('postrender', function (evt) {
const ctx = evt.context;
if (ctx) ctx.restore();
});
const layer = map.addLayer(wmtsLayer);
📄 完整代码
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>openlayer图层裁切</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
/* 右上角形状按钮组 */
#shape-controls {
position: fixed;
top: 12px;
right: 12px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 1000;
}
#shape-controls button {
padding: 6px 10px;
font-size: 14px;
cursor: pointer;
}
#shape-controls button.active {
background: #0b5ed7;
color: #fff;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="shape-controls">
<button id="btn-polygon">添加多边形影像图</button>
</div>
<script src="./ol.js"></script>
<script>
const projection = ol.proj.get('EPSG:3857');
const projectionExtent = projection.getExtent();
const size = ol.extent.getWidth(projectionExtent) / 256;
const resolutions = [];
for (let z = 2; z < 19; ++z) {
resolutions[z] = size / Math.pow(2, z);
}
const matrixIds = ['0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18']
const map = new ol.Map({
layers: [
// 矢量底图
new ol.layer.Tile({
source: new ol.source.XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=2aedde694311cf1e8ac3feca2da4fd3e`,
}),
}),
// 矢量注记
new ol.layer.Tile({
source: new ol.source.XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=2aedde694311cf1e8ac3feca2da4fd3e`,
}),
}),
],
target: 'map',
view: new ol.View({
zoom: 5,
maxZoom: 17,
minZoom: 1,
projection: projection,
center: [116.406393, 39.909006],
}),
})
document.getElementById('btn-polygon').addEventListener('click', function () {
const polygonCoords =
[
[
[
911245.7835522988,
1672839.829528198
],
[
1517290.255658307,
2962709.5078420187
],
[
2862581.9534774087,
3011639.670051078
],
[
3492423.0665472616,
2736407.507625122
],
[
3675871.934431684,
2161478.101668681
],
[
3131640.2930412292,
1647711.398473564
],
[
2141016.406465345,
999386.7492035348
],
[
2581293.6893879604,
63597.39695528569
],
[
2165476.2555166017,
-523564.5495534199
],
[
3107180.4439899744,
-982284.8202633462
],
[
2232740.8404075587,
-1379842.3882119493
],
[
1609014.6896005198,
-1300330.874622228
],
[
1040323.1991588091,
-1104610.2257859926
],
[
471631.7087170966,
-450169.3062398317
],
[
465516.74645428266,
240969.23496312322
],
[
324872.6144095585,
913758.9653376816
],
[
593930.953973379,
1757804.2634439464
],
[
911245.7835522988,
1672839.829528198
]
]
]
const wmtsLayer = new ol.layer.Tile({
source: new ol.source.WMTS({
url: `http://t{0-6}.tianditu.gov.cn/img_c/wmts?tk=2aedde694311cf1e8ac3feca2da4fd3e`,
layer: 'img',
matrixSet: 'c',
style: 'default',
crossOrigin: 'anonymous',
format: 'tiles',
wrapX: true,
crossOrigin: 'anonymous',
tileGrid: new ol.tilegrid.WMTS({
origin: ol.extent.getTopLeft(projectionExtent),
resolutions: resolutions,
matrixIds: matrixIds
})
})
})
const polygon = new ol.Feature({
geometry: new ol.geom.Polygon(polygonCoords)
})
const polygonStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(255, 0, 0, 1)',
width: 2
})
});
// 在渲染前使用多边形对图层进行裁切(clip)
wmtsLayer.on('prerender', function (evt) {
const layerContent = ol.render.getVectorContext(evt)
const ctx = evt.context;
ctx.save();
layerContent.drawFeature(polygon, polygonStyle)
ctx.clip();
});
// 渲染后恢复 canvas 状态,移除裁切
wmtsLayer.on('postrender', function (evt) {
const ctx = evt.context;
if (ctx) ctx.restore();
});
const layer = map.addLayer(wmtsLayer);
});
</script>
</body>
</html>