Skip to content

Commit

Permalink
Slight performance improvement
Browse files Browse the repository at this point in the history
- Deduplicate ring coordinates and remove rings with less than three points before computing coordinates with (x|y)_step and (x|y)_origin
- Use bbox of rings / holes to filter calls to ring_contains, which calculates whether a point falls within a ring
  • Loading branch information
mthh committed Oct 1, 2023
1 parent af5095a commit 36db72e
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 13 deletions.
32 changes: 30 additions & 2 deletions src/contains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ use geo_types::Coord;

/// Compute whether a given ring contains a given hole.
pub(crate) fn contains(ring: &[Coord<f64>], hole: &[Coord<f64>]) -> bool {
// Compute bbox first to avoid more expensive point-in-polygon tests
let (min_x, min_y, max_x, max_y) = get_bbox(ring);
let (hole_min_x, hole_min_y, hole_max_x, hole_max_y) = get_bbox(hole);
if hole_min_x < min_x || hole_min_y < min_y || hole_max_x > max_x || hole_max_y > max_y {
return false;
}
// Hole bbox is inside ring bbox, so we need to compute
// if any point of the hole is inside the ring
for point in hole.iter() {
if ring_contains(ring, point) {
return true;
Expand All @@ -10,11 +18,32 @@ pub(crate) fn contains(ring: &[Coord<f64>], hole: &[Coord<f64>]) -> bool {
false
}

fn get_bbox(ring: &[Coord<f64>]) -> (f64, f64, f64, f64) {
let mut min_x = ring[0].x;
let mut min_y = ring[0].y;
let mut max_x = min_x;
let mut max_y = min_y;

for point in ring.iter().skip(1) {
if point.x < min_x {
min_x = point.x;
} else if point.x > max_x {
max_x = point.x;
}
if point.y < min_y {
min_y = point.y;
} else if point.y > max_y {
max_y = point.y;
}
}

(min_x, min_y, max_x, max_y)
}

fn ring_contains(ring: &[Coord<f64>], point: &Coord<f64>) -> bool {
let x = point.x;
let y = point.y;
let n = ring.len();
// let mut contains = -1;
let mut contains = false;
let mut j = n - 1;
for (i, pi) in ring.iter().enumerate() {
Expand All @@ -27,7 +56,6 @@ fn ring_contains(ring: &[Coord<f64>], point: &Coord<f64>) -> bool {
return false;
}
if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
// contains = -contains;
contains = !contains;
}
j = i;
Expand Down
23 changes: 12 additions & 11 deletions src/isobands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,27 +255,28 @@ impl ContourBuilder {
let mut rings: Vec<(LineString<f64>, f64)> = raw_band
.into_iter()
.map(|mut points| {
// Sometimes paths have repeated points, so we remove them first
points.dedup();
points
})
// We dont want 'empty' rings
.filter(|potential_ring| potential_ring.len() > 2)
.map(|mut points| {
// Use x_origin, y_origin, x_step and y_step to calculate the coordinates of the points
// if they are not the default values
if (self.x_origin, self.y_origin) != (0f64, 0f64)
|| (self.x_step, self.y_step) != (1f64, 1f64)
{
// Use x_origin, y_origin, x_step and y_step to calculate the coordinates of the points
// if they are not the default values
points.iter_mut().for_each(|point| {
let pt_x = point.x_mut();
*pt_x = self.x_origin + *pt_x * self.x_step;
let pt_y = point.y_mut();
*pt_y = self.y_origin + *pt_y * self.y_step;
});
}
// Sometimes paths have repeated points, so we remove them
points.dedup();
points
})
// We dont want 'empty' rings
.filter(|potential_ring| potential_ring.len() > 2)
// We compute the area now as we will need it to sort the rings
// (+ also later to check if a ring is clockwise or not)
.map(|points| {

// We also compute the area now as we will need it to sort the rings
// (+ also later to check if a ring is clockwise or not)
let closed_linestring: LineString = points.into();
let area = area(&closed_linestring.0);
(closed_linestring, area)
Expand Down

0 comments on commit 36db72e

Please sign in to comment.