mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 01:15:12 +03:00
Organize raw_map code a bit more. Let's cleanly separate the detection of short roads from transforming them. #654
And add a convenient script to diff changed maps. (Motivated by preserving the fixpoint behavior in Montlake)
This commit is contained in:
parent
58991ab041
commit
4583dac399
@ -101,7 +101,7 @@ impl MainState {
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("auto mark junctions")
|
||||
.text("detect short roads")
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
@ -256,8 +256,8 @@ impl State<App> for MainState {
|
||||
"adjust boundary" => {
|
||||
self.mode = Mode::SetBoundaryPt1;
|
||||
}
|
||||
"auto mark junctions" => {
|
||||
for r in app.model.map.auto_mark_junctions() {
|
||||
"detect short roads" => {
|
||||
for r in app.model.map.find_traffic_signal_clusters() {
|
||||
app.model.road_deleted(r);
|
||||
app.model.road_added(ctx, r);
|
||||
}
|
||||
|
16
data/diff_changed_map.sh
Executable file
16
data/diff_changed_map.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# If `updater --dry` says a mapchanged, call this to launch a UI and compare.
|
||||
# This only works if you have a copy of the S3 directory in ~/s3_abst_data.
|
||||
|
||||
set -e
|
||||
|
||||
FILE=$1
|
||||
if [ "$FILE" == "" ]; then
|
||||
echo Missing args;
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
rm -f old.bin
|
||||
cp ~/s3_abst_data/dev/${FILE}.gz old.bin.gz
|
||||
gunzip old.bin.gz
|
||||
./target/release/game --dev $FILE --diff old.bin
|
@ -316,47 +316,6 @@ impl RawMap {
|
||||
.get(&to)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Look for short roads that should be merged, and mark them as junction=intersection.
|
||||
pub fn auto_mark_junctions(&mut self) -> Vec<OriginalRoad> {
|
||||
let threshold = Distance::meters(20.0);
|
||||
|
||||
// Simplest start: look for short roads connected to traffic signals.
|
||||
//
|
||||
// (This will miss sequences of short roads with stop signs in between a cluster of traffic
|
||||
// signals)
|
||||
//
|
||||
// After trying out around Loop 101, what we really want to do is find clumps of 2 or 4
|
||||
// traffic signals, find all the segments between them, and merge those.
|
||||
let mut results = Vec::new();
|
||||
for (id, road) in &self.roads {
|
||||
if road.osm_tags.is("junction", "intersection") {
|
||||
continue;
|
||||
}
|
||||
let i1 = self.intersections[&id.i1].intersection_type;
|
||||
let i2 = self.intersections[&id.i2].intersection_type;
|
||||
if i1 == IntersectionType::Border || i2 == IntersectionType::Border {
|
||||
continue;
|
||||
}
|
||||
if i1 != IntersectionType::TrafficSignal && i2 != IntersectionType::TrafficSignal {
|
||||
continue;
|
||||
}
|
||||
if let Ok((pl, _)) = road.get_geometry(*id, &self.config) {
|
||||
if pl.length() <= threshold {
|
||||
results.push(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id in &results {
|
||||
self.roads
|
||||
.get_mut(id)
|
||||
.unwrap()
|
||||
.osm_tags
|
||||
.insert("junction", "intersection");
|
||||
}
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
61
raw_map/src/transform/dual_carriageways.rs
Normal file
61
raw_map/src/transform/dual_carriageways.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use geom::Angle;
|
||||
|
||||
use crate::{osm, OriginalRoad, RawMap, RawRoad};
|
||||
|
||||
/// Does this road go between two divided one-ways? Ideally they're tagged explicitly
|
||||
/// (https://wiki.openstreetmap.org/wiki/Tag:dual_carriageway%3Dyes), but we can also apply simple
|
||||
/// heuristics to guess this.
|
||||
#[allow(unused)]
|
||||
pub fn connects_dual_carriageway(map: &RawMap, id: &OriginalRoad) -> bool {
|
||||
let connectors_angle = angle(&map.roads[id]);
|
||||
// There are false positives like https://www.openstreetmap.org/way/4636259 when we're looking
|
||||
// at a segment along a marked dual carriageway. Filter out by requiring the intersecting dual
|
||||
// carriageways to differ by a minimum angle.
|
||||
let within_degrees = 10.0;
|
||||
|
||||
let mut i1_dual_carriageway = false;
|
||||
let mut oneway_names_i1: BTreeSet<String> = BTreeSet::new();
|
||||
for r in map.roads_per_intersection(id.i1) {
|
||||
let road = &map.roads[&r];
|
||||
if r == *id || connectors_angle.approx_eq(angle(road), within_degrees) {
|
||||
continue;
|
||||
}
|
||||
if road.osm_tags.is("dual_carriageway", "yes") {
|
||||
i1_dual_carriageway = true;
|
||||
}
|
||||
if road.osm_tags.is("oneway", "yes") {
|
||||
if let Some(name) = road.osm_tags.get(osm::NAME) {
|
||||
oneway_names_i1.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut i2_dual_carriageway = false;
|
||||
let mut oneway_names_i2: BTreeSet<String> = BTreeSet::new();
|
||||
for r in map.roads_per_intersection(id.i2) {
|
||||
let road = &map.roads[&r];
|
||||
if r == *id || connectors_angle.approx_eq(angle(road), within_degrees) {
|
||||
continue;
|
||||
}
|
||||
if road.osm_tags.is("dual_carriageway", "yes") {
|
||||
i2_dual_carriageway = true;
|
||||
}
|
||||
if road.osm_tags.is("oneway", "yes") {
|
||||
if let Some(name) = road.osm_tags.get(osm::NAME) {
|
||||
oneway_names_i2.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(i1_dual_carriageway && i2_dual_carriageway)
|
||||
|| oneway_names_i1
|
||||
.intersection(&oneway_names_i2)
|
||||
.next()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn angle(r: &RawRoad) -> Angle {
|
||||
r.center_points[0].angle_to(*r.center_points.last().unwrap())
|
||||
}
|
99
raw_map/src/transform/find_short_roads.rs
Normal file
99
raw_map/src/transform/find_short_roads.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use abstutil::Timer;
|
||||
use geom::Distance;
|
||||
|
||||
use crate::{IntersectionType, OriginalRoad, RawMap};
|
||||
|
||||
/// Combines a few different sources/methods to decide which roads are short. Marks them for
|
||||
/// merging.
|
||||
///
|
||||
/// 1) Anything tagged in OSM
|
||||
/// 2) Anything a temporary local merge_osm_ways.json file
|
||||
/// 3) If `consolidate_all` is true, an experimental distance-based heuristic
|
||||
pub fn find_short_roads(map: &mut RawMap, consolidate_all: bool) -> Vec<OriginalRoad> {
|
||||
let mut roads = Vec::new();
|
||||
for (id, road) in &map.roads {
|
||||
if road.osm_tags.is("junction", "intersection") {
|
||||
roads.push(*id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if consolidate_all && distance_heuristic(*id, map) {
|
||||
roads.push(*id);
|
||||
}
|
||||
}
|
||||
|
||||
// Use this to quickly test overrides to some ways before upstreaming in OSM.
|
||||
// Since these IDs might be based on already merged roads, do these last.
|
||||
if let Ok(ways) = abstio::maybe_read_json::<Vec<OriginalRoad>>(
|
||||
"merge_osm_ways.json".to_string(),
|
||||
&mut Timer::throwaway(),
|
||||
) {
|
||||
roads.extend(ways);
|
||||
}
|
||||
|
||||
for id in &roads {
|
||||
map.roads
|
||||
.get_mut(id)
|
||||
.unwrap()
|
||||
.osm_tags
|
||||
.insert("junction", "intersection");
|
||||
}
|
||||
|
||||
roads
|
||||
}
|
||||
|
||||
fn distance_heuristic(id: OriginalRoad, map: &RawMap) -> bool {
|
||||
let road_length = if let Some(pl) = map.trimmed_road_geometry(id) {
|
||||
pl.length()
|
||||
} else {
|
||||
// The road or something near it collapsed down into a single point or something. This can
|
||||
// happen while merging several short roads around a single junction.
|
||||
return false;
|
||||
};
|
||||
|
||||
// Any road anywhere shorter than this should get merged.
|
||||
road_length < Distance::meters(5.0)
|
||||
}
|
||||
|
||||
impl RawMap {
|
||||
/// A heuristic to find short roads near traffic signals
|
||||
pub fn find_traffic_signal_clusters(&mut self) -> Vec<OriginalRoad> {
|
||||
let threshold = Distance::meters(20.0);
|
||||
|
||||
// Simplest start: look for short roads connected to traffic signals.
|
||||
//
|
||||
// (This will miss sequences of short roads with stop signs in between a cluster of traffic
|
||||
// signals)
|
||||
//
|
||||
// After trying out around Loop 101, what we really want to do is find clumps of 2 or 4
|
||||
// traffic signals, find all the segments between them, and merge those.
|
||||
let mut results = Vec::new();
|
||||
for (id, road) in &self.roads {
|
||||
if road.osm_tags.is("junction", "intersection") {
|
||||
continue;
|
||||
}
|
||||
let i1 = self.intersections[&id.i1].intersection_type;
|
||||
let i2 = self.intersections[&id.i2].intersection_type;
|
||||
if i1 == IntersectionType::Border || i2 == IntersectionType::Border {
|
||||
continue;
|
||||
}
|
||||
if i1 != IntersectionType::TrafficSignal && i2 != IntersectionType::TrafficSignal {
|
||||
continue;
|
||||
}
|
||||
if let Ok((pl, _)) = road.get_geometry(*id, &self.config) {
|
||||
if pl.length() <= threshold {
|
||||
results.push(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id in &results {
|
||||
self.roads
|
||||
.get_mut(id)
|
||||
.unwrap()
|
||||
.osm_tags
|
||||
.insert("junction", "intersection");
|
||||
}
|
||||
results
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
|
||||
use abstutil::Timer;
|
||||
use geom::{Angle, Distance};
|
||||
|
||||
use crate::osm::NodeID;
|
||||
use crate::{osm, OriginalRoad, RawMap, RawRoad};
|
||||
|
||||
/// Merge tiny "roads" that're actually just part of a complicated intersection. Returns all
|
||||
/// surviving intersections adjacent to one of these merged roads.
|
||||
pub fn merge_short_roads(map: &mut RawMap, consolidate_all: bool) -> BTreeSet<NodeID> {
|
||||
#![allow(clippy::logic_bug)] // remove once the TODO below is taken care of
|
||||
let mut merged = BTreeSet::new();
|
||||
|
||||
let mut queue: VecDeque<OriginalRoad> = VecDeque::new();
|
||||
for r in map.roads.keys() {
|
||||
queue.push_back(*r);
|
||||
|
||||
// TODO Enable after improving this heuristic.
|
||||
if false && connects_dual_carriageway(map, r) {
|
||||
debug!("{} connects dual carriageways", r);
|
||||
}
|
||||
}
|
||||
|
||||
while !queue.is_empty() {
|
||||
let id = queue.pop_front().unwrap();
|
||||
|
||||
// The road might've been deleted
|
||||
if !map.roads.contains_key(&id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if should_merge(map, &id, consolidate_all) {
|
||||
match map.merge_short_road(id) {
|
||||
Ok((i, _, _, new_roads)) => {
|
||||
merged.insert(i);
|
||||
queue.extend(new_roads);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Not merging short road / junction=intersection: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use this to quickly test overrides to some ways before upstreaming in OSM.
|
||||
// Since these IDs might be based on already merged roads, do these last.
|
||||
if let Ok(ways) = abstio::maybe_read_json::<Vec<OriginalRoad>>(
|
||||
"merge_osm_ways.json".to_string(),
|
||||
&mut Timer::throwaway(),
|
||||
) {
|
||||
for id in ways {
|
||||
match map.merge_short_road(id) {
|
||||
Ok((i, _, _, _)) => {
|
||||
merged.insert(i);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Not merging short road / junction=intersection: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
merged
|
||||
}
|
||||
|
||||
fn should_merge(map: &RawMap, id: &OriginalRoad, consolidate_all: bool) -> bool {
|
||||
// See https://wiki.openstreetmap.org/wiki/Proposed_features/junction%3Dintersection
|
||||
if map.roads[id].osm_tags.is("junction", "intersection") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO Keep everything below disabled until merging works better.
|
||||
if !consolidate_all {
|
||||
return false;
|
||||
}
|
||||
|
||||
let road_length = if let Some(pl) = map.trimmed_road_geometry(*id) {
|
||||
pl.length()
|
||||
} else {
|
||||
// The road or something near it collapsed down into a single point or something. This can
|
||||
// happen while merging several short roads around a single junction.
|
||||
return false;
|
||||
};
|
||||
|
||||
// Any road anywhere shorter than this should get merged.
|
||||
if road_length < Distance::meters(5.0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Roads connecting dual carriageways can use a longer threshold for merging.
|
||||
if connects_dual_carriageway(map, id) && road_length < Distance::meters(10.0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// Does this road go between two divided one-ways? Ideally they're tagged explicitly
|
||||
// (https://wiki.openstreetmap.org/wiki/Tag:dual_carriageway%3Dyes), but we can also apply simple
|
||||
// heuristics to guess this.
|
||||
fn connects_dual_carriageway(map: &RawMap, id: &OriginalRoad) -> bool {
|
||||
let connectors_angle = angle(&map.roads[id]);
|
||||
// There are false positives like https://www.openstreetmap.org/way/4636259 when we're looking
|
||||
// at a segment along a marked dual carriageway. Filter out by requiring the intersecting dual
|
||||
// carriageways to differ by a minimum angle.
|
||||
let within_degrees = 10.0;
|
||||
|
||||
let mut i1_dual_carriageway = false;
|
||||
let mut oneway_names_i1: BTreeSet<String> = BTreeSet::new();
|
||||
for r in map.roads_per_intersection(id.i1) {
|
||||
let road = &map.roads[&r];
|
||||
if r == *id || connectors_angle.approx_eq(angle(road), within_degrees) {
|
||||
continue;
|
||||
}
|
||||
if road.osm_tags.is("dual_carriageway", "yes") {
|
||||
i1_dual_carriageway = true;
|
||||
}
|
||||
if road.osm_tags.is("oneway", "yes") {
|
||||
if let Some(name) = road.osm_tags.get(osm::NAME) {
|
||||
oneway_names_i1.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut i2_dual_carriageway = false;
|
||||
let mut oneway_names_i2: BTreeSet<String> = BTreeSet::new();
|
||||
for r in map.roads_per_intersection(id.i2) {
|
||||
let road = &map.roads[&r];
|
||||
if r == *id || connectors_angle.approx_eq(angle(road), within_degrees) {
|
||||
continue;
|
||||
}
|
||||
if road.osm_tags.is("dual_carriageway", "yes") {
|
||||
i2_dual_carriageway = true;
|
||||
}
|
||||
if road.osm_tags.is("oneway", "yes") {
|
||||
if let Some(name) = road.osm_tags.get(osm::NAME) {
|
||||
oneway_names_i2.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(i1_dual_carriageway && i2_dual_carriageway)
|
||||
|| oneway_names_i1
|
||||
.intersection(&oneway_names_i2)
|
||||
.next()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn angle(r: &RawRoad) -> Angle {
|
||||
r.center_points[0].angle_to(*r.center_points.last().unwrap())
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
@ -71,8 +71,6 @@ impl RawMap {
|
||||
continue;
|
||||
}
|
||||
// If we're going to delete this later, don't bother!
|
||||
// TODO When we do automatic consolidation and don't just look for this tag,
|
||||
// we'll need to get more clever here, or temporarily apply this tag.
|
||||
if self.roads[&r].osm_tags.is("junction", "intersection") {
|
||||
continue;
|
||||
}
|
||||
@ -187,3 +185,37 @@ impl RawMap {
|
||||
Ok((i1, i2, deleted, created))
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge all roads marked with `junction=intersection`
|
||||
pub fn merge_all_junctions(map: &mut RawMap) {
|
||||
let mut queue: VecDeque<OriginalRoad> = VecDeque::new();
|
||||
for (id, road) in &map.roads {
|
||||
if road.osm_tags.is("junction", "intersection") {
|
||||
queue.push_back(*id);
|
||||
}
|
||||
}
|
||||
|
||||
while !queue.is_empty() {
|
||||
let id = queue.pop_front().unwrap();
|
||||
|
||||
// The road might've been deleted by a previous merge_short_road call
|
||||
if !map.roads.contains_key(&id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match map.merge_short_road(id) {
|
||||
Ok((_, _, _, new_roads)) => {
|
||||
// Some road IDs still in the queue might have changed, so check the new_roads for
|
||||
// anything we should try to merge
|
||||
for r in new_roads {
|
||||
if map.roads[&r].osm_tags.is("junction", "intersection") {
|
||||
queue.push_back(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Not merging short road / junction=intersection: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ use abstutil::Timer;
|
||||
use crate::RawMap;
|
||||
|
||||
mod collapse_intersections;
|
||||
mod merge_intersections;
|
||||
mod dual_carriageways;
|
||||
mod find_short_roads;
|
||||
mod merge_short_road;
|
||||
mod remove_disconnected;
|
||||
mod snappy;
|
||||
@ -35,7 +36,8 @@ impl RawMap {
|
||||
remove_disconnected::remove_disconnected_roads(self, timer);
|
||||
|
||||
timer.start("merging short roads");
|
||||
merge_intersections::merge_short_roads(self, consolidate_all_intersections);
|
||||
find_short_roads::find_short_roads(self, consolidate_all_intersections);
|
||||
merge_short_road::merge_all_junctions(self);
|
||||
timer.stop("merging short roads");
|
||||
|
||||
timer.start("collapsing degenerate intersections");
|
||||
|
Loading…
Reference in New Issue
Block a user