ensuring no tiny Lines exist in most cases

This commit is contained in:
Dustin Carlino 2019-01-29 10:54:37 -08:00
parent 7495d29288
commit 103a4ca053
11 changed files with 151 additions and 77 deletions

View File

@ -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?

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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);
}
}

View File

@ -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
}

View File

@ -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);

View File

@ -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 {