From 37ee990f9e7bbff8707e06cf4f87181a24e8a041 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 19 Apr 2023 15:07:25 +0100 Subject: [PATCH] Optionally ignore direction. #9 Implement by forcing orientation. Not working. --- rust/cli/src/main.rs | 7 +- rust/overline/src/lib.rs | 63 +++-- rust/tests/direction_input.geojson | 365 +++++++++++++++++++++++++++++ rust/tests/src/lib.rs | 16 +- 4 files changed, 426 insertions(+), 25 deletions(-) create mode 100644 rust/tests/direction_input.geojson diff --git a/rust/cli/src/main.rs b/rust/cli/src/main.rs index 2df7e44..d751465 100644 --- a/rust/cli/src/main.rs +++ b/rust/cli/src/main.rs @@ -5,7 +5,7 @@ use clap::{Arg, ArgAction, Command}; use geo::GeodesicLength; use geojson::{Feature, FeatureCollection, GeoJson}; -use overline::{aggregate_properties, feature_to_line_string, overline, Aggregation, Output}; +use overline::{aggregate_properties, feature_to_line_string, overline, Aggregation, Output, Options}; fn main() -> Result<()> { let mut args = Command::new("overline") @@ -24,6 +24,9 @@ fn main() -> Result<()> { let input_path = args.remove_one::("FILE").unwrap(); let output_path = args.remove_one::("output").unwrap(); + // TODO Add a flag + let options = Options::default(); + println!("Reading and deserializing {input_path}"); let mut now = Instant::now(); let geojson: GeoJson = std::fs::read_to_string(input_path)?.parse()?; @@ -36,7 +39,7 @@ fn main() -> Result<()> { println!("Running overline on {} line-strings", input.len()); now = Instant::now(); - let output = overline(&input); + let output = overline(&input, options); println!("... took {:?}", now.elapsed()); if let Some(sum_property) = args.get_one::("summary") { diff --git a/rust/overline/src/lib.rs b/rust/overline/src/lib.rs index eb6120d..ed5097d 100644 --- a/rust/overline/src/lib.rs +++ b/rust/overline/src/lib.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use geo::Winding; use geojson::Feature; use ordered_float::NotNan; use rayon::prelude::*; @@ -20,33 +21,56 @@ pub struct Output { pub indices: Vec, } +#[derive(Default)] +pub struct Options { + /// TODO describe. may reverse input geometry. + pub ignore_direction: bool, +} + /// Ignores anything aside from LineStrings. Returns LineStrings chopped up to remove overlap, with /// exactly one property -- `indices`, an array of numbers indexing the input that share that /// geometry. -pub fn overline(input: &Vec) -> Vec { +pub fn overline(input: &Vec, options: Options) -> Vec { + // Extract LineStrings from input + let input_linestrings: Vec>> = input.par_iter().map(|f| { + feature_to_line_string(f).map(|mut linestring| { + if options.ignore_direction { + // TODO Do we need to project to euclidean first? + println!("input is cw? {:?}", linestring.winding_order()); + linestring.make_cw_winding(); + + /*let pt1 = linestring.0[0]; + let pt2 = linestring.0.last().unwrap(); + let angle = pt1.angle_to(pt2); + TODO normalize based on angle? */ + } + linestring + }) + }).collect(); + // For every individual (directed) line segment, record the index of inputs matching there let mut line_segments: HashMap<(HashedPoint, HashedPoint), Vec> = HashMap::new(); - for (idx, input) in input.iter().enumerate() { - if let Some(geom) = feature_to_line_string(input) { - for line in geom.lines().map(HashedPoint::new_line) { + for (idx, maybe_linestring) in input_linestrings.iter().enumerate() { + if let Some(ref linestring) = maybe_linestring { + for line in linestring.lines().map(HashedPoint::new_line) { line_segments.entry(line).or_insert_with(Vec::new).push(idx); } } } // Then look at each input, accumulating points as long all the indices match - input + input_linestrings .par_iter() .enumerate() - .flat_map(|(idx, input_feature)| { + .flat_map(|(idx, maybe_linestring)| { let mut intermediate_output = Vec::new(); - // This state is reset as we look through this input's points - let mut pts_so_far = Vec::new(); - let mut indices_so_far = Vec::new(); - let mut keep_this_output = false; + if let Some(ref linestring) = maybe_linestring { + // This state is reset as we look through this input's points + let mut pts_so_far = Vec::new(); + let mut indices_so_far = Vec::new(); + let mut keep_this_output = false; - if let Some(geom) = feature_to_line_string(input_feature) { - for line in geom.lines() { + for line in linestring.lines() { // The segment is guaranteed to exist let indices = &line_segments[&HashedPoint::new_line(line)]; @@ -76,13 +100,14 @@ pub fn overline(input: &Vec) -> Vec { // below, since we process input in order. keep_this_output = indices_so_far.iter().all(|i| *i >= idx); } - } - // This input ended; add to output if needed - if !pts_so_far.is_empty() && keep_this_output { - intermediate_output.push(Output { - geometry: pts_so_far.into(), - indices: indices_so_far, - }); + + // This input ended; add to output if needed + if !pts_so_far.is_empty() && keep_this_output { + intermediate_output.push(Output { + geometry: pts_so_far.into(), + indices: indices_so_far, + }); + } } intermediate_output diff --git a/rust/tests/direction_input.geojson b/rust/tests/direction_input.geojson new file mode 100644 index 0000000..24552c5 --- /dev/null +++ b/rust/tests/direction_input.geojson @@ -0,0 +1,365 @@ +{ + "features": [ + { + "geometry": { + "coordinates": [ + [ + -2.5897288003138597, + 51.46422899994321 + ], + [ + -2.5894719994407036, + 51.464287600638315 + ], + [ + -2.5892327003343816, + 51.46434950004507 + ], + [ + -2.588276699923056, + 51.46457110008312 + ], + [ + -2.588069000310658, + 51.46461820025354 + ], + [ + -2.587963600142609, + 51.4646506999377 + ], + [ + -2.5873518001873315, + 51.464891299943304 + ], + [ + -2.5866982993076943, + 51.4651529000077 + ], + [ + -2.5866496997402932, + 51.465168299990864 + ], + [ + -2.5864813003992273, + 51.46522750053348 + ], + [ + -2.586339600057373, + 51.46524980101132 + ], + [ + -2.586212000317643, + 51.46525680043137 + ], + [ + -2.5851213002711964, + 51.46526560029328 + ], + [ + -2.5843142999119495, + 51.465274900177995 + ], + [ + -2.58394340045669, + 51.46529340012273 + ], + [ + -2.5835841995130115, + 51.465323400592155 + ], + [ + -2.5833128006569237, + 51.46531829964001 + ], + [ + -2.5831576005949595, + 51.465336400285956 + ], + [ + -2.583019100165464, + 51.465359299711984 + ], + [ + -2.582795800620937, + 51.46540510036268 + ], + [ + -2.5827379995086077, + 51.46541700018616 + ], + [ + -2.582703099476149, + 51.465457100037206 + ], + [ + -2.582638399720778, + 51.46546720031815 + ], + [ + -2.582586400426185, + 51.465475400332544 + ], + [ + -2.5825061999273426, + 51.46545950032657 + ], + [ + -2.5817932002280557, + 51.4656429997055 + ], + [ + -2.5816254003314882, + 51.46568630024214 + ], + [ + -2.581161099660768, + 51.465839100378766 + ], + [ + -2.5807875005489898, + 51.46596209969536 + ], + [ + -2.5806878993236433, + 51.465999499782946 + ], + [ + -2.5800168003635484, + 51.4662928998593 + ], + [ + -2.5799356999792, + 51.466328001280864 + ], + [ + -2.5798650000287764, + 51.46638020060479 + ], + [ + -2.579813200549016, + 51.46644710023966 + ], + [ + -2.5797861996712803, + 51.46652470000299 + ], + [ + -2.5792748002006802, + 51.466514400072654 + ], + [ + -2.5780957000596607, + 51.466407399687405 + ], + [ + -2.577857900283343, + 51.46638259999483 + ], + [ + -2.5774380993821584, + 51.46634209994567 + ], + [ + -2.5771552995804647, + 51.466315599635934 + ], + [ + -2.577044899729044, + 51.46630580062772 + ], + [ + -2.576622800238523, + 51.466270899754875 + ] + ], + "type": "LineString" + }, + "id": 1, + "properties": { + "foot": 50 + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + [ + -2.576622800238523, + 51.466270899754875 + ], + [ + -2.577044899729044, + 51.46630580062772 + ], + [ + -2.5771552995804647, + 51.466315599635934 + ], + [ + -2.5774380993821584, + 51.46634209994567 + ], + [ + -2.577857900283343, + 51.46638259999483 + ], + [ + -2.5780957000596607, + 51.466407399687405 + ], + [ + -2.5792748002006802, + 51.466514400072654 + ], + [ + -2.5797861996712803, + 51.46652470000299 + ], + [ + -2.579813200549016, + 51.46644710023966 + ], + [ + -2.5798650000287764, + 51.46638020060479 + ], + [ + -2.5799356999792, + 51.466328001280864 + ], + [ + -2.5800168003635484, + 51.4662928998593 + ], + [ + -2.5806878993236433, + 51.465999499782946 + ], + [ + -2.5807875005489898, + 51.46596209969536 + ], + [ + -2.581161099660768, + 51.465839100378766 + ], + [ + -2.5816254003314882, + 51.46568630024214 + ], + [ + -2.5817932002280557, + 51.4656429997055 + ], + [ + -2.5825061999273426, + 51.46545950032657 + ], + [ + -2.5825448001280007, + 51.465413600750495 + ], + [ + -2.5826100993278702, + 51.46540310027143 + ], + [ + -2.582674899709416, + 51.46539269961707 + ], + [ + -2.5827379995086077, + 51.46541700018616 + ], + [ + -2.582795800620937, + 51.46540510036268 + ], + [ + -2.583019100165464, + 51.465359299711984 + ], + [ + -2.5831576005949595, + 51.465336400285956 + ], + [ + -2.5833128006569237, + 51.46531829964001 + ], + [ + -2.5835841995130115, + 51.465323400592155 + ], + [ + -2.58394340045669, + 51.46529340012273 + ], + [ + -2.5843142999119495, + 51.465274900177995 + ], + [ + -2.5851213002711964, + 51.46526560029328 + ], + [ + -2.586212000317643, + 51.46525680043137 + ], + [ + -2.586339600057373, + 51.46524980101132 + ], + [ + -2.5864813003992273, + 51.46522750053348 + ], + [ + -2.5866496997402932, + 51.465168299990864 + ], + [ + -2.5866982993076943, + 51.4651529000077 + ], + [ + -2.5873518001873315, + 51.464891299943304 + ], + [ + -2.587963600142609, + 51.4646506999377 + ], + [ + -2.588069000310658, + 51.46461820025354 + ], + [ + -2.588276699923056, + 51.46457110008312 + ], + [ + -2.5892327003343816, + 51.46434950004507 + ], + [ + -2.5894719994407036, + 51.464287600638315 + ], + [ + -2.5897288003138597, + 51.46422899994321 + ] + ], + "type": "LineString" + }, + "id": 2, + "properties": { + "foot": 10 + }, + "type": "Feature" + } + ], + "type": "FeatureCollection" +} \ No newline at end of file diff --git a/rust/tests/src/lib.rs b/rust/tests/src/lib.rs index dafda9a..ba61d32 100644 --- a/rust/tests/src/lib.rs +++ b/rust/tests/src/lib.rs @@ -1,19 +1,27 @@ #[cfg(test)] mod tests { use geojson::FeatureCollection; - use overline::{aggregate_properties, overline, Aggregation}; + use overline::{aggregate_properties, overline, Aggregation, Options}; #[test] fn test_atip() { - test("atip_input.geojson", "atip_output.geojson"); + test("atip_input.geojson", "atip_output.geojson", Options::default()); } - fn test(input_path: &str, output_path: &str) { + #[test] + fn test_direction() { + //test("direction_input.geojson", "direction_input.geojson"); + test("direction_input.geojson", "direction_output.geojson", Options { + ignore_direction: true + }); + } + + fn test(input_path: &str, output_path: &str, options: Options) { // Just compare as strings. Upon failure, we write and ask the user to check those anyway. let input_string = std::fs::read_to_string(input_path).unwrap(); let input = input_string.parse::().unwrap().features; - let grouped_indices = overline(&input); + let grouped_indices = overline(&input, options); let actual_output = serde_json::to_string_pretty(&FeatureCollection { features: aggregate_properties( &input,