Skip to content

Commit

Permalink
Prepare for rendering cell arrows. #68
Browse files Browse the repository at this point in the history
Leave disabled. The symbol layer is hard to get working at different
zooms; we really need to make our own arrowhead polygons.
  • Loading branch information
dabreegster committed Jan 4, 2025
1 parent 4d5ad81 commit 3cee358
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 5 deletions.
16 changes: 14 additions & 2 deletions backend/src/geo_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use geo::{
BoundingRect, Contains, Coord, Distance, Euclidean, Intersects, LineInterpolatePoint,
BoundingRect, Contains, Coord, Distance, Euclidean, Intersects, Line, LineInterpolatePoint,
LineIntersection, LineLocatePoint, LineString, Point, Polygon, Rect,
};
use rstar::AABB;
Expand Down Expand Up @@ -99,12 +99,16 @@ pub fn aabb<G: BoundingRect<f64, Output = Option<Rect<f64>>>>(geom: &G) -> AABB<
)
}

pub fn angle_of_line(line: Line) -> f64 {
(line.dy()).atan2(line.dx()).to_degrees()
}

pub fn angle_of_pt_on_line(linestring: &LineString, pt: Coord) -> f64 {
let line = linestring
.lines()
.min_by_key(|line| (Euclidean::distance(line, pt) * 10e9) as usize)
.unwrap();
(line.dy()).atan2(line.dx()).to_degrees()
angle_of_line(line)
}

/// Constrain an angle between [0, 180]. Used for rotating modal filter icons visually
Expand All @@ -118,3 +122,11 @@ pub fn limit_angle(a1: f64) -> f64 {
a2
}
}

pub fn euclidean_destination(pt: Point, angle_degs: f64, dist_away_m: f64) -> Point {
let (sin, cos) = angle_degs.to_radians().sin_cos();
Point::new(
pt.x() + dist_away_m * cos,
pt.y() + dist_away_m * sin,
)
}
57 changes: 54 additions & 3 deletions backend/src/neighbourhood.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ use std::collections::{BTreeMap, BTreeSet};

use anyhow::Result;
use geo::{
Area, Distance, Euclidean, Length, LineInterpolatePoint, LineLocatePoint, LineString, Point,
Line, Area, Distance, Euclidean, Length, LineInterpolatePoint, LineLocatePoint, LineString, Point,
Polygon, PreparedGeometry, Relate,
};
use geojson::FeatureCollection;
use geojson::{Feature, FeatureCollection};
use web_time::Instant;

use crate::geo_helpers::{aabb, buffer_aabb, clip_linestring_to_polygon};
use crate::geo_helpers::{aabb, angle_of_line, buffer_aabb, clip_linestring_to_polygon, euclidean_destination};
use crate::render_cells::Color;
use crate::{Cell, Direction, IntersectionID, MapModel, RenderCells, RoadID, Shortcuts};

Expand Down Expand Up @@ -179,6 +179,8 @@ impl Neighbourhood {
let mut f = map.mercator.to_wgs84_gj(&map.get_i(*i).point);
f.set_property("kind", "border_intersection");
features.push(f);

features.extend(self.border_arrow(*i, map));
}

for (polygons, color) in derived
Expand Down Expand Up @@ -211,6 +213,55 @@ impl Neighbourhood {
),
}
}

fn border_arrow(&self, i: IntersectionID, map: &MapModel) -> Vec<Feature> {
let mut features = Vec::new();
let intersection = map.get_i(i);
for r in &intersection.roads {
// Most borders only have one road in the interior of the neighbourhood. Draw an arrow
// for each of those. If there happen to be multiple interior roads for one border, the
// arrows will overlap each other -- but that happens anyway with borders close
// together at certain angles.
if !self.interior_roads.contains(r) {
continue;
}

// Design choice: when we have a filter right at the entrance of a neighbourhood, it
// creates its own little cell allowing access to just the very beginning of the
// road. Let's not draw anything for that.
if map.modal_filters.contains_key(r) {
continue;
}

// Find the angle pointing into the neighbourhood
let road = map.get_r(*r);
let angle_in = if road.src_i == i {
angle_of_line(road.linestring.lines().next().unwrap())
} else {
angle_of_line(road.linestring.lines().last().unwrap()) + 180.0
};

let center = intersection.point;
let pt_farther = euclidean_destination(center, angle_in + 180.0, 40.0);
let pt_closer = euclidean_destination(center, angle_in + 180.0, 10.0);

// Point out of the neighbourhood
let mut line = Line::new(pt_closer, pt_farther);
// If the road is one-way and points in, then flip it
if (map.directions[r] == Direction::Forwards && road.src_i == i)
|| (map.directions[r] == Direction::Backwards && road.dst_i == i)
{
std::mem::swap(&mut line.start, &mut line.end);
}

let mut f = map.mercator.to_wgs84_gj(&line);
// TODO Which cell?
f.set_property("kind", "border_arrow");
f.set_property("oneway", map.directions[r] != Direction::BothWays);
features.push(f);
}
features
}
}

enum LineInPolygon {
Expand Down
23 changes: 23 additions & 0 deletions web/src/RenderNeighbourhood.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
GeoJSON,
hoverStateFilter,
LineLayer,
SymbolLayer,
} from "svelte-maplibre";
import { layerId } from "./common";
import OneWayLayer from "./OneWayLayer.svelte";
Expand Down Expand Up @@ -94,6 +95,28 @@
}}
/>

<!--
<LineLayer
{...layerId("border-arrow-bases")}
filter={["==", ["get", "kind"], "border_arrow"]}
paint={{
"line-width": 10,
"line-color": "cyan",
}}
/>
<SymbolLayer
{...layerId("border-arrows")}
filter={["==", ["get", "kind"], "border_arrow"]}
layout={{
"icon-image": "oneway-arrow",
"icon-size": 1.0,
"symbol-placement": "line",
"icon-allow-overlap": true,
}}
/>
-->

<LineLayer
{...layerId("interior-roads-outlines")}
filter={["==", ["get", "kind"], "interior_road"]}
Expand Down
2 changes: 2 additions & 0 deletions web/src/common/zorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ const layerZorder = [
"cells",
"interior-roads-outlines",
"interior-roads",
"border-arrow-bases",
"border-arrows",

"compare-route",

Expand Down

0 comments on commit 3cee358

Please sign in to comment.