Skip to content

Commit de1d2a9

Browse files
authored
Diagonal filters (#98)
1 parent 53170d2 commit de1d2a9

28 files changed

+910
-59
lines changed

backend/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ opt-level = 3
4646
[[bench]]
4747
name = "build_neighbourhood"
4848
harness = false
49+
[[bench]]
50+
name = "build_map_model"
51+
harness = false
4952

5053
[patch.crates-io]
5154
# use unreleased `geo` and `geo-types` to fix crash: https://github.com/a-b-street/ltn/issues/54

backend/benches/build_map_model.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use backend::test_fixtures::NeighbourhoodFixture;
2+
3+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
4+
5+
fn benchmark_build_map_model(c: &mut Criterion) {
6+
for neighbourhood in [
7+
NeighbourhoodFixture::BRISTOL_EAST,
8+
NeighbourhoodFixture::STRASBOURG,
9+
] {
10+
// Do the file i/o (reading OSM.xml) outside of the bench loop
11+
let map_model_builder = neighbourhood.map_model_builder().unwrap();
12+
c.bench_function(
13+
&format!(
14+
"build map_model: {name}",
15+
name = neighbourhood.study_area_name
16+
),
17+
|b| {
18+
b.iter(|| {
19+
let map_model = map_model_builder().unwrap();
20+
black_box(map_model);
21+
});
22+
},
23+
);
24+
}
25+
}
26+
27+
criterion_group!(benches, benchmark_build_map_model);
28+
criterion_main!(benches);

backend/benches/build_neighbourhood.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ fn benchmark_build_neighbourhood(c: &mut Criterion) {
88
NeighbourhoodFixture::BRISTOL_WEST,
99
NeighbourhoodFixture::STRASBOURG,
1010
] {
11+
let (map, boundary_geo) = neighbourhood.neighbourhood_params().unwrap();
12+
let edit_perimeter_roads = false;
1113
c.bench_function(
1214
&format!(
1315
"build neighbourhood: {name}",
1416
name = neighbourhood.savefile_name
1517
),
1618
|b| {
17-
let (map, boundary_geo) = neighbourhood.neighbourhood_params().unwrap();
18-
let edit_perimeter_roads = false;
1919
b.iter(|| {
2020
let neighbourhood = Neighbourhood::new(
2121
&map,

backend/src/create.rs

+8-13
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,6 @@ pub fn create_from_osm(
138138
remove_disconnected_components(&mut graph);
139139
graph.compact_ids();
140140

141-
// Copy all the fields
142-
let intersections: Vec<Intersection> = graph
143-
.intersections
144-
.into_values()
145-
.map(|i| Intersection {
146-
id: IntersectionID(i.id.0),
147-
point: i.point,
148-
node: i.osm_node,
149-
roads: i.edges.into_iter().map(|e| RoadID(e.0)).collect(),
150-
turn_restrictions: Vec::new(),
151-
})
152-
.collect();
153-
154141
// Add in a bit
155142
let roads: Vec<Road> = graph
156143
.edges
@@ -166,6 +153,13 @@ pub fn create_from_osm(
166153
})
167154
.collect();
168155

156+
// Copy all the fields
157+
let intersections: Vec<Intersection> = graph
158+
.intersections
159+
.into_values()
160+
.map(|i| Intersection::from_graph(i, &roads))
161+
.collect();
162+
169163
for ls in &mut osm.railways {
170164
graph.mercator.to_mercator_in_place(ls);
171165
}
@@ -213,6 +207,7 @@ pub fn create_from_osm(
213207

214208
original_modal_filters: BTreeMap::new(),
215209
modal_filters: BTreeMap::new(),
210+
diagonal_filters: BTreeMap::new(),
216211

217212
directions,
218213

backend/src/geo_helpers/mod.rs

+131
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,37 @@ pub fn angle_of_line(line: Line) -> f64 {
107107
(line.dy()).atan2(line.dx()).to_degrees()
108108
}
109109

110+
/// North is 0°
111+
/// East is 90°
112+
/// South is 180°
113+
/// West is 270°
114+
pub fn euclidean_bearing(origin: Coord, destination: Coord) -> f64 {
115+
(angle_of_line(Line::new(origin, destination)) + 450.0) % 360.0
116+
}
117+
118+
/// The bearing of the first segment of `linestring` starting from `endpoint`.
119+
///
120+
/// precondition: `endpoint` must be either the first or last point in `linestring`
121+
/// precondition: `linestring` must have at least 2 coordinates
122+
pub fn bearing_from_endpoint(endpoint: Point, linestring: &LineString) -> f64 {
123+
assert!(
124+
linestring.0.len() >= 2,
125+
"zero length roads should be filtered out"
126+
);
127+
let next_coord = if endpoint.0 == linestring.0[0] {
128+
linestring.0[1]
129+
} else if endpoint.0 == linestring.0[linestring.0.len() - 1] {
130+
linestring.0[linestring.0.len() - 2]
131+
} else {
132+
// I'm assuming this won't happen, but maybe it's possible,
133+
// e.g. to different rounding schemes.
134+
debug_assert!(false, "road does not terminate at intersection");
135+
linestring.0[1]
136+
};
137+
138+
euclidean_bearing(endpoint.0, next_coord)
139+
}
140+
110141
pub fn angle_of_pt_on_line(linestring: &LineString, pt: Coord) -> f64 {
111142
let line = linestring
112143
.lines()
@@ -267,3 +298,103 @@ pub fn invert_polygon(wgs84_polygon: Polygon) -> Polygon {
267298
vec![wgs84_polygon.into_inner().0],
268299
)
269300
}
301+
302+
/// The "diagonal line" is an equal angular distance from a and b.
303+
/// The diagonal bearing is the bearing of this "diagonal line".
304+
///
305+
/// That is, given the bearing of a and b, returns the bearing of line c.
306+
///
307+
/// ```ignore
308+
/// a b
309+
/// \ /
310+
/// ∂° \/ ∂°
311+
/// ------------ c
312+
/// ```
313+
pub fn diagonal_bearing(bearing_a: f64, bearing_b: f64) -> f64 {
314+
let angle_between = bearing_b - bearing_a;
315+
(angle_between / 2.0 + 90.0 + bearing_a) % 360.0
316+
}
317+
318+
#[cfg(test)]
319+
mod tests {
320+
use super::*;
321+
use approx::assert_relative_eq;
322+
use geo::wkt;
323+
324+
#[test]
325+
fn test_line_bearing() {
326+
let p1 = Point::new(0.0, 0.0);
327+
328+
// Due South in our projection
329+
assert_relative_eq!(90.0, angle_of_line(Line::new(p1, Point::new(0.0, 1.0))));
330+
// East
331+
assert_relative_eq!(0.0, angle_of_line(Line::new(p1, Point::new(1.0, 0.0))));
332+
// North
333+
assert_relative_eq!(-90.0, angle_of_line(Line::new(p1, Point::new(0.0, -1.0))));
334+
// West
335+
assert_relative_eq!(180.0, angle_of_line(Line::new(p1, Point::new(-1.0, 0.0))));
336+
}
337+
338+
#[test]
339+
fn test_bearing_from_endpoint() {
340+
let p1 = Point::new(0.0, 0.0);
341+
342+
// p1 is start point
343+
344+
// North
345+
assert_relative_eq!(
346+
0.,
347+
bearing_from_endpoint(p1, &wkt!(LINESTRING(0. 0.,0. -1.)))
348+
);
349+
// East
350+
assert_relative_eq!(
351+
90.,
352+
bearing_from_endpoint(p1, &wkt!(LINESTRING(0. 0.,1. 0.)))
353+
);
354+
// South
355+
assert_relative_eq!(
356+
180.,
357+
bearing_from_endpoint(p1, &wkt!(LINESTRING(0. 0.,0. 1.)))
358+
);
359+
// West
360+
assert_relative_eq!(
361+
270.,
362+
bearing_from_endpoint(p1, &wkt!(LINESTRING(0. 0.,-1. 0.)))
363+
);
364+
// Northwest
365+
assert_relative_eq!(
366+
315.,
367+
bearing_from_endpoint(p1, &wkt!(LINESTRING(0. 0.,-1. -1.)))
368+
);
369+
370+
// Flipped - p1 is now the end point, not the start point
371+
372+
// North
373+
assert_relative_eq!(
374+
0.,
375+
bearing_from_endpoint(p1, &wkt!(LINESTRING(0. -1.,0. 0.)))
376+
);
377+
// East
378+
assert_relative_eq!(
379+
90.,
380+
bearing_from_endpoint(p1, &wkt!(LINESTRING(1. 0.,0. 0.)))
381+
);
382+
// South
383+
assert_relative_eq!(
384+
180.,
385+
bearing_from_endpoint(p1, &wkt!(LINESTRING(0. 1., 0. 0.)))
386+
);
387+
// West
388+
assert_relative_eq!(
389+
270.,
390+
bearing_from_endpoint(p1, &wkt!(LINESTRING(-1. 0.,0. 0.)))
391+
);
392+
}
393+
394+
#[test]
395+
fn test_diagonal_angle() {
396+
assert_eq!(135.0, diagonal_bearing(0., 90.));
397+
assert_eq!(270.0, diagonal_bearing(135., 225.));
398+
assert_eq!(270.0, diagonal_bearing(300., 60.));
399+
}
400+
}

backend/src/lib.rs

+28
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,33 @@ impl LTN {
225225
self.after_edit();
226226
}
227227

228+
/// Takes an IntersectionID
229+
#[wasm_bindgen(js_name = addDiagonalFilter)]
230+
pub fn add_diagonal_filter(&mut self, intersection_id: usize) -> Result<(), JsValue> {
231+
self.map
232+
.add_diagonal_filter(IntersectionID(intersection_id));
233+
self.after_edit();
234+
Ok(())
235+
}
236+
237+
/// Takes an IntersectionID
238+
#[wasm_bindgen(js_name = rotateDiagonalFilter)]
239+
pub fn rotate_diagonal_filter(&mut self, intersection_id: usize) -> Result<(), JsValue> {
240+
self.map
241+
.rotate_diagonal_filter(IntersectionID(intersection_id));
242+
self.after_edit();
243+
Ok(())
244+
}
245+
246+
/// Takes an IntersectionID
247+
#[wasm_bindgen(js_name = deleteDiagonalFilter)]
248+
pub fn delete_diagonal_filter(&mut self, intersection_id: usize) -> Result<(), JsValue> {
249+
self.map
250+
.delete_diagonal_filter(IntersectionID(intersection_id));
251+
self.after_edit();
252+
Ok(())
253+
}
254+
228255
#[wasm_bindgen(js_name = toggleDirection)]
229256
pub fn toggle_direction(&mut self, road: usize) {
230257
self.map.toggle_direction(RoadID(road));
@@ -345,6 +372,7 @@ impl LTN {
345372
.map(|i| {
346373
let mut f = self.map.mercator.to_wgs84_gj(&i.point);
347374
f.set_property("has_turn_restrictions", !i.turn_restrictions.is_empty());
375+
f.set_property("intersection_id", i.id.0);
348376
f
349377
})
350378
.collect::<Vec<_>>(),

0 commit comments

Comments
 (0)