Skip to content

Commit

Permalink
Buffer around a snapped route. #13
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Aug 20, 2024
1 parent ba0da39 commit b66432f
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 28 deletions.
2 changes: 1 addition & 1 deletion backend/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn buffer_route(
starts.insert(road.src_i);
starts.insert(road.dst_i);

// TODO Doesn't handle the exact start/end
// TODO Doesn't handle the exact start/end, or gluing things together
let mut f = Feature::from(Geometry::from(&graph.mercator.to_wgs84(&road.linestring)));
f.set_property("kind", "route");
features.push(f);
Expand Down
30 changes: 17 additions & 13 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::time::Duration;

use chrono::NaiveTime;
use geo::{Coord, LineString};
use geojson::{de::deserialize_geometry, Feature, GeoJson, Geometry};
use geojson::de::deserialize_geometry;
use serde::Deserialize;
use wasm_bindgen::prelude::*;

Expand Down Expand Up @@ -179,29 +179,31 @@ impl MapModel {
.map_err(err_to_js)
}

#[wasm_bindgen(js_name = snapRoute)]
pub fn snap_route(&self, input: JsValue) -> Result<String, JsValue> {
#[wasm_bindgen(js_name = snapAndBufferRoute)]
pub fn snap_and_buffer_route(&self, input: JsValue) -> Result<String, JsValue> {
let req: SnapRouteRequest = serde_wasm_bindgen::from_value(input)?;
let mode = Mode::parse(&req.mode).map_err(err_to_js)?;
let mut linestrings = Vec::new();

let mut all_steps = Vec::new();
for mut input in
geojson::de::deserialize_feature_collection_str_to_vec::<GeoJsonLineString>(&req.input)
.map_err(err_to_js)?
{
self.graph
.mercator
.to_mercator_in_place(&mut input.geometry);
linestrings.push(input.geometry);
let (steps, _) = self
.graph
.snap_route(&input.geometry, mode)
.map_err(err_to_js)?;
all_steps.extend(steps);
}

let mut output = Vec::new();
for input in linestrings {
let (_, snapped) = self.graph.snap_route(&input, mode).map_err(err_to_js)?;
output.push(Feature::from(Geometry::from(
&self.graph.mercator.to_wgs84(&snapped),
)));
}
Ok(serde_json::to_string(&GeoJson::from(output)).map_err(err_to_js)?)
let start_time = NaiveTime::parse_from_str(&req.start_time, "%H:%M").map_err(err_to_js)?;
let limit = Duration::from_secs(req.max_seconds);

crate::buffer::buffer_route(&self.graph, mode, all_steps, start_time, limit)
.map_err(err_to_js)
}
}

Expand Down Expand Up @@ -295,6 +297,8 @@ pub struct ScoreRequest {
struct SnapRouteRequest {
input: String,
mode: String,
start_time: String,
max_seconds: u64,
}

#[derive(Deserialize)]
Expand Down
83 changes: 71 additions & 12 deletions web/src/UploadRouteMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@
import { NavBar, PickTravelMode } from "./common";
import { SplitComponent } from "svelte-utils/top_bar_layout";
import type { FeatureCollection } from "geojson";
import { backend, travelMode } from "./stores";
import {
backend,
travelMode,
bufferMins,
showRouteBuffer,
showRouteBufferPopulation,
startTime,
type TravelMode,
} from "./stores";
import { GeoJSON, LineLayer, hoverStateFilter } from "svelte-maplibre";
import BufferLayer from "./BufferLayer.svelte";
import { SequentialLegend } from "svelte-utils";
import { colorScale } from "./colors";
let input: FeatureCollection | null = null;
let output: FeatureCollection | null = null;
let totalPopulationInBuffer = 0;
let fileInput: HTMLInputElement;
async function loadFile(e: Event) {
Expand All @@ -16,15 +28,41 @@
) as FeatureCollection;
gj.features = gj.features.filter((f) => f.geometry.type == "LineString");
input = gj;
} catch (err) {
window.alert(`Couldn't snap routes from file: ${err}`);
}
}
async function update(
input: FeatureCollection | null,
mode: TravelMode,
_t: string,
_b: number,
_sb: boolean,
) {
totalPopulationInBuffer = 0;
output = null;
if (!input) {
return;
}
output = await $backend!.snapRoute({
input: input!,
try {
output = await $backend!.snapAndBufferRoute({
input,
mode: $travelMode,
startTime: $startTime,
maxSeconds: $bufferMins * 60,
});
totalPopulationInBuffer = output.total_population;
} catch (err) {
window.alert(`Couldn't snap routes from file: ${err}`);
window.alert(`Problem: ${err}`);
}
}
$: update(input, $travelMode, $startTime, $bufferMins, $showRouteBuffer);
$: limits = Array.from(Array(6).keys()).map(
(i) => (($bufferMins * 60) / (6 - 1)) * i,
);
</script>

<SplitComponent>
Expand All @@ -41,6 +79,22 @@
</label>

<PickTravelMode bind:travelMode={$travelMode} />

{#if input}
<label>
<input type="checkbox" bind:checked={$showRouteBuffer} />
Buffer around route (minutes)
<input type="number" bind:value={$bufferMins} min="1" max="30" />
</label>
{#if $showRouteBuffer}
<label>
{totalPopulationInBuffer.toLocaleString()} people live in this buffer.
Show:
<input type="checkbox" bind:checked={$showRouteBufferPopulation} />
</label>
<SequentialLegend {colorScale} limits={limits.map((l) => l / 60)} />
{/if}
{/if}
</div>
<div slot="map">
{#if input}
Expand All @@ -58,14 +112,19 @@

{#if output}
<GeoJSON data={output} generateId>
<LineLayer
paint={{
"line-width": 20,
"line-color": "red",
"line-opacity": hoverStateFilter(0.5, 1.0),
}}
manageHoverState
/>
{#if $showRouteBuffer}
<BufferLayer {totalPopulationInBuffer} {limits} />
{:else}
<LineLayer
filter={["==", ["get", "kind"], "route"]}
paint={{
"line-width": 20,
"line-color": "red",
"line-opacity": hoverStateFilter(0.5, 1.0),
}}
manageHoverState
/>
{/if}
</GeoJSON>
{/if}
</div>
Expand Down
8 changes: 6 additions & 2 deletions web/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,18 +201,22 @@ export class Backend {
);
}

snapRoute(req: {
snapAndBufferRoute(req: {
input: FeatureCollection;
mode: TravelMode;
startTime: string;
maxSeconds: number;
}): FeatureCollection {
if (!this.inner) {
throw new Error("Backend used without a file loaded");
}

return JSON.parse(
this.inner.snapRoute({
this.inner.snapAndBufferRoute({
input: JSON.stringify(req.input),
mode: req.mode,
start_time: req.startTime,
max_seconds: req.maxSeconds,
}),
);
}
Expand Down

0 comments on commit b66432f

Please sign in to comment.