use crate::core::surface::Surface;
use crate::foundation::{BBox, Result};
use geo::prelude::*;
use geo::{Coord, LineString, Point, Polygon};
use ndarray::Array2;
use std::path::Path;
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct PolygonSet {
polys: Vec<Polygon<f64>>,
}
impl PolygonSet {
pub(crate) fn from_rings(rings: Vec<Vec<[f64; 3]>>) -> PolygonSet {
let polys = rings
.into_iter()
.filter(|r| r.len() >= 3)
.map(|r| {
let coords: Vec<Coord<f64>> =
r.iter().map(|c| Coord { x: c[0], y: c[1] }).collect();
Polygon::new(LineString::new(coords), Vec::new())
})
.collect();
PolygonSet { polys }
}
pub fn load_geojson(path: impl AsRef<Path>) -> Result<PolygonSet> {
let rings = crate::io::vector::load_polygon_rings_geojson(path.as_ref())?;
Ok(PolygonSet::from_rings(rings))
}
pub fn load_irap_polygons(path: impl AsRef<Path>) -> Result<PolygonSet> {
let rings = crate::io::xyz::load_polygons(path.as_ref())?;
Ok(PolygonSet::from_rings(rings))
}
pub fn load_shapefile(path: impl AsRef<Path>) -> Result<PolygonSet> {
let rings = crate::io::vector::load_polygon_rings_shapefile(path.as_ref())?;
Ok(PolygonSet::from_rings(rings))
}
pub fn contains(&self, x: f64, y: f64) -> bool {
let p = Point::new(x, y);
self.polys.iter().any(|poly| poly.contains(&p))
}
pub fn area(&self) -> f64 {
self.polys.iter().map(|p| p.unsigned_area()).sum()
}
pub fn rings(&self) -> Vec<Vec<[f64; 3]>> {
self.polys
.iter()
.map(|p| p.exterior().coords().map(|c| [c.x, c.y, 0.0]).collect())
.collect()
}
pub fn bbox(&self) -> BBox {
let mut b = BBox {
xmin: f64::INFINITY,
ymin: f64::INFINITY,
xmax: f64::NEG_INFINITY,
ymax: f64::NEG_INFINITY,
};
let mut any = false;
for poly in &self.polys {
if let Some(rect) = poly.bounding_rect() {
any = true;
b.xmin = b.xmin.min(rect.min().x);
b.ymin = b.ymin.min(rect.min().y);
b.xmax = b.xmax.max(rect.max().x);
b.ymax = b.ymax.max(rect.max().y);
}
}
if any {
b
} else {
BBox {
xmin: f64::NAN,
ymin: f64::NAN,
xmax: f64::NAN,
ymax: f64::NAN,
}
}
}
pub fn clip(&self, surface: &Surface) -> Surface {
let geom = &surface.geom;
let src = surface.values();
let mut out = Array2::from_elem((geom.ncol, geom.nrow), f64::NAN);
for j in 0..geom.nrow {
for i in 0..geom.ncol {
let (x, y) = geom.node_xy(i, j);
if self.contains(x, y) {
out[[i, j]] = src[[i, j]];
}
}
}
Surface::from_values_unchecked(geom.clone(), out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::foundation::GridGeometry;
fn unit_square() -> PolygonSet {
PolygonSet::from_rings(vec![vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
]])
}
#[test]
fn area_of_unit_square_is_one() {
approx::assert_relative_eq!(unit_square().area(), 1.0);
}
#[test]
fn rings_returns_exterior_vertices() {
let rings = unit_square().rings();
assert_eq!(rings.len(), 1);
assert_eq!(rings[0].first(), rings[0].last());
assert!(rings[0].iter().all(|c| c[2] == 0.0));
for corner in [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]] {
assert!(rings[0]
.iter()
.any(|c| c[0] == corner[0] && c[1] == corner[1]));
}
}
#[test]
fn contains_interior_excludes_boundary() {
let s = unit_square();
assert!(s.contains(0.5, 0.5)); assert!(!s.contains(2.0, 2.0)); assert!(!s.contains(0.0, 0.0)); assert!(!s.contains(0.5, 0.0)); assert!(!s.contains(1.0, 0.5)); }
#[test]
fn bbox_covers_square() {
let b = unit_square().bbox();
approx::assert_relative_eq!(b.xmin, 0.0);
approx::assert_relative_eq!(b.ymin, 0.0);
approx::assert_relative_eq!(b.xmax, 1.0);
approx::assert_relative_eq!(b.ymax, 1.0);
}
#[test]
fn empty_set_is_nan_and_zero() {
let s = PolygonSet::from_rings(Vec::new());
assert!(s.bbox().xmin.is_nan());
approx::assert_relative_eq!(s.area(), 0.0);
assert!(!s.contains(0.0, 0.0));
}
#[test]
fn clip_masks_nodes_outside_polygon() {
let geom = GridGeometry {
xori: 0.0,
yori: 0.0,
xinc: 1.0,
yinc: 1.0,
ncol: 3,
nrow: 3,
rotation_deg: 0.0,
yflip: false,
};
let surf = Surface::constant(geom, 5.0);
let poly = PolygonSet::from_rings(vec![vec![
[-0.5, -0.5, 0.0],
[1.5, -0.5, 0.0],
[1.5, 1.5, 0.0],
[-0.5, 1.5, 0.0],
]]);
let clipped = poly.clip(&surf);
let v = clipped.values();
assert_eq!(v[[0, 0]], 5.0);
assert_eq!(v[[1, 1]], 5.0);
assert!(v[[2, 0]].is_nan());
assert!(v[[0, 2]].is_nan());
assert!(v[[2, 2]].is_nan());
}
}