mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-27 00:12:55 +03:00
ensuring no tiny Lines exist in most cases
This commit is contained in:
parent
7495d29288
commit
103a4ca053
@ -37,9 +37,3 @@
|
||||
- first: all the maps fully convert and display in some form; all tests pass or are disabled
|
||||
- slowly hone away at problems currently with errors printed (like bad pl shift angles)
|
||||
- eventually: every intersection has at least a turn, minimum lengths enforced, etc
|
||||
|
||||
### Current major geometry problems
|
||||
|
||||
- sketchy geometry code that's probably wrong... although actually seems fine in montlake and 23rd currently
|
||||
- dist along of pt in line. can we just use the t value directly?
|
||||
- do we ever directly need dist_along_of_point?
|
||||
|
@ -2,11 +2,16 @@
|
||||
|
||||
## Geometry
|
||||
|
||||
- bad polyline shifting remains
|
||||
- investigate the remaining cases
|
||||
- try fixing all problems again?
|
||||
- read the TODOs carefully
|
||||
- make polygons use the corrections too?
|
||||
- why is E Olin Pl getting totally annihilated? O82
|
||||
|
||||
- try fixed pt again, for determinism purposes mostly
|
||||
- successful: Lines of ~0 length gone
|
||||
- but due to bad polyline shifting, some things would loudly break if we squished pts down always
|
||||
|
||||
- underlying problems
|
||||
- bad polyline shifting remains
|
||||
- from the remaining cases, looks like we need to totally remove some tight points and retry
|
||||
- make polygons use the corrections too?
|
||||
|
||||
- generalized_trim_back
|
||||
- breaks down when we have jagged lane endings due to polyline shift angle correction
|
||||
|
@ -21,7 +21,7 @@ impl DrawBuilding {
|
||||
let mut front_path_line = bldg.front_path.line.clone();
|
||||
let len = front_path_line.length();
|
||||
let trim_back = LANE_THICKNESS / 2.0 * si::M;
|
||||
if len > trim_back {
|
||||
if len > trim_back && len - trim_back > geom::EPSILON_DIST {
|
||||
front_path_line = Line::new(
|
||||
front_path_line.pt1(),
|
||||
front_path_line.dist_along(len - trim_back),
|
||||
|
@ -8,8 +8,11 @@ use std::fmt;
|
||||
pub struct Line(Pt2D, Pt2D);
|
||||
|
||||
impl Line {
|
||||
// TODO only one place outside this crate calls this, try to fix maybe?
|
||||
pub fn new(pt1: Pt2D, pt2: Pt2D) -> Line {
|
||||
let len = pt1.dist_to(pt2);
|
||||
if len < EPSILON_DIST {
|
||||
panic!("Tiny line with length {}", len);
|
||||
}
|
||||
Line(pt1, pt2)
|
||||
}
|
||||
|
||||
@ -68,12 +71,8 @@ impl Line {
|
||||
]
|
||||
}
|
||||
|
||||
// TODO valid to do euclidean distance on world-space points that're formed from
|
||||
// Haversine?
|
||||
pub fn length(&self) -> si::Meter<f64> {
|
||||
((self.pt1().x() - self.pt2().x()).powi(2) + (self.pt1().y() - self.pt2().y()).powi(2))
|
||||
.sqrt()
|
||||
* si::M
|
||||
self.pt1().dist_to(self.pt2())
|
||||
}
|
||||
|
||||
// TODO Also return the distance along self
|
||||
|
@ -17,6 +17,25 @@ pub struct PolyLine {
|
||||
impl PolyLine {
|
||||
pub fn new(pts: Vec<Pt2D>) -> PolyLine {
|
||||
assert!(pts.len() >= 2);
|
||||
// This checks no lines are too small. Could take the other approach and automatically
|
||||
// squish down points here and make sure the final result is at least EPSILON_DIST.
|
||||
// But probably better for the callers to do this -- they have better understanding of what
|
||||
// needs to be squished down, why, and how.
|
||||
if pts
|
||||
.windows(2)
|
||||
.any(|pair| pair[0].approx_eq(pair[1], EPSILON_DIST))
|
||||
{
|
||||
let length = pts.windows(2).fold(0.0 * si::M, |so_far, pair| {
|
||||
so_far + pair[0].dist_to(pair[1])
|
||||
});
|
||||
panic!(
|
||||
"PL with total length {} and {} pts has ~dupe pts: {:?}",
|
||||
length,
|
||||
pts.len(),
|
||||
pts
|
||||
);
|
||||
}
|
||||
|
||||
let length = pts.windows(2).fold(0.0 * si::M, |so_far, pair| {
|
||||
so_far + Line::new(pair[0], pair[1]).length()
|
||||
});
|
||||
@ -32,6 +51,25 @@ impl PolyLine {
|
||||
PolyLine { pts, length }
|
||||
}
|
||||
|
||||
// TODO Get rid of this when possible. Probably have to fix underlying problems in
|
||||
// shift_with_sharp_angles.
|
||||
fn new_without_checks(pts: Vec<Pt2D>) -> PolyLine {
|
||||
assert!(pts.len() >= 2);
|
||||
let length = pts.windows(2).fold(0.0 * si::M, |so_far, pair| {
|
||||
so_far + pair[0].dist_to(pair[1])
|
||||
});
|
||||
|
||||
// Can't have duplicates! If the polyline ever crosses back on itself, all sorts of things
|
||||
// are broken.
|
||||
let seen_pts: HashSet<HashablePt2D> =
|
||||
pts.iter().map(|pt| HashablePt2D::from(*pt)).collect();
|
||||
if seen_pts.len() != pts.len() {
|
||||
panic!("PolyLine has repeat points: {:?}", pts);
|
||||
}
|
||||
|
||||
PolyLine { pts, length }
|
||||
}
|
||||
|
||||
pub fn make_polygons_for_boundary(pts: Vec<Pt2D>, thickness: f64) -> Polygon {
|
||||
// Points WILL repeat -- fast-path some stuff.
|
||||
let pl = PolyLine {
|
||||
@ -82,7 +120,7 @@ impl PolyLine {
|
||||
|
||||
// Returns the excess distance left over from the end.
|
||||
pub fn slice(&self, start: si::Meter<f64>, end: si::Meter<f64>) -> (PolyLine, si::Meter<f64>) {
|
||||
if start >= end || start < 0.0 * si::M || end < 0.0 * si::M {
|
||||
if start >= end || start < 0.0 * si::M || end < 0.0 * si::M || end - start < EPSILON_DIST {
|
||||
panic!("Can't get a polyline slice [{}, {}]", start, end);
|
||||
}
|
||||
|
||||
@ -99,12 +137,17 @@ impl PolyLine {
|
||||
|
||||
// Does this line contain the last point of the slice?
|
||||
if dist_so_far + length >= end {
|
||||
result.push(line.dist_along(end - dist_so_far));
|
||||
let last_pt = line.dist_along(end - dist_so_far);
|
||||
if result.last().unwrap().approx_eq(last_pt, EPSILON_DIST) {
|
||||
result.pop();
|
||||
}
|
||||
result.push(last_pt);
|
||||
return (PolyLine::new(result), 0.0 * si::M);
|
||||
}
|
||||
|
||||
// If we're in the middle, just collect the endpoint.
|
||||
if !result.is_empty() {
|
||||
// If we're in the middle, just collect the endpoint. But not if it's too close to the
|
||||
// previous point (namely, the start, which could be somewhere far along a line)
|
||||
if !result.is_empty() && !result.last().unwrap().approx_eq(line.pt2(), EPSILON_DIST) {
|
||||
result.push(line.pt2());
|
||||
}
|
||||
|
||||
@ -187,19 +230,26 @@ impl PolyLine {
|
||||
Some(PolyLine::new(self.pts[0..self.pts.len() - 1].to_vec()))
|
||||
}
|
||||
|
||||
// Things to remember about shifting polylines:
|
||||
// - the length before and after probably don't match up
|
||||
// - the number of points does match
|
||||
pub fn shift_right(&self, width: f64) -> PolyLine {
|
||||
let result = self.shift_with_sharp_angles(width);
|
||||
let fixed = fix_angles(self, result);
|
||||
check_angles(self, &fixed);
|
||||
fixed
|
||||
self.shift_with_corrections(width)
|
||||
}
|
||||
|
||||
pub fn shift_left(&self, width: f64) -> PolyLine {
|
||||
let result = self.shift_with_sharp_angles(-width);
|
||||
let fixed = fix_angles(self, result);
|
||||
self.shift_with_corrections(-width)
|
||||
}
|
||||
|
||||
// Things to remember about shifting polylines:
|
||||
// - the length before and after probably don't match up
|
||||
// - the number of points will match
|
||||
fn shift_with_corrections(&self, width: f64) -> PolyLine {
|
||||
let result = self.shift_with_sharp_angles(width);
|
||||
// Do this deduping here, so make_polygons can keep using the non-deduped version.
|
||||
let deduped = PolyLine::new_without_checks(Pt2D::approx_dedupe(result.pts, EPSILON_DIST));
|
||||
let fixed = if deduped.pts.len() == self.pts.len() {
|
||||
fix_angles(self, deduped)
|
||||
} else {
|
||||
deduped
|
||||
};
|
||||
check_angles(self, &fixed);
|
||||
fixed
|
||||
}
|
||||
@ -221,17 +271,18 @@ impl PolyLine {
|
||||
|
||||
let l1 = Line::new(pt1_raw, pt2_raw).shift_either_direction(width);
|
||||
let l2 = Line::new(pt2_raw, pt3_raw).shift_either_direction(width);
|
||||
// When the lines are perfectly parallel, it means pt2_shift_1st == pt2_shift_2nd and the
|
||||
// original geometry is redundant.
|
||||
let pt2_shift = l1
|
||||
.infinite()
|
||||
.intersection(&l2.infinite())
|
||||
.unwrap_or_else(|| l1.pt2());
|
||||
|
||||
if pt3_idx == 2 {
|
||||
result.push(l1.pt1());
|
||||
}
|
||||
result.push(pt2_shift);
|
||||
|
||||
if let Some(pt2_shift) = l1.infinite().intersection(&l2.infinite()) {
|
||||
result.push(pt2_shift);
|
||||
} else {
|
||||
// When the lines are perfectly parallel, it means pt2_shift_1st == pt2_shift_2nd
|
||||
// and the original geometry is redundant.
|
||||
result.push(l1.pt2());
|
||||
}
|
||||
if pt3_idx == self.pts.len() - 1 {
|
||||
result.push(l2.pt2());
|
||||
break;
|
||||
@ -243,13 +294,14 @@ impl PolyLine {
|
||||
}
|
||||
|
||||
assert!(result.len() == self.pts.len());
|
||||
PolyLine::new(result)
|
||||
PolyLine::new_without_checks(result)
|
||||
}
|
||||
|
||||
pub fn make_polygons(&self, width: f64) -> Polygon {
|
||||
// TODO Don't use the angle corrections yet -- they seem to do weird things.
|
||||
let side1 = self.shift_with_sharp_angles(width / 2.0);
|
||||
let side2 = self.shift_with_sharp_angles(-width / 2.0);
|
||||
assert_eq!(side1.pts.len(), side2.pts.len());
|
||||
|
||||
let side2_offset = side1.pts.len();
|
||||
let mut points = side1.pts;
|
||||
@ -341,6 +393,10 @@ impl PolyLine {
|
||||
if let Some(idx) = self.lines().iter().position(|l| l.contains_pt(pt)) {
|
||||
let mut pts = self.pts.clone();
|
||||
pts.split_off(idx + 1);
|
||||
// Make sure the last line isn't too tiny
|
||||
if pts.last().unwrap().approx_eq(pt, EPSILON_DIST) {
|
||||
pts.pop();
|
||||
}
|
||||
pts.push(pt);
|
||||
return Some(PolyLine::new(pts));
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Angle, GPSBounds, Line, LonLat};
|
||||
use crate::{Angle, GPSBounds, LonLat};
|
||||
use aabb_quadtree::geom::{Point, Rect};
|
||||
use dimensioned::si;
|
||||
use ordered_float::NotNan;
|
||||
@ -99,12 +99,10 @@ impl Pt2D {
|
||||
Pt2D::new(self.x() + dist * cos, self.y() + dist * sin)
|
||||
}
|
||||
|
||||
// TODO valid to do euclidean distance on world-space points that're formed from
|
||||
// Haversine?
|
||||
pub fn dist_to(self, to: Pt2D) -> si::Meter<f64> {
|
||||
if self == to {
|
||||
0.0 * si::M
|
||||
} else {
|
||||
Line::new(self, to).length()
|
||||
}
|
||||
((self.x() - to.x()).powi(2) + (self.y() - to.y()).powi(2)).sqrt() * si::M
|
||||
}
|
||||
|
||||
pub fn angle_to(&self, to: Pt2D) -> Angle {
|
||||
@ -126,6 +124,17 @@ impl Pt2D {
|
||||
let len = pts.len() as f64;
|
||||
Pt2D::new(x / len, y / len)
|
||||
}
|
||||
|
||||
// Temporary until Pt2D has proper resolution.
|
||||
pub fn approx_dedupe(pts: Vec<Pt2D>, threshold: si::Meter<f64>) -> Vec<Pt2D> {
|
||||
let mut result: Vec<Pt2D> = Vec::new();
|
||||
for pt in pts {
|
||||
if result.is_empty() || !result.last().unwrap().approx_eq(pt, threshold) {
|
||||
result.push(pt);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pt2D {
|
||||
|
@ -20,11 +20,13 @@ pub fn make_all_buildings(
|
||||
timer.start_iter("get building center points", input.len());
|
||||
for b in input {
|
||||
timer.next();
|
||||
let pts = b
|
||||
.points
|
||||
.iter()
|
||||
.map(|coord| Pt2D::from_gps(*coord, gps_bounds).unwrap())
|
||||
.collect();
|
||||
let pts = Pt2D::approx_dedupe(
|
||||
b.points
|
||||
.iter()
|
||||
.map(|coord| Pt2D::from_gps(*coord, gps_bounds).unwrap())
|
||||
.collect(),
|
||||
geom::EPSILON_DIST,
|
||||
);
|
||||
let center: HashablePt2D = Pt2D::center(&pts).into();
|
||||
pts_per_bldg.push(pts);
|
||||
center_per_bldg.push(center);
|
||||
@ -74,7 +76,9 @@ fn trim_front_path(bldg_points: &Vec<Pt2D>, path: Line) -> Line {
|
||||
for bldg_line in bldg_points.windows(2) {
|
||||
let l = Line::new(bldg_line[0], bldg_line[1]);
|
||||
if let Some(hit) = l.intersection(&path) {
|
||||
return Line::new(hit, path.pt2());
|
||||
if !hit.approx_eq(path.pt2(), geom::EPSILON_DIST) {
|
||||
return Line::new(hit, path.pt2());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Just give up
|
||||
|
@ -3,7 +3,6 @@ use crate::{
|
||||
LaneID, Parcel, Road, RoadID, Turn, TurnID, LANE_THICKNESS,
|
||||
};
|
||||
use abstutil::Timer;
|
||||
use dimensioned::si;
|
||||
use geom::{Bounds, GPSBounds, Polygon, Pt2D};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@ -151,7 +150,7 @@ pub fn make_half_map(
|
||||
for t in half_map.turns.values_mut() {
|
||||
t.lookup_idx = half_map.turn_lookup.len();
|
||||
half_map.turn_lookup.push(t.id);
|
||||
if t.geom.length() < 0.01 * si::M {
|
||||
if t.geom.length() < geom::EPSILON_DIST {
|
||||
warn!("u{} is a very short turn", t.lookup_idx);
|
||||
}
|
||||
}
|
||||
|
@ -106,12 +106,28 @@ fn generalized_trim_back(
|
||||
// How could something perpendicular to a shifted polyline never hit the original
|
||||
// polyline?
|
||||
let trim_to = road_center.intersection_infinite(&perp).unwrap();
|
||||
let trimmed = road_center.get_slice_ending_at(trim_to).unwrap();
|
||||
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.
|
||||
if road_center
|
||||
.first_pt()
|
||||
.approx_eq(trim_to, geom::EPSILON_DIST)
|
||||
{
|
||||
// This is happening near a small road.
|
||||
error!(
|
||||
"for {} and {}, cant trim {} (len {}) to {}",
|
||||
r1,
|
||||
r2,
|
||||
road_center,
|
||||
road_center.length(),
|
||||
trim_to
|
||||
);
|
||||
} else {
|
||||
let trimmed = road_center.get_slice_ending_at(trim_to).unwrap();
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +175,7 @@ fn generalized_trim_back(
|
||||
}
|
||||
}
|
||||
endpoints.sort_by_key(|pt| HashablePt2D::from(*pt));
|
||||
endpoints = approx_dedupe(endpoints);
|
||||
endpoints = Pt2D::approx_dedupe(endpoints, 1.0 * si::M);
|
||||
|
||||
let center = Pt2D::center(&endpoints);
|
||||
endpoints.sort_by_key(|pt| Line::new(center, *pt).angle().normalized_degrees() as i64);
|
||||
@ -209,14 +225,3 @@ fn deadend(
|
||||
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
|
||||
}
|
||||
|
@ -18,11 +18,13 @@ pub fn make_all_parcels(
|
||||
let mut center_per_parcel: Vec<HashablePt2D> = Vec::new();
|
||||
let mut query: HashSet<HashablePt2D> = HashSet::new();
|
||||
for p in input {
|
||||
let pts = p
|
||||
.points
|
||||
.iter()
|
||||
.map(|coord| Pt2D::from_gps(*coord, gps_bounds).unwrap())
|
||||
.collect();
|
||||
let pts = Pt2D::approx_dedupe(
|
||||
p.points
|
||||
.iter()
|
||||
.map(|coord| Pt2D::from_gps(*coord, gps_bounds).unwrap())
|
||||
.collect(),
|
||||
geom::EPSILON_DIST,
|
||||
);
|
||||
let center: HashablePt2D = Pt2D::center(&pts).into();
|
||||
pts_per_parcel.push(pts);
|
||||
center_per_parcel.push(center);
|
||||
|
@ -329,7 +329,7 @@ fn make_vehicle_turn(lanes: &Vec<&Lane>, i: IntersectionID, l1: LaneID, l2: Lane
|
||||
to_pt(dst.first_pt()),
|
||||
);
|
||||
let pieces = 5;
|
||||
PolyLine::new(
|
||||
PolyLine::new(Pt2D::approx_dedupe(
|
||||
(0..=pieces)
|
||||
.map(|i| {
|
||||
from_pt(
|
||||
@ -339,7 +339,8 @@ fn make_vehicle_turn(lanes: &Vec<&Lane>, i: IntersectionID, l1: LaneID, l2: Lane
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
geom::EPSILON_DIST,
|
||||
))
|
||||
};
|
||||
|
||||
Turn {
|
||||
|
Loading…
Reference in New Issue
Block a user