From 3cee35828c6ea5a7b7652543b62c08cdfae7c673 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Sat, 4 Jan 2025 14:03:44 +0000 Subject: [PATCH] Prepare for rendering cell arrows. #68 Leave disabled. The symbol layer is hard to get working at different zooms; we really need to make our own arrowhead polygons. --- backend/src/geo_helpers.rs | 16 +++++++-- backend/src/neighbourhood.rs | 57 ++++++++++++++++++++++++++++-- web/src/RenderNeighbourhood.svelte | 23 ++++++++++++ web/src/common/zorder.ts | 2 ++ 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/backend/src/geo_helpers.rs b/backend/src/geo_helpers.rs index b9541f1..9284809 100644 --- a/backend/src/geo_helpers.rs +++ b/backend/src/geo_helpers.rs @@ -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; @@ -99,12 +99,16 @@ pub fn aabb>>>(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 @@ -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, + ) +} diff --git a/backend/src/neighbourhood.rs b/backend/src/neighbourhood.rs index 5c2c07b..bbcd853 100644 --- a/backend/src/neighbourhood.rs +++ b/backend/src/neighbourhood.rs @@ -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}; @@ -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 @@ -211,6 +213,55 @@ impl Neighbourhood { ), } } + + fn border_arrow(&self, i: IntersectionID, map: &MapModel) -> Vec { + 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 { diff --git a/web/src/RenderNeighbourhood.svelte b/web/src/RenderNeighbourhood.svelte index 4673cce..db02b8b 100644 --- a/web/src/RenderNeighbourhood.svelte +++ b/web/src/RenderNeighbourhood.svelte @@ -6,6 +6,7 @@ GeoJSON, hoverStateFilter, LineLayer, + SymbolLayer, } from "svelte-maplibre"; import { layerId } from "./common"; import OneWayLayer from "./OneWayLayer.svelte"; @@ -94,6 +95,28 @@ }} /> + +