Skip to content

Commit

Permalink
Plumb and start to display info about the timing of transit routes
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed May 25, 2024
1 parent d4a1b5e commit 7c78f60
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 29 deletions.
4 changes: 2 additions & 2 deletions backend/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::cell::RefCell;
use anyhow::{bail, Result};
use fast_paths::{deserialize_32, serialize_32, FastGraph, InputGraph, PathCalculator};
use geo::LineString;
use geojson::{Feature, Geometry};
use geojson::{Feature, GeoJson, Geometry};
use serde::{Deserialize, Serialize};
use utils::{deserialize_nodemap, NodeMap};

Expand Down Expand Up @@ -91,6 +91,6 @@ impl Router {
&graph.mercator.to_wgs84(&LineString::new(pts)),
));
f.set_property("kind", "road");
Ok(serde_json::to_string(&f)?)
Ok(serde_json::to_string(&GeoJson::from(vec![f]))?)
}
}
65 changes: 43 additions & 22 deletions backend/src/transit_route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ pub fn route(
//
// or rethink the nodes and edges in the graph. nodes are pathsteps -- a road in some direction
// or a transit thing. an edge is a turn or a transition to/from transit
let mut backrefs: HashMap<IntersectionID, (IntersectionID, PathStep)> = HashMap::new();

let mut backrefs: HashMap<IntersectionID, Backreference> = HashMap::new();

timer.step("dijkstra");
let mut queue: BinaryHeap<PriorityQueueItem<NaiveTime, IntersectionID>> = BinaryHeap::new();
Expand All @@ -50,24 +51,28 @@ pub fn route(
let total_cost = current.cost + cost(road, Mode::Foot);
if road.src_i == current.value && road.allows_forwards(Mode::Foot) {
if let Entry::Vacant(entry) = backrefs.entry(road.dst_i) {
entry.insert((
current.value,
PathStep::Road {
entry.insert(Backreference {
src_i: current.value,
step: PathStep::Road {
road: *r,
forwards: true,
},
));
time1: current.cost,
time2: total_cost,
});
queue.push(PriorityQueueItem::new(total_cost, road.dst_i));
}
} else if road.dst_i == current.value && road.allows_backwards(Mode::Foot) {
if let Entry::Vacant(entry) = backrefs.entry(road.src_i) {
entry.insert((
current.value,
PathStep::Road {
entry.insert(Backreference {
src_i: current.value,
step: PathStep::Road {
road: *r,
forwards: false,
},
));
time1: current.cost,
time2: total_cost,
});
queue.push(PriorityQueueItem::new(total_cost, road.src_i));
}
}
Expand All @@ -86,14 +91,16 @@ pub fn route(
let stop2_road = &graph.roads[graph.gtfs.stops[next_step.stop2.0].road.0];
for i in [stop2_road.src_i, stop2_road.dst_i] {
if let Entry::Vacant(entry) = backrefs.entry(i) {
entry.insert((
current.value,
PathStep::Transit {
entry.insert(Backreference {
src_i: current.value,
step: PathStep::Transit {
stop1: *stop1,
trip: next_step.trip,
stop2: next_step.stop2,
},
));
time1: next_step.time1,
time2: next_step.time2,
});
queue.push(PriorityQueueItem::new(next_step.time2, i));
}
}
Expand All @@ -105,7 +112,17 @@ pub fn route(
bail!("No path found");
}

#[derive(Debug)]
// How'd we get somewhere?
struct Backreference {
// Where were we at the beginning of this step?
src_i: IntersectionID,
step: PathStep,
// When'd we start this step?
time1: NaiveTime,
// When'd we finish?
time2: NaiveTime,
}

enum PathStep {
Road {
road: RoadID,
Expand All @@ -119,30 +136,30 @@ enum PathStep {
}

fn render_path(
backrefs: HashMap<IntersectionID, (IntersectionID, PathStep)>,
mut backrefs: HashMap<IntersectionID, Backreference>,
graph: &Graph,
start: IntersectionID,
end: IntersectionID,
mut timer: Timer,
) -> Result<String> {
timer.step("render");

// Just get PathSteps in order first
let mut steps = Vec::new();
// Just get PathSteps in order first (Step, time1, time2)
let mut steps: Vec<(PathStep, NaiveTime, NaiveTime)> = Vec::new();
let mut at = end;
loop {
if at == start {
break;
}
let (prev_i, step) = &backrefs[&at];
steps.push(step);
at = *prev_i;
let backref = backrefs.remove(&at).unwrap();
steps.push((backref.step, backref.time1, backref.time2));
at = backref.src_i;
}
steps.reverse();

// Assemble PathSteps into features. Group road and transit steps together
let mut features = Vec::new();
for chunk in steps.chunk_by(|a, b| match (a, b) {
for chunk in steps.chunk_by(|a, b| match (&a.0, &b.0) {
(PathStep::Road { .. }, PathStep::Road { .. }) => true,
(PathStep::Transit { trip: trip1, .. }, PathStep::Transit { trip: trip2, .. }) => {
trip1 == trip2
Expand All @@ -152,7 +169,7 @@ fn render_path(
let mut pts = Vec::new();
let mut num_stops = 0;
let mut trip_id = None;
for step in chunk {
for (step, _, _) in chunk {
match step {
PathStep::Road { road, forwards } => {
let road = &graph.roads[road.0];
Expand All @@ -173,9 +190,13 @@ fn render_path(
}
}
pts.dedup();

let mut f = Feature::from(Geometry::from(
&graph.mercator.to_wgs84(&LineString::new(pts)),
));
f.set_property("time1", chunk[0].1.to_string());
f.set_property("time2", chunk.last().unwrap().2.to_string());

if let Some(trip) = trip_id {
f.set_property("kind", "transit");
// TODO Plumb a route name or something
Expand Down
36 changes: 31 additions & 5 deletions web/src/RouteMode.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
<script lang="ts">
import type { MapMouseEvent } from "maplibre-gl";
import { MapEvents, GeoJSON, LineLayer, Marker } from "svelte-maplibre";
import {
MapEvents,
GeoJSON,
LineLayer,
Marker,
hoverStateFilter,
} from "svelte-maplibre";
import { SplitComponent } from "svelte-utils/two_column_layout";
import { mode, backend, type TravelMode } from "./stores";
import PickTravelMode from "./PickTravelMode.svelte";
import { constructMatchExpression } from "svelte-utils/map";
import { Popup, constructMatchExpression } from "svelte-utils/map";
import { notNull, PropertiesTable } from "svelte-utils";
import { onMount } from "svelte";
import type { FeatureCollection } from "geojson";
Expand Down Expand Up @@ -71,8 +78,22 @@
Move the <b>A</b> and <b>B</b> pins to find a route. (Hint: right-click to
set the first pin somewhere.)
</p>

{#if err}
<p>{err}</p>
{:else if gj}
<ol>
{#each gj.features as f}
{@const props = notNull(f.properties)}
{#if props.kind == "road"}
<li>Walk</li>
{:else}
<li>
Take transit (trip {props.trip}) for {props.num_stops} stops
</li>
{/if}
{/each}
</ol>
{/if}
</div>
<div slot="map">
Expand All @@ -86,7 +107,7 @@
{/if}

{#if gj}
<GeoJSON data={gj}>
<GeoJSON data={gj} generateId>
<LineLayer
id="route"
paint={{
Expand All @@ -96,9 +117,14 @@
{ road: "cyan", transit: "purple" },
"red",
),
"line-opacity": 0.5,
"line-opacity": hoverStateFilter(0.5, 1.0),
}}
/>
manageHoverState
>
<Popup openOn="hover" let:props>
<PropertiesTable properties={props} />
</Popup>
</LineLayer>
</GeoJSON>
{/if}
</div>
Expand Down

0 comments on commit 7c78f60

Please sign in to comment.