简介:这是一份北京市十六个区县级行政区划矢量数据包,覆盖全部市辖区,属性信息完整,面向GIS数据分析、地理教学、地图制图、城乡规划与可视化报表等场景的使用者。数据以标准shp格式组织,可直接导入ArcGIS、QGIS、SuperMap等主流平台访问;属性表包含各行政区名称、区域面积等字段,便于进行区域面积对比、人口经济数据挂接、专题图制作、空间查询与基础统计分析,免去手工整理底图的环节。压缩包共7个文件,包含shp、dbf、prj、shx等必要组件,其中shp记录几何边界,dbf承载名称与面积等属性,prj提供坐标参考信息,结构简洁,整体仅53KB,轻量易用。目前已有1562人学习下载,适合需要快速获取北京区划底图、做区县尺度空间可视化或开展区域对比分析的读者;也可与在线底图、统计年鉴数据叠加,进一步承载人口密度、经济总量等分析。
1. 北京行政区划shp:属性清晰为什么比边界本身更值钱
做地图可视化和行政区统计的人,大概率都经历过找底图的折腾。网上的北京行政区划shp版本不少,真正能直接用的很少,多数版本要么坐标对不上底图,要么字段叫FID、SHAPE_Leng这种系统默认名,拿到手得花半天猜每个字段是什么。所以“属性清晰”这四个字才是关键:边界多边形只是外衣,属性表才是能与业务数据对话的桥梁。
一份属性清晰的北京行政区划shp,通常意味着字段名规范(adcode、name、level、parent)、区划代码是更新后的官方版本、中文不乱码、层级能对得上。它能直接用来做行政区聚合统计、按区配色的地图、地址与区划代码的匹配。我早期给某市级大数据可视化项目做底图时,拿到的数据几何没问题,但属性表里区划代码新旧混杂,光清洗就花了两天。后来才意识到,这类基础数据,属性清晰比边界精确更值钱。适合读这篇笔记的,是做数据可视化、GIS空间分析、地址落区匹配的从业者,目标就一个:拿到北京行政区划shp后,能用最短时间确认它能不能用、怎么用、坑在哪。
2. 先摸清北京行政区划shp的底细:组成文件、坐标系与属性字段
2.1 一个shp不是单个文件:.shp、.shx、.dbf、.prj各管什么
第一次接触shp的人常踩同一个坑:把“北京行政区划.shp”单独拷走,到另一台电脑上打不开,或者打开后只剩几何没有属性。这不是数据坏了,而是Shapefile天生就是一组文件,不是一个文件。
一个完整的Shapefile至少包含三个文件,再加上可选的投影和编码文件,实际使用中最好整体管理。
| 扩展名 | 作用 | 如果丢了会怎样 |
|---|---|---|
| .shp | 要素几何坐标 | 打不开 |
| .shx | 几何索引,负责要素顺序 | 部分软件能打开但很卡 |
| .dbf | 属性表,存adcode、name等字段 | 只剩几何,没有任何属性 |
| .prj | 坐标系与投影定义 | 软件按默认坐标系猜,容易错位 |
| .cpg | 属性表字符编码声明 | 中文可能乱码,需要猜测编码 |
.dbf就是属性清晰问题的根源。它用dBase格式存储,字段名长度上限是10个字符,早期数据用拼音缩写做字段名就是这么来的。属性清晰的北京行政区划shp,字段名一般不会是X1、X2这种简写,而是adcode、name、level、parent、lng、lat、childrenNum这一组约定俗成的命名。拿到数据先看字段名是否可读,就能判断这份数据是被人认真整理过,还是直接从某个系统倒出来的。
另一个容易被忽略的是.cpg文件,它记录.dbf的字符编码。Shapefile格式本身没有强制规定属性表编码,中文数据出现乱码是常态。很多“属性清晰”的版本会附带.cpg注明UTF-8或GBK,这决定了后面用代码读取时该传什么encoding参数。如果你做的是工程而不只是在GIS软件里点两下,一定要把这几个文件当成一个整体来管理,放在同一个目录下统一打包,不要单独复制其中某一个。
2.2 坐标系先统一再谈属性:CGCS2000、WGS84与投影坐标的选择
属性清晰可以和坐标系割裂开看吗?不能。如果prj信息丢失或写错,再清晰的属性表也会在地图上错位。
北京地区的行政边界数据,最常见的坐标系有三类。第一类是CGCS2000高斯投影,具体到北京往往是“CGCS2000 / 3-degree Gauss-Kruger CM 117E”,中央经线取117°E,单位是米,适合做面积计算和精度要求较高的工程分析。第二类是WGS84经纬度,单位是度,适合直接输出给Web地图、GeoJSON和空间数据库。第三类是GCJ-02加密经纬度,很多从在线服务取来的边界坐标属于这一类,它相对WGS84做了偏移,和卫星底图对不上。
| 数据样式 | 坐标值量级 | 常见用途 | 是否需要处理 |
|---|---|---|---|
| CGCS2000投影 | X约几十万到几百万,Y约几百万 | 本土工程分析、面积统计 | 输出给前端时转经纬度 |
| WGS84经纬度 | 经度115~117,纬度39~41 | Web地图、GeoJSON | 可直接用 |
| GCJ-02经纬度 | 经度115~117,纬度39~41 | 在线地图来源的边界 | 与WGS84混用会偏移几百米 |
用GIS软件打开一份没有.prj的shp时,软件会默认拿WGS84去套。如果这份数据本来是米制投影坐标,地图上的要素就会跑到海里去;反过来,如果经纬度数据被赋予了投影坐标系,要素位置会挤压变形。这类问题在刚拿到数据时很难发现,通常要叠加在线底图后才意识到错位,看起来有点“玄学”。
判断方法其实很直接:看坐标值量级。经纬度坐标的范围应该在东经115到117度、北纬39到41度之间;米制投影坐标的范围则是X从几十万到几百万、Y在几百万左右。如果几何范围不符合,说明坐标系定义有问题,需要重设prj再做转换。选数据时优先选带.prj且声明CGCS2000或WGS84的版本,能省掉后面一大半排查时间。
2.3 属性清晰到底怎么检查:用ogrinfo五分钟巡视字段结构
拿到一份北京行政区划shp后,我建议先别急着扔进GIS里看,先做一次字段结构体检。最省事的工具是GDAL自带的ogrinfo命令行,一行命令就能输出图层要素数量、几何类型、坐标范围、字段列表。
# 只读方式打开并输出概要信息;-so表示不逐要素打印,-al处理所有图层 ogrinfo -ro -so -al 北京行政区划.shp参数说明:-ro是只读模式,避免误操作改写数据;-so是summary only,只输出概要信息,不会把几千个要素的几何全部打出来;-al表示遍历所有图层。如果GDAL没有安装,可以通过Python的gdal包获得命令行工具,或者直接进入下一步的geopandas读取。
一份典型的清晰属性表长这样:
| 字段名 | 类型 | 含义 | 典型用途 |
|---|---|---|---|
| adcode | 字符串 | 行政区划代码,6位区县/9位街道/12位村居 | 与业务表join |
| name | 字符串 | 行政区名称 | 地图标注 |
| level | 整数 | 层级:省、市、区县、街道 | 按层级过滤 |
| parent | 字符串 | 上级区划代码 | 生成级联关系 |
| lng, lat | 双精度 | 几何中心点坐标 | 标签避让 |
| childrenNum | 整数 | 下级要素数量 | 校验数据是否完整 |
用这张表去对照你的数据:如果adcode缺失,后面做行政区聚合就无从谈起;如果level字段靠猜,层级统计也不可信。“属性清晰”并不是字段多多益善,而是字段语义明确、层级完整、代码版本一致。字段名就像一本字典,决定了你后续写代码时是直接写gdf["adcode"],还是得反复确认X1字段到底是什么。体检完字段,如果字段合格,再进入下一步的工程处理。
3. 把“属性清晰”投入生产:编码修复、新旧adcode对齐与join实操
3.1 中文乱码并不等于文件损坏:两种编码读取路径
前面提到Shapefile的.dbf没有统一的编码元数据,中文数据读取乱码几乎每个人都遇到过。处理乱码的第一原则:不要认为文件坏了,先检查读取端在用哪种编码解码。
第一种路径是用GDAL的OGR_ENC环境变量,让GDAL统一按指定编码读取dbf字段。
import os import sys from osgeo import ogr # 指示GDAL用GBK解码dbf,很多国产工具导出的shp都是这个编码 os.environ["OGR_ENC"] = "GBK" ogr.UseExceptions() ds = ogr.Open("北京行政区划.shp", 0) if ds is None: sys.exit("打开失败,确认路径与文件名") layer = ds.GetLayer(0) defn = layer.GetLayerDefn() for i in range(defn.GetFieldCount()): fld = defn.GetFieldDefn(i) print(fld.GetName(), fld.GetType())这段代码的逻辑是:先设置环境变量,再打开数据,循环打印字段名和类型。ogr.UseExceptions()的作用是把GDAL底层错误转换成Python异常,方便定位路径问题。GetLayerDefn()获取图层结构定义,不会读取全部要素,速度很快。如果这里打印出的字段名仍然乱码,再试把OGR_ENC改成UTF-8——有些数据源用UTF-8写入,但.cpg缺失,GDAL默认按GBK处理了。
第二种路径是geopandas路线,读取时显式指定encoding参数。
import geopandas as gpd # 先按UTF-8尝试,报UnicodeDecodeError就换gbk try: gdf = gpd.read_file("北京行政区划.shp", encoding="utf-8") except UnicodeDecodeError: gdf = gpd.read_file("北京行政区划.shp", encoding="gbk") # 验证关键字段是否可读 print(gdf[["adcode", "name"]].head())read_file会同时读取几何和.dbf属性,参数encoding只影响属性表。这里用try/except做两级尝试,比先用某个编码打开再确认乱码更省事。需要注意:geopandas内部用的是Fiona读取,环境变量的优先级高于read_file参数,如果你同时在脚本里设置了OGR_ENC,read_file的encoding可能不生效。建议在脚本开头统一管理编码策略,要么全走环境变量,要么全走参数。
3.2 adcode对不上行政区划版本:旧代码映射与再校验
北京行政区划经历过多次调整,不同年份导出的shp,adcode可能相差一代。比如老代码里有已经撤销的区级代码,新区划里已经没有它们了。拿业务表直接和最新shp做join,这些老代码的记录会全部匹配不上,留下一大堆name为空的坏数据。
常见做法是维护一张新旧代码映射字典,在join之前先把业务代码归一化。
# 旧区划代码 -> 现行区划代码(示例映射,实际以最新标准代码表为准) adcode_map = { "110103": "110101", "110104": "110102", "110226": "110228", # 占位示例 } def normalize(code): code = str(code).split(".")[0] # 去掉浮点尾巴 code = ("000000" + code)[-6:] # 补齐6位区县代码 return adcode_map.get(code, code) # 命中映射则转换,否则原样返回 # 应用到业务数据 orders["adcode_norm"] = orders["adcode"].map(normalize)代码里的细节值得说明:先把数值型代码转字符串,去掉可能出现的“.0”后缀,再右补零保证6位。区县代码是6位,街道级是9位,如果你处理的是街道级数据,补齐逻辑要改成9位。字典映射只是兜底手段,更可靠的是用标准行政区划代码表做全量映射,而不是自己手写几条。手写字典适合快速验证,长期维护不现实。
映射完成后,还要做一次双向校验:一是统计映射后仍然匹配不上的adcode有多少,二是查看这些未匹配代码是不是园区、开发区等功能区代码,这类代码往往不纳入标准行政区划编码。用shp里的adcode集合反向筛查业务数据,能很快定位脏数据来源。生产环境里,我一般会把这份映射表独立成csv文件维护,业务代码里只引路径,不写死字典。
3.3 join到行政区边界:重复、类型、空值三个关卡
把业务数据join到北京行政区划shp上,听起来只是一次merge,真正动手时要过三个关卡:重复、类型、空值。
关卡一是重复。如果shp里同一个adcode出现了两次,比如某个区由多个多面子要素拼成,直接merge会把业务数据按同一条代码复制多份,统计结果被成倍放大。所以join前先确认adcode有没有重复。
import pandas as pd import geopandas as gpd # 业务表读取时直接按字符串读adcode,避免浮点数 orders = pd.read_csv("订单表.csv", dtype={"adcode": str}) shp = gpd.read_file("北京行政区划.shp", encoding="gbk") shp["adcode"] = shp["adcode"].astype(str) # 关卡1:检查shp里adcode是否重复 duplicated = shp["adcode"].duplicated().sum() print("重复adcode数量:", duplicated) assert duplicated == 0, "存在重复区划代码,先按level去重" # 关卡2:类型统一后再merge merged = orders.merge( shp[["adcode", "name", "geometry"]], on="adcode", how="left" ) # 关卡3:检查匹配率 print(merged["name"].isna().mean())代码里两个关键点:一是astype(str)统一类型,如果shp的adcode是int64而orders里是string,merge会匹配不上;二是how="left"保留订单全量,再用name.isna()检查哪些代码没匹配上。如果shp本身通过了重复检查但匹配率仍然低,就要回查业务表是不是混入了老代码,回到3.2的映射步骤处理。
空值的处理也有技巧。join完成后,不能只看匹配率,还要看匹配到的行政区名称个数是否符合预期。北京区级shp应该匹配出16个区,如果匹配出18个不同name,说明有代码版本混杂或功能区代码混进来了。这一层校验很多人不做,结果地图上多出几个不存在的“区”,统计口径全都对不上。属性清晰的shp在这里的价值会显现:字段语义明确,你才能一眼看出哪里多了、哪里少了。
4. 北京行政区划shp使用避坑:五类高频现象与修复套路
4.1 中文乱码:name字段全是乱字符,图能画但没法标
现象:在GIS里打开,边界形状正常,但name字段显示成“???”或“æµ·æ·”等乱码,地图无法标注,导出图表时名称全是问号。
原因:dbf属性表实际以GBK编码写入,读取端按UTF-8解码;或者反过来。还有一种情况是.cpg文件存在,但内容与实际编码不一致,数据导出工具写错了编码声明。
解决:先看有没有.cpg文件,有则按它的值读取;没有则依次尝试UTF-8、GBK、GB18030。用geopandas的encoding参数或GDAL的OGR_ENC环境变量都行,如果只是在GIS里看一眼,也可以在软件里手动切换字符编码。不要直接去改dbf文件,改错会丢属性。这条的经验是:编码问题不影响几何,所以最容易忽略,但恰是“属性清晰”最容易翻车的点——几何是准的,属性是乱的。
4.2 adcode版本不一致:业务表大量匹配不上
现象:join之后,业务数据里有相当一部分adcode在shp中不存在,匹配率只有六七成,地图上一片空白。
原因:业务表里的区划代码是几年前的版本,shp是新区划版本;或者业务表包含开发区、功能区代码,这些代码不在行政编码里。
解决:先建新旧代码映射再join。这里强调一个小习惯:拿到业务表后先orders["adcode"].nunique()看有多少不重复代码,再对比shp里的adcode集合,两个集合的差异才是真正要手工处理的量。如果差异占了一半,不要手工映射,直接用标准行政区划代码表做全量匹配。不要因为匹配率低就怀疑数据源,很多时候只是版本问题。
4.3 几何自相交与缝隙:属性没问题,空间分析却对不上
现象:做相邻区合并、面积统计、栅格叠加时,结果比预期大或出现异常值,几何检查提示自相交。
原因:shp在数字化或裁剪拼接过程中产生了自相交、重叠、缝隙。属性表再干净,几何拓扑有问题,空间操作就会基于错误的边界。自相交要素在视觉上往往看不出来,因为折返点很小。
解决:先备份原始几何,再统一修复。
from shapely.validation import make_valid import geopandas as gpd gdf = gpd.read_file("北京行政区划.shp", encoding="gbk") gdf["geometry"] = gdf["geometry"].map(make_valid)make_valid会对每个几何做拓扑修复,处理自相交和小缺口。修复后检查要素数量和面积范围是否符合北京实际情况,如果面积数值很离谱,说明投影坐标系单位有问题。之后再做任何空间操作,都建议先做一次拓扑检查。这个坑踩一次就知道,修复方法基本就是这一条,但能提前发现的人不多。
4.4 level字段与adcode自相矛盾:层级统计不可信
现象:shp里level字段标记为“区县”,但adcode长度是9位,其实是街道级;或者childrenNum统计的下级数量与实际要素数对不上。
原因:多个来源的数据拼接后,底层字段没有同步更新。单独看每个字段都有值,放在一起就矛盾。
解决:不要信任level字段,以adcode长度为准重新推断层级。adcode长度6位是区县,9位是街道乡镇,12位是村居。
def infer_level(code: str) -> str: n = len(code) if n == 6: return "区县" if n == 9: return "街道乡镇" if n == 12: return "村居" return "未知" gdf["level_ok"] = gdf["adcode"].map(infer_level) print(gdf["level_ok"].value_counts())这个检查通常能暴露数据集的真实范围。很多网上标着“北京行政区划”的shp其实是16个区级边界,但level字段可能标得五花八门。重新推断后再决定要不要保留原level字段。层级是后续做下钻地图的基础,这里错了,整张图层都不可用。属性清晰的数据在这一点上很有优势:adcode和level能互相印证,而不是互相打架。
4.5 坐标系错位:差几百米的“看起来没毛病”最坑
现象:把shp叠加到在线地图上,边界整体偏移,或者完全不在北京区域。
原因:最常见两种情况——shp缺少.prj,读取工具默认按WGS84套;数据是GCJ-02加密坐标,被当成WGS84使用后整体偏移几百米。前者的错位往往几百到上千公里,一眼能看见;后者只有几百米,小比例尺下不明显,叠加道路或POI时就会露馅。
解决:先确认坐标系,看坐标量级;然后重设正确的crs再转成目标坐标。
import geopandas as gpd gdf = gpd.read_file("北京行政区划.shp") # 坐标值量级为米制时,先声明正确的投影坐标系 gdf = gdf.set_crs("EPSG:4547") # CGCS2000 3度带中央经线117E gdf_wgs84 = gdf.to_crs("EPSG:4326") # 转成Web常用的经纬度这里set_crs只是声明当前坐标系,不改变坐标值;to_crs才做真正的投影转换。如果不确定原始坐标系,先用图层范围反推:米制投影的X坐标通常有几十万到几百万的量级,经纬度则只有两位整数。不要直接做四参数或七参数变换,那需要已知控制点,对一般使用场景来说,选用CGCS2000版本的shp比做参数转换靠谱得多。
5. 在属性清晰的基础上再进一步:层级校验与按区切片导出
5.1 用属性字段拼出层级链并校验完整性
数据进入生产环境之前,我习惯做一次层级链校验。所谓层级链,就是从adcode长度和parent字段还原出“省-市-区-街道”的上下级关系,再和字段声明对比。代码很短:
import geopandas as gpd gdf = gpd.read_file("北京行政区划.shp", encoding="utf-8") gdf["adcode"] = gdf["adcode"].astype(str) gdf["level_inferred"] = gdf["adcode"].str.len().map( {6: "区级", 9: "街道级", 12: "村居级"} ) # 按层级统计要素数,区级应为16个(北京市辖区数量) print(gdf["level_inferred"].value_counts())这个校验的价值在于:拿到任何shp都能在三十秒内知道它到底覆盖到哪一层。很多所谓“北京行政区划shp”下载下来发现只有区级,但业务需要街道级,早点发现就不用浪费时间整理。如果childrenNum字段也在,可以顺带做个交叉验证:每个区下面的街道数量通常稳定在几位到二十位之间,出现过大的值说明层级判定有问题。
5.2 按区导出GeoJSON切片,前端地图不再卡顿
最后一个常用技巧是把区级要素切片输出。前端加载全量shp或一次性转出的综合GeoJSON时,数据量一大交互就卡;把每个区单独导出为一个GeoJSON,加载体验会好很多。
import geopandas as gpd import os os.makedirs("slices", exist_ok=True) gdf = gpd.read_file("北京行政区划.shp", encoding="utf-8") county = gdf[gdf["adcode"].str.len() == 6].copy() for _, row in county.iterrows(): one = gpd.GeoDataFrame([row], crs="EPSG:4326") one.to_file( f"slices/{row['adcode']}.geojson", driver="GeoJSON", encoding="utf-8" )代码逻辑:先按adcode长度过滤出6位区级要素,然后逐条构造单要素GeoDataFrame并导出。注意to_file里driver必须写成GeoJSON,否则geopandas可能按shapefile处理;crs建议统一成EPSG:4326,省得前端还要做投影转换。切出来后每个区一个文件,单个文件通常只有几百KB,地图加载速度会明显改善。做下钻地图时,这就是一份属性清晰的数据在工程端的“最后一公里”。
我早期做行政区下钻地图时,为了让“属性清晰”名副其实,把原始shp里用不到的字段全部删掉,只留adcode、name和geometry。结果后来要做行政区联查,没有parent字段,不得不重新回去找原始数据,走了不少弯路。那之后我养成了一个习惯:属性清晰不等于字段越少越好,层级代码和上级代码这类关键字段必须保留,它们才是让一份shp从“能画图”变成“能分析”的分水岭。希望帮到你。
本文还有配套的精品资源,点击获取