michalis13/ship-tracker
0
1"""Voyage routing: πεδίο κόστους θάλασσας + A* -> βέλτιστη (μπλε) πορεία.2Η στεριά (NaN στα marine δεδομένα) γίνεται αυτόματα αδιάβατη."""3import heapq4import numpy as np5 6IMPASSABLE = 1e67 8 9def make_grid(bbox, n):10 """bbox = (lat_min, lon_min, lat_max, lon_max). Σειρά 0 = βορράς (πάνω)."""11 lat_min, lon_min, lat_max, lon_max = bbox12 lats = np.linspace(lat_max, lat_min, n)13 lons = np.linspace(lon_min, lon_max, n)14 return lats, lons15 16 17def ll_to_idx(lat, lon, lats, lons):18 return int(np.argmin(np.abs(lats - lat))), int(np.argmin(np.abs(lons - lon)))19 20 21def idx_to_ll(path, lats, lons):22 return [[float(lons[c]), float(lats[r])] for r, c in path] # [lon, lat] για pydeck23 24 25def _norm(a):26 a = np.array(a, float)27 finite = a[np.isfinite(a)]28 if finite.size == 0:29 return np.zeros_like(a)30 lo, hi = finite.min(), finite.max()31 if hi - lo < 1e-9:32 return np.zeros_like(a)33 return (a - lo) / (hi - lo)34 35 36def build_cost_grid(wave, current, w_wave=1.0, w_curr=0.5):37 """Συνδυάζει κύμα + ρεύμα σε κόστος ανά κελί. NaN (στεριά) -> αδιάβατο."""38 wave = np.array(wave, float)39 current = np.array(current, float)40 land = ~np.isfinite(wave) | ~np.isfinite(current)41 cost = 1.0 + w_wave * np.nan_to_num(_norm(wave)) + w_curr * np.nan_to_num(_norm(current))42 cost[land] = IMPASSABLE43 return cost44 45 46def astar(cost, start, goal):47 """8-κατευθύνσεων A*. Αποφεύγει αδιάβατα κελιά. Επιστρέφει (path, total_cost)."""48 H, W = cost.shape49 cmin = max(cost[cost < IMPASSABLE].min(), 1e-6)50 gr, gc = goal51 52 def h(r, c):53 return np.hypot(r - gr, c - gc) * cmin54 55 open_h = [(h(*start), 0.0, start)]56 came, gscore, seen = {}, {start: 0.0}, set()57 nbrs = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1)]58 59 while open_h:60 _, g, cur = heapq.heappop(open_h)61 if cur in seen:62 continue63 seen.add(cur)64 if cur == goal:65 path = [cur]66 while cur in came:67 cur = came[cur]; path.append(cur)68 return path[::-1], g69 r, c = cur70 for dr, dc in nbrs:71 nr, nc = r + dr, c + dc72 if not (0 <= nr < H and 0 <= nc < W):73 continue74 if cost[nr, nc] >= IMPASSABLE:75 continue76 ng = g + cost[nr, nc] * np.hypot(dr, dc)77 if ng < gscore.get((nr, nc), 1e18):78 gscore[(nr, nc)] = ng79 came[(nr, nc)] = cur80 heapq.heappush(open_h, (ng + h(nr, nc), ng, (nr, nc)))81 return None, float("inf")82 83 84def path_cost(cost, path):85 total = 0.086 for (r0, c0), (r1, c1) in zip(path[:-1], path[1:]):87 cell = cost[r1, c1]88 if cell >= IMPASSABLE:89 cell = 50 # ποινή αν η πραγματική πορεία ακούμπησε "στεριά"/άκυρο90 total += cell * np.hypot(r1 - r0, c1 - c0)91 return total92 93 94def straight_idx(start, goal, n=160):95 rs = np.linspace(start[0], goal[0], n).round().astype(int)96 cs = np.linspace(start[1], goal[1], n).round().astype(int)97 return list(dict.fromkeys(zip(rs.tolist(), cs.tolist())))98 