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 }