lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

geo.rs (3439B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::vec;
      3 #[cfg(not(feature = "std"))]
      4 use alloc::{string::String, vec::Vec};
      5 
      6 use radroots_events::farm::{RadrootsGeoJsonPoint, RadrootsGeoJsonPolygon};
      7 
      8 const EARTH_RADIUS_M: f64 = 6_378_137.0;
      9 
     10 pub fn geojson_point_from_lat_lng(lat: f64, lng: f64) -> RadrootsGeoJsonPoint {
     11     RadrootsGeoJsonPoint {
     12         r#type: String::from("Point"),
     13         coordinates: [lng, lat],
     14     }
     15 }
     16 
     17 pub fn geojson_polygon_circle_wgs84(
     18     lat: f64,
     19     lng: f64,
     20     radius_m: f64,
     21     steps: usize,
     22 ) -> RadrootsGeoJsonPolygon {
     23     let steps = if steps < 3 { 3 } else { steps };
     24     let lat1 = lat.to_radians();
     25     let lng1 = lng.to_radians();
     26     let angular = radius_m / EARTH_RADIUS_M;
     27     let sin_lat1 = lat1.sin();
     28     let cos_lat1 = lat1.cos();
     29     let sin_ang = angular.sin();
     30     let cos_ang = angular.cos();
     31 
     32     let mut ring = Vec::with_capacity(steps + 1);
     33     for idx in 0..=steps {
     34         let bearing = (idx as f64) * core::f64::consts::TAU / (steps as f64);
     35         let sin_bearing = bearing.sin();
     36         let cos_bearing = bearing.cos();
     37 
     38         let sin_lat2 = sin_lat1 * cos_ang + cos_lat1 * sin_ang * cos_bearing;
     39         let lat2 = sin_lat2.asin();
     40         let y = sin_bearing * sin_ang * cos_lat1;
     41         let x = cos_ang - sin_lat1 * sin_lat2;
     42         let lng2 = lng1 + y.atan2(x);
     43 
     44         let lat_deg = round_coord(lat2.to_degrees());
     45         let lng_deg = round_coord(normalize_lng(lng2.to_degrees()));
     46         ring.push([lng_deg, lat_deg]);
     47     }
     48 
     49     RadrootsGeoJsonPolygon {
     50         r#type: String::from("Polygon"),
     51         coordinates: vec![ring],
     52     }
     53 }
     54 
     55 fn round_coord(value: f64) -> f64 {
     56     let scale = 1_000_000.0;
     57     (value * scale).round() / scale
     58 }
     59 
     60 fn normalize_lng(value: f64) -> f64 {
     61     let mut lng = value;
     62     while lng > 180.0 {
     63         lng -= 360.0;
     64     }
     65     while lng < -180.0 {
     66         lng += 360.0;
     67     }
     68     lng
     69 }
     70 
     71 #[cfg(test)]
     72 mod tests {
     73     use super::{geojson_point_from_lat_lng, geojson_polygon_circle_wgs84};
     74 
     75     #[test]
     76     fn point_uses_lng_lat_coordinate_order() {
     77         let point = geojson_point_from_lat_lng(37.7, -122.4);
     78         assert_eq!(point.r#type, "Point");
     79         assert_eq!(point.coordinates, [-122.4, 37.7]);
     80     }
     81 
     82     #[test]
     83     fn polygon_enforces_minimum_steps_and_closed_ring() {
     84         let polygon = geojson_polygon_circle_wgs84(37.7, -122.4, 100.0, 1);
     85         assert_eq!(polygon.r#type, "Polygon");
     86         assert_eq!(polygon.coordinates.len(), 1);
     87         let ring = &polygon.coordinates[0];
     88         assert_eq!(ring.len(), 4);
     89         assert_eq!(ring.first(), ring.last());
     90     }
     91 
     92     #[test]
     93     fn polygon_normalizes_longitudes_into_wgs84_range() {
     94         let positive = geojson_polygon_circle_wgs84(0.0, 540.0, 10.0, 8);
     95         for point in &positive.coordinates[0] {
     96             assert!(point[0] <= 180.0);
     97             assert!(point[0] >= -180.0);
     98         }
     99 
    100         let negative = geojson_polygon_circle_wgs84(0.0, -540.0, 10.0, 8);
    101         for point in &negative.coordinates[0] {
    102             assert!(point[0] <= 180.0);
    103             assert!(point[0] >= -180.0);
    104         }
    105     }
    106 
    107     #[test]
    108     fn polygon_respects_steps_when_above_minimum() {
    109         let polygon = geojson_polygon_circle_wgs84(0.0, 10.0, 10.0, 5);
    110         assert_eq!(polygon.coordinates[0].len(), 6);
    111         for point in &polygon.coordinates[0] {
    112             assert!(point[0] <= 180.0);
    113             assert!(point[0] >= -180.0);
    114         }
    115     }
    116 }