☰
上海2020年POI矢量数据解析:shp、DEM与GIS清洗实战
2026/10/26 17:42:16 网站建设 项目流程

简介:上海市2020年POI矢量数据集是一份面向GIS研究者、城市规划人员及地图开发者的综合性地理信息数据包,涵盖医疗保健、住宿服务、商务住宅、科教文化、风景名胜等多类兴趣点(POI),并附带30米分辨率数字高程模型(DEM)与区县级行政区划边界,适合用于地形模拟、商业选址、公共服务设施分布评估及城市空间结构分析等场景。压缩包内共126个文件,总大小80.65MB,以15个shp矢量文件及其配套dbf属性表、prj投影定义、sbn/sbx空间索引为主,辅以14个xlsx表格文件便于非GIS用户直接查看统计,另有tif格式DEM高程栅格及tfw、ovr等辅助文件。数据同时提供shp与Excel两种形态,既满足ArcGIS、QGIS等专业平台的空间分析与制图需求,也方便业务人员快速检索地址、分类等属性信息。目前已有815人学习下载,是开展上海地区多源地理数据融合与专题制图的实用基础资料。

1. 上海2020年POI矢量数据集:这份压缩包里到底装了什么

做城市分析、商业选址或者区域评估的人,大概率经历过这种局面:想要一份覆盖全市的POI点位数据,打开地图平台挨个爬,跑断 IP 还被反爬卡脖子;费半天劲拿到手,又是加密的 Excel 表格或者字段残缺的 CSV。这份上海市 2020 年 POI 矢量数据集解决的就是这个「找数据比做分析还费劲」的问题。压缩包把三类常用数据打包在了一起:shp 格式的 POI 矢量面/点数据、Excel 格式的 POI 台账、30 米分辨率的 DEM 高程栅格,外加行政区划边界。对做 GIS 可视化和空间统计的从业者来说,这是一份拆开即用的基础数据包,省去的是数据获取和预处理最头疼的那一两个月。下面我按拆包、加载、清洗、用 DEM、踩坑这个顺序,把这份资源的完整使用过程捋一遍。

2. 解压与格式拆解:7z、shp、excel 三类文件的读取路径

2.1 先过 7z 这一关:常见解压失败与命令行思路

拿到的是一个 .7z 后缀的压缩包,比 zip 的压缩率高,但踩坑概率也高。最常见的翻车现场是拿老版本 WinRAR 双击解压,中途报「数据错误」或者解出来一堆 0 字节文件。原因很简单:7z 格式的固实压缩特性,在分块解压时对版本要求更敏感。我一般直接用 7-Zip 官方版解压,右键 → 提取到当前目录就行,但如果你是在 Linux 服务器上处理,命令行更稳:

# 安装 7zip 命令族,发行版不同包名略有差异 sudo apt install p7zip-full # 解压整个归档,-o 后面指定输出目录,注意冒号与路径之间没有空格 7z x Shanghai_POI_2020.7z -o/data/geo_data/

逻辑说明:7z x保留压缩包内的目录结构,-o指定输出的全路径,忘写这个参数默认解到当前目录,散文件容易和已有数据混在一起。参数上有两个习惯:一是加-y覆盖已存在的同名文件,适合重跑脚本;二是不加-y保留手动确认机会,防止误覆盖。解压过程中如果出现CRC Failed,大概率是压缩包下载不完整,别浪费时间修复,重新下载比折腾恢复快。

解压完成后,你会看到至少四类东西:POI_shp/(矢量数据目录)、POI_excel/(表格台账目录)、DEM/(高程栅格)、District/(行政区划矢量)。这是这份资源比较良心的点——分类目录不是堆在一起的乱文件。

2.2 看清 shp 的本质:不是单文件,是一组文件的合称

很多人拿到 shp 数据后只拷贝.shp后缀那个文件发给别人,这是最容易翻车的操作。Shapefile 格式在技术上是一组文件的集合,至少包含三个核心成员:主文件.shp存储几何坐标,索引文件.shx存储几何位置偏移量,属性表.dbf存储字段属性。除此之外还可能带.prj(坐标系描述)、.cpg(编码声明)、.sbn/.sbx(空间索引)等附属文件。缺了.dbf,属性表是空的;缺了.prj,坐标系信息丢失,后续叠加分析会出现位置偏移的隐患。一份完整的 shp 集合,拷贝时至少要带上.shp/.shx/.dbf/.prj这四个文件。

2.3 Excel 格式 POI 的正确读法:Pandas 与编码问题

Excel 格式的POI数据一般用来做常规的表格处理、透视统计,或给不会用 GIS 的同事做报表。但这个环节有一个非常隐蔽的坑:.xlsx用的是 XML 结构,读取时不会暴露编码问题,但如果你用 WPS 或老版本 Office 另存过一次.xls,再读进来的中文字段容易出现乱码。用 Python 读 Excel 格式 POI 的常见写法是:

import pandas as pd # 读取 Excel 格式 POI,sheet_name 指定为明细表 poi_df = pd.read_excel( "POI_excel/shanghai_poi_2020.xlsx", sheet_name="POI", dtype={"POI_ID": str, "ADCODE": str} ) # 打印列名和行数确认读取完整 print(poi_df.columns.tolist()) print(poi_df.shape)

逻辑说明:dtype参数把 ID 和行政区划代码强制设为字符串,否则POI_ID以数字开头时,Pandas 会自动解析成 int64,前导零丢失。该参数在此类数据读取中尤其重要,因为区划代码、POI 编号这类字段本来就是文本标识,不是数值。列名清单打印出来之后,先核对有没有缺列,再继续清洗。

3. shp 格式 POI 的加载与清洗:从 ArcGIS 到 Python 的完整链路

3.1 ArcGIS 直接加载与属性表识别

如果你用的是 ArcMap 10.8 或 ArcGIS Pro,直接在 Catalog 中连接到 POI_shp 目录,把对应图层的 shp 文件拖进内容列表。加载后先做两件事:右键图层 → 打开属性表,确认记录数和字段数;再右键 → 属性 → 源,确认坐标系。因为这份包里同时有 POI 和行政区划两个 shp,坐标参考如果不一致,后续空间连接会静默出错,或者出现图层偏移到海里的现象。ARCGIS 中加载 shp 后如果字段名出现乱码,比如本该是name却显示成乱符号,属于.cpg编码声明丢失,一般解决方式是使用 QGIS 打开再重新导出。

3.2 Python 读写 shp:GeoPandas 是效率首选

不想依赖桌面 GIS 软件时,GeoPandas 是处理 shp 最顺手的库。它的 DataFrame 结构让筛选、分组、空间连接都能用熟悉的 Pandas 思维完成。这里我一般会用两步:先读取 shp 看结构,再按行政区划代码筛选出目标区域的数据。

import geopandas as gpd # 读取 POI 点图层 poi_gdf = gpd.read_file("POI_shp/shanghai_poi.shp", encoding="utf-8") # 读取行政区划面图层 district_gdf = gpd.read_file("District/shanghai_district.shp", encoding="utf-8") # 查看坐标系和记录数,确认加载成功 print(poi_gdf.crs) print(poi_gdf.head())

逻辑说明:encoding参数在 shp 读取中经常被忽略,但上海市的数据字段名通常带中文,像名称、地址、区县,如果.cpg文件缺失或者损坏,encoding="utf-8"能保证中文属性正常显示。坐标系crs属性返回的是投影信息,如果 POI 是EPSG:4326(经纬度)而行政区划是EPSG:3857(Web 墨卡托),两者叠加前必须统一到同一个坐标系。

3.3 坐标统一与 POI 分类字段清洗

这份资源里 POI 的字段设计一般会比较规整,通常包含POI_ID、名称、大类、中类、小类、地址、区县、经度、纬度。但规整不等于可以直接用,常见的两个毛病:一是经纬度字段是字符串格式并且可能有空格;二是「大类」字段存在多个值,比如某个银行网点同时被标了「金融」「生活服务」两个类别。清洗时按以下思路处理:

import pandas as pd # 清洗经度纬度列:去空格后再转 float poi_gdf["经度"] = pd.to_numeric(poi_gdf["经度"].astype(str).str.strip(), errors="coerce") poi_gdf["纬度"] = pd.to_numeric(poi_gdf["纬度"].astype(str).str.strip(), errors="coerce") # 类别字段只取主类别,分隔符常见的有中文顿号或斜杠 poi_gdf["主类别"] = poi_gdf["大类"].str.split("、|/").str[0] # 删除经纬度缺失的记录,静默过滤而不是报错中断 poi_gdf = poi_gdf.dropna(subset=["经度", "纬度"]) print(poi_gdf["主类别"].value_counts().head(10))

逻辑说明:errors="coerce"参数很关键,它把无法解析成数字的非法字符串转成NaN,代替直接抛异常,适合脏数据多的真实场景。split("、|/")是正则拆分,或号逻辑同时处理中文顿号和斜杠两种分隔符,这是清洗多值字段时最简单可靠的做法。清洗之后再做分类统计也好、做核密度分析也好,误差会小很多。

3.4 按行政区划提取特定范围的 POI:空间裁剪的正确姿势

如果只需要黄浦区或者浦东新区的 POI,不必在属性表里筛选所有区县的数据。用空间连接或者空间裁剪的方式更稳妥,尤其是当 POI 坐标有微小偏差时,属性筛选可能把边界上的点漏掉。在 GeoPandas 中做裁剪,常见做法是建立空间索引后再查询:

import geopandas as gpd # 读取 POI 与行政区划 poi_gdf = gpd.read_file("POI_shp/shanghai_poi.shp", encoding="utf-8") districts = gpd.read_file("District/shanghai_district.shp", encoding="utf-8") # 确保两图层坐标系一致,常见做法是统一到目标区划的坐标系 districts = districts.to_crs(poi_gdf.crs) # 取出浦东新区的外边界 pudong = districts[districts["name"] == "浦东新区"] # 空间连接模式裁剪:保留落在面内的点 pudong_poi = gpd.sjoin(poi_gdf, pudong, how="inner", predicate="within") print(pudong_poi.shape)

逻辑说明:to_crs是把行政区划图层重新投影到 POI 所在坐标系,避免因坐标系不一致产生空间关系错误。sjoin是空间连接,predicate="within"表示只保留完全落在面内部的点,边界上的点如果因为浮点精度问题被剔除,可以改成intersects放宽条件。这个环节是空间分析的常规门槛,坐标系统一这一步没做,后面的一切统计都不可信。

4. 30 米 DEM 数据的正确应用:按区裁剪与栅格陷阱

4.1 认识 30 米 DEM:分辨率和数据格式的边界条件

标题里写的 30M 分辨率 DEM,指的通常是 30 米分辨率的数字高程模型,来源多为航天飞机雷达地形任务或同类公开数据源。30 米代表每个像元对应地面上 30×30 米的面积,用来做城市尺度分析够用,做地块级高差评估则精度不足。DEM 的常见存储格式是 tif 或 img 栅格,这份压缩包里给出的通常是 tif。和矢量数据不同,栅格不能像 Excel 一样直接打开看属性,需要专门的软件读取,ArcGIS 里是加载后查看「图层属性 → 源 → 像元大小」,Python 里则是用 rasterio 读取元数据。

4.2 ArcMap 中按面图层裁剪 DEM:掩码提取与裁剪工具的差别

在 ARCGIS 中处理 DEM 时,新手最容易搞混的是「按掩膜提取」和「裁剪工具」的边界条件。ARCMAP 里依靠面图层裁剪 DEM 栅格 tif 文件的常用方式是「按掩膜提取」,在 ArcToolbox 里路径是 Spatial Analyst 工具 → 提取分析 → 按掩膜提取。输入栅格是 DEM,掩膜数据选择行政区划面图层,输出就是裁好的局部 DEM。两者差别在于:裁剪工具要求输入和输出格式遵循严格的矩形范围,掩膜提取则按面要素的几何形状裁剪,边缘是渐变的。做地形分析时用按掩膜提取。实际操作中有一个影响结果的因素:掩膜面图层和 DEM 的坐标系不一致时,裁剪结果可能为空或者边角错位,建议在工具参数里先把环境设置中的处理范围改为面边框。

4.3 Python 按区批量裁剪 DEM:rasterio 加 GeoPandas 组合

需要按多个区的边界批量切 DEM 时,桌面软件逐个操作效率太低,用 rasterio 加 GeoPandas 批量处理是常规做法:

import geopandas as gpd import rasterio from rasterio.mask import mask # 读取行政区划面图层,注意坐标系必须是 DEM 的坐标系 districts = gpd.read_file("District/shanghai_district.shp", encoding="utf-8") dem_path = "DEM/shanghai_dem_30m.tif" # 读取 DEM 元信息 with rasterio.open(dem_path) as src: dem_crs = src.crs districts = districts.to_crs(dem_crs) for idx, row in districts.iterrows(): name = row["name"] geom = [row["geometry"]] with rasterio.open(dem_path) as src: out_image, out_transform = mask(src, geom, crop=True) out_meta = src.meta.copy() # 更新元数据并写入新文件 out_meta.update({ "driver": "GTiff", "height": out_image.shape[1], "width": out_image.shape[2], "transform": out_transform }) with rasterio.open(f"DEM_cut/{name}_dem.tif", "w", **out_meta) as dst: dst.write(out_image)

逻辑说明:mask函数的第二个参数是几何要素列表,这里把row["geometry"]包成单元素列表传入,是因为 rasterio 要求遮罩输入类型为 GeoJSON-like 的几何对象集合。crop=True表示裁剪后按面元素边界切掉边缘,而不是保留矩形。元数据更新是必须步骤,因为输出矩阵的尺寸和仿射变换变了,不更新写入的 tif 会读到错位的地理范围。处理时如果 DEM 文件较大,建议逐个区写完后检查输出文件的像元尺寸是否一致,防止因重采样参数缺失导致像元大小漂移。

5. 避坑盘点:五个高频翻车现场与处理方案

5.1 乱码:属性表中文全部显示为问号

现象:在 ArcGIS 中打开 shp 属性表,中文字段显示为「??????」,或者一堆无法辨认的符号。原因:shp 的 .dbf 属性表没有附带编码声明,或者 .cpg 文件丢失,软件默认按本地编码解析,中文就阵亡了。解决:用 GeoPandas 读取时指定encoding="gbk"或encoding="utf-8"试一遍;如果在 QGIS 中打开,在图层属性里重新选择编码并保存。读取 shp 后如果中文字段名乱码,先把 GeoPandas 读出来的 DataFrame 用to_file重新导出一次,ArcGIS 能正常显示。

5.2 坐标偏移:POI 点整体落入海里或错位到邻近城市

现象:叠加行政区划图层后,POI 点位整体偏移导航到长江口或者杭州湾方向,和区划边界对不上。原因:POI 图层用的是 WGS84 经纬度坐标,但行政区划图层用的是 GCJ02(火星坐标),两者不加转换直接叠加必然偏移。解决:先确认两个图层的crs属性,如果坐标系不一致,统一转换到同一标准。这里就是所谓的玄学场景,坐标系问题不解决,所有空间分析结果都是不准的。一般我会在项目开始时先做一次坐标普查,花 10 分钟可以省下后面几天的排查时间。

5.3 字段缺失:Excel 里能看到的列,shp 里没有

现象:同一份 POI 数据,Excel 格式里有电话、营业时间这些字段,shp 格式的属性表里却只有名称和类别。原因:shp 格式的 dBase 字段数量限制在某些场景下会触发字段被截断,或者原始数据制作时 shp 本身就只保留核心字段。解决:如果分析中必须用完整字段,以 Excel 格式为主表,shp 的POI_ID和外键做表关联。这个步骤在 Pandas 里用merge操作完成,多走一步但数据完整性有保障。

5.4 Excel 打开 POI 表导致软件卡死

现象:Excel 格式的 POI 文件动辄几十万行,双击打开后直接无响应,或者往下滚动就卡成白屏。原因:Excel 本身的行数限制和渲染性能瓶颈,POI 这种大规模数据量放到数据库或专业数据分析工具里更合适。解决:数据量大的时候用 Python 的 Pandas 处理,或者用 SQLite 把 Excel 导入成数据库表。开源 EXCEL 数据库软件或数据仓库方案在这个场景下比 Excel 本身高效得多。如果只是给别人快速浏览,先用 Pandas 抽样生成一个预览版 Excel。

5.5 解压后文件大小不对或图层加载提示缺少数据

现象:解压后 POI_shp 目录下只有一个.shp主文件,.dbf和.shx丢失,或者加载图层时弹出「数据源不可读」。原因:7z 解压时磁盘空间不足、中断,或者原始压缩包在传输过程中缺失文件成员。解决:重新解压前先检查磁盘剩余空间,解压完成后核对目录文件数。shp 加载失败时先检查同目录是否同时存在.shp/.shx/.dbf/.prj四件套。从那次之后,我每次解压完都先跑一遍文件清单统计,确认记录数和压缩包说明一致再进入下一步。

6. 进阶玩法:把 Excel 格式 POI 批量入库与 shp 转 3DTiles

当 POI 数据量大到影响读写效率,或者需要支撑 Web 端地图应用的动态查询时,把 Excel 格式 POI 导入数据库是必经的一段路。这里我用 SQLite 做演示,逻辑通用,换成 PostgreSQL 或者 MySQL 只是改连接字符串的事。操作分两步:第一步在 Python 里把 Excel 读取为 DataFrame,第二步通过to_sql写入数据库表。注意写入前把字段类型设置到位,尤其是经纬度需要明确指定为 REAL 类型,否则后续做范围查询时会被存储成文本导致排序错乱:

import pandas as pd import sqlite3 poi_df = pd.read_excel("POI_excel/shanghai_poi_2020.xlsx", dtype={"POI_ID": str}) conn = sqlite3.connect("shanghai_poi.db") poi_df.to_sql("poi", conn, if_exists="replace", index=False) cur = conn.cursor() cur.execute("CREATE INDEX idx_poi_lat ON poi(纬度)") cur.execute("CREATE INDEX idx_poi_lng ON poi(经度)") conn.commit() # 验证写入结果 count = cur.execute("SELECT COUNT(*) FROM poi").fetchone()[0] print(f"写入记录数: {count}")

逻辑说明:to_sql的if_exists="replace"表示如果表已存在则替换重建,适合反复实验的场景,但生产环境请改成append或者先DROP TABLE再写入。索引的创建位置放在写入之后,数据量大的时候建索引会稍慢,但对后续按经纬度范围查询的性能提升显著。验证记录数是常规习惯,号码对不上说明 Excel 读取阶段就有问题,不需要再往下查。

如果目标是做三维可视化,shp 转 3DTiles 是大场景渲染的首选方案。把 POI 提取成点要素并赋予高度属性后,用工具转换即可。这套流程里,这份资源的价值从二维叠加分析延伸到了三维场景展示,DEM 正好可以作为高度场数据一起参与构建。我自己的习惯是先把 POI 按类别拆分几组测试转换,避免一次大规模转换失败还排查不了。从那以后我每次拿到新数据都会强制走一遍这个顺序:核查坐标系、核查编码、确认字段完整度、再做正经分析。这套血泪经验换来的工作流,希望帮到你。

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

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

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

立即咨询