diff --git a/pavement_parking/src/census_areas.rs b/pavement_parking/src/census_areas.rs index 8b3a3f6..5bd62a5 100644 --- a/pavement_parking/src/census_areas.rs +++ b/pavement_parking/src/census_areas.rs @@ -10,6 +10,8 @@ use geojson::{Feature, FeatureWriter, Value}; use indicatif::{ProgressBar, ProgressStyle}; use rstar::{primitives::GeomWithData, RTree, RTreeObject}; +use crate::Class; + pub struct CensusAreas { rtree: RTree>, info: HashMap, @@ -76,13 +78,13 @@ impl CensusAreas { &mut self, geom: &LineString, average_rating: &str, - road_class: &str, + class: Class, ) -> Result<(Option, f64)> { // For each output area, sum the kerb length where it is possible to park a car. // Calculate the parkable kerb length per car in the area. // Estimate the length of the kerb where it is possible to park a car - let parkable_length = parkable_kerb_length(geom, average_rating, road_class); + let parkable_length = parkable_kerb_length(geom, average_rating, class); // Assign each road to exactly one output area. If it intersects multiple output areas, // it can be assigned by arbitrary but repeatable method. @@ -122,7 +124,7 @@ impl CensusAreas { } } -fn parkable_kerb_length(geom: &LineString, rating: &str, class: &str) -> f64 { +fn parkable_kerb_length(geom: &LineString, rating: &str, class: Class) -> f64 { // Returns the length of the kerb where it is possible to park a car // i.e. not on a junction or a pedestrian crossing, etc. // This attempts to implement the table of proposed interventions in @@ -136,10 +138,8 @@ fn parkable_kerb_length(geom: &LineString, rating: &str, class: &str) -> f64 { "green" => 2.0 * raw_length, "amber" => raw_length, "red" => match class { - "A Road" => 0.0, - "B Road" | "Classified Unnumbered" | "Unclassified" => raw_length, - "Unknown" | "Not Classified" => raw_length, - _ => panic!("Unknown class {class}"), + Class::A => 0.0, + Class::B | Class::C | Class::Unclassified => raw_length, }, "TODO" => raw_length, _ => panic!("Unknown rating {rating}"), diff --git a/pavement_parking/src/main.rs b/pavement_parking/src/main.rs index 9326e2b..a8b062a 100644 --- a/pavement_parking/src/main.rs +++ b/pavement_parking/src/main.rs @@ -1,6 +1,6 @@ use std::io::BufWriter; -use anyhow::{bail, Result}; +use anyhow::Result; use fs_err::File; use gdal::vector::LayerAccess; use gdal::Dataset; @@ -9,7 +9,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use crate::boundaries::Boundaries; use crate::census_areas::CensusAreas; -use crate::roads::Road; +use crate::roads::{Class, Road}; mod boundaries; mod census_areas; @@ -68,9 +68,9 @@ fn handle_road( }; let average_rating_inc_pavements = - rating(&road.class, road.road_average + road.pavement_average)?; - let average_rating_exc_pavements = rating(&road.class, road.road_average)?; - let minimum_rating = rating(&road.class, road.road_minimum)?; + rating(road.class, road.road_average + road.pavement_average)?; + let average_rating_exc_pavements = rating(road.class, road.road_average)?; + let minimum_rating = rating(road.class, road.road_minimum)?; let rating_change = if average_rating_inc_pavements == average_rating_exc_pavements { "no_change" @@ -81,7 +81,7 @@ fn handle_road( let (output_area_geoid, parkable_length) = census_areas.aggregate_kerb_length_per_oa( &road.geom, &average_rating_exc_pavements, - &road.class, + road.class, )?; // TODO Use average_rating_exc_pavements for now @@ -101,18 +101,18 @@ fn handle_road( output_area_geoid.unwrap_or("NONE".to_string()), ); output_line.set_property("rating_change", rating_change); - output_line.set_property("class", road.class); + output_line.set_property("class", format!("{:?}", road.class)); output_line.set_property("direction", road.direction); writer.write_feature(&output_line)?; Ok(()) } -fn rating(class: &str, width: f64) -> Result<&'static str> { +fn rating(class: Class, width: f64) -> Result<&'static str> { // See https://www.ordnancesurvey.co.uk/documents/os-open-roads-user-guide.pdf page 22 for the // cases. The width thresholds come from a TBD table. match class { - "A Road" | "B Road" => Ok(if width >= 11.8 { + Class::A | Class::B => Ok(if width >= 11.8 { "green" } else if width >= 10.4 { "amber" @@ -120,8 +120,7 @@ fn rating(class: &str, width: f64) -> Result<&'static str> { "red" }), - // Note "Classified Unnumbered" is how OS calls C Roads - "Classified Unnumbered" | "Unclassified" => Ok(if width >= 9.0 { + Class::C | Class::Unclassified => Ok(if width >= 9.0 { "green" } else if width >= 7.5 { "amber" @@ -129,10 +128,5 @@ fn rating(class: &str, width: f64) -> Result<&'static str> { // TODO Table doesn't handle [7, 7.5] "red" }), - - // TODO Need to see what these are - "Unknown" | "Not Classified" => Ok("TODO"), - - _ => bail!("Unknown roadclassification {class}"), } } diff --git a/pavement_parking/src/roads.rs b/pavement_parking/src/roads.rs index 9aa191b..e06bb25 100644 --- a/pavement_parking/src/roads.rs +++ b/pavement_parking/src/roads.rs @@ -3,7 +3,7 @@ use geo::{Coord, LineString, MapCoordsInPlace}; pub struct Road { pub geom: LineString, - pub class: String, + pub class: Class, pub road_average: f64, pub road_minimum: f64, /// Assume that where there are pavements on both sides of the road, then this value is the sum @@ -12,16 +12,32 @@ pub struct Road { pub direction: String, } +#[derive(Clone, Copy, Debug)] +pub enum Class { + A, + B, + C, + Unclassified, +} + impl Road { /// Parse data about one road from the input gpkg. `None` means to skip this road. pub fn new(input: gdal::vector::Feature) -> Result> { let Some(class) = input.field_as_string_by_name("roadclassification")? else { bail!("Missing roadclassification"); }; - // Skip roads that shouldn't be analyzed for pavement parking - if class == "Motorway" { - return Ok(None); - } + let class = match class.as_str() { + "A Road" => Class::A, + "B Road" => Class::B, + "Classified Unnumbered" => Class::C, + "Unclassified" => Class::Unclassified, + + // Skip roads that shouldn't be analyzed for pavement parking + "Motorway" | "Unknown" | "Not Classified" => { + return Ok(None); + } + _ => bail!("Unknown roadclassification {class}"), + }; let mut geom: LineString = input.geometry().unwrap().to_geo()?.try_into()?; // Remove unnecessary precision diff --git a/pavement_parking/web/src/App.svelte b/pavement_parking/web/src/App.svelte index f38b03b..bc4823a 100644 --- a/pavement_parking/web/src/App.svelte +++ b/pavement_parking/web/src/App.svelte @@ -11,7 +11,7 @@ } from "svelte-maplibre"; import PavementLayers from "./PavementLayers.svelte"; import SummaryLayers from "./SummaryLayers.svelte"; - import StreetFilters from "./StreetFilters.svelte"; + import RoadFilters from "./RoadFilters.svelte"; import OutputAreas from "./OutputAreas.svelte"; import About from "./About.svelte"; import { defaultFilters, type Mode } from "./types"; @@ -20,8 +20,8 @@ ? undefined : ([-5.96, 49.89, 2.31, 55.94] as LngLatBoundsLike); - let show: Mode = "streets"; - let streetFilters = defaultFilters; + let show: Mode = "roads"; + let roadFilters = defaultFilters; let params = new URLSearchParams(window.location.search); let pavementsUrl = params.get("data") || ""; @@ -60,8 +60,8 @@
Show:
- {#if show == "streets"} + {#if show == "roads"} {#if zoom >= 10} - + {:else} -

Zoom in more to see streets

+

Zoom in more to see roads

{/if} {/if} @@ -97,11 +97,11 @@ {#if pavementsUrl.endsWith(".geojson")} - + {:else if pavementsUrl.endsWith(".pmtiles")} - + {/if} diff --git a/pavement_parking/web/src/PavementLayers.svelte b/pavement_parking/web/src/PavementLayers.svelte index 23a2619..72523e9 100644 --- a/pavement_parking/web/src/PavementLayers.svelte +++ b/pavement_parking/web/src/PavementLayers.svelte @@ -5,7 +5,7 @@ import type { ExpressionSpecification } from "maplibre-gl"; export let show: Mode; - export let streetFilters: Filters; + export let roadFilters: Filters; export let sourceLayer: string | undefined; function makeFilter(f: Filters): ExpressionSpecification { @@ -30,13 +30,13 @@ {#if data?.properties} -

{data.properties.class} street

+

{data.properties.class} road

Direction: {data.properties.direction}

Average road width {data.properties.average_width}, rating {data diff --git a/pavement_parking/web/src/StreetFilters.svelte b/pavement_parking/web/src/RoadFilters.svelte similarity index 91% rename from pavement_parking/web/src/StreetFilters.svelte rename to pavement_parking/web/src/RoadFilters.svelte index d5b2b80..d21f42e 100644 --- a/pavement_parking/web/src/StreetFilters.svelte +++ b/pavement_parking/web/src/RoadFilters.svelte @@ -3,14 +3,7 @@ export let filters: Filters; - let classes = [ - "A Road", - "B Road", - "Classified Unnumbered", - "Unclassified", - "Unknown", - "Not Classified", - ] as const; + let classes = ["A", "B", "C", "Unclassified"] as const;

diff --git a/pavement_parking/web/src/types.ts b/pavement_parking/web/src/types.ts index 401279f..3991a9d 100644 --- a/pavement_parking/web/src/types.ts +++ b/pavement_parking/web/src/types.ts @@ -1,4 +1,4 @@ -export type Mode = "streets" | "lad-summary" | "ca-summary" | "census-area"; +export type Mode = "roads" | "lad-summary" | "ca-summary" | "census-area"; export interface Filters { useRating: "average_rating" | "rating_change" | "minimum_rating"; @@ -9,12 +9,10 @@ export interface Filters { no_change: boolean; }; showClasses: { - "A Road": boolean; - "B Road": boolean; - "Classified Unnumbered": boolean; + A: boolean; + B: boolean; + C: boolean; Unclassified: boolean; - Unknown: boolean; - "Not Classified": boolean; }; showDirections: { both: boolean; @@ -31,12 +29,10 @@ export const defaultFilters: Filters = { no_change: true, }, showClasses: { - "A Road": true, - "B Road": true, - "Classified Unnumbered": true, + A: true, + B: true, + C: true, Unclassified: true, - Unknown: true, - "Not Classified": true, }, showDirections: { both: true,