Skip to content

Commit

Permalink
Configure the starting POI types, discovering the possible set automa…
Browse files Browse the repository at this point in the history
…tically. #5
  • Loading branch information
dabreegster committed Jun 2, 2024
1 parent 216a56d commit 4cd9081
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 21 deletions.
17 changes: 15 additions & 2 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extern crate anyhow;
#[macro_use]
extern crate log;

use std::collections::HashSet;
use std::sync::Once;

use chrono::NaiveTime;
Expand Down Expand Up @@ -150,8 +151,15 @@ impl MapModel {
}

#[wasm_bindgen(js_name = score)]
pub fn score(&self, progress_cb: Option<js_sys::Function>) -> Result<String, JsValue> {
score::calculate(&self.graph, Timer::new("score", progress_cb)).map_err(err_to_js)
pub fn score(
&self,
input: JsValue,
progress_cb: Option<js_sys::Function>,
) -> Result<String, JsValue> {
let req: ScoreRequest = serde_wasm_bindgen::from_value(input)?;
let poi_kinds: HashSet<String> = req.poi_kinds.into_iter().collect();
score::calculate(&self.graph, poi_kinds, Timer::new("score", progress_cb))
.map_err(err_to_js)
}
}

Expand All @@ -177,6 +185,11 @@ pub struct RouteRequest {
start_time: String,
}

#[derive(Deserialize)]
pub struct ScoreRequest {
poi_kinds: Vec<String>,
}

fn err_to_js<E: std::fmt::Display>(err: E) -> JsValue {
JsValue::from_str(&err.to_string())
}
Expand Down
7 changes: 3 additions & 4 deletions backend/src/score.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::time::Duration;

use anyhow::Result;
Expand All @@ -9,8 +9,7 @@ use crate::timer::Timer;

// Return GeoJSON points for each POI, with info about that POI, a score to the nearest cycle
// parking, and the location of that parking
pub fn calculate(graph: &Graph, mut timer: Timer) -> Result<String> {
let poi_kinds = ["cafe", "pub", "restaurant", "bank", "nightclub"];
pub fn calculate(graph: &Graph, poi_kinds: HashSet<String>, mut timer: Timer) -> Result<String> {
let limit = Duration::from_secs(10 * 60);
// Exact time doesn't matter
let start_time = NaiveTime::from_hms_opt(7, 0, 0).unwrap();
Expand All @@ -34,7 +33,7 @@ pub fn calculate(graph: &Graph, mut timer: Timer) -> Result<String> {
));
let mut features = Vec::new();
for amenity in &graph.amenities {
if !poi_kinds.contains(&amenity.kind.as_str()) {
if !poi_kinds.contains(&amenity.kind) {
continue;
}

Expand Down
38 changes: 24 additions & 14 deletions web/src/ScoreMode.svelte
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
<script lang="ts">
import * as Comlink from "comlink";
import { Loading, NavBar } from "./common";
import { Loading, NavBar, PickAmenityKinds } from "./common";
import type { Feature, FeatureCollection, Point } from "geojson";
import { colorScale } from "./colors";
import { GeoJSON, CircleLayer, LineLayer } from "svelte-maplibre";
import { SplitComponent } from "svelte-utils/top_bar_layout";
import { backend, type ScoreProps } from "./stores";
import { SequentialLegend } from "svelte-utils";
import { Popup, makeColorRamp } from "svelte-utils/map";
import { onMount } from "svelte";
let loading: string[] = [];
let poiKinds: string[] = [];
let gj: FeatureCollection<Point, ScoreProps> | null = null;
onMount(async () => {
loading = ["Calculating scores"];
gj = await $backend!.score(Comlink.proxy(progressCb));
$: updateScores(poiKinds);
async function updateScores(_x: string[]) {
loading = [...loading, "Calculating scores"];
gj = await $backend!.score(
{
poiKinds,
},
Comlink.proxy(progressCb),
);
loading = [];
});
}
function progressCb(msg: string) {
loading = [...loading, msg];
}
Expand Down Expand Up @@ -67,17 +75,19 @@
<div slot="sidebar">
<h2>Score mode</h2>

<PickAmenityKinds bind:enabled={poiKinds} />

<SequentialLegend {colorScale} {limits} />

<p>
This is an early experiment of a mode to show an "access score". Right
now, it's starting from every POI of a few fixed types (cafe, pub,
restaurant, bank, nightclub) and walking up to one minute to the nearest
bicycle parking. This is a simple way of showing POIs without any nearby
parking. Note the granularity of results is poor; the search begins and
ends at the nearest intersection, and the time to walk doesn't take into
account the side of the road or walking partly down some road.
now, it's starting from every POI based on the types chosen below and
walking up to 10 minutes to the nearest bicycle parking. This is a simple
way of showing POIs without any nearby parking. Note the granularity of
results is poor; the search begins and ends at the nearest intersection,
and the time to walk doesn't take into account the side of the road or
walking partly down some road.
</p>

<SequentialLegend {colorScale} {limits} />
</div>
<div slot="map">
{#if gj}
Expand Down
58 changes: 58 additions & 0 deletions web/src/common/PickAmenityKinds.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script lang="ts">
import { backend } from "../stores";
import { onMount } from "svelte";
import { Modal, notNull } from "svelte-utils";
let kinds: { [name: string]: boolean } = {};
let show = false;
export let enabled: string[];
$: enabled = getEnabled(kinds);
onMount(async () => {
let gj = await $backend!.renderDebug();
let allKinds: Set<string> = new Set();
for (let f of gj.features) {
let kind: string | undefined = f.properties!.amenity_kind;
if (kind) {
allKinds.add(kind);
}
}
// Make the order work
for (let kind of [...allKinds].sort()) {
kinds[kind] = false;
}
kinds = kinds;
});
function getEnabled(kinds: { [name: string]: boolean }): string[] {
return Object.entries(kinds)
.filter((pair) => pair[1])
.map((pair) => pair[0]);
}
</script>

{#if show}
<Modal on:close={() => (show = false)} let:dialog>
<h2>Pick types of amenity to search from</h2>

<fieldset>
<legend>Amenities:</legend>

{#each Object.keys(kinds) as key}
<label>
<input type="checkbox" bind:checked={kinds[key]} />
{key}
</label>
{/each}
</fieldset>

<center
><button on:click={() => notNull(dialog).close()}>Start!</button></center
>
</Modal>
{:else}
<p>Amenities: {enabled.join(", ")}</p>
<button on:click={() => (show = true)}>Choose</button>
{/if}
1 change: 1 addition & 0 deletions web/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export { default as AmenityLayer } from "./AmenityLayer.svelte";
export { default as AmenityList } from "./AmenityList.svelte";
export { default as Loading } from "./Loading.svelte";
export { default as NavBar } from "./NavBar.svelte";
export { default as PickAmenityKinds } from "./PickAmenityKinds.svelte";
export { default as PickTravelMode } from "./PickTravelMode.svelte";
export { default as StopsLayer } from "./StopsLayer.svelte";
12 changes: 11 additions & 1 deletion web/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,23 @@ export class Backend {
}

score(
req: {
poiKinds: string[];
},
progressCb: (msg: string) => void,
): FeatureCollection<Point, ScoreProps> {
if (!this.inner) {
throw new Error("Backend used without a file loaded");
}

return JSON.parse(this.inner.score(progressCb));
return JSON.parse(
this.inner.score(
{
poi_kinds: req.poiKinds,
},
progressCb,
),
);
}
}

Expand Down

0 comments on commit 4cd9081

Please sign in to comment.