use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
use crate::error::{ProjectionError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperationMethod {
DatumPipeline,
GridShift,
DynamicGridShift,
Concatenated,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoordinateOperationDef {
pub operation_code: u32,
pub source_crs_code: u32,
pub target_crs_code: u32,
pub method: OperationMethod,
pub preferred: bool,
}
impl CoordinateOperationDef {
pub const fn new(
operation_code: u32,
source_crs_code: u32,
target_crs_code: u32,
method: OperationMethod,
) -> Self {
Self {
operation_code,
source_crs_code,
target_crs_code,
method,
preferred: false,
}
}
pub const fn preferred(mut self, preferred: bool) -> Self {
self.preferred = preferred;
self
}
}
static OPERATION_REGISTRY: OnceLock<RwLock<HashMap<u32, CoordinateOperationDef>>> = OnceLock::new();
fn registry() -> &'static RwLock<HashMap<u32, CoordinateOperationDef>> {
OPERATION_REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn register_coordinate_operation(op: CoordinateOperationDef) -> Result<()> {
let mut m = registry().write().map_err(|_| {
ProjectionError::DatumError("operation registry lock poisoned".to_string())
})?;
m.insert(op.operation_code, op);
Ok(())
}
pub fn unregister_coordinate_operation(operation_code: u32) -> Result<bool> {
let mut m = registry().write().map_err(|_| {
ProjectionError::DatumError("operation registry lock poisoned".to_string())
})?;
Ok(m.remove(&operation_code).is_some())
}
pub fn clear_coordinate_operations() -> Result<()> {
let mut m = registry().write().map_err(|_| {
ProjectionError::DatumError("operation registry lock poisoned".to_string())
})?;
m.clear();
Ok(())
}
pub fn has_coordinate_operation(operation_code: u32) -> Result<bool> {
let m = registry().read().map_err(|_| {
ProjectionError::DatumError("operation registry lock poisoned".to_string())
})?;
Ok(m.contains_key(&operation_code))
}
pub fn get_coordinate_operation(operation_code: u32) -> Result<Option<CoordinateOperationDef>> {
let m = registry().read().map_err(|_| {
ProjectionError::DatumError("operation registry lock poisoned".to_string())
})?;
Ok(m.get(&operation_code).cloned())
}
#[cfg(test)]
pub(crate) fn coordinate_operation_test_guard() -> std::sync::MutexGuard<'static, ()> {
static GUARD: OnceLock<std::sync::Mutex<()>> = OnceLock::new();
GUARD
.get_or_init(|| std::sync::Mutex::new(()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coordinate_operation_registry_round_trip() {
clear_coordinate_operations().unwrap();
let op = CoordinateOperationDef::new(10715, 22817, 2958, OperationMethod::DynamicGridShift)
.preferred(true);
register_coordinate_operation(op.clone()).unwrap();
assert!(has_coordinate_operation(10715).unwrap());
let loaded = get_coordinate_operation(10715).unwrap();
assert_eq!(loaded, Some(op));
assert!(unregister_coordinate_operation(10715).unwrap());
assert!(!has_coordinate_operation(10715).unwrap());
}
}