简介河北省2022年7月版的行政区划与交通网络GIS数据包涵盖省、市、县三级行政边界与道路网、公路网、铁路网图层主要面向使用ArcGIS、QGIS等软件开展区域分析、城市规划、交通研究与地图制图的GIS从业者及学习者。压缩包共33个文件总大小46.65MB以shp矢量主文件为核心辅以dbf属性表存储区域代码、道路等级等要素信息并由prj文件定义地理坐标系配套shx索引文件与xml元数据便于在GIS平台直接读取与叠加使用。数据覆盖全省基层行政单元与道路交通骨架读者可直接获取边界几何、属性字段和坐标基准开展行政信息查询、路网连通性分析、交通流量模拟及灾害应急响应规划等操作。该数据集版本较新适合作为现状底图或研究基础数据目前已有1316人学习下载获得较多GIS领域用户关注。1. 河北省SHP数据包里到底有什么从省界到公路网的层次拆解做地理信息这行最怕的不是数据精度不够而是拿到一份不写元数据的SHP文件后连哪个图层是省界、哪个图层是公路网都分不清。这份标题标注为2022.07的河北省SHP数据包核心内容是两大部分省、市、县三级行政区划边界以及道路网与公路网两类线性基础设施。对做城市规划、物流选址、区域分析或者地图制图的人来说它解决的是“底图和路网从哪来”的问题尤其是当你想在河北境内做跨市、跨县的统计或路径可视化时一个能直接读进软件的矢量包能省掉大量爬取和配准时间。很多人拿到SHP后第一反应是用ArcGIS或QGIS打开然后发现面要素边界对不上、路网断断续续、属性表全是乱码就开始怀疑数据有问题。实际上这套SHP最大的价值在于把行政区划和路网分成了独立的图层文件你不需要自己从测绘局网站上一点点拼接。省界负责宏观范围市界和县界负责中微观分析道路网与公路网则能配合区划做缓冲区、可达性计算。适合刚入行的GIS从业者、城乡规划专业的学生以及需要在项目里快速接入河北区域底图的数据分析人员。需要注意的是SHP本身是一种古老且带有局限性的矢量格式一个完整的要素类由.shp、.shx、.dbf、.prj等多个文件组成缺少任何一个都会导致打开失败。所以拿到文件后先别急着加载第一步应该确认文件完整性和坐标系定义而这恰恰是本文要展开讲的起点。2. 打开SHP前的准备GIS软件选型与数据坐标系避坑2.1 用开源QGIS还是商业GIS选型逻辑与这包数据的环境适配处理这份河北省SHP数据你最常用的工具无非是QGIS、ArcGIS Pro或者Python的GeoPandas库。我的建议是如果只是看数据、做简单制图直接上QGIS免费且跨平台如果需要发布地图服务或者用到企业级数据库管理再考虑商业GIS。原因很简单SHP文件是开放格式任何能读矢量的软件都认不存在“某商业软件打开后效果更好”的玄学。基于QGIS的操作环境做加载检查通常会看到图层名以文件名显示例如“河北省省界.shp”“河北省市界.shp”“河北省县界.shp”以及“河北道路网.shp”“河北公路网.shp”。如果是国家标准分幅的行政区划文件名里可能带经纬度网格编号但这里大概率是按行政区域命名的。打开后先别急着做符号化按F6或者右键打开属性表检查要素数量和字段结构。另一个常被忽略的问题是QGIS默认用UTF-8读取DBF编码而国内很多SHP自带的DBF文件是GBK编码。如果你的中文属性表里出现“æ²³å”之类的乱码那就要手动设置编码。在加载时可以临时把编码改成“GBK”或“GB2312”让QGIS重新读一次。这一招在后续处理字段时非常关键因为乱码字段名会让所有表达式和过滤操作全部失效。2.2 坐标系先对齐再干活CGCS2000与WGS84的区别、单位与显示问题SHP数据包的坐标系决定你的分析结果是不是“原位”。河北地处北纬36°03′到42°40′之间常见的投影坐标系有CGCS2000 / 3-degree Gauss-Kruger zone 38–40或者UTM Zone 50N。如果使用地理坐标系GCS_WGS_1984经纬度单位是度而在做面积计算、缓冲区生成时必须先重投影到米制投影坐标系否则算出的面积是“平方度”毫无意义。拿到这份数据后第一步应该在图层属性里查看“坐标系”信息。如果.prj文件缺失QGIS会显示“未知基准面”此时需要靠数据范围来判断如果X、Y数值在70到140之间、Y在20到45之间就是经纬度如果X是七八位、Y是四五位大概率是投影坐标。前一种情况我一般会选中图层右键-导出-另存为在“CRS”里选择EPSG:4490CGCS2000地理坐标系或者EPSG:4528CGCS2000 / 3-degree Gauss-Kruger CM 117E中央经线117°适用于河北统一坐标再干活。注意即便原始数据投影正确市面上很多SHP文件会顺手把.prj写成WGS_1984_UTM_Zone_50N而实际坐标却是CGCS2000。这两种基准面差值大约在几十厘米到几米之间做小比例尺出图无所谓但做高精度选址或县界权属分析时就会看出偏差。我会用GPS实测点的坐标与图层点做比对偏差超过5米就考虑重投影。2.3 用Python快速探测SHP文件结构读取前不盲加载的检查脚本在正式打开SHP前用一个十几行的Python脚本就能把图层数量、要素数、字段列表、坐标范围一次看清。这比挨个拖进GIS加载要高效得多尤其是当你手头有多个SHP文件需要批量检查时。下面是我常用的探测脚本基于GeoPandas库读取前先不加载全量数据。import geopandas as gpd import os # 设置SHP文件夹路径注意用正斜杠或转义 shp_dir D:/gis_data/hebei_202207 for root, dirs, files in os.walk(shp_dir): for f in files: if f.endswith(.shp): path os.path.join(root, f) try: gdf gpd.read_file(path, encodingutf-8) print(文件:, f) print(要素数量:, len(gdf)) print(坐标系:, gdf.crs) print(字段列表:, list(gdf.columns)) print(四至范围:, gdf.total_bounds) print(几何类型:, gdf.geom_type.unique()) print(- * 50) except Exception as e: print(读取失败:, f, 原因:, e)这段代码做了四件事遍历文件夹内所有SHP、尝试读取、打印要素数量与坐标系、输出字段和范围。合理的逻辑是先用小样本判断文件是否损坏再决定要不要花时间深入分析。参数说明encoding参数如果报错可以改成gbk再试total_bounds返回[xmin, ymin, xmax, ymax]用于确认数据范围是否落在河北境内。读取失败通常是.shp文件缺失或SHP内部几何损坏此时别急着删除用QGIS的“添加矢量图层”再试一次有时是GeoPandas对多几何类型的兼容性问题。3. 河北省市县三级行政区划SHP属性表字段、边界修正与常用查询3.1 字段里藏着什么省市区划代码、层级标记与名称字段行政区划SHP的核心价值不在图形而在属性表。河北省级、市级、县级三个图层通常各自或统一放在一个SHP里用字段区分级别。常见字段有NAME名称、ADCODE或PAC行政区划代码、LEVEL省级1市级2县级3有的还会带SHORTNAME简称和AREA面积字段。拿到数据后第一件事就是打开属性表看看ADCODE字段的前两位河北省的省级代码是13市级代码前四位如1301是石家庄、1302是唐山、1303是秦皇岛、1304是邯郸、1305是邢台、1306是保定、1307是张家口、1308是承德、1309是沧州、1310是廊坊、1311是衡水县级代码再来两位比如130123是正定县。如果不清楚代码含义最稳妥的办法是不要依赖名称字段做关联因为“河北区”这种非标准名称可能出现在其他省份的同名图层里。统一用行政代码字段来做关联、筛选、join这是血泪经验。例如你想提取保定市的所有县级边界不要用NAME 保定而要用ADCODE.str.startswith(1306)这样能精确捞回市辖区、县级市和县不会漏掉名称中不含“保定”的飞地或特殊区域。属性表里常见另一个字段是children或SUB表示子级个数比如市级要素的children24表示该市下属24个县级行政区。这个字段可以用作数量核验防止图形漏画导致统计数量对不上。另外AREA字段的单位需要确认如果值是小数且总数约等于河北总面积19万平方公里则可能是平方公里或公顷否则就是投影坐标下的平面面积不能直接当公里数用。3.2 提取某个市的县级边界按属性筛选再导出的标准操作和脚本项目里最常见的需求不是打开全河北而是只要某一个市或县的边界。这时候我一般用QGIS的表达式筛选或者用Python脚本批量导出。先看QGIS操作右键图层-打开属性表-“选择要素”按钮点击“按表达式选择”输入ADCODE LIKE 1306%点击“选择要素”退出属性表后右键图层-导出-保存所选要素为指定坐标系和文件名即可。在Python里做更直接用GeoPandas筛选并导出脚本如下import geopandas as gpd gdf gpd.read_file(hebei_202207_县界.shp, encodingutf-8) # 筛选保定市注意代码长度和填充 baoding gdf[gdf[ADCODE].astype(str).str.startswith(1306)] # 检查结果保定市下辖和代管县级行政区数量 print(baoding[NAME].tolist()) # 重新整理索引避免原索引断层 baoding baoding.reset_index(dropTrue) # 导出为SHP文件名自定义编码建议用utf-8 baoding.to_file(baoding_county.shp, encodingutf-8)逻辑说明astype(str)是防止字段原本是类似1306的整数直接调用startswith会报错reset_index也不是可有可无因为GeoPandas继承pandas索引筛选后索引不连续可能导致后续空间连接出错。参数说明to_file默认的driver是ESRI Shapefile但不要以为它会自动生成.prj文件新版GeoPandas一般会写但如果你要用老GIS软件读最好在导出时手动指定坐标baoding.to_file(..., crsEPSG:4528)。这里有一个坑个别县界SHP的图形在边界处有细微飞边或不贴合的缝隙直接按属性筛选导出的县界用作裁剪路网的输入时会因为边界不闭合导致裁剪结果出现细碎线头。所以下一步要做边界修复。3.3 处理边界裂缝与相邻面重叠拓扑检查与修复的常见做法行政区划SHP的面要素如果来自不同来源拼接常常出现相邻县界不共享顶点导致视觉上有缝隙或重叠。这种问题在做Overlay分析时会被放大两个相邻县叠加后中间会出现细长的未定义区域。在QGIS里可以用“拓扑检查器”插件Topology Checker发现这类问题规则设为“不能有缝隙”“不能有重叠”。对于县界这种简单行政边界我推荐的修复顺序是先检查几何有效性再统一到同一坐标系再执行“消除”Dissolve或“修复几何”。如果是相邻面的公共边界存在微小间隙最快的方法是对县级SHP做一次联合Union或“修复几何”但会改变属性结构更保守的做法是使用“捕捉几何到图层”功能把相邻面的顶点捕捉到彼此。在Python里可以用shapely的make_valid来修复部分无效几何但复杂的缝隙修复建议用PostGIS的ST_MakeValid和ST_Snap操作起来更可控。import geopandas as gpd from shapely.validation import make_valid gdf gpd.read_file(hebei_202207_县界.shp, encodingutf-8) # 逐要素检查并标记无效几何 invalid_mask ~gdf.geometry.is_valid print(无效要素数量:, invalid_mask.sum()) # 如果是由于多边形自相交尝试修复 gdf.loc[invalid_mask, geometry] gdf.loc[invalid_mask, geometry].apply(make_valid) # 导出修复后的数据 gdf.to_file(hebei_county_valid.shp, encodingutf-8)这段代码主要用于自相交、退化线段等常见无效情形。make_valid的底层是把无效多边形拆成多个有效多边形可能产生MultiPolygon多面。参数说明如果修复后出现要素不连续还要用highs或者voronoi方法生成辅助面但行政边界极少走到那一步。真正要留意的是修复后某些县的面积可能变小或变大几平方米在做精确县界统计时需要容忍这个误差。4. 道路网与公路网SHP的差异图层拆分、拓扑修复与字段标准4.1 道路网与公路网不是一回事图层要素类型与路网等级字段很多新人把“道路网”和“公路网”混为一谈其实在这个SHP包里它们是两个不同的图层。道路网通常覆盖更广包含城市道路、省道、县道、乡村道路甚至机耕道要素数量可能几十万条而公路网往往是经过筛选的干线网络主要保留高速公路、国道、省道以及部分重要的县道几何上相对简洁密度低得多。这也解释了为什么文件包会把它们分别命名一个用于宏观交通分析一个用于地图渲染或导航路径规划。打开这两个图层的属性表你会发现公路网通常带有CLASS或ROAD_LEVEL字段值从1到5不等1代表高速公路2代表国道3代表省道4代表县道5代表乡村路。而道路网图层里还可能出现WIDTH、LANES车道数、SURFACE路面类型等字段。没有标准字段名的时候应该先看字段值的分布不要用名称字段里的“高速”“国道”做模糊匹配因为有些道路名称写不全用FCLASS这类代码字段筛选才稳定。几何类型上这两个SHP都是线要素LineString / MultiLineString但质量差异很大。道路网图层里常能看到重复线段、自相交、悬挂节点而公路网图层通常经过人工整理拓扑关系更干净。如果你的目标是从道路网里提取某一等级的路段不要直接按字段画出来截图而是先做拓扑检查否则你会发现明明选了“省道”结果却出现穿越山体或中断的路段这是线要素数据固有的问题。4.2 按道路等级抽稀和合并从高速公路到乡村路的筛选逻辑拿到公路网SHP最常用的操作是抽稀出高速公路和国道用于制作交通图底图。在QGIS里打开属性表筛选ROAD_LEVEL 2导出即可。但如果你想做路径网络分析则要把断头路、重复线处理掉。Python里按等级筛选并合并成单一MultLineString的方法如下import geopandas as gpd roads gpd.read_file(hebei_202207_公路网.shp, encodingutf-8) # 检查字段分布注意打印字段值不要凭猜 print(roads[ROAD_LEVEL].value_counts()) # 筛选高速国道 highway roads[roads[ROAD_LEVEL].isin([1, 2])] # 简化几何减少顶点数tolerance单位与坐标系一致这里是米 simplified highway.copy() simplified[geometry] simplified.geometry.simplify(tolerance200) # 按道路名称合并线段保留道路名属性 combined simplified.dissolve(byROADNAME, as_indexFalse) combined.to_file(hebei_highway.shp, encodingutf-8)参数说明simplify的tolerance是简化几何时允许的最大偏移量200米在高比例尺下偏大只适合全省范围出图如果要做局部精细展示把tolerance改成30。dissolve按道路名称融合后同一条高速的不同路段变成一行要素但代价是拐弯处顶点被抽稀所以务必在分析前保存一份未简化的版本。抽取高速路段时如果输出图形出现“跳跃”或奇怪的直线连接那不是视觉错觉而是原数据里相邻段之间没有共享端点简化操作放大了这个间隙。此时应该先做拓扑修复再简化顺序不能反。4.3 路网拓扑修复悬挂点、断头路与路口打断的三种场景路网数据的拓扑问题比行政区划面要复杂得多。最常见的三类毛病一是悬挂点路的末端没有连接到下一条路上像是伸进农田里突然消失二是断头路明明在路口应该相交两条线却各走各的三是路口相交但未打断两条路在立交桥处看起来交叉实际几何没有产生节点导致路径规划时认为不能转弯。在QGIS里用“处理工具箱”里的“修复拓扑”的线节点修复Snap geometries to layer可以先把线端点捕捉到其他线的交点。Python中用shapely的snap函数能够做类似操作但工程上我更推荐用PostGIS的pgr_nodeNetwork来打断线路然后创建拓扑。# 用shapely自动识别悬挂节点伪代码示意 import geopandas as gpd import shapely roads gpd.read_file(hebei_202207_道路网.shp, encodingutf-8) # 提取所有线的端点 starts roads.geometry.apply(lambda x: shapely.geometry.Point(x.coords[0])) ends roads.geometry.apply(lambda x: shapely.geometry.Point(x.coords[-1])) all_endpoints gpd.GeoSeries(list(starts) list(ends)) # 计算每个端点被其他线接触的次数这里仅示意 # 实际工程中会用一个小的容差值做intersects判断悬挂点和断头路的判断是同一个问题端点到其他线的距离小于容差则认为连接正常大于容差则被标记为悬挂。这个容差通常是1到5米取决于数据精度但道路网数据经常出现几百米的间隔需要结合影像判断是真实断头路还是数据缺失。我的经验是不要把修复当成全自动流程先标注悬挂点数量再用底图人工确认断头路如果属于乡村土路直接删除比强行连接更符合实际。5. 常见问题排查投影偏差、字段乱码、裁剪失效的三个典型翻车点5.1 投影偏了几百米甚至上千米坐标系定义与重投影的排查顺序现象在QGIS中把行政区划SHP与路网SHP叠加发现路网整体偏移到相邻县里或者两条本应重合的道路边界相差几百米。原因这份SHP包内部的文件可能使用了混合坐标系省界是CGCS2000地理坐标而公路网是WGS84 Web墨卡托两个图层的CRS定义不同导致动态投影显示偏移。解决先确认每个图层的CRS元数据再统一重投影到同一个投影坐标系。不要直接用“临时变换”的默认显示因为那只是视觉对齐数据坐标还是原来的。我的排查顺序是第一步右键图层-属性-源看“坐标系”是否已知第二步用坐标范围判断是经纬度还是投影第三步用QGIS的“按时显示”关掉改用“图层CRS”显示第四步打开几何检查器查看一条县界在两种模式下的坐标差。如果偏移呈规律性比如某方向固定平移300米那多半是使用了不同椭球体下的同名坐标系需要做七参数转换。但在开源软件里没有内置坐标转换算法时最简单的方法是用reproject功能强制把图层重投影到目标CRS然后人工校准一个已知点。河北地区常见的正确投影是CGCS2000 / Gaussian-Kruger zone 40NEPSG:4549对应中央经线120°但河北省东西跨度很大用单个投影会导致东西两侧变形建议按区域切割。若是全省出图就使用EPSG:4549并接受边缘误差若是精准分析把张家口和承德单独切出来用zone 39或38。5.2 属性表中文乱码SHP的DBF编码识别与转换方法现象打开属性表字段名和属性值显示为“鍖椾含”“鐪侀檰”等乱码。原因SHP文件的dBaseDBF属性表没有统一编码规范国内许多生产单位用GBK或GB2312保存中文而QGIS和ArcGIS Pro默认按UTF-8读取导致字符解码错位。解决在QGIS加载SHP时把“图层编码”从“UTF-8”改为“GBK”如果还不对就是“GB2312”或“GB18030”。如果数据已经被错误保存则需要转码。转码的痛点是字段名和字段值可能编码不一致。有一个比较笨但有效的办法用文本编辑器打开.dbf文件前先用QGIS导出编码为UTF-8的新SHP再做一次字段检查。在GeoPandas里可以用encodinggbk读取然后重新to_file(encodingutf-8)保存。注意to_file时不要只用encoding参数还要确保原有字段名不含非ASCII字符否则部分GIS软件无法正常显示字段名。import geopandas as gpd # 先按GBK读取 gdf gpd.read_file(hebei_202207_县界.shp, encodinggbk) # 查看字段名是否正常 print(gdf.columns.tolist()) # 强制写成UTF-8编码的新文件 gdf.to_file(hebei_202207_县界_utf8.shp, encodingutf-8)参数说明如果读取时encodinggbk报错先换gb18030这是GBK的超集兼容性更好。读出来之后要抽查几个中文值比如print(gdf[NAME].head())确保没有出现人名或地名错位。这里有个陷阱即使字段名正常字段值也可能带有不可见的前后空格或换行符这一步可以用gdf[NAME] gdf[NAME].str.strip()处理。5.3 裁剪行政区划后路网对不上裁剪时忽略空间索引的后果现象用县界去裁剪公路网SHP裁剪结果里的路网在边界处不是清爽截断而是带着一小段超出县界的尾巴或者本应完整的路过境段消失。原因裁剪前没有进行拓扑检查和空间索引重建数据自相交导致裁剪算法产生畸形输出也可能是县界SHP与路网SHP的坐标系不一致动态转换产生的微位移让线头穿出边界。解决裁剪前先对两个图层执行“修复几何”和“重投影”并确保裁剪面边界是闭合的。在QGIS里使用“裁剪”Clip工具前我一般会在处理工具箱里先运行“修复几何”和“消除悬挂节点”。用Python做的话需要注意clip函数的几何类型匹配问题import geopandas as gpd county gpd.read_file(baoding_county.shp, encodingutf-8) roads gpd.read_file(hebei_202207_公路网_wsg84.shp, encodingutf-8) # 统一坐标系 roads roads.to_crs(county.crs) # 修复无效几何 from shapely.validation import make_valid roads[geometry] roads.geometry.apply(make_valid) # 用单个县界面裁剪路网返回那些与面相交的线 clipped gpd.overlay(roads, county, howintersection) # 输出裁剪结果 clipped.to_file(baoding_roads_clip.shp, encodingutf-8)代码逻辑是先用to_crs确保两个图层坐标一致再对线要素执行make_valid最后用overlay做交集。intersection操作会保留被县界完整覆盖的线段但注意结果里的线可能在县界边界处被切碎后续做长度统计时需要按原道路ID分组再求和。另一个习惯做法是裁剪后用select by location重新检查一下每条路是否与县界交叉如果有交叉但长度极短说明县界有微小的锯齿此时就需要用“简化”或“平滑”来修整边界线。6. 把这份SHP用起来从数据质检、符号化到打印出图的一个最小闭环6.1 数据质检用几何面积和边界长度快速识别畸形要素在正式制图或者做分析前我会先用面积和边界长度的合理区间做一轮机器筛查。河北县级行政区的面积大多在300到3000平方公里之间如果发现某个县的面积字段只有0.003那多半是单位是平方米而其他县是平方公里或者该县的图形只画了一个碎片。通过计算各要素面积的中位数和标准差可以在几分钟内找出异常值。import geopandas as gpd gdf gpd.read_file(hebei_202207_县界.shp, encodingutf-8) # 计算面积假定坐标为米制 EPSG:4549 gdf[area_km2] gdf.geometry.area / 1e6 print(gdf[area_km2].describe()) # 标记面积异常小的要素 anomaly gdf[gdf[area_km2] 10] print(异常要素:, anomaly[NAME].tolist())把geometry.area除以1e6得到平方公里前提是坐标系是米制。如果是经纬度面积单位是平方度数值会小到不可信。参数说明describe输出的最小值如果出现0就说明有零面积要素也就是退化多边形必须在后续分析中过滤掉。处理这条线后我还会对比每个县界的图形边界长度与理论值若某个县的复杂度远高于周围县可能是图形在数据化桩时插入了过多重复点不会影响面积计算但会让裁剪和叠加操作变慢许多。6.2 出图配置按道路等级做动态标注与比例尺联动质检完毕后最直接的落地方案是制作一张河北全省交通图。在QGIS里我习惯按道路等级做分级样式高速公路用红色实线、宽度2.0国道用橙色实线、宽度1.4省道用黄色实线、宽度1.0县道用虚线。标注时用“规则标注”只在高等级道路上显示名称并且设置“按比例尺缩放”当缩小到全省范围时自动隐藏县道标注避免图面挤成一团。还有一个常常被忽略的细节是道路名称字段里的“京港澳高速”“G4”等别名如果你希望图上同时显示编号和名称需要新建一个表达式字段把两个字段拼接起来。要注意字段用||连接在QGIS表达式里写成CASE WHEN ROAD_NUM IS NOT NULL THEN ROAD_NUM || || ROAD_NAME ELSE ROAD_NAME END这个表达式在属性表里可以新建虚拟字段。做了这一步打印输出时基本不用再去人工整理标注。最后导出图片时我建议把DPI设成300比例尺设为1:1200000这样打印后线条清晰。如果是用于网页展示则导出SVG或GeoJSON不要直接截图否则矢量数据在缩放时会有锯齿。这份SHP数据包真正让我省时间的部分是省界线因为很多开源数据里省界和市界不统一省内贴图总是出现细缝。从拿到数据、修复坐标系、清理路网、裁剪行政区划到最终出图我踩过最深的坑就是坐标系定义不一致导致的偏移为此我后来养成了一个习惯每次处理前先打印所有图层的CRS和范围再做任何操作。希望这篇笔记能帮你少走几步弯路把这份数据真正用起来。本文还有配套的精品资源点击获取