当前位置: 首页 > news >正文

完整教程:分享一个基于服务端地图服务裁剪的方法

完整教程:分享一个基于服务端地图服务裁剪的方法

概述

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

实现思路

大致实现逻辑如下逻辑图。
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}`);
});

再加一个Python的版本

from flask import Flask, request, send_file, Response
from flask_cors import CORS
import requests
from PIL import Image
import io
import os
app = Flask(__name__)
CORS(app)
# WMS合并服务
@app.route('/wms-merged', methods=['GET'])
def wms_merged():try:# 获取查询参数SERVICE = request.args.get('SERVICE', 'WMS')VERSION = request.args.get('VERSION', '1.1.1')REQUEST = request.args.get('REQUEST', 'GetMap')FORMAT = request.args.get('FORMAT', 'image/png')TRANSPARENT = request.args.get('TRANSPARENT', 'true')WIDTH = request.args.get('WIDTH')HEIGHT = request.args.get('HEIGHT')SRS = request.args.get('SRS', 'EPSG:3857')BBOX = request.args.get('BBOX')# 验证必要参数if not WIDTH or not HEIGHT or not BBOX:return '缺少必要的参数: WIDTH, HEIGHT, BBOX', 400width = int(WIDTH)height = int(HEIGHT)# 基础WMS URLwms_base_url = 'http://localhost:8086/geoserver/lzugis/wms'# 图层配置layers = [{'name': 'lzugis:base_county','params': {'SERVICE': SERVICE,'VERSION': VERSION,'REQUEST': REQUEST,'FORMAT': FORMAT,'TRANSPARENT': TRANSPARENT}},{'name': 'lzugis:base_city','params': {'SERVICE': SERVICE,'VERSION': VERSION,'REQUEST': REQUEST,'FORMAT': FORMAT,'TRANSPARENT': TRANSPARENT,'CQL_FILTER': "province <> '甘肃省'"}}]# 构建每个图层的URLdef build_layer_url(layer_config):params = layer_config['params'].copy()params.update({'LAYERS': layer_config['name'],'WIDTH': str(width),'HEIGHT': str(height),'SRS': SRS,'BBOX': BBOX})return f"{wms_base_url}?{'&'.join(f'{k}={v}' for k, v in params.items())}"# 下载所有图层图片image_urls = [build_layer_url(layer) for layer in layers]image_buffers = []for url in image_urls:response = requests.get(url, stream=True)response.raise_for_status()image_buffers.append(response.content)# 使用PIL合并图层images = [Image.open(io.BytesIO(buffer)) for buffer in image_buffers]# 创建基础图像if images:composite_image = images[0].convert('RGBA')# 叠加其他图层for i in range(1, len(images)):overlay = images[i].convert('RGBA')composite_image = Image.alpha_composite(composite_image, overlay)# 处理白色透明data = composite_image.getdata()new_data = []for item in data:# 检查是否为白色(RGB值都接近255)if item[0] > 240 and item[1] > 240 and item[2] > 240:# 设置为透明new_data.append((255, 255, 255, 0))else:new_data.append(item)composite_image.putdata(new_data)# 将图像保存到字节流img_byte_arr = io.BytesIO()composite_image.save(img_byte_arr, format='PNG')img_byte_arr.seek(0)# 返回图像响应return send_file(img_byte_arr, mimetype='image/png')else:return '没有可用的图像数据', 500except Exception as e:print(f'WMS合并错误: {e}')return '服务器内部错误', 500
if __name__ == '__main__':port = int(os.environ.get('PORT', 8888))app.run(host='0.0.0.0', port=port, debug=True)
http://www.jsqmd.com/news/71233/

相关文章:

  • Nginx安全配置
  • 并发编程的三大基石:从底层逻辑聊透“同步、互斥与分工”
  • 个人电脑本地私有知识库解决方案:访答知识库全面解析
  • 【Agent】MemOS 源码笔记---(4)---KV Cache
  • 2025.12.10
  • 大数据存储新范式:RustFS与Hadoop生态无缝集成实战指南
  • Ai元人文构想:黑箱之渡,白箱之锚——大行为模型践行意义行为原生
  • 在 .Net 8 WEBAPI 中实现实体框架的 Code First 办法
  • 60
  • Coppersmith 学习笔记
  • python —— 树的遍历 —— 深度优先遍历(先序、中序、后序) —— 非递归方式(使用栈数据结构进行辅助)
  • 【SQL技术】不同数据库引擎 SQL 优化方案剖析 - 详解
  • IntelliJ IDEA 最常用的快捷键
  • C++ 循环结构:控制程序重复执行的核心机制 - 教程
  • ASP.NET 实战:用 CSS 选择器打造一个可搜索、响应式的书籍管理系统 - 教程
  • Python list all files in dir recursivelly
  • python —— 树的遍历 —— 深度优先遍历(先序、中序、后序)
  • springAI集成智谱--流式输出
  • python —— 满二叉树的广度优先遍历
  • 切比雪夫多项式与数值最优化算法收敛率的关系
  • 恰好被k个区间覆盖的点的数量
  • Day59(29)-F:\vs_ai_work\vue-tlias-management
  • windriver 第5章:USB 概述
  • Airflow - from airflow import DAG and from airflow.sdk import DAG, which one is better?
  • langchain工具上下文
  • 货代邮件自动化处理系统设计文档
  • 吐血整理!新房全包装修,性价比之王大盘点 - 品牌测评鉴赏家
  • DSU on array
  • Resources资源同步加载、异步加载、卸载
  • 新房全包装修怎么选?这 3 类高性价比公司帮你省心省钱(附 2025 口碑红榜) - 品牌测评鉴赏家