Skip to main content

oxiproj_engine/
context.rs

1//! Transformation context: error state, algorithm-selection knobs, and grid registry.
2//!
3//! Ported in spirit from PROJ 9.8.0 `src/4D_api.cpp` (`PJ_CONTEXT`).
4
5use std::collections::HashMap;
6
7use oxiproj_core::ProjError;
8use oxiproj_transformations::GridRegistry;
9
10/// Algorithm selection for transverse Mercator, mirroring PROJ's tmerc `algo` choice.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum TmercAlgo {
14    /// Pick automatically. AUTO defaults to [`TmercAlgo::PoderEngsager`] for
15    /// ellipsoidal tmerc here, matching PROJ's preference for the exact series.
16    Auto,
17    /// Evenden/Snyder approximate series (PROJ `approx`).
18    EvendenSnyder,
19    /// Poder/Engsager exact series (PROJ `exact`, the default for ellipsoids).
20    PoderEngsager,
21}
22
23/// A lightweight transformation context.
24///
25/// Holds the last error encountered, the transverse-Mercator algorithm
26/// preference, and an in-memory registry of raw grid file bytes indexed by name.
27#[derive(Debug, Clone)]
28pub struct Context {
29    /// The most recent error recorded on this context, if any.
30    pub last_error: Option<ProjError>,
31    /// Preferred transverse-Mercator algorithm.
32    pub tmerc_algo: TmercAlgo,
33    /// In-memory grid data indexed by grid name.
34    grid_data: HashMap<String, Vec<u8>>,
35}
36
37impl Context {
38    /// Create a fresh context with no error, `TmercAlgo::Auto`, and an empty grid registry.
39    pub fn new() -> Context {
40        Context {
41            last_error: None,
42            tmerc_algo: TmercAlgo::Auto,
43            grid_data: HashMap::new(),
44        }
45    }
46
47    /// Register raw grid bytes under `name`.
48    ///
49    /// Subsequent calls to `get_grid(name)` (via [`GridRegistry`]) will return a
50    /// reference to these bytes.  Calling this twice with the same `name` replaces
51    /// the previous data.
52    pub fn register_grid(&mut self, name: impl Into<String>, data: Vec<u8>) {
53        self.grid_data.insert(name.into(), data);
54    }
55}
56
57impl GridRegistry for Context {
58    fn get_grid(&self, name: &str) -> Option<&[u8]> {
59        self.grid_data.get(name).map(|v| v.as_slice())
60    }
61}
62
63impl Default for Context {
64    fn default() -> Self {
65        Context::new()
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn new_has_no_error() {
75        let ctx = Context::new();
76        assert!(ctx.last_error.is_none());
77        assert_eq!(ctx.tmerc_algo, TmercAlgo::Auto);
78    }
79
80    #[test]
81    fn default_matches_new() {
82        let a = Context::default();
83        let b = Context::new();
84        assert!(a.last_error.is_none() && b.last_error.is_none());
85        assert_eq!(a.tmerc_algo, b.tmerc_algo);
86    }
87
88    #[test]
89    fn register_and_get_grid() {
90        let mut ctx = Context::new();
91        ctx.register_grid("test.gsb", vec![1u8, 2, 3]);
92        let data = oxiproj_transformations::GridRegistry::get_grid(&ctx, "test.gsb");
93        assert_eq!(data, Some([1u8, 2, 3].as_ref()));
94        assert!(oxiproj_transformations::GridRegistry::get_grid(&ctx, "missing").is_none());
95    }
96}