Skip to content

Commit

Permalink
Make road classes match terminology in tables, and filter out the
Browse files Browse the repository at this point in the history
unknown cases
  • Loading branch information
dabreegster committed Aug 21, 2024
1 parent 632986c commit e1306a6
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 62 deletions.
14 changes: 7 additions & 7 deletions pavement_parking/src/census_areas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GeomWithData<Polygon, String>>,
info: HashMap<String, CensusArea>,
Expand Down Expand Up @@ -76,13 +78,13 @@ impl CensusAreas {
&mut self,
geom: &LineString,
average_rating: &str,
road_class: &str,
class: Class,
) -> Result<(Option<String>, 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.
Expand Down Expand Up @@ -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
Expand All @@ -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}"),
Expand Down
26 changes: 10 additions & 16 deletions pavement_parking/src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -101,38 +101,32 @@ 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"
} else {
"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"
} else {
// 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}"),
}
}
26 changes: 21 additions & 5 deletions pavement_parking/src/roads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Option<Self>> {
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
Expand Down
20 changes: 10 additions & 10 deletions pavement_parking/web/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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") || "";
Expand Down Expand Up @@ -60,8 +60,8 @@
<fieldset>
<legend>Show:</legend>
<label>
<input type="radio" value="streets" bind:group={show} />
Streets
<input type="radio" value="roads" bind:group={show} />
Roads
</label>
<label>
<input type="radio" value="lad-summary" bind:group={show} />
Expand All @@ -77,11 +77,11 @@
</label>
</fieldset>

{#if show == "streets"}
{#if show == "roads"}
{#if zoom >= 10}
<StreetFilters bind:filters={streetFilters} />
<RoadFilters bind:filters={roadFilters} />
{:else}
<p>Zoom in more to see streets</p>
<p>Zoom in more to see roads</p>
{/if}
{/if}
</div>
Expand All @@ -97,11 +97,11 @@

{#if pavementsUrl.endsWith(".geojson")}
<GeoJSON data={pavementsUrl} generateId>
<PavementLayers {show} {streetFilters} sourceLayer={undefined} />
<PavementLayers {show} {roadFilters} sourceLayer={undefined} />
</GeoJSON>
{:else if pavementsUrl.endsWith(".pmtiles")}
<VectorTileSource url={`pmtiles://${pavementsUrl}`}>
<PavementLayers {show} {streetFilters} sourceLayer="pavements" />
<PavementLayers {show} {roadFilters} sourceLayer="pavements" />
</VectorTileSource>
{/if}

Expand Down
10 changes: 5 additions & 5 deletions pavement_parking/web/src/PavementLayers.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -30,13 +30,13 @@

<LineLayer
{sourceLayer}
filter={makeFilter(streetFilters)}
layout={{ visibility: show == "streets" ? "visible" : "none" }}
filter={makeFilter(roadFilters)}
layout={{ visibility: show == "roads" ? "visible" : "none" }}
manageHoverState
paint={{
"line-width": hoverStateFilter(5, 10),
"line-color": constructMatchExpression(
["get", streetFilters.useRating],
["get", roadFilters.useRating],
{
green: colors.green,
amber: colors.amber,
Expand All @@ -50,7 +50,7 @@
>
<Popup openOn="hover" let:data popupClass="popup">
{#if data?.properties}
<h1>{data.properties.class} street</h1>
<h1>{data.properties.class} road</h1>
<p>Direction: {data.properties.direction}</p>
<p>
Average road width {data.properties.average_width}, rating {data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
</script>

<fieldset>
Expand Down
18 changes: 7 additions & 11 deletions pavement_parking/web/src/types.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand All @@ -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,
Expand Down

0 comments on commit e1306a6

Please sign in to comment.