Skip to main content

wxy_domain/
positions.rs

1//! Positions: how a position follows from an index. One variant per rung.
2
3use std::fmt;
4use std::sync::Arc;
5
6use crate::affine::Affine;
7
8/// The rung of the position ladder (design doc 02, section 3). Each rung
9/// stores strictly more than the one before.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum Rung {
12    /// R0: index only.
13    IndexOnly,
14    /// R1: arithmetic from origin and step (affine).
15    Regular,
16    /// R2: outer product of 1D coordinate vectors.
17    Rectilinear,
18    /// R3: one position per element, still (i, j) indexed.
19    Curvilinear,
20    /// R3': a fitted function of the index (GCP polynomial, RPC, TPS).
21    Fitted,
22    /// R4: implicit mesh; arithmetic from a cell id (DGGS, L3 bins).
23    ImplicitMesh,
24    /// R5: explicit mesh; stored positions plus connectivity.
25    ExplicitMesh,
26    /// R6: features; a geometry per element.
27    Features,
28}
29
30/// A coordinate array: values in C order (last dim fastest) with a shape.
31#[derive(Debug, Clone, PartialEq)]
32pub struct CoordArray {
33    pub shape: Vec<usize>,
34    pub values: Vec<f64>,
35}
36
37impl CoordArray {
38    pub fn new(shape: Vec<usize>, values: Vec<f64>) -> Self {
39        assert_eq!(
40            shape.iter().product::<usize>(),
41            values.len(),
42            "CoordArray shape does not match its values"
43        );
44        Self { shape, values }
45    }
46}
47
48/// A fitted model from index to frame (R3'): GCP polynomial, RPC, TPS.
49pub trait FittedModel: fmt::Debug + Send + Sync {
50    fn forward(&self, index: &[f64]) -> Option<Vec<f64>>;
51    fn inverse(&self, _position: &[f64]) -> Option<Vec<f64>> {
52        None
53    }
54}
55
56/// A cell scheme (R4): positions computed from a cell id.
57pub trait CellScheme: fmt::Debug + Send + Sync {
58    /// A short name, such as `"healpix"`, `"h3"` or `"l3bin"`.
59    fn name(&self) -> &str;
60    /// The centre of a cell, in the scheme's frame.
61    fn centre(&self, cell: u64) -> Option<Vec<f64>>;
62}
63
64/// Stored elements (R5 vertices, R6 geometries). Kept opaque in S1.
65pub trait Elements: fmt::Debug + Send + Sync {
66    fn len(&self) -> usize;
67    fn is_empty(&self) -> bool {
68        self.len() == 0
69    }
70}
71
72/// How positions follow from an index, for the dims a mapping covers.
73#[derive(Debug, Clone)]
74pub enum Positions {
75    /// R0. No positions.
76    IndexOnly,
77    /// R1, two dims `(row, col)` in that order: an affine transform.
78    Affine(Affine),
79    /// R1, one dim: `origin` is the position at index 0.0 (the outer edge
80    /// of the first element), `step` the change per element.
81    Regular1 { origin: f64, step: f64 },
82    /// R2. One coordinate vector per covered dim.
83    Rectilinear(Vec<Vec<f64>>),
84    /// R3. One array per output coordinate, each shaped like the covered
85    /// dims.
86    Curvilinear(Vec<CoordArray>),
87    /// R3'.
88    Fitted(Arc<dyn FittedModel>),
89    /// R4.
90    Scheme(Arc<dyn CellScheme>),
91    /// R5 and R6.
92    Explicit(Arc<dyn Elements>),
93}
94
95impl Positions {
96    pub fn rung(&self) -> Rung {
97        match self {
98            Positions::IndexOnly => Rung::IndexOnly,
99            Positions::Affine(_) | Positions::Regular1 { .. } => Rung::Regular,
100            Positions::Rectilinear(_) => Rung::Rectilinear,
101            Positions::Curvilinear(_) => Rung::Curvilinear,
102            Positions::Fitted(_) => Rung::Fitted,
103            Positions::Scheme(_) => Rung::ImplicitMesh,
104            Positions::Explicit(_) => Rung::ExplicitMesh,
105        }
106    }
107
108    /// Number of values stored to describe the positions. The "six numbers
109    /// against 209,824" comparison in regular-in-what.
110    pub fn stored_values(&self) -> usize {
111        match self {
112            Positions::IndexOnly => 0,
113            Positions::Affine(_) => 6,
114            Positions::Regular1 { .. } => 2,
115            Positions::Rectilinear(v) => v.iter().map(Vec::len).sum(),
116            Positions::Curvilinear(a) => a.iter().map(|c| c.values.len()).sum(),
117            Positions::Fitted(_) | Positions::Scheme(_) | Positions::Explicit(_) => 0,
118        }
119    }
120}