
简介一套2022年7月的内蒙古行政区划与交通路网GIS矢量数据集面向区域规划、交通物流与地理信息研究人员提供省、市、县三级行政边界以及道路网、铁路网的空间数据。压缩包约26.32MB内含SHP、DBF、PRJ等格式文件SHP存储空间几何形状DBF关联行政代码、道路等级等属性PRJ定义坐标参考系统覆盖自治区级、地市级、区县级边界与roads、railways图层。已有1338人学习下载。借助该数据集可开展行政区划变迁分析、路网连通性评估、区域经济统计、环境影响评价及应急响应场景的空间查询与制图。资源可直接导入ArcGIS等常用GIS平台便于可视化与空间分析适合需要快速获取内蒙古基础地理底图的科研与规划工作。1. 拿到“内蒙古行政区划道路网”SHP第一件事不该是双击打开下载 SHP 文件后第一件不该做的事就是双击 .shp。很多人会直接得到一个“选择打开方式”的弹窗或者好不容易拖进 GIS 软件看到中文地名全是问号道路和区县边界歪七八钮地对不上。这不是数据没用而是 SHP 从基因上就不是一个普通双击就能打开的文件。标题里的“【SHP文件-2022.07】内蒙古行政区划省级、地市、区县道路网公路网”说得很直白这是一套内蒙古自治区的矢量底图包含三级行政边界和两套路网线要素。这套数据能解决的实际问题是你缺一份可以画图、统计、做叠加分析的内蒙古基础地图。适合地图可视化、GIS 空间分析、物流路径规划、商业选址和报表类项目。新手需要先搞懂文件结构和编码熟手可以直接跳到叠加分析和避坑章节。下面按我拿到数据后的处理顺序来写先拆文件再统一坐标系接着用 QGIS 和 Python 跑通样例最后把踩过的坑和前置发布技巧放出来让你用一个晚上把这份数据变成自己的底图。2. 拆开SHP看本质一组文件、两层语义坐标系和编码决定生死SHP 是 ESRI Shapefile 的简称但它从来都不是单个文件。一个完整的 SHP 数据本质上是“几何信息 属性表 坐标系描述 索引文件”的组合。内蒙古行政区划这份数据里省级、地市、区县三个图层分别对应不同层级的区域多边形道路网和公路网是线要素。要让它不翻车第一步必须看文件组结构。2.1 一个“SHP文件”其实是一组文件少了任何一个都会翻车解压下载包后你会看到一堆同名但扩展名不同的文件至少要有下面这几个才算完整。扩展名作用缺失后果.shp存储要素几何坐标点、线、面打开直接报错无几何可画.shx几何索引加速按要素读取部分软件能忍但速度明显变慢.dbf属性表存“名称”“代码”等字段图层在但属性表全部丢失.prj坐标系描述WKT 格式软件按默认 WGS84 误读图形跑到境外.cpg字符编码声明中文读出来是乱码需手动试编码当你从压缩包里单独拿出一个 .shp 发给别人换台电脑就会遇到“Unable to open”的报错。我处理这类数据时习惯先把整个目录打包成 zip 再分发或者干脆在 GIS 软件里导出成 GeoPackage 单文件后面避坑章还会再说。这里要记住文件组结构不完整坐标和属性就无从谈起。2.2 投影坐标系为什么内蒙古边界在有的软件里显示到非洲去了SHP 的 .prj 文件用纯文本记录坐标系如果缺失或者写错软件会默认按 WGS84 经纬度EPSG:4326来渲染。内蒙古地区的数据源生产环境不同坐标系可能是 CGCS2000 地理坐标、CGCS2000 3 度带投影也可能是旧版北京 54 / 西安 80 的高斯投影。这些坐标系下的坐标数值和经纬度差得非常远投影坐标往往是百万量级的“米”一旦被当作经纬度图形自然会缩到地图窗口外。拿到数据后建议先做两件事一是用文本编辑器打开 .prj 文件看里面有没有“Gauss_Kruger”“CGCS2000”“UTM”“带号”这类关键词二是打印图层的实际边界范围如果 x 坐标值在 100 到 130 之间大概率是经纬度如果在 300万 到 700万 之间就是投影坐标必须转。常见坐标系对比如下。坐标系EPSG单位适用场景WGS84 经纬度4326度在线地图叠加、GeoJSON 输出CGCS2000 经纬度4490度国家 2000 基准下的地理坐标CGCS2000 3 度带4520 系列米面积长度计算需匹配中央经线UTM326xx / 327xx米中小范围精确测量跨带误差大内蒙古东西跨度很大整个区域横跨多个投影带。做全区域面积统计时如果只套用一个投影带东边和西边的面积误差会非常明显。我常用的做法是在地图展示阶段统一用 EPSG:4490在计算长度和面积阶段按区县所在带单独投影或者是用等积投影后面有具体代码。2.3 属性表里的行政区划代码从名称匹配到代码匹配别用汉字做Key.dbf 属性表里一般会有名称字段和行政区划代码字段代码字段经常命名为 adcode、pac 或 district_code。内蒙古自治区、盟市、旗县三级之间存在清晰的代码前缀关系上级代码是下级代码的开头几位。这套代码是字符串不是数字但很多工具读进来会自动转成 int导致前导零丢失后续 join 全部变成 NaN。用 Python 读取时第一件事就是把代码字段显式转成字符串并确认名称字段的编码。示例代码如下import geopandas as gpd # 尝试按 GBK 读取如果中文乱码就改成 UTF-8 gdf gpd.read_file(内蒙古_区县.shp, encodingGBK) # 打印字段名和前几行看看字段叫 adcode 还是 pac print(gdf.columns.tolist()) print(gdf[[adcode, name]].head()) # 强制转成字符串防止前导零丢失 gdf[adcode] gdf[adcode].astype(str) print(gdf[[adcode, name]].dtypes)encoding参数需要根据实际情况切换如果 .cpg 文件声明了 UTF-8就改成UTF-8。astype(str)这一步看起来多余但我在实际工作中被它救过很多次尤其是从 CSV 合并统计指标时字符串代码和整型代码互相匹配不上问题很难发现。3. 从零跑通QGIS加载和Python脚本处理行政区划与道路网有了上面的基础现在开始真正使用这套数据。这一章给两条路线一条是用 QGIS 做图形化检视适合快速确认视觉效果另一条是 Python 批处理适合自动化、可重复的操作。无论哪条第一步都是统一坐标系。3.1 QGIS加载先选编码再选文件最后看坐标单位打开 QGIS 后在“数据源管理器”的“矢量”页签里先看“编码”下拉框再点按钮选择 .shp 文件。如果目录下有 .cpg 文件QGIS 会自动读取编码声明但如果数据源本身没有 .cpg或者声明写错了中文就变成乱码。这时需要手动把编码从 UTF-8 切到 GBK 或 GB18030重新加载一次。图层加载顺序有讲究。先把区县面图层放到底层地市和省级边界放在中间道路网和公路网线图层放在最上层这样视觉上“面在下、线在上”地图不会互相遮挡。加载完成后右键图层进入“图层属性”的“信息”页签查看“坐标参考系统”这一行。如果是 EPSG:4490 说明是 CGCS2000 地理坐标如果是“未定义”就得回到 2.2 节的思路判断。QGIS 的好处是能快速做符号化道路按等级或类型分类区县按某个统计字段做渐变配色几分钟就能出一张能汇报的示意图。但要注意QGIS 屏幕显示的不准确并不代表数据有问题做面积统计时一定要切到投影坐标系后再计算。3.2 Python读取三份SHP先看边界、数量与坐标范围当数据要批量处理或嵌入到分析脚本时GeoPandas 是最顺手的工具。读取五份数据后第一步不是画图而是打印每个要素的数量、坐标参考和边界范围。这样可以立刻看出坐标系是否一致、图层是否完整。import geopandas as gpd province gpd.read_file(内蒙古_省级.shp, encodingUTF-8) city gpd.read_file(内蒙古_地市.shp, encodingUTF-8) county gpd.read_file(内蒙古_区县.shp, encodingUTF-8) road gpd.read_file(道路网.shp, encodingUTF-8) highway gpd.read_file(公路网.shp, encodingUTF-8) for name, gdf in [(province, province), (city, city), (county, county), (road, road), (highway, highway)]: print(name, crs, gdf.crs) print(name, rows, len(gdf)) print(name, bounds, gdf.total_bounds)total_bounds返回的是[minx, miny, maxx, maxy]。如果 x 最大值大于 180 或者小于 -180说明这份数据不是经纬度坐标而是投影坐标直接画图就会错位。rows告诉你每个图层有多少要素区县层级通常上百个道路网可能有几千甚至上万条要素总数异常少的时候要怀疑是不是只读了不完整文件。3.3 统一坐标系先转CGCS2000经纬度再按需求转投影显示的通用标准是经纬度因此我会先统一到 EPSG:4490这是 CGCS2000 的经纬度表示方便和在线底图叠加也方便后续输出 GeoJSON。如果源文件有 .prj直接to_crs就能完成如果 crs 为 None就需要根据边界范围或元数据手动指定这不是瞎猜而是根据已有信息恢复坐标系。for gdf in [province, city, county, road, highway]: if gdf.crs is None: # 已知源数据是 CGCS2000 但缺少prj时才可手动指定 gdf.crs EPSG:4490 gdf gdf.to_crs(EPSG:4490)统一到经纬度后可以看到所有图层的边界范围都落在 97 到 127 度左右这是正确的。接下来要计算面积和长度需要把数据转成投影坐标系。GeoPandas 提供了一个快捷方法estimate_utm_crs()它会根据数据整体中心点估算合适的 UTM 投影带。county_utm county.to_crs(county.estimate_utm_crs()) county_utm[area_m2] county_utm.geometry.area # 验证一下面积是否合理单位不再是度而是米 print(county_utm[area_m2].sum() / 1e6, 平方公里)这里有一个严格的注意事项内蒙古东西跨度太大全数据集直接套用一个 UTM 带边缘区域误差会放大。正确的统计方式是按地市或按区县逐个转换到其所在带再计算长度面积。estimate_utm_crs()只能作为快速评估不能作为精确出报表的最终方案。后面避坑章还会再讲一次。4. 叠加分析把道路网落到区县上算出“路网密度”行政区划和道路网放一起最常见的产出不是一张好看的地图而是“每个旗县有多少公里路、每平方公里有几公里路”。这种统计需要做空间连接和聚合计算。很多人直接拿着原始道路和区县边界做sjoin最后发现一条跨两个旗县的路被重复算了两次。正确做法是先裁剪再归属。4.1 用clip把道路“切”到区县边界内裁剪是空间分析里非常关键的一步它会把穿越边界的路线在交点处打断生成落在多边形内部的线段。这样每条路段的归属是唯一的不会重复统计。必须确保两张图处于同一个投影坐标系否则裁剪结果不可信。# 切换到投影坐标系保证长度单位是米 county county.to_crs(county.estimate_utm_crs()) road road.to_crs(county.crs) # 裁剪线要素到面要素范围内 road_clipped gpd.clip(road, county) print(原始道路条数:, len(road)) print(裁剪后条数:, len(road_clipped))裁剪后要素数量通常会变多因为原本一条完整道路可能被切成好几段。如果裁剪后的要素数暴增到原来的三五倍说明路网边界和区县边界重叠很严重或者两个源数据本身精度不一致。这种情况下要先回到 3.3 节确认二者已经统一到同一个坐标系再考虑是否要先把区县边界做一次 5.4 节里的拓扑修复。4.2 空间连接把每个路段归到唯一区县gpd.clip之后用空间连接给每个路段打上区县代码。这里的predicatewithin表示“线段完全落在多边形内部”加上已经裁剪过所以基本不会出现无归属路段。如果还有少量线落在缝隙里说明区县面之间有拓扑缝隙需要修复后再跑一次。# 空间连接把区县的adcode和name赋给每条路段 joined gpd.sjoin(road_clipped, county[[adcode, name, geometry]], howleft, predicatewithin) # 按区县代码汇总道路长度单位从米转成公里 road_len (joined.groupby(adcode).geometry.length / 1000).rename(road_km) print(road_len.head())分组聚合时geometry.length返回的是每条线在投影坐标系下的长度。因为这个数据已经转成投影坐标系所以结果是米。除以 1000 变成公里后就可以和区县面积合并。这里再次强调adcode要用字符串类型否则某些以数字开头的代码可能在聚合后变成小数导致 join 失败。4.3 合并面积与密度输出一张专题统计表最后将区县面积、道路长度合并到一张表里计算路网密度这个指标是“每平方公里拥有多少公里道路”能直观反映交通基础设施覆盖水平。把结果写出 CSV 时要注意编码用utf-8-sig否则 Excel 打开中文表格会乱码。county[area_km2] county.geometry.area / 1e6 county_stats county.merge( road_len.rename(road_km), left_onadcode, right_indexTrue, howleft ).fillna({road_km: 0}) county_stats[road_density] county_stats[road_km] / county_stats[area_km2] print(county_stats.sort_values(road_density, ascendingFalse).head(10)) # 输出UTF-8 with BOM防止Excel乱码 county_stats.to_csv(内蒙旗县路网密度.csv, indexFalse, encodingutf-8-sig)这一套流程下来你就从原来“只能画图”的数据变成了一份可量化分析的数据表。后续可以把它导入报表工具做可视化也可以在 QGIS 里按 road_density 字段做分级配色。核心要点还是那句计算过程全程用投影坐标系展示时再转回 EPSG:4490。5. 避坑指南内蒙古SHP使用中的6个典型翻车现场这部分是我在这类数据上反复踩过的坑按“现象、原因、解决”的顺序写方便你直接对号入座。每一条都值得在你自己项目里提前规避。5.1 中文乱码字段名和地名变成“锟斤拷”或问号现象在 QGIS 打开属性表name 字段全是“???”在 Python 里打印也是乱码。原因.dbf 属性表通常是 GBK 编码而 QGIS 和 GeoPandas 默认按 UTF-8 读取两边不匹配。解决先看目录下有没有 .cpg 文件有就打开看声明的是哪种编码没有就分别用 UTF-8 和 GBK 试读。Python 中在read_file里指定encodingGBK后即可正常显示中文。这个乱码问题不解决后面所有匹配和输出都会带病。5.2 道路和边界错位线与面明明是一个地区的一个在国界外现象区县边界居中显示道路网却稀稀拉拉分布在几百公里外或者干脆在地图窗口边缘。原因两个 SHP 的坐标系不一致。例如边界是 CGCS2000 三度带投影坐标数值是百万级别道路是 WGS84 经纬度数值是 100 多按同一画布渲染自然错开。解决先打印两侧的crs和total_bounds如果一边是 100一边是 3000000赶紧统一坐标系。print(road.crs, county.crs) print(road bounds:, road.total_bounds) print(county bounds:, county.total_bounds)5.3 行政区划代码被Excel/CSV“科学计数法”毁掉现象从 Excel 或 CSV 读取统计表后合并到区县面数据上全都是 NaN。原因行政区划代码被当作数字读入变成科学计数法或者文本型代码与整型代码明显对不上。解决读取 CSV 时强制指定列为字符串pd.read_csv(stats.csv, dtype{adcode: str})。Excel 里也把这个列设置成文本格式不要使用默认常规格式。代码字段本质是编码任何数字化处理都会造成不可察觉的错位。5.4 相邻区县边界有重叠或缝隙面积统计不可靠现象把所有区县面积加总和省级边界面积差出几百平方公里。原因不同批次数字化成果在边界处没有完全咬合出现重叠或空隙。解决先用 QGIS 的“拓扑检查器”定位问题区域再用make_valid修复无效几何。Python 里的处理方式如下from shapely.validation import make_valid county[geometry] county.geometry.apply(make_valid) county county.explode().reset_index(dropTrue) print(county.geometry.is_valid.all())explode()会把修复产生的多组件几何拆成单个多边形这一步必须有否则后续统计可能出现 MultiPolygon 的面积重复计算。修复后重新计算面积字段再合并路网数据。这套流程不完美但至少比带着缝隙统计靠谱得多。5.5 路网看着连成一条网络分析却“断头”现象两条公路在交叉口视觉相交但做最短路径分析时完全不能互通绕行几十公里。原因线要素在交点处没有共享顶点拓扑上就是断开的。解决在 QGIS 里使用“修复线断点”工具设置一个合理的搜索半径。半径不要设置太大一般不超过道路宽度的 2 到 3 倍否则会把平行的两条路错误吸成一条。处理完后用“网络分析”工具验证连通性。这个问题的本质是数据生产时没有做节点捕捉属于常见数据质量问题。5.6 只拷贝.shp导致“打开失败Unable to open”现象把数据发给同事或者换一台电脑软件报错打不开。原因传输过程中只拷贝了 .shp缺少 .shx、.dbf、.prj。解决拷贝时要么打包整个目录要么把数据在 GIS 里导出为 GeoPackage 单文件。GeoPackage 格式把几何、属性、坐标系、空间索引全封装在一个 .gpkg 文件里传输最安全以后打开也更省事。6. 进阶用法把内蒙古SHP变成轻量GeoJSON/矢量瓦片优化前端加载如果你的最终目的是 Web 地图可视化SHP 直接让浏览器加载并不现实格式不支持体积也偏大。常见做法是先转成 GeoJSON再按缩放级别做简化如果数据量非常大可以进一步生成矢量瓦片。这章给出一个可复用的脚本模板。6.1 用GeoPandas转GeoJSON并控制简化容差import geopandas as gpd def shp_to_geojson(input_shp, output_geojson, tolerance_deg0.001): gdf gpd.read_file(input_shp, encodingUTF-8) if gdf.crs is None: gdf.crs EPSG:4490 gdf gdf.to_crs(EPSG:4490) # Douglas-Peucker简化0.001度约合100米 gdf.geometry gdf.geometry.simplify(tolerance_deg, preserve_topologyTrue) gdf.to_file(output_geojson, driverGeoJSON)tolerance_deg的参数选择直接决定文件大小和边界精度。0.001 度适合省级概览0.0001 度适合盟市级视图具体看你的地图最大缩放级别。preserve_topologyTrue会让简化后的边界尽量保持衔接减少裂缝。如果只做显示用途这个脚本够用。6.2 路网太大时先转矢量瓦片道路网转成 GeoJSON 后经常几十 MB前端渲染卡顿。我一般用 Tippecanoe 把 GeoJSON 转成 .mbtiles 矢量瓦片然后交给 MapLibre 加载。命令行示例tippecanoe -o 内蒙道路.mbtiles -Z4 -z12 -l roads roads_simplified.geojson-Z4是最小缩放级别 4-z12是最大缩放级别 12-l roads指定图层名。Tippecanoe 会按层级自动抽稀放大到盟市级别时恢复细节前端渲染压力小很多。如果只是内部预览也可以直接用 QGIS 加载 mbtiles。6.3 一个容易被忽略的字段保留问题简化几何会改变线段长度但属性表里自带的shape_length字段不会自动更新。如果你后续要用长度字段做标签或查询一定要在简化前重新计算。推荐顺序是先投影算真实里程再转回经纬度最后简化。gdf gpd.read_file(公路网.shp) # 先投影计算真实长度 gdf gdf.to_crs(gdf.estimate_utm_crs()) gdf[road_km] gdf.geometry.length / 1000 # 转回经纬度并简化 gdf gdf.to_crs(EPSG:4490) gdf.geometry gdf.geometry.simplify(0.0005, preserve_topologyTrue) gdf.drop(columns[shape_length, SHAPE_Length], errorsignore, inplaceTrue) gdf.to_file(公路网_轻量.geojson, driverGeoJSON)先算后简这个顺序保证了“几何变得轻但统计字段不失真”。把这一段沉淀成自己的习惯后你会发现 SHP 数据在前端项目里也没有那么难啃。我第一次把内蒙古这套 SHP 做成旗县路网密度图时因为没有检查坐标系道路线整整齐齐偏移到境外盯着屏幕看了十分钟才意识到是 .prj 缺失系统默认按 WGS84 读了投影坐标。自那以后我拿到任何 SHP 的第一件事都是打印crs和total_bounds再决定要不要to_crs。这个习惯救了我很多次希望这篇笔记里同样的坑你一次都不用踩。希望帮到你。本文还有配套的精品资源点击获取