From 0b50baad9854c70175c6e80f75c51c240e55c402 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Mon, 18 Sep 2023 12:31:44 +0100 Subject: [PATCH] Optionally use centroids for origin zones without any points. #27 --- aggregate_routes/src/config.rs | 2 ++ aggregate_routes/src/od.rs | 25 ++++++++++++++++++++----- aggregate_routes/src/timer.rs | 5 ++++- examples/liverpool/config.json | 3 ++- examples/york/config.json | 3 ++- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/aggregate_routes/src/config.rs b/aggregate_routes/src/config.rs index 7de36bd..e21ece2 100644 --- a/aggregate_routes/src/config.rs +++ b/aggregate_routes/src/config.rs @@ -54,6 +54,8 @@ pub enum ODPattern { csv_path: String, /// Path to a GeoJSON file containing Points with a "name" property destinations_path: String, + /// If a zone doesn't have any matching origin points, use the zone's centroid instead. + origin_zone_centroid_fallback: bool, }, } diff --git a/aggregate_routes/src/od.rs b/aggregate_routes/src/od.rs index a15ba9f..10da155 100644 --- a/aggregate_routes/src/od.rs +++ b/aggregate_routes/src/od.rs @@ -3,7 +3,7 @@ use std::io::BufReader; use anyhow::Result; use fs_err::File; -use geo::{BoundingRect, Contains, MultiPolygon}; +use geo::{BoundingRect, Centroid, Contains, MultiPolygon}; use geojson::{FeatureReader, Value}; use indicatif::HumanCount; use nanorand::{Rng, WyRand}; @@ -81,8 +81,9 @@ pub fn generate( let zones = load_zones(&zones_path)?; timer.stop(); timer.start("Matching points to zones"); - let origins_per_zone = points_per_polygon("origin", origins, &zones)?; - let destinations_per_zone = points_per_polygon("destination", destinations, &zones)?; + let origins_per_zone = points_per_polygon("origin", origins, &zones, false)?; + let destinations_per_zone = + points_per_polygon("destination", destinations, &zones, false)?; timer.stop(); timer.start(format!("Generating requests from {csv_path}")); @@ -118,6 +119,7 @@ pub fn generate( zones_path, csv_path, destinations_path, + origin_zone_centroid_fallback, } => { let zones_path = format!("{input_directory}/{zones_path}"); let csv_path = format!("{input_directory}/{csv_path}"); @@ -130,7 +132,8 @@ pub fn generate( let destinations = load_named_points(&destinations_path)?; timer.stop(); timer.start("Matching points to zones"); - let origins_per_zone = points_per_polygon("origin", origins, &zones)?; + let origins_per_zone = + points_per_polygon("origin", origins, &zones, origin_zone_centroid_fallback)?; timer.stop(); timer.start(format!("Generating requests from {csv_path}")); @@ -209,6 +212,7 @@ fn points_per_polygon( name: &str, points: Vec<(f64, f64)>, polygons: &HashMap>, + use_centroids_for_empty_zones: bool, ) -> Result>> { let tree = RTree::bulk_load(points); @@ -231,9 +235,20 @@ fn points_per_polygon( output.insert(key.clone(), pts_inside); } - if !empty.is_empty() { + if !empty.is_empty() && !use_centroids_for_empty_zones { bail!("Some zones have no matching {name} points: {:?}", empty); } + println!( + "{} zones have no matching {name} points. Using centroid instead.", + HumanCount(empty.len() as u64) + ); + for key in empty { + if let Some(centroid) = polygons[key].centroid() { + output.insert(key.clone(), vec![centroid.into()]); + } else { + bail!("{key} had no matching {name} points, and couldn't calculate its centroid"); + } + } Ok(output) } diff --git a/aggregate_routes/src/timer.rs b/aggregate_routes/src/timer.rs index 108fb5a..2bb033c 100644 --- a/aggregate_routes/src/timer.rs +++ b/aggregate_routes/src/timer.rs @@ -74,7 +74,10 @@ impl Timer { impl Drop for Timer { fn drop(&mut self) { if let Some(current) = self.stack.last() { - println!("WARNING: Dropping timer during block {}. Probably crashing.", current.name); + println!( + "WARNING: Dropping timer during block {}. Probably crashing.", + current.name + ); return; } diff --git a/examples/liverpool/config.json b/examples/liverpool/config.json index c3a05a0..419b727 100644 --- a/examples/liverpool/config.json +++ b/examples/liverpool/config.json @@ -5,7 +5,8 @@ "ZoneToPoint": { "zones_path": "zones.geojson", "destinations_path": "destinations.geojson", - "csv_path": "od.csv" + "csv_path": "od.csv", + "origin_zone_centroid_fallback": true } } } diff --git a/examples/york/config.json b/examples/york/config.json index c3a05a0..b22cad8 100644 --- a/examples/york/config.json +++ b/examples/york/config.json @@ -5,7 +5,8 @@ "ZoneToPoint": { "zones_path": "zones.geojson", "destinations_path": "destinations.geojson", - "csv_path": "od.csv" + "csv_path": "od.csv", + "origin_zone_centroid_fallback": false } } }