diff --git a/gpx-rs/src/algorithm/smooth.rs b/gpx-rs/src/algorithm/smooth.rs index 636e6948b..9c520419c 100644 --- a/gpx-rs/src/algorithm/smooth.rs +++ b/gpx-rs/src/algorithm/smooth.rs @@ -8,43 +8,44 @@ macro_rules! for_each_window { |$a:ident, $b:ident| $distance:expr, |$i:ident, $l:ident, $r:ident| $body:block, ) => {{ - let mut start = $left.unwrap_or_default(); - let mut cur = $left; - while let Some(idx) = cur { + let mut window_start = $left.unwrap_or_default(); + let mut window_center = $left; + let mut window_end = $left.unwrap_or_default(); + while let Some(idx) = window_center { // advance left if needed - while start != idx && { - let $a = start; + while window_start != idx && { + let $a = window_start; let $b = idx; $distance } > $window { - if let Some(next) = $trkseg.next_index(Some(start)) { + if let Some(next) = $trkseg.next_index(Some(window_start)) { if next == idx { break; } - start = next; + window_start = next; } else { break; } } // advance right if needed - let mut end = idx; - while let Some(next) = $trkseg.next_index(Some(end)) { - if { + window_end = window_end.max(idx); + while let Some(next) = $trkseg.next_index(Some(window_end)) { + if window_end != idx && { let $a = idx; - let $b = end; + let $b = next; $distance } > $window { break; } - end = next; + window_end = next; } // apply body let $i = idx; - let $l = start; - let $r = end; + let $l = window_start; + let $r = window_end; $body // go next - cur = $trkseg.next_index(cur); + window_center = $trkseg.next_index(window_center); } }}; } diff --git a/gpx-rs/src/buffer/coordinates.rs b/gpx-rs/src/buffer/coordinates.rs new file mode 100644 index 000000000..25282d4fa --- /dev/null +++ b/gpx-rs/src/buffer/coordinates.rs @@ -0,0 +1,4 @@ +pub struct CoordinatesBuffer { + pub lng: Vec, + pub lat: Vec, +} diff --git a/gpx-rs/src/buffer/mod.rs b/gpx-rs/src/buffer/mod.rs new file mode 100644 index 000000000..88a55c55a --- /dev/null +++ b/gpx-rs/src/buffer/mod.rs @@ -0,0 +1,5 @@ +mod coordinates; +mod statistics; + +pub use coordinates::*; +pub use statistics::*; diff --git a/gpx-rs/src/statistics/buffer.rs b/gpx-rs/src/buffer/statistics.rs similarity index 92% rename from gpx-rs/src/statistics/buffer.rs rename to gpx-rs/src/buffer/statistics.rs index 89057d1f3..09c12528e 100644 --- a/gpx-rs/src/statistics/buffer.rs +++ b/gpx-rs/src/buffer/statistics.rs @@ -1,11 +1,11 @@ -use crate::statistics::{GPXStatistics, GlobalStatistics, sum_options}; +use crate::statistics::{GlobalStatistics, Statistics, sum_options}; #[derive(Debug, Default)] pub struct StatisticsBuffer { pub total_distance: Vec, pub moving_distance: Vec, - pub total_time: Vec, - pub moving_time: Vec, + pub total_time: Vec, + pub moving_time: Vec, pub speed: Vec, pub elevation_gain: Vec, pub elevation_loss: Vec, @@ -15,7 +15,7 @@ pub struct StatisticsBuffer { } impl StatisticsBuffer { - pub fn update(&mut self, stats: &[&GPXStatistics]) { + pub fn update(&mut self, stats: &[&Statistics]) { self.total_distance.clear(); self.moving_distance.clear(); self.total_time.clear(); diff --git a/gpx-rs/src/engine.rs b/gpx-rs/src/engine.rs index fd26c09dd..dc26b5e75 100644 --- a/gpx-rs/src/engine.rs +++ b/gpx-rs/src/engine.rs @@ -2,75 +2,151 @@ use std::rc::Rc; -use js_sys::Function; +use js_sys::{Float64Array, Function, Int32Array}; use uuid::Uuid; use wasm_bindgen::prelude::*; use crate::{ - gpx::GPXFile, + buffer::StatisticsBuffer, + gpx::{File, FileId}, io::parse, selection::Selection, state::{Stack, StackEntry}, - statistics::StatisticsBuffer, + statistics::StatisticsCache, }; +extern crate console_error_panic_hook; + +#[derive(Debug, Default)] #[wasm_bindgen] pub struct Engine { stack: Stack, selection: Selection, - statistics: StatisticsBuffer, - callback: Function, + statistics_cache: StatisticsCache, + statistics_buffer: StatisticsBuffer, + callback: Option, } #[wasm_bindgen] impl Engine { #[wasm_bindgen(constructor)] pub fn new(callback: Function) -> Self { + console_error_panic_hook::set_once(); Self { stack: Default::default(), selection: Default::default(), - statistics: Default::default(), - callback, + statistics_cache: Default::default(), + statistics_buffer: Default::default(), + callback: Some(callback), } } - #[wasm_bindgen] + pub fn total_distance(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.total_distance) } + } + + pub fn moving_distance(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.moving_distance) } + } + + pub fn total_time(&self) -> Int32Array { + unsafe { Int32Array::view(&self.statistics_buffer.total_time) } + } + + pub fn moving_time(&self) -> Int32Array { + unsafe { Int32Array::view(&self.statistics_buffer.moving_time) } + } + + pub fn speed(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.speed) } + } + + pub fn elevation_gain(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.elevation_gain) } + } + + pub fn elevation_loss(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.elevation_loss) } + } + + pub fn slope(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.slope) } + } + + pub fn slope_segment_slope(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.slope_segment_slope) } + } + + pub fn slope_segment_distance(&self) -> Float64Array { + unsafe { Float64Array::view(&self.statistics_buffer.slope_segment_distance) } + } + pub fn create_file(&mut self, name: &str) { - let mut file = GPXFile::default(); - file.info.name = name.to_string(); - self.stack.create_and_push_next(|entry: &mut StackEntry| { + self.event_loop(|entry| { + let mut file = File::default(); + file.info.name = name.to_string(); entry.insert(file.id, Rc::new(file)); true }); } - #[wasm_bindgen] pub fn load_file(&mut self, data: &[u8]) { - if let Ok(file) = parse(data) { - self.stack.create_and_push_next(|entry: &mut StackEntry| { + self.event_loop(|entry| { + if let Ok(file) = parse(data) { entry.insert(file.id, Rc::new(file)); true - }); - } - let _ = self.callback.call1(&JsValue::NULL, &JsValue::from("done!")); + } else { + false + } + }); } - #[wasm_bindgen] pub fn delete_file(&mut self, id: &[u8]) { - if let Ok(id) = Uuid::from_slice(id) { - self.stack - .create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some()); - } + self.event_loop(|entry| { + if let Ok(id) = Uuid::from_slice(id) { + let id = FileId(id); + entry.remove(&id).is_some() + } else { + false + } + }); } - fn event_loop(&mut self) { - // Control flow - // - get lock to cancel any other updates - // - update files - // - update separate statistics - // - update selection - // - update selection statistics - // self.statistics.update(stats); - // - notify UI through callback (changed files, updated selection, new stats) + fn event_loop(&mut self, f: F) + where + F: FnOnce(&mut StackEntry) -> bool, + { + if let Some(diff) = self.stack.create_and_push_next(f) { + self.selection.select(diff.added[0]); + self.statistics_cache.update(self.stack.current()); + self.statistics_buffer.update( + &self + .statistics_cache + .get(self.stack.current(), &self.selection), + ); + if let Some(c) = &self.callback { + let _ = c.call0(&JsValue::NULL); + } + } + } +} + +#[cfg(test)] +mod tests { + use std::{fs::File, io::Read}; + + use super::*; + + #[test] + fn test_load_file() { + let mut engine = Engine::default(); + + let mut f = File::open("data/simple.gpx").unwrap(); + let mut data = String::new(); + let _ = f.read_to_string(&mut data); + + engine.load_file(data.as_bytes()); + + assert_eq!(engine.statistics_buffer.total_distance.len(), 80); } } diff --git a/gpx-rs/src/gpx/chunk.rs b/gpx-rs/src/gpx/chunk.rs index 31b8a11d7..7d624ad09 100644 --- a/gpx-rs/src/gpx/chunk.rs +++ b/gpx-rs/src/gpx/chunk.rs @@ -4,19 +4,19 @@ use crate::gpx::{Trackpoint, Waypoint}; static MAX_TRKPT_CHUNK_SIZE: usize = 4096; -#[derive(Debug)] -pub struct TrackpointChunk { - pub id: Uuid, - pub trkpt: Vec, +#[derive(Debug, PartialEq, Eq)] +pub struct TrackpointChunkId(Uuid); + +impl Default for TrackpointChunkId { + fn default() -> Self { + Self(Uuid::new_v4()) + } } -impl Default for TrackpointChunk { - fn default() -> Self { - Self { - id: Uuid::new_v4(), - trkpt: Default::default(), - } - } +#[derive(Debug, Default)] +pub struct TrackpointChunk { + pub id: TrackpointChunkId, + pub trkpt: Vec, } impl PartialEq for TrackpointChunk { @@ -31,21 +31,21 @@ impl TrackpointChunk { } } -static MAX_WPT_CHUNK_SIZE: usize = 128; +#[derive(Debug, PartialEq, Eq)] +pub struct WaypointChunkId(Uuid); -#[derive(Debug)] -pub struct WaypointChunk { - pub id: Uuid, - pub wpt: Vec, +impl Default for WaypointChunkId { + fn default() -> Self { + Self(Uuid::new_v4()) + } } -impl Default for WaypointChunk { - fn default() -> Self { - Self { - id: Uuid::new_v4(), - wpt: Default::default(), - } - } +static MAX_WPT_CHUNK_SIZE: usize = 128; + +#[derive(Debug, Default)] +pub struct WaypointChunk { + pub id: WaypointChunkId, + pub wpt: Vec, } impl PartialEq for WaypointChunk { diff --git a/gpx-rs/src/gpx/file.rs b/gpx-rs/src/gpx/file.rs index 35459846a..8ccfcd8ef 100644 --- a/gpx-rs/src/gpx/file.rs +++ b/gpx-rs/src/gpx/file.rs @@ -4,28 +4,26 @@ use uuid::Uuid; use crate::gpx::{Link, Track, WaypointChunk}; -#[derive(Debug, PartialEq)] -pub struct GPXFile { - pub id: Uuid, - pub info: GPXFileInfo, +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FileId(pub Uuid); + +impl Default for FileId { + fn default() -> Self { + Self(Uuid::new_v4()) + } +} + +#[derive(Debug, Default, PartialEq)] +pub struct File { + pub id: FileId, + pub info: FileInfo, pub trk: Vec, pub wpt: Vec>, // TODO routes } -impl Default for GPXFile { - fn default() -> Self { - Self { - id: Uuid::new_v4(), - info: Default::default(), - trk: Default::default(), - wpt: Default::default(), - } - } -} - #[derive(Debug, Default, PartialEq)] -pub struct GPXFileInfo { +pub struct FileInfo { pub name: String, pub desc: Option, pub author: Option, diff --git a/gpx-rs/src/gpx/segment.rs b/gpx-rs/src/gpx/segment.rs index 50dd59723..7d6104d91 100644 --- a/gpx-rs/src/gpx/segment.rs +++ b/gpx-rs/src/gpx/segment.rs @@ -1,9 +1,31 @@ use std::{ops::Index, rc::Rc}; +use uuid::Uuid; + use crate::gpx::{Trackpoint, TrackpointChunk}; +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct TrackSegmentId(Uuid); + +impl Default for TrackSegmentId { + fn default() -> Self { + Self(Uuid::new_v4()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TrackSegmentRevisionId(Uuid); + +impl Default for TrackSegmentRevisionId { + fn default() -> Self { + Self(Uuid::new_v4()) + } +} + #[derive(Debug, Default, PartialEq)] pub struct TrackSegment { + pub id: TrackSegmentId, + pub rev_id: TrackSegmentRevisionId, chunks: Vec>, cumul_length: Vec, } diff --git a/gpx-rs/src/gpx/track.rs b/gpx-rs/src/gpx/track.rs index 86d1c761c..00f5f0a8f 100644 --- a/gpx-rs/src/gpx/track.rs +++ b/gpx-rs/src/gpx/track.rs @@ -1,7 +1,19 @@ +use uuid::Uuid; + use crate::gpx::{Link, TrackSegment}; +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct TrackId(Uuid); + +impl Default for TrackId { + fn default() -> Self { + Self(Uuid::new_v4()) + } +} + #[derive(Debug, Default, PartialEq)] pub struct Track { + pub id: TrackId, pub info: TrackInfo, pub trkseg: Vec, } diff --git a/gpx-rs/src/gpx/trackpoint.rs b/gpx-rs/src/gpx/trackpoint.rs index 35dac699c..d08800dff 100644 --- a/gpx-rs/src/gpx/trackpoint.rs +++ b/gpx-rs/src/gpx/trackpoint.rs @@ -11,9 +11,3 @@ pub struct Trackpoint { pub power: Option, // TODO OSM data? or store intervals at a higher level? } - -impl Trackpoint { - pub fn time_diff(&self, other: &Trackpoint) -> Option { - self.time.zip(other.time).map(|(t1, t2)| t1 - t2) - } -} diff --git a/gpx-rs/src/gpx/waypoint.rs b/gpx-rs/src/gpx/waypoint.rs index 9a2fc46d3..88b5ae335 100644 --- a/gpx-rs/src/gpx/waypoint.rs +++ b/gpx-rs/src/gpx/waypoint.rs @@ -1,7 +1,19 @@ +use uuid::Uuid; + use crate::gpx::{Link, LngLat}; +#[derive(Debug, PartialEq, Eq)] +pub struct WaypointId(Uuid); + +impl Default for WaypointId { + fn default() -> Self { + Self(Uuid::new_v4()) + } +} + #[derive(Debug, Default)] pub struct Waypoint { + pub id: WaypointId, pub coordinates: LngLat, pub ele: f64, pub time: Option, diff --git a/gpx-rs/src/io/parse.rs b/gpx-rs/src/io/parse.rs index 3f8bf285d..3f45f33ee 100644 --- a/gpx-rs/src/io/parse.rs +++ b/gpx-rs/src/io/parse.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use crate::gpx::{ - Author, GPXFile, Link, LngLat, Track, TrackSegment, Trackpoint, TrackpointChunk, Waypoint, + Author, File, Link, LngLat, Track, TrackSegment, Trackpoint, TrackpointChunk, Waypoint, WaypointChunk, }; use chrono::DateTime; @@ -50,10 +50,10 @@ fn parse_coordinates(attributes: Attributes<'_>) -> LngLat { coordinates } -pub fn parse(data: &[u8]) -> Result { +pub fn parse(data: &[u8]) -> Result { let mut reader = Reader::from_reader(data); let mut buf = vec![]; - let mut gpx = GPXFile::default(); + let mut gpx = File::default(); let mut stack: Vec = vec![]; let mut trkpt_chunk = TrackpointChunk::default(); let mut wpt_chunk = WaypointChunk::default(); diff --git a/gpx-rs/src/lib.rs b/gpx-rs/src/lib.rs index 11524e3e7..cd49cadf8 100644 --- a/gpx-rs/src/lib.rs +++ b/gpx-rs/src/lib.rs @@ -1,5 +1,6 @@ mod action; mod algorithm; +mod buffer; mod engine; mod gpx; mod io; @@ -7,3 +8,5 @@ mod selection; mod state; mod statistics; mod utils; + +pub use engine::*; diff --git a/gpx-rs/src/selection.rs b/gpx-rs/src/selection.rs index d37a56592..ef93e74f7 100644 --- a/gpx-rs/src/selection.rs +++ b/gpx-rs/src/selection.rs @@ -1,29 +1,34 @@ use std::collections::HashSet; -use uuid::Uuid; +use crate::gpx::{FileId, TrackId, TrackSegmentId, WaypointId}; #[derive(Debug)] pub struct FileSelection { - files: HashSet, + pub files: HashSet, } #[derive(Debug)] pub struct TrackSelection { - file: Uuid, - trk: HashSet, + pub file: FileId, + pub trk: HashSet, } #[derive(Debug)] pub struct TrackSegmentSelection { - file: Uuid, - trk: Uuid, - trkseg: HashSet, + pub file: FileId, + pub trk: TrackId, + pub trkseg: HashSet, +} + +#[derive(Debug)] +pub struct WaypointsSelection { + pub file: FileId, } #[derive(Debug)] pub struct WaypointSelection { - file: Uuid, - wpt: HashSet, + pub file: FileId, + pub wpt: HashSet, } #[derive(Debug, Default)] @@ -33,6 +38,14 @@ pub enum Selection { File(FileSelection), Track(TrackSelection), TrackSegment(TrackSegmentSelection), - Waypoints, + Waypoints(WaypointsSelection), Waypoint(WaypointSelection), } + +impl Selection { + pub fn select(&mut self, id: FileId) { + let mut files = HashSet::default(); + files.insert(id); + *self = Selection::File(FileSelection { files }); + } +} diff --git a/gpx-rs/src/state/stack.rs b/gpx-rs/src/state/stack.rs index 7f0bec870..e70c121d7 100644 --- a/gpx-rs/src/state/stack.rs +++ b/gpx-rs/src/state/stack.rs @@ -3,11 +3,9 @@ use std::{ rc::Rc, }; -use uuid::Uuid; +use crate::gpx::{File, FileId}; -use crate::gpx::GPXFile; - -#[derive(Default)] +#[derive(Debug, Default)] pub struct Stack { entries: Vec, index: Option, @@ -99,8 +97,8 @@ impl Stack { } let prev = prev.map_or_default(|i| self.get(i)); let cur = cur.map_or_default(|i| self.get(i)); - let prev_ids: HashSet = prev.map_or_default(|e| e.keys().copied().collect()); - let cur_ids: HashSet = cur.map_or_default(|e| e.keys().copied().collect()); + let prev_ids: HashSet = prev.map_or_default(|e| e.keys().copied().collect()); + let cur_ids: HashSet = cur.map_or_default(|e| e.keys().copied().collect()); let mut modified = vec![]; for id in prev_ids.intersection(&cur_ids) { let before = prev.map_or_default(|e| e.get(id)); @@ -117,13 +115,13 @@ impl Stack { } } -pub type StackEntry = HashMap>; +pub type StackEntry = HashMap>; #[derive(Debug, Default)] pub struct Diff { - pub added: Vec, - pub removed: Vec, - pub modified: Vec, + pub added: Vec, + pub removed: Vec, + pub modified: Vec, } #[cfg(test)] @@ -139,7 +137,7 @@ mod tests { assert!(stack.current().is_none()); let diff = stack.create_and_push_next(|e| { - let file = Rc::new(GPXFile::default()); + let file = Rc::new(File::default()); e.insert(file.id, file); true }); diff --git a/gpx-rs/src/statistics/cache.rs b/gpx-rs/src/statistics/cache.rs new file mode 100644 index 000000000..adfcb7078 --- /dev/null +++ b/gpx-rs/src/statistics/cache.rs @@ -0,0 +1,113 @@ +use std::{ + collections::{HashMap, HashSet, hash_map::Entry}, + rc::Rc, +}; + +use crate::{ + gpx::{File, Track, TrackSegment, TrackSegmentRevisionId}, + selection::Selection, + state::StackEntry, + statistics::Statistics, +}; + +#[derive(Debug, Default)] +pub struct StatisticsCache { + map: HashMap, +} + +impl StatisticsCache { + pub fn update(&mut self, state: Option<&StackEntry>) { + let mut stale: HashSet = self.map.keys().copied().collect(); + if let Some(state) = state { + for file in state.values() { + for trk in file.trk.iter() { + for trkseg in trk.trkseg.iter() { + match self.map.entry(trkseg.rev_id) { + Entry::Occupied(_) => { + stale.remove(&trkseg.rev_id); + } + Entry::Vacant(e) => { + e.insert(Statistics::compute(trkseg)); + } + } + } + } + } + } + for id in stale.iter() { + self.map.remove(id); + } + } + + pub fn get<'a>( + &'a self, + state: Option<&StackEntry>, + selection: &Selection, + ) -> Vec<&'a Statistics> { + let mut stats = vec![]; + if let Some(state) = state { + match selection { + Selection::Empty => (), + Selection::File(s) => { + for id in s.files.iter() { + if let Some(file) = state.get(id) { + self.add_file_stats(file, &mut stats); + } + } + } + Selection::Track(s) => { + if let Some(file) = state.get(&s.file) { + for trk in file.trk.iter() { + if s.trk.contains(&trk.id) { + self.add_track_stats(trk, &mut stats); + } + } + } + } + Selection::TrackSegment(s) => { + if let Some(file) = state.get(&s.file) { + for trk in file.trk.iter() { + if s.trk == trk.id { + for trkseg in trk.trkseg.iter() { + if s.trkseg.contains(&trkseg.id) { + self.add_segment_stats(trkseg, &mut stats); + } + } + break; + } + } + } + } + Selection::Waypoints(s) => { + if let Some(file) = state.get(&s.file) { + self.add_file_stats(file, &mut stats); + } + } + Selection::Waypoint(s) => { + if let Some(file) = state.get(&s.file) { + self.add_file_stats(file, &mut stats); + } + } + } + } + stats + } + + fn add_file_stats<'a>(&'a self, file: &Rc, stats: &mut Vec<&'a Statistics>) { + for trk in file.trk.iter() { + self.add_track_stats(trk, stats); + } + } + + fn add_track_stats<'a>(&'a self, trk: &Track, stats: &mut Vec<&'a Statistics>) { + for trkseg in trk.trkseg.iter() { + self.add_segment_stats(trkseg, stats); + } + } + + fn add_segment_stats<'a>(&'a self, trkseg: &TrackSegment, stats: &mut Vec<&'a Statistics>) { + if let Some(s) = self.map.get(&trkseg.rev_id) { + stats.push(s); + } + } +} diff --git a/gpx-rs/src/statistics/mod.rs b/gpx-rs/src/statistics/mod.rs index 0db599c42..0e9972d24 100644 --- a/gpx-rs/src/statistics/mod.rs +++ b/gpx-rs/src/statistics/mod.rs @@ -1,7 +1,7 @@ -mod buffer; +mod cache; mod statistics; mod utils; -pub use buffer::*; +pub use cache::*; pub use statistics::*; pub use utils::*; diff --git a/gpx-rs/src/utils.rs b/gpx-rs/src/utils.rs index 7ad73f2a6..1fd5fe8a9 100644 --- a/gpx-rs/src/utils.rs +++ b/gpx-rs/src/utils.rs @@ -18,8 +18,15 @@ pub fn distance(p1: LngLat, p2: LngLat) -> f64 { EARTH_RADIUS * c } +pub fn time_diff(a: &Option, b: &Option) -> Option { + match (a, b) { + (Some(t1), Some(t2)) => Some((t1 - t2) as i32), + _ => None, + } +} + /// Computes the speed for a given distance in kilometers and a time in milliseconds -pub fn speed(distance: f64, time: i64) -> f64 { +pub fn speed(distance: f64, time: i32) -> f64 { distance / (time as f64 / 3600_000.0) } @@ -40,7 +47,7 @@ fn get_meters_per_longitude_degree(latitude: f64) -> f64 { // Calculates the point on the line segment defined by p1 and p2 // that is closest to the third point, p3. // Uses simple planar geometry (ignores earth curvature). -fn projected(p1: LngLat, p2: LngLat, coord3: LngLat) -> LngLat { +fn projected(p1: LngLat, p2: LngLat, p3: LngLat) -> LngLat { // Convert to meters using approximate scaling let meters_per_longitude_degree = get_meters_per_longitude_degree(p1.lat); @@ -48,8 +55,8 @@ fn projected(p1: LngLat, p2: LngLat, coord3: LngLat) -> LngLat { let y1 = p1.lat * METERS_PER_LATITUDE_DEGREE; let x2 = p2.lng * meters_per_longitude_degree; let y2 = p2.lat * METERS_PER_LATITUDE_DEGREE; - let x3 = coord3.lng * meters_per_longitude_degree; - let y3 = coord3.lat * METERS_PER_LATITUDE_DEGREE; + let x3 = p3.lng * meters_per_longitude_degree; + let y3 = p3.lat * METERS_PER_LATITUDE_DEGREE; let dx = x2 - x1; let dy = y2 - y1; @@ -78,17 +85,20 @@ fn projected(p1: LngLat, p2: LngLat, coord3: LngLat) -> LngLat { /// Calculates the perpendicular distance in meters /// between a line segment (defined by p1 and p2) and a third point, p3. /// Uses simple planar geometry (ignores earth curvature). -fn crossarc(p1: LngLat, p2: LngLat, p3: LngLat) -> f64 { +fn crossarc_lnglat(p1: LngLat, p2: LngLat, p3: LngLat) -> f64 { // Convert to meters using approximate scaling let meters_per_longitude_degree = get_meters_per_longitude_degree(p1.lat); + crossarc( + p1.lng * meters_per_longitude_degree, + p1.lat * METERS_PER_LATITUDE_DEGREE, + p2.lng * meters_per_longitude_degree, + p2.lat * METERS_PER_LATITUDE_DEGREE, + p3.lng * meters_per_longitude_degree, + p3.lat * METERS_PER_LATITUDE_DEGREE, + ) +} - let x1 = p1.lng * meters_per_longitude_degree; - let y1 = p1.lat * METERS_PER_LATITUDE_DEGREE; - let x2 = p2.lng * meters_per_longitude_degree; - let y2 = p2.lat * METERS_PER_LATITUDE_DEGREE; - let x3 = p3.lng * meters_per_longitude_degree; - let y3 = p3.lat * METERS_PER_LATITUDE_DEGREE; - +pub fn crossarc(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> f64 { let dx = x2 - x1; let dy = y2 - y1; let segment_length_squared = dx * dx + dy * dy;