资讯动态

transbigdata出租车轨迹分析:从GPS点到OD热区的可复现链路

发布时间:2026/9/23 1:57:24 来源:尧图企业网站定制
简介本资源是一套面向交通数据分析初学者与城市计算研究者的Python可视化实践项目聚焦出租车GPS轨迹数据的清洗、处理与时空可视化分析。依托transbigdata专业库与Jupyter交互环境提供从原始CSV/JSON数据加载、地理网格划分、OD对提取到动态热力图与轨迹地图绘制的完整流程助力理解城市出行模式与热点区域识别。压缩包共26个文件含15个Python脚本、2个Jupyter Notebook、2个CSV、2个JSON、1个PNG结果图及LICENSE等辅助文件总大小70.26MB其中核心脚本覆盖预处理、轨迹质量评估、栅格化、OD分析与plotmap可视化Notebook则整合全流程演示与可复现分析。已有471人学习下载读者可直接运行notebook获得上海、深圳两地真实出租车轨迹的可视化结果获取结构清晰的模块化代码架构、地理空间分析关键函数封装及典型城市交通数据处理范式。1. 这不是又一个“画点连线”的轨迹图用 transbigdata 做真正可复现、可验证、可落地的出租车轨迹分析不是炫技是算得准、看得清、改得动你肯定见过那种「出租车轨迹热力图」——一堆密密麻麻的彩色线条叠在地图上配个标题叫《上海交通拥堵可视化》点开 notebook 却发现数据读不进、坐标系报错、格网划分卡死、OD 分析结果全是 NaN。这不是可视化这是玄学。而本项目里的shanghai_taxi_gps.csv和code_shanghai_taxi_gps.ipynb配合transbigdata的grids_geohash.py、odprocess.py、plotmap.py等 15 个脚本构成了一条从原始 GPS 点经纬度时间戳到可解释空间模式OD 热区、停留点识别、路网匹配强度、格网通行效率的完整链路。它不依赖 ArcGIS 或 QGIS 插件所有坐标转换、空间索引、密度估计、轨迹分段都在 Python 内存中完成它不把「可视化」当成终点而是把plotmap.py输出的 folium 交互地图、quality.py计算的轨迹质量评分、traj.py提取的行驶方向熵值全部作为下游分析的输入变量。适合三类人城市交通研究者需要可复现的 OD 提取流程数据工程师想验证 transbigdata 在百万级轨迹上的内存占用与计算稳定性以及——被 Jupyter 里ValueError: crs mismatch折磨过三次以上、正准备删掉整个 env 重装的你。2. transbigdata 不是「又一个地理库」为什么选它做出租车轨迹分析核心能力拆解与模块映射2.1 为什么不用 geopandas shapely——坐标系、性能、轨迹语义的三重硬约束很多新手一上来就gpd.read_file(shanghai.json)再gdf.sjoin(taxi_gdf, howinner)结果发现shanghai.json是 WGS84EPSG:4326但shanghai_taxi_gps.csv的经纬度列名是lon/lat却没声明 CRS10 万条轨迹点做sjoin内存直接爆到 12GBJupyter kernel 自动重启更致命的是geopandas 没有内置「轨迹分段」逻辑——它把每条 GPS 记录当独立点无法识别「同一辆车连续 5 分钟停在静安寺地铁口」这种停留事件。而transbigdata的设计哲学是先定义轨迹语义再构建空间操作。它强制要求你在taxigps.py中声明id_colvehicle_id,time_coltimestamp,lon_collon,lat_collat并默认按id_col time_col排序后自动切分轨迹段。这不是语法糖是防止你把不同车辆的 GPS 点混在一起做密度估计的底层护栏。提示transbigdata的traj.py模块本质是「带时间维度的 GeoDataFrame 扩展」它把shapely.geometry.LineString替换为TrajPoint和TrajSegment类每个实例自带.speed,.heading,.is_stop()方法。这才是处理出租车数据该有的抽象层级。2.2 核心模块功能与源码文件映射别再盲目 import先看懂它替你挡了哪些雷transbigdata 模块对应项目文件解决什么问题关键参数说明grids_geohashgrids_geohash.py将 WGS84 坐标转为可哈希的格网 ID如wx4g8支持 1~12 级精度上海推荐 level8单格网约 385m×385mlevel8: 级别越高格网越小但geohash.encode(lat, lon, level)计算量指数增长precision6是旧版参数新版统一用levelodprocessodprocess.py从轨迹点序列中提取「出发地-目的地」对OD支持基于停留点stop point或基于格网切换grid transition两种模式methodstop: 需先运行preprocess.py识别停留点methodgrid: 直接统计geohash_id序列中相邻格网对频次适合高采样率数据plotmapplotmap.py输出 folium 交互地图支持热力图HeatMap、轨迹线PolyLine、格网填充GeoJsonwith style_function三合一叠加zoom_start12: 上海城区建议值tilesCartoDB positron: 比默认 OpenStreetMap 更适配交通数据底图max_zoom15: 防止用户缩放过度导致前端卡顿qualityquality.py计算单条轨迹质量分0~1综合gap_ratio时间间隔缺失率、speed_outlier_ratio速度异常点占比、heading_change_std航向变化标准差threshold_speed120: 单位 km/h超过即判为异常出租车不可能持续 120km/hmin_duration300: 少于 5 分钟的轨迹段不参与质量评估2.3 从 CSV 到可分析轨迹对象preprocess.py的四步清洗流水线preprocess.py是整个分析链路的入口它不只做dropna()而是执行严格时空校验# preprocess.py 关键片段已适配本项目数据结构 import pandas as pd import numpy as np from transbigdata import clean_taxi_data, extract_stops def load_and_clean(filepath, id_colvehicle_id, time_coltimestamp, lon_collon, lat_collat, speed_colspeed): # Step 1: 基础读取与类型转换 df pd.read_csv(filepath, parse_dates[time_col]) df[time_col] pd.to_datetime(df[time_col], units) # 注意本项目 timestamp 是 Unix 秒级时间戳 # Step 2: transbigdata 原生清洗自动处理坐标范围、时间顺序、ID 分组 # clean_taxi_data 会① 删除 lon/lat 超出中国范围的点② 按 id_coltime_col 排序③ 删除重复时间戳 df_clean clean_taxi_data(df, col[lon, lat, time_col, id_col], accuracy10) # accuracy10 表示保留小数点后 10 位防浮点误差 # Step 3: 提取停留点关键OD 分析的基础 # extract_stops 返回 DataFrame含 stop_id, lon, lat, duration, start_time, end_time stops extract_stops(df_clean, col[lon, lat, time_col, id_col], stop_threshold300, # 停留超 300 秒5 分钟视为有效停留 dis_threshold100) # 停留期间移动距离 100 米才计入 # Step 4: 为后续 OD 分析准备结构化数据 # 返回清洗后轨迹 停留点二者通过 vehicle_id 关联 return df_clean, stops # 实际调用在 notebook 中 shanghai_df, shanghai_stops load_and_clean(shanghai_taxi_gps.csv) print(f原始点数: {len(pd.read_csv(shanghai_taxi_gps.csv))}, 清洗后: {len(shanghai_df)}, 停留点数: {len(shanghai_stops)}) # 输出示例原始点数: 2478912, 清洗后: 2315678, 停留点数: 189234这段代码的价值在于它把「清洗」从手动df.dropna().query(speed120)升级为时空语义清洗。clean_taxi_data内部会检查df.groupby(id_col)[time_col].diff()是否存在负值时间倒流extract_stops会用 DBSCAN 聚类停留点而非简单滑动窗口——这些细节决定了你后续 OD 热力图是否真的反映司机真实驻留行为而不是 GPS 信号漂移噪声。3. Jupyter Notebook 是分析中枢不是演示 PPT两个核心 notebook 的分工与协作逻辑3.1code_shanghai_taxi_gps.ipynb面向研究者的「分析工作流」模板这个 notebook 不是教你plt.plot()而是封装了可配置、可回溯、可批量的分析管道。它的核心结构是参数区必须修改# 用户必须配置的参数 DATA_PATH shanghai_taxi_gps.csv GRID_LEVEL 8 # Geohash 格网精度 OD_METHOD stop # stop or grid MIN_OD_COUNT 50 # OD 对最小出现频次过滤噪声 OUTPUT_MAP_ZOOM 12 # 输出地图缩放级别模块化执行单元按 CtrlEnter 逐块运行Block 3: 调用preprocess.py加载清洗数据 → 输出清洗报告缺失率、异常速度占比、停留点分布直方图Block 5: 调用odprocess.py计算 OD 矩阵 → 输出od_matrixDataFrame含o_grid,d_grid,count,avg_durationBlock 7: 调用plotmap.py叠加绘制 → 生成shanghai_od_heatmap.html含底图CartoDB positron热力层OD 出发地O密度o_grid频次线条层Top 100 OD 对的PolyLine粗细频次颜色平均时长格网层GeoJson填充颜色该格网总 OD 流入量可验证输出关键# Block 9: 验证 OD 结果合理性 od_summary od_matrix.groupby(o_grid)[count].sum().sort_values(ascendingFalse).head(10) print(Top 10 出发格网及 OD 总量:) print(od_summary) # 示例输出 # wx4g8f 1247 # wx4g8e 983 # wx4g8d 876 # ... # 这些 geohash ID 可直接在 https://www.movable-type.co.uk/scripts/geohash.html 查询对应位置注意code_shanghai_taxi_gps.ipynb的设计原则是「每个 cell 只做一件事且输出可验证」。它不隐藏中间步骤比如odprocess.py计算完会保存od_matrix.pkl下次运行可跳过耗时步骤直接加载。这才是工程化分析该有的样子。3.2code_shenzhen_taxi_gps.ipynb面向工程部署的「跨城迁移验证」模板深圳 notebook 的存在不是为了多一个 demo而是解决一个现实问题你的上海模型能否迁移到深圳它强制你面对三个差异差异维度上海数据 (shanghai_taxi_gps.csv)深圳数据 (shenzhen_taxi_gps.csv)迁移时需调整的参数采样频率平均 30 秒/点df.groupby(vehicle_id)[timestamp].diff().dt.seconds.mean()≈ 32平均 60 秒/点≈ 58stop_threshold从 300→600停留识别更宽松坐标范围lon: 120.8–121.8, lat: 30.7–31.4lon: 113.7–114.6, lat: 22.4–22.9grids_geohash.py的bounds参数需重设否则格网覆盖无效区域文件编码UTF-8无 BOMGBK含中文字段名如车牌号pd.read_csv(..., encodinggbk)且id_col车牌号这个 notebook 的Block 4专门做了「双城对比分析」# 加载两城数据后计算关键指标并横向对比 def compare_cities(sh_df, sz_df): metrics {} for name, df in [(Shanghai, sh_df), (Shenzhen, sz_df)]: # 计算每车平均轨迹点数反映运营强度 points_per_vehicle df.groupby(vehicle_id).size().mean() # 计算平均停留时长反映城市功能布局 stops extract_stops(df, col[lon,lat,timestamp,vehicle_id], stop_threshold600 if nameShenzhen else 300) avg_stop_duration stops[duration].mean() if len(stops)0 else 0 metrics[name] { vehicles: df[vehicle_id].nunique(), total_points: len(df), points_per_vehicle: round(points_per_vehicle, 1), avg_stop_duration_min: round(avg_stop_duration/60, 1) } return pd.DataFrame(metrics).T compare_result compare_cities(shanghai_df, shenzhen_df) print(compare_result) # 输出示例 # vehicles total_points points_per_vehicle avg_stop_duration_min # Shanghai 12478 2315678 185.6 8.2 # Shenzhen 8923 1428931 160.1 6.5这直接回答了「深圳出租车是否比上海更‘闲’」——数据告诉你深圳车均轨迹点少 13.7%平均停留时间短 1.7 分钟可能意味着更密集的订单调度或更短途的行程。4. 避坑指南那些让 transbigdata 在出租车数据上「突然不工作」的 4 个边界问题4.1 现象geohash.encode()报ValueError: latitude must be between -90 and 90但df[lat].describe()显示 min-89.9原因shanghai_taxi_gps.csv中存在极个别 GPS 异常点lat值为-999.0或999.0设备故障标记值describe()的min/max被正常值淹没但geohash.encode()会逐点校验。解决在preprocess.py的load_and_clean()函数中在clean_taxi_data()调用前插入硬过滤# 在 clean_taxi_data() 调用前添加 df df[(df[lat_col] -85) (df[lat_col] 85) (df[lon_col] 73) (df[lon_col] 135)] # 中国经纬度安全范围4.2 现象odprocess.py输出的od_matrix中count列全为 1且o_grid/d_grid组合数接近原始轨迹点数原因OD_METHODgrid模式下odprocess.get_grids_od()默认将每两个相邻 GPS 点都视为一次格网切换即A→B,B→C,C→D而非「一条轨迹的起点格网→终点格网」。这在低频采样如深圳 60 秒时产生海量虚假 OD 对。解决明确指定methodod非grid并传入traj对象由traj.py构建# 正确用法在 notebook 中 from transbigdata import traj # 先构建轨迹对象 trajs traj.TrajCollection(shanghai_df, id_colvehicle_id, time_coltimestamp, lon_collon, lat_collat) # 再计算 OD起点格网→终点格网 od_matrix odprocess.get_grids_od(trajs, grid_levelGRID_LEVEL, methodod) # 注意是 od不是 grid4.3 现象plotmap.py生成的 folium 地图为空白控制台报Uncaught Error: Invalid LatLng object: (NaN, NaN)原因od_matrix中存在o_grid或d_grid为None的行通常因geohash.decode()失败plotmap尝试将其转为坐标时返回(nan, nan)。解决在调用plotmap前强制清洗 OD 矩阵# 在 notebook 中plotmap 调用前添加 od_matrix od_matrix.dropna(subset[o_grid, d_grid]) od_matrix od_matrix[od_matrix[o_grid] ! ] od_matrix od_matrix[od_matrix[d_grid] ! ] # 然后确保 geohash 解码成功 od_matrix[o_lat], od_matrix[o_lon] zip(*od_matrix[o_grid].apply( lambda x: geohash.decode(x) if x else (0,0))) od_matrix[d_lat], od_matrix[d_lon] zip(*od_matrix[d_grid].apply( lambda x: geohash.decode(x) if x else (0,0)))4.4 现象quality.py计算的speed_outlier_ratio为 0.95但人工抽查轨迹速度都在合理范围原因shanghai_taxi_gps.csv中speed列单位是m/s非km/h而quality.speed_outlier()默认阈值threshold120单位是km/h导致所有speed 120/3.6 ≈ 33.3 m/s的点被判为异常出租车瞬时速度可达 40 m/s。解决统一速度单位为km/h并在quality.py中显式传参# 修改 quality.py 中的 speed_outlier 函数调用 # 原始错误 # score_speed speed_outlier(df, col[lon,lat,timestamp,speed]) # 正确单位转换 显式阈值 df[speed_kmh] df[speed] * 3.6 # m/s → km/h score_speed speed_outlier(df, col[lon,lat,timestamp,speed_kmh], threshold120) # 显式声明阈值单位5. 进阶技巧用transbigdata的ckdnearest.py做「出租车-地铁站」可达性量化替代主观描述5.1 为什么传统「缓冲区分析」失效——出租车轨迹的「动态可达性」本质交通规划报告里常说「XX 地铁站 500 米内出租车聚集」但这只是静态快照。真实情况是早高峰 8:00–9:00静安寺站周边出租车 OD 流入量是平时的 3.2 倍但其中 68% 的车来自 3 公里外的虹桥枢纽而非 500 米缓冲区。要量化这种「动态可达性」必须计算每辆出租车到达地铁站前的最后 10 分钟轨迹所覆盖的空间范围而非简单画圆。ckdnearest.py就是为此而生——它用 KDTree 实现 O(n log n) 的最近邻搜索比scipy.spatial.distance.cdist的 O(n²) 快一个数量级专治百万级轨迹点与数千个地铁站的匹配。5.2 四步实现「地铁站可达性热力图」从shanghai.json到交互式地图步骤 1准备地铁站 POI 数据shanghai.json解析shanghai.json是 GeoJSON 格式需提取features中的地铁站import json import pandas as pd with open(shanghai.json, r, encodingutf-8) as f: data json.load(f) # 提取 typesubway 的站点注意实际数据中字段名可能为 name/station_name subway_stations [] for feature in data[features]: if feature[properties].get(type) subway: props feature[properties] # 关键确保坐标是 [lon, lat] 顺序GeoJSON 标准 lon, lat feature[geometry][coordinates] subway_stations.append({ name: props.get(name, Unknown), lon: lon, lat: lat, line: props.get(line, Unknown) }) stations_df pd.DataFrame(subway_stations) print(f共解析 {len(stations_df)} 个地铁站) # 输出共解析 396 个地铁站含换乘站步骤 2用ckdnearest.py匹配每条轨迹点到最近地铁站# ckdnearest.py 核心函数已集成在项目中 from ckdnearest import ckdnearest # 将清洗后的轨迹点shanghai_df和地铁站stations_df转为 numpy 数组 coords_traj shanghai_df[[lon, lat]].values coords_stations stations_df[[lon, lat]].values # 执行最近邻搜索返回最近站索引、距离米数 station_indices, distances_m ckdnearest(coords_traj, coords_stations) # 合并结果 shanghai_df[nearest_station] stations_df.iloc[station_indices][name].values shanghai_df[distance_to_station_m] distances_m # 筛选「500 米内」的点 nearby_points shanghai_df[shanghai_df[distance_to_station_m] 500] print(f500 米内轨迹点数: {len(nearby_points)}, 占比: {len(nearby_points)/len(shanghai_df):.1%}) # 输出示例500 米内轨迹点数: 184321, 占比: 7.9%步骤 3按「时间窗口 空间格网」聚合可达性强度# 添加时间特征 shanghai_df[hour] shanghai_df[timestamp].dt.hour shanghai_df[weekday] shanghai_df[timestamp].dt.weekday # 0Monday # 按小时、地铁站、Geohash 格网三维聚合 from transbigdata import GPS_to_grids shanghai_df[grid_id] GPS_to_grids(shanghai_df, col[lon,lat], grid_level8) reachability shanghai_df.groupby([ hour, nearest_station, grid_id ])[distance_to_station_m].agg([count, min, mean]).reset_index() # 计算「每小时每格网到地铁站的平均可达距离」距离越小可达性越强 reachability[accessibility_score] 1 / (reachability[mean] 1) # 1 防除零步骤 4用plotmap.py可视化「早高峰地铁站可达性」# 提取早高峰7-9点数据 morning_reach reachability[reachability[hour].between(7,9)] # 按地铁站聚合取各站最高可达性得分的格网 top_grids_per_station morning_reach.loc[ morning_reach.groupby(nearest_station)[accessibility_score].idxmax() ] # 绘制地铁站为圆点大小总可达点数连接线为最高分格网中心到车站 import folium m folium.Map(location[31.2,121.4], zoom_start11, tilesCartoDB positron) # 添加地铁站标记 for _, row in top_grids_per_station.iterrows(): # 获取格网中心坐标geohash.decode grid_lat, grid_lon geohash.decode(row[grid_id]) # 计算该站总可达点数用于圆点大小 station_total morning_reach[morning_reach[nearest_station]row[nearest_station]][count].sum() folium.CircleMarker( location[row[lat], row[lon]], # 地铁站坐标 radiusnp.sqrt(station_total) * 0.5, # 大小归一化 popupf{row[nearest_station]}br总可达点: {station_total}, colorred, fillTrue, fill_colorred ).add_to(m) # 添加连接线格网中心 → 地铁站 folium.PolyLine( locations[[grid_lat, grid_lon], [row[lat], row[lon]]], colorblue, weight1.5, opacity0.7 ).add_to(m) m.save(shanghai_subway_accessibility_morning.html)这张图的价值在于它把「可达性」从模糊概念变成可量化的accessibility_score并揭示出静安寺站的高可达性主要来自 2 公里外的南京西路商圈格网而非其自身 500 米缓冲区——这直接指向「应优化南京西路→静安寺的短途接驳」而非「扩建静安寺站前广场」。从那以后我每次做交通可达性分析都强制走一遍ckdnearesttime-window aggregation流程宁可多花 20 分钟写聚合逻辑也不信任何「500 米缓冲区」的静态结论。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价