简介这份资源是2022年7月整理的内蒙古自治区GIS矢量数据集面向地理信息研究者、交通规划人员、区域经济分析者及高校师生用于行政区划研究、路网分析与空间制图等场景。压缩包约26.32MB内含省级、地市、区县三级行政边界以及道路网与铁路网等SHP矢量文件并配套prj坐标参考与dbf属性表可支撑空间查询、投影对齐与专题制图。借助这些数据读者能快速搭建内蒙古空间分析底图开展行政边界对比、道路铁路连通性评估、物流路径优化与应急响应定位等工作也可与人口、经济等统计资料叠加完成区域发展与环境影响评估。资源标签涵盖内蒙古行政边界、道路网与铁路网已有1338人学习下载适合作为区域研究与规划决策的基础数据参考。1. 拿到一份 2022.07 的内蒙古 SHP 数据先别急着往 GIS 里拖如果你手上正好有一份标注为 2022.07 的内蒙古行政区划 SHP 文件里面同时包含省级、地市、区县三级边界外加道路网和公路网两层线数据那这大概率是一个「看起来什么都有、用起来处处要命」的组合包。我见过太多人第一反应是双击拖进 QGIS 或 ArcGIS结果要么中文属性全是乱码要么区县边界和道路网对不上要么坐标系一叠加就飘到几百公里外。这份数据的价值不在于「有」而在于你能不能把它清洗成一套可复用的底图资产——省级做宏观统计、地市做区域对比、区县做精细化分析道路网和公路网则承担连通性和可达性计算。适合谁做区域经济分析、物流路径规划、应急资源调度、地图可视化大屏的从业者以及需要一套稳定行政底图做空间 join 的数据工程师。下面我按实际处理顺序把这份数据从「能打开」推到「能进生产」。2. 三级行政区划的字段结构与属性表清洗2.1 先搞清楚省级、地市、区县三层到底怎么分文件拿到 SHP 包第一步不是打开属性表而是先看目录里到底有几个.shp。常见做法是三层各一个文件比如省级一个面文件、地市一个面文件、区县一个面文件也有偷懒的打包方式把三级塞进同一个面文件靠一个level字段区分。这两种结构处理逻辑完全不同先确认再动手。用ogrinfo快速扫一遍比在 GUI 里点来点去靠谱得多# 列出目录下所有 shp 的基本信息不加载几何 for f in *.shp; do echo $f ogrinfo -so -al $f | head -30 done这段命令做两件事-so表示只输出摘要不读几何-al表示列出所有图层。输出里重点看三样东西——Feature Count要素数量、Geometry是 Polygon 还是 MultiPolygon、Extent范围。如果区县文件要素数在 100 上下基本符合内蒙古的县级单元规模如果只有十几个那多半是地市文件被误命名了。逻辑说明先做文件级盘点避免后面把地市当区县处理。参数上head -30只是防止输出刷屏真正要关注的是Extent的坐标范围——如果数值在 100 左右说明是投影坐标米如果在 110 左右说明是地理坐标度。这一步决定了后面要不要做投影转换。2.2 属性表里必须保留和必须丢弃的字段行政区划 SHP 的属性表通常字段很杂有标准代码、名称、拼音、面积、上级代码也可能混进一些导出时残留的临时字段。我的原则是只保留能支撑空间 join 和统计聚合的最小字段集。典型需要保留的字段字段名含义用途adcode行政区划代码唯一主键做 joinname行政区名称展示与匹配level层级标识区分省/市/县parent上级代码构建层级关系geometry几何空间分析清洗用 GDAL 的ogr2ogr配合 SQL 一步到位# 只保留必要字段并统一字段名 ogr2ogr -f ESRI Shapefile clean_county.shp raw_county.shp \ -sql SELECT adcode, name, level, parent FROM raw_county \ -lco ENCODINGUTF-8逻辑说明-sql里的 SELECT 显式列出字段等于做了一次投影裁剪把无关字段全部丢掉。-lco ENCODINGUTF-8是关键很多 SHP 的中文乱码就是因为编码没指定默认走了 Latin1。参数上如果你的源文件字段名本身是中文建议在 SQL 里用双引号包住字段名否则部分 GDAL 版本会解析失败。注意SHP 格式的字段名有 10 个字符长度限制adcode、parent这类短名没问题但如果你想把province_name写进去会被截断成province_n后面 join 时对不上。要么改用 GeoPackage要么提前把字段名压短。2.3 用 Python 做一次属性完整性校验清洗完不代表没问题行政区划数据最常见的暗坑是区县代码和地市代码对不上、parent 字段为空、同名不同码。写一段校验脚本把这些情况提前暴露import geopandas as gpd # 读取三层数据 prov gpd.read_file(clean_prov.shp) city gpd.read_file(clean_city.shp) county gpd.read_file(clean_county.shp) # 校验1区县的 parent 是否都能在地市里找到 city_codes set(city[adcode].astype(str)) county_parent set(county[parent].astype(str)) orphan county_parent - city_codes print(找不到上级的区县 parent:, orphan) # 校验2adcode 是否有重复 for name, gdf in [(省, prov), (市, city), (县, county)]: dup gdf[gdf[adcode].duplicated(keepFalse)] if len(dup) 0: print(f{name}级存在重复 adcode:, dup[adcode].tolist()) # 校验3几何是否有效 for name, gdf in [(省, prov), (市, city), (县, county)]: invalid gdf[~gdf.geometry.is_valid] print(f{name}级无效几何数量:, len(invalid))逻辑说明第一段校验层级关系parent找不到对应adcode说明数据在拼接时丢了一层第二段查主键重复重复会导致后续 join 出现笛卡尔积第三段查几何有效性自相交、空几何在空间运算时会直接报错。参数上astype(str)是为了避免 int 和 str 类型不一致导致的假性不匹配这个坑我踩过不止一次。3. 道路网与公路网的拓扑处理与分级3.1 道路网和公路网的区别以及为什么要分开处理很多人以为道路网和公路网是一回事其实在这类数据包里它们通常代表两个不同精度的图层公路网偏向国道、省道、高速这类骨干路属性里有道路等级和编号道路网则更细可能包含城市道路、县乡道甚至一些无名路段。分开处理的原因是它们的拓扑规则和用途不同——公路网适合做长距离路径分析道路网适合做局部连通性计算。先看两个图层的属性结构差异ogrinfo -so -al road_network.shp | grep -A 20 Fields ogrinfo -so -al highway_network.shp | grep -A 20 Fields重点对比有没有road_class、road_code、oneway这几个字段。如果公路网有road_class而道路网没有那道路网的分级就得靠几何长度或连接度来推断。3.2 打断线段与节点提取的标准流程道路数据做网络分析前必须保证线段在交叉口处是打断的否则路径算法会认为两条交叉的路互不相通。标准做法是先用v.clean或GRASS的break工具在交点处打断# 在交点处打断线段 v.clean inputroad_network outputroad_broken toolbreak threshold0.001 # 提取节点 v.net inputroad_broken outputroad_net operationnodes逻辑说明toolbreak表示在几何交点处把线段拆开threshold0.001是容差单位跟你的坐标系一致——如果是投影坐标米0.001 米足够如果是地理坐标度这个值要放大到 0.00001 左右。打断之后每条线段的端点就是潜在的网络节点后续建图就靠这些节点。如果你不想装 GRASS用 GeoPandas 也能做但性能差不少import geopandas as gpd from shapely.ops import unary_union roads gpd.read_file(road_network.shp) # 合并所有线段后重新拆分强制在交点打断 merged unary_union(roads.geometry) # 这一步会得到 MultiLineString再 explode 成单段 broken gpd.GeoDataFrame(geometry[merged]).explode(index_partsFalse) broken.to_file(road_broken.shp, encodingUTF-8)逻辑说明unary_union把所有线段合并成一个几何体explode再拆回单段这个过程会自然在交点处断开。参数上index_partsFalse是为了不生成多级索引保持输出干净。缺点是数据量大时内存吃紧几万条以上建议还是走 GRASS。3.3 道路分级与单行道标记的处理公路网一般自带等级字段常见取值是高速、国道、省道、县道。做路径分析时不同等级要设不同权重——高速快但可能收费县道慢但免费。如果数据里没有速度字段我一般按经验值补一列# 按道路等级赋默认速度km/h speed_map { 高速: 100, 国道: 80, 省道: 60, 县道: 40, 乡道: 30 } highway[speed] highway[road_class].map(speed_map).fillna(20) # 计算通行时间小时 长度(km) / 速度 highway[length_km] highway.geometry.length / 1000 highway[travel_time] highway[length_km] / highway[speed]逻辑说明map按等级映射速度fillna(20)兜底未知等级。travel_time是后续做最短时间路径的核心权重字段。参数上geometry.length的单位取决于坐标系投影坐标下是米除以 1000 得公里地理坐标下直接算长度会得到度必须先投影。注意单行道字段如果存在取值通常是 0/1 或 true/false但不同数据源编码不统一。建图前一定要打印唯一值确认我见过用 Y/N 的也见过用 1/0 的直接当布尔用会翻车。4. 坐标系对齐与空间叠加的避坑清单4.1 为什么你的区县和道路网叠不上这是最高频的问题行政区划和道路网来自不同来源坐标系不一致拖进 GIS 一看道路飘到边界外面去了。内蒙古常用的坐标系有两种——地理坐标 CGCS2000EPSG:4490和投影坐标 CGCS2000 3 度带EPSG:4524 到 4534 之间按中央经线不同。先确认每个图层的 CRSogrinfo -so -al county.shp | grep SRS ogrinfo -so -al road_network.shp | grep SRS如果输出里一个是EPSG:4490另一个是EPSG:4524那就必须统一。统一的原则是做空间叠加和距离计算统一到投影坐标做展示和存储可以保留地理坐标。# 把区县转到投影坐标 ogr2ogr -f ESRI Shapefile county_proj.shp county.shp \ -t_srs EPSG:4524 # 道路网同样处理 ogr2ogr -f ESRI Shapefile road_proj.shp road_network.shp \ -t_srs EPSG:4524逻辑说明-t_srs指定目标坐标系GDAL 会自动做坐标转换。参数上EPSG:4524 对应的是 CGCS2000 3 度带第 24 带中央经线 72°E覆盖内蒙古西部东部区域可能需要 4525 或 4526。选错带号不会报错但距离计算会偏这个坑很隐蔽。4.2 空间叠加时的容差设置即使坐标系统一了边界和道路叠加时仍可能出现「差一点点」的情况——道路端点离边界几厘米导致空间查询判定为不相交。这时候需要设容差# 给区县边界做微小缓冲吸收容差 county_buffered county.copy() county_buffered[geometry] county.geometry.buffer(10) # 10米容差 # 用缓冲后的边界做空间查询 roads_in_county gpd.sjoin(road_proj, county_buffered, predicateintersects)逻辑说明buffer(10)把边界向外扩 10 米吸收掉坐标转换和采集误差。参数上10 米是个经验值如果数据精度高可以降到 1 米如果误差大可以升到 50 米。predicateintersects表示只要相交就算比within宽松适合道路这种跨边界的线要素。4.3 面积和长度的单位陷阱投影坐标下算面积得到平方米除以 10^6 才是平方公里地理坐标下直接算面积会得到平方度毫无意义。长度同理。我见过有人在 EPSG:4490 下直接算geometry.area然后当成平方公里用结果数值差了十几个数量级。# 确认坐标系后再算面积 if county.crs.to_epsg() 4490: # 地理坐标先投影再算 county_proj county.to_crs(epsg4524) county_proj[area_km2] county_proj.geometry.area / 1e6 else: # 已经是投影坐标 county[area_km2] county.geometry.area / 1e6逻辑说明先判断 CRS地理坐标必须先to_crs转投影再算面积。参数上1e6是平方米到平方公里的换算系数别写成1e3那是长度换算。5. 常见问题排查从乱码到几何报错的五条血泪记录5.1 中文属性全是问号或方块现象打开属性表name 字段显示为????或乱码方块。原因SHP 的.dbf文件默认编码是 Latin1中文没有正确声明。解决用ogr2ogr重新导出并指定-lco ENCODINGUTF-8或者在 QGIS 里右键图层属性手动改编码为 GBK/UTF-8 试。如果源文件本身就是 GBK导出时写 UTF-8 即可如果源文件已经损坏只能找回原始数据重新导出。5.2 区县边界出现大量碎片多边形现象区县图层里有很多面积极小的碎片几十平方米甚至几平方米。原因数据在拼接或简化时产生了拓扑错误或者原本就是多部件几何被错误拆分。解决先算面积把小于阈值的碎片过滤掉再对剩余部分做融合# 过滤面积小于 0.01 平方公里的碎片 county[area_km2] county.geometry.area / 1e6 county_clean county[county[area_km2] 0.01]参数上0.01 平方公里是个保守阈值实际可以按数据情况调整。过滤后如果同一个 adcode 还剩多个部件用dissolve按 adcode 融合。5.3 道路网在交叉口断开但属性丢失现象打断线段后原本的道路等级、编号字段变成空值。原因v.clean的 break 工具只处理几何不保留属性关联。解决打断前先给每条线段加唯一 ID打断后用空间 join 把属性挂回去# 打断前加 ID road[road_id] range(len(road)) road.to_file(road_with_id.shp) # 打断后用原始线段与打断结果做空间 join broken gpd.read_file(road_broken.shp) original gpd.read_file(road_with_id.shp) joined gpd.sjoin(broken, original[[road_id, geometry]], predicatewithin)逻辑说明打断后的线段一定落在原始线段范围内用within做空间 join 就能把 ID 找回来。参数上如果打断后有线段跨了两条原始线段within可能匹配不到这时候改用intersects并取第一条匹配。5.4 空间 join 后行数暴增现象区县和道路做sjoin后结果行数比道路原始行数多出好几倍。原因一条道路跨了多个区县intersects会为每个区县生成一行这是正常的一对多关系。解决如果只想统计每个区县的道路总长度用groupby聚合不要直接拿 join 结果当明细用# 按区县聚合道路长度 roads_in_county gpd.sjoin(road_proj, county_proj, predicateintersects) length_by_county roads_in_county.groupby(adcode)[length_km].sum()5.5 几何有效性报错导致空间运算中断现象执行buffer、intersection时报TopologyException。原因多边形自相交或环方向错误。解决用make_valid修复from shapely.validation import make_valid county[geometry] county.geometry.apply( lambda g: make_valid(g) if not g.is_valid else g )逻辑说明make_valid会把自相交的多边形拆成合法几何可能是 MultiPolygon。参数上修复后要重新检查is_valid个别情况需要二次处理。这个操作不可逆建议先备份原始数据。6. 把这份数据变成可复用底图资产的三个进阶技巧第一个技巧是分层存储。别把省、市、县和道路全塞进一个 GeoPackage 就完事按用途拆库一个admin.gpkg放三级行政区划一个network.gpkg放道路网和公路网再加一个meta表记录每个图层的坐标系、字段说明、更新日期。这样别人接手时不用猜也方便后续增量更新。我一般会在 meta 表里写清楚每个字段的取值范围比如level字段的 1/2/3 分别对应什么省得下次自己都忘了。第二个技巧是建空间索引。SHP 格式自带.shx索引但性能有限。转成 GeoPackage 后用CREATE SPATIAL INDEX显式建索引空间查询速度能差出一个数量级-- 在 GeoPackage 里建空间索引 SELECT CreateSpatialIndex(county, geom); SELECT CreateSpatialIndex(road_network, geom);逻辑说明空间索引基于 R-Tree对intersects、within这类查询加速明显。参数上GeoPackage 的索引是自动维护的插入新数据后不需要重建。第三个技巧是做一个最小可用的验证脚本。每次数据更新后跑一遍检查要素数量、坐标系、字段完整性、几何有效性。我习惯把校验结果输出成一个简单的文本报告一眼能看出这次更新有没有引入问题def validate(gdf, name, expected_crs4524): report [] report.append(f{name}: {len(gdf)} features) report.append(fCRS: {gdf.crs.to_epsg()} (expected {expected_crs})) report.append(fInvalid geometries: {(~gdf.geometry.is_valid).sum()}) report.append(fNull geometries: {gdf.geometry.isna().sum()}) return \n.join(report) print(validate(county_proj, county)) print(validate(road_proj, road))逻辑说明这个函数只做四件事——数要素、查坐标系、查无效几何、查空几何。看着简单但能拦住八成以上的数据事故。参数上expected_crs按你的项目实际坐标系传别硬编码。最后说个我自己的习惯拿到任何一份行政区划数据先做一次「最小闭环验证」——用区县边界裁一次道路网看能不能跑通再用裁出来的道路算一次总长度跟官方统计对一下量级。如果量级差太多说明坐标系或单位有问题后面所有分析都不可信。这个习惯帮我省过很多次返工。希望帮到你。本文还有配套的精品资源点击获取