☰
公交GPS轨迹数据清洗与散点图可视化实战
2026/10/14 16:48:54 网站建设 项目流程

1. 从线路到轨迹:为什么需要拿到公交行驶路径

上一篇文章里,我们把城市公交的静态数据——线路名称、站点列表、首末班时间——整理成了一张能看能查的基础表。但那张表有个天然的局限:它只告诉我们“车会经过哪些站”,却没说“车是怎么从这一站开到下一站的”。这两件事听起来差不多,实际差得很远。

举个生活里的例子。你打开地图查公交,看到“从A站到B站,途经5站”,这是站点序列。但如果你想知道这趟车具体拐了几个弯、走了哪条路、在哪个路口掉的头,那就得拿到行驶路径。站点序列是骨架,行驶路径才是血肉。做城市公交网络分析,如果只停留在站点层面,你能算换乘次数、能算站点覆盖率,但算不了实际行驶距离、算不了线路弯曲系数、也算不了两条线路在空间上的真实重叠程度。这些指标恰恰是评估公交线网合理性的关键。

所以这一篇的核心任务很明确:把公交的行驶路径从数据源里捞出来,清洗成可用的坐标序列,然后用散点图把轨迹画出来。散点图听起来简单,但在路径可视化这个场景里,它是最诚实的一种表达方式——不插值、不美化,每个点就是车辆实际经过的一个位置采样。你能从点的疏密程度直接看出车速变化,从点的分布形状直接看出道路走向。

这篇文章适合谁看?如果你已经做过基础的数据清洗,会用Python处理表格数据,但对地理空间数据的处理还不太熟,那这篇正好补上这块。如果你之前只画过静态的线路图,想进一步做轨迹级别的可视化,这篇也能给你一套可直接复现的流程。我会把每一步的参数选择、踩过的坑、以及为什么这么做的理由都讲清楚,你照着抄作业就行。

2. 行驶路径数据的获取思路与方案选型

2.1 路径数据的三种常见来源

要拿到公交行驶路径,市面上能走的路子大概有三条,我分别说说它们的优缺点和适用场景。

第一条路是公交GPS轨迹数据。这是最直接的方式,车辆上装的定位设备会按固定间隔上报经纬度、速度、方向角等信息。优点是精度高、时间粒度细,能还原真实的行驶过程。缺点是数据量大、需要做大量的清洗工作,而且很多城市的GPS数据并不公开,获取门槛较高。

第二条路是地图服务商提供的路径规划接口。你给定起点和终点,接口返回一条推荐路径的坐标序列。优点是获取方便、路径规整,适合做线路走向的宏观分析。缺点是返回的是“推荐路径”而非“实际行驶路径”,公交可能因为专用道、临时绕行等原因走不同的路。

第三条路是基于站点序列做路径推断。用相邻两个站点的坐标,结合路网数据推算出一条合理路径。优点是只需要站点数据就能做,缺点是推断结果和实际路径可能有偏差,尤其是在道路复杂的区域。

提示:如果你手头只有站点数据,第三条路是唯一选择;如果你能拿到GPS数据,优先用第一条路,因为后续所有分析都建立在“真实”二字上。

2.2 为什么我最终选了GPS轨迹加地图接口校验的方案

在实际项目里,我采用的是以GPS轨迹为主、地图接口为辅的方案。具体来说,先用GPS轨迹数据提取出每趟车的实际行驶坐标序列,然后用地图接口对轨迹做一次“合理性校验”——如果某段轨迹的偏移量超过阈值,就标记出来人工核查。

这么做的理由有三个。第一,GPS轨迹能保留真实的道路走向,包括那些地图接口不会推荐的“小路”。第二,地图接口的校验能帮你发现数据质量问题,比如定位漂移导致的异常点。第三,两者结合能在精度和覆盖率之间取得平衡,不至于因为某一段数据缺失就整条线路作废。

方案选型上,数据处理用Pandas加NumPy做基础清洗,地理计算用GeoPandas处理坐标系和空间关系,可视化用Matplotlib的散点图功能。为什么不直接用专门的地图可视化库?因为散点图的核心是“看点的分布”,Matplotlib在这件事上足够灵活,而且能精确控制每个点的颜色、大小和透明度,这些细节在轨迹分析里很重要。

2.3 坐标系的选择与转换逻辑

这里有个容易被忽略但非常关键的细节:坐标系。GPS原始数据通常是WGS84坐标系,也就是国际通用的经纬度。但国内的地图服务商在展示时往往会做一次偏移处理,如果你直接把WGS84的坐标画在地图底图上,会发现轨迹整体偏移了几百米。

我的处理方式是:统一转换成Web Mercator投影坐标系再做可视化。为什么选这个?因为散点图是在平面上画的,经纬度是球面坐标,直接画会导致高纬度地区的东西向距离被拉伸。Web Mercator能把经纬度转换成平面米制坐标,画出来的散点图在距离比例上是正确的。

转换的代码逻辑大概是这样:

import geopandas as gpd from shapely.geometry import Point # 假设df里有lon和lat两列 geometry = [Point(xy) for xy in zip(df['lon'], df['lat'])] gdf = gpd.GeoDataFrame(df, geometry=geometry, crs='EPSG:4326') # 转换到Web Mercator gdf_mercator = gdf.to_crs('EPSG:3857') # 提取转换后的x和y df['x'] = gdf_mercator.geometry.x df['y'] = gdf_mercator.geometry.y

这段代码里,EPSG:4326是WGS84的代号,EPSG:3857是Web Mercator的代号。转换之后,x和y的单位就是米,画散点图时坐标轴的比例是真实的。

注意:如果你后续要和其他地图底图叠加,记得确认底图用的坐标系,不一致的话还得再转一次。我一般会在数据表里同时保留原始经纬度和投影坐标,方便不同场景切换。

3. 轨迹数据清洗的核心细节与实操要点

3.1 定位漂移的识别与处理

GPS数据最让人头疼的问题就是定位漂移。车辆明明在马路上跑,数据点却飘到了旁边的楼顶上。这种点如果不处理,画出来的散点图会出现明显的“毛刺”,影响对轨迹走向的判断。

识别漂移点的思路是看速度和加速度的突变。正常情况下,公交车的行驶速度在0到60公里每小时之间,相邻两个点之间的时间间隔如果是10秒,那距离不应该超过170米左右。如果某个点算出来的瞬时速度超过80公里每小时,基本可以判定是漂移。

处理方式有两种。一种是直接剔除,把异常点从序列里删掉。另一种是平滑修正,用前后点的插值替换异常值。我一般优先用剔除,因为插值会引入人为构造的数据,在轨迹分析里可能掩盖真实问题。但如果异常点连续出现,剔除会导致轨迹断裂,这时候才考虑插值。

# 计算相邻点之间的距离(单位:米) df['dist'] = np.sqrt(df['x'].diff()**2 + df['y'].diff()**2) # 计算时间差(单位:秒) df['time_diff'] = df['timestamp'].diff().dt.total_seconds() # 计算瞬时速度(单位:米/秒) df['speed'] = df['dist'] / df['time_diff'] # 标记速度超过22米/秒(约80公里/小时)的点 df['is_drift'] = df['speed'] > 22 # 剔除漂移点 df_clean = df[~df['is_drift']].copy()

这段代码里,22米每秒这个阈值是根据公交车的实际行驶能力定的。城市公交最高时速一般不超过60公里,换算下来是16.7米每秒,留一点余量取22比较稳妥。

3.2 时间间隔不均匀的处理

GPS上报的时间间隔往往不是严格固定的。理想情况下是每10秒一个点,但实际可能因为信号遮挡、设备延迟等原因变成15秒、20秒甚至更长。时间间隔不均匀会直接影响速度计算的准确性,也会让散点图的疏密程度失真。

我的处理方式是按时间窗口重采样。把整条轨迹按30秒为一个窗口切分,每个窗口内如果有多个点就取平均位置,如果没有点就标记为缺失。这样处理之后,点的分布就均匀了,画出来的散点图能更真实地反映行驶过程。

# 设置时间索引 df_clean = df_clean.set_index('timestamp') # 按30秒窗口重采样,取x和y的均值 df_resampled = df_clean[['x', 'y']].resample('30S').mean() # 剔除空值 df_resampled = df_resampled.dropna()

提示:重采样的窗口大小要根据数据密度来定。如果原始数据是每秒一个点,窗口可以设小一点,比如10秒;如果是每30秒一个点,窗口就别超过60秒,否则会丢失太多细节。

3.3 轨迹分段与线路匹配

一条公交线路一天可能跑几十趟,GPS数据是混在一起的。要画某一条线路的轨迹,得先把数据按趟次切分开。切分的依据通常是首末站的时间戳——车辆到达首站和末站的时间点之间的数据,就是一趟完整的行驶记录。

切分之后还要做线路匹配,确认这一趟数据确实属于目标线路。匹配的方法有两种:一种是看站点序列,把GPS点和最近的站点做关联,看是否覆盖了目标线路的所有站点;另一种是看空间范围,判断轨迹是否落在目标线路的缓冲区范围内。我一般用第一种,因为更精确。

# 假设已经按趟次切分好,df_trip是某一趟的数据 # 计算每个GPS点到最近站点的距离 def nearest_station_distance(point, stations): distances = stations.geometry.distance(point) return distances.min() df_trip['nearest_dist'] = df_trip.geometry.apply( lambda p: nearest_station_distance(p, stations_gdf) ) # 如果超过80%的点距离站点在100米以内,认为匹配成功 match_ratio = (df_trip['nearest_dist'] < 100).mean() if match_ratio > 0.8: print("线路匹配成功")

这里100米这个阈值是根据城市道路宽度和GPS精度综合定的。太严会导致匹配失败率过高,太松会把相邻线路的数据混进来。

4. 散点图绘制的完整实操流程

4.1 基础散点图的绘制与参数调优

数据清洗完之后,画散点图本身其实很简单。但要把散点图画得“能看”,参数调优很关键。我一般会关注四个参数:点的大小、透明度、颜色映射、坐标轴比例。

点的大小决定了散点图的视觉密度。点太大,轨迹会糊成一条粗线,看不出点的分布;点太小,稀疏区域会显得太空。我的经验值是点的大小设为3到8之间,具体看数据密度。数据密集的时候用3,稀疏的时候用8。

透明度是散点图的灵魂。公交轨迹在交叉路口会有重叠,如果点不透明,重叠区域就是一团黑,看不出层次。把透明度设在0.3到0.6之间,重叠的地方颜色会加深,能直观反映出哪些路段车流密集。

import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(12, 12)) ax.scatter( df_resampled['x'], df_resampled['y'], s=5, # 点的大小 alpha=0.5, # 透明度 c='steelblue', # 颜色 edgecolors='none' # 去掉点的边框,避免视觉杂乱 ) ax.set_aspect('equal') # 保证x和y轴比例一致 ax.set_xlabel('X (meters)') ax.set_ylabel('Y (meters)') ax.set_title('Bus Route Trajectory Scatter Plot') plt.tight_layout() plt.show()

set_aspect('equal')这行很重要。如果不设置,Matplotlib会自动调整坐标轴比例,画出来的轨迹会被拉伸或压缩,形状就失真了。

4.2 用颜色编码速度信息

基础散点图只能看出轨迹形状,看不出行驶状态。要让散点图承载更多信息,可以用颜色编码速度。速度快的点用暖色,速度慢的点用冷色,这样一眼就能看出哪里堵车、哪里畅通。

实现方式是把速度值映射到颜色上:

# 计算每个点的速度(重采样后的数据需要重新算) df_resampled['speed'] = np.sqrt( df_resampled['x'].diff()**2 + df_resampled['y'].diff()**2 ) / 30 # 30是重采样窗口的秒数 fig, ax = plt.subplots(figsize=(12, 12)) scatter = ax.scatter( df_resampled['x'], df_resampled['y'], c=df_resampled['speed'], s=5, alpha=0.6, cmap='RdYlGn_r', # 红黄绿反向,红色表示慢 vmin=0, vmax=15 # 速度上限设为15米/秒 ) plt.colorbar(scatter, label='Speed (m/s)') ax.set_aspect('equal') plt.show()

RdYlGn_r这个色带是从红到绿的反向,红色代表低速,绿色代表高速。vmin和vmax设定了颜色的映射范围,超过15米每秒的都按最红处理,避免个别异常值把整体颜色拉偏。

4.3 多线路叠加与图层控制

单条线路的散点图看的是细节,多条线路叠加看的是全局。把几条线路画在同一张图上,能直观看出线路之间的空间关系——哪里重叠、哪里分叉、哪里形成换乘节点。

多线路叠加的关键是颜色区分和图层顺序。每条线路用一个固定的颜色,图例里标清楚。图层顺序上,把线路长度短的画在上面,长的画在下面,避免短线路被长线路盖住。

routes = { 'Route_A': df_route_a, 'Route_B': df_route_b, 'Route_C': df_route_c } colors = {'Route_A': '#1f77b4', 'Route_B': '#ff7f0e', 'Route_C': '#2ca02c'} fig, ax = plt.subplots(figsize=(14, 14)) # 按线路长度排序,长的先画 sorted_routes = sorted(routes.items(), key=lambda x: len(x[1]), reverse=True) for name, data in sorted_routes: ax.scatter( data['x'], data['y'], s=3, alpha=0.4, c=colors[name], label=name ) ax.legend(markerscale=3) ax.set_aspect('equal') plt.show()

markerscale=3是把图例里的点放大三倍,不然图例里的点太小看不清。

4.4 输出高分辨率图片与交互式探索

静态图片适合放进报告,但如果你要自己探索数据,交互式的方式更高效。我一般会先用Matplotlib出一张高分辨率的静态图存档,然后用Plotly做一版交互式的用于探索。

静态图的输出参数:

plt.savefig( 'bus_trajectory.png', dpi=300, # 高分辨率 bbox_inches='tight', # 去掉多余白边 facecolor='white' # 白色背景 )

dpi=300是印刷级别的分辨率,放到报告里放大也不会糊。bbox_inches='tight'能自动裁掉图表周围多余的空白,让图片更紧凑。

交互式版本用Plotly的话,代码逻辑类似,但多了一个悬浮提示的功能,鼠标放到点上能看到具体的时间和速度值。这个在排查异常点时特别有用。

5. 常见问题与排查技巧实录

5.1 轨迹画出来是断断续续的怎么办

这是最常见的问题,原因通常有三个。一是数据缺失,某些时间段没有GPS上报;二是清洗过度,把正常点也当漂移点剔除了;三是坐标系转换出错,导致部分点的位置异常。

排查顺序建议从数据缺失开始查。先看原始数据的时间戳,有没有大段的空白。如果有,那就是数据源的问题,只能接受或者用插值补。如果没有空白但轨迹还是断的,那就检查清洗逻辑,把速度阈值放宽一点试试。最后再检查坐标系,确认转换前后的数据范围是否合理。

5.2 散点图上的点分布不均匀

点分布不均匀通常是因为时间间隔不均匀。GPS上报频率不稳定,导致有些路段点密集、有些路段点稀疏。这个问题在重采样之后应该能缓解,但如果重采样窗口设得太大,又会丢失细节。

我的经验是先用原始数据画一版,再用重采样数据画一版,对比着看。如果两版差异很大,说明原始数据的时间间隔问题比较严重,需要调整重采样策略。如果差异不大,那就用重采样版本,视觉上更干净。

5.3 多条线路叠加后看不清单条线路

这是图层顺序和透明度的问题。如果所有线路都用同样的透明度,叠加区域会变成一团深色,单条线路的走向就看不出来了。

解决办法有两个。一是调整透明度,把底层线路的透明度调低,上层线路调高。二是用不同的点形状,比如圆形、三角形、方形,这样即使颜色接近也能区分。我一般会结合使用,颜色区分大类别,形状区分小类别。

5.4 速度颜色映射不准确

速度映射不准确通常是因为异常值拉偏了色带范围。如果有个别点的速度算出来是100米每秒,那色带的vmax就会被拉到100,正常速度的点全挤在色带的一端,颜色区分度就没了。

解决办法是用分位数来设定vmax,而不是用最大值。比如取95分位数的速度作为vmax,这样能排除极端异常值的影响。

vmax = df_resampled['speed'].quantile(0.95)

5.5 常见问题速查表

问题现象可能原因排查方法解决方式
轨迹断裂数据缺失检查时间戳连续性插值补全或接受缺失
轨迹断裂清洗过度放宽速度阈值重试调整漂移判定阈值
点分布不均时间间隔不均对比原始与重采样数据调整重采样窗口
叠加图混乱图层顺序不当检查绘制顺序按线路长度排序绘制
颜色区分度低异常值拉偏色带查看速度分布用分位数设定vmax
轨迹整体偏移坐标系不匹配检查CRS设置统一转换为投影坐标系

提示:排查问题时,我习惯先把数据按时间排序,然后画一张时间-速度的折线图。这张图能快速定位到异常时间段,比直接看散点图更高效。

6. 从散点图到线路分析:几个实用的延展方向

散点图画出来只是第一步,真正有价值的是从图里读出信息。我平时会从三个角度做延展分析。

第一个角度是站点停靠识别。公交车在站点停靠时,速度会降到接近零,散点图上表现为一簇密集的低速点。通过识别这些低速簇,能反推出站点的实际位置,和站点列表做交叉验证。

第二个角度是线路弯曲系数计算。把轨迹的总长度除以首末站的直线距离,得到的比值就是弯曲系数。这个指标能反映线路的绕行程度,系数越高说明线路越绕。计算总长度时,把相邻点的距离累加就行。

第三个角度是多线路重叠分析。把多条线路的轨迹做空间叠加,计算重叠路段的长度和比例。重叠比例高的线路,说明服务范围重合度高,可能存在资源浪费;重叠比例低的线路,说明覆盖了不同的区域,线网结构比较合理。

这三个方向都不需要额外的数据,用现有的轨迹坐标就能算。我建议你先从站点停靠识别做起,因为这个最直观,而且能顺便验证轨迹数据的质量。如果识别出的站点位置和实际站点偏差很大,那说明轨迹数据本身有问题,后面的分析也就没有意义了。

我个人在实际操作中的体会是,散点图这种看似简单的可视化方式,在轨迹分析里的价值被严重低估了。它不像热力图那样花哨,也不像交互地图那样直观,但它对数据的“诚实”程度是最高的。每一个点都是真实采样,没有任何插值和美化,你能从点的分布里读出很多隐藏信息。踩过几次坑之后,我现在做任何轨迹分析,第一步都是先画一张散点图看看,心里有底了再往下做。

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

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

立即咨询