
简介这份2020—2022年成都市矢量数据资源面向GIS从业者、城市规划研究者、交通与水文分析人员及地图应用开发者用于支撑空间分析、地图制图与决策支持等场景。数据覆盖路网含高速、国道、省道、县道、乡道及铁路地铁轻轨、河流水系、建筑轮廓、省市县乡镇行政区划以及十万级POI兴趣点可满足交通规划、洪水预警、日照分析、商业选址与公共服务布局等需求。资源包共229个文件以shp、shx、dbf、prj等Shapefile核心格式为主辅以sbx、sbn空间索引、cpg编码说明及xlsx统计表、xml元数据另含DEM高程tif影像整体约95.58MB目录按主题分模块组织便于按需检索。目前已有1673人学习下载适合需要快速获取成都多要素底图、开展空间建模或搭建智慧城市原型的中高级用户参考使用。1. 2020-2022年成都市矢量数据一份被低估的城市时空底图如果你手头正好有一批 2020 到 2022 年的成都市矢量数据却不确定它能拿来做什么、怎么清洗、怎么和业务数据挂接那这篇笔记就是写给你的。我最早接触这类数据是为了给一个门店选址模型补路网和行政区划底图结果发现真正卡住进度的不是模型本身而是矢量数据的坐标系、字段结构和年份口径对不上。2020-2022 这三年恰好横跨了城市管理口径调整和大量基础设施更新同一份“成都市矢量数据”在不同来源里可能指行政区划面、路网线、POI 点甚至建筑轮廓面粒度差异极大。它适合做城市分析、空间可视化、选址评估、轨迹匹配的从业者也适合想用真实城市数据练手空间数据库和 GIS 管线的人。下面我按“先搞清楚是什么再动手跑通最后避开坑”的顺序把这条数据链路讲透。2. 先分清成都市矢量数据到底有哪几类几何对象2.1 面、线、点三类矢量在成都场景下的典型用途拿到一份标注“成都市矢量数据”的文件第一件事不是打开看而是先问它是什么几何类型。面数据通常是行政区划区、县、街道或用地地块适合做统计聚合和分级设色线数据多是路网、河流、轨道适合做可达性分析和路径计算点数据常见 POI、监测站、事件点位适合做密度分析和热力图。2020-2022 这三年里成都的行政区划有过调整部分街道边界和名称发生了变化所以面数据必须确认年份版本否则你把 2022 年的业务数据挂到 2020 年的区划面上会出现点位落在边界外或归属错误的玄学现象。我一般会先用一个轻量脚本把几何类型和字段列出来而不是直接扔进 QGIS 里肉眼翻。下面这段 Python 用geopandas读取并打印基本信息适合在数据入库前做一次体检。import geopandas as gpd # 读取矢量文件fiona 会自动识别 shp/geojson/gpkg gdf gpd.read_file(chengdu_2020_2022.gpkg, layerboundary) # 几何类型分布面/线/点一眼看清 print(gdf.geom_type.value_counts()) # 字段名和类型判断有没有年份、区县编码等关键列 print(gdf.dtypes) # 坐标系EPSG:4326 是经纬度投影坐标系才适合算距离 print(gdf.crs)逻辑说明geom_type能直接暴露混合几何问题有些数据把面和点塞在同一个图层里后续空间连接会报错。dtypes帮你看有没有year、adcode这类字段没有的话就得从文件名或外部表补。crs是最容易被忽略的参数经纬度坐标系下直接算面积会得到平方度数值毫无意义。参数上如果crs是EPSG:4326做距离和面积前必须转到投影坐标系成都常用EPSG:32648UTM 48N或EPSG:4547CGCS2000 3 度带。2.2 2020-2022 三年数据的时间口径怎么对齐这三年数据最大的坑不是几何而是时间口径。行政区划面可能只有年初版本路网可能是年底快照POI 可能是滚动采集。如果你要做年度对比必须先把每条记录的时间字段统一成“数据代表的时间点”而不是“文件生成时间”。常见做法是面数据按年份建多个图层或用year字段区分线数据如果变化不大可以用最新版反推但要在文档里注明假设点数据尽量保留原始采集时间聚合时按年切片。我一般会建一张时间对照表把每个图层的年份、来源、更新频率写清楚避免后面自己都忘了哪份是哪年的。表格比口头记忆可靠得多。图层名几何类型年份口径建议用途boundary面2020/2021/2022 各一版统计聚合、分级设色road线2022 年底快照可达性、路径分析poi点2020-2022 滚动密度、热力、选址building面2021 年建筑轮廓、容积率估算这张表不是给别人看的是给自己后面写 SQL 和脚本时当字典用。没有它三个月后你大概率会对着final_v2_real.shp发呆。2.3 用 GeoPandas 做一次最小可用的空间连接搞清楚几何和时间之后下一步是验证它能不能和你的业务数据挂上。最小可用的验证方式是拿一批带经纬度的业务点做一次空间连接看有多少点能落到成都的区划面内。这一步能同时暴露坐标系不一致、边界偏移、字段编码错误三个问题。import pandas as pd import geopandas as gpd from shapely.geometry import Point # 业务点示例假设有 id、lng、lat 三列 df pd.read_csv(biz_points.csv) geometry [Point(xy) for xy in zip(df[lng], df[lat])] pts gpd.GeoDataFrame(df, geometrygeometry, crsEPSG:4326) # 区划面同样必须是 EPSG:4326 boundary gpd.read_file(chengdu_2020_2022.gpkg, layerboundary_2022) # 空间连接point 在 polygon 内则匹配 joined gpd.sjoin(pts, boundary, howleft, predicatewithin) # 统计匹配率低于 95% 就要查原因 match_rate joined[index_right].notna().mean() print(f匹配率: {match_rate:.2%}) # 没匹配上的点单独导出方便排查 unmatched joined[joined[index_right].isna()] unmatched.to_file(unmatched_points.geojson, driverGeoJSON)逻辑说明sjoin的predicatewithin表示点完全落在面内如果边界有缝隙或点落在边界线上可能匹配失败可以改用intersects。howleft保留所有业务点没匹配上的index_right为空。匹配率低于 95% 时优先查坐标系是否一致再查边界是否只覆盖了主城区而业务点在外围。参数上crs必须两边一致不一致时用to_crs转换不要直接改crs属性那是自欺欺人。3. 把矢量数据落进 PostGIS 并跑通空间查询3.1 用 ogr2ogr 批量导入 Shapefile 到 PostGIS文件放在磁盘上只能看放进空间数据库才能做复杂查询和并发访问。我一般用ogr2ogr做批量导入它比写 Python 循环稳字段类型推断也靠谱。前提是 PostGIS 扩展已经启用目标表可以提前建好也可以让ogr2ogr自动建。# 先确保数据库启用 PostGIS psql -d citydb -c CREATE EXTENSION IF NOT EXISTS postgis; # 导入区划面-nln 指定表名-lco 控制建表参数 ogr2ogr -f PostgreSQL PG:dbnamecitydb userpostgres passwordsecret hostlocalhost \ chengdu_boundary_2022.shp \ -nln boundary_2022 \ -lco GEOMETRY_NAMEgeom \ -lco FIDgid \ -nlt MULTIPOLYGON \ -t_srs EPSG:4326 # 导入路网线字段多时加 -select 只保留需要的列 ogr2ogr -f PostgreSQL PG:dbnamecitydb userpostgres passwordsecret hostlocalhost \ chengdu_road_2022.shp \ -nln road_2022 \ -lco GEOMETRY_NAMEgeom \ -nlt MULTILINESTRING \ -t_srs EPSG:4326逻辑说明-nln是目标表名建议带年份后缀方便多版本共存。-lco GEOMETRY_NAMEgeom统一几何列名后面写 SQL 不用猜。-nlt强制几何类型避免混合类型导致建表失败。-t_srs统一转成经纬度如果后续要做距离计算可以在查询时用ST_Transform转到投影坐标系而不是导入时就转保留原始精度。导入后记得建空间索引否则查询会慢到让你怀疑人生。-- 建空间索引GIST 是 PostGIS 的标准选择 CREATE INDEX idx_boundary_2022_geom ON boundary_2022 USING GIST (geom); CREATE INDEX idx_road_2022_geom ON road_2022 USING GIST (geom); -- 验证数据量和坐标系 SELECT COUNT(*) FROM boundary_2022; SELECT ST_SRID(geom) FROM boundary_2022 LIMIT 1;3.2 用 SQL 做区县统计和路网长度汇总数据入库后最有价值的操作是用 SQL 做聚合。比如统计每个区县的业务点数量或者算每个区县的路网总长度。下面这段 SQL 先做点面连接统计再算路网长度注意长度计算要转到投影坐标系。-- 每个区县的业务点数量假设 biz_points 表已存在 SELECT b.name, COUNT(p.id) AS point_count FROM boundary_2022 b LEFT JOIN biz_points p ON ST_Within(p.geom, b.geom) GROUP BY b.name ORDER BY point_count DESC; -- 每个区县的路网总长度单位米 SELECT b.name, SUM(ST_Length(ST_Transform(r.geom, 4547))) AS road_length_m FROM boundary_2022 b JOIN road_2022 r ON ST_Intersects(r.geom, b.geom) GROUP BY b.name ORDER BY road_length_m DESC;逻辑说明ST_Within用于点面归属ST_Intersects用于线面相交两者都会走空间索引。ST_Transform(r.geom, 4547)把经纬度转到 CGCS2000 3 度带ST_Length得到的才是米。如果直接对EPSG:4326的线算长度单位是度数值没有业务意义。参数上4547是成都所在投影带不同城市要换拿不准就用ST_Transform到EPSG:3857做近似但正式统计还是用本地投影带更准。3.3 用 QGIS 做一次可视化验收SQL 跑完不代表数据没问题还得肉眼验收。我一般会把 PostGIS 图层直接连进 QGIS做一张分级设色图看边界有没有重叠、空洞、飞地路网有没有断头。QGIS 连 PostGIS 的步骤是图层 → 添加图层 → 添加 PostGIS 图层 → 新建连接填主机、端口、数据库、用户名密码然后选表。验收时重点看三件事区划面是否无缝无重叠路网是否连通点位是否落在合理位置。如果发现某个区县的点全落在边界外大概率是坐标系或边界版本问题回到 2.3 的匹配率排查。4. 避坑2020-2022 成都矢量数据最常见的五个翻车点4.1 坐标系混用导致面积和距离全错现象用EPSG:4326直接算面积得到 0.00x 平方度或者算路网长度得到个位数。原因经纬度坐标系的单位是度不是米。解决所有距离和面积计算前用ST_Transform转到投影坐标系成都用EPSG:4547或EPSG:32648。在 Python 里用gdf.to_crs(4547)在 SQL 里用ST_Transform(geom, 4547)。这个坑我踩过不止一次血泪经验是只要数值小得离谱先查坐标系。4.2 行政区划年份和业务数据年份错位现象2022 年的门店点位挂到 2020 年的区划面上部分点位归属到已经不存在的街道。原因2020-2022 年间部分街道边界和名称有调整。解决业务数据按年份匹配对应年份的区划面如果只有一版区划就在文档里注明假设并对边界附近的点位做人工复核。常见做法是保留多版区划表用year字段区分查询时带上年份条件。4.3 字段编码和中文乱码现象导入 PostGIS 后区县名称变成乱码或者 QGIS 里属性表显示问号。原因Shapefile 的.dbf默认编码可能是 GBK而 PostGIS 和 QGIS 默认 UTF-8。解决导入时用-lco ENCODINGUTF-8或者在 Python 里用gpd.read_file(..., encodinggbk)读入后再写出为 UTF-8。如果已经导入乱码最稳的办法是重新导入不要试图在数据库里改编码容易越改越乱。4.4 几何无效导致空间查询报错现象ST_Within或ST_Intersects报TopologyException或者部分记录被跳过。原因面数据有自相交、重复节点、零面积多边形。解决导入前用ST_MakeValid修复或者在 Python 里用gdf.geometry gdf.geometry.buffer(0)做一次缓冲修复。修复后要重新检查面积和边界避免修复引入新的偏移。我一般会在入库前跑一遍ST_IsValid把无效记录单独导出处理。4.5 空间索引缺失导致查询超时现象小数据量查询正常数据量上来后 SQL 跑几分钟不出结果。原因没有建 GIST 空间索引每次查询都全表扫描。解决对每个几何列建CREATE INDEX ... USING GIST (geom)。建索引后第一次查询可能稍慢之后会明显加快。注意索引不是越多越好只给常用的几何列建字段列按业务查询需要建 B-tree 索引。5. 用分区统计和栅格化把矢量数据变成可分析指标5.1 用 GeoPandas 做分区统计并导出 CSV矢量数据的进阶用法之一是把几何对象转成可统计的指标。比如按区县统计 POI 密度、路网密度、建筑覆盖率。下面这段代码用geopandas做分区统计输出 CSV 供后续分析。import geopandas as gpd import pandas as pd # 读取区划面和 POI 点 boundary gpd.read_file(chengdu_2020_2022.gpkg, layerboundary_2022) poi gpd.read_file(chengdu_2020_2022.gpkg, layerpoi) # 统一投影坐标系方便算面积 boundary_proj boundary.to_crs(4547) poi_proj poi.to_crs(4547) # 空间连接统计每个区县的 POI 数量 joined gpd.sjoin(poi_proj, boundary_proj, howinner, predicatewithin) count joined.groupby(name).size().reset_index(namepoi_count) # 算区县面积单位平方公里 boundary_proj[area_km2] boundary_proj.geometry.area / 1e6 count count.merge(boundary_proj[[name, area_km2]], onname) # POI 密度 count[poi_density] count[poi_count] / count[area_km2] # 导出 CSV count.to_csv(poi_density_by_district.csv, indexFalse, encodingutf-8-sig) print(count.head())逻辑说明to_crs(4547)保证面积计算单位是米除以1e6得到平方公里。sjoin用within做点面归属groupby统计数量。encodingutf-8-sig让 Excel 打开 CSV 不乱码。参数上area_km2的计算依赖投影坐标系如果换城市要换对应 EPSG。密度指标适合做横向对比但要注意区县面积差异大时密度高不代表总量大。5.2 把矢量栅格化做热力图和覆盖分析有些分析需要栅格格式比如热力图、覆盖范围、成本面。用rasterio和geocube可以把矢量转成栅格。下面是一个最小示例把 POI 点转成 100 米分辨率的计数栅格。import geopandas as gpd from geocube.api.core import make_geocube # 读取 POI 点并投影 poi gpd.read_file(chengdu_2020_2022.gpkg, layerpoi).to_crs(4547) # 加一列常量 1用于计数 poi[count] 1 # 栅格化分辨率 100 米 cube make_geocube( vector_datapoi, measurements[count], resolution(100, 100), fill0 ) # 导出 GeoTIFF cube[count].rio.to_raster(poi_count_100m.tif)逻辑说明make_geocube把矢量按指定分辨率转成栅格measurements指定要栅格化的字段fill0让空白区域为 0。分辨率 100 米适合城市尺度太细会导致文件巨大太粗会丢失细节。导出后可以用 QGIS 做热力图渲染或者用rasterio做进一步统计。注意栅格化前必须投影否则分辨率单位是度结果不可用。5.3 用分区统计结果反查数据质量分区统计不只是出指标还能反查数据质量。比如某个区县的 POI 密度异常低可能是边界不匹配或点位缺失某个区县的路网密度异常高可能是重复线或边界重叠。我一般会把统计结果按密度排序取最高和最低各三个区县回到 QGIS 里肉眼检查。这一步能发现很多自动化脚本查不出的问题比如边界飞地、重复采集、字段错位。数据质量不是靠一次检查保证的是靠统计加抽查反复验证出来的。6. 把三年数据做成可复用的版本化管线6.1 用年份后缀和元数据表管理多版本2020-2022 三年数据如果每年一版最怕的是版本混乱。我的习惯是所有表名带年份后缀比如boundary_2020、boundary_2021、boundary_2022同时建一张metadata表记录每个图层的年份、来源、几何类型、坐标系、字段说明、更新时间。这样即使换人接手也能快速搞清楚每份数据的来龙去脉。CREATE TABLE metadata ( layer_name TEXT, data_year INT, geom_type TEXT, srid INT, source TEXT, updated_at TIMESTAMP DEFAULT NOW() ); INSERT INTO metadata (layer_name, data_year, geom_type, srid, source) VALUES (boundary_2020, 2020, MULTIPOLYGON, 4326, internal), (boundary_2021, 2021, MULTIPOLYGON, 4326, internal), (boundary_2022, 2022, MULTIPOLYGON, 4326, internal), (road_2022, 2022, MULTILINESTRING, 4326, internal), (poi, 2022, POINT, 4326, internal);逻辑说明metadata表不参与空间查询但它是数据治理的核心。srid记录坐标系source记录来源updated_at自动记录更新时间。查询时可以先查元数据再决定用哪张表。这个习惯看起来多余但当你半年后回来找数据时它会救你一命。6.2 用 Makefile 或 Shell 脚本固化导入流程手动导入容易漏步骤我一般会把导入流程写成 Shell 脚本固定坐标系、表名、索引。下面是一个简化版实际使用时按年份循环。#!/bin/bash set -e DBdbnamecitydb userpostgres passwordsecret hostlocalhost YEAR2022 # 导入区划面 ogr2ogr -f PostgreSQL PG:$DB \ chengdu_boundary_${YEAR}.shp \ -nln boundary_${YEAR} \ -lco GEOMETRY_NAMEgeom \ -nlt MULTIPOLYGON \ -t_srs EPSG:4326 \ -lco ENCODINGUTF-8 # 建空间索引 psql -d citydb -c CREATE INDEX IF NOT EXISTS idx_boundary_${YEAR}_geom ON boundary_${YEAR} USING GIST (geom); # 写入元数据 psql -d citydb -c INSERT INTO metadata (layer_name, data_year, geom_type, srid, source) VALUES (boundary_${YEAR}, ${YEAR}, MULTIPOLYGON, 4326, internal); echo boundary_${YEAR} 导入完成逻辑说明set -e让脚本遇错即停避免半成品数据入库。IF NOT EXISTS让索引可重复执行。ENCODINGUTF-8解决中文乱码。这个脚本可以按年份循环也可以扩展成导入路网和 POI。参数上数据库连接信息不要硬编码在脚本里实际使用时用环境变量或配置文件避免密码泄露。6.3 用视图统一多版本查询口径如果业务查询需要跨年份可以建视图把多版表合并加一个data_year字段区分。这样上层查询不用关心表名只查视图即可。CREATE OR REPLACE VIEW boundary_all AS SELECT *, 2020 AS data_year FROM boundary_2020 UNION ALL SELECT *, 2021 AS data_year FROM boundary_2021 UNION ALL SELECT *, 2022 AS data_year FROM boundary_2022; -- 查询时按年份过滤 SELECT name, data_year, ST_Area(ST_Transform(geom, 4547)) / 1e6 AS area_km2 FROM boundary_all WHERE data_year 2022;逻辑说明视图把多版表合并data_year字段让查询可以按年份切片。ST_Area前先ST_Transform到投影坐标系得到平方公里。视图不存数据查询时实时计算适合数据量不大的场景。如果数据量大可以考虑物化视图但要注意刷新时机。这个做法让三年数据既能统一查询又能保留版本差异是我目前最常用的管理方式。6.4 一个具体技巧用边界差异检测区划调整2020-2022 年区划调整可以用几何差异检测出来。下面这段 SQL 找出 2022 年相对 2020 年新增或减少的面。-- 2022 年有但 2020 年没有的面新增 SELECT ST_AsText(ST_Difference(a.geom, b.geom)) AS diff_geom FROM boundary_2022 a JOIN boundary_2020 b ON ST_Intersects(a.geom, b.geom) WHERE NOT ST_Equals(a.geom, b.geom); -- 面积差异超过阈值的区县 SELECT a.name, ST_Area(ST_Transform(a.geom, 4547)) / 1e6 AS area_2022, ST_Area(ST_Transform(b.geom, 4547)) / 1e6 AS area_2020, (ST_Area(ST_Transform(a.geom, 4547)) - ST_Area(ST_Transform(b.geom, 4547))) / 1e6 AS diff_km2 FROM boundary_2022 a JOIN boundary_2020 b ON a.name b.name WHERE ABS(ST_Area(ST_Transform(a.geom, 4547)) - ST_Area(ST_Transform(b.geom, 4547))) 1000000;逻辑说明ST_Difference得到几何差异ST_Equals判断是否完全相同。面积差异查询用name关联阈值设 1 平方公里超过的区县重点核查。这个技巧能帮你快速定位区划调整范围避免把调整误判为数据错误。参数上阈值按城市规模调整成都这种体量用 1 平方公里比较合适。我自己的习惯是每次拿到新一年数据先跑一遍差异检测把变化记录到元数据表里。这样三年下来你手里不只是一堆文件而是一条可追溯、可复现、可解释的数据管线。希望帮到你。本文还有配套的精品资源点击获取