Marco310 commited on
Commit
9f2e319
·
1 Parent(s): 182cdaf

"feat(viz): add alt distance and stop rating to map popups"

Browse files
Files changed (1) hide show
  1. core/visualizers.py +49 -21
core/visualizers.py CHANGED
@@ -152,7 +152,7 @@ def create_animated_map(structured_data=None):
152
  if "lat" in sl and "lng" in sl:
153
  center_lat, center_lon = sl["lat"], sl["lng"]
154
 
155
- # 🔥 Map修正 1: height="100%" 讓它自動填滿父容器 (ui/theme.py 定義的 650px)
156
  m = folium.Map(location=[center_lat, center_lon], zoom_start=13, tiles="OpenStreetMap",
157
  height="100%",
158
  width="100%"
@@ -173,11 +173,24 @@ def create_animated_map(structured_data=None):
173
 
174
  index_to_name = {stop.get("stop_index"): stop.get("location") for stop in timeline}
175
 
176
- poi_id_to_name = {}
 
177
  for t in raw_tasks:
178
  for cand in t.get("candidates", []):
179
- if cand.get("poi_id"):
180
- poi_id_to_name[cand["poi_id"]] = cand.get("name")
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  task_id_to_seq = {}
183
  for r in route_info:
@@ -240,21 +253,17 @@ def create_animated_map(structured_data=None):
240
 
241
  route_group.add_to(m)
242
 
243
- # --- Layer 2: 備用方案 (分組修正) ---
244
- # 🔥 Map修正 2: 不再使用單一的 alt_group,而是為每個任務建立獨立的 FeatureGroup
245
-
246
  for idx, task in enumerate(tasks_detail):
247
  tid = task.get("task_id")
248
  step_seq = task_id_to_seq.get(tid, 0)
249
 
250
- # 取得該任務的主題色
251
  theme_idx = step_seq % len(THEMES)
252
  theme_color, theme_name = THEMES[theme_idx]
253
 
254
  chosen = task.get("chosen_poi", {})
255
  alternatives = task.get("alternative_pois", [])
256
 
257
- # 如果沒有備選點,就跳過
258
  if not chosen or not alternatives:
259
  continue
260
 
@@ -262,11 +271,10 @@ def create_animated_map(structured_data=None):
262
  if not center_lat or not center_lng:
263
  continue
264
 
265
- # 取得主地點名稱 (用於圖層標籤)
266
- chosen_name = poi_id_to_name.get(chosen.get("poi_id"), f"Task {idx + 1}")
 
267
 
268
- # 🔥 建立該任務專屬的 Group,並預設顯示 (show=True)
269
- # 圖層名稱範例: "↳ Alt: Taipei 101"
270
  specific_alt_group = folium.FeatureGroup(
271
  name=f"↳ Alt: {chosen_name}",
272
  show=True
@@ -277,14 +285,17 @@ def create_animated_map(structured_data=None):
277
  if alat and alng:
278
  bounds.append([alat, alng])
279
 
280
- # 虛線連接
281
  folium.PolyLine(
282
  locations=[[center_lat, center_lng], [alat, alng]],
283
  color=theme_color, weight=2, dash_array='5, 5', opacity=0.5
284
  ).add_to(specific_alt_group)
285
 
286
- poi_name = poi_id_to_name.get(alt.get("poi_id"), "Alternative Option")
 
 
287
  extra_min = alt.get("delta_travel_time_min", 0)
 
 
288
 
289
  popup_html = create_popup_html(
290
  title="ALTERNATIVE",
@@ -292,6 +303,7 @@ def create_animated_map(structured_data=None):
292
  color="gray",
293
  metrics={
294
  "Add. Time": f"+{extra_min} min",
 
295
  },
296
  is_alternative=True
297
  )
@@ -303,7 +315,6 @@ def create_animated_map(structured_data=None):
303
  tooltip=f"Alt: {poi_name}"
304
  ).add_to(specific_alt_group)
305
 
306
- # 將這個任務的專屬 Group 加入地圖
307
  specific_alt_group.add_to(m)
308
 
309
  # --- Layer 3: 主要站點 ---
@@ -312,6 +323,7 @@ def create_animated_map(structured_data=None):
312
  for i, stop in enumerate(timeline):
313
  coords = stop.get("coordinates", {})
314
  lat, lng = coords.get("lat"), coords.get("lng")
 
315
 
316
  if lat and lng:
317
  bounds.append([lat, lng])
@@ -319,15 +331,31 @@ def create_animated_map(structured_data=None):
319
  color_code, theme_name = THEMES[theme_idx]
320
  loc_name = stop.get("location", "")
321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  popup_html = create_popup_html(
323
  title=f"STOP {i + 1}",
324
  subtitle=loc_name,
325
  color=theme_name,
326
- metrics={
327
- "Arrival": stop.get("time", ""),
328
- "Weather": stop.get("weather", "").split(',')[0],
329
- "AQI": stop.get("aqi", {}).get("label", "").split(' ')[-1]
330
- }
331
  )
332
 
333
  if i == 0:
 
152
  if "lat" in sl and "lng" in sl:
153
  center_lat, center_lon = sl["lat"], sl["lng"]
154
 
155
+ # 🔥 Map修正 1: height="100%" 讓它自動填滿父容器
156
  m = folium.Map(location=[center_lat, center_lon], zoom_start=13, tiles="OpenStreetMap",
157
  height="100%",
158
  width="100%"
 
173
 
174
  index_to_name = {stop.get("stop_index"): stop.get("location") for stop in timeline}
175
 
176
+ # 🔥 Data Prep 1: 建立 POI 詳細資訊對照表 (包含 Name 和 Rating)
177
+ poi_ref = {}
178
  for t in raw_tasks:
179
  for cand in t.get("candidates", []):
180
+ pid = cand.get("poi_id")
181
+ if pid:
182
+ poi_ref[pid] = {
183
+ "name": cand.get("name"),
184
+ "rating": cand.get("rating")
185
+ }
186
+
187
+ # 🔥 Data Prep 2: 建立 Step 到 POI ID 的對照表 (用於將 Timeline 連結到 Rating)
188
+ step_to_poi_id = {}
189
+ for r in route_info:
190
+ step_id = r.get("step")
191
+ pid = r.get("poi_id")
192
+ if step_id is not None and pid:
193
+ step_to_poi_id[step_id] = pid
194
 
195
  task_id_to_seq = {}
196
  for r in route_info:
 
253
 
254
  route_group.add_to(m)
255
 
256
+ # --- Layer 2: 備用方案 (FeatureGroup 分組) ---
 
 
257
  for idx, task in enumerate(tasks_detail):
258
  tid = task.get("task_id")
259
  step_seq = task_id_to_seq.get(tid, 0)
260
 
 
261
  theme_idx = step_seq % len(THEMES)
262
  theme_color, theme_name = THEMES[theme_idx]
263
 
264
  chosen = task.get("chosen_poi", {})
265
  alternatives = task.get("alternative_pois", [])
266
 
 
267
  if not chosen or not alternatives:
268
  continue
269
 
 
271
  if not center_lat or not center_lng:
272
  continue
273
 
274
+ # 取得主地點名稱
275
+ chosen_pid = chosen.get("poi_id")
276
+ chosen_name = poi_ref.get(chosen_pid, {}).get("name", f"Task {idx + 1}")
277
 
 
 
278
  specific_alt_group = folium.FeatureGroup(
279
  name=f"↳ Alt: {chosen_name}",
280
  show=True
 
285
  if alat and alng:
286
  bounds.append([alat, alng])
287
 
 
288
  folium.PolyLine(
289
  locations=[[center_lat, center_lng], [alat, alng]],
290
  color=theme_color, weight=2, dash_array='5, 5', opacity=0.5
291
  ).add_to(specific_alt_group)
292
 
293
+ alt_pid = alt.get("poi_id")
294
+ poi_name = poi_ref.get(alt_pid, {}).get("name", "Alternative Option")
295
+
296
  extra_min = alt.get("delta_travel_time_min", 0)
297
+ # 🔥 更新:獲取 delta distance (注意 JSON key 是 'm' 而非 'meters')
298
+ extra_dist = alt.get("delta_travel_distance_m", 0)
299
 
300
  popup_html = create_popup_html(
301
  title="ALTERNATIVE",
 
303
  color="gray",
304
  metrics={
305
  "Add. Time": f"+{extra_min} min",
306
+ "Add. Dist": f"+{extra_dist} m" # 🔥 新增距離顯示
307
  },
308
  is_alternative=True
309
  )
 
315
  tooltip=f"Alt: {poi_name}"
316
  ).add_to(specific_alt_group)
317
 
 
318
  specific_alt_group.add_to(m)
319
 
320
  # --- Layer 3: 主要站點 ---
 
323
  for i, stop in enumerate(timeline):
324
  coords = stop.get("coordinates", {})
325
  lat, lng = coords.get("lat"), coords.get("lng")
326
+ stop_idx = stop.get("stop_index")
327
 
328
  if lat and lng:
329
  bounds.append([lat, lng])
 
331
  color_code, theme_name = THEMES[theme_idx]
332
  loc_name = stop.get("location", "")
333
 
334
+ # 🔥 更新:透過 stop_index -> route -> poi_id -> rating 獲取評分
335
+ rating_display = ""
336
+ if stop_idx is not None:
337
+ # 嘗試從 route map 找到 poi_id
338
+ pid = step_to_poi_id.get(stop_idx)
339
+ if pid and pid in poi_ref:
340
+ rating_val = poi_ref[pid].get("rating")
341
+ if rating_val:
342
+ rating_display = f"{rating_val} ⭐"
343
+
344
+ metrics_data = {
345
+ "Arrival": stop.get("time", ""),
346
+ "Weather": stop.get("weather", "").split(',')[0],
347
+ "AQI": stop.get("aqi", {}).get("label", "").split(' ')[-1]
348
+ }
349
+
350
+ # 🔥 新增 Rating 到 Metrics
351
+ if rating_display:
352
+ metrics_data["Rating"] = rating_display
353
+
354
  popup_html = create_popup_html(
355
  title=f"STOP {i + 1}",
356
  subtitle=loc_name,
357
  color=theme_name,
358
+ metrics=metrics_data
 
 
 
 
359
  )
360
 
361
  if i == 0: