ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

伦敦GIS实战:SHP数据验证、修复与空间关联分析

伦敦GIS实战:SHP数据验证、修复与空间关联分析 简介本资源是一套面向GIS初学者与城市数据分析研究者的伦敦交通与犯罪空间数据集适用于地理信息教学、城市安全分析及空间统计实践。数据以标准Shapefile格式组织共26个文件包含3个.shp点位几何、4个.dbf属性表含犯罪类型、时间、交通流量等字段、3个.prj坐标系定义及配套.shx、.cpg、.qpj等另有CSV事件汇总表、QGIS工程文件.qgz和LSOA区域社会经济指标Excel表完整支撑从数据加载、可视化到叠加分析的全流程。包体14.3MB结构规范适配ArcGIS、QGIS等主流平台。已有318人学习下载读者可直接开展犯罪热点识别、交通设施与案发位置空间关联分析、IMD贫困指数与犯罪率相关性探索等典型任务并复用QGIS工程配置与元数据说明快速上手。1. 用伦敦交通与犯罪数据练手 GIS 分析SHP 文件不是“点开就能用”的地图图层而是带坐标、属性、拓扑关系的结构化空间数据包很多人第一次拿到“伦敦交通和犯罪 GIS 数据SHP 格式”时下意识打开 ArcGIS 或 QGIS发现图层加载失败、属性表空、坐标错位甚至弹出“Invalid geometry”警告——这不是数据坏了而是 SHP 本质被误解了。SHPShapefile从来不是一个独立文件而是一组至少含.shp几何、.shx索引、.dbf属性三个同名文件的协作体它不自带坐标系定义也不保证几何完整性更不隐含业务逻辑。真正能跑通分析的是把这组文件当作“原始空间数据原料”在明确坐标系如 WGS84 / EPSG:4326 或 OSGB36 / EPSG:27700、校验几何有效性、关联犯罪类型字段与交通网络拓扑后才能支撑诸如“地铁站 500 米内高发盗窃案热区识别”或“公交线路覆盖盲区与暴力犯罪率的空间相关性建模”这类真实任务。本文面向已安装 QGIS 或 Python 环境的 GIS 实践者不讲概念复读只拆解从下载原始 SHP 到产出可验证空间结论的完整链路怎么确认它真能用、怎么修常见破损、怎么用代码批量做缓冲区与空间连接、怎么把犯罪点按交通网络分段聚合统计——每一步都附可粘贴执行的命令与参数说明。2. 验证与修复伦敦 SHP 数据用ogrinfo和shapely检查坐标系、几何完整性与字段一致性拿到一组名为london_crime_2023.shp和london_transport_network.shp的文件第一件事不是画图而是确认它们是否构成可用的数据基础。SHP 常见失效场景有三类坐标系缺失导致叠加错位、多部件几何断裂引发空间运算崩溃、DBF 字段类型错配使 SQL 查询报错。必须用命令行工具快速诊断而非依赖 GUI 软件的自动猜测。2.1 用 ogrinfo 查看元数据与坐标系声明GDAL 自带的ogrinfo是最轻量级的 SHP 元数据探针。在终端中执行ogrinfo -so london_crime_2023.shp关键输出需逐项核对Geometry: Point确认几何类型与业务匹配犯罪事件应为点道路应为线Feature Count: 12487记录总数是否符合预期可与官网文档比对Extent: (-0.512345, 51.287654) - (0.287654, 51.678901)坐标范围是否落在伦敦地理边界内经度约 -0.5~0.3纬度约 51.2~51.7Layer SRS WKT:后是否为空若为空说明无内置坐标系必须手动指定常见于英国数据应设为EPSG:27700即 OSGB36 / British National GridOGRFeature(london_crime_2023): 1下列出的字段名如crime_type,month,lsoa_code是否与元数据文档一致特别注意字段名含空格或特殊字符如Crime Type会导致后续 Python 读取失败需提前重命名。提示若Layer SRS WKT为空绝不能直接在 QGIS 中“设置当前图层 CRS”那只是临时覆盖导出新文件时仍会丢失。正确做法是用ogr2ogr强制赋值并生成新文件ogr2ogr -s_srs EPSG:27700 -t_srs EPSG:27700 london_crime_fixed.shp london_crime_2023.shp2.2 用 Python shapely 批量检测几何有效性即使ogrinfo显示正常SHP 中仍可能藏有自相交线、零长度线段、重复节点等“静默破损”这些会在缓冲区分析或空间连接时突然报错。用以下脚本遍历所有要素并标记问题from osgeo import ogr import shapely.wkt from shapely.geometry import shape, Point, LineString import pandas as pd def validate_shp_geometry(shp_path): ds ogr.Open(shp_path) layer ds.GetLayer() invalid_records [] for i, feature in enumerate(layer): geom feature.GetGeometryRef() if geom is None: invalid_records.append((i, NULL geometry)) continue # 转为 shapely 对象以利用 robust validation try: wkt geom.ExportToWkt() s_geom shape(shapely.wkt.loads(wkt)) if not s_geom.is_valid: reason shapely.validation.explain_validity(s_geom) invalid_records.append((i, fInvalid: {reason})) except Exception as e: invalid_records.append((i, fParse error: {str(e)})) return invalid_records # 执行检查 invalid_list validate_shp_geometry(london_transport_network.shp) print(fFound {len(invalid_list)} invalid geometries) # 输出示例[(124, Invalid: Ring Self-intersection), (892, Invalid: Too few points in geometry)]该脚本返回的(feature_id, reason)元组可直接定位到 DBF 中第 124 行记录用 QGIS 的“Select Features by Expression”输入$id 124即可高亮查看。对“Ring Self-intersection”类问题QGIS 的Vector Geometry Tools Fix Geometries可自动修复对“Too few points”需人工核查原始数据源是否遗漏了关键节点。2.3 字段类型校验与 DBF 编码适配英国 SHP 的.dbf文件常以 Latin-1 编码保存但中文系统默认用 UTF-8 解析会乱码。用dbfread库安全读取并检查字段类型from dbfread import DBF table DBF(london_crime_2023.dbf, encodinglatin-1) print(Field types:, {f.name: f.type for f in table.fields}) # 输出示例{crime_type: C, value: N, date: D} # CText, NNumber, DDate —— 若 crime_type 字段为 N 类型则说明数值被误存为数字需转回字符串若发现crime_type字段类型为N数字证明原始数据将文本编码为数字 ID如 1Theft, 2Violence此时必须加载对应编码字典通常在数据文档codebook.pdf中否则直接统计会得到无意义的数字分布。这是伦敦犯罪数据集如 Metropolitan Police 发布的 CSV的典型设计SHP 版本若未附带字典需从官网同步下载crime_type_lookup.csv并用 Pandas 合并。3. 构建交通-犯罪空间关联模型用 GeoPandas 批量生成缓冲区、执行空间连接与分段统计验证无误的 SHP 数据核心价值在于揭示“空间关系”。伦敦案例中典型需求是识别哪些公交线路段周边 200 米内犯罪率显著高于均值这需要将点状犯罪数据与线状交通网络建立距离关联并按线路 ID 分组聚合。纯 GUI 操作易出错且不可复现GeoPandas 提供全 Python 流水线。3.1 加载数据并统一坐标系import geopandas as gpd import pandas as pd # 强制指定 CRS避免依赖 SHP 自带常为空 crimes gpd.read_file(london_crime_fixed.shp, crsEPSG:27700) transport gpd.read_file(london_transport_network_fixed.shp, crsEPSG:27700) # 检查 CRS 是否一致 print(Crimes CRS:, crimes.crs) print(Transport CRS:, transport.crs) # 输出应均为 EPSG:27700。若不一致用 transport transport.to_crs(crimes.crs) # 确保几何列名为 geometryGeoPandas 默认要求 crimes crimes.set_geometry(geometry) transport transport.set_geometry(geometry)注意gpd.read_file()的crs参数是强制覆盖不是“建议使用”。当原始 SHP 无 CRS 时此参数必不可少当原始 CRS 错误时此参数可纠正。忽略此步后续所有距离计算如缓冲区半径将单位错乱米变度。3.2 为交通线路生成 200 米缓冲区并空间连接犯罪点缓冲区分析是空间关联的基础。关键参数是distance200单位为 CRS 的线性单位EPSG:27700 下即米以及cap_style和join_style控制端点与拐角形状# 为每条公交线路生成 200 米缓冲区保留原始线路 ID transport_buffered transport.copy() transport_buffered[geometry] transport.geometry.buffer( distance200, cap_style2, # 2flat平头避免圆形端点导致缓冲区重叠过度 join_style2, # 2mitre尖角保持锐利拐角适合道路网络 resolution16 # 点数16 是平衡精度与性能的常用值 ) # 执行空间连接找出每个缓冲区内包含的犯罪点 # howinner 只保留有匹配的记录predicatewithin 确保点在面内 joined gpd.sjoin(crimes, transport_buffered, howinner, predicatewithin) # 查看结果每行是一个“犯罪点-线路缓冲区”对 print(joined[[crime_type, route_id, geometry]].head()) # 输出示例crime_typeTheft, route_id123A, geometryPOINT (532100 178900)gpd.sjoin()返回的是原始crimes行与transport_buffered行的笛卡尔积匹配因此同一犯罪点若落在多条线路缓冲区内会产生多行记录。这符合现实如交叉路口但后续聚合需去重或加权。3.3 按线路 ID 分组统计犯罪类型频次与密度空间连接后核心指标是“每公里线路的犯罪发生数”需结合线路长度计算密度# 计算每条线路的原始长度米 transport[length_m] transport.length # 统计每条线路缓冲区内的犯罪总数及各类型分布 crime_stats joined.groupby([route_id]).agg({ crime_type: lambda x: x.value_counts().to_dict(), # 各类型计数字典 geometry: count # 总犯罪点数 }).rename(columns{geometry: total_crimes}) # 合并回线路数据计算密度犯罪数/公里 result transport[[route_id, name, length_m]].merge( crime_stats, onroute_id, howleft ).fillna({total_crimes: 0}) result[crimes_per_km] (result[total_crimes] / (result[length_m] / 1000)).round(2) result result.sort_values(crimes_per_km, ascendingFalse) # 输出前 10 高风险线路 print(result[[route_id, name, length_m, total_crimes, crimes_per_km]].head(10))该结果表可直接导入 QGIS 作为属性表用crimes_per_km字段做分级色彩渲染直观显示高风险走廊。若需进一步分析可提取crime_type字典列用pd.json_normalize()展开为宽表计算 Theft 占比、Violence 增长率等衍生指标。4. 处理 SHP 常见工程陷阱坐标系混淆、属性丢失、几何拓扑断裂的实操修复方案SHP 在跨工具流转中极易失真。以下三个高频陷阱每个都附带一行命令或一段代码即可解决无需重装软件或求助专家。4.1 “QGIS 中复制图层后粘贴失败”本质是剪贴板不支持空间数据协议当在 QGIS 中选中要素 → CtrlC → 新建图层 → CtrlV 失败错误提示“Paste failed: No features to paste”并非软件故障而是 QGIS 剪贴板仅支持内存图层Memory Layer的临时交换不支持跨 CRS 或跨数据源粘贴。正确做法是导出为中间格式右键图层 →Export Save Features As...→ 格式选GeoPackage推荐或ESRI Shapefile→ 勾选Add saved layer to map或用 Python 批量导出避免 GUI 操作# 将当前选中要素导出为新 SHP layer iface.activeLayer() selected_ids [f.id() for f in layer.selectedFeatures()] selected_layer layer.materialize(QgsFeatureRequest().setFilterFids(selected_ids)) QgsVectorFileWriter.writeAsVectorFormatV3( selected_layer, /path/to/exported_selection.shp, QgsProject.instance().transformContext(), QgsVectorFileWriter.SaveVectorOptions() )4.2 “SHP 转 TXT 后经纬度错乱”根源在于未进行坐标系转换shp转txt需求常用于导入 Excel 或数据库。直接用ogr2ogr -f CSV output.csv input.shp会输出平面坐标如 EPSG:27700 下的东距/北距非经纬度。要获得 WGS84 经纬度必须先转换 CRS# 步骤1转为 WGS84 点坐标 ogr2ogr -s_srs EPSG:27700 -t_srs EPSG:4326 -f CSV london_crime_wgs84.csv london_crime_fixed.shp # 步骤2CSV 默认含 X,Y 列重命名为 lon,lat可选 sed -i 1s/X/lon/; 1s/Y/lat/ london_crime_wgs84.csv提示若原始 SHP 是点数据-f CSV直接输出经纬度若是线或面需先ogr2ogr -nlt POINT -dsco GEOMETRYAS_XY强制转点再导出。4.3 “SHP 文件下载后打不开提示损坏”90% 是文件传输不完整或解压错误从政府开放数据平台如 data.london.gov.uk下载的 SHP 常为 ZIP 包但解压工具可能忽略隐藏文件或损坏.shx。验证方法# 检查三个必需文件是否存在且大小非零 ls -lh london_crime_*.sh* # 正确输出应类似 # -rw-r--r-- 1 user user 12M Jun 10 london_crime_2023.dbf # -rw-r--r-- 1 user user 6.2K Jun 10 london_crime_2023.shp # -rw-r--r-- 1 user user 24K Jun 10 london_crime_2023.shx # 若 .shx 缺失用 ogrinfo 重建索引仅当 .shp 完整时有效 ogrinfo -al london_crime_2023.shp | head -20 # 先确认能读取 ogr2ogr -f ESRI Shapefile london_crime_rebuilt.shp london_crime_2023.shp # 此命令会生成新的 .shx 和 .dbf覆盖原文件若.shp文件本身损坏ogrinfo报错Unable to open ...唯一可靠方案是重新下载。切勿尝试用十六进制编辑器手动修复SHP 二进制结构复杂微小错误即导致全文件失效。5. 进阶技巧用shapely.ops.split将犯罪点按交通网络分段实现“路段级犯罪热力图”前述缓冲区分析给出的是“线路级”风险但实际管理需细化到“某一路段如 Oxford St 从 Marble Arch 到 Bond St 段”。这要求将线状交通网络按交叉点切割为独立路段再统计每段上的犯罪点数。shapely.ops.split是实现此目标的最小依赖方案。5.1 提取交通网络的所有交叉点并切割线路from shapely.ops import split, linemerge from shapely.geometry import MultiPoint, LineString, MultiLineString # 步骤1获取所有线路的节点起点、终点、交叉点 all_points [] for line in transport.geometry: if isinstance(line, LineString): all_points.extend([line.coords[0], line.coords[-1]]) # 起终点 # 添加与其他线路的交点 for other_line in transport.geometry: if other_line is not line and isinstance(other_line, LineString): inter line.intersection(other_line) if not inter.is_empty and inter.geom_type Point: all_points.append(inter.coords[0]) elif inter.geom_type MultiPoint: all_points.extend([p.coords[0] for p in inter.geoms]) # 步骤2用交叉点切割每条线路 segmented_lines [] for line in transport.geometry: if not isinstance(line, LineString): continue # 将 LineString 与所有交叉点集合分割 cut_points MultiPoint(all_points) segments split(line, cut_points) if isinstance(segments, MultiLineString): segmented_lines.extend(list(segments.geoms)) else: segmented_lines.append(segments) # 步骤3构建新 GeoDataFrame每行是一个路段 segmented_gdf gpd.GeoDataFrame( {segment_id: range(len(segmented_lines)), geometry: segmented_lines}, crstransport.crs )5.2 将犯罪点分配至最近路段并统计# 计算每个犯罪点到所有路段的距离分配至最近路段 crime_assignments [] for _, crime in crimes.iterrows(): point crime.geometry min_dist float(inf) assigned_seg_id None for _, seg in segmented_gdf.iterrows(): dist point.distance(seg.geometry) if dist min_dist: min_dist dist assigned_seg_id seg.segment_id crime_assignments.append(assigned_seg_id) # 添加分配结果并统计 crimes[segment_id] crime_assignments segment_stats crimes.groupby(segment_id).size().reset_index(namecrime_count) # 合并回路段数据 final_segments segmented_gdf.merge(segment_stats, onsegment_id, howleft).fillna({crime_count: 0}) # 导出为 SHP 供 QGIS 可视化 final_segments.to_file(london_transport_segments_with_crime.shp, driverESRI Shapefile)最终生成的london_transport_segments_with_crime.shp其属性表含crime_count字段可在 QGIS 中用“Graduated”渲染器按crime_count设置颜色深浅生成真正的路段级犯罪热力图。此方法不依赖 NetworkX 或 pgRouting纯几何运算适用于中小规模数据10 万路段。若需处理超大规模网络可改用rtree构建空间索引加速最近邻搜索但上述代码已覆盖伦敦市区全部交通路段的分析需求。本文还有配套的精品资源点击获取
返回列表