Skip to content

Commit 5908a5c

Browse files
committed
Different method for rendering filters and the rest of the
neighbourhood. Show filters on the network mode.
1 parent c3d4c85 commit 5908a5c

11 files changed

+92
-60
lines changed

backend/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ impl LTN {
9595
bytes
9696
}
9797

98+
#[wasm_bindgen(js_name = renderModalFilters)]
99+
pub fn render_modal_filters(&self) -> Result<String, JsValue> {
100+
Ok(serde_json::to_string(&self.map.filters_to_gj()).map_err(err_to_js)?)
101+
}
102+
98103
#[wasm_bindgen(js_name = renderNeighbourhood)]
99104
pub fn render_neighbourhood(&self) -> Result<String, JsValue> {
100105
Ok(

backend/src/map_model.rs

+20-12
Original file line numberDiff line numberDiff line change
@@ -213,27 +213,35 @@ impl MapModel {
213213
self.after_edited();
214214
}
215215

216-
pub fn to_savefile(&self) -> GeoJson {
216+
pub fn filters_to_gj(&self) -> FeatureCollection {
217217
let mut features = Vec::new();
218-
219-
// A point per modal filter
220-
// (When we detect existing, maybe need to instead compact edits)
221-
for (r, modal_filter) in &self.modal_filters {
218+
for (r, filter) in &self.modal_filters {
222219
let pt = self
223220
.get_r(*r)
224221
.linestring
225-
.line_interpolate_point(modal_filter.percent_along)
222+
.line_interpolate_point(filter.percent_along)
226223
.unwrap();
227-
// TODO Maybe make the WASM API always do the mercator stuff
228224
let mut f = Feature::from(Geometry::from(&self.mercator.to_wgs84(&pt)));
229-
f.set_property("kind", "modal_filter");
230-
f.set_property("filter_kind", modal_filter.kind.to_string());
225+
f.set_property("filter_kind", filter.kind.to_string());
226+
f.set_property("road", r.0);
231227
features.push(f);
232228
}
229+
FeatureCollection {
230+
features,
231+
bbox: None,
232+
foreign_members: None,
233+
}
234+
}
233235

234-
features.extend(self.boundaries.values().cloned());
235-
236-
GeoJson::from(features)
236+
pub fn to_savefile(&self) -> FeatureCollection {
237+
let mut gj = self.filters_to_gj();
238+
// TODO When we detect existing filters, maybe need to instead compact edits
239+
for f in &mut gj.features {
240+
f.set_property("kind", "modal_filter");
241+
f.remove_property("road");
242+
}
243+
gj.features.extend(self.boundaries.values().cloned());
244+
gj
237245
}
238246

239247
pub fn load_savefile(&mut self, gj: FeatureCollection) -> Result<()> {

backend/src/neighbourhood.rs

+1-15
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use std::collections::{HashMap, HashSet};
22

33
use anyhow::Result;
44
use geo::{
5-
BooleanOps, Contains, EuclideanDistance, EuclideanLength, Intersects, LineInterpolatePoint,
6-
MultiLineString, Polygon,
5+
BooleanOps, Contains, EuclideanDistance, EuclideanLength, Intersects, MultiLineString, Polygon,
76
};
87
use geojson::{Feature, FeatureCollection, Geometry};
98

@@ -115,19 +114,6 @@ impl Neighbourhood {
115114
features.push(f);
116115
}
117116

118-
for (r, filter) in &map.modal_filters {
119-
let pt = map
120-
.get_r(*r)
121-
.linestring
122-
.line_interpolate_point(filter.percent_along)
123-
.unwrap();
124-
let mut f = Feature::from(Geometry::from(&map.mercator.to_wgs84(&pt)));
125-
f.set_property("kind", "modal_filter");
126-
f.set_property("filter_kind", filter.kind.to_string());
127-
f.set_property("road", r.0);
128-
features.push(f);
129-
}
130-
131117
FeatureCollection {
132118
features,
133119
bbox: None,

web/src/DebugMode.svelte

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { CircleLayer, LineLayer, Popup } from "svelte-maplibre";
2+
import { CircleLayer, GeoJSON, LineLayer, Popup } from "svelte-maplibre";
33
import { notNull, PropertiesTable } from "./common";
44
import RenderNeighbourhood from "./RenderNeighbourhood.svelte";
55
import SplitComponent from "./SplitComponent.svelte";
@@ -28,11 +28,6 @@
2828
<PropertiesTable properties={notNull(data).properties} />
2929
</Popup>
3030
</div>
31-
<div slot="circle-popup">
32-
<Popup openOn="hover" let:data>
33-
<PropertiesTable properties={notNull(data).properties} />
34-
</Popup>
35-
</div>
3631
<svelte:fragment slot="more-layers">
3732
<CircleLayer
3833
filter={["==", ["get", "kind"], "border_intersection"]}
@@ -58,5 +53,18 @@
5853
</LineLayer>
5954
</svelte:fragment>
6055
</RenderNeighbourhood>
56+
57+
<GeoJSON data={notNull($app).renderModalFilters()} generateId>
58+
<CircleLayer
59+
paint={{
60+
"circle-radius": 15,
61+
"circle-color": "black",
62+
}}
63+
>
64+
<Popup openOn="hover" let:data>
65+
<PropertiesTable properties={notNull(data).properties} />
66+
</Popup>
67+
</CircleLayer>
68+
</GeoJSON>
6169
</div>
6270
</SplitComponent>

web/src/ModalFilterLayer.svelte

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script lang="ts">
2+
import { CircleLayer, GeoJSON } from "svelte-maplibre";
3+
import { app, mutationCounter } from "./stores";
4+
5+
// TODO Runes would make this so nicer. The > 0 part is a hack...
6+
$: gj = $mutationCounter > 0 ? JSON.parse($app!.renderModalFilters()) : null;
7+
</script>
8+
9+
<GeoJSON data={gj}>
10+
<CircleLayer
11+
paint={{
12+
"circle-radius": 15,
13+
"circle-color": "black",
14+
}}
15+
/>
16+
</GeoJSON>

web/src/NetworkMode.svelte

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { FillLayer, GeoJSON, hoverStateFilter, Popup } from "svelte-maplibre";
44
import { isPolygon, notNull } from "./common";
55
import ManageSavefiles from "./ManageSavefiles.svelte";
6+
import ModalFilterLayer from "./ModalFilterLayer.svelte";
67
import SplitComponent from "./SplitComponent.svelte";
78
import { app, mode } from "./stores";
89
@@ -88,6 +89,7 @@
8889
<p>{notNull(data).properties.name}</p>
8990
</Popup>
9091
</FillLayer>
92+
<ModalFilterLayer />
9193
</GeoJSON>
9294
</div>
9395
</SplitComponent>

web/src/RenderNeighbourhood.svelte

+2-16
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<script lang="ts">
22
import type { Feature, FeatureCollection } from "geojson";
3-
import { CircleLayer, FillLayer, GeoJSON, LineLayer } from "svelte-maplibre";
4-
import { constructMatchExpression, isPolygon } from "./common";
3+
import { FillLayer, GeoJSON, LineLayer } from "svelte-maplibre";
4+
import { isPolygon } from "./common";
55
import { showBasemap } from "./stores";
66
77
export let gjInput: FeatureCollection;
88
// When disabled, can't click lines or circles, no slots, no hoverCursor
99
export let interactive = true;
1010
export let onClickLine = (f: Feature) => {};
11-
export let onClickCircle = (f: Feature) => {};
1211
1312
let gj: FeatureCollection;
1413
let maxShortcuts: number;
@@ -82,18 +81,5 @@
8281
<slot name="line-popup" />
8382
{/if}
8483
</LineLayer>
85-
86-
<CircleLayer
87-
filter={["==", ["get", "kind"], "modal_filter"]}
88-
paint={{
89-
"circle-radius": 15,
90-
"circle-color": "black",
91-
}}
92-
on:click={(e) => interactive && onClickCircle(e.detail.features[0])}
93-
>
94-
{#if interactive}
95-
<slot name="circle-popup" />
96-
{/if}
97-
</CircleLayer>
9884
<slot name="more-layers" />
9985
</GeoJSON>

web/src/RouteMode.svelte

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { onDestroy, onMount } from "svelte";
44
import { GeoJSON, LineLayer, Marker } from "svelte-maplibre";
55
import { constructMatchExpression, notNull } from "./common";
6+
import ModalFilterLayer from "./ModalFilterLayer.svelte";
67
import RenderNeighbourhood from "./RenderNeighbourhood.svelte";
78
import SplitComponent from "./SplitComponent.svelte";
89
import { app, map, mode } from "./stores";
@@ -58,6 +59,7 @@
5859
gjInput={JSON.parse(notNull($app).renderNeighbourhood())}
5960
interactive={false}
6061
/>
62+
<ModalFilterLayer />
6163
<GeoJSON data={gj}>
6264
<LineLayer
6365
paint={{

web/src/ViewShortcutsMode.svelte

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { onDestroy, onMount } from "svelte";
44
import { GeoJSON, LineLayer, Popup } from "svelte-maplibre";
55
import { notNull } from "./common";
6+
import ModalFilterLayer from "./ModalFilterLayer.svelte";
67
import RenderNeighbourhood from "./RenderNeighbourhood.svelte";
78
import SplitComponent from "./SplitComponent.svelte";
89
import { app, map, mode } from "./stores";
@@ -127,5 +128,6 @@
127128
</GeoJSON>
128129
{/if}
129130
{/if}
131+
<ModalFilterLayer />
130132
</div>
131133
</SplitComponent>

web/src/edit/NeighbourhoodMode.svelte

+25-11
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
} from "geojson";
88
import type { MapMouseEvent } from "maplibre-gl";
99
import { onDestroy } from "svelte";
10-
import { Popup } from "svelte-maplibre";
10+
import { CircleLayer, GeoJSON, Popup } from "svelte-maplibre";
1111
import { notNull } from "../common";
1212
import ManageSavefiles from "../ManageSavefiles.svelte";
1313
import RenderNeighbourhood from "../RenderNeighbourhood.svelte";
1414
import SplitComponent from "../SplitComponent.svelte";
15-
import { app, map, mode } from "../stores";
15+
import { app, map, mode, mutationCounter } from "../stores";
1616
import ChangeModalFilter from "./ChangeModalFilter.svelte";
1717
import FreehandLine from "./FreehandLine.svelte";
1818
@@ -27,9 +27,10 @@
2727
let boundary: Feature<Polygon> | null;
2828
2929
let gjInput: FeatureCollection;
30-
rerender();
30+
let modalFilterGj: FeatureCollection;
31+
$: rerender($mutationCounter);
3132
32-
function rerender() {
33+
function rerender(_x: number) {
3334
gjInput = JSON.parse($app!.renderNeighbourhood());
3435
boundary = gjInput.features.find(
3536
(f) => f.properties!.kind == "boundary"
@@ -39,6 +40,8 @@
3940
undoLength = gjInput.undo_length;
4041
// @ts-ignore These foreign members exist
4142
redoLength = gjInput.redo_length;
43+
44+
modalFilterGj = JSON.parse($app!.renderModalFilters());
4245
}
4346
4447
$: if (addingFilter) {
@@ -51,7 +54,7 @@
5154
});
5255
function onClick(e: MapMouseEvent) {
5356
$app!.addModalFilter(e.lngLat, filterType);
54-
rerender();
57+
$mutationCounter++;
5558
stopAddingFilter();
5659
}
5760
function stopAddingFilter() {
@@ -60,10 +63,11 @@
6063
$map!.getCanvas().style.cursor = "inherit";
6164
}
6265
63-
function deleteFilter(f: Feature) {
66+
function deleteFilter(e) {
67+
let f = e.detail.features[0];
6468
if (f.properties!.kind == "modal_filter") {
6569
$app!.deleteModalFilter(f.properties!.road);
66-
rerender();
70+
$mutationCounter++;
6771
}
6872
}
6973
@@ -80,11 +84,11 @@
8084
}
8185
function undo() {
8286
$app!.undo();
83-
rerender();
87+
$mutationCounter++;
8488
}
8589
function redo() {
8690
$app!.redo();
87-
rerender();
91+
$mutationCounter++;
8892
}
8993
9094
function pickNewNeighbourhood() {
@@ -97,7 +101,7 @@
97101
let f = e.detail;
98102
if (f) {
99103
$app!.addManyModalFilters(f, filterType);
100-
rerender();
104+
$mutationCounter++;
101105
}
102106
103107
addingMultipleFilters = false;
@@ -210,14 +214,24 @@
210214
<RenderNeighbourhood
211215
{gjInput}
212216
interactive={!addingFilter && !addingMultipleFilters}
213-
onClickCircle={deleteFilter}
214217
>
215218
<div slot="line-popup">
216219
<Popup openOn="hover" let:data
217220
><p>{notNull(data).properties.shortcuts} shortcuts</p></Popup
218221
>
219222
</div>
220223
</RenderNeighbourhood>
224+
<GeoJSON data={modalFilterGj} generateId>
225+
<CircleLayer
226+
paint={{
227+
"circle-radius": 15,
228+
"circle-color": "black",
229+
}}
230+
on:click={deleteFilter}
231+
>
232+
<Popup openOn="hover">Click to delete</Popup>
233+
</CircleLayer>
234+
</GeoJSON>
221235
{#if addingMultipleFilters}
222236
<FreehandLine map={notNull($map)} on:done={gotFreehandLine} />
223237
{/if}

web/src/stores.ts

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export type Mode =
2929
};
3030

3131
export let app: Writable<LTN | null> = writable(null);
32+
// A way for different components to know when internal app state has changed
33+
// and they might need to rerender
34+
export let mutationCounter: Writable<number> = writable(1);
3235
export let mode: Writable<Mode> = writable({ mode: "title" });
3336
export let showBasemap: Writable<boolean> = writable(true);
3437
export let map: Writable<Map | null> = writable(null);

0 commit comments

Comments
 (0)