
简介2020年上海建筑物面数据以shp格式覆盖城市与农村区域包含建筑面要素、面积、人口等属性可用于城乡规划、内涝治理、建筑能源建模和下垫面分析等场景适合GIS研究者、SWMM建模人员及城乡可持续发展评估人员使用。rar压缩包共6个文件除主shp外还配套prj投影文件、dbf属性表、shx空间索引、cpg字符编码和xml元数据整体108.78MB层级完整导入ArcGIS或QGIS后即可读取、投影转换与裁剪。该数据能用于提取屋顶轮廓、计算不透水面覆盖率、按建筑关联人口信息支撑城市排水模型参数设置和城镇化发展评估对于需要快速获取可靠建筑轮廓底图、评价城乡发展差异的读者是较细颗粒度的基础数据。目前已有621人学习下载对需要精细下垫面数据开展建模分析的用户具有实用参考价值可直接用于研究或项目前期数据准备。1. 2020上海建筑面shp一份能直接用于空间分析的建筑轮廓基础数据做城市体检、人口密度估算、设施选址时最缺的不是一张好看的底图而是能把每栋建筑的轮廓、面积和人口串起来的底账。这份2020上海建筑物面数据城市农村shp格式正好是这类底账建筑面要素以多边形图斑保存属性里带面积和人口信息而且同时覆盖城市和农村适合直接做叠加分析。它解决的痛点是让人不必从遥感影像重新勾轮廓。适合城市规划、GIS和空间统计分析的人。不过反直觉的是shp格式数据越像拿到就能用越要做字段体检和面积重算否则后续所有统计都会带着坐标、单位的误差。2. 看一眼shp里藏了什么字段字典与坐标系是入场券shp格式数据虽然是GIS通用格式但它并不像数据库表那样自带字段注释。很多情况下一份建筑面shp的字段名是AREA、LEN、CLASS这类看不出完整含义的短名甚至可能混着编码错误的中文字段。所以拿到文件后第一件事不是双击打开画个彩色地图而是把几何类型、字段列表、坐标系和空值率打印出来看一遍。2.1 建筑面要素Polygon几何与城市/农村的区分方式建筑面要素在shp里通常保存为Polygon或MultiPolygon。普通住宅楼大多是单多边形但一些复杂建筑、带天井或连廊的楼会被处理成MultiPolygon后续做面积计算和空间连接时需要注意。用GeoPandas读文件第一行就能看清几何类型import geopandas as gpd gdf gpd.read_file(2020_上海建筑面.shp, encodingutf-8) print(gdf.columns.tolist()) print(gdf.geom_type.value_counts()) print(gdf.head().to_string()) print(CRS:, gdf.crs)这段代码会输出字段名、几何类型分布、前5行和坐标系信息。encoding参数要先试utf-8如果中文乱码再换成gbk上海本地数据用GBK编码的shp很常见。geom_type.value_counts()如果同时出现Polygon和MultiPolygon建议在第一阶段就把MultiPolygon拆开或统一否则之后做contains、within容易得出意外结果。城市和农村的区分方式常见做法是看字段取值。有的数据用CLASS区分“住宅/商业/公共”有的用TYPE直接写“城市/农村”也有用行政区代码加居村代码间接表达的。在没有数据字典的情况下我一般会先对几个可能的分类字段做频数统计for col in [CLASS, TYPE, XZDM]: if col in gdf.columns: print(col, gdf[col].value_counts().head(20))这一步不是浪费时间。知道“城市/农村”是按建设用地性质分的还是按居村边界分的决定了后续能否用它做城乡人口分组。如果这个字段取值里出现“城镇/村庄/零星建设”这类词多半是按自然资源调查口径分的如果只有“市区/郊区/开发区”则更接近行政区管理口径两者不能互相替代。2.2 面积字段先做一次体检单位、量级和NULL标题写着“包含建筑面要素、面积、人口信息”但shp里的面积字段往往不是刚做好的。常见问题有三个单位不是平方米而是公顷或亩字段是旧版本遗留和几何图形对不上部分图斑面积为0或NULL。所以拿到面积字段后我先不看它的具体数值而是看统计摘要和缺失情况。area_col AREA # 以第一步打印出的实际字段名为准 if area_col in gdf.columns: print(gdf[area_col].describe()) print(空值数量:, gdf[area_col].isna().sum()) # 再看几何量级判断原始坐标系是经纬度还是投影米 print(gdf.geometry.total_bounds)describe()会给出count、mean、min、max。如果一栋普通住宅的面积数值是几千那很可能是平方米如果同样是住宅数值却是个位数到两位数那大概率是公顷或某种缩放的投影单位。total_bounds输出的范围是[minx, miny, maxx, maxy]。如果minx在31左右miny在121左右说明是经纬度坐标必须投影后才能算面积如果minx在几百公里、miny在三千公里量级说明已经是投影坐标系但也可能是上海地方坐标不能直接用area字段代替几何面积。如果面积字段与几何不一致最可靠的办法不是修改旧字段而是用投影后几何重新算一遍。这一步我会放到下一章做原因很简单面积字段只是属性几何才是这次分析真正的地基。地基歪了后面所有汇总都是白搭。2.3 人口信息的口径是人口字段能不能用的前提人口字段是最容易让新手翻车的部分。标题只写“人口信息”但建筑面数据里的人口通常有几种完全不同的口径字段可能口径特征使用注意事项楼栋户数整数多、数值小需要按户均人口换算常住人口有小数、数值较大可直接汇总但要注意时点一致性小区/网格分摊人口多栋楼字段值相近不能逐栋当作独立人口重复汇总我在拿到人口字段后会先做一次“合理性”检查而不是直接求和。判断口径的快速方法是看数值量级和空值位置pop_col POP # 以实际字段名为准 if pop_col in gdf.columns: print(gdf[pop_col].describe()) print(人口全为0的图斑数:, (gdf[pop_col] 0).sum()) print(人口为空的图斑数:, gdf[pop_col].isna().sum()) print(人口值最大的5个图斑) print(gdf[pop_col].nlargest(5).to_frame().assign( 面积gdf[area_m2].loc[gdf[pop_col].nlargest(5).index] ))如果最大人口对应的是普通住宅楼数值可能是几十人这比较符合“楼栋常住人口”如果对应的是一个小区轮廓数值达到几千人那说明人口已经按小区聚合再分摊到楼栋需要把“建筑面”理解为“小区地块面”。如果人口字段大量为0但面积字段正常则可能是把公共建筑、学校、厂房全部放在了0人口组。这种情况下不要把所有0都当真也不要直接删掉先看0值图斑是否集中在非住宅类型里。3. 把2020上海建筑面数据整理成可用状态清洗、面积重算与人口补全上一章把字段看懂了这一步才是真正动手把shp“洗干净”。很多分析任务翻车不是因为在算法上出了问题而是因为在数据阶段少做了两步坐标没统一、面积没重算。这里我按自己习惯的顺序讲先定坐标系再算面积最后处理人口。3.1 统一坐标系从原始CRS到可算面积的投影坐标shp文件的坐标系信息存在.prj文件里但有些数据在拷贝时丢了.prjGeoPandas读出来crs会是None。如果crs是None后续任何投影转换都会报错必须先根据坐标值判断它大概是什么坐标系。print(gdf.crs) print(gdf.geometry.total_bounds)如果crs is None而且坐标范围在[x: 31, y: 121]附近基本可以断定原始数据是经纬度需要先定义成WGS84或CGCS2000地理坐标系再投影。我一般这样处理import geopandas as gpd from pyproj import CRS # 如果crs为空且坐标范围像经纬度先按WGS84地理坐标定义 if gdf.crs is None: gdf gdf.set_crs(EPSG:4326, allow_overrideTrue) # 如果原始坐标是经纬度转成适合上海的高斯-克吕格投影 if gdf.crs.is_geographic: local_crs CRS.from_proj4( projtmerc lat_00 lon_0120 k1 x_0500000 y_00 ellpsGRS80 ) gdf gdf.to_crs(local_crs) print(gdf.crs)这段代码里的lon_0120是按上海经度范围选的中央经线。真实数据如果在上海范围内三度带中央经线120度是常见选法如果数据坐标在带号边缘可以先打印所有图斑的质心经度确认。投影坐标选型的核心原则是让目标区域尽量靠近中央经线离中央经线越远面积变形就越大。把地理度坐标直接用于area计算算出来是“度的平方”没有任何实际意义这是最常见的低级错误。set_crs的作用是“声明”当前坐标系to_crs的作用是“转换”坐标系。两者区别要分清楚如果文件没有.prj只能用set_crs先声明再to_crs如果文件本身带坐标系但显示错误则不该用set_crs强行覆盖而应该用to_crs转到目标坐标。3.2 面积重算把原面积字段扔到一边坐标系确定后就可以放心重算面积了。重算面积不是“不信任原字段”而是shp里的面积属性常常不会跟随几何更新。有些数据是从CAD导入的面积字段是CAD里的旧值有些是批量合并时生成的面积和其他字段并不匹配。我的做法是保留原字段新建一个计算字段这样以后再对比也不会丢失原始信息。# 先清理无效几何避免自相交多边形导致面积偏大 gdf[geometry] gdf.geometry.make_valid() # 重算面积投影坐标下单位是平方米 gdf[area_m2] gdf.geometry.area gdf[area_ha] gdf[area_m2] / 10000 gdf[area_km2] gdf[area_m2] / 1_000_000 print(建筑面总面积平方米, gdf[area_m2].sum()) print(gdf[[area_m2, AREA]].describe())make_valid()是针对几何拓扑错误的常见补救方法。shp中的建筑多边形有时会出现自相交、重复顶点、缝隙导致area计算结果和真实轮廓相差很大。这个方法不是万能的严重的几何错误需要回到原始数据修复但对大多数建筑面数据来说已经能解决绝大部分问题。area_ha和area_km2两个派生字段是用来看量级和后续与其他数据的单位对齐用的。算完面积后我还会和原面积字段做一次对比。如果原AREA字段和area_m2之间的差值普遍在1%以内说明原始字段基本可信如果差了一倍或几个量级那就直接以area_m2为准。不需要花时间去追旧字段的计算历史因为投影方式和单位不一样时这个差值没有修复意义。3.3 人口字段补全别把NaN直接填0人口信息的处理比面积要谨慎得多。很多人拿到人口字段发现有空值顺手就fillna(0)。这个动作听起来无害实际上会直接把一批建筑的人口变成0汇总时总人口会偏低。正确做法是先判断空值集中在哪类建筑上。# 用分类字段判断空值是否集中在非住宅 if CLASS in gdf.columns: print(gdf.groupby(CLASS)[POP].apply(lambda s: s.isna().sum())) # 如果空值主要在住宅里按人均居住面积估算补全 residential_mask gdf[CLASS].isin([住宅, 商住]) if CLASS in gdf.columns else None if residential_mask is not None: density gdf.loc[residential_mask gdf[POP].notna(), POP].sum() / \ gdf.loc[residential_mask gdf[POP].notna(), area_m2].sum() gdf[POP_filled] gdf[POP].fillna(0) missing_res residential_mask gdf[POP].isna() gdf.loc[missing_res, POP_filled] gdf.loc[missing_res, area_m2] * density这笔逻辑的要点是先用“有人口且是住宅”的图斑算出一个平均人口密度单位是“人/平方米”再用这个密度去补住宅里人口缺失的图斑。如果人口字段原本是户数而不是常住人口需要先确定有没有户均人数系数同样可以按“总人口/总户数”反推。这一步的产出不一定要用真实人口数字关键是让补全逻辑清晰可复现后续做人口密度图时别人能看懂这个字段是原始值还是估算值。人口字段补全后的校验也很简单把POP_filled按街镇或居委会汇总和公开统计总量对比。偏差在20%以内说明估算系数可接受偏差超过50%不要急着调系数先回头检查空值图斑里是不是混进了学校、医院等公共建筑。4. 建筑面数据避坑5个高频问题从现象到解决shp格式数据的问题往往不是“读不出来”而是“读出来了但结果不对”。这里把我在建筑面数据上遇到的高频问题整理成5条每条都按“现象、原因、解决”来写希望能帮你少走点弯路。4.1 建筑面飞到上海以外的位置或与路网边界错位现象打开shp后建筑多边形出现在海上、农田或者明显偏离上海市域范围和底图对不上。原因.prj文件缺失GeoPandas或QGIS把投影坐标当成了经纬度也可能是坐标系被错误定义成WGS84而真实数据是上海地方坐标。解决先看total_bounds判断坐标量级。如果坐标是6-7位的大数应当先set_crs到正确的投影坐标再to_crs到目标坐标系如果坐标是小数且范围接近经纬度就按经纬度坐标系处理。反过来也一样千万不能把经纬度数据直接当米制投影坐标用。4.2 面积字段和实际几何对不上现象一个明显只有200平方米的房子面积字段写的是2把面积字段求和和区域总面积相差巨大。原因原面积字段可能是CAD里的图纸面积单位是平方米、公顷或当地特殊单位还有可能是旧版本的属性没有被重新计算。解决不要在原字段上修改新建一列用投影几何geometry.area重算。同时把原始面积字段保留作为对照方便后期追溯。对比AREA和area_m2的差异超过5%就直接放弃原字段。4.3 人口总和对不上市级或街镇统计数现象把所有建筑的人口字段加起来比统计年鉴多出几十万或少了十几万。原因人口字段口径可能是“楼栋户数”“小区分摊人口”或“网格人口”与公开人口统计口径不同图斑也可能存在重复覆盖同一个小区轮廓和内部楼栋都带人口。解决按行政编码汇总后和公开数据比对确定一个修正系数。如果人口字段是户数先乘以户均人口如果有重复覆盖需要先按空间去重保留最小楼栋图斑或最大小区图斑避免同一批人算两次。4.4 “城市/农村”字段和行政区划对不上现象看字段名CLASS以为是“城市/农村”结果统计出来很多城郊村落的图斑被标成城市或者市区里的老旧居住区标成农村。原因字段定义可能是按用地性质来的比如“城镇住宅用地”“农村宅基地”不是按行政管理边界来的。郊区里也有城镇性质用地市区里也可能有农村集体用地。解决先做value_counts()看分类值有哪些不要只看字段名。要做人口分组时最好用统计部门认可的“城镇/乡村”口径而不是直接拿这个字段当依据。4.5 几何拓扑错误导致面积和缓冲区结果偏大现象做缓冲区或面积汇总时某一栋建筑的面积突然比其他建筑大几十倍放大查看发现多边形有自相交或重复折叠。原因shp来源可能是CAD转出或由多段线手工闭合拓扑错误没有清理。解决用geometry.make_valid()批量修复如果修复后仍有问题可以输出无效几何到单独文件排查。gdf[~gdf.is_valid]可以筛出问题图斑逐一点位查看而不是把所有几何都交给算法处理。5. 把shp格式矢量数据导出为WKT让建筑面数据进入数据库和算法shp格式虽然通用但不适合直接交给数据库、算法和前端渲染。PostGIS、MySQL空间字段、R语言、Python里的shapely都更习惯使用WKT文本格式。把shp格式矢量数据导出为WKT是打通“GIS数据”和“分析计算”之间最常用的一条路。5.1 为什么要把shp转成WKT而不是直接读文件shp格式一个问题在于它是多文件组合格式.shp、.dbf、.shx、.prj少一个文件都可能读不出来。很多内存计算框架只接受一个字符串来描述几何比如POLYGON((...))。WKT解决了这个问题它把几何对象编码成纯文本属性字段可以放在同一行的CSV里。建筑面shp里的每个多边形导成WKT后就是一行字符串非常好存储和分发。另一个原因是WKT是OGC标准PostGIS、Oracle Spatial、ClickHouse、DuckDB等都能解析。用Python做完清洗后只要把几何转成WKT就能直接把整个建筑面数据灌进数据库不需要再走一遍QGIS的导入向导。5.2 GeoPandas一行导出WKT字段、精度与编码三个参数GeoPandas的to_wkt()方法可以把每个几何对象转成WKT字符串配合to_csv即可完成导出。推荐设置rounding_precision避免几何坐标全精度输出导致文件体积膨胀好几倍。import pandas as pd # 建议先重算面积并补全人口再进入导出环节 gdf[geom_wkt] gdf.geometry.to_wkt(rounding_precision3) out gdf.drop(geometry, axis1).copy() out.to_csv(2020_上海建筑面_wkt.csv, indexFalse, encodingutf-8)rounding_precision3表示坐标保留3位小数约等于毫米级精度对绝大多数城市分析和人口统计足够用。如果要做高精度边界或判断两个建筑面是否贴邻建议保留6位甚至默认全精度。drop(geometry, axis1)是必须的因为CSV不能直接保存GeoPandas几何列WKT列已经替代了它。导出WKT的同时一定要把坐标系信息也导出。WKT本身没有坐标系描述同一个POLYGON((121.4 31.2,...))在WGS84和CGCS2000里的含义相差很小但如果是投影坐标含义会完全不一样。我一般会在CSV里加一列crs_wkt内容用gdf.crs.to_string()生成。out[crs_wkt] gdf.crs.to_string() if gdf.crs is not None else unknown5.3 从WKT再回到shp一个可复用的小脚本WKT是文本但随时能变回几何。Shapely的wkt.loads可以把字符串解析成几何对象再组装成GeoDataFrame写回shp。这个过程适合做数据交换、备份或给同事临时传文件。import pandas as pd import geopandas as gpd from shapely import wkt df pd.read_csv(2020_上海建筑面_wkt.csv, encodingutf-8) df[geometry] df[geom_wkt].apply(wkt.loads) gdf2 gpd.GeoDataFrame(df, geometrygeometry) # 如果导出时保存了crs_wkt可以恢复坐标系否则手动指定 if crs_wkt in df.columns and df[crs_wkt].iloc[0] ! unknown: gdf2 gdf2.set_crs(df[crs_wkt].iloc[0], allow_overrideTrue) gdf2.to_file(2020_上海建筑面_复建.shp, encodingutf-8)这个脚本的关键点是wkt.loads会自动识别POLYGON、MULTIPOLYGON等几何类型不需要额外写解析逻辑。需要注意的坑是geom_wkt如果来自不同投影坐标必须在重建时恢复正确的CRS否则后续空间分析会整体错位。WKT是几何的“内容”CRS是几何的“坐标参照”两兄弟缺一不可。6. 用2020上海建筑面做人口密度与设施覆盖两个进阶验证技巧数据清洗完之后下一步就是拿它做点真分析。这里分享两个我常用的验证思路。6.1 按渔网网格重算人口密度建筑面是带空间位置的点状多边形直接按行政区汇总会损失内部分布信息。我一般会把上海切成500米或250米渔网按建筑面与网格的相交面积分摊人口得到一张连续的人口密度图。# 生成500m渔网并做面积权重分摊这里是核心逻辑示意 grid gpd.GeoDataFrame( gpd.GeoSeries([ box(minx i * 500, miny j * 500, minx (i 1) * 500, miny (j 1) * 500) for i in range(int((maxx - minx) / 500) 1) for j in range(int((maxy - miny) / 500) 1) ]), columns[geometry], crsgdf.crs ) intersect gpd.overlay(grid, gdf, howintersection) intersect[share] intersect.geometry.area / 500 / 500 intersect[grid_pop] intersect[POP_filled] * intersect[share]这个结果可以直接用来看人口在空间上的冷热区比单纯按街镇染色更接近真实分布。6.2 用建筑面校验设施覆盖率设施服务范围用圆形缓冲区虽然粗糙但作为第一版校验够用。把幼儿园、社区医疗点做500米缓冲区再和建筑面相交统计被覆盖的建筑数量和人口buffer facility.geometry.buffer(500) covered gpd.sjoin(gdf, buffer[[geometry]], howinner, predicateintersects) print(覆盖建筑数:, covered[geometry].nunique()) print(覆盖人口:, covered[POP_filled].sum())这版验证适合先快速判断设施缺口正式结论还是要用路网可达性替代缓冲区。早年我拿到建筑面数据第一步就着急画专题图结果一张人口密度图里混着总面积和建筑面积两种单位找了一下午才发现是面积字段没重算。后来不管谁给我的shp格式数据我都先跑一遍坐标、面积、人口三位一体的体检再谈分析。希望帮到你。本文还有配套的精品资源点击获取