十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深圳街道级地图数据处理:SVG解析、JSON转换与可视化应用

深圳街道级地图数据处理:SVG解析、JSON转换与可视化应用 简介深圳街道级行政区划地图数据包覆盖全市十个区及大鹏新区适合城市规划、地理信息研究、地图应用开发等场景使用。包内提供SVG矢量地图、JSON地理坐标数据及PNG预览图三类文件SVG可无损缩放用于精细制图与交互设计JSON适合导入GIS软件进行空间查询与密度分析PNG便于快速查看整体布局。文件总数42个压缩包大小1.23MB结构按区分文件夹存放检索方便。已有3424人学习下载。通过这组数据读者可快速获取各区街道边界轮廓辅助完成人口分布、商业选址、交通流量等专题研究也可直接用于教学演示或地图产品开发。1. 为什么街道级区划地图要同时给出 SVG、JSON 和 PNG深圳十个辖区的街道级边界平时散落在不同平台的数据集里不是缺这个区就是少那个区想凑齐一套矢量轮廓和坐标数据往往得自己做一轮清洗。这份《深圳各区区划地图街道级.rar》把南山区、福田区、罗湖区、宝安区、龙岗区、盐田区、龙华区、坪山区、光明区和大鹏新区的 SVG 矢量轮廓、JSON 坐标数据以及 PNG 预览图全部按区编排解压出来就能直接用省去跨平台凑素材的功夫。对两类人价值最大一类是做可视化大屏和 Web 地图的前端开发者拿 SVG 当渲染底图另一类是搞地理数据分析和城市规划的数据工程师拿 JSON 做空间统计。SVG 把轮廓画得足够清楚JSON 把街道边界的坐标暴露成可计算的字段两者互相补充。接下来直接看文件内部结构把解析、转换和验证这两套数据的完整流程走一遍。2. 解包先做结构分析区划目录、SVG path 与 JSON geometry2.1 压缩包的目录组织与命名规律解压后整体目录结构如下深圳各区区划地图街道级/ ├── 1-南山区/ │ ├── 南山区.svg │ ├── 南山区.json │ ├── 南山区.png │ └── 南山区-缩.png ├── 2-福田区/ │ ├── 福田区.svg │ ├── 福田区.json │ ├── 福田区.png │ └── 福田区-缩.png ├── 3-罗湖区/ ├── 4-盐田区/ ├── 5-宝安区/ ├── 6-龙岗区/ ├── 7-坪山区/ ├── 8-光明区/ ├── 9-龙华区/ ├── 10-大鹏新区/ ├── 全深圳.png └── shenzheng.svg目录命名由“序号 区名”组成序号从 1 到 10大鹏新区排在最后。每个区目录下固定有四种文件SVG、JSON、PNG 和-缩.png。PNG 适合做列表缩略图-缩.png是更低分辨率的版本用在地图加载占位。根目录的shenzheng.svg和全深圳.png是十个区拼好的全域视图适合需要整体展示深圳轮廓的场景。在实际项目里建议先扫一遍目录生成区名到文件路径的映射表后续切图、着色、数据关联都从这张表取路径避免硬编码。这套包没有自带说明文件数据来源、坐标系和更新时间需要自己通过内容判断。2.2 SVG 文件内部path 节点、viewBox 与街道命名用文本编辑器打开任意一个区的 SVG结构大致如下svg xmlnshttp://www.w3.org/2000/svg viewBox0 0 1000 800 width100% height100% g id南山区 path id南头街道 dM123.4,56.7 L145.6,78.9 L167.8,100.2 Z fill#f7f7f7 stroke#999999 stroke-width0.5/ path id粤海街道 dM167.8,100.2 L190.1,122.5 L210.3,140.1 Z fill#f7f7f7 stroke#999999 stroke-width0.5/ /g /svgviewBox是绘制坐标系的范围四位数字分别表示左上角 x、左上角 y、逻辑宽度和高度。d属性中M是起点L是经过的点Z闭合路径。这里要特别注意这类导出地图的 SVGpath坐标是画布像素坐标不是经纬度直接贴到 Leaflet 或 Mapbox 上会错位必须做坐标换算。命令含义参数M移动到起点x yL画直线到指定点x yH水平直线xV垂直直线yZ闭合路径无id与街道名的对应关系也不完全可靠。部分工具导出时会把id写成内部序号街道名放在>{ type: FeatureCollection, features: [ { type: Feature, properties: { name: 南头街道 }, geometry: { type: MultiPolygon, coordinates: [ [ [ [113.929, 22.533], [113.931, 22.534], [113.933, 22.531] ] ] ] } } ] }properties里保存的是街道名称和可能的行政编码geometry.type决定coordinates的嵌套层数。判断规则很直接Polygon是三级嵌套MultiPolygon是四级嵌套。解析时先读geometry.type再决定取第几层否则碰到 MultiPolygon 直接按 Polygon 取索引会越界。把两份文件放在一起看分工就很明确了SVG 负责把轮廓呈现到页面上JSON 负责给程序提供可计算的坐标序列。用街道名当主键两套数据就能关联起来。3. 解析 SVG path 并生成 GeoJSON街道轮廓的提取与坐标转换3.1 用 Python 批量提取十个区的街道 path压缩包里没有现成的汇总 GeoJSON第一步就是把十个区的 SVG 批量读一遍。用标准库xml.etree.ElementTree解析即可不依赖额外库import os import xml.etree.ElementTree as ET BASE_DIR 深圳各区区划地图街道级 NS {svg: http://www.w3.org/2000/svg} result {} for entry in sorted(os.listdir(BASE_DIR)): district_path os.path.join(BASE_DIR, entry) if not os.path.isdir(district_path): continue svg_file next( (f for f in os.listdir(district_path) if f.endswith(.svg)), None ) if svg_file is None: continue tree ET.parse(os.path.join(district_path, svg_file)) root tree.getroot() streets [] for node in root.findall(.//svg:path, NS): name node.get(id) or node.get(data-name) or f未命名_{len(streets)} d node.get(d, ) if d: streets.append({name: name, d: d}) result[entry] streets for district, streets in result.items(): print(f{district}: {len(streets)} 个街道)代码逻辑说明先遍历BASE_DIR下的子目录目录名即区名用next取目录内第一个.svg文件避免路径写死findall(.//svg:path, NS)查找全部 path 节点NS是 SVG 命名空间字典不传这个参数path 节点会匹配不到。街道名优先取id缺失时退到>import re def parse_d_to_points(d: str): tokens re.findall(r[MLHVQCZ]|[-]?\d*\.?\d, d) points [] i 0 while i len(tokens): cmd tokens[i] if cmd in (M, L): x float(tokens[i 1]) y float(tokens[i 2]) points.append((x, y)) i 3 elif cmd Z: break else: i 1 return points处理流程是每遇到M或L就取后面两个数字作为坐标点遇到Z代表路径闭合停止解析。tokens[i]是命令字符tokens[i1]和tokens[i2]是坐标数值。若碰上C、Q这类曲线指令简版会跳过控制点对整体面积和相对形状影响不大如果需要精确重建再用svgpathtools做完整解析。3.3 坐标映射从 viewBox 像素坐标到经纬度SVG 的像素坐标系和经纬度坐标系不直接对应要做一个线性映射。思路是先把像素坐标按 viewBox 范围归一化再用同一比例映射到该区的经纬度包围盒 bbox 里function svgToLngLat(px, py, viewBox, bbox) { const [vx0, vy0, vw, vh] viewBox; const [lng0, lat0, lng1, lat1] bbox; const ratioX (px - vx0) / vw; const ratioY (py - vy0) / vh; const lng lng0 ratioX * (lng1 - lng0); const lat lat1 - ratioY * (lat1 - lat0); return [lng, lat]; }viewBox取 SVG 根节点的四个值bbox是目标区的经纬度范围可以从随包的 JSON 坐标里算出。最需要注意的是纬度计算SVG 的 y 轴向下递增纬度从南到北递增因此ratioY必须用lat1 - ratioY * (lat1 - lat0)否则映射出来的图形上下颠倒。这个线性变换依赖“源 SVG 为正交投影”的假设对绝大多数从地图平台导出的行政区划素材成立如果换成墨卡托投影的经纬度数据要改成投影反算。3.4 输出可复用的 GeoJSON 文件解析完成后把坐标写进标准 GeoJSON后面无论是 QGIS、GeoPandas 还是 Leaflet 都能直接读取import json geojson { type: FeatureCollection, features: [] } for district, streets in result.items(): for s in streets: points parse_d_to_points(s[d]) if len(points) 3: continue coordinates [ [svgToLngLat(x, y, view_box, bbox) for x, y in points] ] geojson[features].append({ type: Feature, properties: { district: district, name: s[name] }, geometry: { type: Polygon, coordinates: coordinates } }) with open(shenzhen_streets_from_svg.geojson, w, encodingutf-8) as f: json.dump(geojson, f, ensure_asciiFalse)生成 GeoJSON 的关键点少于三个点的 path 直接跳过因为无法构成有效面坐标对的顺序要保持一致否则某些 GIS 工具会报拓扑错误ensure_asciiFalse保证中文字段以 UTF-8 写出。这段代码里的view_box和bbox是示意变量实际建议每个区单独配置一份元数据字典在循环里按district读取避免共用包围盒导致边缘街道偏移。4. JSON 坐标数据的空间分析坐标系识别、面积计算与完整性校验4.1 坐标系判断从经纬度范围到偏移量比对JSON 的坐标字段可以直接读进代码但坐标系不确认所有统计结果都不可信。深圳本地数据最常碰到三种坐标系WGS-84、GCJ-02 和 BD-09数值范围很接近肉眼很难区分。下面的判断表可以作为第一步坐标系经度范围纬度范围坐标示例WGS-84113.75 ~ 114.6522.43 ~ 22.85113.920000, 22.540000GCJ-02113.75 ~ 114.6522.43 ~ 22.85113.923456, 22.541234BD-09113.76 ~ 114.6622.44 ~ 22.86113.929876, 22.547654EPSG:385712660000 ~ 127600002570000 ~ 263000012678384, 2568230如果 JSON 坐标是千万元素级别的整数直接按 EPSG:3857 处理如果是 113 开头的小数需要进一步判断偏移量。用一个街道边界上的已知锚点与底图对比或者用pyproj把 WGS-84 转成 GCJ-02 后和文件坐标做差import json from pyproj import Transformer with open(南山区.json, encodingutf-8) as f: data json.load(f) coords data[features][0][geometry][coordinates] sample_lng coords[0][0][0][0] sample_lat coords[0][0][0][1] print(文件坐标:, sample_lng, sample_lat)这段代码从第一条 feature 里取第一个坐标点并输出。拿到数值后与在线底图上同一个街道的标志性位置比对偏移几十米且方向规律基本可认定为 GCJ-02。这一步决定了后续在做空间统计时要不要先做投影转换。需要注意pyproj不是标准库使用前先执行pip install pyproj。4.2 用 geopandas 做街道级面积与空间统计坐标基准确认后GeoPandas 读 JSON 成本很低。下面这段代码计算南山区每个街道的近似面积和代表性中心点import geopandas as gpd gdf gpd.read_file(南山区.json) gdf[area_km2] gdf.geometry.area / 1_000_000 gdf[center] gdf.geometry.representative_point() print(gdf[[name, area_km2]].sort_values(area_km2, ascendingFalse))geometry.area的计算单位取决于当前坐标系。如果gdf是经纬度得到的是平方度只能横向比较相对大小不能直接当平方公里。要拿真实面积需先将数据转换到深圳所在地区的 UTM 投影即 EPSG:32649gdf gdf.to_crs(EPSG:32649) gdf[area_km2] gdf.geometry.area / 1e6to_crs负责投影转换转换后area的单位变为平方米除以 1e6 得到平方公里。representative_point()返回多边形内部的代表性点比centroid()更适合画 label因为后者在多边形是凹形时可能落到图形外部。4.3 街道数量缺失检查与边界差异分析拿到十个区的 JSON 后最先做街道数量核对。把name字段打印出来和正规划区名录比对也可以用脚本检查明显缺失expected { 南山区: [南头, 南山, 沙河, 蛇口, 招商, 粤海, 西丽, 桃源], 福田区: [园岭, 南园, 福田, 沙头, 梅林, 华富, 香蜜湖, 莲花, 福保, 华强北] } for district, names in expected.items(): gdf gpd.read_file(f{district}.json) actual set(gdf[name].str.replace(街道, )) missing set(names) - actual extra actual - set(names) if missing or extra: print(district, 缺少:, missing, 多出:, extra)expected需要根据当前行政区划版本手动维护replace(街道, )是为了去掉后缀再比对避免“蛇口”和“蛇口街道”匹配不上。出现差异时要判断是街道调整、文件缺块还是命名不一致导致不能直接忽略。深圳近几年做过多次街道拆分旧数据和现势数据对不上是常见情况。5. 把 SVG 轮廓和 JSON 数据联动街道级可视化与细节调优5.1 用 JSON 驱动 SVG 着色的联动方案SVG 和 JSON 的链路打通后常见落地场景是街道级指标地图。实现思路是 SVG 负责呈现轮廓JSON 负责存指标数值用街道名关联svg idmap viewBox0 0 1000 800 path>gdf_simple gdf.copy() gdf_simple[geometry] gdf.geometry.simplify( 0.0001, preserve_topologyTrue )simplify中0.0001是最小允许误差单位与当前坐标系一致。经纬度坐标系下 0.0001 度约合 11 米街道轮廓整体形状不变但顶点数会明显下降。preserve_topology设为True可以避免多边形之间出现重叠或空隙。化简后的数据既能用于 GeoJSON 输出也能转成 SVG path 画到页面。这套压缩包里的 SVG、JSON 和 PNG 到这里就全部串起来了SVG 当渲染层JSON 当数据层PNG 做预览兜底。调试时如果发现某个街道的轮廓在放大后出现锯齿别忘了给对应的path加上shape-renderinggeometricPrecision矢量地图的边缘毛刺多半出在这个属性上。本文还有配套的精品资源点击获取
返回列表