open-standards/map/map-rs/crates/map-tiles/src/lib.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

124 lines
3.6 KiB
Rust

//! Map tiles — the payload codecs behind the `Tile` cargo (SPEC §1.1).
//!
//! Each codec produces the opaque bytes of a [`map_schema::Tile`]; the
//! [`tile`] bridge wraps them as a content-addressed tile at the right cascade
//! tier (SPEC §1.2):
//!
//! - **MVT** — Mapbox Vector Tiles (roads, boundaries, labels) → `F3:Feature`
//! - **Raster** — PNG/WebP imagery and field tiles → `F2:Field`
//! - **Quantized mesh** — terrain elevation mesh → `F1:Base`
//! - **3D Tiles** — Cesium 3D Tiles buildings (`F3:Feature`) and a generic
//! photoreal/splat wrapper (`F4:Photoreal`)
//!
//! Ported from `joule-tiles`. The codecs are self-contained (no S2/geo or
//! routing dependency); metering lives in `map-receipt`, not here.
pub mod mvt;
pub mod raster;
pub mod splat;
pub mod tiles3d;
pub mod tile;
use thiserror::Error;
/// Errors from tile encoding/decoding operations.
#[derive(Debug, Error)]
pub enum TileError {
#[error("geometry encoding error: {0}")]
GeometryError(String),
#[error("protobuf encoding error: {0}")]
ProtobufError(String),
#[error("image error: {0}")]
ImageError(String),
#[error("invalid tile coordinate: z={z}, x={x}, y={y}")]
InvalidTileCoord { z: u8, x: u32, y: u32 },
#[error("tile not found")]
NotFound,
#[error("compression error: {0}")]
CompressionError(String),
}
/// Standard tile coordinate (slippy map convention).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct TileCoord {
pub z: u8,
pub x: u32,
pub y: u32,
}
impl TileCoord {
/// Create a new tile coordinate.
pub fn new(z: u8, x: u32, y: u32) -> Result<Self, TileError> {
let max = 1u32 << z;
if x >= max || y >= max {
return Err(TileError::InvalidTileCoord { z, x, y });
}
Ok(Self { z, x, y })
}
/// Bounding box of this tile in lat/lon degrees.
pub fn bounds(&self) -> (f64, f64, f64, f64) {
let n = (1u32 << self.z) as f64;
let lon_min = self.x as f64 / n * 360.0 - 180.0;
let lon_max = (self.x + 1) as f64 / n * 360.0 - 180.0;
let lat_max = (std::f64::consts::PI * (1.0 - 2.0 * self.y as f64 / n))
.sinh()
.atan()
.to_degrees();
let lat_min = (std::f64::consts::PI * (1.0 - 2.0 * (self.y + 1) as f64 / n))
.sinh()
.atan()
.to_degrees();
(lat_min, lon_min, lat_max, lon_max)
}
/// Tile coordinate from lat/lon at a given zoom level.
pub fn from_lat_lon(lat: f64, lon: f64, z: u8) -> Self {
let n = (1u32 << z) as f64;
let x = ((lon + 180.0) / 360.0 * n).floor() as u32;
let lat_rad = lat.to_radians();
let y = ((1.0 - lat_rad.tan().asinh() / std::f64::consts::PI) / 2.0 * n).floor() as u32;
let max = (1u32 << z) - 1;
Self {
z,
x: x.min(max),
y: y.min(max),
}
}
/// Parent tile (one zoom level up).
pub fn parent(&self) -> Option<Self> {
if self.z == 0 {
return None;
}
Some(Self {
z: self.z - 1,
x: self.x / 2,
y: self.y / 2,
})
}
/// Four children (one zoom level down).
pub fn children(&self) -> [Self; 4] {
let z = self.z + 1;
let x = self.x * 2;
let y = self.y * 2;
[
Self { z, x, y },
Self { z, x: x + 1, y },
Self { z, x, y: y + 1 },
Self {
z,
x: x + 1,
y: y + 1,
},
]
}
}