use crate::terrain::error::TerrainError;
use rustial_math::{ElevationGrid, TileId};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ElevationSourceFailureDiagnostics {
pub network_failures: usize,
pub decode_failures: usize,
pub unsupported_format_failures: usize,
pub other_failures: usize,
pub ignored_completed_responses: usize,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ElevationSourceDiagnostics {
pub queued_requests: usize,
pub in_flight_requests: usize,
pub max_concurrent_requests: usize,
pub known_requests: usize,
pub cancelled_in_flight_requests: usize,
pub failure_diagnostics: ElevationSourceFailureDiagnostics,
}
pub trait ElevationSource: Send + Sync {
fn request(&self, id: TileId);
fn poll(&self) -> Vec<(TileId, Result<ElevationGrid, TerrainError>)>;
fn cancel(&self, _id: TileId) -> bool {
false
}
fn diagnostics(&self) -> Option<ElevationSourceDiagnostics> {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TerrainRgbEncoding {
Terrarium,
Mapbox,
}
impl TerrainRgbEncoding {
#[inline]
pub fn decode(&self, r: u8, g: u8, b: u8) -> f32 {
let raw = match self {
TerrainRgbEncoding::Terrarium => {
(r as f32 * 256.0 + g as f32 + b as f32 / 256.0) - 32768.0
}
TerrainRgbEncoding::Mapbox => {
-10000.0 + (r as f32 * 65536.0 + g as f32 * 256.0 + b as f32) * 0.1
}
};
raw.clamp(-500.0, 9_100.0)
}
}
pub struct FlatElevationSource {
width: u32,
height: u32,
pending: std::sync::Mutex<Vec<TileId>>,
}
impl FlatElevationSource {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
pending: std::sync::Mutex::new(Vec::new()),
}
}
}
impl ElevationSource for FlatElevationSource {
fn request(&self, id: TileId) {
if let Ok(mut pending) = self.pending.lock() {
pending.push(id);
}
}
fn poll(&self) -> Vec<(TileId, Result<ElevationGrid, TerrainError>)> {
let tiles = if let Ok(mut pending) = self.pending.lock() {
std::mem::take(&mut *pending)
} else {
return Vec::new();
};
tiles
.into_iter()
.map(|id| {
let grid = ElevationGrid::flat(id, self.width, self.height);
(id, Ok(grid))
})
.collect()
}
fn diagnostics(&self) -> Option<ElevationSourceDiagnostics> {
let pending = self.pending.lock().map(|p| p.len()).unwrap_or(0);
Some(ElevationSourceDiagnostics {
queued_requests: 0,
in_flight_requests: pending,
max_concurrent_requests: 0,
known_requests: pending,
cancelled_in_flight_requests: 0,
failure_diagnostics: ElevationSourceFailureDiagnostics::default(),
})
}
fn cancel(&self, id: TileId) -> bool {
if let Ok(mut pending) = self.pending.lock() {
let before = pending.len();
pending.retain(|queued| *queued != id);
return pending.len() != before;
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn terrarium_decode_sea_level() {
let elev = TerrainRgbEncoding::Terrarium.decode(128, 0, 0);
assert!((elev - 0.0).abs() < 0.01);
}
#[test]
fn terrarium_decode_positive() {
let elev = TerrainRgbEncoding::Terrarium.decode(131, 232, 0);
assert!((elev - 1000.0).abs() < 1.0);
}
#[test]
fn mapbox_decode_sea_level() {
let elev = TerrainRgbEncoding::Mapbox.decode(1, 134, 160);
assert!((elev - 0.0).abs() < 0.1);
}
#[test]
fn mapbox_decode_positive() {
let elev = TerrainRgbEncoding::Mapbox.decode(1, 173, 208);
assert!((elev - 1003.2).abs() < 1.0);
}
#[test]
fn flat_source_returns_zero() {
let source = FlatElevationSource::new(3, 3);
source.request(TileId::new(5, 10, 10));
let results = source.poll();
assert_eq!(results.len(), 1);
let (id, grid) = &results[0];
assert_eq!(*id, TileId::new(5, 10, 10));
let grid = grid.as_ref().unwrap();
assert_eq!(grid.sample(0.5, 0.5), Some(0.0));
}
}