File size: 3,375 Bytes
7034378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9066e49
7034378
 
9066e49
7034378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import numpy as np
from typing import List, Dict, Optional
from datetime import datetime
import logging

logger = logging.getLogger(__name__)


class LocalRouteEstimator:
    """
    一個本地的距離與時間估算器,用於取代昂貴的 Google Distance Matrix API。
    使用 Haversine 公式 + 曲折係數進行估算。
    """
    # 曲折係數 (Circuity Factor): 直線距離 x 此係數 = 估算道路距離
    # 一般城市道路約為 1.3 ~ 1.5
    TORTUOSITY_FACTOR = 1.55

    # 預設行駛速度 (km/h) -> 轉換為 m/s
    AVERAGE_SPEED_KMH = 32.5
    AVERAGE_SPEED_MPS = AVERAGE_SPEED_KMH * 1000 / 3600

    def compute_route_matrix(
            self,
            origins: List[Dict[str, float]],
            destinations: List[Dict[str, float]],
            *args, **kwargs
    ) -> Dict[str, np.ndarray]:
        """
        本地版矩陣計算 (Drop-in replacement for Google API)

        完全不發送 HTTP 請求,純數學計算。
        """
        O_len = len(origins)
        D_len = len(destinations)

        logger.info(
            f"🧮 Computing LOCAL route matrix: {O_len}×{D_len} "
            f"(Speed assumed: {self.AVERAGE_SPEED_KMH} km/h)"
        )

        # 1. 準備座標數據 (轉成 numpy array)
        # origins structure: [{'lat': x, 'lng': y}, ...]
        o_lats = np.array([o['lat'] for o in origins], dtype=np.float64)
        o_lngs = np.array([o['lng'] for o in origins], dtype=np.float64)

        d_lats = np.array([d['lat'] for d in destinations], dtype=np.float64)
        d_lngs = np.array([d['lng'] for d in destinations], dtype=np.float64)

        # 2. 向量化 Haversine 計算 (利用 Broadcasting 建立矩陣)
        # 將維度調整為 (O, 1) 和 (1, D) 以便廣播運算
        lat1 = np.radians(o_lats)[:, np.newaxis]
        lon1 = np.radians(o_lngs)[:, np.newaxis]
        lat2 = np.radians(d_lats)[np.newaxis, :]
        lon2 = np.radians(d_lngs)[np.newaxis, :]

        # Haversine 公式
        dlon = lon2 - lon1
        dlat = lat2 - lat1
        a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
        c = 2 * np.arcsin(np.sqrt(a))
        r = 6371000  # 地球半徑 (公尺)

        # 得到直線距離 (Great Circle Distance)
        straight_distance = c * r

        # 3. 估算道路距離 (Road Distance)
        # 乘以曲折係數
        estimated_distance = straight_distance * self.TORTUOSITY_FACTOR

        # 4. 估算時間 (Duration)
        # 時間 = 距離 / 速度
        # 為了避免除以零,我們假設最小時間
        estimated_duration = estimated_distance / self.AVERAGE_SPEED_MPS

        # 轉換為 int32 (與 Google API 回傳格式保持一致)
        distance_matrix = estimated_distance.astype(np.int32)
        duration_matrix = estimated_duration.astype(np.int32)

        # 5. 自我對角線處理 (如果是同一個點,距離時間設為 0)
        # 只有當 origins 和 destinations 列表完全一樣時才需要檢查
        # 這裡簡單處理:如果距離極小 (< 10米),設為 0
        mask = distance_matrix < 10
        distance_matrix[mask] = 0
        duration_matrix[mask] = 0

        logger.debug("✅ Local matrix computation done.")

        return {
            "duration_matrix": duration_matrix,
            "distance_matrix": distance_matrix,
        }