Skip to main content

oxigdal_algorithms/vector/
envelope.rs

1//! Envelope (bounding box) calculation for geometric features
2//!
3//! This module provides functions for computing envelopes (axis-aligned bounding boxes)
4//! of various geometry types as polygon geometries.
5//!
6//! # Envelope vs Bounds
7//!
8//! - **Bounds**: Returns tuple (min_x, min_y, max_x, max_y) - available in core
9//! - **Envelope**: Returns a rectangular Polygon geometry representing the bounding box
10//!
11//! # Examples
12//!
13//! ```
14//! # use oxigdal_algorithms::error::Result;
15//! use oxigdal_algorithms::vector::{Coordinate, LineString, envelope_linestring};
16//!
17//! # fn main() -> Result<()> {
18//! let coords = vec![
19//!     Coordinate::new_2d(0.0, 0.0),
20//!     Coordinate::new_2d(4.0, 2.0),
21//!     Coordinate::new_2d(2.0, 5.0),
22//! ];
23//! let line = LineString::new(coords)?;
24//! let env = envelope_linestring(&line)?;
25//! // Envelope will be a rectangle from (0,0) to (4,5)
26//! # Ok(())
27//! # }
28//! ```
29
30use crate::error::{AlgorithmError, Result};
31use oxigdal_core::vector::{
32    Coordinate, Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint,
33    MultiPolygon, Point, Polygon,
34};
35
36/// Computes the envelope of any geometry type
37///
38/// Returns a rectangular polygon that represents the axis-aligned bounding box
39/// of the input geometry.
40///
41/// # Arguments
42///
43/// * `geometry` - Input geometry
44///
45/// # Returns
46///
47/// Polygon representing the envelope (bounding box)
48///
49/// # Errors
50///
51/// Returns error if geometry is empty or invalid
52///
53/// # Examples
54///
55/// ```
56/// # use oxigdal_algorithms::error::Result;
57/// use oxigdal_core::vector::Geometry;
58/// use oxigdal_algorithms::vector::{Point, envelope};
59///
60/// # fn main() -> Result<()> {
61/// let point = Point::new(3.0, 5.0);
62/// let geom = Geometry::Point(point);
63/// let env = envelope(&geom)?;
64/// // For a point, envelope is a degenerate rectangle at that point
65/// # Ok(())
66/// # }
67/// ```
68pub fn envelope(geometry: &Geometry) -> Result<Polygon> {
69    match geometry {
70        Geometry::Point(p) => envelope_point(p),
71        Geometry::LineString(ls) => envelope_linestring(ls),
72        Geometry::Polygon(p) => envelope_polygon(p),
73        Geometry::MultiPoint(mp) => envelope_multipoint(mp),
74        Geometry::MultiLineString(mls) => envelope_multilinestring(mls),
75        Geometry::MultiPolygon(mp) => envelope_multipolygon(mp),
76        Geometry::GeometryCollection(gc) => envelope_collection(gc),
77    }
78}
79
80/// Computes the envelope of a point
81///
82/// For a single point, the envelope is a degenerate rectangle (all four corners
83/// at the same location).
84///
85/// # Arguments
86///
87/// * `point` - Input point
88///
89/// # Returns
90///
91/// Polygon representing the envelope
92///
93/// # Errors
94///
95/// Returns error if polygon creation fails
96pub fn envelope_point(point: &Point) -> Result<Polygon> {
97    let x = point.coord.x;
98    let y = point.coord.y;
99
100    // Create a degenerate rectangle (all corners at the same point)
101    let coords = vec![
102        Coordinate::new_2d(x, y),
103        Coordinate::new_2d(x, y),
104        Coordinate::new_2d(x, y),
105        Coordinate::new_2d(x, y),
106        Coordinate::new_2d(x, y),
107    ];
108
109    let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
110    Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
111}
112
113/// Computes the envelope of a linestring
114///
115/// Returns a rectangular polygon that bounds all points in the linestring.
116///
117/// # Arguments
118///
119/// * `linestring` - Input linestring
120///
121/// # Returns
122///
123/// Polygon representing the envelope
124///
125/// # Errors
126///
127/// Returns error if linestring is empty or envelope creation fails
128pub fn envelope_linestring(linestring: &LineString) -> Result<Polygon> {
129    if linestring.coords.is_empty() {
130        return Err(AlgorithmError::EmptyInput {
131            operation: "envelope_linestring",
132        });
133    }
134
135    let bounds = linestring
136        .bounds()
137        .ok_or_else(|| AlgorithmError::GeometryError {
138            message: "failed to compute bounds for linestring".to_string(),
139        })?;
140
141    create_envelope_polygon(bounds)
142}
143
144/// Computes the envelope of a polygon
145///
146/// Returns a rectangular polygon that bounds the input polygon.
147///
148/// # Arguments
149///
150/// * `polygon` - Input polygon
151///
152/// # Returns
153///
154/// Polygon representing the envelope
155///
156/// # Errors
157///
158/// Returns error if polygon is invalid or envelope creation fails
159pub fn envelope_polygon(polygon: &Polygon) -> Result<Polygon> {
160    if polygon.exterior.coords.is_empty() {
161        return Err(AlgorithmError::EmptyInput {
162            operation: "envelope_polygon",
163        });
164    }
165
166    let bounds = polygon
167        .bounds()
168        .ok_or_else(|| AlgorithmError::GeometryError {
169            message: "failed to compute bounds for polygon".to_string(),
170        })?;
171
172    create_envelope_polygon(bounds)
173}
174
175/// Computes the envelope of a multipoint
176///
177/// Returns a rectangular polygon that bounds all points.
178///
179/// # Arguments
180///
181/// * `multipoint` - Input multipoint
182///
183/// # Returns
184///
185/// Polygon representing the envelope
186///
187/// # Errors
188///
189/// Returns error if multipoint is empty or envelope creation fails
190pub fn envelope_multipoint(multipoint: &MultiPoint) -> Result<Polygon> {
191    if multipoint.points.is_empty() {
192        return Err(AlgorithmError::EmptyInput {
193            operation: "envelope_multipoint",
194        });
195    }
196
197    let bounds = multipoint
198        .bounds()
199        .ok_or_else(|| AlgorithmError::GeometryError {
200            message: "failed to compute bounds for multipoint".to_string(),
201        })?;
202
203    create_envelope_polygon(bounds)
204}
205
206/// Computes the envelope of a multilinestring
207///
208/// Returns a rectangular polygon that bounds all linestrings.
209///
210/// # Arguments
211///
212/// * `multilinestring` - Input multilinestring
213///
214/// # Returns
215///
216/// Polygon representing the envelope
217///
218/// # Errors
219///
220/// Returns error if multilinestring is empty or envelope creation fails
221pub fn envelope_multilinestring(multilinestring: &MultiLineString) -> Result<Polygon> {
222    if multilinestring.line_strings.is_empty() {
223        return Err(AlgorithmError::EmptyInput {
224            operation: "envelope_multilinestring",
225        });
226    }
227
228    let bounds = multilinestring
229        .bounds()
230        .ok_or_else(|| AlgorithmError::GeometryError {
231            message: "failed to compute bounds for multilinestring".to_string(),
232        })?;
233
234    create_envelope_polygon(bounds)
235}
236
237/// Computes the envelope of a multipolygon
238///
239/// Returns a rectangular polygon that bounds all polygons.
240///
241/// # Arguments
242///
243/// * `multipolygon` - Input multipolygon
244///
245/// # Returns
246///
247/// Polygon representing the envelope
248///
249/// # Errors
250///
251/// Returns error if multipolygon is empty or envelope creation fails
252pub fn envelope_multipolygon(multipolygon: &MultiPolygon) -> Result<Polygon> {
253    if multipolygon.polygons.is_empty() {
254        return Err(AlgorithmError::EmptyInput {
255            operation: "envelope_multipolygon",
256        });
257    }
258
259    let bounds = multipolygon
260        .bounds()
261        .ok_or_else(|| AlgorithmError::GeometryError {
262            message: "failed to compute bounds for multipolygon".to_string(),
263        })?;
264
265    create_envelope_polygon(bounds)
266}
267
268/// Computes the envelope of a geometry collection
269///
270/// Returns a rectangular polygon that bounds all geometries in the collection.
271///
272/// # Arguments
273///
274/// * `collection` - Input geometry collection
275///
276/// # Returns
277///
278/// Polygon representing the envelope
279///
280/// # Errors
281///
282/// Returns error if collection is empty or envelope creation fails
283pub fn envelope_collection(collection: &GeometryCollection) -> Result<Polygon> {
284    if collection.geometries.is_empty() {
285        return Err(AlgorithmError::EmptyInput {
286            operation: "envelope_collection",
287        });
288    }
289
290    let bounds = collection
291        .bounds()
292        .ok_or_else(|| AlgorithmError::GeometryError {
293            message: "failed to compute bounds for geometry collection".to_string(),
294        })?;
295
296    create_envelope_polygon(bounds)
297}
298
299/// Creates a rectangular polygon from bounds
300///
301/// # Arguments
302///
303/// * `bounds` - Tuple of (min_x, min_y, max_x, max_y)
304///
305/// # Returns
306///
307/// Polygon representing the envelope
308///
309/// # Errors
310///
311/// Returns error if polygon creation fails
312fn create_envelope_polygon(bounds: (f64, f64, f64, f64)) -> Result<Polygon> {
313    let (min_x, min_y, max_x, max_y) = bounds;
314
315    // Create rectangle corners (counter-clockwise)
316    let coords = vec![
317        Coordinate::new_2d(min_x, min_y),
318        Coordinate::new_2d(max_x, min_y),
319        Coordinate::new_2d(max_x, max_y),
320        Coordinate::new_2d(min_x, max_y),
321        Coordinate::new_2d(min_x, min_y), // Close the ring
322    ];
323
324    let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
325    Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
326}
327
328/// Computes expanded envelope with buffer
329///
330/// Creates an envelope that is expanded by a specified distance in all directions.
331///
332/// # Arguments
333///
334/// * `geometry` - Input geometry
335/// * `buffer` - Distance to expand envelope in all directions
336///
337/// # Returns
338///
339/// Expanded envelope polygon
340///
341/// # Errors
342///
343/// Returns error if geometry is invalid or buffer is negative
344///
345/// # Examples
346///
347/// ```
348/// # use oxigdal_algorithms::error::Result;
349/// use oxigdal_core::vector::Geometry;
350/// use oxigdal_algorithms::vector::{Point, envelope_with_buffer};
351///
352/// # fn main() -> Result<()> {
353/// let point = Point::new(5.0, 5.0);
354/// let geom = Geometry::Point(point);
355/// let env = envelope_with_buffer(&geom, 1.0)?;
356/// // Envelope will be rectangle from (4,4) to (6,6)
357/// # Ok(())
358/// # }
359/// ```
360pub fn envelope_with_buffer(geometry: &Geometry, buffer: f64) -> Result<Polygon> {
361    if buffer < 0.0 {
362        return Err(AlgorithmError::InvalidParameter {
363            parameter: "buffer",
364            message: "buffer must be non-negative".to_string(),
365        });
366    }
367
368    let base_envelope = envelope(geometry)?;
369    let bounds = base_envelope
370        .bounds()
371        .ok_or_else(|| AlgorithmError::GeometryError {
372            message: "failed to compute bounds for envelope".to_string(),
373        })?;
374
375    let (min_x, min_y, max_x, max_y) = bounds;
376
377    // Expand bounds by buffer
378    let expanded_bounds = (
379        min_x - buffer,
380        min_y - buffer,
381        max_x + buffer,
382        max_y + buffer,
383    );
384
385    create_envelope_polygon(expanded_bounds)
386}
387
388/// Checks if an envelope contains a point
389///
390/// # Arguments
391///
392/// * `envelope` - The envelope polygon
393/// * `point` - Point to test
394///
395/// # Returns
396///
397/// True if the point is within or on the boundary of the envelope
398pub fn envelope_contains_point(envelope: &Polygon, point: &Point) -> bool {
399    if let Some((min_x, min_y, max_x, max_y)) = envelope.bounds() {
400        point.coord.x >= min_x
401            && point.coord.x <= max_x
402            && point.coord.y >= min_y
403            && point.coord.y <= max_y
404    } else {
405        false
406    }
407}
408
409/// Checks if two envelopes intersect
410///
411/// # Arguments
412///
413/// * `env1` - First envelope polygon
414/// * `env2` - Second envelope polygon
415///
416/// # Returns
417///
418/// True if the envelopes intersect or touch
419pub fn envelopes_intersect(env1: &Polygon, env2: &Polygon) -> bool {
420    if let (Some(b1), Some(b2)) = (env1.bounds(), env2.bounds()) {
421        let (min_x1, min_y1, max_x1, max_y1) = b1;
422        let (min_x2, min_y2, max_x2, max_y2) = b2;
423
424        // Check if bounding boxes overlap
425        !(max_x1 < min_x2 || max_x2 < min_x1 || max_y1 < min_y2 || max_y2 < min_y1)
426    } else {
427        false
428    }
429}
430
431/// Computes the union of two envelopes
432///
433/// Returns the smallest envelope that contains both input envelopes.
434///
435/// # Arguments
436///
437/// * `env1` - First envelope polygon
438/// * `env2` - Second envelope polygon
439///
440/// # Returns
441///
442/// Envelope polygon containing both input envelopes
443///
444/// # Errors
445///
446/// Returns error if envelope creation fails
447pub fn envelope_union(env1: &Polygon, env2: &Polygon) -> Result<Polygon> {
448    let b1 = env1.bounds().ok_or_else(|| AlgorithmError::GeometryError {
449        message: "failed to compute bounds for first envelope".to_string(),
450    })?;
451
452    let b2 = env2.bounds().ok_or_else(|| AlgorithmError::GeometryError {
453        message: "failed to compute bounds for second envelope".to_string(),
454    })?;
455
456    let (min_x1, min_y1, max_x1, max_y1) = b1;
457    let (min_x2, min_y2, max_x2, max_y2) = b2;
458
459    let union_bounds = (
460        min_x1.min(min_x2),
461        min_y1.min(min_y2),
462        max_x1.max(max_x2),
463        max_y1.max(max_y2),
464    );
465
466    create_envelope_polygon(union_bounds)
467}
468
469/// Computes the intersection of two envelopes
470///
471/// Returns the envelope representing the overlapping region, or None if they don't intersect.
472///
473/// # Arguments
474///
475/// * `env1` - First envelope polygon
476/// * `env2` - Second envelope polygon
477///
478/// # Returns
479///
480/// Some(envelope) if they intersect, None otherwise
481///
482/// # Errors
483///
484/// Returns error if envelope creation fails
485pub fn envelope_intersection(env1: &Polygon, env2: &Polygon) -> Result<Option<Polygon>> {
486    if !envelopes_intersect(env1, env2) {
487        return Ok(None);
488    }
489
490    let b1 = env1.bounds().ok_or_else(|| AlgorithmError::GeometryError {
491        message: "failed to compute bounds for first envelope".to_string(),
492    })?;
493
494    let b2 = env2.bounds().ok_or_else(|| AlgorithmError::GeometryError {
495        message: "failed to compute bounds for second envelope".to_string(),
496    })?;
497
498    let (min_x1, min_y1, max_x1, max_y1) = b1;
499    let (min_x2, min_y2, max_x2, max_y2) = b2;
500
501    let intersection_bounds = (
502        min_x1.max(min_x2),
503        min_y1.max(min_y2),
504        max_x1.min(max_x2),
505        max_y1.min(max_y2),
506    );
507
508    // Verify the intersection is valid
509    if intersection_bounds.0 <= intersection_bounds.2
510        && intersection_bounds.1 <= intersection_bounds.3
511    {
512        Ok(Some(create_envelope_polygon(intersection_bounds)?))
513    } else {
514        Ok(None)
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    fn create_test_linestring() -> Result<LineString> {
523        let coords = vec![
524            Coordinate::new_2d(1.0, 1.0),
525            Coordinate::new_2d(5.0, 3.0),
526            Coordinate::new_2d(3.0, 7.0),
527        ];
528        LineString::new(coords).map_err(AlgorithmError::Core)
529    }
530
531    fn create_test_polygon() -> Result<Polygon> {
532        let coords = vec![
533            Coordinate::new_2d(2.0, 2.0),
534            Coordinate::new_2d(8.0, 2.0),
535            Coordinate::new_2d(8.0, 6.0),
536            Coordinate::new_2d(2.0, 6.0),
537            Coordinate::new_2d(2.0, 2.0),
538        ];
539        let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
540        Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
541    }
542
543    #[test]
544    fn test_envelope_point() {
545        let point = Point::new(3.0, 5.0);
546        let env = envelope_point(&point);
547
548        assert!(env.is_ok());
549        if let Ok(e) = env {
550            let bounds = e.bounds();
551            assert!(bounds.is_some());
552            if let Some((min_x, min_y, max_x, max_y)) = bounds {
553                assert_eq!(min_x, 3.0);
554                assert_eq!(min_y, 5.0);
555                assert_eq!(max_x, 3.0);
556                assert_eq!(max_y, 5.0);
557            }
558        }
559    }
560
561    #[test]
562    fn test_envelope_linestring() {
563        let line = create_test_linestring();
564        assert!(line.is_ok());
565
566        if let Ok(l) = line {
567            let env = envelope_linestring(&l);
568            assert!(env.is_ok());
569
570            if let Ok(e) = env {
571                let bounds = e.bounds();
572                assert!(bounds.is_some());
573                if let Some((min_x, min_y, max_x, max_y)) = bounds {
574                    assert_eq!(min_x, 1.0);
575                    assert_eq!(min_y, 1.0);
576                    assert_eq!(max_x, 5.0);
577                    assert_eq!(max_y, 7.0);
578                }
579            }
580        }
581    }
582
583    #[test]
584    fn test_envelope_polygon() {
585        let poly = create_test_polygon();
586        assert!(poly.is_ok());
587
588        if let Ok(p) = poly {
589            let env = envelope_polygon(&p);
590            assert!(env.is_ok());
591
592            if let Ok(e) = env {
593                let bounds = e.bounds();
594                assert!(bounds.is_some());
595                if let Some((min_x, min_y, max_x, max_y)) = bounds {
596                    assert_eq!(min_x, 2.0);
597                    assert_eq!(min_y, 2.0);
598                    assert_eq!(max_x, 8.0);
599                    assert_eq!(max_y, 6.0);
600                }
601            }
602        }
603    }
604
605    #[test]
606    fn test_envelope_multipoint() {
607        let points = vec![
608            Point::new(1.0, 1.0),
609            Point::new(5.0, 3.0),
610            Point::new(3.0, 7.0),
611        ];
612        let mp = MultiPoint::new(points);
613
614        let env = envelope_multipoint(&mp);
615        assert!(env.is_ok());
616
617        if let Ok(e) = env {
618            let bounds = e.bounds();
619            assert!(bounds.is_some());
620            if let Some((min_x, min_y, max_x, max_y)) = bounds {
621                assert_eq!(min_x, 1.0);
622                assert_eq!(min_y, 1.0);
623                assert_eq!(max_x, 5.0);
624                assert_eq!(max_y, 7.0);
625            }
626        }
627    }
628
629    #[test]
630    fn test_envelope_with_buffer() {
631        let point = Point::new(5.0, 5.0);
632        let geom = Geometry::Point(point);
633
634        let env = envelope_with_buffer(&geom, 2.0);
635        assert!(env.is_ok());
636
637        if let Ok(e) = env {
638            let bounds = e.bounds();
639            assert!(bounds.is_some());
640            if let Some((min_x, min_y, max_x, max_y)) = bounds {
641                assert_eq!(min_x, 3.0);
642                assert_eq!(min_y, 3.0);
643                assert_eq!(max_x, 7.0);
644                assert_eq!(max_y, 7.0);
645            }
646        }
647    }
648
649    #[test]
650    fn test_envelope_with_negative_buffer() {
651        let point = Point::new(5.0, 5.0);
652        let geom = Geometry::Point(point);
653
654        let env = envelope_with_buffer(&geom, -1.0);
655        assert!(env.is_err());
656    }
657
658    #[test]
659    fn test_envelope_contains_point() {
660        let poly = create_test_polygon();
661        assert!(poly.is_ok());
662
663        if let Ok(p) = poly {
664            let env = envelope_polygon(&p);
665            assert!(env.is_ok());
666
667            if let Ok(e) = env {
668                // Point inside
669                let inside = Point::new(5.0, 4.0);
670                assert!(envelope_contains_point(&e, &inside));
671
672                // Point on boundary
673                let boundary = Point::new(2.0, 4.0);
674                assert!(envelope_contains_point(&e, &boundary));
675
676                // Point outside
677                let outside = Point::new(10.0, 10.0);
678                assert!(!envelope_contains_point(&e, &outside));
679            }
680        }
681    }
682
683    #[test]
684    fn test_envelopes_intersect() {
685        let poly1 = create_test_polygon();
686        let coords2 = vec![
687            Coordinate::new_2d(5.0, 4.0),
688            Coordinate::new_2d(10.0, 4.0),
689            Coordinate::new_2d(10.0, 8.0),
690            Coordinate::new_2d(5.0, 8.0),
691            Coordinate::new_2d(5.0, 4.0),
692        ];
693        let ext2 = LineString::new(coords2);
694
695        assert!(poly1.is_ok() && ext2.is_ok());
696        if let (Ok(p1), Ok(e2)) = (poly1, ext2) {
697            let poly2 = Polygon::new(e2, vec![]);
698            assert!(poly2.is_ok());
699
700            if let Ok(p2) = poly2 {
701                let env1 = envelope_polygon(&p1);
702                let env2 = envelope_polygon(&p2);
703
704                assert!(env1.is_ok() && env2.is_ok());
705                if let (Ok(e1), Ok(e2)) = (env1, env2) {
706                    assert!(envelopes_intersect(&e1, &e2));
707                }
708            }
709        }
710    }
711
712    #[test]
713    fn test_envelopes_no_intersect() {
714        let poly1 = create_test_polygon();
715        let coords2 = vec![
716            Coordinate::new_2d(20.0, 20.0),
717            Coordinate::new_2d(25.0, 20.0),
718            Coordinate::new_2d(25.0, 25.0),
719            Coordinate::new_2d(20.0, 25.0),
720            Coordinate::new_2d(20.0, 20.0),
721        ];
722        let ext2 = LineString::new(coords2);
723
724        assert!(poly1.is_ok() && ext2.is_ok());
725        if let (Ok(p1), Ok(e2)) = (poly1, ext2) {
726            let poly2 = Polygon::new(e2, vec![]);
727            assert!(poly2.is_ok());
728
729            if let Ok(p2) = poly2 {
730                let env1 = envelope_polygon(&p1);
731                let env2 = envelope_polygon(&p2);
732
733                assert!(env1.is_ok() && env2.is_ok());
734                if let (Ok(e1), Ok(e2)) = (env1, env2) {
735                    assert!(!envelopes_intersect(&e1, &e2));
736                }
737            }
738        }
739    }
740
741    #[test]
742    fn test_envelope_union() {
743        let coords1 = vec![
744            Coordinate::new_2d(0.0, 0.0),
745            Coordinate::new_2d(5.0, 0.0),
746            Coordinate::new_2d(5.0, 5.0),
747            Coordinate::new_2d(0.0, 5.0),
748            Coordinate::new_2d(0.0, 0.0),
749        ];
750        let coords2 = vec![
751            Coordinate::new_2d(3.0, 3.0),
752            Coordinate::new_2d(8.0, 3.0),
753            Coordinate::new_2d(8.0, 8.0),
754            Coordinate::new_2d(3.0, 8.0),
755            Coordinate::new_2d(3.0, 3.0),
756        ];
757
758        let ext1 = LineString::new(coords1);
759        let ext2 = LineString::new(coords2);
760
761        assert!(ext1.is_ok() && ext2.is_ok());
762        if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
763            let poly1 = Polygon::new(e1, vec![]);
764            let poly2 = Polygon::new(e2, vec![]);
765
766            assert!(poly1.is_ok() && poly2.is_ok());
767            if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
768                let env1 = envelope_polygon(&p1);
769                let env2 = envelope_polygon(&p2);
770
771                assert!(env1.is_ok() && env2.is_ok());
772                if let (Ok(e1), Ok(e2)) = (env1, env2) {
773                    let union = envelope_union(&e1, &e2);
774                    assert!(union.is_ok());
775
776                    if let Ok(u) = union {
777                        let bounds = u.bounds();
778                        assert!(bounds.is_some());
779                        if let Some((min_x, min_y, max_x, max_y)) = bounds {
780                            assert_eq!(min_x, 0.0);
781                            assert_eq!(min_y, 0.0);
782                            assert_eq!(max_x, 8.0);
783                            assert_eq!(max_y, 8.0);
784                        }
785                    }
786                }
787            }
788        }
789    }
790
791    #[test]
792    fn test_envelope_intersection() {
793        let coords1 = vec![
794            Coordinate::new_2d(0.0, 0.0),
795            Coordinate::new_2d(6.0, 0.0),
796            Coordinate::new_2d(6.0, 6.0),
797            Coordinate::new_2d(0.0, 6.0),
798            Coordinate::new_2d(0.0, 0.0),
799        ];
800        let coords2 = vec![
801            Coordinate::new_2d(3.0, 3.0),
802            Coordinate::new_2d(9.0, 3.0),
803            Coordinate::new_2d(9.0, 9.0),
804            Coordinate::new_2d(3.0, 9.0),
805            Coordinate::new_2d(3.0, 3.0),
806        ];
807
808        let ext1 = LineString::new(coords1);
809        let ext2 = LineString::new(coords2);
810
811        assert!(ext1.is_ok() && ext2.is_ok());
812        if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
813            let poly1 = Polygon::new(e1, vec![]);
814            let poly2 = Polygon::new(e2, vec![]);
815
816            assert!(poly1.is_ok() && poly2.is_ok());
817            if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
818                let env1 = envelope_polygon(&p1);
819                let env2 = envelope_polygon(&p2);
820
821                assert!(env1.is_ok() && env2.is_ok());
822                if let (Ok(e1), Ok(e2)) = (env1, env2) {
823                    let intersection = envelope_intersection(&e1, &e2);
824                    assert!(intersection.is_ok());
825
826                    if let Ok(Some(i)) = intersection {
827                        let bounds = i.bounds();
828                        assert!(bounds.is_some());
829                        if let Some((min_x, min_y, max_x, max_y)) = bounds {
830                            assert_eq!(min_x, 3.0);
831                            assert_eq!(min_y, 3.0);
832                            assert_eq!(max_x, 6.0);
833                            assert_eq!(max_y, 6.0);
834                        }
835                    }
836                }
837            }
838        }
839    }
840
841    #[test]
842    fn test_envelope_intersection_no_overlap() {
843        let coords1 = vec![
844            Coordinate::new_2d(0.0, 0.0),
845            Coordinate::new_2d(5.0, 0.0),
846            Coordinate::new_2d(5.0, 5.0),
847            Coordinate::new_2d(0.0, 5.0),
848            Coordinate::new_2d(0.0, 0.0),
849        ];
850        let coords2 = vec![
851            Coordinate::new_2d(10.0, 10.0),
852            Coordinate::new_2d(15.0, 10.0),
853            Coordinate::new_2d(15.0, 15.0),
854            Coordinate::new_2d(10.0, 15.0),
855            Coordinate::new_2d(10.0, 10.0),
856        ];
857
858        let ext1 = LineString::new(coords1);
859        let ext2 = LineString::new(coords2);
860
861        assert!(ext1.is_ok() && ext2.is_ok());
862        if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
863            let poly1 = Polygon::new(e1, vec![]);
864            let poly2 = Polygon::new(e2, vec![]);
865
866            assert!(poly1.is_ok() && poly2.is_ok());
867            if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
868                let env1 = envelope_polygon(&p1);
869                let env2 = envelope_polygon(&p2);
870
871                assert!(env1.is_ok() && env2.is_ok());
872                if let (Ok(e1), Ok(e2)) = (env1, env2) {
873                    let intersection = envelope_intersection(&e1, &e2);
874                    assert!(intersection.is_ok());
875
876                    if let Ok(result) = intersection {
877                        assert!(result.is_none());
878                    }
879                }
880            }
881        }
882    }
883
884    #[test]
885    fn test_envelope_empty_linestring() {
886        let coords: Vec<Coordinate> = vec![];
887        let line = LineString::new(coords);
888
889        // Empty linestring cannot be created
890        assert!(line.is_err());
891    }
892
893    #[test]
894    fn test_envelope_multipoint_empty() {
895        let mp = MultiPoint::empty();
896        let env = envelope_multipoint(&mp);
897        assert!(env.is_err());
898    }
899
900    #[test]
901    fn test_envelope_geometry_dispatch() {
902        let point = Point::new(3.0, 5.0);
903        let geom = Geometry::Point(point);
904
905        let env = envelope(&geom);
906        assert!(env.is_ok());
907
908        if let Ok(e) = env {
909            let bounds = e.bounds();
910            assert!(bounds.is_some());
911        }
912    }
913}