☰
内蒙古行政区划SHP文件处理指南:坐标系、道路网与GIS叠加分析全解
2026/10/9 13:26:23 网站建设 项目流程

简介:一套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', encoding='GBK') # 打印字段名和前几行,看看字段叫 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', encoding='UTF-8') city = gpd.read_file('内蒙古_地市.shp', encoding='UTF-8') county = gpd.read_file('内蒙古_区县.shp', encoding='UTF-8') road = gpd.read_file('道路网.shp', encoding='UTF-8') highway = gpd.read_file('公路网.shp', encoding='UTF-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之后,用空间连接给每个路段打上区县代码。这里的predicate='within'表示“线段完全落在多边形内部”,加上已经裁剪过,所以基本不会出现无归属路段。如果还有少量线落在缝隙里,说明区县面之间有拓扑缝隙,需要修复后再跑一次。

# 空间连接,把区县的adcode和name赋给每条路段 joined = gpd.sjoin(road_clipped, county[['adcode', 'name', 'geometry']], how='left', predicate='within') # 按区县代码汇总道路长度,单位从米转成公里 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_on='adcode', right_index=True, how='left' ).fillna({'road_km': 0}) county_stats['road_density'] = county_stats['road_km'] / county_stats['area_km2'] print(county_stats.sort_values('road_density', ascending=False).head(10)) # 输出UTF-8 with BOM,防止Excel乱码 county_stats.to_csv('内蒙旗县路网密度.csv', index=False, encoding='utf-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里指定encoding='GBK'后即可正常显示中文。这个乱码问题不解决,后面所有匹配和输出都会带病。

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(drop=True) 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_deg=0.001): gdf = gpd.read_file(input_shp, encoding='UTF-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_topology=True) gdf.to_file(output_geojson, driver='GeoJSON')

tolerance_deg的参数选择直接决定文件大小和边界精度。0.001 度适合省级概览,0.0001 度适合盟市级视图,具体看你的地图最大缩放级别。preserve_topology=True会让简化后的边界尽量保持衔接,减少裂缝。如果只做显示用途,这个脚本够用。

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_topology=True) gdf.drop(columns=['shape_length', 'SHAPE_Length'], errors='ignore', inplace=True) gdf.to_file('公路网_轻量.geojson', driver='GeoJSON')

先算后简,这个顺序保证了“几何变得轻,但统计字段不失真”。把这一段沉淀成自己的习惯后,你会发现 SHP 数据在前端项目里也没有那么难啃。

我第一次把内蒙古这套 SHP 做成旗县路网密度图时,因为没有检查坐标系,道路线整整齐齐偏移到境外,盯着屏幕看了十分钟才意识到是 .prj 缺失,系统默认按 WGS84 读了投影坐标。自那以后,我拿到任何 SHP 的第一件事都是打印crs和total_bounds,再决定要不要to_crs。这个习惯救了我很多次,希望这篇笔记里同样的坑,你一次都不用踩。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询