-
-
Notifications
You must be signed in to change notification settings - Fork 763
Rotation version based on map transform #572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
XianlinSheng
wants to merge
14
commits into
louis-e:main
Choose a base branch
from
XianlinSheng:dev-xzpoly
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
8d6f7f0
add polygon prototype in xzbbox, only during tests
XianlinSheng 26bed97
Merge branch 'main' into dev-xzpoly
XianlinSheng b32542d
Merge branch 'main' into dev-xzpoly
XianlinSheng cc375bf
merge
XianlinSheng 68e8646
polygon cleanup, impl to xzbbox
XianlinSheng 4e5d95c
merge new main into poly branch
XianlinSheng 881cab5
rotation operator for map elements
XianlinSheng 9958be5
add rotation to ground elevation
XianlinSheng 55aeb00
add laplacian smooth after rotating elevation data; merge and fix jso…
XianlinSheng df0d658
fix test build
XianlinSheng ef65d62
fix clippy
XianlinSheng 9ba1fd2
move map transform into example files, no extra operations will be ad…
XianlinSheng 7ab71e1
add transforms for relations
XianlinSheng e82d730
merge main; is_multiple_of temporary change back to modulus for compi…
XianlinSheng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| mod polygon; | ||
| mod rectangle; | ||
| mod xzbbox_enum; | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,373 @@ | ||
| use super::rectangle::XZBBoxRect; | ||
| use crate::coordinate_system::cartesian::{XZPoint, XZVector}; | ||
| use geo::{Area, BoundingRect, Intersects, LineString, Point, Polygon}; | ||
| use ndarray::Array2; | ||
| use std::fmt; | ||
| use std::ops::{Add, AddAssign, Sub, SubAssign}; | ||
|
|
||
| /// An underlying shape polygon of XZBBox enum. | ||
| #[derive(Clone, Debug)] | ||
| pub struct XZBBoxPoly { | ||
| /// XZPoint list of the polygon | ||
| points: Vec<XZPoint>, | ||
|
|
||
| /// Mask on its bounding rect | ||
| mask: Array2<bool>, | ||
|
|
||
| /// Curcumscribed rect | ||
| rect: XZBBoxRect, | ||
|
|
||
| /// Total blocks covered by polygon (block center on the edge is valid) | ||
| total_valid_blocks: u64, | ||
| } | ||
|
|
||
| impl XZBBoxPoly { | ||
| pub fn new(points: Vec<XZPoint>) -> Result<Self, String> { | ||
| let len_ge_3 = points.len() >= 3; | ||
| if !len_ge_3 { | ||
| return Err(format!( | ||
| "Points too few to construct a polygon, minimal 3 but has only {}", | ||
| points.len() | ||
| )); | ||
| } | ||
|
|
||
| let linestring: LineString = points.iter().map(|p| (p.x as f64, p.z as f64)).collect(); | ||
| let geopolygon = Polygon::new(linestring.clone(), vec![]); | ||
|
|
||
| // find any intersections, use geopolygon | ||
| let segments: Vec<_> = geopolygon.exterior().lines().collect(); | ||
| let total_segments = segments.len(); | ||
| let no_intersect = !segments.iter().enumerate().any(|(i, seg1)| { | ||
| segments | ||
| .iter() | ||
| .skip(i + 2) // skip self-self and self-neighbor (they must intersect) | ||
| .take(total_segments - 3) // avoid start-last (they form a loop so must intersect) | ||
| .any(|seg2| seg1.intersects(seg2)) | ||
| }); | ||
| if !no_intersect { | ||
| return Err("Polygon self intersect".to_string()); | ||
| } | ||
|
|
||
| let area_nonzero = geopolygon.unsigned_area() > 0.0; | ||
| if !area_nonzero { | ||
| return Err("Polygon degenerate to zero area".to_string()); | ||
| } | ||
|
|
||
| let bbox = geopolygon | ||
| .bounding_rect() | ||
| .ok_or("Failed to obtain polygon bounding rect from geo".to_string())?; | ||
| let rect = XZBBoxRect::new( | ||
| XZPoint::new(bbox.min().x as i32, bbox.min().y as i32), | ||
| XZPoint::new(bbox.max().x as i32, bbox.max().y as i32), | ||
| ) | ||
| .map_err(|e| format!("Polygon bounding box error:\n{}", e))?; | ||
|
|
||
| let mut mask = Array2::from_elem( | ||
| ( | ||
| rect.total_blocks_x() as usize, | ||
| rect.total_blocks_z() as usize, | ||
| ), | ||
| false, | ||
| ); | ||
| for i in 0..rect.total_blocks_x() as usize { | ||
| for k in 0..rect.total_blocks_z() as usize { | ||
| let point = Point::new( | ||
| (i as i32 + rect.min().x) as f64, | ||
| (k as i32 + rect.min().z) as f64, | ||
| ); | ||
| mask[(i, k)] = geopolygon.intersects(&point); | ||
| } | ||
| } | ||
|
|
||
| let total_valid_blocks: u64 = mask.mapv(|v| v as u64).sum(); | ||
| let has_valid_blocks = total_valid_blocks > 0; | ||
| if !has_valid_blocks { | ||
| return Err(format!( | ||
| "Expected at least one valid blocks, but has {}", | ||
| total_valid_blocks | ||
| )); | ||
| } | ||
|
|
||
| Ok(Self { | ||
| points, | ||
| mask, | ||
| rect, | ||
| total_valid_blocks, | ||
| }) | ||
| } | ||
|
|
||
| pub fn points(&self) -> &Vec<XZPoint> { | ||
| &self.points | ||
| } | ||
|
|
||
| pub fn bounding_rect(&self) -> &XZBBoxRect { | ||
| &self.rect | ||
| } | ||
|
|
||
| /// Check whether an XZPoint is covered | ||
| pub fn contains(&self, xzpoint: &XZPoint) -> bool { | ||
| if !self.rect.contains(xzpoint) { | ||
| return false; | ||
| } | ||
|
|
||
| let ikpoint = xzpoint.calc_relative(self.rect.min()); | ||
| self.mask[(ikpoint.x as usize, ikpoint.z as usize)] | ||
| } | ||
| } | ||
|
|
||
| impl fmt::Display for XZBBoxPoly { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| write!(f, "Poly()") | ||
| } | ||
| } | ||
|
|
||
| // below are associated +- operators | ||
| impl Add<XZVector> for XZBBoxPoly { | ||
| type Output = XZBBoxPoly; | ||
|
|
||
| fn add(self, other: XZVector) -> Self { | ||
| Self { | ||
| points: self.points.iter().map(|p| *p + other).collect(), | ||
| mask: self.mask.clone(), | ||
| rect: self.rect + other, | ||
| total_valid_blocks: self.total_valid_blocks, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl AddAssign<XZVector> for XZBBoxPoly { | ||
| fn add_assign(&mut self, other: XZVector) { | ||
| self.points.iter_mut().for_each(|p| *p += other); | ||
| self.rect += other; | ||
| } | ||
| } | ||
|
|
||
| impl Sub<XZVector> for XZBBoxPoly { | ||
| type Output = XZBBoxPoly; | ||
|
|
||
| fn sub(self, other: XZVector) -> Self { | ||
| Self { | ||
| points: self.points.iter().map(|p| *p - other).collect(), | ||
| mask: self.mask.clone(), | ||
| rect: self.rect - other, | ||
| total_valid_blocks: self.total_valid_blocks, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl SubAssign<XZVector> for XZBBoxPoly { | ||
| fn sub_assign(&mut self, other: XZVector) { | ||
| self.points.iter_mut().for_each(|p| *p -= other); | ||
| self.rect -= other; | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use super::*; | ||
| use ndarray::array; | ||
|
|
||
| #[test] | ||
| fn test_construct() { | ||
| // 0,0 -> 1,1 rect | ||
| let points = vec![ | ||
| XZPoint::new(0, 0), | ||
| XZPoint::new(1, 0), | ||
| XZPoint::new(1, 1), | ||
| XZPoint::new(0, 1), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_ok()); | ||
|
|
||
| let poly = obj.unwrap(); | ||
| assert_eq!(poly.rect.min().x, 0); | ||
| assert_eq!(poly.rect.min().z, 0); | ||
| assert_eq!(poly.rect.max().x, 1); | ||
| assert_eq!(poly.rect.max().z, 1); | ||
|
|
||
| assert_eq!(poly.mask, Array2::from_elem((2, 2), true)); | ||
| assert_eq!(poly.total_valid_blocks, 4); | ||
|
|
||
| // 0,0 -> 2,2 rect | ||
| let points = vec![ | ||
| XZPoint::new(0, 0), | ||
| XZPoint::new(2, 0), | ||
| XZPoint::new(2, 2), | ||
| XZPoint::new(0, 2), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_ok()); | ||
|
|
||
| let poly = obj.unwrap(); | ||
| assert_eq!(poly.rect.min().x, 0); | ||
| assert_eq!(poly.rect.min().z, 0); | ||
| assert_eq!(poly.rect.max().x, 2); | ||
| assert_eq!(poly.rect.max().z, 2); | ||
|
|
||
| assert_eq!(poly.mask, Array2::from_elem((3, 3), true)); | ||
| assert_eq!(poly.total_valid_blocks, 9); | ||
|
|
||
| // -10,-5 -> 7,3 rect | ||
| let points = vec![ | ||
| XZPoint::new(-10, -5), | ||
| XZPoint::new(7, -5), | ||
| XZPoint::new(7, 3), | ||
| XZPoint::new(-10, 3), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_ok()); | ||
|
|
||
| let poly = obj.unwrap(); | ||
| assert_eq!(poly.rect.min().x, -10); | ||
| assert_eq!(poly.rect.min().z, -5); | ||
| assert_eq!(poly.rect.max().x, 7); | ||
| assert_eq!(poly.rect.max().z, 3); | ||
|
|
||
| assert_eq!(poly.mask, Array2::from_elem((18, 9), true)); | ||
| assert_eq!(poly.total_valid_blocks, 162); | ||
|
|
||
| // |x| + |y| = 1 diamond | ||
| let points = vec![ | ||
| XZPoint::new(0, -1), | ||
| XZPoint::new(1, 0), | ||
| XZPoint::new(0, 1), | ||
| XZPoint::new(-1, 0), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_ok()); | ||
|
|
||
| let poly = obj.unwrap(); | ||
| assert_eq!(poly.rect.min().x, -1); | ||
| assert_eq!(poly.rect.min().z, -1); | ||
| assert_eq!(poly.rect.max().x, 1); | ||
| assert_eq!(poly.rect.max().z, 1); | ||
|
|
||
| #[rustfmt::skip] | ||
| assert_eq!( | ||
| poly.mask, | ||
| array![ | ||
| [0, 1, 0], | ||
| [1, 1, 1], | ||
| [0, 1, 0], | ||
| ].mapv(|v| v != 0) | ||
| ); | ||
| assert_eq!(poly.total_valid_blocks, 5); | ||
|
|
||
| // triangle | ||
| let points = vec![ | ||
| XZPoint::new(0, 1), | ||
| XZPoint::new(-2, -1), | ||
| XZPoint::new(2, -1), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_ok()); | ||
|
|
||
| let poly = obj.unwrap(); | ||
| assert_eq!(poly.rect.min().x, -2); | ||
| assert_eq!(poly.rect.min().z, -1); | ||
| assert_eq!(poly.rect.max().x, 2); | ||
| assert_eq!(poly.rect.max().z, 1); | ||
|
|
||
| // edges exactly pass the center of (-1, 0) (1, 0) | ||
| #[rustfmt::skip] | ||
| assert_eq!( | ||
| poly.mask, | ||
| array![ | ||
| [1, 1, 1, 1, 1], | ||
| [0, 1, 1, 1, 0], | ||
| [0, 0, 1, 0, 0], | ||
| ].mapv(|v| v != 0).t() | ||
| ); | ||
| assert_eq!(poly.total_valid_blocks, 9); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_invalid_construct() { | ||
| // 0,0 -> 1,0 single line, not enough points | ||
| #[rustfmt::skip] | ||
| let points = vec![ | ||
| XZPoint::new(0, 0), | ||
| XZPoint::new(1, 0), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_err()); | ||
| let errmsg = obj.unwrap_err(); | ||
| assert!(errmsg.contains("too few")); | ||
|
|
||
| // one single line area 0 | ||
| #[rustfmt::skip] | ||
| let points = vec![ | ||
| XZPoint::new(0, 0), | ||
| XZPoint::new(1, 0), | ||
| XZPoint::new(1, 0) | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_err()); | ||
| let errmsg = obj.unwrap_err(); | ||
| assert!(!errmsg.contains("intersect")); // 0-length edges are not geo::intersections | ||
| assert!(errmsg.contains("degenerate")); // but if all following edges 0-len || parallel, it's degenerate | ||
|
|
||
| // 0,0 -> 2,0 -> 1,0 area 0 | ||
| #[rustfmt::skip] | ||
| let points = vec![ | ||
| XZPoint::new(0, 0), | ||
| XZPoint::new(2, 0), | ||
| XZPoint::new(1, 0) | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_err()); | ||
| let errmsg = obj.unwrap_err(); | ||
| assert!(!errmsg.contains("intersect")); // parallel covered edges are not geo::intersections | ||
| assert!(errmsg.contains("degenerate")); // but if all following edges 0-len || parallel, it's degenerate | ||
|
|
||
| // 0,0 -> 2,0 -> 1,0 -> 1,1 self intersect but non-zero area | ||
| let points = vec![ | ||
| XZPoint::new(0, 0), | ||
| XZPoint::new(2, 0), | ||
| XZPoint::new(1, 0), | ||
| XZPoint::new(1, 1), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_err()); | ||
| let errmsg = obj.unwrap_err(); | ||
| assert!(errmsg.contains("intersect")); | ||
| assert!(!errmsg.contains("degenerate")); // intersect is checked before area | ||
|
|
||
| // duplicate point | ||
| let points = vec![ | ||
| XZPoint::new(0, 0), | ||
| XZPoint::new(2, 0), | ||
| XZPoint::new(2, 0), | ||
| XZPoint::new(2, 1), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_err()); | ||
| let errmsg = obj.unwrap_err(); | ||
| assert!(errmsg.contains("intersect")); | ||
| assert!(!errmsg.contains("degenerate")); // intersect is checked before area | ||
|
|
||
| // |x| + |y| = 1 diamond but wrong order | ||
| let points = vec![ | ||
| XZPoint::new(0, -1), | ||
| XZPoint::new(-1, 0), | ||
| XZPoint::new(1, 0), | ||
| XZPoint::new(0, 1), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_err()); | ||
| let errmsg = obj.unwrap_err(); | ||
| assert!(errmsg.contains("intersect")); | ||
|
|
||
| // non symmetric diamond with wrong order | ||
| let points = vec![ | ||
| XZPoint::new(0, -1), | ||
| XZPoint::new(-1, 0), | ||
| XZPoint::new(2, 0), | ||
| XZPoint::new(0, 1), | ||
| ]; | ||
| let obj = XZBBoxPoly::new(points); | ||
| assert!(obj.is_err()); | ||
| let errmsg = obj.unwrap_err(); | ||
| assert!(errmsg.contains("intersect")); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rasterizing the polygon mask over the bounding rect is O(W*H); this is fine for normal areas but won't it balloon on huge selections?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might, but peak memory usage is only 2% higher in the benchmark. Since the elements on the map also scales with the area, I suppose this ratio will stay in the order of constant. I did this to increase inference performance, but if you want some more optimizations on it there are ways to compress it and balance between speed and memory.