open-standards/map/map-rs/crates/map-index/src/lat_lng.rs
Transaction Science ba1212361f Add aac, map, sandbox standards; catalog the standards family
Commit the previously-untracked standard directories and catalog them in the
root README alongside proof:

- aac/    — communication cargo class: portable boards + a ten-level
            offline-first cascade (Apache-2.0 code / CC-BY-4.0 spec).
- map/    — geospatial cargo class: range-requestable signed tilesets +
            an F0–F4 fidelity cascade (Apache-2.0 code / CC-BY-4.0 spec).
- sandbox/— isolated, metered, attested execution across the heterogeneous
            compute fabric (Apache-2.0 code / CC-BY-4.0 spec).
- jouleclaw-otel-authz, jouleclaw-trust — new jouleclaw reference crates.

Root README now lists aac, map, sandbox, and proof in the catalog, the tree,
and the contribution guide.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-07 17:04:36 -04:00

144 lines
3.8 KiB
Rust

// S2LatLng — latitude/longitude in radians.
use crate::point::S2Point;
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
/// Latitude and longitude stored in radians.
/// Latitude in [-PI/2, PI/2], longitude normalized to [-PI, PI].
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct S2LatLng {
/// Latitude in radians.
pub lat: f64,
/// Longitude in radians (normalized to [-PI, PI]).
pub lng: f64,
}
impl PartialEq for S2LatLng {
fn eq(&self, other: &Self) -> bool {
(self.lat - other.lat).abs() < 1e-15 && (self.lng - other.lng).abs() < 1e-15
}
}
impl S2LatLng {
/// Create from radians. Longitude is normalized to [-PI, PI].
#[inline]
pub fn new(lat: f64, lng: f64) -> Self {
Self {
lat,
lng: normalize_lng(lng),
}
}
/// Create from degrees.
#[inline]
pub fn from_degrees(lat_deg: f64, lng_deg: f64) -> Self {
Self::new(lat_deg.to_radians(), lng_deg.to_radians())
}
/// Convert to (lat_deg, lng_deg).
#[inline]
pub fn to_degrees(self) -> (f64, f64) {
(self.lat.to_degrees(), self.lng.to_degrees())
}
/// Create from an S2Point.
#[inline]
pub fn from_point(p: S2Point) -> Self {
let (lat, lng) = p.to_lat_lng();
Self { lat, lng }
}
/// Convert to an S2Point on the unit sphere.
#[inline]
pub fn to_point(self) -> S2Point {
S2Point::from_lat_lng(self.lat, self.lng)
}
/// Angular distance to another LatLng (great-circle distance in radians).
#[inline]
pub fn get_distance(self, other: Self) -> f64 {
self.to_point().angle(other.to_point())
}
/// Check if coordinates are within valid ranges.
#[inline]
pub fn is_valid(self) -> bool {
self.lat.abs() <= PI / 2.0 + 1e-15 && self.lng.abs() <= PI + 1e-15
}
}
/// Normalize longitude to [-PI, PI].
#[inline]
fn normalize_lng(mut lng: f64) -> f64 {
if lng > PI {
lng %= 2.0 * PI;
if lng > PI {
lng -= 2.0 * PI;
}
} else if lng < -PI {
lng %= 2.0 * PI;
if lng < -PI {
lng += 2.0 * PI;
}
}
lng
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_degrees_to_degrees() {
let ll = S2LatLng::from_degrees(37.7749, -122.4194);
let (lat, lng) = ll.to_degrees();
assert!((lat - 37.7749).abs() < 1e-10);
assert!((lng - (-122.4194)).abs() < 1e-10);
}
#[test]
fn normalize_longitude() {
let ll = S2LatLng::from_degrees(0.0, 270.0);
let (_, lng) = ll.to_degrees();
assert!((lng - (-90.0)).abs() < 1e-10);
}
#[test]
fn normalize_negative_longitude() {
let ll = S2LatLng::from_degrees(0.0, -270.0);
let (_, lng) = ll.to_degrees();
assert!((lng - 90.0).abs() < 1e-10);
}
#[test]
fn round_trip_point() {
let ll = S2LatLng::from_degrees(45.0, 90.0);
let p = ll.to_point();
let ll2 = S2LatLng::from_point(p);
assert!((ll.lat - ll2.lat).abs() < 1e-14);
assert!((ll.lng - ll2.lng).abs() < 1e-14);
}
#[test]
fn distance() {
let a = S2LatLng::from_degrees(0.0, 0.0);
let b = S2LatLng::from_degrees(0.0, 90.0);
let dist = a.get_distance(b);
assert!((dist - PI / 2.0).abs() < 1e-14);
}
#[test]
fn is_valid_check() {
assert!(S2LatLng::from_degrees(45.0, 90.0).is_valid());
assert!(!S2LatLng::new(2.0, 0.0).is_valid()); // lat > PI/2
}
#[test]
fn serialize_deserialize() {
let ll = S2LatLng::from_degrees(37.0, -122.0);
let json = serde_json::to_string(&ll).unwrap();
let ll2: S2LatLng = serde_json::from_str(&json).unwrap();
assert_eq!(ll, ll2);
}
}