Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,7 +6,6 @@
|
|
| 6 |
# B) Variable heading & speed (beam search, BTO-first)
|
| 7 |
# C) Variable heading & speed (SE bias after 22:40) (beam search, BTO-first + tiny east bias only after 22:40)
|
| 8 |
# D) Variable heading, CONSTANT southern speed (443 kt) (beam search, BTO-first)
|
| 9 |
-
# E) Variable heading & speed (force BTO+BFO ~= 0 where measured) (segment-wise: BTO forced via speed-fit, BFO forced via heading root)
|
| 10 |
#
|
| 11 |
# Key behavior:
|
| 12 |
# - Last radar spot FIXED: 18:22:00Z @ 6.578N, 96.341E
|
|
@@ -16,7 +15,6 @@
|
|
| 16 |
#
|
| 17 |
# THIS VERSION (changes requested):
|
| 18 |
# - Mode D constant southern speed changed from 447 kt -> 443 kt
|
| 19 |
-
# - NEW Mode E: variable heading & speed trying to force BOTH BTO and BFO errors to 0 (where measured)
|
| 20 |
# - NEW Map feature: show lat/lon under mouse cursor (Leaflet MousePosition plugin)
|
| 21 |
|
| 22 |
import os
|
|
@@ -152,16 +150,6 @@ SE_EAST_DIVERGE_START_SEG_INDEX = 4 # seg 4 starts after 22:40
|
|
| 152 |
SE_VAR_EAST_DELTA_SET = [-8, -4, -2, 0, 2] # prefer small decreases (eastward) after 22:40
|
| 153 |
SE_EAST_BIAS_PENALTY_US_PER_DEG = 0.8 # small; BTO still dominates
|
| 154 |
|
| 155 |
-
# ===== NEW: "force BTO+BFO to 0" knobs =====
|
| 156 |
-
ZERO_H_SCAN_HALF_WINDOW_DEG = 14.0 # scan around previous heading for sign change
|
| 157 |
-
ZERO_H_SCAN_STEP_DEG = 0.5
|
| 158 |
-
ZERO_H_BISECT_ITERS = 22
|
| 159 |
-
ZERO_BTO_TOL_US = 0.2 # practical tolerance; speed-fit tries to hit near 0 already
|
| 160 |
-
ZERO_BFO_TOL_HZ = 0.02 # practical tolerance
|
| 161 |
-
ZERO_TURN_H_STEP_DEG = 2.0 # coarse grid for turn-end fit
|
| 162 |
-
ZERO_TURN_V_STEP_KT = 10.0 # coarse grid for turn speed
|
| 163 |
-
ZERO_SCORE_BFO_TO_BTO_US = 20.0 # convert 1 Hz ~ 20 us for scoring
|
| 164 |
-
|
| 165 |
# Search area colors (optional)
|
| 166 |
SEARCH_AREA_COLORS = {
|
| 167 |
"Ocean Infinity": "#ff3b3b",
|
|
@@ -1284,19 +1272,6 @@ def _bto_calc(method: str, lat: float, lon: float, dt_ping: datetime, alt_ft: fl
|
|
| 1284 |
return float(fp.calculateBTO_at_time(float(lat), float(lon), alt_m, dt_ping))
|
| 1285 |
|
| 1286 |
|
| 1287 |
-
def _bfo_calc(method: str, lat: float, lon: float, dt_ping: datetime, hdg: float, spd_kt: float,
|
| 1288 |
-
alt_ft: float, vs_fpm: float, start_dt: datetime, descent_start_dt: datetime | None):
|
| 1289 |
-
alt_now_ft = altitude_ft_at_dt(dt_ping, start_dt, float(alt_ft), float(vs_fpm), descent_start_dt)
|
| 1290 |
-
alt_m = alt_now_ft * 0.3048
|
| 1291 |
-
|
| 1292 |
-
vs_mps = float(vs_fpm) * 0.00508
|
| 1293 |
-
if descent_start_dt is not None and dt_ping < descent_start_dt:
|
| 1294 |
-
vs_mps = 0.0
|
| 1295 |
-
|
| 1296 |
-
spd_mps = float(spd_kt) * 1852.0 / 3600.0
|
| 1297 |
-
return float(fp.calculateBFO_at_time(float(spd_mps), float(hdg), float(lat), float(lon), float(alt_m), float(vs_mps), dt_ping))
|
| 1298 |
-
|
| 1299 |
-
|
| 1300 |
def _golden_min_abs(fn, lo: float, hi: float, iters: int = 24):
|
| 1301 |
grc = (math.sqrt(5.0) - 1.0) / 2.0
|
| 1302 |
a, b = float(lo), float(hi)
|
|
@@ -1351,7 +1326,7 @@ def _fit_segment_speed(
|
|
| 1351 |
for _ in range(26):
|
| 1352 |
m = 0.5 * (a + b)
|
| 1353 |
fm = float(err(m))
|
| 1354 |
-
if abs(fm) <
|
| 1355 |
a = b = m
|
| 1356 |
break
|
| 1357 |
if (fa < 0 and fm > 0) or (fa > 0 and fm < 0):
|
|
@@ -1951,246 +1926,6 @@ def solve_guided_constant_speed(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1951 |
return best_global_legs, "", start_dt
|
| 1952 |
|
| 1953 |
|
| 1954 |
-
# ================= NEW Guided Solver: Force BTO + BFO =================
|
| 1955 |
-
def _fit_heading_speed_force_bto_bfo(
|
| 1956 |
-
method: str,
|
| 1957 |
-
lat0: float,
|
| 1958 |
-
lon0: float,
|
| 1959 |
-
h_prev: float,
|
| 1960 |
-
minutes: float,
|
| 1961 |
-
dt_end: datetime,
|
| 1962 |
-
alt_ft: float,
|
| 1963 |
-
vs_fpm: float,
|
| 1964 |
-
start_dt: datetime,
|
| 1965 |
-
descent_start_dt: datetime | None,
|
| 1966 |
-
):
|
| 1967 |
-
# Measurements at dt_end
|
| 1968 |
-
bfo_meas, bto_meas = _lookup_measured_at_dt(dt_end)
|
| 1969 |
-
if dt_end == dt_0020_utc():
|
| 1970 |
-
try:
|
| 1971 |
-
bfo_meas = float(MEASURED_BFO_0020_HZ)
|
| 1972 |
-
except Exception:
|
| 1973 |
-
pass
|
| 1974 |
-
|
| 1975 |
-
have_bto = (bto_meas is not None)
|
| 1976 |
-
have_bfo = (bfo_meas is not None)
|
| 1977 |
-
|
| 1978 |
-
# If we can't force both, fallback to "BTO only" speed fit and keep heading near previous
|
| 1979 |
-
if not (have_bto and have_bfo):
|
| 1980 |
-
h_use = _clamp(float(h_prev), VAR_HEADING_MIN, VAR_HEADING_MAX)
|
| 1981 |
-
v_use, bto_err, (lat1, lon1) = _fit_segment_speed(
|
| 1982 |
-
method=method,
|
| 1983 |
-
lat0=float(lat0),
|
| 1984 |
-
lon0=float(lon0),
|
| 1985 |
-
hdg=float(h_use),
|
| 1986 |
-
minutes=float(minutes),
|
| 1987 |
-
dt_end=dt_end,
|
| 1988 |
-
alt_ft=alt_ft,
|
| 1989 |
-
vs_fpm=vs_fpm,
|
| 1990 |
-
start_dt=start_dt,
|
| 1991 |
-
descent_start_dt=descent_start_dt,
|
| 1992 |
-
vmin=SOUTH_SPEED_MIN_KT,
|
| 1993 |
-
vmax=SOUTH_SPEED_MAX_KT,
|
| 1994 |
-
)
|
| 1995 |
-
bfo_err = float("nan")
|
| 1996 |
-
if have_bfo:
|
| 1997 |
-
bfo_calc = _bfo_calc(method, lat1, lon1, dt_end, h_use, v_use, alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 1998 |
-
bfo_err = float(bfo_calc - float(bfo_meas))
|
| 1999 |
-
return float(h_use), float(v_use), float(bto_err), float(bfo_err), (float(lat1), float(lon1))
|
| 2000 |
-
|
| 2001 |
-
bto_meas = float(bto_meas)
|
| 2002 |
-
bfo_meas = float(bfo_meas)
|
| 2003 |
-
|
| 2004 |
-
# g(h) = BFO error after choosing v(h) that enforces BTO (via speed-fit)
|
| 2005 |
-
def eval_at_h(h):
|
| 2006 |
-
h = _clamp(float(h), VAR_HEADING_MIN, VAR_HEADING_MAX)
|
| 2007 |
-
v, bto_err, (lat1, lon1) = _fit_segment_speed(
|
| 2008 |
-
method=method,
|
| 2009 |
-
lat0=float(lat0),
|
| 2010 |
-
lon0=float(lon0),
|
| 2011 |
-
hdg=float(h),
|
| 2012 |
-
minutes=float(minutes),
|
| 2013 |
-
dt_end=dt_end,
|
| 2014 |
-
alt_ft=alt_ft,
|
| 2015 |
-
vs_fpm=vs_fpm,
|
| 2016 |
-
start_dt=start_dt,
|
| 2017 |
-
descent_start_dt=descent_start_dt,
|
| 2018 |
-
vmin=SOUTH_SPEED_MIN_KT,
|
| 2019 |
-
vmax=SOUTH_SPEED_MAX_KT,
|
| 2020 |
-
)
|
| 2021 |
-
bfo_calc = _bfo_calc(method, lat1, lon1, dt_end, h, v, alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 2022 |
-
return float(bfo_calc - bfo_meas), float(v), float(bto_err), (float(lat1), float(lon1))
|
| 2023 |
-
|
| 2024 |
-
# Scan around previous heading for sign change (so we can bisection to drive BFO error -> 0)
|
| 2025 |
-
h_center = _clamp(float(h_prev), VAR_HEADING_MIN, VAR_HEADING_MAX)
|
| 2026 |
-
h_lo = _clamp(h_center - float(ZERO_H_SCAN_HALF_WINDOW_DEG), VAR_HEADING_MIN, VAR_HEADING_MAX)
|
| 2027 |
-
h_hi = _clamp(h_center + float(ZERO_H_SCAN_HALF_WINDOW_DEG), VAR_HEADING_MIN, VAR_HEADING_MAX)
|
| 2028 |
-
hs = _frange(h_lo, h_hi, ZERO_H_SCAN_STEP_DEG)
|
| 2029 |
-
if not hs:
|
| 2030 |
-
hs = [h_center]
|
| 2031 |
-
|
| 2032 |
-
vals = []
|
| 2033 |
-
for h in hs:
|
| 2034 |
-
be, v, bto_e, (la, lo2) = eval_at_h(h)
|
| 2035 |
-
vals.append((h, be, v, bto_e, la, lo2))
|
| 2036 |
-
|
| 2037 |
-
# Try to find bracket with sign change
|
| 2038 |
-
bracket = None
|
| 2039 |
-
for i in range(len(vals) - 1):
|
| 2040 |
-
h0, e0, *_ = vals[i]
|
| 2041 |
-
h1, e1, *_ = vals[i + 1]
|
| 2042 |
-
if (e0 == 0.0) or (e1 == 0.0):
|
| 2043 |
-
bracket = (h0, h0) if abs(e0) <= abs(e1) else (h1, h1)
|
| 2044 |
-
break
|
| 2045 |
-
if (e0 < 0 and e1 > 0) or (e0 > 0 and e1 < 0):
|
| 2046 |
-
bracket = (h0, h1)
|
| 2047 |
-
break
|
| 2048 |
-
|
| 2049 |
-
if bracket is None:
|
| 2050 |
-
# No sign change -> pick best abs error from scan, optionally refine by golden min abs
|
| 2051 |
-
best = min(vals, key=lambda t: abs(float(t[1])))
|
| 2052 |
-
h_best = float(best[0])
|
| 2053 |
-
# small local refine by golden around neighbors if possible
|
| 2054 |
-
h_l = _clamp(h_best - 2.0, h_lo, h_hi)
|
| 2055 |
-
h_r = _clamp(h_best + 2.0, h_lo, h_hi)
|
| 2056 |
-
|
| 2057 |
-
def abs_bfo(h):
|
| 2058 |
-
return abs(eval_at_h(h)[0])
|
| 2059 |
-
|
| 2060 |
-
if h_r - h_l > 1e-6:
|
| 2061 |
-
h_best, _ = _golden_min_abs(lambda x: abs_bfo(x), h_l, h_r, iters=14)
|
| 2062 |
-
|
| 2063 |
-
bfo_err, v_best, bto_err, (lat1, lon1) = eval_at_h(h_best)
|
| 2064 |
-
return float(h_best), float(v_best), float(bto_err), float(bfo_err), (float(lat1), float(lon1))
|
| 2065 |
-
|
| 2066 |
-
a, b = bracket
|
| 2067 |
-
if abs(b - a) < 1e-9:
|
| 2068 |
-
bfo_err, v_best, bto_err, (lat1, lon1) = eval_at_h(a)
|
| 2069 |
-
return float(a), float(v_best), float(bto_err), float(bfo_err), (float(lat1), float(lon1))
|
| 2070 |
-
|
| 2071 |
-
fa, *_ = eval_at_h(a)
|
| 2072 |
-
fb, *_ = eval_at_h(b)
|
| 2073 |
-
|
| 2074 |
-
# Bisection on heading to drive BFO error to 0
|
| 2075 |
-
left, right = float(a), float(b)
|
| 2076 |
-
fL, fR = float(fa), float(fb)
|
| 2077 |
-
best_h = left
|
| 2078 |
-
best_abs = abs(fL)
|
| 2079 |
-
|
| 2080 |
-
for _ in range(int(ZERO_H_BISECT_ITERS)):
|
| 2081 |
-
mid = 0.5 * (left + right)
|
| 2082 |
-
fM, vM, btoM, (laM, loM) = eval_at_h(mid)
|
| 2083 |
-
|
| 2084 |
-
if abs(float(fM)) < best_abs:
|
| 2085 |
-
best_abs = abs(float(fM))
|
| 2086 |
-
best_h = float(mid)
|
| 2087 |
-
|
| 2088 |
-
if abs(float(fM)) <= float(ZERO_BFO_TOL_HZ):
|
| 2089 |
-
best_h = float(mid)
|
| 2090 |
-
break
|
| 2091 |
-
|
| 2092 |
-
if (fL < 0 and fM > 0) or (fL > 0 and fM < 0):
|
| 2093 |
-
right, fR = float(mid), float(fM)
|
| 2094 |
-
else:
|
| 2095 |
-
left, fL = float(mid), float(fM)
|
| 2096 |
-
|
| 2097 |
-
bfo_err, v_best, bto_err, (lat1, lon1) = eval_at_h(best_h)
|
| 2098 |
-
return float(best_h), float(v_best), float(bto_err), float(bfo_err), (float(lat1), float(lon1))
|
| 2099 |
-
|
| 2100 |
-
|
| 2101 |
-
def solve_guided_force_bto_bfo(method, alt_ft, vs_fpm, descent_start_choice):
|
| 2102 |
-
start_dt = _guided_start_dt()
|
| 2103 |
-
end_dt = _dt_from_label(GUIDED_FINAL_LABEL)
|
| 2104 |
-
descent_start_dt = _descent_start_dt_from_choice(descent_start_choice)
|
| 2105 |
-
|
| 2106 |
-
# ---- Step 1: choose (v_turn, h1) to best match BOTH BTO & BFO at 18:40 end-of-turn (best-effort)
|
| 2107 |
-
dt_1840 = TURN_COMPLETE_TIME_UTC
|
| 2108 |
-
bfo_meas_1840, bto_meas_1840 = _lookup_measured_at_dt(dt_1840)
|
| 2109 |
-
|
| 2110 |
-
# If missing, fall back to a reasonable default search like variable mode start
|
| 2111 |
-
have_turn_meas = (bto_meas_1840 is not None) and (bfo_meas_1840 is not None)
|
| 2112 |
-
if dt_1840 == dt_0020_utc():
|
| 2113 |
-
try:
|
| 2114 |
-
bfo_meas_1840 = float(MEASURED_BFO_0020_HZ)
|
| 2115 |
-
except Exception:
|
| 2116 |
-
pass
|
| 2117 |
-
|
| 2118 |
-
v_candidates = list(np.arange(TURN_SPEED_MIN_KT, TURN_SPEED_MAX_KT + 1e-9, float(ZERO_TURN_V_STEP_KT)))
|
| 2119 |
-
h_candidates = list(np.arange(VAR_HEADING_MIN, VAR_HEADING_MAX + 1e-9, float(ZERO_TURN_H_STEP_DEG)))
|
| 2120 |
-
|
| 2121 |
-
best_score = None
|
| 2122 |
-
best_turn = None # (v_turn, h1, turn_legs, turn_end_latlon)
|
| 2123 |
-
|
| 2124 |
-
if not have_turn_meas:
|
| 2125 |
-
# just pick something consistent with your previous modes
|
| 2126 |
-
for v_turn in v_candidates:
|
| 2127 |
-
for h1 in h_candidates:
|
| 2128 |
-
turn_legs, _ = build_turn_legs(float(v_turn), method, float(h1))
|
| 2129 |
-
pts_turn, _, _ = simulate_path(RADAR_FIX_LAT, RADAR_FIX_LON, turn_legs, method)
|
| 2130 |
-
lat0, lon0 = float(pts_turn[-1][0]), float(pts_turn[-1][1])
|
| 2131 |
-
score = 0.0
|
| 2132 |
-
if best_score is None or score < best_score:
|
| 2133 |
-
best_score = score
|
| 2134 |
-
best_turn = (float(v_turn), float(h1), turn_legs, (lat0, lon0))
|
| 2135 |
-
else:
|
| 2136 |
-
bto_meas_1840 = float(bto_meas_1840)
|
| 2137 |
-
bfo_meas_1840 = float(bfo_meas_1840)
|
| 2138 |
-
|
| 2139 |
-
for v_turn in v_candidates:
|
| 2140 |
-
for h1 in h_candidates:
|
| 2141 |
-
turn_legs, _ = build_turn_legs(float(v_turn), method, float(h1))
|
| 2142 |
-
pts_turn, _, _ = simulate_path(RADAR_FIX_LAT, RADAR_FIX_LON, turn_legs, method)
|
| 2143 |
-
lat0, lon0 = float(pts_turn[-1][0]), float(pts_turn[-1][1])
|
| 2144 |
-
|
| 2145 |
-
bto_calc = _bto_calc(method, lat0, lon0, dt_1840, alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 2146 |
-
bfo_calc = _bfo_calc(method, lat0, lon0, dt_1840, float(h1), float(v_turn), alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 2147 |
-
|
| 2148 |
-
bto_err = float(bto_calc - bto_meas_1840)
|
| 2149 |
-
bfo_err = float(bfo_calc - bfo_meas_1840)
|
| 2150 |
-
|
| 2151 |
-
score = abs(bto_err) + float(ZERO_SCORE_BFO_TO_BTO_US) * abs(bfo_err)
|
| 2152 |
-
|
| 2153 |
-
if best_score is None or score < best_score:
|
| 2154 |
-
best_score = score
|
| 2155 |
-
best_turn = (float(v_turn), float(h1), turn_legs, (lat0, lon0))
|
| 2156 |
-
|
| 2157 |
-
if best_turn is None:
|
| 2158 |
-
return None, "Guided solve: Force BTO+BFO could not initialize turn.", None
|
| 2159 |
-
|
| 2160 |
-
v_turn_best, h1_best, turn_legs_best, (lat_cur, lon_cur) = best_turn
|
| 2161 |
-
|
| 2162 |
-
# ---- Step 2: sequentially solve each south segment to force BTO (via speed) and BFO (via heading)
|
| 2163 |
-
headings = []
|
| 2164 |
-
speeds = []
|
| 2165 |
-
h_prev = float(h1_best)
|
| 2166 |
-
|
| 2167 |
-
for mins, end_lbl in zip(GUIDED_SOUTH_SEG_MIN, GUIDED_SOUTH_SEG_LABELS_END):
|
| 2168 |
-
dt_end = _dt_from_label(end_lbl)
|
| 2169 |
-
|
| 2170 |
-
h_seg, v_seg, bto_err, bfo_err, (lat1, lon1) = _fit_heading_speed_force_bto_bfo(
|
| 2171 |
-
method=method,
|
| 2172 |
-
lat0=float(lat_cur),
|
| 2173 |
-
lon0=float(lon_cur),
|
| 2174 |
-
h_prev=float(h_prev),
|
| 2175 |
-
minutes=float(mins),
|
| 2176 |
-
dt_end=dt_end,
|
| 2177 |
-
alt_ft=alt_ft,
|
| 2178 |
-
vs_fpm=vs_fpm,
|
| 2179 |
-
start_dt=start_dt,
|
| 2180 |
-
descent_start_dt=descent_start_dt,
|
| 2181 |
-
)
|
| 2182 |
-
|
| 2183 |
-
headings.append(float(h_seg))
|
| 2184 |
-
speeds.append(float(v_seg))
|
| 2185 |
-
lat_cur, lon_cur = float(lat1), float(lon1)
|
| 2186 |
-
h_prev = float(h_seg)
|
| 2187 |
-
|
| 2188 |
-
south_legs = _build_south_legs_variable(headings, speeds, GUIDED_SOUTH_SEG_MIN)
|
| 2189 |
-
full_legs = clip_legs_to_end_dt(turn_legs_best + south_legs, start_dt, end_dt)
|
| 2190 |
-
|
| 2191 |
-
return full_legs, "", start_dt
|
| 2192 |
-
|
| 2193 |
-
|
| 2194 |
# ================= RUNNERS =================
|
| 2195 |
def _run_common(start_lat, start_lon, legs, method, start_dt, alt_ft, vs_fpm, descent_start_choice):
|
| 2196 |
if start_dt == RADAR_FIX_TIME_UTC:
|
|
@@ -2239,7 +1974,8 @@ def run_guided(guided_mode, start_lat, start_lon, method, alt_ft, vs_fpm, descen
|
|
| 2239 |
elif guided_mode.startswith("Variable heading (constant"):
|
| 2240 |
legs, _report_md, start_dt = solve_guided_constant_speed(method, alt_ft, vs_fpm, descent_start_choice)
|
| 2241 |
else:
|
| 2242 |
-
|
|
|
|
| 2243 |
|
| 2244 |
if legs is None:
|
| 2245 |
fig = go.Figure()
|
|
@@ -2286,7 +2022,6 @@ with gr.Blocks() as demo:
|
|
| 2286 |
"Variable heading & speed",
|
| 2287 |
"Variable heading & speed (SE after 22:40)",
|
| 2288 |
f"Variable heading (constant {CONST_SOUTH_SPEED_KT:.0f} kt)",
|
| 2289 |
-
"Variable heading & speed (force BTO+BFO = 0)",
|
| 2290 |
],
|
| 2291 |
value="Straight heading",
|
| 2292 |
label="Guided scenario",
|
|
|
|
| 6 |
# B) Variable heading & speed (beam search, BTO-first)
|
| 7 |
# C) Variable heading & speed (SE bias after 22:40) (beam search, BTO-first + tiny east bias only after 22:40)
|
| 8 |
# D) Variable heading, CONSTANT southern speed (443 kt) (beam search, BTO-first)
|
|
|
|
| 9 |
#
|
| 10 |
# Key behavior:
|
| 11 |
# - Last radar spot FIXED: 18:22:00Z @ 6.578N, 96.341E
|
|
|
|
| 15 |
#
|
| 16 |
# THIS VERSION (changes requested):
|
| 17 |
# - Mode D constant southern speed changed from 447 kt -> 443 kt
|
|
|
|
| 18 |
# - NEW Map feature: show lat/lon under mouse cursor (Leaflet MousePosition plugin)
|
| 19 |
|
| 20 |
import os
|
|
|
|
| 150 |
SE_VAR_EAST_DELTA_SET = [-8, -4, -2, 0, 2] # prefer small decreases (eastward) after 22:40
|
| 151 |
SE_EAST_BIAS_PENALTY_US_PER_DEG = 0.8 # small; BTO still dominates
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
# Search area colors (optional)
|
| 154 |
SEARCH_AREA_COLORS = {
|
| 155 |
"Ocean Infinity": "#ff3b3b",
|
|
|
|
| 1272 |
return float(fp.calculateBTO_at_time(float(lat), float(lon), alt_m, dt_ping))
|
| 1273 |
|
| 1274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1275 |
def _golden_min_abs(fn, lo: float, hi: float, iters: int = 24):
|
| 1276 |
grc = (math.sqrt(5.0) - 1.0) / 2.0
|
| 1277 |
a, b = float(lo), float(hi)
|
|
|
|
| 1326 |
for _ in range(26):
|
| 1327 |
m = 0.5 * (a + b)
|
| 1328 |
fm = float(err(m))
|
| 1329 |
+
if abs(fm) < 0.2:
|
| 1330 |
a = b = m
|
| 1331 |
break
|
| 1332 |
if (fa < 0 and fm > 0) or (fa > 0 and fm < 0):
|
|
|
|
| 1926 |
return best_global_legs, "", start_dt
|
| 1927 |
|
| 1928 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1929 |
# ================= RUNNERS =================
|
| 1930 |
def _run_common(start_lat, start_lon, legs, method, start_dt, alt_ft, vs_fpm, descent_start_choice):
|
| 1931 |
if start_dt == RADAR_FIX_TIME_UTC:
|
|
|
|
| 1974 |
elif guided_mode.startswith("Variable heading (constant"):
|
| 1975 |
legs, _report_md, start_dt = solve_guided_constant_speed(method, alt_ft, vs_fpm, descent_start_choice)
|
| 1976 |
else:
|
| 1977 |
+
# Safety fallback: if an unknown label appears, just run Straight heading.
|
| 1978 |
+
legs, _report_md, start_dt = solve_guided_straight(method, alt_ft, vs_fpm, descent_start_choice)
|
| 1979 |
|
| 1980 |
if legs is None:
|
| 1981 |
fig = go.Figure()
|
|
|
|
| 2022 |
"Variable heading & speed",
|
| 2023 |
"Variable heading & speed (SE after 22:40)",
|
| 2024 |
f"Variable heading (constant {CONST_SOUTH_SPEED_KT:.0f} kt)",
|
|
|
|
| 2025 |
],
|
| 2026 |
value="Straight heading",
|
| 2027 |
label="Guided scenario",
|