简介这份南京市区县乡镇街道及交通设施矢量数据包面向城市规划、地理信息研究与GIS教学实训场景解决本地基础空间数据获取零散、图层不全的问题。资源覆盖南京市全域包含市、区、县、镇、村多级行政边界国道、省道、县道、高速、地铁轻轨、铁路及乡村道路等线状交通要素并整合水系、绿地、岛屿等面状自然要素以及公交站、地铁出入口、停车场、收费站、学校、医院、银行、超市、餐饮、药店、宾馆酒店、政府机构、公园、大厦等二十余类点状POI。压缩包共184个文件以shp、shx、dbf、prj、xml等标准SHP配套文件为主另有mxd工程、py脚本与说明文本整体约11.17MB坐标系为WGS84或CGCS2000投影信息完整可直接在ArcGIS、QGIS中叠加分析与制图。目前已有84人学习下载适合需要快速搭建南京空间分析底图、开展城市研究与课程实训的读者。1. 南京市区县乡镇街道及交通设施矢量数据包从一份SHP到一套可用的空间底图如果你做过南京相关的空间分析、地图可视化或者选址评估大概率遇到过这种局面底图能拿到但行政边界只到区一级乡镇街道这一层要么缺失要么年份对不上道路和水系更是各说各话。一份包含区县、乡镇街道、道路、水系、POI 的 SHP 矢量数据包解决的正是这个断层——它把多层级行政单元和交通设施要素统一到同一套坐标体系下让你不用再花两三天做数据对齐。这类数据包适合做城市研究、物流路径预判、商业网点覆盖分析也适合需要快速搭建本地空间数据库的开发者。但拿到压缩包只是起点真正决定成败的是坐标系、字段结构和拓扑质量这三件事。2. 先搞清楚数据包里到底有什么图层清单与字段结构2.1 五类核心图层的用途与取舍一份典型的南京矢量数据包通常按要素类型拆成多个 SHP 文件。SHP 格式本身是 Esri 定义的一种矢量数据交换格式一个完整图层至少包含 .shp、.shx、.dbf 三个文件缺一不可。下面这张表是我拿到任何一份行政交通数据包时第一遍会核对的图层清单图层类型几何类型典型字段主要用途区县边界Polygon名称、代码、面积宏观统计、分区着色乡镇街道边界Polygon名称、所属区县、代码精细化治理、网格分析道路中心线LineString名称、等级、长度路网分析、可达性计算水系面/线Polygon/LineString名称、类型、等级生态廊道、洪涝评估POI 点Point名称、类别、坐标设施覆盖、热力分析这里有个容易翻车的点乡镇街道边界在不同数据源里有的按民政区划有的按实际管理范围两者在城乡结合部差异明显。我一般会先看字段里有没有“区划代码”和“统计用代码”两套编码如果有说明数据制作者考虑过口径问题如果只有名称那就要警惕同名街道跨区的情况。2.2 用 QGIS 快速检查图层完整性拿到数据后不要急着导入数据库先用 QGIS 做一次目视检查。QGIS 是开源桌面 GIS对 SHP 的支持最直接。打开后把 .shp 文件拖进去重点看三件事坐标系是否统一、几何有没有明显偏移、属性表字段是否完整。# 用 geopandas 批量读取并检查坐标系与字段 import geopandas as gpd import os data_dir ./nanjing_shp layers [districts, townships, roads, water, poi] for name in layers: path os.path.join(data_dir, f{name}.shp) if not os.path.exists(path): print(f[缺失] {name}.shp 不存在) continue gdf gpd.read_file(path) print(f[{name}] 要素数{len(gdf)}, CRS{gdf.crs}, 字段{list(gdf.columns)}) # 检查几何有效性 invalid (~gdf.geometry.is_valid).sum() if invalid 0: print(f 警告{invalid} 个无效几何)这段代码的逻辑很直白逐个读取图层打印要素数量、坐标参考系和字段名同时统计无效几何。参数上data_dir换成你解压后的实际路径layers列表按压缩包内文件名调整。如果某个图层 CRS 显示为 None说明缺少 .prj 文件这时候不要硬猜坐标系先找数据说明。提示SHP 的 .prj 文件只记录坐标系定义不记录投影转换参数。如果 .prj 丢失即使你知道是 CGCS2000也需要确认是地理坐标还是投影坐标。2.3 坐标系判断为什么这一步不能省南京地区常见坐标系有三种WGS84 地理坐标EPSG:4326、CGCS2000 地理坐标EPSG:4490、以及各类高斯克吕格投影坐标。很多数据包为了兼容 Web 地图会直接给 WGS84但做面积统计和距离计算时地理坐标的单位是度直接算面积会得到荒谬结果。判断方法很简单看坐标数值范围。如果经纬度数值在 118~119 和 31~33 之间那是地理坐标如果数值是六位数甚至七位数那是投影坐标。我一般会统一转成 EPSG:4326 做展示转成 EPSG:3857 或当地投影做量算。转换用 geopandas 一行搞定# 统一转为 WGS84 地理坐标便于后续叠加 gdf_roads gpd.read_file(./nanjing_shp/roads.shp) if gdf_roads.crs is None: gdf_roads gdf_roads.set_crs(EPSG:4490) # 按数据说明设定 gdf_roads_wgs gdf_roads.to_crs(EPSG:4326) gdf_roads_wgs.to_file(./output/roads_wgs84.shp, encodingutf-8)set_crs是“声明”坐标系to_crs是“转换”坐标系两者不能混用。如果数据本身有 .prj直接to_crs即可如果没有先用set_crs声明再转换。这个顺序搞反数据会飞到几千公里外。3. 把 SHP 用起来入库、裁剪与路网分析的最小流程3.1 导入 PostGIS 做空间查询SHP 适合交换不适合频繁查询。要把数据用起来常见做法是导入 PostGIS。PostGIS 是 PostgreSQL 的空间扩展支持空间索引和空间函数。导入用 shp2pgsql 命令行工具最稳# 将 roads.shp 导入 PostGISSRID 按实际坐标系填写 shp2pgsql -s 4326 -I -W UTF-8 -D ./nanjing_shp/roads.shp public.nj_roads | \ psql -h localhost -U postgres -d gis_db参数说明-s 4326指定 SRID-I创建空间索引-W UTF-8解决中文乱码-D使用 dump 模式比默认的 insert 模式快很多。导入后可以用一条 SQL 查某个区县内的道路长度-- 统计某区县内各等级道路总长度 SELECT r.road_class, SUM(ST_Length(r.geom::geography)) / 1000 AS km FROM nj_roads r JOIN nj_districts d ON ST_Intersects(r.geom, d.geom) WHERE d.name 某区 GROUP BY r.road_class ORDER BY km DESC;这里::geography是把几何转为地理类型让长度单位变成米。如果直接用ST_Length(geom)在 4326 下得到的是度没有意义。ST_Intersects配合空间索引比用属性字段关联快一个量级。3.2 按行政边界裁剪出研究区做具体项目时往往只需要一个区或几个街道。用 geopandas 做裁剪比在 GIS 软件里点选更可复现import geopandas as gpd districts gpd.read_file(./nanjing_shp/districts.shp) roads gpd.read_file(./nanjing_shp/roads.shp) poi gpd.read_file(./nanjing_shp/poi.shp) # 统一坐标系后再裁剪 target_crs districts.crs roads roads.to_crs(target_crs) poi poi.to_crs(target_crs) study_area districts[districts[name] 某区] roads_clip gpd.clip(roads, study_area) poi_clip gpd.clip(poi, study_area) roads_clip.to_file(./output/roads_study.shp, encodingutf-8) poi_clip.to_file(./output/poi_study.shp, encodingutf-8)gpd.clip会保留落在研究区内的几何边界处自动切断。注意裁剪前必须统一 CRS否则 clip 结果为空或报错。如果研究区是多个不相邻的街道先把它们合并成一个 GeoDataFrame 再裁剪。3.3 路网连通性检查与最短路径道路数据用于分析时最大的坑是“断头路”——几何上相交但属性上没有节点。做最短路径前需要先构建拓扑。常见做法是用 networkx 把道路中心线转成图结构import networkx as nx import geopandas as gpd from shapely.geometry import LineString roads gpd.read_file(./output/roads_study.shp) G nx.Graph() for idx, row in roads.iterrows(): line row.geometry if not isinstance(line, LineString): continue coords list(line.coords) # 用首尾点作为节点中间点忽略简化处理 start, end coords[0], coords[-1] length line.length G.add_edge(start, end, weightlength, road_ididx) # 检查连通分量 components list(nx.connected_components(G)) print(f连通分量数{len(components)})这段代码把每条道路的首尾点作为图节点道路长度作为权重。connected_components返回的连通分量数如果远大于 1说明路网存在大量断头。实际项目中我一般会先用ST_Node或shapely.ops.unary_union做节点打断再构建图。参数上weight用几何长度还是通行时间取决于分析目标如果做应急响应应该用限速字段换算时间。注意SHP 字段名有 10 字符限制导入 PostGIS 或转 GeoJSON 时可能被截断。如果原始字段名较长建议在导入前用 ogr2ogr 重命名。4. 避坑与排查SHP 数据包最常见的五个翻车现场4.1 中文乱码属性表打开全是问号现象用 QGIS 打开正常用 Python 读取后中文变成乱码或问号。原因SHP 的 .dbf 文件默认编码是系统区域设置中文 Windows 下常为 GBK而 geopandas 默认按 UTF-8 读取。解决读取时显式指定编码gpd.read_file(path, encodinggbk)如果写入后乱码在to_file时加encodingutf-8。更彻底的办法是用 ogr2ogr 转成 GeoPackageGeoPackage 原生支持 UTF-8。4.2 坐标系“看起来对算起来错”现象地图上位置正确但面积统计结果离谱。原因数据是地理坐标度直接参与了几何计算。解决所有量算前先投影到合适的投影坐标系。南京地区可用 EPSG:4547CGCS2000 3 度带或 EPSG:3857 做近似。判断方法gdf.crs.is_geographic返回 True 就是地理坐标需要to_crs后再算。4.3 乡镇街道边界与区县边界不套合现象乡镇街道拼起来后边缘和区县边界有缝隙或重叠。原因两套数据来源不同或制图综合程度不一致。解决以区县边界为准用gpd.overlay做相交修正如果缝隙很小小于容差可以用buffer(0)或snap处理。但要注意行政边界调整涉及口径问题修正后要在报告里说明。4.4 POI 重复与坐标偏移现象同一家店出现多次或 POI 整体偏移几十米。原因POI 数据多源采集去重不彻底坐标偏移常见于不同坐标系混用。解决按名称坐标距离去重距离阈值设 50 米左右偏移问题用已知控制点做配准或直接与道路数据叠加目视检查。如果偏移是系统性的考虑数据是否经过加密处理。4.5 道路等级字段缺失或口径不一现象想做路网密度分析发现道路等级字段为空或取值混乱。原因数据包只保留了几何属性简化。解决用道路宽度或车道数反推等级或与开源路网数据做空间连接补全。如果都没有只能按几何长度做加权在结论里注明局限性。5. 进阶技巧用空间连接把 POI 挂到街道上前面把数据读进来、裁出来、跑通路径最后一步往往是把 POI 和行政单元关联起来做分街道的设施统计。这个操作叫空间连接核心是判断点落在哪个面内。geopandas 的sjoin一行就能完成但有几个参数决定结果对不对。import geopandas as gpd townships gpd.read_file(./nanjing_shp/townships.shp) poi gpd.read_file(./nanjing_shp/poi.shp) # 统一坐标系 poi poi.to_crs(townships.crs) # 空间连接点落在面内 poi_with_town gpd.sjoin(poi, townships, howleft, predicatewithin) # 统计每个街道的 POI 数量 count_by_town poi_with_town.groupby(name_right).size().reset_index(namepoi_count) print(count_by_town.sort_values(poi_count, ascendingFalse).head(10))howleft保证所有 POI 都保留落在边界外的会标记为 NaNpredicatewithin表示点完全在面内。如果有些 POI 正好落在边界线上within会漏掉可以改用intersects但那样边界点可能被两个街道同时统计。我一般先用within再单独检查未匹配的点看是数据问题还是边界问题。另一个实用技巧是计算每个街道的 POI 密度。直接用数量除以面积面积要从投影坐标算# 投影后计算面积再算密度 townships_proj townships.to_crs(EPSG:4547) townships_proj[area_km2] townships_proj.geometry.area / 1e6 # 合并统计结果 result townships_proj.merge(count_by_town, left_onname, right_onname_right, howleft) result[poi_density] result[poi_count] / result[area_km2] print(result[[name, poi_count, area_km2, poi_density]].sort_values(poi_density, ascendingFalse).head())这里EPSG:4547是 CGCS2000 的 3 度带投影单位是米面积除以 1e6 得到平方公里。密度排序能快速看出哪些街道商业设施集中但要注意面积口径——如果街道边界包含大片水域或山地密度会被低估这时候需要先扣除不可达区域。最后说一个我踩过的坑空间连接前一定要检查两个图层的坐标系是否一致。有一次我直接 sjoin结果所有 POI 都没匹配上排查半天才发现一个图层是 4326另一个是 3857。现在我的习惯是任何空间操作前先打印gdf.crs确认一致再往下走。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?