diff --git a/pavement_parking/src/boundaries.rs b/pavement_parking/src/boundaries.rs index 6b1a39c..b242a90 100644 --- a/pavement_parking/src/boundaries.rs +++ b/pavement_parking/src/boundaries.rs @@ -8,7 +8,7 @@ use geo::{Contains, MultiPolygon, Polygon}; use geojson::{Feature, FeatureCollection, FeatureWriter, Value}; use rstar::{primitives::GeomWithData, RTree, RTreeObject}; -use crate::{Rating, Road, Scenario}; +use crate::{Intervention, Rating, Road, Scenario}; /// Aggregate counts per LAD and CA boundaries pub struct Boundaries { @@ -21,6 +21,9 @@ struct Boundary { // Given a scenario and rating, count the roads and sum the length counts: EnumMap>, total_length: EnumMap>, + + intervention_counts: EnumMap, + intervention_total_length: EnumMap, } impl Boundaries { @@ -47,6 +50,8 @@ impl Boundaries { any_matches: false, counts: EnumMap::default(), total_length: EnumMap::default(), + intervention_counts: EnumMap::default(), + intervention_total_length: EnumMap::default(), }, ); } @@ -73,6 +78,8 @@ impl Boundaries { for (scenario, lengths) in &mut info.total_length { lengths[road.ratings[scenario]] += road.length; } + info.intervention_counts[road.intervention] += 1; + info.intervention_total_length[road.intervention] += road.length; } } } @@ -104,6 +111,15 @@ impl Boundaries { ); } } + for (intervention, count) in &info.intervention_counts { + f.set_property(format!("intervention_counts_{:?}", intervention), *count); + } + for (intervention, length) in &info.intervention_total_length { + f.set_property( + format!("intervention_lengths_{:?}", intervention), + length.round() as usize, + ); + } summaries_writer.write_feature(&f)?; } diff --git a/pavement_parking/src/main.rs b/pavement_parking/src/main.rs index 0457990..fe77811 100644 --- a/pavement_parking/src/main.rs +++ b/pavement_parking/src/main.rs @@ -10,7 +10,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use crate::boundaries::Boundaries; use crate::census_areas::CensusAreas; -use crate::ratings::{Rating, Scenario}; +use crate::ratings::{Intervention, Rating, Scenario}; use crate::roads::{Class, Road}; mod boundaries; @@ -101,7 +101,7 @@ fn tippecanoe(input: &str, output: &str) -> Result<()> { .arg("-Z10") .arg("-z11") .arg("--drop-densest-as-needed"); - println!("Running: {cmd:?}"); + println!("\nRunning: {cmd:?}"); if !cmd.status()?.success() { bail!("tippecanoe failed"); } diff --git a/pavement_parking/src/ratings.rs b/pavement_parking/src/ratings.rs index 0ad6bec..05403e4 100644 --- a/pavement_parking/src/ratings.rs +++ b/pavement_parking/src/ratings.rs @@ -1,4 +1,4 @@ -use enum_map::Enum; +use enum_map::{Enum, EnumMap}; use crate::Class; @@ -64,3 +64,37 @@ impl Rating { } } } + +/// Isomorphic to `Option`, but seems more clear to represent this way. +#[derive(Clone, Copy, Debug, PartialEq, Enum)] +pub enum Intervention { + /// No change needed (Scenario::U) + None, + Y, + X, + Z, + /// No scenario yields green + Impossible, +} + +impl Intervention { + // TODO The ordering here may need to vary locally, especially depending on the total + // supply/demand of parking. + pub fn calculate(ratings: &EnumMap, already_one_way: bool) -> Self { + for (scenario, intervention) in [ + (Scenario::U, Intervention::None), + (Scenario::Y, Intervention::Y), + (Scenario::X, Intervention::X), + (Scenario::Z, Intervention::Z), + ] { + if already_one_way && scenario == Scenario::X { + continue; + } + + if ratings[scenario] == Rating::Green { + return intervention; + } + } + Intervention::Impossible + } +} diff --git a/pavement_parking/src/roads.rs b/pavement_parking/src/roads.rs index 8b82c60..b241856 100644 --- a/pavement_parking/src/roads.rs +++ b/pavement_parking/src/roads.rs @@ -3,7 +3,7 @@ use enum_map::EnumMap; use geo::{Coord, HaversineLength, LineString, MapCoordsInPlace}; use geojson::{Feature, Value}; -use crate::{Rating, Scenario}; +use crate::{Intervention, Rating, Scenario}; // All distance units are in meters pub struct Road { @@ -21,6 +21,7 @@ pub struct Road { pub pavement_average_width: f64, pub ratings: EnumMap, + pub intervention: Intervention, } #[derive(Clone, Copy, Debug)] @@ -90,6 +91,7 @@ impl Road { // TODO Only consider road width as input, or do we want to continue to also try with // pavement width? let ratings = EnumMap::from_fn(|scenario| Rating::new(scenario, class, road_average_width)); + let intervention = Intervention::calculate(&ratings, direction == "one-way"); Ok(Some(Self { geom, @@ -104,6 +106,7 @@ impl Road { pavement_average_width, ratings, + intervention, })) } @@ -122,6 +125,7 @@ impl Road { for (scenario, rating) in self.ratings { f.set_property(format!("rating_{:?}", scenario), rating.to_str()); } + f.set_property("intervention", format!("{:?}", self.intervention)); f.set_property("parkable_length", trim_meters(parkable_length)); // TODO Just debug right now, not used in the UI diff --git a/pavement_parking/web/src/PavementLayers.svelte b/pavement_parking/web/src/PavementLayers.svelte index 30e6f27..462457e 100644 --- a/pavement_parking/web/src/PavementLayers.svelte +++ b/pavement_parking/web/src/PavementLayers.svelte @@ -6,7 +6,13 @@ VectorTileSource, hoverStateFilter, } from "svelte-maplibre"; - import { colors, scenarios, type Mode, type Filters } from "./types"; + import { + colors, + scenarios, + interventions, + type Mode, + type Filters, + } from "./types"; import type { ExpressionSpecification } from "maplibre-gl"; export let show: Mode; @@ -73,6 +79,9 @@ ]}

{/each} +

+ Intervention required: {interventions[data.properties.intervention]} +

{/if} diff --git a/pavement_parking/web/src/SummaryLayers.svelte b/pavement_parking/web/src/SummaryLayers.svelte index c018b0b..94438bb 100644 --- a/pavement_parking/web/src/SummaryLayers.svelte +++ b/pavement_parking/web/src/SummaryLayers.svelte @@ -6,7 +6,13 @@ hoverStateFilter, FillLayer, } from "svelte-maplibre"; - import { colors, scenarios, ratings, type Mode } from "./types"; + import { + colors, + scenarios, + ratings, + interventions, + type Mode, + } from "./types"; export let show: Mode; export let url: string; @@ -30,6 +36,7 @@ {#if data?.properties}

{data.properties.name}

+ @@ -53,6 +60,29 @@ {/each}
Scenario
+ + + + + + + + {#each Object.entries(interventions) as [intervention, label]} + + + + + + {/each} +
InterventionNumber of roadsTotal km
{label} + {data.properties[ + `intervention_counts_${intervention}` + ].toLocaleString()} + + {( + data.properties[`intervention_lengths_${intervention}`] / 1000 + ).toFixed(2)} +
{/if}
diff --git a/pavement_parking/web/src/types.ts b/pavement_parking/web/src/types.ts index a6a609f..50aa0d5 100644 --- a/pavement_parking/web/src/types.ts +++ b/pavement_parking/web/src/types.ts @@ -52,3 +52,11 @@ export const scenarios = [ ["Y", "One-way, parking both sides"], ["Z", "One-way, parking one side only"], ]; + +export const interventions = { + None: "U: No change needed", + Y: "Y: One-way, parking both sides", + X: "X: Parking one side only", + Z: "Z: One-way, parking one side only", + Impossible: "Impossible: No scenario makes the road green", +};