ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用transbigdata设计出租车GPS轨迹分析源码:从清洗到热力图

2026/9/11 19:36:41 拓冰建站 浏览量
用transbigdata设计出租车GPS轨迹分析源码:从清洗到热力图 简介基于Python和transbigdata库的出租车轨迹数据可视化分析设计源码面向具备一定Python基础的数据分析学习者与城市规划、交通管理研究者专注于出租车GPS轨迹数据的清洗、处理与可视化可对上海、深圳等城市数万条出租车记录进行联动探索提取出行热点、路径分布和区域起讫点特征。压缩包约70.26MB共26个文件包括15个Python脚本、2个Jupyter Notebook交互式分析文档、2个CSV与2个JSON轨迹数据文件并附有说明文档、开源许可、Git忽略与子模块配置目录结构清晰适合二次开发。目前已有470人学习适合需要快速掌握transbigdata库并完成城市级移动轨迹分析的研究者。源码覆盖数据预处理、地理网格聚合、轨迹重构、起讫点计算、地图绘制等核心模块支持按时间窗口和空间范围进行筛选统计Notebook环境可实时调整参数并观察结果变化帮助用户建立从原始轨迹点到可视化图表的完整分析链路。借助该设计读者既能深入理解开源大数据可视化方法也能为通勤特征挖掘、交通规划与运营调度提供实际的数据支撑。1. 从一段乱飞的 GPS 记录里找回一辆车的完整故事打开任何一份出租车订单数据最先看到的往往不是规律而是噪音经纬度在街区里乱跳、速度偶尔飙到 180km/h、时间戳前后颠倒——这些原始的轨迹数据如果直接画到地图上得到的不叫可视化叫涂鸦。transbigdata 是国内开发者开源的时空数据处理工具箱它把数据清洗、坐标纠偏、轨迹切分、OD 提取、网格聚合这一整套流程封装成了可以直接调用的函数配合 Python 的数据栈能在一小时内把几百万条出租车 GPS 记录变成街道热力图和出行起讫点图。标题里的“设计源码”四个字正是这篇文章的主线不只要画出图还要把代码组织成可复用、可改参数、可接新数据源的小型分析框架。如果你的日常工作里要碰网约车、外卖配送、货运车辆这类轨迹数据这篇文章会给你一套直接能抄的落地方案。2. 环境准备与数据表的“坐标系陷阱”2.1 安装 transbigdata 与地理数据三板斧transbigdata 的安装很简单但真正容易出问题的是它依赖的地理计算环境。常见做法是创建一个干净的 Python 3.9 以上的虚拟环境然后一次性装齐四件套pip install transbigdata pandas numpy matplotlib pip install geopandas shapely如果你打算把结果叠加到底图上建议同时装contextily或osmnx来拉取在线瓦片。装完后跑一句import transbigdata as tbd验证版本号正常输出即可。提示: transbigdata 会用到shapely的矢量计算和geopandas的 GeoDataFrame 结构这两者如果版本冲突容易在调用tbd.traj_to_od()这类函数时出现“GEOSException: TopologyException”报错。遇到时先执行pip install -U shapely geopandas试试。2.2 出租车轨迹表的标准字段结构traj 数据在 transbigdata 中没有强制 schema但你要在调用函数时把字段名校准。最常用的是taxi_gps_process函数它要求数据至少包含车辆 ID、时间、经度、纬度、载客状态import pandas as pd import transbigdata as tbd # 原始数据字段名可能是中文或缩写先统一重命名 taxi pd.read_csv(taxi_gps_20240101.csv, header0) taxi taxi.rename(columns{ CAR_ID: VehicleNum, GPS_TIME: Stime, LNG: Lng, LAT: Lat, CAR_STATE: OpenStatus }) # 统一时间格式这是轨迹切分的重要前提 taxi[Stime] pd.to_datetime(taxi[Stime], format%Y%m%d%H%M%S) # 应用 transbigdata 的出租车专用处理流程 taxi tbd.taxi_gps_process(taxi, col[VehicleNum, Stime, Lng, Lat, OpenStatus]) print(taxi[[VehicleNum, Stime, Lng, Lat, OpenStatus]].head())taxi_gps_process内部会做两件事把时间列转成标准时间索引把 OpenStatus 从字符串状态变成 0/1 的载客标记。注意这个函数不会替你过滤明显异常点比如停在山顶或海中央的坐标这些要交给下一章的清洗逻辑。2.3 国测局坐标与 WGS-84 的纠偏问题这是几乎所有做国内轨迹可视化的人都会踩的坑。出租车 GPS 终端输出的原始坐标多数是 WGS-84 经纬度但高德地图、腾讯地图的底图用的是 GCJ-02国测局加密坐标百度地图则用 BD-09。直接把 WGS-84 坐标画在 GCJ-02 底图上所有轨迹会整体偏移几百米跨街区的高速路轨迹看起来会“骑”在楼顶上。transbigdata 的taxi_gps_process并不包含坐标转换需要你自己判断数据来源。判断办法很简单取一个你熟悉的交叉路口坐标用一个在线坐标拾取器和你手上的点做对比或者直接先画一次散点图看偏移方向。国内常见做法是用tbd.towgs84做北斗/WGS 坐标到 GCJ-02 的修正但更通用的是自己封装一个坐标转换函数def wgs84_to_gcj02(lng, lat): # 此函数实现 WGS-84 转 GCJ-02代码为常见标准实现 import math a 6378245.0 ee 0.006693421622965943 if lat 0.1 or lat 55.0 or lng 72.0 or lng 137.0: return lng, lat # 计算偏移量 dLat _transform_lat(lng - 105.0, lat - 35.0) dLng _transform_lng(lng - 105.0, lat - 35.0) radLat lat / 180.0 * math.pi magic math.sin(radLat) magic 1 - ee * magic * magic sqrtMagic math.sqrt(magic) dLat (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * math.pi) dLng (dLng * 180.0) / (a / sqrtMagic * math.cos(radLat) * math.pi) return lng dLng, lat dLat # 对整个 DataFrame 批量转换 taxi[Lng], taxi[Lat] zip(*taxi.apply(lambda r: wgs84_to_gcj02(r[Lng], r[Lat]), axis1))坐标转换函数是那种跑一遍就忘、再跑又要搜一次的东西建议直接放进项目里的geo_utils.py模块别和主流程代码混在一起。转换完成后把结果存成 parquet 或 feather 格式因为 CSV 的文本解析会在后续反复读取时拖慢速度。3. 轨迹数据清洗与停留点/OD 提取的实现3.1 漂移点过滤与轨迹切分的参数设定拿到原始轨迹后第一件事永远是清洗。transbigdata 提供了一套可组合的清洗函数但参数要根据你的采样频率和数据采集方式来定不能用默认值硬跑。import transbigdata as tbd import geopandas as gpd import numpy as np # 清洗速度异常瞬时速度超过 120km/h 的点基本是 GPS 漂移 taxi taxi[(taxi[速度] 0) (taxi[速度] 120)] # 清洗时段内的重复点和静止点 taxi tbd.clean_traj(taxi, col[VehicleNum, Stime, Lng, Lat], speed_limit120, timegap10) # 车辆长时间未移动说明可能熄火或停在停车场需要切分为独立轨迹段 taxi tbd.cut_traj(taxi, col[VehicleNum, Stime], methodtime, timegap180)cut_traj的timegap180表示如果同一辆车的相邻两条记录时间间隔超过 180 秒就把轨迹切分为两段。这个值不是随便定的城市出租车在等红灯时通常间隔 30 到 60 秒但停车吃饭或者交接班可能会超过 5 分钟所以 180 秒是区分“行驶中短暂停留”和“行程结束”的常用阈值。如果你的数据是网约车的订单数据从下单到结束已经有明确的订单 ID那可以跳过cut_traj直接按订单 ID 分组。3.2 轨迹到 OD 的聚合从点线到起讫点矩阵清洗完成后就进入了“可视化分析”的核心环节——OD 矩阵提取。OD 指的是 One-Destination 的缩写每个行程的起点和终点构成了城市交通分析的基本单元。transbigdata 中做这件事的函数是traj_to_od它会根据车辆 ID 和切分好的轨迹段取每段的第一条和最后一条记录作为起终点# 提取 OD 矩阵 od_data tbd.traj_to_od(taxi, col[VehicleNum, Stime, Lng, Lat], odcol[VehicleNum, Stime, Lng, Lat]) # 查看 OD 结果 print(od_data.head())OD 提取后你会得到一张新表每一行代表一个出行的起点和终点坐标。此时数据量已经从“每个 GPS 点”压缩到“每次出行”可以做很多有意思的分析。比如统计某个商圈作为起点的订单占比、计算早晚高峰的平均出行距离、分析机场和火车站订单的峰值时段。对于“可视化”这个主题下一步通常是把 OD 点画成散点图或者用网格聚合成 OD 流量矩阵然后用桑基图或流图来表达。3.3 用网格聚合把散点变成热力图底稿单个 OD 点画在地图上会互相遮挡看不出空间密度差异。我一般会先把研究区域切分成公里网格用 transbigdata 的GPS_to_grid和grid_to_od把点聚合成“网格到网格”的统计量# 设定网格边界与大小 bounds [121.2, 31.0, 121.6, 31.4] # 经度范围、纬度范围 grid_size 0.005 # 约 500 米 # 将轨迹点映射到网格 taxi_grid tbd.GPS_to_grid(taxi, col[Lng, Lat], grid_sizegrid_size, boundsbounds) # 聚合每个网格的轨迹点数量 grid_count taxi_grid.groupby([Lng, Lat]).size().reset_index(namecount) # 网格 ID 转中心点坐标用于绘图 grid_center tbd.grid_to_center(taxi_grid[[Lng, Lat]], grid_sizegrid_size, boundsbounds)GPS 点和网格的映射是完全确定性的grid_size的选取直接决定图上“热点”的粒度。500 米网格适合看全市的供需分布100 米网格适合看某个街道的细部特征。网格聚合的一个隐藏价值是数据脱敏如果项目需要把分析结果交付给第三方网格计数比原始 GPS 坐标更安全且网格的边界天然对空间噪声有平滑作用。4. 可视化渲染把清洗后的轨迹画成地图4.1 轨迹线绘制的三种方案静态、交互、瓦片叠加Python 生态里做轨迹可视化有三条主流路径用 matplotlib 画静态图用 folium 生成可交互 HTML用 pydeck 做大数据的 GPU 渲染。三条路径我基本都会用取决于交付对象论文插图用 matplotlib业务汇报用 folium几百万点量级的探索用 pydeck。transbigdata 本身带了一个简单的可视化函数tbd.plot_map但它只是给你一个带坐标轴的底画布真正的轨迹绘制还是要自己操纵 matplotlib:import matplotlib.pyplot as plt # 在 transbigdata 设置的地图坐标系上画图 fig, ax plt.subplots(figsize(12, 12)) tbd.plot_map(ax, boundsbounds, osm_styledark) # 加载暗色底图样式 # 按车辆分组逐条绘制轨迹线 for vehicle, group in taxi.groupby(VehicleNum): # 过滤轨迹点数少于 10 的短轨迹避免线段杂乱 if len(group) 10: ax.plot(group[Lng], group[Lat], linewidth0.8, alpha0.6) plt.savefig(taxi_trajectory_map.png, dpi200, bbox_inchestight)这里要注意的是osm_styledark需要联网拉取 OpenStreetMap 的瓦片样式。在内网环境或没有外网时改成osm_stylelight也一样会失败解决办法是直接去掉osm_style参数用浅灰色背景代替。轨迹线画得好不好看关键在于linewidth和alpha的搭配线路越密集就越需要降低透明度否则整个图就是一团黑。4.2 用散点密度图展示上下客热点区域载客状态OpenStatus是出租车轨迹数据里最值钱的字段。它标记了乘客上车和落车的时刻把这两个时刻的坐标筛选出来就能得到一张城市打车热力图# 0 表示空车1 表示载客根据你的数据源定义可能不同 boarding taxi[taxi[OpenStatus] 0] # 如果 1 是上车状态则相反 alighting taxi[taxi[OpenStatus] 1] # 用 hexbin 画六边形密度图比 scatter 更适合大数据量 fig, ax plt.subplots(figsize(12, 10)) tbd.plot_map(ax, boundsbounds) hb ax.hexbin(boarding[Lng], boarding[Lat], gridsize150, binslog, cmapYlOrRd, alpha0.8) plt.colorbar(hb, axax, labelBoarding Count (log scale)) plt.title(Taxi Boarding Hotspots) plt.savefig(taxi_boarding_hotspot.png, dpi200, bbox_inchestight)hexbin是 matplotlib 里被很多人忽略的宝藏函数。它和hist2d的区别是六边形网格能更自然地贴合圆形空间分布且没有方块边缘的视觉生硬感。gridsize150决定网格的密度数字越大网格越细但过大的值会在点稀疏的区域产生大量空网格。binslog是为了解决一个问题市中心的人民广场可能有几万个上车点而郊区的某个地铁站只有几十个线性 colorbar 会把郊区全部压成同一个颜色log 变换能让热力图的层次感显现出来。4.3 基于 OD 矩阵绘制流量流图轨迹线图反映的是路径OD 流图反映的是连接强度。把 OD 数据按起讫点分组统计每条 OD 对的订单量然后用弧线或直线连接能得到一张类似“城市通勤欲望线”的图。这里用 ECharts 的百度地图风格配合 Pyecharts 可以快速生成 HTML 可视化页面from pyecharts import options as opts from pyecharts.charts import Geo # 按起点-终点聚合 OD 计数 od_count od_data.groupby([OLng, OLat, DLng, DLat]).size().reset_index(namecount) top_od od_count.sort_values(count, ascendingFalse).head(1000) geo Geo().add_schema(maptypechina) for _, row in top_od.iterrows(): geo.add(flow, [(row[OLng], row[OLat], row[count])], type_effectScatter, symbol_size3) geo.set_global_opts(visualmap_optsopts.VisualMapOpts()) geo.render(od_flow_map.html)这里只贴一个骨架是因为 OD 流图的样式高度依赖底图素材和业务偏好但通用逻辑是一致的把 OD 点对按条数排序取 Top N 来画。全量画几百几万条 OD 连线只会得到一张无法阅读的线球图。Top 1000 是平衡信息量和可读性的常用选择。5. 源码目录怎么组织才算是“设计”→源码5.1 一个能复用的出租车轨迹分析项目结构标题里带的“设计源码”落到工程上就是目录怎么切、模块之间怎么解耦。我见过太多人把清洗、画图、分析写在一个 Jupyter Notebook 里跑通一次后就再也跑不通第二次——因为数据路径变了、字段名变了、时间范围变了。规范的做法是拆成四个模块taxi-analysis/ ├── data/ │ ├── raw/ # 原始 GPS 数据只读不写 │ ├── interim/ # 清洗后的中间结果如 parquet │ └── processed/ # OD 矩阵和网格聚合结果 ├── scripts/ │ ├── clean_taxi.py │ ├── extract_od.py │ └── visualize.py ├── geo_utils.py # 坐标转换、网格参数配置 └── config.yaml # 统一存放所有可变参数config.yaml是这套源码设计里最容易忽略但价值最高的文件。把网格大小、速度上限、切分时间间隔、研究区域边界、数据列名映射全部放在配置里可以让一个完全不了解这套代码的数据分析师只改参数不碰代码就完成一轮新数据的分析data: raw_path: data/raw/taxi_20240101.csv interim_path: data/interim/taxi_cleaned.parquet od_path: data/processed/od_matrix.csv cleaning: speed_limit: 120 timegap_cut: 180 coordinate_system: wgs84_to_gcj02 visualization: bounds: [121.2, 31.0, 121.6, 31.4] grid_size: 0.005 hexbin_gridsize: 150 top_n_od: 1000配置文件的价值在换数据源的时刻体现得最明显。换了城市改bounds换了采样频率改timegap_cut换了字段命名改列名映射其他代码一行不动。5.2 用函数封装可视化链路而不是直接写脚本再来谈“源码”的第二个层次代码本身的可复用性。直接写脚本的坏处是每换一个画图需求就要复制粘贴一大段用函数封装后可以做到一行代码出图# analyze.py 中的核心函数 def analyze_taxi_flow(taxi_df: pd.DataFrame, cfg: dict): 从清洗后的轨迹数据生成 OD 流图与热点图 cleaned tbd.clean_traj( taxi_df, col[VehicleNum, Stime, Lng, Lat], speed_limitcfg[cleaning][speed_limit] ) od tbd.traj_to_od(cleaned, col[VehicleNum, Stime, Lng, Lat]) od.to_csv(cfg[data][od_path], indexFalse) return od这个函数看起来很朴素但它体现了两个关键的设计决策第一函数的输入是 DataFrame 而不是文件路径意味着你可以在执行中间插入额外的数据检查逻辑第二OD 提取的中间结果落盘保存下次调图时不必重新跑一遍清洗大幅提高迭代速度。5.3 可视化方案对比与内存管理方案数据量级支持交互性适用交付场景matplotlib 静态图百万点以下无论文、报告插图folium 交互地图十万点以下点击查看弹窗业务汇报 HTML 页面pydeck GPU 渲染千万点级别缩放、筛选大数据量探索分析ECharts 流图聚合后 OD 对悬浮提示Dashboard 大屏内存管理是轨迹数据分析绕不开的坎。200 万条 GPS 记录的 DataFrame 占内存大概 200 到 400MB加上清洗过程中的多份拷贝很容易突破 4GB。解决办法是洗数据时只保留VehicleNum, Stime, Lng, Lat, OpenStatus五列把车牌号、司机姓名这类业务字段放在另一张表里最后用VehicleNum关联回来节省的内存在分析阶段是数量级的。6. 画“OD 弦图”时的 3 个验证技巧轨迹可视化里最容易出问题的是 OD 聚合结果和轨迹切分结果的正确性验证。我每次写完一套新的分析流程都会用下面三个技巧来确认结果没被坐标系、时间格式或者切分逻辑偷走。第一个技巧叫“单日单车的完整轨迹回放”。取一辆车一天的数据不要聚合画成一条连续线配上时间标签人为检查它是否经过了合理的道路。城市道路网有天然的几何约束——出租车不会横穿一条河而不走桥不会在高速公路上 90 度转弯。如果轨迹线出现了这种几何异常直接从地图服务和路网数据里拉一条边界做空间过滤。第二个技巧是“OD 距离与行驶时间的相关性校验”。正常情况下OD 距离与行程耗时呈明显的正相关。如果发现 OD 距离只有 500 米但耗时 2 小时或者 OD 距离 30 公里但耗时只有 10 分钟说明轨迹切分产生了“伪行程”——很可能是司机在行驶中熄火休息超过了切分阈值导致一条完整轨迹被误切成两条。我一般会画一张“距离 vs 耗时”的二维密度图一眼就能看出是否有异常簇群。第三个技巧是把 OD 点的经纬度反向转换回地址用街道名称做直观校验。坐标转换有一类比较隐蔽的错位是“东经西经”或者南北纬的符号错误这种错误在图上甚至看起来形状正常但整体镜像翻转了。取一个 OD 点用reverse_geocoder库反查详细地址如果返回的地址是从未听过的城市名或海外地名坐标系定位大概率有问题。校验通过的 OD 数据才值得进入后续的可视化和报告环节。本文还有配套的精品资源点击获取