ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Python与高德地图的交通数据分析:接口调用、拥堵指数计算与可视化实战

基于Python与高德地图的交通数据分析:接口调用、拥堵指数计算与可视化实战 简介这是一套基于Python的高德地图交通数据分析实战项目面向交通物流领域的数据分析师、Python学习者及有日常通勤规划需求的开发者用于解决早晚高峰时段指定路段拥堵情况难以提前预判的问题。项目完整覆盖数据采集、道路拥堵指数解析、时段统计与可视化展示等环节结构清晰适合作为课程设计或竞赛高分参考。压缩包共47个文件、约1.62MB主要包含Python脚本、Web前端样式与脚本、地图数据文件及项目说明文档其中py文件实现核心分析逻辑css与js负责可视化界面map文件存储地理与路况数据目录组织便于按模块学习。已有408人学习浏览。通过该资源可掌握高德地图API调用、交通数据清洗与聚合方法并获得可运行的完整源码、配置模板及项目文档便于二次开发也可扩展至其他城市或路段具有较强的实战参考价值。1. 交通数据分析入门为什么是高德地图与Python如果你手头正捧着“基于Python实现高德地图的交通数据分析源码项目说明高分项目.zip”这种压缩包大概率是课程设计或毕设阶段要做一套能跑、能出图、能写进报告的东西。这类项目的核心诉求其实特别直接用Python抓到高德地图实时路况把拥堵等级、旅行时间、路段速度这些数据拿下来清洗后画成图表回答“这座城市哪条路在堵、什么时候堵、堵成什么样”。做这类分析高德开放平台是首选数据源——免费额度对个人开发足够用接口返回结构规整不要求你自建采集车队而Python生态里requests、pandas、matplotlib刚好把获取、处理、可视化三步串成一条线。这篇文章不聊PPT层面的“智慧交通”只讲怎么把那个zip里的思路自己动手复现出来包括接口怎么调、数据怎么洗、图表怎么出、哪些地方会翻车。2. 拿到高德路况数据接口选型、请求构造与字段解读2.1 先搞懂高德交通数据长什么样两套常见接口怎么选高德开放平台对外开放的地图API里和交通数据分析关系最直接的有两类接口。一类是路径规划类接口比如驾车路径规划返回结果里每一段道路的耗时、距离、方向以及实时路况等级适合做点对点的通勤分析另一类是交通态势类接口直接按矩形区域或城市返回路况拥堵数据适合做大范围路网扫描。做课程设计时我一般建议先拿路径规划接口练手因为它的返回结构简单、字段稳定、不需要处理复杂的多边形边界而且它是理解“路况等级坐标轨迹旅行时间”这套数据模型最快的路径。两类接口的定位差异也决定了后续分析的方向。路径规划接口适合回答“从A到B在这条路上要堵多久”交通态势接口适合回答“整个城区哪些片区正在飘红”。如果你只有一周时间交差做前者就够了如果项目说明里写了“区域拥堵分析”“路网热力图”这类词那就要把后者也纳入采集范围。选型时还要注意一个细节高德接口按QPS计费个人开发者默认并发有限第一版先把抓取频率压住别一上来就跑多线程后文第5章会专门说这个坑。2.2 用Python拿到第一批路况数据最小请求示例与字段解读先确认你的环境有requests和pandas。申请高德开放平台Web服务API的Key是前置条件控制台里创建应用后会给你一个Key字符串这个Key要作为请求参数传给每个接口。下面这个示例是最小可用的驾车路径规划请求跑通了再往工程化方向改。import requests import json def fetch_driving_route(origin, destination, city, key): url https://restapi.amap.com/v3/direction/driving params { key: key, origin: origin, # 起点坐标格式为经度,纬度 destination: destination, # 终点坐标格式为经度,纬度 city: city, # 起点所在城市避免跨城绕路 extensions: all, # 返回详细路段级信息必选 strategy: 10, # 不考虑当前路况只按距离推荐便于对比 output: json } resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() return resp.json() key 你的高德Web服务Key origin 116.481028,39.989643 destination 116.465302,39.995904 data fetch_driving_route(origin, destination, 北京, key) print(json.dumps(data[route][paths][0][steps][0], ensure_asciiFalse, indent2))这段请求看起来简单但有三个参数直接决定了你能分析什么。extensionsall是硬性要求缺省值base只返回整体耗时和距离不会返回路段级路况strategy10的意思是“距离优先且不考虑实时路况”做分析时要用它拿到标准路线再叠加实时状态否则两次请求走的路线不同数据没法对比city参数容易被忽略不传的话高德按起点坐标推断城市遇到边界坐标容易判错跨城通勤分析尤其要显式传参。返回的JSON结构需要花两分钟看清route下面有paths数组代表一条推荐路线paths里的steps数组是这条路切分成的路段集合每一段的distance、duration、traffic_status、polyline才是后续分析真正要用的。traffic_status字段的取值是整数0为未知、1为畅通、2为缓行、3为拥堵后面所有可视化都得靠这个数字撑起来。2.3 把路况数据落到表里DataFrame清洗与等级映射拿到原始JSON后不能直接分析要做两层处理先把嵌套的steps拆平成记录行再把traffic_status转成可计算的数值。下面是数据清洗的示例顺便把每段路的坐标串也解析出来后面画轨迹要用。import pandas as pd def parse_route_to_frame(data, route_id): steps data[route][paths][0][steps] rows [] for step in steps: row { route_id: route_id, road_name: step.get(road_name, ), distance_km: round(float(step[distance]) / 1000, 3), duration_s: int(step[duration]), traffic_status: int(step[traffic_status]), instruction: step[instruction] } # polyline是经度,纬度;经度,纬度坐标串后续画轨迹需要 row[polyline] step[polyline] rows.append(row) return pd.DataFrame(rows) df parse_route_to_frame(data, route_001) # 将路况等级映射成通行指数数值越高越畅通 status_map {0: -1, 1: 3, 2: 2, 3: 1} df[traffic_index] df[traffic_status].map(status_map) print(df.head())这里的核心操作是等级映射。很多初学者直接把traffic_status当连续变量做均值出来的图谁看了都困惑拥堵代码3和畅通代码1的平均值等于2缓行完全没意义。映射成通行指数后-1表示未知状态后续计算要剔除1到3分别对应拥堵、缓行、畅通这样求路段平均分才说得通。duration_s保留秒数是为了后面按路段汇总旅行时间distance_km转成公里是纯为了方便阅读数据量大了以后建议统一用米避免单位混淆。这段清洗逻辑做完你已经具备了做交通数据分析最核心的原材料——一张结构化表格每一行是一条路段的实时状态。下一步要解决的问题变成了这张表怎么变成报告里那张有说服力的图。3. 把路况数据变成分析结论拥堵指数计算与可视化输出3.1 拥堵指数怎么算路况等级转评分的三种口径手上有路段级路况数据后第一步动作通常是聚合。聚合口径决定了整个项目的研究视角常见做法有三种按时间聚合看单条路一天内的拥堵演变按区域聚合对比不同片区的总体拥堵程度按路线聚合比较通勤备选路线的优劣。这里给出一套按区域聚合的参考实现输入是一天的路段采集结果输出是每个道路名称的平均通行指数和拥堵路段占比。拥堵路段占比的定义是traffic_status≥2的记录数占比这个指标的优点是直观写报告时“早高峰东三环拥堵路段占比38%”比一个抽象的平均分更好懂。def aggregate_by_road(df): # 剔除未知状态避免污染占比计算 valid df[df[traffic_status] 0].copy() # 按道路聚合统计总记录数、缓行及以上占比 summary valid.groupby(road_name).agg( record_count(traffic_status, count), avg_index(traffic_index, mean), congestion_ratio(traffic_status, lambda x: (x 2).mean()) ).reset_index() # 只保留有足够样本的道路少于3条记录的说明抓取次数太少 summary summary[summary[record_count] 3] summary summary.sort_values(congestion_ratio, ascendingFalse) return summary road_rank aggregate_by_road(df) print(road_rank.head(10))这里做了两个数据处理决策一是剔除了traffic_status为0的未知记录否则这些未知状态会假装成畅通数据拉低拥堵占比二是设置了最小样本数阈值只有3次以上采样的道路才参与排序不然某条路只抓到一次又是缓行排名就失真了。用lambda做分组内的条件占比是个小技巧pandas的groupby里可以传入任意函数比起先筛选再合并这种写法少写好几行代码。3.2 热力图与断面时序把数据画成能放进报告里的图表格数据交到导师手里是没有说服力的得画图。交通数据分析里最常见的两类输出是路况时序曲线和道路拥堵热力图。时序曲线展示单条道路一天内通行指数的变化能看出早高峰峰值、午间回落、晚高峰次生拥堵热力图展示区域网格或路网线段在不同时段的拥堵等级颜色从绿到红直接传达“哪里红了”。import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] # 中文字体 def plot_road_timeline(df_time, road_name): sub df_time[df_time[road_name] road_name].sort_values(record_time) plt.figure(figsize(10, 4)) plt.plot(sub[record_time], sub[traffic_index], markero, markersize3) plt.axhline(y2, colororange, linestyle--, label缓行阈值) plt.axhline(y1, colorred, linestyle--, label拥堵阈值) plt.title(f{road_name} 通行指数时序变化) plt.xlabel(采样时间) plt.ylabel(通行指数3畅通 / 2缓行 / 1拥堵) plt.legend() plt.grid(alpha0.3) plt.tight_layout() return plt.gcf()画图画到中文字体乱码是个高频翻车点matplotlib默认字体不支持中文必须在脚本开头显式指定中文字体否则标题里的“拥堵”直接变方块。横轴记录时间在数据清洗时就要转成datetime类型排序后用matplotlib绘图不用额外处理。阈值横线的用意是让“什么时候开始堵、什么时候恢复”在视觉上一眼可读比单画一条折线更有分析价值。如果项目说明里提到“热力图”而你又不想引入额外地图库还有一个轻量替代方案把路网轨迹按拥堵等级着色后平铺画出来。高德路况图层本身的颜色语义是绿黄红你可以在matplotlib里用LineCollection按traffic_status给每条路段上色画出来就是简化版的路线热力效果。这个方案不需要瓦片地图也不需要额外装库报告里交代清楚“基于高德路段轨迹数据的拥堵着色图”即可。想要更漂亮的效果再考虑接高德地图瓦片或QGIS导底图那是锦上添花不是第一版必须做的事。3.3 区域横向对比多路段聚合与早晚高峰排序单条路看完了要做横向对比。比如分析高新区和老城区晚高峰的拥堵差异或者对比三条通勤备选路线谁更稳。这里的关键不是再写新逻辑而是把3.1的聚合函数复用起来按自定义分组字段多跑几次。def compare_routes(routes_data): # routes_data是多个DataFrame的字典key是路线名 compare_rows [] for name, df_item in routes_data.items(): valid df_item[df_item[traffic_status] 0] row { route: name, total_distance_km: round(valid[distance_km].sum(), 2), total_duration_min: round(valid[duration_s].sum() / 60, 1), avg_travel_speed_kmh: round( valid[distance_km].sum() / (valid[duration_s].sum() / 3600), 1 ), congestion_ratio: round((valid[traffic_status] 2).mean(), 3) } compare_rows.append(row) return pd.DataFrame(compare_rows).sort_values(congestion_ratio)对比表里最容易让读者眼前一亮的是avg_travel_speed_kmh这个字段——把路段距离除以耗时换算成平均车速立刻把“缓行占比”这种抽象指标转成“高峰期这条线路平均只能跑到21公里/小时”这种具象结论。换算逻辑是距离和单位保持一致先算总距离公里除以总耗时小时别混单位。横向对比表适合放进项目报告的“多方案比选”章节数据量少两张表格加一张柱状图就够了不用硬做地图可视化。4. 分析结果要可信3个必调参数与坐标系转换4.1 影响结果可信度的3个必调参数city、strategy、extensions很多人在参数上翻车不是因为代码写错而是没理解参数背后的潜台词。第一个必调参数是city。高德路径规划默认按起点坐标推断城市但城市边界附近的点位很容易被划到邻市导致绕路策略完全错误。做通勤分析时起点终点都在同一城市还感知不明显一旦分析跨城通勤这个问题就放大成路线完全不可比。显式传city不仅是正确性问题也是结果可复现性的保障——你和你的答辩老师用同一组坐标跑拿到的路线应该一致。第二个必调参数是strategy。驾车路径规划里strategy默认值是0含义是“速度优先且考虑实时路况”这个模式下高德会实时避开拥堵路段返回给你一条“已经绕开堵点”的路线。但你要做的是分析拥堵而不是躲避拥堵如果直接拿默认策略的数据采集到的是绕行后的路况原路段的拥堵状态根本不会出现在结果里。做交通分析必须用strategy10等不考虑实时路况的策略拿到固定路线再读取这条路线上的实时traffic_status这样分析对象才是稳定的。这个参数不调后面所有的时序对比都是伪命题。第三个必调参数是extensionsall。extensions默认值是base只返回整体耗时距离不返回路段级信息。虽然这个参数在前文已经提过但还是要单独强调base模式下你没有traffic_status可用更拿不到polyline坐标串。整个项目的数据分析能力都建立在extensionsall之上这一步省了后面全都要返工。调用前先自测一遍返回的steps里有没有traffic_status字段字段有再继续写逻辑。4.2 坐标系统一GCJ-02、WGS-84与火星坐标转换高德地图所有接口返回的坐标都是GCJ-02坐标系俗称火星坐标。而很多公开数据集、GPS原始记录、或者第三方工具导出的坐标都是WGS-84标准。两种坐标系之间差几百米如果你把WGS-84的点直接传给高德你会发现路段匹配得歪七扭八距离和耗时全不可信。做交通数据分析必须先把所有外部坐标转成GCJ-02再调用高德接口。import math def wgs84_to_gcj02(lng, lat): # WGS-84转GCJ-02适用于中国大陆区域 a 6378245.0 ee 0.006693421622965943 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 def _transform_lat(x, y): ret -100.0 2.0 * x 3.0 * y 0.2 * y * y 0.1 * x * y ret 0.2 * math.sqrt(abs(x)) ret (20.0 * math.sin(6.0 * x * math.pi) 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(y * math.pi) 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0 ret (160.0 * math.sin(y / 12.0 * math.pi) 320.0 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0 return ret def _transform_lng(x, y): ret 300.0 x 2.0 * y 0.1 * x * x 0.1 * x * y ret 0.1 * math.sqrt(abs(x)) ret (20.0 * math.sin(6.0 * x * math.pi) 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(x * math.pi) 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0 ret (150.0 * math.sin(x / 12.0 * math.pi) 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0 return ret这段转换代码是WGS-84转GCJ-02的标准实现数学公式看起来复杂但不用细究直接用即可。注意它的适用范围是大陆区域港澳台坐标不适用。实际项目里建议封装成坐标工具模块所有外部数据进入分析管线之前先过一遍转换输出时统一GCJ-02。这样后续和地图API交互、和高德瓦片叠加都不会出偏差。4.3 请求频率与渠道参数让抓取更稳的请求配置高德的个人开发者Key有每日调用量和QPS双重限制课程设计的数据量通常够用但高频连续抓取容易触发限流。一个稳妥的抓取策略是加sleep控制频率每次请求间隔0.2秒以上抓取长时段数据时分批跑每100次请求停一下再继续。另外保持稳定的请求头很重要随机换User-Agent反而容易被识别为异常行为不如用一个固定的浏览器UA。渠道参数这块网上常有人说高德请求里带某个通用渠道号可以放宽限制比如c04030322001这类数字串在部分教程里被提及。但在我的经验里这类渠道号和Key的权益绑定不稳定正规分析项目不建议去蹭除非你只是想临时验证某个接口的返回结构。真正影响抓取稳定性的不是渠道号而是你自己的调用节奏QPS控制在个位数、请求超时设置10秒、失败重试最多3次且退避时间递增。把这三点稳住课程设计期间的采集任务基本不会断。5. 避坑交通数据分析项目最常见的5个翻车点5.1 Key失效或QPS超限导致请求返回错误信息现象是抓取过程中突然连续返回“USER_DAILY_QUERY_OVER_LIMIT”或类似错误码代码逻辑没问题但数据就是拿不到。原因绝大多数是对个人开发者Key的每日配额和并发上限没有预估采集任务跑在高峰期或者循环里没有控制请求间隔。解决办法是写一个带退避的重试包装函数遇到限流错误码就sleep几秒再重试重试超过3次自动跳过当前路段并记录日志最后分析时剔除缺失路段。日志是一定要写的不然一次长时段采集跑完你根本不知道哪些路段的数据是空的。5.2 坐标系偏移导致路段匹配错乱现象是所有地点都偏移了几百米路段名称和实际位置对不上画出来的轨迹图明显漂移。原因有两个方向一是把WGS-84坐标直接传给高德没有做GCJ-02转换二是经纬度传参顺序颠倒高德要求“经度,纬度”有些工具导出的是“纬度,经度”肉眼很难看出来。解决办法是封装坐标校验函数检查经度范围在73到135之间、纬度范围在18到54之间超出范围直接报错入参前用4.2节的转换函数统一坐标系并把原始坐标和转换后坐标一起存到表里方便复核。5.3 路况等级编码在不同接口里取值不一致现象是同一个路段在两个接口里返回的拥堵程度对不上一个说畅通一个说缓行。原因是从路径规划接口拿到的traffic_status和从交通态势接口拿到的等级编码定义不完全一致前者按0未知1畅通2缓行3拥堵编码后者有的版本直接用颜色字符串。解决办法是建一张编码映射表所有数据源统一映射成“通行指数三档”分析逻辑只认自己这张表不直接对比原始值。这个坑最隐蔽因为两个接口单独看文档都没问题放一起才暴露差异。5.4 抓取任务跑太久程序挂了不知道现象是定时采集睡一觉来看程序报错退出或者数据采了一半停了下来已采数据没落盘全丢。原因是脚本没有做增量落盘和断点续采采集逻辑跑在内存里一步报错就全盘皆输。解决办法是每抓完一个路段就追加写入CSV文件采集脚本设计成可重入模式启动时先读已有文件跳过已完成的路段时间片。这个设计在前端看起来只是多了几行文件操作但对长时段采集是生命线没有断点续采的采集脚本等于没有后悔药的赌局。5.5 时序分析的时间字段没解析对现象是画时序图时横轴乱序或者报错pandas排序无效。原因是高德返回的时间字段有的是时间戳毫秒值有的是yyyymmddHHMMSS字符串还有的直接只有日期没有时分秒混在一起直接排序会出乱子。解决办法是在清洗步骤统一转成pandas的datetime类型全部转成北京时间再按该列排序和分组。采集时顺手记录本地时间戳是最省事的不要依赖接口返回的时间字段做时序分析接口时间字段语义不稳定自己记录最靠谱。6. 进阶玩法把一次性分析变成持续监测与趋势预警做到这一步你已经有了抓取脚本、清洗逻辑、可视化函数和一堆踩坑经验整套系统能跑通但还是一次性分析。更进一步的方向是把采集任务定时化让数据每天自动累积并加一个轻量预警实时路况的拥堵程度如果显著高于过去15分钟的平均水平就输出预警记录告诉你“这条路正在非预期地变堵”。这个能力看起来门槛高实现起来只需要定时任务加阈值判断但它能把课程设计从“展示数据”提升到“提出发现”的层面答辩时的讨论深度完全不同。import time import datetime import csv def scheduled_collect(func, interval_s300, max_runs10): # 示例每5分钟采集一次最多采10轮用于小规模验证 records [] for run_id in range(max_runs): ts datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) try: data func() records.append({run_id: run_id, time: ts, data: data}) except Exception as exc: records.append({run_id: run_id, time: ts, error: str(exc)}) time.sleep(interval_s) return records def alert_on_congestion(df_realtime, df_history, window_min15, threshold0.5): # 核心逻辑当前拥堵指数低于历史窗口均值减阈值则触发预警 recent_mean df_history[df_history[time] datetime.datetime.now() - datetime.timedelta(minuteswindow_min)][traffic_index].mean() current df_realtime[traffic_index].mean() if current recent_mean - threshold: return f预警拥堵指数从 {recent_mean:.2f} 降至 {current:.2f}可能存在突发拥堵 return None最后这段代码里timedelta窗口和阈值是两个可以调的旋钮。15分钟窗口适合城市快速路因为流量变化快阈值0.5意味着通行指数跌了半档就报警太灵敏会天天误报太迟钝失去预警意义建议用历史数据回放调参。这算整个项目里最接近“工程化”的部分跑上三五天积累历史数据后这套预警脚本就能给你输出一份简单的“异常拥堵事件日志”再叠加前文的热力图和时序曲线整个东西就不是课程作业而是一个小型交通运行监测原型了。这个方向做到这里投入产出比已经很高核心代码量不大但覆盖了数据获取、清洗、聚合、可视化、定时调度、异常预警全链路。如果后续想继续扩展可以接入高德的瓦片底图做更精致的地图叠加或者把线程池引进来做并行采集再往后是预测模型那是另一个复杂度等级的事了。就课程设计和初级项目而言把这一条链路做扎实远胜于东抄一段代码西凑一个图表。我自己做这类项目最大的教训是别贪多先保证一条链路完整可靠再谈扩展。希望帮到你。本文还有配套的精品资源点击获取
返回列表