diff --git a/apps/ltn/src/render/mod.rs b/apps/ltn/src/render/mod.rs index 373b5af277..1c5a9a22ce 100644 --- a/apps/ltn/src/render/mod.rs +++ b/apps/ltn/src/render/mod.rs @@ -2,8 +2,10 @@ mod cells; pub mod colors; mod filters; +use std::collections::HashMap; + use geom::{Angle, Distance, Pt2D}; -use map_model::{AmenityType, ExtraPOIType, FilterType, Map, RestrictionType, Road, TurnType}; +use map_model::{AmenityType, ExtraPOIType, FilterType, Map, RestrictionType, Road, TurnType, RoadID}; use widgetry::mapspace::DrawCustomUnzoomedShapes; use widgetry::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, RewriteColor, Text}; @@ -77,35 +79,42 @@ pub fn render_bus_routes(ctx: &EventCtx, map: &Map) -> Drawable { ctx.upload(batch) } + pub fn render_turn_restrictions(ctx: &EventCtx, map: &Map) -> Drawable { let mut batch = GeomBatch::new(); for r1 in map.all_roads() { // TODO Also interpret lane-level? Maybe just check all the generated turns and see what's // allowed / banned in practice? + + // Count the number of turn restrictions at each end of the road + let mut icon_counter = HashMap::from([ + (r1.dst_i, 1), + (r1.src_i, 1), + ]); + for (restriction, r2) in &r1.turn_restrictions { // TODO "Invert" OnlyAllowTurns so we can just draw banned things if *restriction == RestrictionType::BanTurns { - batch.append(draw_restriction(ctx, map, r1, map.get_r(*r2))); + let (t_type, sign_pt, r1_angle, i) = map.get_ban_turn_info(r1, map.get_r(*r2), &icon_counter); + // add to the counter + icon_counter.entry(i).and_modify(|n| *n+=1); + batch.append(draw_turn_restriction_icon( + ctx, t_type, sign_pt, r1, r1_angle, + )); } } for (_via, r2) in &r1.complicated_turn_restrictions { // TODO Show the 'via'? Or just draw the entire shape? - batch.append(draw_restriction(ctx, map, r1, map.get_r(*r2))); + let (t_type, sign_pt, r1_angle, i) = map.get_ban_turn_info(r1, map.get_r(*r2), &icon_counter); + icon_counter.entry(i).and_modify(|n| *n+=1); + batch.append(draw_turn_restriction_icon( + ctx, t_type, sign_pt, r1, r1_angle, + )); } } ctx.upload(batch) } -fn draw_restriction(ctx: &EventCtx, map: &Map, r1: &Road, r2: &Road) -> GeomBatch { - let mut batch = GeomBatch::new(); - // TODO: remove/name this wrapper, which is just for debugging svg icon placement/rotation - let (t_type, sign_pt, r1_angle, _) = map.get_ban_turn_info(r1, r2); - batch.append(draw_turn_restriction_icon( - ctx, t_type, sign_pt, r1, r1_angle, - )); - batch -} - fn draw_turn_restriction_icon( ctx: &EventCtx, t_type: TurnType, diff --git a/map_model/src/map.rs b/map_model/src/map.rs index 503902ea03..71366db028 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -1112,6 +1112,7 @@ impl Map { &self, r1: &Road, r2: &Road, + icon_counter: &HashMap, ) -> (TurnType, Pt2D, Angle, IntersectionID) { // Determine where to place the symbol let i = match r1.common_endpoint(r2) { @@ -1126,10 +1127,25 @@ impl Map { } }; - // Determine what type of turn is it? + // Determine where to place and orientate the icon + let sign_count = *icon_counter.get(&i).unwrap_or(&1); + let mut ideal_dist_from_intersection = sign_count as f64 * 1.1 * r1.get_width(); + if 2.0 * ideal_dist_from_intersection > r1.center_pts.length() { + // We've run out of space on the road to fit all of the icons on. We will just pile them up where we can + // Next try near the end of the stack, but just still in the appropriate half of the road + ideal_dist_from_intersection = 0.5 * (r1.center_pts.length() - r1.get_width()); + if ideal_dist_from_intersection < Distance::ZERO { + // The road is wider than it is long, so just squeeze them in: + ideal_dist_from_intersection = 0.3 * r1.center_pts.length(); + } + } + + // Adjust according to which end of the road we've measuring from + let dist_from_intersection = if r1.src_i == i { ideal_dist_from_intersection } else { r1.center_pts.length() - ideal_dist_from_intersection }; + let (sign_pt, mut r1_angle) = r1 .center_pts - .must_dist_along((if r1.src_i == i { 0.2 } else { 0.8 }) * r1.center_pts.length()); + .must_dist_along(dist_from_intersection); // Correct the angle, based on whether the vector direction is towards or away from the intersection // TODO what is the standard way of describing the vector direction (rather than the traffic direction) for roads? diff --git a/tests/src/main.rs b/tests/src/main.rs index 312cad0828..5680073c2b 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -1,6 +1,6 @@ //! Integration tests -use std::io::Write; +use std::{io::Write, collections::HashMap}; use anyhow::{bail, Result}; use fs_err::File; @@ -113,8 +113,14 @@ fn all_turn_info_as_string(map: &Map) -> String { s.push_str("\n------------\nRestrictions:\n------------\n"); for r1 in map.all_roads() { + + let icon_counter = HashMap::from([ + (r1.dst_i, 1), + (r1.src_i, 1), + ]); + for (restriction, r2) in &r1.turn_restrictions { - let (t_type, sign_pt, _, i_id) = map.get_ban_turn_info(r1, map.get_r(*r2)); + let (t_type, sign_pt, _, i_id) = map.get_ban_turn_info(r1, map.get_r(*r2), &icon_counter); let i = map.get_i(i_id); s.push_str(&format!( "Turn from {} into {}, at intersection {:?} is a {:?}, type {:?}, location {}\n",