Spaces:
Running
Running
File size: 5,234 Bytes
b7d08cf 25c25cb b7d08cf 25c25cb b7d08cf 25c25cb b7d08cf 25c25cb b7d08cf |
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
"""
圖構建器 - 構建 OR-Tools 需要的圖結構
完全保留原始 tsptw_solver_old.py 的圖構建邏輯
"""
from typing import List, Dict, Tuple, Any
import numpy as np
from src.infra.logger import get_logger
from src.services.local_route_estimator import LocalRouteEstimator
from ..models.internal_models import _Task, _Location, _Graph
logger = get_logger(__name__)
class GraphBuilder:
"""
圖構建器
職責:
- 收集所有地點(depot + POI candidates)
- 構建節點元數據(type, task_id, poi_id, time_window...)
- 計算距離/時間矩陣
"""
def __init__(self, **kwargs):
self.estimator = LocalRouteEstimator()
def build_graph(
self,
start_location: _Location,
tasks: List[_Task],
travel_mode="DRIVE",
) -> _Graph:
"""
構建完整的圖
完全保留原始邏輯:
- _build_locations_and_meta()
- _build_service_time_per_node()
- compute_route_matrix()
Returns:
_Graph: 包含 node_meta, locations, duration_matrix, distance_matrix
"""
# 1. 構建位置列表和節點元數據
locations, node_meta = self._build_locations_and_meta(
start_location, tasks
)
num_nodes = len(locations)
logger.info(f"GraphBuilder: {num_nodes} nodes (1 depot + {num_nodes - 1} POIs)")
# 2. 計算距離/時間矩陣
if num_nodes <= 1:
# 只有 depot,沒有 POI
duration_matrix = np.zeros((1, 1), dtype=int)
distance_matrix = np.zeros((1, 1), dtype=int)
else:
duration_matrix, distance_matrix = self._calculate_matrices(locations)
# 3. 返回圖
return _Graph(
node_meta=node_meta,
locations=locations,
duration_matrix=duration_matrix,
distance_matrix=distance_matrix,
)
@staticmethod
def _build_locations_and_meta(
start_location: _Location,
tasks: List[_Task],
) -> Tuple[List[Dict[str, float]], List[Dict[str, Any]]]:
"""
構建位置列表和節點元數據
完全保留原始邏輯: _build_locations_and_meta()
Returns:
locations: [{"lat": float, "lng": float}, ...]
node_meta: [{"type": "depot"}, {"type": "poi", ...}, ...]
"""
locations: List[Dict[str, float]] = []
node_meta: List[Dict[str, Any]] = []
# depot
locations.append({"lat": start_location.lat, "lng": start_location.lng})
node_meta.append({"type": "depot"})
# candidate POIs
for task_idx, task in enumerate(tasks):
for cand_idx, cand in enumerate(task.candidates):
lat = cand.lat
lng = cand.lng
time_windows = cand.time_windows
if time_windows is None:
if cand.time_window is not None:
time_windows = [cand.time_window]
else:
time_windows = [None]
for interval_idx, tw in enumerate(time_windows):
locations.append({"lat": lat, "lng": lng})
node_meta.append(
{
"type": "poi",
"task_id": task.task_id,
"poi_id": cand.poi_id,
"task_idx": task_idx,
"candidate_idx": cand_idx,
"interval_idx": interval_idx,
"poi_time_window": tw,
}
)
return locations, node_meta
def _calculate_matrices(
self,
locations: List[Dict[str, float]],
) -> Tuple[np.ndarray, np.ndarray]:
"""
計算距離/時間矩陣
完全保留原始邏輯: 調用 gmaps.compute_route_matrix()
Returns:
(duration_matrix, distance_matrix): 兩個 numpy array
"""
locations_dict = [{"lat": loc["lat"], "lng": loc["lng"]} for loc in locations]
compute_route_result = self.estimator.compute_route_matrix(
origins=locations_dict,
destinations=locations_dict)
duration_matrix = np.array(compute_route_result["duration_matrix"])
distance_matrix = np.array(compute_route_result["distance_matrix"])
return duration_matrix, distance_matrix
@staticmethod
def build_service_time_per_node(
tasks: List[_Task],
node_meta: List[Dict[str, Any]],
) -> List[int]:
"""
構建每個節點的服務時間(秒)
完全保留原始邏輯: _build_service_time_per_node()
Returns:
service_time: [0, service_sec, service_sec, ...]
"""
service_time = [0] * len(node_meta)
for node, meta in enumerate(node_meta):
if meta["type"] == "poi":
task_idx = meta["task_idx"]
task = tasks[task_idx]
service_time[node] = task.service_duration_sec
return service_time |