use crate::error::{AlgorithmError, Result};
use oxigdal_core::vector::{
Coordinate, Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint,
MultiPolygon, Point, Polygon,
};
pub fn envelope(geometry: &Geometry) -> Result<Polygon> {
match geometry {
Geometry::Point(p) => envelope_point(p),
Geometry::LineString(ls) => envelope_linestring(ls),
Geometry::Polygon(p) => envelope_polygon(p),
Geometry::MultiPoint(mp) => envelope_multipoint(mp),
Geometry::MultiLineString(mls) => envelope_multilinestring(mls),
Geometry::MultiPolygon(mp) => envelope_multipolygon(mp),
Geometry::GeometryCollection(gc) => envelope_collection(gc),
}
}
pub fn envelope_point(point: &Point) -> Result<Polygon> {
let x = point.coord.x;
let y = point.coord.y;
let coords = vec![
Coordinate::new_2d(x, y),
Coordinate::new_2d(x, y),
Coordinate::new_2d(x, y),
Coordinate::new_2d(x, y),
Coordinate::new_2d(x, y),
];
let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
}
pub fn envelope_linestring(linestring: &LineString) -> Result<Polygon> {
if linestring.coords.is_empty() {
return Err(AlgorithmError::EmptyInput {
operation: "envelope_linestring",
});
}
let bounds = linestring
.bounds()
.ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for linestring".to_string(),
})?;
create_envelope_polygon(bounds)
}
pub fn envelope_polygon(polygon: &Polygon) -> Result<Polygon> {
if polygon.exterior.coords.is_empty() {
return Err(AlgorithmError::EmptyInput {
operation: "envelope_polygon",
});
}
let bounds = polygon
.bounds()
.ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for polygon".to_string(),
})?;
create_envelope_polygon(bounds)
}
pub fn envelope_multipoint(multipoint: &MultiPoint) -> Result<Polygon> {
if multipoint.points.is_empty() {
return Err(AlgorithmError::EmptyInput {
operation: "envelope_multipoint",
});
}
let bounds = multipoint
.bounds()
.ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for multipoint".to_string(),
})?;
create_envelope_polygon(bounds)
}
pub fn envelope_multilinestring(multilinestring: &MultiLineString) -> Result<Polygon> {
if multilinestring.line_strings.is_empty() {
return Err(AlgorithmError::EmptyInput {
operation: "envelope_multilinestring",
});
}
let bounds = multilinestring
.bounds()
.ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for multilinestring".to_string(),
})?;
create_envelope_polygon(bounds)
}
pub fn envelope_multipolygon(multipolygon: &MultiPolygon) -> Result<Polygon> {
if multipolygon.polygons.is_empty() {
return Err(AlgorithmError::EmptyInput {
operation: "envelope_multipolygon",
});
}
let bounds = multipolygon
.bounds()
.ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for multipolygon".to_string(),
})?;
create_envelope_polygon(bounds)
}
pub fn envelope_collection(collection: &GeometryCollection) -> Result<Polygon> {
if collection.geometries.is_empty() {
return Err(AlgorithmError::EmptyInput {
operation: "envelope_collection",
});
}
let bounds = collection
.bounds()
.ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for geometry collection".to_string(),
})?;
create_envelope_polygon(bounds)
}
fn create_envelope_polygon(bounds: (f64, f64, f64, f64)) -> Result<Polygon> {
let (min_x, min_y, max_x, max_y) = bounds;
let coords = vec![
Coordinate::new_2d(min_x, min_y),
Coordinate::new_2d(max_x, min_y),
Coordinate::new_2d(max_x, max_y),
Coordinate::new_2d(min_x, max_y),
Coordinate::new_2d(min_x, min_y), ];
let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
}
pub fn envelope_with_buffer(geometry: &Geometry, buffer: f64) -> Result<Polygon> {
if buffer < 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer",
message: "buffer must be non-negative".to_string(),
});
}
let base_envelope = envelope(geometry)?;
let bounds = base_envelope
.bounds()
.ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for envelope".to_string(),
})?;
let (min_x, min_y, max_x, max_y) = bounds;
let expanded_bounds = (
min_x - buffer,
min_y - buffer,
max_x + buffer,
max_y + buffer,
);
create_envelope_polygon(expanded_bounds)
}
pub fn envelope_contains_point(envelope: &Polygon, point: &Point) -> bool {
if let Some((min_x, min_y, max_x, max_y)) = envelope.bounds() {
point.coord.x >= min_x
&& point.coord.x <= max_x
&& point.coord.y >= min_y
&& point.coord.y <= max_y
} else {
false
}
}
pub fn envelopes_intersect(env1: &Polygon, env2: &Polygon) -> bool {
if let (Some(b1), Some(b2)) = (env1.bounds(), env2.bounds()) {
let (min_x1, min_y1, max_x1, max_y1) = b1;
let (min_x2, min_y2, max_x2, max_y2) = b2;
!(max_x1 < min_x2 || max_x2 < min_x1 || max_y1 < min_y2 || max_y2 < min_y1)
} else {
false
}
}
pub fn envelope_union(env1: &Polygon, env2: &Polygon) -> Result<Polygon> {
let b1 = env1.bounds().ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for first envelope".to_string(),
})?;
let b2 = env2.bounds().ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for second envelope".to_string(),
})?;
let (min_x1, min_y1, max_x1, max_y1) = b1;
let (min_x2, min_y2, max_x2, max_y2) = b2;
let union_bounds = (
min_x1.min(min_x2),
min_y1.min(min_y2),
max_x1.max(max_x2),
max_y1.max(max_y2),
);
create_envelope_polygon(union_bounds)
}
pub fn envelope_intersection(env1: &Polygon, env2: &Polygon) -> Result<Option<Polygon>> {
if !envelopes_intersect(env1, env2) {
return Ok(None);
}
let b1 = env1.bounds().ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for first envelope".to_string(),
})?;
let b2 = env2.bounds().ok_or_else(|| AlgorithmError::GeometryError {
message: "failed to compute bounds for second envelope".to_string(),
})?;
let (min_x1, min_y1, max_x1, max_y1) = b1;
let (min_x2, min_y2, max_x2, max_y2) = b2;
let intersection_bounds = (
min_x1.max(min_x2),
min_y1.max(min_y2),
max_x1.min(max_x2),
max_y1.min(max_y2),
);
if intersection_bounds.0 <= intersection_bounds.2
&& intersection_bounds.1 <= intersection_bounds.3
{
Ok(Some(create_envelope_polygon(intersection_bounds)?))
} else {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_linestring() -> Result<LineString> {
let coords = vec![
Coordinate::new_2d(1.0, 1.0),
Coordinate::new_2d(5.0, 3.0),
Coordinate::new_2d(3.0, 7.0),
];
LineString::new(coords).map_err(AlgorithmError::Core)
}
fn create_test_polygon() -> Result<Polygon> {
let coords = vec![
Coordinate::new_2d(2.0, 2.0),
Coordinate::new_2d(8.0, 2.0),
Coordinate::new_2d(8.0, 6.0),
Coordinate::new_2d(2.0, 6.0),
Coordinate::new_2d(2.0, 2.0),
];
let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
}
#[test]
fn test_envelope_point() {
let point = Point::new(3.0, 5.0);
let env = envelope_point(&point);
assert!(env.is_ok());
if let Ok(e) = env {
let bounds = e.bounds();
assert!(bounds.is_some());
if let Some((min_x, min_y, max_x, max_y)) = bounds {
assert_eq!(min_x, 3.0);
assert_eq!(min_y, 5.0);
assert_eq!(max_x, 3.0);
assert_eq!(max_y, 5.0);
}
}
}
#[test]
fn test_envelope_linestring() {
let line = create_test_linestring();
assert!(line.is_ok());
if let Ok(l) = line {
let env = envelope_linestring(&l);
assert!(env.is_ok());
if let Ok(e) = env {
let bounds = e.bounds();
assert!(bounds.is_some());
if let Some((min_x, min_y, max_x, max_y)) = bounds {
assert_eq!(min_x, 1.0);
assert_eq!(min_y, 1.0);
assert_eq!(max_x, 5.0);
assert_eq!(max_y, 7.0);
}
}
}
}
#[test]
fn test_envelope_polygon() {
let poly = create_test_polygon();
assert!(poly.is_ok());
if let Ok(p) = poly {
let env = envelope_polygon(&p);
assert!(env.is_ok());
if let Ok(e) = env {
let bounds = e.bounds();
assert!(bounds.is_some());
if let Some((min_x, min_y, max_x, max_y)) = bounds {
assert_eq!(min_x, 2.0);
assert_eq!(min_y, 2.0);
assert_eq!(max_x, 8.0);
assert_eq!(max_y, 6.0);
}
}
}
}
#[test]
fn test_envelope_multipoint() {
let points = vec![
Point::new(1.0, 1.0),
Point::new(5.0, 3.0),
Point::new(3.0, 7.0),
];
let mp = MultiPoint::new(points);
let env = envelope_multipoint(&mp);
assert!(env.is_ok());
if let Ok(e) = env {
let bounds = e.bounds();
assert!(bounds.is_some());
if let Some((min_x, min_y, max_x, max_y)) = bounds {
assert_eq!(min_x, 1.0);
assert_eq!(min_y, 1.0);
assert_eq!(max_x, 5.0);
assert_eq!(max_y, 7.0);
}
}
}
#[test]
fn test_envelope_with_buffer() {
let point = Point::new(5.0, 5.0);
let geom = Geometry::Point(point);
let env = envelope_with_buffer(&geom, 2.0);
assert!(env.is_ok());
if let Ok(e) = env {
let bounds = e.bounds();
assert!(bounds.is_some());
if let Some((min_x, min_y, max_x, max_y)) = bounds {
assert_eq!(min_x, 3.0);
assert_eq!(min_y, 3.0);
assert_eq!(max_x, 7.0);
assert_eq!(max_y, 7.0);
}
}
}
#[test]
fn test_envelope_with_negative_buffer() {
let point = Point::new(5.0, 5.0);
let geom = Geometry::Point(point);
let env = envelope_with_buffer(&geom, -1.0);
assert!(env.is_err());
}
#[test]
fn test_envelope_contains_point() {
let poly = create_test_polygon();
assert!(poly.is_ok());
if let Ok(p) = poly {
let env = envelope_polygon(&p);
assert!(env.is_ok());
if let Ok(e) = env {
let inside = Point::new(5.0, 4.0);
assert!(envelope_contains_point(&e, &inside));
let boundary = Point::new(2.0, 4.0);
assert!(envelope_contains_point(&e, &boundary));
let outside = Point::new(10.0, 10.0);
assert!(!envelope_contains_point(&e, &outside));
}
}
}
#[test]
fn test_envelopes_intersect() {
let poly1 = create_test_polygon();
let coords2 = vec![
Coordinate::new_2d(5.0, 4.0),
Coordinate::new_2d(10.0, 4.0),
Coordinate::new_2d(10.0, 8.0),
Coordinate::new_2d(5.0, 8.0),
Coordinate::new_2d(5.0, 4.0),
];
let ext2 = LineString::new(coords2);
assert!(poly1.is_ok() && ext2.is_ok());
if let (Ok(p1), Ok(e2)) = (poly1, ext2) {
let poly2 = Polygon::new(e2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let env1 = envelope_polygon(&p1);
let env2 = envelope_polygon(&p2);
assert!(env1.is_ok() && env2.is_ok());
if let (Ok(e1), Ok(e2)) = (env1, env2) {
assert!(envelopes_intersect(&e1, &e2));
}
}
}
}
#[test]
fn test_envelopes_no_intersect() {
let poly1 = create_test_polygon();
let coords2 = vec![
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(25.0, 20.0),
Coordinate::new_2d(25.0, 25.0),
Coordinate::new_2d(20.0, 25.0),
Coordinate::new_2d(20.0, 20.0),
];
let ext2 = LineString::new(coords2);
assert!(poly1.is_ok() && ext2.is_ok());
if let (Ok(p1), Ok(e2)) = (poly1, ext2) {
let poly2 = Polygon::new(e2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let env1 = envelope_polygon(&p1);
let env2 = envelope_polygon(&p2);
assert!(env1.is_ok() && env2.is_ok());
if let (Ok(e1), Ok(e2)) = (env1, env2) {
assert!(!envelopes_intersect(&e1, &e2));
}
}
}
}
#[test]
fn test_envelope_union() {
let coords1 = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(5.0, 0.0),
Coordinate::new_2d(5.0, 5.0),
Coordinate::new_2d(0.0, 5.0),
Coordinate::new_2d(0.0, 0.0),
];
let coords2 = vec![
Coordinate::new_2d(3.0, 3.0),
Coordinate::new_2d(8.0, 3.0),
Coordinate::new_2d(8.0, 8.0),
Coordinate::new_2d(3.0, 8.0),
Coordinate::new_2d(3.0, 3.0),
];
let ext1 = LineString::new(coords1);
let ext2 = LineString::new(coords2);
assert!(ext1.is_ok() && ext2.is_ok());
if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
let poly1 = Polygon::new(e1, vec![]);
let poly2 = Polygon::new(e2, vec![]);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let env1 = envelope_polygon(&p1);
let env2 = envelope_polygon(&p2);
assert!(env1.is_ok() && env2.is_ok());
if let (Ok(e1), Ok(e2)) = (env1, env2) {
let union = envelope_union(&e1, &e2);
assert!(union.is_ok());
if let Ok(u) = union {
let bounds = u.bounds();
assert!(bounds.is_some());
if let Some((min_x, min_y, max_x, max_y)) = bounds {
assert_eq!(min_x, 0.0);
assert_eq!(min_y, 0.0);
assert_eq!(max_x, 8.0);
assert_eq!(max_y, 8.0);
}
}
}
}
}
}
#[test]
fn test_envelope_intersection() {
let coords1 = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(6.0, 0.0),
Coordinate::new_2d(6.0, 6.0),
Coordinate::new_2d(0.0, 6.0),
Coordinate::new_2d(0.0, 0.0),
];
let coords2 = vec![
Coordinate::new_2d(3.0, 3.0),
Coordinate::new_2d(9.0, 3.0),
Coordinate::new_2d(9.0, 9.0),
Coordinate::new_2d(3.0, 9.0),
Coordinate::new_2d(3.0, 3.0),
];
let ext1 = LineString::new(coords1);
let ext2 = LineString::new(coords2);
assert!(ext1.is_ok() && ext2.is_ok());
if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
let poly1 = Polygon::new(e1, vec![]);
let poly2 = Polygon::new(e2, vec![]);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let env1 = envelope_polygon(&p1);
let env2 = envelope_polygon(&p2);
assert!(env1.is_ok() && env2.is_ok());
if let (Ok(e1), Ok(e2)) = (env1, env2) {
let intersection = envelope_intersection(&e1, &e2);
assert!(intersection.is_ok());
if let Ok(Some(i)) = intersection {
let bounds = i.bounds();
assert!(bounds.is_some());
if let Some((min_x, min_y, max_x, max_y)) = bounds {
assert_eq!(min_x, 3.0);
assert_eq!(min_y, 3.0);
assert_eq!(max_x, 6.0);
assert_eq!(max_y, 6.0);
}
}
}
}
}
}
#[test]
fn test_envelope_intersection_no_overlap() {
let coords1 = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(5.0, 0.0),
Coordinate::new_2d(5.0, 5.0),
Coordinate::new_2d(0.0, 5.0),
Coordinate::new_2d(0.0, 0.0),
];
let coords2 = vec![
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(15.0, 10.0),
Coordinate::new_2d(15.0, 15.0),
Coordinate::new_2d(10.0, 15.0),
Coordinate::new_2d(10.0, 10.0),
];
let ext1 = LineString::new(coords1);
let ext2 = LineString::new(coords2);
assert!(ext1.is_ok() && ext2.is_ok());
if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
let poly1 = Polygon::new(e1, vec![]);
let poly2 = Polygon::new(e2, vec![]);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let env1 = envelope_polygon(&p1);
let env2 = envelope_polygon(&p2);
assert!(env1.is_ok() && env2.is_ok());
if let (Ok(e1), Ok(e2)) = (env1, env2) {
let intersection = envelope_intersection(&e1, &e2);
assert!(intersection.is_ok());
if let Ok(result) = intersection {
assert!(result.is_none());
}
}
}
}
}
#[test]
fn test_envelope_empty_linestring() {
let coords: Vec<Coordinate> = vec![];
let line = LineString::new(coords);
assert!(line.is_err());
}
#[test]
fn test_envelope_multipoint_empty() {
let mp = MultiPoint::empty();
let env = envelope_multipoint(&mp);
assert!(env.is_err());
}
#[test]
fn test_envelope_geometry_dispatch() {
let point = Point::new(3.0, 5.0);
let geom = Geometry::Point(point);
let env = envelope(&geom);
assert!(env.is_ok());
if let Ok(e) = env {
let bounds = e.bounds();
assert!(bounds.is_some());
}
}
}