shared-framework 0.0.17

Reusable building blocks for HTTP services — Hyper routing, SeaORM data layer, validation, OpenAPI docs, jobs, queues, cache.
Documentation
//! GeoJSON polygon with point-in-polygon tests.
//!
//! [`GeoPolygon`] serializes as `{ "type": "Polygon", "coordinates": ... }` where
//! `coordinates[0]` is the exterior ring and the rest are holes. Each ring is a
//! list of `[longitude, latitude]` positions. Use [`contains`](GeoPolygon::contains)
//! to test whether a coordinate lies inside the polygon but outside any hole.
//!
//! ```ignore
//! let poly = GeoPolygon::new(exterior_and_holes);
//! let inside = poly.contains(48.85, 2.35);
//! ```

use serde::{Deserialize, Serialize};

/// GeoJSON polygon: an exterior ring followed by zero or more holes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeoPolygon {
    /// GeoJSON type name; always `"Polygon"` when built via [`GeoPolygon::new`].
    #[serde(rename = "type")]
    pub type_name: String,
    /// Rings of `[longitude, latitude]` positions; first is the exterior, rest are holes.
    pub coordinates: Vec<Vec<Vec<f64>>>, // exterior + holes
}

impl GeoPolygon {
    /// Creates a polygon with type `"Polygon"` and the given rings.
    pub fn new(coordinates: Vec<Vec<Vec<f64>>>) -> Self {
        Self { type_name: "Polygon".to_string(), coordinates }
    }

    /// Returns true when `(lat, lon)` is inside the exterior ring and outside every hole.
    /// Uses ray casting; returns false for empty rings or rings with fewer than 3 positions.
    pub fn contains(&self, lat: f64, lon: f64) -> bool {
        if self.coordinates.is_empty() {
            return false;
        }
        let exterior = &self.coordinates[0];
        if !point_in_ring(lon, lat, exterior) {
            return false;
        }
        for hole in self.coordinates.iter().skip(1) {
            if point_in_ring(lon, lat, hole) {
                return false;
            }
        }
        true
    }
}

fn point_in_ring(x: f64, y: f64, ring: &[Vec<f64>]) -> bool {
    let mut inside = false;
    let n = ring.len();
    if n < 3 { return false; }
    let mut j = n - 1;
    for i in 0..n {
        let xi = ring[i][0];
        let yi = ring[i][1];
        let xj = ring[j][0];
        let yj = ring[j][1];
        let intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
        if intersect { inside = !inside; }
        j = i;
    }
    inside
}