diff --git a/backend/src/isochrone.rs b/backend/src/isochrone.rs index 89420ae..ec6c21d 100644 --- a/backend/src/isochrone.rs +++ b/backend/src/isochrone.rs @@ -17,10 +17,15 @@ pub enum Style { Contours, } +pub enum Source { + Single(Coord), + FromAmenity(String), +} + pub fn calculate( graph: &Graph, amenities: &Amenities, - req: Coord, + source: Source, profile: ProfileID, style: Style, public_transit: bool, @@ -28,9 +33,34 @@ pub fn calculate( limit: Duration, mut timer: Timer, ) -> Result { + let mut starts = Vec::new(); + match source { + Source::Single(pt) => { + starts.push(graph.snap_to_road(pt, profile).intersection); + } + Source::FromAmenity(kind) => { + for (r, lists) in amenities.per_road.iter().enumerate() { + for a in &lists[profile.0] { + let amenity = &amenities.amenities[a.0]; + if amenity.kind == kind { + let road = &graph.roads[r]; + // TODO Which intersection is closer? Just start from either + starts.push(road.src_i); + starts.push(road.dst_i); + } + } + } + starts.sort(); + starts.dedup(); + if starts.is_empty() { + bail!("No amenities of kind {kind}"); + } + } + } + timer.step("get_costs"); let cost_per_road = graph.get_costs( - vec![graph.snap_to_road(req, profile).intersection], + starts, profile, public_transit, start_time, diff --git a/backend/src/lib.rs b/backend/src/lib.rs index f42bcb6..63b4280 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -108,10 +108,16 @@ impl MapModel { #[wasm_bindgen(js_name = isochrone)] pub fn isochrone(&self, input: JsValue) -> Result { let req: IsochroneRequest = serde_wasm_bindgen::from_value(input)?; - let start = self - .graph - .mercator - .pt_to_mercator(Coord { x: req.x, y: req.y }); + let start = if req.from_amenity.is_empty() { + isochrone::Source::Single( + self.graph + .mercator + .pt_to_mercator(Coord { x: req.x, y: req.y }), + ) + } else { + isochrone::Source::FromAmenity(req.from_amenity) + }; + let profile = self.parse_profile(&req.profile)?; isochrone::calculate( &self.graph, @@ -331,6 +337,9 @@ pub struct IsochroneRequest { // TODO Rename lon, lat to be clear? x: f64, y: f64, + // TODO Improve this API -- it's an enum, either a single start, or from every amenity + from_amenity: String, + profile: String, transit: bool, style: String, diff --git a/web/src/App.svelte b/web/src/App.svelte index c356331..c6e4eef 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -19,6 +19,7 @@ import RouteMode from "./RouteMode.svelte"; import DebugRouteMode from "./DebugRouteMode.svelte"; import ScoreMode from "./ScoreMode.svelte"; + import CoverageMode from "./CoverageMode.svelte"; import { map as mapStore, mode, @@ -171,6 +172,8 @@ {:else if $mode.kind == "score"} + {:else if $mode.kind == "coverage"} + {:else if $mode.kind == "debug-route"} + import { PickProfile, NavBar } from "./common"; + import { colorScale } from "./colors"; + import type { FeatureCollection } from "geojson"; + import { SymbolLayer, GeoJSON, FillLayer, LineLayer } from "svelte-maplibre"; + import { SplitComponent } from "svelte-utils/top_bar_layout"; + import { + backend, + profile, + type Profile, + startTime, + coverageMins, + } from "./stores"; + import { SequentialLegend, notNull } from "svelte-utils"; + import { Popup, makeColorRamp, isLine, isPolygon } from "svelte-utils/map"; + + let fromAmenity = "bicycle_parking"; + // TODO Generalize; show source amenity + let showParking = true; + + let style = "Roads"; + + let isochroneGj: FeatureCollection | null = null; + let err = ""; + + async function updateIsochrone( + _x: string, + _y: Profile, + _z: string, + _t: string, + _im: number, + ) { + try { + isochroneGj = await $backend!.isochroneFromAmenity({ + fromAmenity, + profile: $profile, + style, + startTime: $startTime, + maxSeconds: 60 * $coverageMins, + }); + err = ""; + } catch (err: any) { + isochroneGj = null; + err = err.toString(); + } + } + $: updateIsochrone(fromAmenity, $profile, style, $startTime, $coverageMins); + + $: limits = Array.from(Array(6).keys()).map( + (i) => (($coverageMins * 60) / (6 - 1)) * i, + ); + + + +
+ +
+ +
+

Coverage mode

+ + + + + + + + + + + l / 60)} /> + {#if err} +

{err}

+ {/if} +
+ +
+ {#if isochroneGj} + + + + {(props.cost_seconds / 60).toFixed(1)} minutes away + + + + + + + {#await notNull($backend).renderAmenities() then data} + + + + {/await} + {/if} +
+
diff --git a/web/src/common/NavBar.svelte b/web/src/common/NavBar.svelte index c5b32d2..acd04b8 100644 --- a/web/src/common/NavBar.svelte +++ b/web/src/common/NavBar.svelte @@ -31,6 +31,13 @@ > +
  • + +
  • +