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]>
144 lines
3.8 KiB
Rust
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);
|
|
}
|
|
}
|