Skip to content

Commit

Permalink
Optionally include perimeter roads in the analysis. #19, #49, #58
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Dec 24, 2024
1 parent 07b5cb5 commit dd8ee47
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 28 deletions.
14 changes: 10 additions & 4 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,20 @@ impl LTN {
}

#[wasm_bindgen(js_name = setCurrentNeighbourhood)]
pub fn set_current_neighbourhood(&mut self, name: String) -> Result<(), JsValue> {
pub fn set_current_neighbourhood(
&mut self,
name: String,
edit_perimeter_roads: bool,
) -> Result<(), JsValue> {
info!("setCurrentNeighbourhood");
let boundary_gj = self.map.boundaries.get(&name).cloned().unwrap();
let mut boundary_geo: Polygon = boundary_gj.try_into().map_err(err_to_js)?;
self.map.mercator.to_mercator_in_place(&mut boundary_geo);

self.neighbourhood =
Some(Neighbourhood::new(&self.map, name, boundary_geo).map_err(err_to_js)?);
self.neighbourhood = Some(
Neighbourhood::new(&self.map, name, boundary_geo, edit_perimeter_roads)
.map_err(err_to_js)?,
);
info!("done");
Ok(())
}
Expand All @@ -175,7 +181,7 @@ impl LTN {
x: pos.lng,
y: pos.lat,
}),
Some(&self.neighbourhood.as_ref().unwrap().interior_roads),
Some(self.neighbourhood.as_ref().unwrap().editable_roads()),
FilterKind::from_string(&kind).unwrap(),
);
self.after_edit();
Expand Down
12 changes: 5 additions & 7 deletions backend/src/map_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl MapModel {
pub fn add_modal_filter(
&mut self,
pt: Coord,
candidate_roads: Option<&BTreeSet<RoadID>>,
candidate_roads: Option<Vec<RoadID>>,
kind: FilterKind,
) {
let cmd = self.do_edit(self.add_modal_filter_cmd(pt, candidate_roads, kind));
Expand All @@ -130,7 +130,7 @@ impl MapModel {
fn add_modal_filter_cmd(
&self,
pt: Coord,
candidate_roads: Option<&BTreeSet<RoadID>>,
candidate_roads: Option<Vec<RoadID>>,
kind: FilterKind,
) -> Command {
let (r, percent_along) = self.closest_point_on_road(pt, candidate_roads).unwrap();
Expand All @@ -146,18 +146,16 @@ impl MapModel {
fn closest_point_on_road(
&self,
click_pt: Coord,
candidate_roads: Option<&BTreeSet<RoadID>>,
candidate_roads: Option<Vec<RoadID>>,
) -> Option<(RoadID, f64)> {
// If candidate_roads is not specified, search around the point with a generous buffer
let roads: Vec<RoadID> = if let Some(set) = candidate_roads {
set.iter().cloned().collect()
} else {
let roads = candidate_roads.unwrap_or_else(|| {
let bbox = buffer_aabb(AABB::from_point(click_pt.into()), 50.0);
self.closest_road
.locate_in_envelope_intersecting(&bbox)
.map(|r| r.data)
.collect()
};
});

roads
.into_iter()
Expand Down
35 changes: 29 additions & 6 deletions backend/src/neighbourhood.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub struct Neighbourhood {
// Mercator
pub boundary_polygon: Polygon,
boundary_area_km2: f64,
/// If true, shortcuts across perimeter roads will be calculated, and the user can edit these
/// roads.
pub edit_perimeter_roads: bool,

// Updated after mutations
derived: Option<DerivedNeighbourhoodState>,
Expand All @@ -33,7 +36,12 @@ struct DerivedNeighbourhoodState {
}

impl Neighbourhood {
pub fn new(map: &MapModel, name: String, boundary_polygon: Polygon) -> Result<Self> {
pub fn new(
map: &MapModel,
name: String,
boundary_polygon: Polygon,
edit_perimeter_roads: bool,
) -> Result<Self> {
info!("match roads");
let bbox = buffer_aabb(aabb(&boundary_polygon), 50.0);

Expand Down Expand Up @@ -88,6 +96,7 @@ impl Neighbourhood {
name,
boundary_polygon,
boundary_area_km2,
edit_perimeter_roads,
derived: None,
};
n.after_edit(map);
Expand All @@ -111,6 +120,18 @@ impl Neighbourhood {
}
}

pub fn editable_roads(&self) -> Vec<RoadID> {
if self.edit_perimeter_roads {
self.interior_roads
.iter()
.chain(self.crosses.keys())
.cloned()
.collect()
} else {
self.interior_roads.iter().cloned().collect()
}
}

pub fn to_gj(&self, map: &MapModel) -> FeatureCollection {
let mut features = Vec::new();

Expand All @@ -119,27 +140,29 @@ impl Neighbourhood {
// Just one boundary
features.push(map.boundaries.get(&self.name).cloned().unwrap());

for r in &self.interior_roads {
let road = map.get_r(*r);
for r in self.editable_roads() {
let road = map.get_r(r);
let mut f = road.to_gj(&map.mercator);
f.set_property("kind", "interior_road");
f.set_property(
"shortcuts",
derived
.shortcuts
.count_per_road
.get(r)
.get(&r)
.cloned()
.unwrap_or(0),
);
f.set_property("direction", map.directions[r].to_string());
f.set_property("direction", map.directions[&r].to_string());
f.set_property(
"direction_edited",
map.directions[r] != Direction::from_osm(&road.tags),
map.directions[&r] != Direction::from_osm(&road.tags),
);
f.set_property("road", r.0);
features.push(f);
}

// Only for debugging
for (r, pct) in &self.crosses {
let mut f = map.get_r(*r).to_gj(&map.mercator);
f.set_property("kind", "crosses");
Expand Down
8 changes: 4 additions & 4 deletions backend/src/shortcuts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ impl Shortcuts {
let mut input_graph = InputGraph::new();
let mut node_map = NodeMap::new();

for r in &neighbourhood.interior_roads {
if map.modal_filters.contains_key(r) {
for r in neighbourhood.editable_roads() {
if map.modal_filters.contains_key(&r) {
continue;
}
let road = map.get_r(*r);
let road = map.get_r(r);
// Loops can't be part of a shortest path
if road.src_i == road.dst_i {
continue;
Expand All @@ -35,7 +35,7 @@ impl Shortcuts {
let i1 = node_map.get_or_insert(road.src_i);
let i2 = node_map.get_or_insert(road.dst_i);
let cost = (road.length() * 100.0) as usize;
match map.directions[r] {
match map.directions[&r] {
Direction::Forwards => {
input_graph.add_edge(i1, i2, cost);
}
Expand Down
10 changes: 9 additions & 1 deletion backend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ fn get_gj(study_area_name: &str, savefile_name: &str, neighbourhood_name: &str)
let boundary_gj = map.boundaries.get(neighbourhood_name).cloned().unwrap();
let mut boundary_geo: Polygon = boundary_gj.try_into()?;
map.mercator.to_mercator_in_place(&mut boundary_geo);
let neighbourhood = Neighbourhood::new(&map, neighbourhood_name.to_string(), boundary_geo)?;
let edit_perimeter_roads = false;
let neighbourhood = Neighbourhood::new(
&map,
neighbourhood_name.to_string(),
boundary_geo,
edit_perimeter_roads,
)?;

let mut gj = prune_features(neighbourhood.to_gj(&map));

Expand All @@ -59,6 +65,7 @@ fn get_gj(study_area_name: &str, savefile_name: &str, neighbourhood_name: &str)
f.set_property("kind", "existing_modal_filter");
f.remove_property("road");
f.remove_property("edited");
f.remove_property("angle");
gj.features.push(f);
}

Expand All @@ -85,6 +92,7 @@ fn prune_features(mut gj: FeatureCollection) -> FeatureCollection {
"way",
]
.contains(&k.as_str())
&& k != "angle"
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions web/src/AutoBoundariesMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { Link, Popup, layerId } from "./common";
import { isLine, isPolygon } from "svelte-utils/map";
import { SplitComponent } from "svelte-utils/top_bar_layout";
import { app, mode, autosave } from "./stores";
import { app, mode, autosave, editPerimeterRoads } from "./stores";
import { downloadGeneratedFile } from "svelte-utils";
import { pickNeighbourhoodName } from "./common/pick_names";
Expand All @@ -38,7 +38,7 @@
};
$app!.setNeighbourhoodBoundary(name, feature);
autosave();
$app!.setCurrentNeighbourhood(name);
$app!.setCurrentNeighbourhood(name, $editPerimeterRoads);
$mode = {
mode: "neighbourhood",
};
Expand Down
10 changes: 8 additions & 2 deletions web/src/NetworkMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
import { layerId, Popup, Link, HelpButton } from "./common";
import ModalFilterLayer from "./ModalFilterLayer.svelte";
import { SplitComponent } from "svelte-utils/top_bar_layout";
import { app, autosave, mode, projectName } from "./stores";
import {
app,
autosave,
mode,
projectName,
editPerimeterRoads,
} from "./stores";
import { pickNeighbourhoodName } from "./common/pick_names";
// Note we do this to trigger a refresh when loading stuff
Expand All @@ -17,7 +23,7 @@
.map((f: Feature) => f.properties!.name);
function pickNeighbourhood(name: string) {
$app!.setCurrentNeighbourhood(name);
$app!.setCurrentNeighbourhood(name, $editPerimeterRoads);
$mode = { mode: "neighbourhood" };
}
Expand Down
4 changes: 2 additions & 2 deletions web/src/SetBoundaryMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { notNull } from "svelte-utils";
import AreaControls from "./common/draw_area/AreaControls.svelte";
import { calculateArea, waypoints } from "./common/draw_area/stores";
import { autosave, app, mode, map } from "./stores";
import { autosave, app, mode, map, editPerimeterRoads } from "./stores";
import type { AreaProps } from "route-snapper-ts";
export let name: string;
Expand Down Expand Up @@ -36,7 +36,7 @@
let feature = calculateArea($waypoints);
$app!.setNeighbourhoodBoundary(name, feature);
autosave();
$app!.setCurrentNeighbourhood(name);
$app!.setCurrentNeighbourhood(name, $editPerimeterRoads);
$mode = {
mode: "neighbourhood",
};
Expand Down
18 changes: 18 additions & 0 deletions web/src/edit/NeighbourhoodMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
filterType,
autosave,
animateShortcuts,
editPerimeterRoads,
} from "../stores";
import ChangeModalFilter from "./ChangeModalFilter.svelte";
import FreehandLine from "./FreehandLine.svelte";
Expand Down Expand Up @@ -68,6 +69,14 @@
autosave();
}
function recalculateNeighbourhoodDefinition() {
$app!.setCurrentNeighbourhood(
boundary!.properties.name,
$editPerimeterRoads,
);
$mutationCounter++;
}
function onClickLine(f: Feature, pt: LngLat) {
if (action == "filter") {
$app!.addModalFilter(pt, $filterType);
Expand Down Expand Up @@ -239,6 +248,15 @@
Animate shortcuts
</label>

<label>
<input
type="checkbox"
bind:checked={$editPerimeterRoads}
on:change={recalculateNeighbourhoodDefinition}
/>
Include perimeter roads
</label>

<div style="display: flex; justify-content: space-between;">
<button disabled={undoLength == 0} on:click={undo} data-tooltip="Ctrl+Z">
{#if undoLength == 0}
Expand Down
1 change: 1 addition & 0 deletions web/src/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export let mutationCounter: Writable<number> = writable(1);
export let mode: Writable<Mode> = writable({ mode: "title" });
export let filterType: Writable<string> = writable("walk_cycle_only");
export let animateShortcuts = writable(false);
export let editPerimeterRoads = writable(false);

export function autosave() {
let key = get(projectName);
Expand Down

0 comments on commit dd8ee47

Please sign in to comment.