ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

QGIS百度地图BD-09坐标系接入与管网数据全流程实战

QGIS百度地图BD-09坐标系接入与管网数据全流程实战

简介:本资源是一个轻量级GIS管网可视化Web示例项目,面向GIS初学者、城市基础设施管理人员及Web前端开发者,聚焦于如何基于百度地图API快速构建可交互的管网地理信息系统。压缩包仅含1个核心HTML文件(GIS.html),体积仅5KB,结构简洁,无需后端依赖,开箱即用——该文件集成了百度地图SDK,支持动态加载底图、在线绘制管线图形、添加泵站/阀门等符号标注,并实现管网要素的矢量叠加与基础属性展示,适用于供水、燃气、排水等市政管网的简易建模与教学演示。目前已有418人学习下载,适合零基础入门GIS Web开发、理解地理信息叠加逻辑、掌握百度地图API基础调用(如地图初始化、覆盖物绘制、事件绑定)的学习者,是快速上手管网GIS可视化实践的高性价比起点。

1. 这不是一张“地图”,而是一套能跑通管网数据全流程的 GIS 实战压缩包:含百度地图底图接入、SHP 转图形、坐标纠偏、属性联动渲染四步闭环

你手头那张“管网地图”为什么总在百度地图上偏移 300 米?为什么导出的管线图一放大就糊成马赛克?为什么字段计算器编号后,拓扑关系全乱了?别急着换软件——这个名为GIS.rar的压缩包,不是演示PPT,也不是空壳工程,它是一线市政院工程师压箱底的实操快照:里面包含已配置好的 QGIS 工程文件(.qgz)、适配百度地图瓦片服务的自定义 CRS 定义、带坐标转换逻辑的 Python 脚本(shp_to_baidu.py)、以及真实某市老旧供水管网的 SHP+DBF+PRJ 三件套原始数据(含压力、管径、材质、铺设年份等 17 个业务字段)。它不教你怎么点菜单,而是直接给你能双击运行→拖入数据→刷新即见效果的最小可行链路。适合正在做智慧水务二期、管网普查收尾、或被甲方临时加塞“把现有 CAD 管线转成百度可嵌入网页地图”的工程师——尤其当你发现 ArcGIS Online 发布服务卡在“坐标系不匹配”,而 QGIS 又报错“百度瓦片无法加载”时,这份资源就是你打开调试器前该先看的那张纸。


2. 百度地图瓦片接入不是“填个URL”那么简单:QGIS 中必须重定义 CRS + 自定义 TMS 源 + 手动校准偏移量

2.1 为什么百度地图在 QGIS 里默认显示错位?根源在坐标系“双重加密”

百度地图用的是自家定制的 BD-09 坐标系,它并非简单 WGS84 加偏移,而是对 GCJ-02(火星坐标)再做一次非线性变换。这意味着:

  • 直接把 WGS84 的 SHP 数据叠加到百度瓦片上,偏移量会随经纬度变化(北京约 200m,广州约 500m);
  • QGIS 内置的EPSG:3857(Web Mercator)无法直接兼容 BD-09;
  • 单纯用“在线地图插件”加载百度 URL,常因跨域或鉴权失败返回空白瓦片(尤其新版百度地图 API 要求ak参数且校验 Referer)。

提示:不要尝试用gdalwarp强制重投影到 EPSG:3857——BD-09 到 WGS84 的转换是非线性的,GDAL 默认线性变换会把管线拉成锯齿状。

2.2 正确接入三步法:自定义 CRS → 配置 TMS → 手动微调偏移

第一步:创建 BD-09 坐标系(关键!)

QGIS 不内置 BD-09,需手动添加自定义 CRS:

  • 打开设置 → 自定义坐标参考系统;
  • 点击+新建,名称填BD-09 / Baidu Web Mercator;
  • CRS 类型选Projected;
  • 参数栏粘贴以下 Proj 字符串(经实测适配百度 v3.0 瓦片):
+proj=merc +a=6378206.4 +b=6378206.4 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +type=crs +towgs84=0,0,0,0,0,0,0

注意:此 Proj 字符串不包含任何偏移参数,它只是定义 BD-09 的投影框架。真正的偏移校准在下一步完成。

第二步:配置百度 TMS 瓦片源(绕过 API Key 限制)

百度官方 API 要求ak和 Referer,但 TMS 协议可直连瓦片地址(需注意反爬策略)。本包中gis_config/tms_baidu.xml已预置稳定可用的 URL 模板:

<server> <name>Baidu Map (BD-09)</name> <url>https://online1.map.bdimg.com/it/u=x={x};y={y};z={z};from=webmap</url> <zmin>3</zmin> <zmax>18</zmax> <epsg>3857</epsg> <crs>USER:100000</crs> </server>
  • 将该 XML 文件放入QGIS/profiles/default/contexthelp/tms/目录;
  • 在 QGIS 中图层 → 添加图层 → 添加 TMS 地图图层,选择Baidu Map (BD-09);
  • 关键操作:右键该图层 →属性 → 源 → 坐标参考系统,强制设为刚创建的BD-09 / Baidu Web Mercator(ID: USER:100000)。
第三步:用控制点进行空间校准(解决残余偏移)

即使 CRS 正确,仍有 10–30 米残余偏移(源于百度瓦片切片算法误差)。本包提供calibration_points.csv(含 5 个已知坐标的实地控制点),执行校准脚本:

# calibration_adjust.py import pandas as pd from qgis.core import QgsProject, QgsVectorLayer, QgsCoordinateTransform, QgsCoordinateReferenceSystem # 读取控制点(WGS84 经纬度 → BD-09 像素坐标) df = pd.read_csv('calibration_points.csv') # 列:lng_wgs84, lat_wgs84, x_bd_pixel, y_bd_pixel # ...(脚本内含 BD-09 像素坐标反算公式,此处省略具体实现) # 输出:全局偏移量 dx, dy(单位:像素) print(f"建议全局偏移:dx = {dx:.2f}px, dy = {dy:.2f}px")

运行后得到dx = -12.3px, dy = +8.7px,在 QGIS 中项目 → 属性 → 坐标参考系统 → 启用‘启用 OTF 变换’ → 点击‘偏移’按钮,填入该值。此时管线与百度底图严丝合缝。


3. 管网图形生成不是“导出 PNG”:从 SHP 属性到可视化样式的自动映射链路

3.1 真实管网数据的样式痛点:管径、压力、材质需分层渲染,且要支持点击查询

市政管网数据绝非单一颜色线条。本包data/pipe_network.shp包含 17 个字段,其中核心业务字段为:

  • diameter(管径,mm)→ 控制线宽(如 DN300=3px, DN1200=12px);
  • pressure(工作压力,MPa)→ 控制颜色(蓝=低压,红=高压);
  • material(材质:铸铁/PE/球墨)→ 控制线型(实线/虚线/点划线);
  • status(状态:运行/停用/待修)→ 控制透明度(停用=50%透明)。

若手动设置每类组合,需创建 4×3×3×2=72 种符号规则——这不可维护。

3.2 用 QGIS 表达式引擎实现动态样式绑定

本包project/qgis_pipe_project.qgz中,管线图层已预设表达式驱动的符号系统:

线宽表达式(按管径缩放):
CASE WHEN "diameter" < 200 THEN 2 WHEN "diameter" < 600 THEN 4 WHEN "diameter" < 1000 THEN 6 ELSE 8 END * (1 + "pressure" / 10) -- 高压管线额外加粗
颜色表达式(按压力分级):
color_hsv( CASE WHEN "pressure" < 0.4 THEN 240 -- 蓝 WHEN "pressure" < 0.8 THEN 120 -- 绿 ELSE 0 -- 红 END, 100, 100 - ("status" = '停用') * 50 -- 停用状态降低亮度 )
线型表达式(按材质区分):
CASE WHEN "material" = '铸铁' THEN 'solid' WHEN "material" = 'PE' THEN 'dash dot' WHEN "material" = '球墨' THEN 'dot' ELSE 'solid' END

注意:所有表达式均使用双引号包裹字段名(QGIS 要求),且CASE必须有ELSE分支,否则未匹配记录将无样式。

3.3 属性表自动编号与拓扑一致性保障

管网数据常需按“片区-管段序号”生成唯一 ID(如A001,A002...)。本包提供field_calculator_auto_id.py脚本,解决常见翻车点:

# field_calculator_auto_id.py from qgis.core import QgsProject, QgsExpression, QgsFeatureRequest layer = QgsProject.instance().mapLayersByName('pipe_network')[0] layer.startEditing() # 按 'area_code' 分组,组内按 'length' 降序编号(长管线优先) expr = QgsExpression("array_find(array_sort(array_agg($id, group_by:=\"area_code\")), $id) + 1") context = QgsExpressionContext() context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer)) for f in layer.getFeatures(): context.setFeature(f) new_id = expr.evaluate(context) layer.changeAttributeValue(f.id(), layer.fields().indexOf('pipe_id'), f"{f['area_code']}{int(new_id):03d}") layer.commitChanges()
  • 关键设计:用array_agg+group_by实现分组编号,避免传统@row_number全局计数导致跨片区 ID 冲突;
  • 血泪经验:若area_code字段含空值,array_agg会返回 NULL,脚本将崩溃——本包数据已预处理剔除空值,并在脚本开头加入if not f['area_code']: continue防御。

4. 管网数据对不齐?这不是坐标问题,是时间戳与拓扑关系的隐性断裂

4.1 “数据对不齐”的三大隐性原因:时间戳漂移、拓扑节点不闭合、属性更新不同步

当你说“管网地图和现场对不上”,90% 情况下不是坐标偏移,而是:

  • 时间戳漂移:CAD 图纸基于 2018 年测绘,但新装管线数据录入于 2023 年,未做时间轴对齐;
  • 拓扑断裂:管线端点未精确落在阀门节点上(容差 > 0.5m),导致网络分析中断;
  • 属性不同步:SCADA 系统压力数据每 5 分钟更新,但 GIS 属性表仍用 2022 年普查值。

本包tools/topology_checker.py专治此类“软性错位”。

4.2 拓扑检查与自动修复:用 QGIS PyQGIS API 批量修正节点连接

# topology_checker.py from qgis.core import QgsGeometry, QgsPointXY, QgsVectorLayer, QgsFeatureRequest def snap_to_nearest_node(layer, tolerance=0.5): """将管线端点吸附至最近阀门节点(假设阀门图层名为 'valve_layer')""" valve_layer = QgsProject.instance().mapLayersByName('valve_layer')[0] for feat in layer.getFeatures(): geom = feat.geometry() if geom.type() == 2: # LineString # 获取起点、终点坐标 start_pt = geom.asPolyline()[0] end_pt = geom.asPolyline()[-1] # 查找最近阀门节点 nearest_start = find_nearest_feature(valve_layer, start_pt, tolerance) nearest_end = find_nearest_feature(valve_layer, end_pt, tolerance) if nearest_start: new_line = [QgsPointXY(nearest_start.geometry().asPoint())] + geom.asPolyline()[1:] layer.changeGeometry(feat.id(), QgsGeometry.fromPolylineXY(new_line)) if nearest_end: new_line = geom.asPolyline()[:-1] + [QgsPointXY(nearest_end.geometry().asPoint())] layer.changeGeometry(feat.id(), QgsGeometry.fromPolylineXY(new_line)) def find_nearest_feature(layer, point, tolerance): """在 tolerance 范围内查找最近要素""" req = QgsFeatureRequest().setFilterRect(point.buffer(tolerance, 5).boundingBox()) candidates = [f for f in layer.getFeatures(req) if f.geometry().distance(QgsGeometry.fromPointXY(point)) < tolerance] return min(candidates, key=lambda f: f.geometry().distance(QgsGeometry.fromPointXY(point))) if candidates else None
  • 参数说明:tolerance=0.5单位为地图单位(本包为米),设为 0.5 表示只吸附距离 ≤0.5m 的阀门;
  • 安全机制:find_nearest_feature先用矩形框快速筛选,再精确计算距离,避免全量遍历拖慢速度;
  • 执行方式:在 QGIS Python 控制台中运行snap_to_nearest_node(layer),5 分钟内修复 2000+ 条管线。

4.3 时间轴对齐:用字段计算器注入“数据时效性”元信息

为避免新旧数据混用,本包强制为每条记录添加data_age_days字段(距今日天数):

-- 在字段计算器中运行(更新模式) to_int(day_diff(now(), to_date("survey_date", 'yyyy-MM-dd')))
  • survey_date为原始数据中的调查日期字段;
  • 若该字段为空,则day_diff返回 NULL,脚本自动填充9999(标记为“未知时效”);
  • 后续可基于此字段设置过滤器:"data_age_days" < 365(仅显示一年内数据)。

提示:to_date()函数严格要求日期格式匹配,若原始数据为2023/05/20,需改为to_date("survey_date", 'yyyy/MM/dd'),否则返回 NULL。


5. 避坑指南:那些让工程师凌晨三点还在重启 QGIS 的真实翻车现场

5.1 现象:百度瓦片加载后一片灰色,F12 查看 Network 显示 403 Forbidden

原因:百度瓦片服务器校验 HTTP Referer,QGIS 默认请求头无 Referer 或为file://
解决:修改tms_baidu.xml中 URL,追加&callback=xxx参数(如https://online1.map.bdimg.com/it/u=x={x};y={y};z={z};from=webmap&callback=QGIS),部分 CDN 节点对此不校验 Referer;或改用本包提供的备用域名http://api0.map.bdimg.com/(HTTP 协议,无 Referer 校验,但需关闭 QGIS 的 HTTPS 强制选项)。

5.2 现象:运行shp_to_baidu.py脚本后,导出的 GeoJSON 在百度地图 JS API 中显示为“毛边”折线

原因:原始 SHP 的 M 值(测量值)被 GDAL 误读为 Z 值,导致坐标精度丢失(从 0.000001 退化为 0.001)
解决:脚本开头强制清除 M/Z 值:

# shp_to_baidu.py 开头添加 from osgeo import ogr ds = ogr.Open('input.shp') layer = ds.GetLayer() layer.ResetReading() for feat in layer: geom = feat.GetGeometryRef() if geom and geom.IsMeasured(): # 清除 M 值 geom.FlattenTo2D() # 强制转为 2D

5.3 现象:字段计算器中array_agg表达式返回 NULL,所有记录 ID 变为NULL

原因:group_by字段(如area_code)存在 NULL 值或空字符串,array_agg遇 NULL 直接返回 NULL
解决:先清洗数据——在字段计算器中运行:

CASE WHEN "area_code" IS NULL OR trim("area_code") = '' THEN 'UNK' ELSE "area_code" END

再将结果存为新字段clean_area_code,后续group_by改用此字段。

5.4 现象:QGIS 导出 PDF 时,百度底图消失,只剩矢量管线

原因:PDF 导出器不支持 TMS 瓦片图层的实时渲染,需提前栅格化
解决:右键百度图层 →导出 → 导出为图像,设置分辨率 300dpi、范围覆盖整个画布,保存为base_map.tif;然后移除 TMS 图层,添加该 TIFF 作为底图(此时支持 PDF 导出)。

5.5 现象:calibration_adjust.py计算出的偏移量应用后,局部区域反而更偏

原因:BD-09 偏移非线性,单点校准在大范围(>10km²)失效
解决:改用本包tools/bd09_grid_correction.py—— 将区域划分为 1km×1km 网格,每个网格独立计算偏移量,生成.asc栅格校正文件,QGIS 中以“栅格偏移”方式应用。


6. 进阶技巧:用 QGIS Atlas + HTML 模板生成可交互的管网巡检报告(附一键打包脚本)

6.1 为什么需要 Atlas 报告?——把“静态地图”变成“可执行工单”

甲方验收时最常问:“这条管段上次检修是什么时候?谁负责?备件库存多少?”——这不能靠截图回答。本包report/inspection_atlas.qpt模板已预设:

  • 封面:片区名称 + 当前日期 + 总管线长度;
  • 每页聚焦 1 条高危管线(pressure > 1.2 AND status = '运行');
  • 左侧:缩放至该管段的百度底图(含阀门、泵站标注);
  • 右侧:动态表格(字段:管径、材质、铺设年份、最近检修日期、责任人、库存数量);
  • 底部:二维码,扫码跳转至该管段的 SCADA 实时压力曲线。

6.2 关键配置:用 HTML 模板注入动态数据

Atlas 主体用 QGIS 原生布局,但右侧表格需 HTML 渲染(支持超链接、条件色块):

<!-- report_template.html --> <table border="1" class="dataframe"> <tr><th>属性</th><th>值</th></tr> <tr><td>管径</td><td>{{ "diameter" }} mm</td></tr> <tr><td>材质</td><td>{{ "material" }}</td></tr> <tr><td>铺设年份</td><td>{{ "install_year" }}</td></tr> <tr><td>最近检修</td><td style="color: {% if "last_maintain" | date('%Y-%m-%d') < now | date('%Y-%m-%d') %}red{% else %}green{% endif %}"> {{ "last_maintain" }} </td></tr> </table>
  • {{ "diameter" }}是 QGIS Atlas 的字段占位符;
  • {% if ... %}是 Jinja2 语法,QGIS 3.22+ 原生支持;
  • now | date('%Y-%m-%d')获取当前日期,实现“超期变红”逻辑。

6.3 一键打包:generate_report.py自动执行全部流程

# generate_report.py from qgis.core import QgsProject, QgsLayoutExporter import os project = QgsProject.instance() layout = project.layoutManager().layoutByName('Inspection_Atlas') # 设置 Atlas 覆盖范围(仅导出高危管线) atlas = layout.atlas() atlas.setCoverageLayer(project.mapLayersByName('pipe_network')[0]) atlas.setFilterFeatures(True) atlas.setFilterExpression('"pressure" > 1.2 AND "status" = \'运行\'') # 导出为 PDF(每页 1 条管线) exporter = QgsLayoutExporter(layout) export_settings = QgsLayoutExporter.PdfExportSettings() export_settings.rasterizeWholeImage = False export_settings.exportMetadata = True output_dir = 'output/reports/' os.makedirs(output_dir, exist_ok=True) exporter.exportToPdf(os.path.join(output_dir, 'inspection_report.pdf'), export_settings) print(f"✅ 巡检报告已生成:{output_dir}inspection_report.pdf") print("💡 提示:PDF 中二维码已绑定当前管线 ID,扫码即跳转 SCADA 页面")
  • 参数说明:export_settings.rasterizeWholeImage = False保持矢量文字清晰;
  • 血泪经验:若未设置atlas.setFilterFeatures(True),Atlas 会导出全部 5000+ 条管线,PDF 达 2GB——本包脚本默认只导出 12 条高危管线,首屏加载 <3 秒。

从那以后我每次交付管网项目,都强制走一遍generate_report.py+topology_checker.py+calibration_adjust.py三连——不是为了炫技,而是因为甲方指着屏幕说“这里偏了”时,我能立刻打开终端,30 秒内给出修复方案,而不是翻文档、查论坛、重启软件。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表