2025-11-13分享一个基于服务端地图服务裁剪的方法

概述

需求大致是这样的:有一个全国的土地利用数据,要根据用户权限实现从省级、市级和区级不同级别的区划的不同展示,问题是数据没有关联区划数据。针对上述这个问题,我探索出了一种将区划数据发布成一个纯色(全白样式)服务,对需要展示的区域进行筛选后和原服务进行叠加,在通过代码将白色转成透明色的实现方式。

实现思路

大致实现逻辑如下逻辑图。


image.png

实现效果

合并后未抠图
抠图后
叠加到页面后

实现代码

前端使用openlayers调用,代码如下:

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/css/ol.css"
    type="text/css">
  <style>
    html,
    body,
    .map {
      height: 100%;
      width: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
  </style>
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/build/ol.js"></script>
  <title>OpenLayers example</title>
</head>

<body>
  <div id="map" class="map"></div>
  <script type="text/javascript">
    // 创建地图
    var map = new ol.Map({
      target: 'map',
      layers: [
        // 底图
        new ol.layer.Tile({
          source: new ol.source.XYZ({
            url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
          })
        }),
        // 使用ImageWMS源调用后端合并的WMS服务
        new ol.layer.Image({
          source: new ol.source.ImageWMS({
            url: 'http://localhost:8888/wms-merged',
            params: {
              'SERVICE': 'WMS',
              'VERSION': '1.1.1',
              'REQUEST': 'GetMap',
              'FORMAT': 'image/png',
              'TRANSPARENT': 'true'
            },
            serverType: 'geoserver',
            crossOrigin: 'anonymous'
          })
        })
      ],
      view: new ol.View({
        center: ol.proj.fromLonLat([104.06, 30.67]),
        zoom: 5
      })
    });
  </script>
</body>

</html>

后端服务使用node,实现代码如下:

const express = require('express');
const axios = require('axios');
const { createCanvas, loadImage } = require('canvas');
const path = require('path');

const app = express();
const PORT = process.env.PORT || 8888;

// 启用CORS
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

// WMS合并服务
app.get('/wms-merged', async (_, res) => {
  try {
    const {
      SERVICE = 'WMS',
      VERSION = '1.1.1',
      REQUEST = 'GetMap',
      FORMAT = 'image/png',
      TRANSPARENT = 'true',
      WIDTH,
      HEIGHT,
      SRS = 'EPSG:3857',
      BBOX
    } = req.query;

    if (!WIDTH || !HEIGHT || !BBOX) {
      return res.status(400).send('缺少必要的参数: WIDTH, HEIGHT, BBOX');
    }

    const width = parseInt(WIDTH);
    const height = parseInt(HEIGHT);

    // 创建Canvas
    const canvas = createCanvas(width, height);
    const ctx = canvas.getContext('2d');

    // 基础WMS URL
    const wmsBaseUrl = 'http://localhost:8086/geoserver/lzugis/wms';

    // 图层配置
    const layers = [
      {
        name: 'lzugis:base_county',
        params: {
          SERVICE,
          VERSION,
          REQUEST,
          FORMAT,
          TRANSPARENT
        }
      },
      {
        name: 'lzugis:base_city',
        params: {
          SERVICE,
          VERSION,
          REQUEST,
          FORMAT,
          TRANSPARENT,
          CQL_FILTER: "province <> '甘肃省'"  // 这个参数可以前端传过来
        }
      }
    ];

    // 构建每个图层的URL
    const buildLayerUrl = (layerConfig) => {
      const params = new URLSearchParams();
      
      // 添加基础参数
      Object.keys(layerConfig.params).forEach(key => {
        params.append(key, layerConfig.params[key]);
      });
      
      // 添加图层特定参数
      params.append('LAYERS', layerConfig.name);
      params.append('WIDTH', width.toString());
      params.append('HEIGHT', height.toString());
      params.append('SRS', SRS);
      params.append('BBOX', BBOX);

      return `${wmsBaseUrl}?${params.toString()}`;
    };

    // 加载并绘制两个图层
    const imageUrls = layers.map(buildLayerUrl);
    const images = await Promise.all(imageUrls.map(url => 
      axios.get(url, { responseType: 'arraybuffer' })
        .then(response => loadImage(Buffer.from(response.data)))
    ));

    // 绘制第一个图层
    ctx.drawImage(images[0], 0, 0, width, height);
    
    // 绘制第二个图层
    ctx.drawImage(images[1], 0, 0, width, height);

    // 处理白色透明
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;
    
    for (let i = 0; i < data.length; i += 4) {
      // 检查是否为白色(RGB值都接近255)
      if (data[i] > 240 && data[i + 1] > 240 && data[i + 2] > 240) {
        // 设置为透明
        data[i + 3] = 0;
      }
    }
    
    ctx.putImageData(imageData, 0, 0);

    // 设置响应头并发送图片
    res.setHeader('Content-Type', 'image/png');
    canvas.pngStream().pipe(res);
    
  } catch (error) {
    console.error('WMS合并错误:', error);
    res.status(500).send('服务器内部错误');
  }
});

// 静态文件服务
app.use(express.static(path.join(__dirname)));

// 启动服务器
app.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}`);
});
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容