Marco310 commited on
Commit
7034378
·
1 Parent(s): 25c25cb

hotfix: replace Google Maps API with local estimation in optimizer

Browse files

- Disabled Google Maps `Compute Routes` and `Distance Matrix` APIs in the optimization loop to prevent billing overages.
- Swapped the underlying algorithm with a local Haversine-based distance estimator.
- This is an emergency fix to resolve the high API usage incident.

src/services/local_route_estimator.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from typing import List, Dict, Optional
3
+ from datetime import datetime
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class LocalRouteEstimator:
10
+ """
11
+ 一個本地的距離與時間估算器,用於取代昂貴的 Google Distance Matrix API。
12
+ 使用 Haversine 公式 + 曲折係數進行估算。
13
+ """
14
+ # 曲折係數 (Circuity Factor): 直線距離 x 此係數 = 估算道路距離
15
+ # 一般城市道路約為 1.3 ~ 1.5
16
+ TORTUOSITY_FACTOR = 1.4
17
+
18
+ # 預設行駛速度 (km/h) -> 轉換為 m/s
19
+ AVERAGE_SPEED_KMH = 35.0
20
+ AVERAGE_SPEED_MPS = AVERAGE_SPEED_KMH * 1000 / 3600
21
+
22
+ def compute_route_matrix(
23
+ self,
24
+ origins: List[Dict[str, float]],
25
+ destinations: List[Dict[str, float]],
26
+ *args, **kwargs
27
+ ) -> Dict[str, np.ndarray]:
28
+ """
29
+ 本地版矩陣計算 (Drop-in replacement for Google API)
30
+
31
+ 完全不發送 HTTP 請求,純數學計算。
32
+ """
33
+ O_len = len(origins)
34
+ D_len = len(destinations)
35
+
36
+ logger.info(
37
+ f"🧮 Computing LOCAL route matrix: {O_len}×{D_len} "
38
+ f"(Speed assumed: {self.AVERAGE_SPEED_KMH} km/h)"
39
+ )
40
+
41
+ # 1. 準備座標數據 (轉成 numpy array)
42
+ # origins structure: [{'lat': x, 'lng': y}, ...]
43
+ o_lats = np.array([o['lat'] for o in origins], dtype=np.float64)
44
+ o_lngs = np.array([o['lng'] for o in origins], dtype=np.float64)
45
+
46
+ d_lats = np.array([d['lat'] for d in destinations], dtype=np.float64)
47
+ d_lngs = np.array([d['lng'] for d in destinations], dtype=np.float64)
48
+
49
+ # 2. 向量化 Haversine 計算 (利用 Broadcasting 建立矩陣)
50
+ # 將維度調整為 (O, 1) 和 (1, D) 以便廣播運算
51
+ lat1 = np.radians(o_lats)[:, np.newaxis]
52
+ lon1 = np.radians(o_lngs)[:, np.newaxis]
53
+ lat2 = np.radians(d_lats)[np.newaxis, :]
54
+ lon2 = np.radians(d_lngs)[np.newaxis, :]
55
+
56
+ # Haversine 公式
57
+ dlon = lon2 - lon1
58
+ dlat = lat2 - lat1
59
+ a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
60
+ c = 2 * np.arcsin(np.sqrt(a))
61
+ r = 6371000 # 地球半徑 (公尺)
62
+
63
+ # 得到直線距離 (Great Circle Distance)
64
+ straight_distance = c * r
65
+
66
+ # 3. 估算道路距離 (Road Distance)
67
+ # 乘以曲折係數
68
+ estimated_distance = straight_distance * self.TORTUOSITY_FACTOR
69
+
70
+ # 4. 估算時間 (Duration)
71
+ # 時間 = 距離 / 速度
72
+ # 為了避免除以零,我們假設最小時間
73
+ estimated_duration = estimated_distance / self.AVERAGE_SPEED_MPS
74
+
75
+ # 轉換為 int32 (與 Google API 回傳格式保持一致)
76
+ distance_matrix = estimated_distance.astype(np.int32)
77
+ duration_matrix = estimated_duration.astype(np.int32)
78
+
79
+ # 5. 自我對角線處理 (如果是同一個點,距離時間設為 0)
80
+ # 只有當 origins 和 destinations 列表完全一樣時才需要檢查
81
+ # 這裡簡單處理:如果距離極小 (< 10米),設為 0
82
+ mask = distance_matrix < 10
83
+ distance_matrix[mask] = 0
84
+ duration_matrix[mask] = 0
85
+
86
+ logger.debug("✅ Local matrix computation done.")
87
+
88
+ return {
89
+ "duration_matrix": duration_matrix,
90
+ "distance_matrix": distance_matrix,
91
+ }