去年年底开始规划西班牙假期的时候,我干了件很程序员的事情:给自己写了一个自动化的逐日行程规划器,把每天几点去哪、怎么串联、在哪个城市停留几天,全部交给算法去算。这件事做完之后,最大的感受是——我以前手动做行程规划时浏览各种攻略、反复打开地图查距离、担心错过景点开放时间的那几天,实在太浪费了。
这个项目的核心很简单:输入一个目的地清单和总天数,自动输出一份从早到晚排好的每日行程,精确到每个景点、每段交通、每个休息时段。听起来很多旅行App都有类似功能,但真用起来会发现大部分只是“懒人推荐路线”,真正能按你的节奏、兴趣偏好、城市间通勤时间做逐日动态规划的,少之又少。所以我决定自己造一个,顺带还真的带去了西班牙跑了一趟实测。
这篇文章不聊虚的,把我从思路拆解、算法设计、数据获取到最终上路的完整过程写出来。适合有一点Python基础、想自建旅行规划工具的开发者参考,也适合喜欢亲自安排行程但厌恶繁琐操作的旅行者,看看自动化在这件事上究竟能帮忙到什么程度。
1. 为什么要做自动行程规划器——项目缘起与痛点拆解
1.1 手动做行程到底有多痛苦
先说背景。我计划用14天走西班牙,城市锁定在马德里、巴塞罗那、塞维利亚和格拉纳达四个地方。刚决定这个计划的时候,我兴致勃勃地打开各种旅行指南和景点清单,打算手动排一份“完美行程”。结果折腾了三天,越排越暴躁。
为什么暴躁?不是你列不出景点,而是景点之间的组合计算太折磨人。比如马德里的普拉多博物馆和索菲亚王后艺术中心,都值得花小半天,但放在同一天还是不同天,直接影响前一天傍晚是否来得及看日落、第三天的城际火车在几点。每个景点除了位置还有开放时间,周一闭馆的、周三免费入场的、午休关门午后再开的,五花八门。再加上四个城市之间有高铁和长途大巴的时间差,跨城那天基本只能安排半天景点,这部分约束一旦漏算,整份行程就废了。
我试过用Excel表格,试过在备忘录里列清单,也试过手动画时间轴。最后发现,手动规划的瓶颈不在于“记不住”,而在于“组合数量爆炸”:十几个景点塞进一天,排列组合上万种可能,靠人肉枚举根本做不到全局最优。你顶多是用眼睛看到一个顺眼的顺序,然后停下来告诉自己“差不多了”。但“差不多”的代价是,某天暴走3万步,另一天闲得在咖啡馆发呆两小时。
1.2 现成旅行工具的尴尬
市面上的旅行规划工具我也没少用。一类是预定类App,帮你订住宿、订机票,但行程规划功能非常弱,基本是手动添加每天要去的景点,没有任何优化逻辑;另一类是“路线推荐”类工具,给你几条固定的城市浏览路线,看似智能,实际上换个酒店位置、换个偏好就完全不适用。
最让我失望的是,几乎没有工具能回答三个关键问题:第一,如果一个景点周一闭馆,我的行程是否会自动避开;第二,跨城移动当天的半天空闲,能不能自动塞进一个顺路的小景点;第三,连续几天高密度行程后,系统会不会主动安排一天“放风日”,把步行距离压下来。这些需求非常普通,但现成工具几乎全都不支持。我猜测原因是这类产品的用户量撑不起精细化规划功能的研发成本,大部分用户也习惯了“手动微调”,所以这类痛点被长期当作默认配置接受了。
既然没人做,就自己做。这个项目就是这么立项的。
2. 核心设计思路:把行程规划变成可计算的约束问题
2.1 三类核心模型
动手写代码之前,我先把行程规划这件事拆成了三个子问题,分别对应三个数据模型。
第一个是POI(兴趣点)模型。每个景点是一个对象,包含名字、城市、经纬度、建议游玩时长、开放时间、热度评分、以及一个我自己定义的“兴趣权重”。这些字段大部分可以被行程算法直接使用。
第二个是交通模型。我需要知道任意两个POI之间的通行耗时,以及城市与城市之间的城际交通耗时。同一个城市内的通行,我用步行或地铁,取两者中更合理的值;跨城市则使用高铁或大巴的实际耗时,分城市对写死配置。其实一开始我想全自动调API,后来发现对这种单次使用的项目,手动配置几个“城市间通行矩阵”反而比接一堆接口更稳定。
第三个是Schedule模型,也就是行程结果本身。每天包含若干条目,每条记录begin_time、end_time、poi_id、travel_from_previous、walking_distance这些字段。注意,这里没有直接给每天做“流水账”,而是保留了一个结构化的对象,后续如果要改成地图展示或日历同步,直接序列化输出就行。
这三个模型定义完之后,后面的逻辑全部围绕它们来写。一个额外的体会是,建模阶段多花一个小时,后面调算法和调界面都省下两三个小时。尤其是POI模型的字段设计,决定了我能不能在后期加入“预约了票所以必须在某个时段进入”这种硬约束。
2.2 把“玩得舒服”翻译成目标函数
“玩得舒服”这句话听起来很主观,但如果要让算法去优化,就必须翻译成一个可计算的数值。我定义了一个综合得分,把每天行程的优劣量化:
- 行程得分 = 当天游玩景点的兴趣权重总和
- 疲劳惩罚 = 当天总步行时间 × 系数 + 跨城当天的交通时间 × 系数
- 时间浪费惩罚 = 到达景点后等待开放的时间总和 × 系数
每一步贪心选择的目标是:让累计得分减掉疲劳惩罚的数值最大。这个表达并不神秘,本质上就是带约束的路线规划问题。我不追求真正的全局最优解,因为景点数量不大时,贪心加局部修复已经足够好;一旦把“全天候实时天气”“临时排队时间”这些变量加进来,全局最优本身也无意义。
这就引出一个重要观点:行程规划算法的目标不是算出“数学上最优”,而是算出“我实际愿意照做的方案”。很多开发者做这类工具容易陷进优化算法的炫技里,用遗传算法、模拟退火去跑一个几十点的路径规划,最后跑出来的路径确实最短,但把开门时间差、午休节奏全忽略了,反而不实用。我的选择是用带约束的贪心为主,只是在生成整日计划之后做一轮“局部修复”,检查是否有明显违反开放时间或疲劳阈值的片段。
2.3 为什么选择“贪心贪心+约束修复”而不是更复杂的算法
这里多说一点选型逻辑。我有考虑过用经典的TSP(旅行商问题)模型去求解全行程,理论上把每天的景点走法当成TSP,用dynanmic programming或遗传算法找最优路径是完全可行的。但实际一推演就发现,TSP模型的假设是“点与点之间的距离是固定的”,而行程规划中,点与点之间的“距离”还要随开放时间、日期的不同而变化——比如某个景点周三提前关门,那它就不适合排在傍晚。这是时间窗约束下的路由问题,复杂度远高于普通TSP,对一个小型个人项目来说,做整套精确解法是完全不值得的。
贪心算法的核心逻辑其实相当朴素:在给定的“当前时间点”,从还没去的景点里选择一个“现在去最值得”的景点,然后更新当前时间,继续选下一个。关键在于“现在去最值得”的评估函数,要把景点的兴趣分数、过去那段交通时间、预计排队/步行时间、当前时段离闭馆时间还有多久全部折算进去。这个评估函数一旦调好,效果非常稳。
当然,纯贪心会有一个问题:前期选了一个高分景点,后期可能把整个下午卡死在低效率的交通上。所以我在每天的生成结束之后,加了一步“局部优化”:检查是否存在两个相邻景点可以交换顺序而不违反时间约束;如果交换之后总耗费缩短,就执行交换。这个操作类似一次邻居搜索,实现成本低,但能把大多数明显不合理的顺序修正过来。对个人项目来说,这套“贪心+局部修复”的组合已经性价比很高。
3. 实操拆解:从零搭出逐日行程规划器
3.1 技术栈与工程结构
因为只有我一个人开发,而且核心目标是快速可用,技术栈选得比较克制:Python做后端逻辑,FastAPI暴露接口,前端用了一个很简单的静态页面加地图展示。数据库压根没上,所有数据都在启动时加载到内存里,行程结果直接输出成JSON,然后前端渲染成时间线卡片。
目录结构大概是这样的:
itinerary_organizer/ ├── data/ │ ├── pois.csv │ └── city_config.json ├── core/ │ ├── models.py │ ├── distance.py │ ├── planner.py │ └── optimizer.py ├── api/ │ └── main.py ├── web/ │ ├── index.html │ └── itinerary.js └── scripts/ ├── fetch_pois.py └── test_run.py这个结构没什么惊艳的,但胜在清晰。data/放所有静态配置,core/放算法核心,scripts/放数据抓取和测试脚本,前后端分离。数据直接用CSV和JSON,过期不心疼,不需要上数据库。
3.2 POI数据获取:静态配置比实时API更靠谱
最开始我想全部用实时API拉数据,用了Google Places API试了一圈,发现几个问题:一是返回的数据质量参差不齐,很多小众景点的坐标偏移严重;二是开放时间的字段有大量缺失,有些景点干脆不返回;三是每分钟请求限制虽然初始额度免费,但对这个项目频繁测试时不友好。
后来我改为“半自动获取”:先用脚本从Overpass API拉取候选景点列表,然后人工核对并补充开放时间,最终固化到CSV里。这一步看似返祖,实际上非常高效。整个马德里、巴塞罗那、塞维利亚、格拉纳达的核心景点就那么三四十个,人工核对一次花不了半小时,换来的是后续算法运行时几乎不会踩“景点数据为空”的坑。
POI的CSV字段设计如下:
poi_id,city,name,lat,lng,interest_score,min_minutes,max_minutes,opening_hours,closed_days,note其中interest_score是我自己打的1到10的兴趣分,min_minutes和max_minutes是建议游玩时长的区间,opening_hours是每天的开放时间,closed_days是闭馆日。比如普拉多博物馆,周一闭馆,平日10:00-20:00开放,min_minutes给90,max_minutes给180。
这里有一个经验:即使是半自动数据准备,也要写脚本去拉初始数据,而不是手敲CSV。手敲一来容易出错,二来坐标精度没法保证。我写了个简单的fetch_pois.py脚本,用Overpass查询每个城市tourism=attraction的节点,导出名字和经纬度,再用一个标记脚本去补开放时间。
3.3 距离矩阵:用实际地图API还是用直线距离近似
距离计算是整个行程规划中最容易出问题的地方。如果只用直线距离乘一个固定系数来估算通行时间,在街道密集、人行道复杂的欧洲老城区会离谱得很,比如从巴塞罗那哥特区到对角线大道,直线距离看着近,实际走起来可能要多绕30%的路。
我在项目里做了两层设计:同一城市内的POI间通行,默认调用OpenRouteService的矩阵API,计算步行和公共交通两种模式的耗时,优先取时间短的那一种;如果API调用失败或是在脚本快速测试阶段,就回退到Haversine直线距离乘一个1.4的绕路系数再除以步行速度5公里/小时。
OpenRouteService的矩阵API一次能批量查询多个点之间的距离,对一个城市几十个POI来说,一次调用就能算出完整的距离矩阵,非常省事。它的免费额度对一个个人项目足够用了,而且支持步行、骑行、驾车、公共交通四种模式。需要说明的是,公共交通模式的数据在某些城市覆盖不全,所以我常用步行模式作为默认,毕竟欧洲城市里两三个景点之间步行是绝大部分情况下的最优解。
距离计算这一步是行程规划器的基础设施,跑一次生成一个distance_cache.json缓存下来。实际执行时,每次查询两个POI之间的耗时,先查字典,没有再计算并回填,避免重复计算。
3.4 逐日行程生成:贪心算法实现细节
逐日行程生成是这个项目的核心函数。我单独写了一个planner.py,具体流程可以概括为以下几步。
首先,按城市和分配给该城市的天数,把行程拆成城市段。比如14天行程:马德里4天,巴塞罗那3天,塞维利亚3天,格拉纳达2天,中间预留2天给城际交通和机动安排。这样一来,问题被从“14天大全局规划”拆成“4个城市的子规划加城市间衔接”,复杂度大幅下降。
然后,按城市逐日规划。每个城市的第一天,处理方式和其他天稍有不同:如果前一天有跨城移动,那么第一天的起始时间不是早上9点,而是到达时间加一个小时缓冲。比如从马德里坐高铁到巴塞罗那需2.5小时,如果早上8点出发,大约11点能到酒店,12点左右可以开始第一个景点。原计划里就保留了这个buffer。
接下来进入贪心主循环。核心伪代码如下:
def generate_daily_plan(city_pois, unvisited, start_time, max_walk_minutes, date): current_time = start_time schedule = [] total_walk = 0 while unvisited and current_time < END_OF_DAY: feasible = [] for poi in unvisited: travel_time = get_travel_time(prev_poi, poi) arrival_time = current_time + travel_time if not is_open(poi, date, arrival_time): continue # 不在开放时段内,跳过 remaining_capacity = 计算距离该景点当日关闭还有多久 visit_duration = clamp(poi.min_minutes, poi.max_minutes, remaining_capacity) if visit_duration <= 0: continue score = evaluate_poi(poi, travel_time, current_time, visit_duration) feasible.append((score, poi, arrival_time, visit_duration)) if not feasible: break # 当天实在选不出,结束 sorted_feasible = sorted(feasible, key=lambda x: -x[0]) next_poi = sorted_feasible[0][1] schedule.append(build_entry(next_poi, arrival_time, visit_duration)) unvisited.remove(next_poi) prev_poi = next_poi current_time += visit_duration + LUNCH_BUFFER_IF_NEEDED total_walk += get_walk_distance(prev_poi, next_poi) return schedule这里的evaluate_poi函数是整个贪心策略的灵魂。我给每个候选景点打分,分数构成包括兴趣分、距离惩罚、等待开放惩罚和“闭馆前剩余时间紧迫度”。公式概括如下:
def evaluate_poi(poi, travel_time, current_time, visit_duration): # 兴趣分:直接用我标的加权值 interest_score = poi.interest_score # 距离惩罚:交通时间越长,分数打折 travel_penalty = alpha * travel_time # 等待惩罚:如果还没开门,要等到开门,扣分 wait_time = max(0, poi.opening - current_time - travel_time) wait_penalty = beta * wait_time # 紧迫度奖励:如果再不去今天就没机会了,加分 urgency_bonus = gamma / max(1, poi.close_time - current_time - travel_time) return interest_score - travel_penalty - wait_penalty + urgency_bonus这组参数alpha、beta、gamma我是手动调的,后面会细讲调参过程。当前这个公式的直觉是:优先去高兴趣景点,但不要让遥远的低兴趣景点挤掉顺路的好景点;如果今天闭馆前只有两小时了,那优先去能赶上的高分景点。
生成完每天的行程后,进入“局部修复”阶段。这一步我遍历同一天内所有相邻景点对,尝试交换它们的访问顺序,如果交换后总通行时间降低且仍然满足开放时间约束,就执行交换;重复这样的邻居搜索直到没有可以改进的交换。这个local search逻辑虽然简单,但往往能把贪心产生的“绕路”纠正回来。
3.5 前端展示:让行程像聊天记录一样直观
算法算完,光输出JSON可不行,人没法直接照着执行。我做了个极简前端:按天分组展示时间线卡片,每张卡片包含时间区间、景点名称、建议游玩时长、从上一景点过来的交通方式和耗时。另外,每张时间线卡片下方叠加了一个小地图,用Leaflet展示当天的行走轨迹,红色线为步行,蓝色线为地铁。
前端是在本地跑的,没有上线到服务器,所以技术上用了最原始的fetch拉取/api/itinerary接口,然后渲染DOM。地图部分用了Leaflet加OpenStreetMap瓦片,没有任何复杂的状态管理框架。实际使用时,我会在手机浏览器里打开这个页面,然后按照卡片顺序往下走,每天早上看第一张卡片就知道去哪。
地图配置的核心代码长这样:
function drawDay(dayIndex, itinerary) { const container = document.getElementById(`day-${dayIndex}`); const map = L.map(container).setView(center, 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); const points = itinerary.map(item => item.position); L.polyline(points, {color: 'red', weight: 3}).addTo(map); }这段代码没什么复杂度,但它把“算法排出来的抽象计划”变成了“可跟随的地图路径”,这一步是让行程规划器真正能落地使用的关键。因为它从“读一个计划”变成“看看地图往哪走”,人的空间感知远比时间线数字更直观。
4. 实测调参:这6个坑我踩了一遍
4.1 开放时间处理不当,行程直接“开门营业”
这个坑是我踩得最深的一个。早期版本的is_open判断只看了“当前时间是否在开放时间区间内”,但这有个致命问题:如果一个景点允许的最晚进入时间是闭馆前半小时,而我的算法在闭馆前40分钟才把游客送达,虽然游客到达时景点还开着,但只剩10分钟可以看,基本等于白跑。
修正方法是给每个POI增加一个last_entry_offset字段,表示从闭馆时间往前推多少分钟是最后可以入场的时刻。普拉多博物馆这种大体量的,我设置成闭馆前60分钟;小教堂、美术馆这种参观需求不大的,设置成30分钟。在贪心选择阶段,除了判断arrival_time < close_time,还追加一个判断arrival_time <= close_time - last_entry_offset。这个改动让许多“理论上觉得能赶上”的行程,在实际执行时不至于白跑一趟。
4.2 距离矩阵用直线距离近似,被现实狠狠打脸
最开始为了快速跑通流程,我在距离计算里直接用Haversine公式,然后乘以1.4的绕路系数。这个近似在塞维利亚老城里表现还行,因为街道还算规整;但在巴塞罗那的哥特区、格拉纳达的阿尔拜辛区,效果惨不忍睹。阿尔拜辛区的地形上下起伏,步行路线的实际耗时可能比直线距离推测的多出60%以上。第一次实测行程那天,我从阿尔拜辛走到圣尼古拉斯观景台,Google预估12分钟,我的算法估算8分钟,结果呢?因为全是爬坡,我花了差不多20分钟,直接把后面一个预约的入场时间给耽误了。
后来我直接把距离模块升级成OpenRouteService矩阵API,这个API返回的是真实路网路径长度和预估步行时间,准确性高了一大截。代码里加了一个开关,USE_ROUTING_API = True,如果网络请求超时会自动回退到直线近似,但优先级绝对要保证真实路网优先。对旅行规划器来说,距离不准就是原罪,影响所有相邻景点的串联决策。
4.3 跨城日的“半天浪费”没有处理
最初版本的planner.py是无视日期的,每天固定从早上9点排到晚上7点,甚至跨城日也被当成完整的一天来做。结果第一版输出里,跨城那天居然排了三个景点,实际根本来不及。
修这个问题的思路是给每天的行程设置“起始时间偏移”和“可用时长上限”。跨城日的起始时间变成上一个城市出发时间加城际交通时间加酒店寄存行李时间,可用时长上限用max_capacity_hours控制。例如从马德里到巴塞罗那的高铁是8:00发车,11:00到,12:00入住寄存后,那天最多只能安排5.5小时的有效游玩时间。这个字段加进Schedule模型之后,输出结果明显贴近现实多了。
4.4 贪心参数的“玄学调参”,不如多跑几组对比
alpha、beta、gamma这几个参数,一开始我是凭感觉定的:alpha=1.2,beta=2.0,gamma=8。跑出来的行程倒也能看,但总觉得有些地方不对。比如马德里的一天,明明普拉多博物馆(兴趣分10)就在附近,却因为当天剩余时间很充足,紧迫度奖励低,算法反而先去了一个兴趣分只有6但更“顺路”的小景点,把普拉多留到了下午。
最后我没有靠脑子调参,而是写了一个简单的网格搜索脚本:把alpha的取值设为[0.8, 1.0, 1.2, 1.4],beta设为[1.5, 2.0, 2.5],gamma设为[5, 8, 11],穷举所有组合,对每个组合跑一趟完整行程,然后用“实际执行总耗时 + 覆盖景点数 + 估算疲劳”加权打分,选出最合适的参数组合。跑完之后发现,alpha=1.0, beta=2.0, gamma=8时,行程总分最高。这个结果和直觉基本一致,但用数据验证一遍,心里踏实很多。
4.5 景点“兴趣分”全凭感觉,结果被一小撮人的偏好绑架
我的兴趣分是按最主流的攻略偏好标定,比如普拉多博物馆、圣家堂、阿尔罕布拉宫都给了10分,一些小众博物馆只给了5分。但实际执行时发现,我本人对某些小众景点的兴趣其实远高于大众分数,比如塞维利亚的都市阳伞和格拉纳达的阿尔拜辛小巷,我个人非常喜欢,却被算法安排到了下午很晚的时段,游玩时间也被压缩。
这个问题的根源是:兴趣分本质上是纯主观的评估,因人而异。为了缓解,我在前端加了一个“兴趣分调节器”,允许在生成行程前调整每个景点的兴趣权重,系统直接按调整后的分数重新跑一遍规划。这比在算法里反复调参数高效得多——参数是全局的,而兴趣权重是每个景点独立的。
4.6 休息时间和午饭,绝不能当成“自动补齐”的隐形时间
初版行程规划器把每段游玩之间的休息时间算得特别理想:景点结束,下一个景点之间的交通时间之外,没有额外的“喘气”时间。结果真实执行时,几乎每天到下午三四点就累得不行,后两个景点要么草草打卡,要么干脆放弃。
修改方案是在一天的中间强制插入一个午休/缓冲block。具体地,每天排完主行程后,扫描时间段,如果从当天开始到下午1点到2点之间没有超过20分钟的空隙,就在1点到2点之间自动插入一段1小时的“午餐休息”。同时,每个下午的行程最多连续排三个景点,之后必须插入一个至少15分钟的“自由休息”。这个规则是固定策略,不是靠参数调出来的,所以几乎不影响性能,但让行程的“可执行性”大幅提升。
5. 真实效果对比:算法排的行程,跟我手动排的差多少
5.1 同一个14天行程,两种方式对比
为了验证这个项目的价值,我做了个枯燥但很有说服力的对比:同样是14天、同样的城市组合和酒店位置,一份是之前手动规划的旧版本,一份是算法生成的行程,两份都按“可实际执行”的标准去校验。
对比结果见下表:
| 对比项 | 手动规划 | 算法规划 |
|---|---|---|
| 覆盖景点数量 | 22个 | 27个 |
| 累计步行距离 | 约86公里 | 约74公里 |
| 违反开放时间的次数 | 2次 | 0次 |
| 日均高峰后的休息时间 | 无意识安排,常忘 | 每天固定午休1小时+下午15分钟休息 |
| 跨城衔接不佳导致的空档 | 3次 | 1次 |
| 规划耗时 | 约3天 | 脚本运行约5分钟 |
从数据上看,算法的优势主要在开放时间约束和跨城衔接上。手动规划两天后,脑子基本记不清楚每个景点的周二闭馆时间,但算法可以在零点几秒内过滤掉这些不可行安排。而且算法规划的行程在步行总距离上少了12公里,主要原因是距离矩阵和贪心目标函数始终倾向于“顺路”,而手动规划更多是“我想去这里,所以排这天”。
5.2 实际踏上巴塞罗那街头,体验没有翻车
规划只是纸面的,真正的考试在实地。我按照算法生成的行程,从马德里一路走到格拉纳达,总体执行度很高。最惊喜的是在巴塞罗那的第二天,算法把圣家堂、米拉之家和巴特罗之家按顺序排在同一条路线上,上午去圣家堂,下午沿着格拉西亚大道看高迪的两栋建筑,最后在落日时分走到感恩区的小巷子里喝一杯。这套安排完全符合巴塞罗那老城区的步行逻辑,几乎不用走回头路。
当然也有一两处小瑕疵。比如在塞维利亚,算法把西班牙广场排在了第二天早上十点多,但当地很多攻略建议傍晚去,因为夕阳时分的光线非常美。这种“时刻美感”是算法完全不可能感知的,因为我的评分系统里没有一个“最佳观赏时段”的字段。后来我在POI数据里增加了一个best_time_tag字段,暂时只做展示不做约束,算是给未来版本留的口子。
5.3 给这个项目一个客观的自我评价
说真的,做完这个项目,我不会认为我的算法能替代一个非常懂当地的城市向导。它擅长的是处理约束、优化顺序、避免低级错误,但在“理解城市气质”这件事上依然无能为力。它不会知道巴塞罗那的博盖利亚市场应该空着肚子去,也不会知道马德里的丽池公园应该在划船之后再去逛水晶宫。
但反过来说,以前我做行程规划踩过的坑,这个系统帮我填掉了大半。那些“周一闭馆导致白跑一趟”“跨城日排了四个景点最后只来得及去俩”的低级失误,在我实际旅行中一次都没有发生。对于一个工具类项目来说,这已经值回票价。
6. 后续这波扩展,我建议你优先做这三件事
6.1 接入地图POI数据源,自动生成候选景点
现在项目的候选POI是我手动整理收集的,如果换一个城市,就需要重新拉起脚本跑一遍Overpass查询,再人工核对。这已经能解决大多数情况,但如果要长期复用,我建议把数据获取步骤再往前推一步:直接支持输入城市名自动生成候选POI列表,自动调用Nominatim解析城市坐标,再调用Overpass抓取景点,最后只留一个人工确认的环节。
6.2 增加“重规划”接口,支持临时调整
真实旅行中,计划赶不上变化是常态——某个景点临时关闭、某天突然下雨、和朋友临时约了顿饭。我的改进方向是给系统加一个“从当前时间重新规划”的接口,输入今天已经完成的景点和新增加的时间约束,算法自动把剩余行程重排一遍。这个功能在实现上并不难,核心就是把“未访问景点集合”重新喂给贪心初始化就行。
6.3 不同旅行节奏的模式化
现在项目的“疲劳阈值”是全局固定值,但不同旅行者的节奏差异非常大。有人喜欢每天睡到自然醒,一天只安排两个重头景点;有人是特种兵式打卡,一天恨不得塞满八个景点。我建议做一个“节奏预设”功能,按不同类型的游客调整max_walk_minutes、每日景点数上限、午休时长这三个参数。算法本身不用大改,只是参数输入方式变了而已。
最后分享一点私货
我做完这个自动行程规划器之后,最深的体会不是“算法多厉害”,而是“把一件日常琐事抽象成可计算的约束问题”这个过程本身,能给生活带来实实在在的收获。很多听起来只能靠经验、靠感觉的事情,其实都能通过建模拆解成可优化的目标。旅行规划如此,健身计划、学习计划、甚至采购清单,本质上也都是“在约束下安排顺序”的问题。
如果你也想做一个类似的工具,我的建议是:先把数据模型定义清楚,尤其是开放时间和距离计算这两个坑,一定不要偷懒省事;再去考虑算法花哨不花哨。行程规划软件的生存底线是“安排出来的行程真的走得通”,在这个基础上再谈优化和智能。以上,就是我这个“西班牙自动行程规划器”的完整复盘,希望能给你的下一个旅行项目带来一些灵感。