mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 20:29:04 +03:00
making an isolated layer for the map creation phase focused on roads and intersections, geometry and merging. not using it yet.
This commit is contained in:
parent
5ca066a7df
commit
ec38717a2c
@ -7,10 +7,14 @@
|
||||
- sometimes a lane polyline hits the perpendicular of a trimmed road! where was this happening?
|
||||
|
||||
- handle small roads again somehow?
|
||||
- try manually merging stuff after running the automatic. i think we need to repeat based on short roads. maybe we need kind of a hybrid approach, using the fancy deleter but re-running intersection polygon and halfmap and turn creation from a lower level?
|
||||
- make halfmap also be indexed by stable IDs
|
||||
- try merging with roads and intersections, but with new trimmed road lengths
|
||||
- deal with loop roads still
|
||||
- restore original road points, then redo the intersection polygon and lane center pt expansion
|
||||
- organize map creation code better...
|
||||
- remove halfmap now
|
||||
- dont need to store shortest road lines in a hash anymore
|
||||
- final Intersection.polygon can now be a Polygon!
|
||||
|
||||
- manually draw a picture of the weird intersection to see what would look reasonable. i think we need original road bands from deleted stuff to make decent polygons.
|
||||
|
||||
- what's correct for 14th and e boston? if we had less lanes there, would it help?
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{ControlStopSign, ControlTrafficSignal, IntersectionID, Lane, LaneType, Road, RoadID};
|
||||
use crate::raw_data::StableRoadID;
|
||||
use crate::{ControlStopSign, ControlTrafficSignal, IntersectionID, Lane, LaneType, Road};
|
||||
use abstutil;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
@ -9,7 +10,7 @@ pub struct MapEdits {
|
||||
pub map_name: String,
|
||||
|
||||
// TODO detect when we wind up editing back to the original thing
|
||||
pub(crate) roads: BTreeMap<RoadID, RoadEdit>,
|
||||
pub(crate) roads: BTreeMap<StableRoadID, RoadEdit>,
|
||||
pub(crate) stop_signs: BTreeMap<IntersectionID, ControlStopSign>,
|
||||
pub(crate) traffic_signals: BTreeMap<IntersectionID, ControlTrafficSignal>,
|
||||
}
|
||||
@ -52,11 +53,12 @@ impl MapEdits {
|
||||
new_type: LaneType,
|
||||
) {
|
||||
let edit = RoadEdit::change_lane_type(reason, r, lane, new_type).unwrap();
|
||||
self.roads.insert(r.id, edit);
|
||||
self.roads.insert(r.stable_id, edit);
|
||||
}
|
||||
|
||||
pub fn delete_lane(&mut self, r: &Road, lane: &Lane) {
|
||||
self.roads.insert(r.id, RoadEdit::delete_lane(r, lane));
|
||||
self.roads
|
||||
.insert(r.stable_id, RoadEdit::delete_lane(r, lane));
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +70,7 @@ pub enum EditReason {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RoadEdit {
|
||||
road: RoadID,
|
||||
road: StableRoadID,
|
||||
pub(crate) forwards_lanes: Vec<LaneType>,
|
||||
pub(crate) backwards_lanes: Vec<LaneType>,
|
||||
reason: EditReason,
|
||||
@ -106,7 +108,7 @@ impl RoadEdit {
|
||||
}
|
||||
|
||||
Some(RoadEdit {
|
||||
road: r.id,
|
||||
road: r.stable_id,
|
||||
forwards_lanes: forwards,
|
||||
backwards_lanes: backwards,
|
||||
reason,
|
||||
@ -127,7 +129,7 @@ impl RoadEdit {
|
||||
}
|
||||
|
||||
RoadEdit {
|
||||
road: r.id,
|
||||
road: r.stable_id,
|
||||
forwards_lanes: forwards,
|
||||
backwards_lanes: backwards,
|
||||
reason: EditReason::BasemapWrong,
|
||||
|
@ -85,7 +85,7 @@ pub fn make_half_map(
|
||||
});
|
||||
|
||||
// TODO move this to make/lanes.rs too
|
||||
for lane in make::lanes::get_lane_specs(r, road_id, edits) {
|
||||
for lane in make::lanes::get_lane_specs(r, *stable_id, edits) {
|
||||
let id = LaneID(counter);
|
||||
counter += 1;
|
||||
|
||||
|
221
map_model/src/make/initial/geometry.rs
Normal file
221
map_model/src/make/initial/geometry.rs
Normal file
@ -0,0 +1,221 @@
|
||||
use crate::make::initial::{Intersection, Road};
|
||||
use crate::raw_data::{StableIntersectionID, StableRoadID};
|
||||
use abstutil::wraparound_get;
|
||||
use dimensioned::si;
|
||||
use geom::{Angle, HashablePt2D, Line, PolyLine, Pt2D};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::marker;
|
||||
|
||||
const DEGENERATE_INTERSECTION_HALF_LENGTH: si::Meter<f64> = si::Meter {
|
||||
value_unsafe: 5.0,
|
||||
_marker: marker::PhantomData,
|
||||
};
|
||||
|
||||
// The polygon should exist entirely within the thick bands around all original roads -- it just
|
||||
// carves up part of that space, doesn't reach past it.
|
||||
pub fn intersection_polygon(
|
||||
i: &Intersection,
|
||||
roads: &mut BTreeMap<StableRoadID, Road>,
|
||||
) -> Vec<Pt2D> {
|
||||
// Turn all of the incident roads into two PolyLines (the "forwards" and "backwards" borders of
|
||||
// the road, if the roads were oriented to both be incoming to the intersection), both ending
|
||||
// at the intersection (which may be different points for merged intersections!), and the angle
|
||||
// of the last segment of the center line.
|
||||
// TODO Maybe express the two incoming PolyLines as the "right" and "left"
|
||||
let mut lines: Vec<(StableRoadID, Angle, PolyLine, PolyLine)> = i
|
||||
.roads
|
||||
.iter()
|
||||
.map(|id| {
|
||||
let r = &roads[id];
|
||||
|
||||
let (line, width_normal, width_reverse) = if r.src_i == i.id {
|
||||
(r.original_center_pts.reversed(), r.back_width, r.fwd_width)
|
||||
} else if r.dst_i == i.id {
|
||||
(r.original_center_pts.clone(), r.fwd_width, r.back_width)
|
||||
} else {
|
||||
panic!("Incident road {} doesn't have an endpoint at {}", id, i.id);
|
||||
};
|
||||
|
||||
let pl_normal = line.shift_right(width_normal);
|
||||
let pl_reverse = line.shift_left(width_reverse);
|
||||
(*id, line.last_line().angle(), pl_normal, pl_reverse)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort the polylines by the angle of their last segment.
|
||||
// TODO This might break weirdly for polylines with very short last lines!
|
||||
// TODO This definitely can break for merged intersections. To get the lines "in order", maybe
|
||||
// we have to look at all the endpoints and sort by angle from the center of the points?
|
||||
lines.sort_by_key(|(_, angle, _, _)| angle.normalized_degrees() as i64);
|
||||
|
||||
let mut endpoints = if lines.len() == 1 {
|
||||
deadend(roads, i.id, &lines)
|
||||
} else {
|
||||
generalized_trim_back(roads, i.id, &lines)
|
||||
};
|
||||
|
||||
// Close off the polygon
|
||||
endpoints.push(endpoints[0]);
|
||||
endpoints
|
||||
}
|
||||
|
||||
fn generalized_trim_back(
|
||||
roads: &mut BTreeMap<StableRoadID, Road>,
|
||||
i: StableIntersectionID,
|
||||
lines: &Vec<(StableRoadID, Angle, PolyLine, PolyLine)>,
|
||||
) -> Vec<Pt2D> {
|
||||
let mut road_lines: Vec<(StableRoadID, &PolyLine)> = Vec::new();
|
||||
for (r, _, pl1, pl2) in lines {
|
||||
road_lines.push((*r, pl1));
|
||||
road_lines.push((*r, pl2));
|
||||
}
|
||||
|
||||
// TODO We can overwrite trimmed now
|
||||
let mut new_road_centers: HashMap<StableRoadID, PolyLine> = HashMap::new();
|
||||
|
||||
// Intersect every road's boundary lines with all the other lines
|
||||
for (r1, pl1) in &road_lines {
|
||||
// road_center ends at the intersection.
|
||||
let road_center = if roads[r1].dst_i == i {
|
||||
roads[r1].original_center_pts.clone()
|
||||
} else {
|
||||
roads[r1].original_center_pts.reversed()
|
||||
};
|
||||
|
||||
// Always trim back a minimum amount, if possible.
|
||||
let mut shortest_center = if road_center.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH {
|
||||
road_center
|
||||
.slice(
|
||||
0.0 * si::M,
|
||||
road_center.length() - DEGENERATE_INTERSECTION_HALF_LENGTH,
|
||||
)
|
||||
.0
|
||||
} else {
|
||||
road_center.clone()
|
||||
};
|
||||
|
||||
for (r2, pl2) in &road_lines {
|
||||
if r1 == r2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((hit, angle)) = pl1.intersection(pl2) {
|
||||
// Find where the perpendicular hits the original road line
|
||||
let perp = Line::new(hit, hit.project_away(1.0, angle.rotate_degs(90.0)));
|
||||
// How could something perpendicular to a shifted polyline never hit the original
|
||||
// polyline?
|
||||
let trim_to = road_center.intersection_infinite_line(perp).unwrap();
|
||||
let trimmed = road_center.trim_to_pt(trim_to);
|
||||
if trimmed.length() < shortest_center.length() {
|
||||
shortest_center = trimmed;
|
||||
}
|
||||
|
||||
// We could also do the update for r2, but we'll just get to it later.
|
||||
}
|
||||
}
|
||||
|
||||
let new_center = if roads[r1].dst_i == i {
|
||||
shortest_center
|
||||
} else {
|
||||
shortest_center.reversed()
|
||||
};
|
||||
if let Some(existing) = new_road_centers.get(r1) {
|
||||
if new_center.length() < existing.length() {
|
||||
new_road_centers.insert(*r1, new_center);
|
||||
}
|
||||
} else {
|
||||
new_road_centers.insert(*r1, new_center);
|
||||
}
|
||||
}
|
||||
|
||||
// After doing all the intersection checks, copy over the new centers. Also shift those centers
|
||||
// out again to find the endpoints that'll make up the polygon.
|
||||
let mut endpoints: Vec<Pt2D> = Vec::new();
|
||||
for (id, center_pts) in new_road_centers {
|
||||
let r = roads.get_mut(&id).unwrap();
|
||||
r.trimmed_center_pts = center_pts;
|
||||
|
||||
if r.dst_i == i {
|
||||
endpoints.push(r.trimmed_center_pts.shift_right(r.fwd_width).last_pt());
|
||||
endpoints.push(r.trimmed_center_pts.shift_left(r.back_width).last_pt());
|
||||
} else {
|
||||
endpoints.push(r.trimmed_center_pts.shift_right(r.fwd_width).first_pt());
|
||||
endpoints.push(r.trimmed_center_pts.shift_left(r.back_width).first_pt());
|
||||
}
|
||||
}
|
||||
// Include collisions between polylines of adjacent roads, so the polygon doesn't cover area
|
||||
// not originally covered by the thick road bands.
|
||||
for idx in 0..lines.len() as isize {
|
||||
let (_, _, fwd_pl, back_pl) = wraparound_get(&lines, idx);
|
||||
let (_, _, adj_back_pl, _) = wraparound_get(&lines, idx + 1);
|
||||
let (_, _, _, adj_fwd_pl) = wraparound_get(&lines, idx - 1);
|
||||
|
||||
if let Some((hit, _)) = fwd_pl.intersection(adj_fwd_pl) {
|
||||
endpoints.push(hit);
|
||||
}
|
||||
if let Some((hit, _)) = back_pl.intersection(adj_back_pl) {
|
||||
endpoints.push(hit);
|
||||
}
|
||||
}
|
||||
endpoints.sort_by_key(|pt| HashablePt2D::from(*pt));
|
||||
endpoints = approx_dedupe(endpoints);
|
||||
|
||||
let center = Pt2D::center(&endpoints);
|
||||
endpoints.sort_by_key(|pt| Line::new(center, *pt).angle().normalized_degrees() as i64);
|
||||
endpoints
|
||||
}
|
||||
|
||||
fn deadend(
|
||||
roads: &mut BTreeMap<StableRoadID, Road>,
|
||||
i: StableIntersectionID,
|
||||
lines: &Vec<(StableRoadID, Angle, PolyLine, PolyLine)>,
|
||||
) -> Vec<Pt2D> {
|
||||
let (id, _, pl_a, pl_b) = &lines[0];
|
||||
let pt1 = pl_a
|
||||
.reversed()
|
||||
.safe_dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH * 2.0)
|
||||
.map(|(pt, _)| pt);
|
||||
let pt2 = pl_b
|
||||
.reversed()
|
||||
.safe_dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH * 2.0)
|
||||
.map(|(pt, _)| pt);
|
||||
if pt1.is_some() && pt2.is_some() {
|
||||
let r = roads.get_mut(&id).unwrap();
|
||||
if r.src_i == i {
|
||||
r.trimmed_center_pts = r
|
||||
.original_center_pts
|
||||
.slice(
|
||||
DEGENERATE_INTERSECTION_HALF_LENGTH * 2.0,
|
||||
r.original_center_pts.length(),
|
||||
)
|
||||
.0;
|
||||
} else {
|
||||
r.trimmed_center_pts = r
|
||||
.original_center_pts
|
||||
.slice(
|
||||
0.0 * si::M,
|
||||
r.original_center_pts.length() - DEGENERATE_INTERSECTION_HALF_LENGTH * 2.0,
|
||||
)
|
||||
.0;
|
||||
}
|
||||
|
||||
vec![pt1.unwrap(), pt2.unwrap(), pl_b.last_pt(), pl_a.last_pt()]
|
||||
} else {
|
||||
error!(
|
||||
"{} is a dead-end for {}, which is too short to make degenerate intersection geometry",
|
||||
i, id
|
||||
);
|
||||
vec![pl_a.last_pt(), pl_b.last_pt()]
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary until Pt2D has proper resolution.
|
||||
fn approx_dedupe(pts: Vec<Pt2D>) -> Vec<Pt2D> {
|
||||
let mut result: Vec<Pt2D> = Vec::new();
|
||||
for pt in pts {
|
||||
if result.is_empty() || !result.last().unwrap().approx_eq(pt, 1.0 * si::M) {
|
||||
result.push(pt);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
114
map_model/src/make/initial/mod.rs
Normal file
114
map_model/src/make/initial/mod.rs
Normal file
@ -0,0 +1,114 @@
|
||||
mod geometry;
|
||||
|
||||
use crate::raw_data::{StableIntersectionID, StableRoadID};
|
||||
use crate::{make, raw_data, MapEdits, LANE_THICKNESS};
|
||||
use abstutil::Timer;
|
||||
use geom::{GPSBounds, PolyLine, Pt2D};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
pub struct InitialMap {
|
||||
pub roads: BTreeMap<StableRoadID, Road>,
|
||||
pub intersections: BTreeMap<StableIntersectionID, Intersection>,
|
||||
}
|
||||
|
||||
pub struct Road {
|
||||
pub id: StableRoadID,
|
||||
pub src_i: StableIntersectionID,
|
||||
pub dst_i: StableIntersectionID,
|
||||
pub original_center_pts: PolyLine,
|
||||
pub trimmed_center_pts: PolyLine,
|
||||
pub fwd_width: f64,
|
||||
pub back_width: f64,
|
||||
pub lane_specs: Vec<make::lanes::LaneSpec>,
|
||||
}
|
||||
|
||||
pub struct Intersection {
|
||||
pub id: StableIntersectionID,
|
||||
pub polygon: Vec<Pt2D>,
|
||||
pub roads: BTreeSet<StableRoadID>,
|
||||
}
|
||||
|
||||
pub fn make_initial_map(
|
||||
data: &raw_data::Map,
|
||||
gps_bounds: &GPSBounds,
|
||||
edits: &MapEdits,
|
||||
timer: &mut Timer,
|
||||
) -> InitialMap {
|
||||
let mut m = InitialMap {
|
||||
roads: BTreeMap::new(),
|
||||
intersections: BTreeMap::new(),
|
||||
};
|
||||
|
||||
for stable_id in data.intersections.keys() {
|
||||
m.intersections.insert(
|
||||
*stable_id,
|
||||
Intersection {
|
||||
id: *stable_id,
|
||||
polygon: Vec::new(),
|
||||
roads: BTreeSet::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (stable_id, r) in &data.roads {
|
||||
if r.i1 == r.i2 {
|
||||
// TODO Cul-de-sacs should be valid, but it really makes pathfinding screwy
|
||||
error!(
|
||||
"OSM way {} is a loop on {}, skipping what would've been {}",
|
||||
r.osm_way_id, r.i1, stable_id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
m.intersections
|
||||
.get_mut(&r.i1)
|
||||
.unwrap()
|
||||
.roads
|
||||
.insert(*stable_id);
|
||||
m.intersections
|
||||
.get_mut(&r.i2)
|
||||
.unwrap()
|
||||
.roads
|
||||
.insert(*stable_id);
|
||||
|
||||
let original_center_pts = PolyLine::new(
|
||||
r.points
|
||||
.iter()
|
||||
.map(|coord| Pt2D::from_gps(*coord, &gps_bounds).unwrap())
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let lane_specs = make::lanes::get_lane_specs(r, *stable_id, edits);
|
||||
let mut fwd_width = 0.0;
|
||||
let mut back_width = 0.0;
|
||||
for l in &lane_specs {
|
||||
if l.reverse_pts {
|
||||
back_width += LANE_THICKNESS;
|
||||
} else {
|
||||
fwd_width += LANE_THICKNESS;
|
||||
}
|
||||
}
|
||||
|
||||
m.roads.insert(
|
||||
*stable_id,
|
||||
Road {
|
||||
id: *stable_id,
|
||||
src_i: r.i1,
|
||||
dst_i: r.i2,
|
||||
original_center_pts: original_center_pts.clone(),
|
||||
trimmed_center_pts: original_center_pts,
|
||||
fwd_width,
|
||||
back_width,
|
||||
lane_specs,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
timer.start_iter("find each intersection polygon", m.intersections.len());
|
||||
for i in m.intersections.values_mut() {
|
||||
timer.next();
|
||||
|
||||
i.polygon = geometry::intersection_polygon(i, &mut m.roads);
|
||||
}
|
||||
|
||||
m
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::{raw_data, LaneType, MapEdits, RoadID};
|
||||
use crate::{raw_data, LaneType, MapEdits};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::iter;
|
||||
|
||||
@ -111,7 +111,11 @@ pub struct LaneSpec {
|
||||
pub reverse_pts: bool,
|
||||
}
|
||||
|
||||
pub fn get_lane_specs(r: &raw_data::Road, id: RoadID, edits: &MapEdits) -> Vec<LaneSpec> {
|
||||
pub fn get_lane_specs(
|
||||
r: &raw_data::Road,
|
||||
id: raw_data::StableRoadID,
|
||||
edits: &MapEdits,
|
||||
) -> Vec<LaneSpec> {
|
||||
let (side1_types, side2_types) = if let Some(e) = edits.roads.get(&id) {
|
||||
info!("Using edits for {}", id);
|
||||
(e.forwards_lanes.clone(), e.backwards_lanes.clone())
|
||||
|
@ -1,6 +1,7 @@
|
||||
mod buildings;
|
||||
mod bus_stops;
|
||||
mod half_map;
|
||||
mod initial;
|
||||
mod intersections;
|
||||
mod lanes;
|
||||
mod merge_intersections;
|
||||
|
Loading…
Reference in New Issue
Block a user