阅读视图

发现新文章,点击刷新页面。

🌰在 OpenLayers 中实现图层裁切

img💬 前言

做地图的时候,常会给地图套个“遮罩”——把不关心的地方调暗,把想看的地方突出出来。但有时我们并不想动整张地图的视觉效果,只想把某一张图层按不规则形状展示出来,比如只在某块多边形里显示指定图层。

🎯 最终效果如下

给定一个多边形(polygonCoords),对单一 WMTS/瓦片图层进行裁切:图层仅在该多边形内部可见,其他图层不受影响。

图层裁切最终效果

🧭 实现思路

通过监听图层的 prerender 事件 和 postrender事件 实现裁切

  1. 在图层开始绘制之前(prerender)准备裁切:
    • 回调里会拿到两个东西:OpenLayers 的矢量渲染上下文和原生的 Canvas 上下文(ctx)。
    • 先用 ctx.save() 保存当前 canvas 状态。
    • drawFeature(feature, style) 把多边形画到当前画布上(这会在 canvas 上生成路径)。
    • 然后调用 ctx.clip(),把这条路径设为裁切区,将当前图层进行裁切。
  2. 在图层绘制结束后(postrender)恢复状态:
    • 在 postrender 回调里调用 ctx.restore(),把之前保存的 canvas 状态还原,移除裁切,确保别的图层不受影响。

⚙️ 初始化项目

这里我设置地图的坐标系是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>

效果如下

image-20260117182119131

📐 多边形预览

前面实现思路说了,我们需要一个多边形裁切图层,我这里准备了一个

   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
                        ]
                    ]
                ]

image-20260117190344593

后面就会用这个多边形来对图层进行裁切

🗺️ 添加一张影像图

<!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>

效果如下

image-20260117191715249

🔔 监听图层事件

前面已经将这个影像图添加到地图上面去了, 接下来就可以监听这个图层事件,然后用多边形去裁切这张图层

// 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>
❌