splitting map model into more modules

This commit is contained in:
Dustin Carlino 2018-06-25 07:53:39 -07:00
parent 2b9eb66ba1
commit d9e46c5960
8 changed files with 446 additions and 382 deletions

View File

@ -83,3 +83,20 @@ the transition is hard:
so really do this in two parts:
1) current structure with weird intermediate stuff, but new geometry libs
2) careful reorg
wait slow down even more -- before any of this change, lanes on adjacent roads smoosh into each other. main road doesnt, but other parts do.
- at every intersection, find corresponding lanes and trim back center lines
- do we need to do this at the level of the polygons?!
- follow aorta's multi phase map construction better.
- FIRST: move geom things into the Map structs directly. get rid of that crate.
---> option 1: module per object type, geometry and graph squished together
- option 2: try to separate the graph/geom stuff within map model.
- THEN: express the proto -> runtime map loading as a sequence of phases
- keep doing the current road trimming for the moment
- later, this could be the same as the OSM conversion. just
like aorta's map make. but instead, be able to restart from
any point, by the magic of easy serialization.
- get rid of the protobuf

21
map_model/src/building.rs Normal file
View File

@ -0,0 +1,21 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use Pt2D;
// TODO reconsider pub usize. maybe outside world shouldnt know.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct BuildingID(pub usize);
#[derive(Debug)]
pub struct Building {
pub id: BuildingID,
pub points: Vec<Pt2D>,
pub osm_tags: Vec<String>,
pub osm_way_id: i64,
}
impl PartialEq for Building {
fn eq(&self, other: &Building) -> bool {
self.id == other.id
}
}

View File

@ -0,0 +1,23 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use Pt2D;
use TurnID;
// TODO reconsider pub usize. maybe outside world shouldnt know.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct IntersectionID(pub usize);
#[derive(Debug)]
pub struct Intersection {
pub id: IntersectionID,
pub point: Pt2D,
pub turns: Vec<TurnID>,
pub elevation_meters: f64,
pub has_traffic_signal: bool,
}
impl PartialEq for Intersection {
fn eq(&self, other: &Intersection) -> bool {
self.id == other.id
}
}

View File

@ -7,16 +7,27 @@ extern crate serde;
#[macro_use]
extern crate serde_derive;
mod building;
mod intersection;
mod map;
mod parcel;
pub mod pb;
mod polyline;
mod road;
mod turn;
pub use building::{Building, BuildingID};
pub use intersection::{Intersection, IntersectionID};
pub use map::Map;
use ordered_float::NotNaN;
pub use parcel::{Parcel, ParcelID};
pub use polyline::{polygons_for_polyline, shift_polyline};
use protobuf::error::ProtobufError;
use protobuf::{CodedInputStream, CodedOutputStream, Message};
use std::collections::HashMap;
pub use road::{LaneType, Road, RoadID};
use std::f64;
use std::fs::File;
pub use turn::{Turn, TurnID};
pub fn write_pb(map: &pb::Map, path: &str) -> Result<(), ProtobufError> {
let mut file = File::create(path)?;
@ -128,387 +139,6 @@ impl Bounds {
}
}
// TODO reconsider pub usize. maybe outside world shouldnt know.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct RoadID(pub usize);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct IntersectionID(pub usize);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct TurnID(pub usize);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct BuildingID(pub usize);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ParcelID(pub usize);
pub struct Map {
roads: Vec<Road>,
intersections: Vec<Intersection>,
turns: Vec<Turn>,
buildings: Vec<Building>,
parcels: Vec<Parcel>,
pt_to_intersection: HashMap<Pt2D, IntersectionID>,
intersection_to_roads: HashMap<IntersectionID, Vec<RoadID>>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum LaneType {
Driving,
Parking,
Sidewalk,
}
#[derive(Debug)]
pub struct Road {
pub id: RoadID,
pub osm_tags: Vec<String>,
pub osm_way_id: i64,
pub lane_type: LaneType,
// Ideally all of these would just become translated center points immediately, but this is
// hard due to the polyline problem.
// All roads are two-way (since even one-way streets have sidewalks on both sides). Offset 0 is
// the centermost lane on each side, then it counts up.
pub offset: u8,
// The orientation is implied by the order of these points
pub points: Vec<Pt2D>,
// Should this lane own the drawing of the yellow center lines? For two-way roads, this is
// arbitrarily grouped with one of the lanes. Ideally it would be owned by something else.
pub use_yellow_center_lines: bool,
// Need to remember this just for detecting U-turns here.
other_side: Option<RoadID>,
}
impl PartialEq for Road {
fn eq(&self, other: &Road) -> bool {
self.id == other.id
}
}
#[derive(Debug)]
pub struct Intersection {
pub id: IntersectionID,
pub point: Pt2D,
pub turns: Vec<TurnID>,
pub elevation_meters: f64,
pub has_traffic_signal: bool,
}
impl PartialEq for Intersection {
fn eq(&self, other: &Intersection) -> bool {
self.id == other.id
}
}
#[derive(Debug)]
pub struct Turn {
pub id: TurnID,
pub parent: IntersectionID,
pub src: RoadID,
pub dst: RoadID,
}
impl PartialEq for Turn {
fn eq(&self, other: &Turn) -> bool {
self.id == other.id
}
}
#[derive(Debug)]
pub struct Building {
pub id: BuildingID,
pub points: Vec<Pt2D>,
pub osm_tags: Vec<String>,
pub osm_way_id: i64,
}
impl PartialEq for Building {
fn eq(&self, other: &Building) -> bool {
self.id == other.id
}
}
#[derive(Debug)]
pub struct Parcel {
pub id: ParcelID,
pub points: Vec<Pt2D>,
}
impl PartialEq for Parcel {
fn eq(&self, other: &Parcel) -> bool {
self.id == other.id
}
}
impl Map {
pub fn new(data: &pb::Map) -> Map {
let mut m = Map {
roads: Vec::new(),
intersections: Vec::new(),
turns: Vec::new(),
buildings: Vec::new(),
parcels: Vec::new(),
pt_to_intersection: HashMap::new(),
intersection_to_roads: HashMap::new(),
};
for (idx, i) in data.get_intersections().iter().enumerate() {
let id = IntersectionID(idx);
let pt = Pt2D::from(i.get_point());
m.intersections.push(Intersection {
id,
point: pt,
turns: Vec::new(),
elevation_meters: i.get_elevation_meters(),
// TODO use the data again!
//has_traffic_signal: i.get_has_traffic_signal(),
has_traffic_signal: idx % 2 == 0,
});
m.pt_to_intersection.insert(pt, id);
}
let mut counter = 0;
for r in data.get_roads() {
let oneway = r.get_osm_tags().contains(&String::from("oneway=yes"));
let orig_direction = true;
let reverse_direction = false;
// lane_type, offset, reverse the points or not, offset to get the other_side's ID
let mut lanes: Vec<(LaneType, u8, bool, Option<isize>)> = vec![
(
LaneType::Driving,
0,
orig_direction,
if oneway { None } else { Some(3) },
),
(LaneType::Parking, 1, orig_direction, None),
(LaneType::Sidewalk, 2, orig_direction, None),
];
if oneway {
lanes.push((LaneType::Sidewalk, 0, reverse_direction, None));
} else {
lanes.extend(vec![
(LaneType::Driving, 0, reverse_direction, Some(-3)),
(LaneType::Parking, 1, reverse_direction, None),
(LaneType::Sidewalk, 2, reverse_direction, None),
]);
}
for lane in &lanes {
let id = RoadID(counter);
counter += 1;
let other_side = lane.3
.map(|offset| RoadID(((id.0 as isize) + offset) as usize));
let pts: Vec<Pt2D> = if lane.2 == orig_direction {
r.get_points().iter().map(Pt2D::from).collect()
} else {
r.get_points().iter().rev().map(Pt2D::from).collect()
};
let i1 = m.pt_to_intersection[&pts[0]];
let i2 = m.pt_to_intersection[pts.last().unwrap()];
m.intersection_to_roads
.entry(i1)
.or_insert_with(Vec::new)
.push(id);
m.intersection_to_roads
.entry(i2)
.or_insert_with(Vec::new)
.push(id);
m.roads.push(Road {
id,
other_side,
osm_tags: r.get_osm_tags().to_vec(),
osm_way_id: r.get_osm_way_id(),
lane_type: lane.0,
offset: lane.1,
points: pts,
use_yellow_center_lines: if let Some(other) = other_side {
id.0 < other.0
} else {
lane.1 == 0
},
});
}
}
for i in &m.intersections {
// TODO: Figure out why this happens in the huge map
if m.intersection_to_roads.get(&i.id).is_none() {
println!("WARNING: intersection {:?} has no roads", i);
continue;
}
let incident_roads: Vec<RoadID> = m.intersection_to_roads[&i.id]
.iter()
.filter(|id| m.roads[id.0].lane_type == LaneType::Driving)
.map(|id| *id)
.collect();
for src in &incident_roads {
let src_r = &m.roads[src.0];
if i.point != *src_r.points.last().unwrap() {
continue;
}
for dst in &incident_roads {
let dst_r = &m.roads[dst.0];
if i.point != dst_r.points[0] {
continue;
}
// Don't create U-turns unless it's a dead-end
if src_r.other_side == Some(dst_r.id) && incident_roads.len() > 2 {
continue;
}
let id = TurnID(m.turns.len());
m.turns.push(Turn {
id,
parent: i.id,
src: *src,
dst: *dst,
});
}
}
}
for t in &m.turns {
m.intersections[t.parent.0].turns.push(t.id);
}
for (idx, b) in data.get_buildings().iter().enumerate() {
m.buildings.push(Building {
id: BuildingID(idx),
points: b.get_points().iter().map(Pt2D::from).collect(),
osm_tags: b.get_osm_tags().to_vec(),
osm_way_id: b.get_osm_way_id(),
});
}
for (idx, p) in data.get_parcels().iter().enumerate() {
m.parcels.push(Parcel {
id: ParcelID(idx),
points: p.get_points().iter().map(Pt2D::from).collect(),
});
}
m
}
pub fn get_roads_to_intersection(&self, id: IntersectionID) -> Vec<&Road> {
self.intersection_to_roads[&id]
.iter()
.map(|id| &self.roads[id.0])
.filter(|r| *r.points.last().unwrap() == self.get_i(id).point)
.collect()
}
pub fn get_roads_from_intersection(&self, id: IntersectionID) -> Vec<&Road> {
self.intersection_to_roads[&id]
.iter()
.map(|id| &self.roads[id.0])
.filter(|r| r.points[0] == self.get_i(id).point)
.collect()
}
pub fn all_roads(&self) -> &Vec<Road> {
&self.roads
}
pub fn all_intersections(&self) -> &Vec<Intersection> {
&self.intersections
}
pub fn all_turns(&self) -> &Vec<Turn> {
&self.turns
}
pub fn all_buildings(&self) -> &Vec<Building> {
&self.buildings
}
pub fn all_parcels(&self) -> &Vec<Parcel> {
&self.parcels
}
pub fn get_r(&self, id: RoadID) -> &Road {
&self.roads[id.0]
}
pub fn get_i(&self, id: IntersectionID) -> &Intersection {
&self.intersections[id.0]
}
pub fn get_t(&self, id: TurnID) -> &Turn {
&self.turns[id.0]
}
pub fn get_b(&self, id: BuildingID) -> &Building {
&self.buildings[id.0]
}
pub fn get_p(&self, id: ParcelID) -> &Parcel {
&self.parcels[id.0]
}
// All these helpers should take IDs and return objects.
pub fn get_source_intersection(&self, r: RoadID) -> &Intersection {
self.get_i(self.pt_to_intersection[&self.get_r(r).points[0]])
}
pub fn get_destination_intersection(&self, r: RoadID) -> &Intersection {
self.get_i(self.pt_to_intersection[self.get_r(r).points.last().unwrap()])
}
pub fn get_turns_in_intersection(&self, id: IntersectionID) -> Vec<&Turn> {
self.get_i(id)
.turns
.iter()
.map(|t| self.get_t(*t))
.collect()
}
pub fn get_turns_from_road(&self, id: RoadID) -> Vec<&Turn> {
let i = self.get_destination_intersection(id);
// TODO can't filter on get_turns_in_intersection... winds up being Vec<&&Turn>
i.turns
.iter()
.map(|t| self.get_t(*t))
.filter(|t| t.src == id)
.collect()
}
pub fn get_next_roads(&self, from: RoadID) -> Vec<&Road> {
// TODO assumes no duplicates
self.get_turns_from_road(from)
.iter()
.map(|t| self.get_r(t.dst))
.collect()
}
pub fn get_gps_bounds(&self) -> Bounds {
let mut b = Bounds::new();
for r in &self.roads {
for pt in &r.points {
b.update_pt(pt);
}
}
for i in &self.intersections {
b.update_pt(&i.point);
}
for bldg in &self.buildings {
for pt in &bldg.points {
b.update_pt(pt);
}
}
for p in &self.parcels {
for pt in &p.points {
b.update_pt(pt);
}
}
b
}
}
pub fn has_osm_tag(tags: &Vec<String>, key: &str, value: &str) -> bool {
tags.contains(&format!("{}={}", key, value))
}

290
map_model/src/map.rs Normal file
View File

@ -0,0 +1,290 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use Bounds;
use Pt2D;
use building::{Building, BuildingID};
use intersection::{Intersection, IntersectionID};
use parcel::{Parcel, ParcelID};
use pb;
use road::{LaneType, Road, RoadID};
use std::collections::HashMap;
use turn::{Turn, TurnID};
pub struct Map {
roads: Vec<Road>,
intersections: Vec<Intersection>,
turns: Vec<Turn>,
buildings: Vec<Building>,
parcels: Vec<Parcel>,
pt_to_intersection: HashMap<Pt2D, IntersectionID>,
intersection_to_roads: HashMap<IntersectionID, Vec<RoadID>>,
}
impl Map {
pub fn new(data: &pb::Map) -> Map {
let mut m = Map {
roads: Vec::new(),
intersections: Vec::new(),
turns: Vec::new(),
buildings: Vec::new(),
parcels: Vec::new(),
pt_to_intersection: HashMap::new(),
intersection_to_roads: HashMap::new(),
};
for (idx, i) in data.get_intersections().iter().enumerate() {
let id = IntersectionID(idx);
let pt = Pt2D::from(i.get_point());
m.intersections.push(Intersection {
id,
point: pt,
turns: Vec::new(),
elevation_meters: i.get_elevation_meters(),
// TODO use the data again!
//has_traffic_signal: i.get_has_traffic_signal(),
has_traffic_signal: idx % 2 == 0,
});
m.pt_to_intersection.insert(pt, id);
}
let mut counter = 0;
for r in data.get_roads() {
let oneway = r.get_osm_tags().contains(&String::from("oneway=yes"));
let orig_direction = true;
let reverse_direction = false;
// lane_type, offset, reverse the points or not, offset to get the other_side's ID
let mut lanes: Vec<(LaneType, u8, bool, Option<isize>)> = vec![
(
LaneType::Driving,
0,
orig_direction,
if oneway { None } else { Some(3) },
),
(LaneType::Parking, 1, orig_direction, None),
(LaneType::Sidewalk, 2, orig_direction, None),
];
if oneway {
lanes.push((LaneType::Sidewalk, 0, reverse_direction, None));
} else {
lanes.extend(vec![
(LaneType::Driving, 0, reverse_direction, Some(-3)),
(LaneType::Parking, 1, reverse_direction, None),
(LaneType::Sidewalk, 2, reverse_direction, None),
]);
}
for lane in &lanes {
let id = RoadID(counter);
counter += 1;
let other_side = lane.3
.map(|offset| RoadID(((id.0 as isize) + offset) as usize));
let pts: Vec<Pt2D> = if lane.2 == orig_direction {
r.get_points().iter().map(Pt2D::from).collect()
} else {
r.get_points().iter().rev().map(Pt2D::from).collect()
};
let i1 = m.pt_to_intersection[&pts[0]];
let i2 = m.pt_to_intersection[pts.last().unwrap()];
m.intersection_to_roads
.entry(i1)
.or_insert_with(Vec::new)
.push(id);
m.intersection_to_roads
.entry(i2)
.or_insert_with(Vec::new)
.push(id);
m.roads.push(Road {
id,
other_side,
osm_tags: r.get_osm_tags().to_vec(),
osm_way_id: r.get_osm_way_id(),
lane_type: lane.0,
offset: lane.1,
points: pts,
use_yellow_center_lines: if let Some(other) = other_side {
id.0 < other.0
} else {
lane.1 == 0
},
});
}
}
for i in &m.intersections {
// TODO: Figure out why this happens in the huge map
if m.intersection_to_roads.get(&i.id).is_none() {
println!("WARNING: intersection {:?} has no roads", i);
continue;
}
let incident_roads: Vec<RoadID> = m.intersection_to_roads[&i.id]
.iter()
.filter(|id| m.roads[id.0].lane_type == LaneType::Driving)
.map(|id| *id)
.collect();
for src in &incident_roads {
let src_r = &m.roads[src.0];
if i.point != *src_r.points.last().unwrap() {
continue;
}
for dst in &incident_roads {
let dst_r = &m.roads[dst.0];
if i.point != dst_r.points[0] {
continue;
}
// Don't create U-turns unless it's a dead-end
if src_r.other_side == Some(dst_r.id) && incident_roads.len() > 2 {
continue;
}
let id = TurnID(m.turns.len());
m.turns.push(Turn {
id,
parent: i.id,
src: *src,
dst: *dst,
});
}
}
}
for t in &m.turns {
m.intersections[t.parent.0].turns.push(t.id);
}
for (idx, b) in data.get_buildings().iter().enumerate() {
m.buildings.push(Building {
id: BuildingID(idx),
points: b.get_points().iter().map(Pt2D::from).collect(),
osm_tags: b.get_osm_tags().to_vec(),
osm_way_id: b.get_osm_way_id(),
});
}
for (idx, p) in data.get_parcels().iter().enumerate() {
m.parcels.push(Parcel {
id: ParcelID(idx),
points: p.get_points().iter().map(Pt2D::from).collect(),
});
}
m
}
pub fn get_roads_to_intersection(&self, id: IntersectionID) -> Vec<&Road> {
self.intersection_to_roads[&id]
.iter()
.map(|id| &self.roads[id.0])
.filter(|r| *r.points.last().unwrap() == self.get_i(id).point)
.collect()
}
pub fn get_roads_from_intersection(&self, id: IntersectionID) -> Vec<&Road> {
self.intersection_to_roads[&id]
.iter()
.map(|id| &self.roads[id.0])
.filter(|r| r.points[0] == self.get_i(id).point)
.collect()
}
pub fn all_roads(&self) -> &Vec<Road> {
&self.roads
}
pub fn all_intersections(&self) -> &Vec<Intersection> {
&self.intersections
}
pub fn all_turns(&self) -> &Vec<Turn> {
&self.turns
}
pub fn all_buildings(&self) -> &Vec<Building> {
&self.buildings
}
pub fn all_parcels(&self) -> &Vec<Parcel> {
&self.parcels
}
pub fn get_r(&self, id: RoadID) -> &Road {
&self.roads[id.0]
}
pub fn get_i(&self, id: IntersectionID) -> &Intersection {
&self.intersections[id.0]
}
pub fn get_t(&self, id: TurnID) -> &Turn {
&self.turns[id.0]
}
pub fn get_b(&self, id: BuildingID) -> &Building {
&self.buildings[id.0]
}
pub fn get_p(&self, id: ParcelID) -> &Parcel {
&self.parcels[id.0]
}
// All these helpers should take IDs and return objects.
pub fn get_source_intersection(&self, r: RoadID) -> &Intersection {
self.get_i(self.pt_to_intersection[&self.get_r(r).points[0]])
}
pub fn get_destination_intersection(&self, r: RoadID) -> &Intersection {
self.get_i(self.pt_to_intersection[self.get_r(r).points.last().unwrap()])
}
pub fn get_turns_in_intersection(&self, id: IntersectionID) -> Vec<&Turn> {
self.get_i(id)
.turns
.iter()
.map(|t| self.get_t(*t))
.collect()
}
pub fn get_turns_from_road(&self, id: RoadID) -> Vec<&Turn> {
let i = self.get_destination_intersection(id);
// TODO can't filter on get_turns_in_intersection... winds up being Vec<&&Turn>
i.turns
.iter()
.map(|t| self.get_t(*t))
.filter(|t| t.src == id)
.collect()
}
pub fn get_next_roads(&self, from: RoadID) -> Vec<&Road> {
// TODO assumes no duplicates
self.get_turns_from_road(from)
.iter()
.map(|t| self.get_r(t.dst))
.collect()
}
pub fn get_gps_bounds(&self) -> Bounds {
let mut b = Bounds::new();
for r in &self.roads {
for pt in &r.points {
b.update_pt(pt);
}
}
for i in &self.intersections {
b.update_pt(&i.point);
}
for bldg in &self.buildings {
for pt in &bldg.points {
b.update_pt(pt);
}
}
for p in &self.parcels {
for pt in &p.points {
b.update_pt(pt);
}
}
b
}
}

19
map_model/src/parcel.rs Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use Pt2D;
// TODO reconsider pub usize. maybe outside world shouldnt know.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ParcelID(pub usize);
#[derive(Debug)]
pub struct Parcel {
pub id: ParcelID,
pub points: Vec<Pt2D>,
}
impl PartialEq for Parcel {
fn eq(&self, other: &Parcel) -> bool {
self.id == other.id
}
}

42
map_model/src/road.rs Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use Pt2D;
// TODO reconsider pub usize. maybe outside world shouldnt know.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct RoadID(pub usize);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum LaneType {
Driving,
Parking,
Sidewalk,
}
#[derive(Debug)]
pub struct Road {
pub id: RoadID,
pub osm_tags: Vec<String>,
pub osm_way_id: i64,
pub lane_type: LaneType,
// Ideally all of these would just become translated center points immediately, but this is
// hard due to the polyline problem.
// All roads are two-way (since even one-way streets have sidewalks on both sides). Offset 0 is
// the centermost lane on each side, then it counts up.
pub offset: u8,
// The orientation is implied by the order of these points
pub points: Vec<Pt2D>,
// Should this lane own the drawing of the yellow center lines? For two-way roads, this is
// arbitrarily grouped with one of the lanes. Ideally it would be owned by something else.
pub use_yellow_center_lines: bool,
// Need to remember this just for detecting U-turns here.
pub(crate) other_side: Option<RoadID>,
}
impl PartialEq for Road {
fn eq(&self, other: &Road) -> bool {
self.id == other.id
}
}

22
map_model/src/turn.rs Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use IntersectionID;
use RoadID;
// TODO reconsider pub usize. maybe outside world shouldnt know.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct TurnID(pub usize);
#[derive(Debug)]
pub struct Turn {
pub id: TurnID,
pub parent: IntersectionID,
pub src: RoadID,
pub dst: RoadID,
}
impl PartialEq for Turn {
fn eq(&self, other: &Turn) -> bool {
self.id == other.id
}
}