use crate::range_cache::SegmentRangeTables;
use crate::spherical::{LatLon, Segment, destination_point, haversine, initial_bearing};
use crate::units::Path;
use crate::{LandmassSource, Sailboat, WindSource};
pub(crate) fn get_travel_time_range<SB: Sailboat, WS: WindSource>(
sailboat: &SB,
wind_source: &WS,
segment: Segment,
) -> (f64, f64) {
let a = sailboat.get_travel_time(wind_source, segment, 1.0);
let (_, b) = get_worst_travel_time(sailboat, wind_source, segment);
(a.min(b), a.max(b))
}
pub(crate) fn get_worst_travel_time<SB: Sailboat, WS: WindSource>(
sailboat: &SB,
wind_source: &WS,
segment: Segment,
) -> (f64, f64) {
let t = sailboat.get_travel_time(wind_source, segment, 0.0);
if t.is_infinite() {
let t = sailboat.get_travel_time(wind_source, segment, 0.1);
(0.1, t)
} else {
(0.0, t)
}
}
pub(crate) fn solve_mcr_01<SB: Sailboat, WS: WindSource>(
sailboat: &SB,
wind_source: &WS,
segment: Segment,
arrival_time: f64,
) -> f64 {
if haversine(segment.origin, segment.destination) <= 0.0 {
return 0.0;
}
let delta_time = arrival_time - segment.origin_time;
let best_time = sailboat.get_travel_time(wind_source, segment, 1.0);
let (worst_mcr, worst_time) = get_worst_travel_time(sailboat, wind_source, segment);
if delta_time <= best_time {
return 1.0;
}
if delta_time >= worst_time {
return worst_mcr;
}
const MAX_ITER: usize = 12;
let mut lo = worst_mcr;
let mut hi = 1.0;
for _ in 0..MAX_ITER {
let mid = 0.5 * (lo + hi);
let t = sailboat.get_travel_time(wind_source, segment, mid);
if t > delta_time {
lo = mid;
} else {
hi = mid;
}
}
0.5 * (lo + hi)
}
pub(crate) fn get_segment_metrics<SB: Sailboat, WS: WindSource>(
sailboat: &SB,
wind_source: &WS,
segment: Segment,
arrival_time: f64,
) -> (f64, f64) {
let mcr_01 = solve_mcr_01(sailboat, wind_source, segment, arrival_time);
let fuel = sailboat.get_fuel_consumed(mcr_01, arrival_time - segment.origin_time);
(mcr_01, fuel)
}
pub(crate) fn solve_mcr_01_cached<const N: usize>(
tables: &SegmentRangeTables<N>,
seg_i: usize,
origin: LatLon,
destination: LatLon,
departure_time: f64,
arrival_time: f64,
) -> f64 {
if haversine(origin, destination) <= 0.0 {
return 0.0;
}
tables.query_mcr_for_delta_time(seg_i, departure_time, arrival_time - departure_time)
}
pub fn get_segment_land_metres<LS: LandmassSource>(
landmass: &LS,
origin: LatLon,
destination: LatLon,
step_distance_max: f64,
) -> f64 {
let total_distance = haversine(origin, destination);
if total_distance <= 0.0 {
return 0.0;
}
let step_count = (total_distance / step_distance_max).ceil().max(1.0) as usize;
let step_distance = total_distance / step_count as f64;
let mut position = origin;
let mut land_metres = 0.0;
for _ in 0..step_count {
let Some(bearing) = initial_bearing(position, destination) else {
return f64::INFINITY;
};
let Some(mid) = destination_point(position, step_distance * 0.5, bearing) else {
return f64::INFINITY;
};
if landmass.is_land(mid) {
land_metres += step_distance;
}
let Some(next) = destination_point(position, step_distance, bearing) else {
return f64::INFINITY;
};
position = next;
}
land_metres
}
pub(crate) fn get_segment_metrics_cached<SB: Sailboat, const N: usize>(
sailboat: &SB,
tables: &SegmentRangeTables<N>,
seg_i: usize,
origin: LatLon,
destination: LatLon,
departure_time: f64,
arrival_time: f64,
) -> (f64, f64) {
let mcr_01 = solve_mcr_01_cached(
tables,
seg_i,
origin,
destination,
departure_time,
arrival_time,
);
let fuel = sailboat.get_fuel_consumed(mcr_01, arrival_time - departure_time);
(mcr_01, fuel)
}
pub fn get_segment_fuel_and_time<const N: usize, SB: Sailboat, WS: WindSource>(
sailboat: &SB,
wind_source: &WS,
path: Path<N>,
departure_time: f64,
step_distance_max: f64,
) -> Vec<(f64, f64, f64)> {
path.iter_with_running_clock(departure_time)
.map(|seg| {
let segment = Segment {
origin: seg.origin,
destination: seg.destination,
origin_time: seg.t_depart,
step_distance_max,
};
let (mcr_01, fuel) = get_segment_metrics(sailboat, wind_source, segment, seg.t_arrive);
(mcr_01, fuel, seg.segment_time)
})
.collect()
}