Skip to main content

oxigdal_embedded/
minimal.rs

1//! Minimal feature set for ultra-constrained environments
2//!
3//! Provides lightweight geospatial primitives with minimal memory footprint
4
5use crate::error::{EmbeddedError, Result};
6use core::fmt;
7
8/// Minimal coordinate representation (32-bit floats)
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct MinimalCoordinate {
11    /// Longitude or X coordinate
12    pub x: f32,
13    /// Latitude or Y coordinate
14    pub y: f32,
15}
16
17impl MinimalCoordinate {
18    /// Create a new coordinate
19    pub const fn new(x: f32, y: f32) -> Self {
20        Self { x, y }
21    }
22
23    /// Calculate distance to another coordinate (Euclidean)
24    pub fn distance_to(&self, other: &Self) -> f32 {
25        let dx = self.x - other.x;
26        let dy = self.y - other.y;
27        libm::sqrtf(dx * dx + dy * dy)
28    }
29
30    /// Check if coordinate is valid (not NaN or infinite)
31    pub fn is_valid(&self) -> bool {
32        self.x.is_finite() && self.y.is_finite()
33    }
34}
35
36impl fmt::Display for MinimalCoordinate {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(f, "({}, {})", self.x, self.y)
39    }
40}
41
42/// Minimal bounding box representation
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct MinimalBounds {
45    /// Minimum X
46    pub min_x: f32,
47    /// Minimum Y
48    pub min_y: f32,
49    /// Maximum X
50    pub max_x: f32,
51    /// Maximum Y
52    pub max_y: f32,
53}
54
55impl MinimalBounds {
56    /// Create a new bounding box
57    pub const fn new(min_x: f32, min_y: f32, max_x: f32, max_y: f32) -> Self {
58        Self {
59            min_x,
60            min_y,
61            max_x,
62            max_y,
63        }
64    }
65
66    /// Create from center and size
67    pub fn from_center(center: MinimalCoordinate, width: f32, height: f32) -> Self {
68        let half_w = width / 2.0;
69        let half_h = height / 2.0;
70
71        Self {
72            min_x: center.x - half_w,
73            min_y: center.y - half_h,
74            max_x: center.x + half_w,
75            max_y: center.y + half_h,
76        }
77    }
78
79    /// Check if point is inside bounds
80    pub fn contains(&self, point: &MinimalCoordinate) -> bool {
81        point.x >= self.min_x
82            && point.x <= self.max_x
83            && point.y >= self.min_y
84            && point.y <= self.max_y
85    }
86
87    /// Check if bounds intersect
88    pub fn intersects(&self, other: &Self) -> bool {
89        !(self.max_x < other.min_x
90            || self.min_x > other.max_x
91            || self.max_y < other.min_y
92            || self.min_y > other.max_y)
93    }
94
95    /// Get width
96    pub fn width(&self) -> f32 {
97        self.max_x - self.min_x
98    }
99
100    /// Get height
101    pub fn height(&self) -> f32 {
102        self.max_y - self.min_y
103    }
104
105    /// Get area
106    pub fn area(&self) -> f32 {
107        self.width() * self.height()
108    }
109
110    /// Get center point
111    pub fn center(&self) -> MinimalCoordinate {
112        MinimalCoordinate::new(
113            (self.min_x + self.max_x) / 2.0,
114            (self.min_y + self.max_y) / 2.0,
115        )
116    }
117
118    /// Expand bounds to include point
119    pub fn expand_to_include(&mut self, point: &MinimalCoordinate) {
120        if point.x < self.min_x {
121            self.min_x = point.x;
122        }
123        if point.x > self.max_x {
124            self.max_x = point.x;
125        }
126        if point.y < self.min_y {
127            self.min_y = point.y;
128        }
129        if point.y > self.max_y {
130            self.max_y = point.y;
131        }
132    }
133
134    /// Check if bounds are valid
135    pub fn is_valid(&self) -> bool {
136        self.min_x <= self.max_x
137            && self.min_y <= self.max_y
138            && self.min_x.is_finite()
139            && self.max_x.is_finite()
140            && self.min_y.is_finite()
141            && self.max_y.is_finite()
142    }
143}
144
145/// Minimal raster metadata
146#[derive(Debug, Clone, Copy)]
147pub struct MinimalRasterMeta {
148    /// Width in pixels
149    pub width: u16,
150    /// Height in pixels
151    pub height: u16,
152    /// Number of bands
153    pub bands: u8,
154    /// Data type size in bytes
155    pub pixel_size: u8,
156}
157
158impl MinimalRasterMeta {
159    /// Create new raster metadata
160    pub const fn new(width: u16, height: u16, bands: u8, pixel_size: u8) -> Self {
161        Self {
162            width,
163            height,
164            bands,
165            pixel_size,
166        }
167    }
168
169    /// Calculate total size in bytes
170    pub const fn total_size(&self) -> usize {
171        self.width as usize * self.height as usize * self.bands as usize * self.pixel_size as usize
172    }
173
174    /// Calculate band size in bytes
175    pub const fn band_size(&self) -> usize {
176        self.width as usize * self.height as usize * self.pixel_size as usize
177    }
178
179    /// Calculate row size in bytes
180    pub const fn row_size(&self) -> usize {
181        self.width as usize * self.pixel_size as usize
182    }
183}
184
185/// Minimal transformation (affine transform coefficients)
186#[derive(Debug, Clone, Copy)]
187pub struct MinimalTransform {
188    /// Top-left X coordinate
189    pub x0: f32,
190    /// Pixel width
191    pub dx: f32,
192    /// Rotation (usually 0)
193    pub rx: f32,
194    /// Top-left Y coordinate
195    pub y0: f32,
196    /// Rotation (usually 0)
197    pub ry: f32,
198    /// Pixel height (usually negative)
199    pub dy: f32,
200}
201
202impl MinimalTransform {
203    /// Create identity transform
204    pub const fn identity() -> Self {
205        Self {
206            x0: 0.0,
207            dx: 1.0,
208            rx: 0.0,
209            y0: 0.0,
210            ry: 0.0,
211            dy: -1.0,
212        }
213    }
214
215    /// Create simple transform (no rotation)
216    pub const fn new_simple(x0: f32, y0: f32, pixel_width: f32, pixel_height: f32) -> Self {
217        Self {
218            x0,
219            dx: pixel_width,
220            rx: 0.0,
221            y0,
222            ry: 0.0,
223            dy: pixel_height,
224        }
225    }
226
227    /// Transform pixel coordinates to world coordinates
228    pub fn pixel_to_world(&self, col: u16, row: u16) -> MinimalCoordinate {
229        let x = self.x0 + self.dx * col as f32 + self.rx * row as f32;
230        let y = self.y0 + self.ry * col as f32 + self.dy * row as f32;
231        MinimalCoordinate::new(x, y)
232    }
233
234    /// Transform world coordinates to pixel coordinates
235    pub fn world_to_pixel(&self, coord: &MinimalCoordinate) -> Result<(u16, u16)> {
236        // Inverse transform (simplified for non-rotated case)
237        if self.rx != 0.0 || self.ry != 0.0 {
238            return Err(EmbeddedError::UnsupportedOperation);
239        }
240
241        let col = ((coord.x - self.x0) / self.dx) as i32;
242        let row = ((coord.y - self.y0) / self.dy) as i32;
243
244        if col < 0 || row < 0 || col > u16::MAX as i32 || row > u16::MAX as i32 {
245            return Err(EmbeddedError::OutOfBounds {
246                index: col.max(row) as usize,
247                max: u16::MAX as usize,
248            });
249        }
250
251        Ok((col as u16, row as u16))
252    }
253}
254
255/// Minimal vector feature (point, line, or polygon)
256#[derive(Debug, Clone)]
257pub struct MinimalFeature<const MAX_POINTS: usize> {
258    /// Feature points
259    pub points: heapless::Vec<MinimalCoordinate, MAX_POINTS>,
260    /// Feature type
261    pub feature_type: FeatureType,
262}
263
264/// Feature type
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub enum FeatureType {
267    /// Point feature
268    Point,
269    /// Line feature
270    Line,
271    /// Polygon feature (closed line)
272    Polygon,
273}
274
275impl<const MAX_POINTS: usize> MinimalFeature<MAX_POINTS> {
276    /// Create a new feature
277    pub const fn new(feature_type: FeatureType) -> Self {
278        Self {
279            points: heapless::Vec::new(),
280            feature_type,
281        }
282    }
283
284    /// Add a point to the feature
285    pub fn add_point(&mut self, point: MinimalCoordinate) -> Result<()> {
286        self.points
287            .push(point)
288            .map_err(|_| EmbeddedError::BufferTooSmall {
289                required: 1,
290                available: 0,
291            })
292    }
293
294    /// Get bounding box
295    pub fn bounds(&self) -> Option<MinimalBounds> {
296        if self.points.is_empty() {
297            return None;
298        }
299
300        let first = self.points[0];
301        let mut bounds = MinimalBounds::new(first.x, first.y, first.x, first.y);
302
303        for point in &self.points {
304            bounds.expand_to_include(point);
305        }
306
307        Some(bounds)
308    }
309
310    /// Calculate total length (for lines and polygons)
311    pub fn length(&self) -> f32 {
312        if self.points.len() < 2 {
313            return 0.0;
314        }
315
316        let mut total = 0.0;
317        for i in 0..(self.points.len() - 1) {
318            total += self.points[i].distance_to(&self.points[i + 1]);
319        }
320
321        // Close polygon
322        if self.feature_type == FeatureType::Polygon && self.points.len() > 2 {
323            if let (Some(first), Some(last)) = (self.points.first(), self.points.last()) {
324                total += last.distance_to(first);
325            }
326        }
327
328        total
329    }
330
331    /// Calculate area (for polygons only, using shoelace formula)
332    pub fn area(&self) -> Result<f32> {
333        if self.feature_type != FeatureType::Polygon {
334            return Err(EmbeddedError::UnsupportedOperation);
335        }
336
337        if self.points.len() < 3 {
338            return Ok(0.0);
339        }
340
341        let mut area = 0.0;
342        let n = self.points.len();
343
344        for i in 0..n {
345            let j = (i + 1) % n;
346            area += self.points[i].x * self.points[j].y;
347            area -= self.points[j].x * self.points[i].y;
348        }
349
350        Ok(libm::fabsf(area) / 2.0)
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn test_coordinate() {
360        let coord = MinimalCoordinate::new(10.0, 20.0);
361        assert_eq!(coord.x, 10.0);
362        assert_eq!(coord.y, 20.0);
363        assert!(coord.is_valid());
364
365        let other = MinimalCoordinate::new(13.0, 24.0);
366        let dist = coord.distance_to(&other);
367        assert!((dist - 5.0).abs() < 0.001);
368    }
369
370    #[test]
371    fn test_bounds() {
372        let bounds = MinimalBounds::new(0.0, 0.0, 10.0, 10.0);
373        assert_eq!(bounds.width(), 10.0);
374        assert_eq!(bounds.height(), 10.0);
375        assert_eq!(bounds.area(), 100.0);
376
377        let point = MinimalCoordinate::new(5.0, 5.0);
378        assert!(bounds.contains(&point));
379
380        let outside = MinimalCoordinate::new(15.0, 15.0);
381        assert!(!bounds.contains(&outside));
382    }
383
384    #[test]
385    fn test_bounds_intersection() {
386        let b1 = MinimalBounds::new(0.0, 0.0, 10.0, 10.0);
387        let b2 = MinimalBounds::new(5.0, 5.0, 15.0, 15.0);
388        let b3 = MinimalBounds::new(20.0, 20.0, 30.0, 30.0);
389
390        assert!(b1.intersects(&b2));
391        assert!(!b1.intersects(&b3));
392    }
393
394    #[test]
395    fn test_raster_meta() {
396        let meta = MinimalRasterMeta::new(100, 100, 3, 1);
397        assert_eq!(meta.total_size(), 30000);
398        assert_eq!(meta.band_size(), 10000);
399        assert_eq!(meta.row_size(), 100);
400    }
401
402    #[test]
403    fn test_transform() {
404        let transform = MinimalTransform::new_simple(0.0, 100.0, 1.0, -1.0);
405        let coord = transform.pixel_to_world(10, 10);
406        assert_eq!(coord.x, 10.0);
407        assert_eq!(coord.y, 90.0);
408
409        let (col, row) = transform.world_to_pixel(&coord).expect("transform failed");
410        assert_eq!(col, 10);
411        assert_eq!(row, 10);
412    }
413
414    #[test]
415    fn test_feature_line() {
416        let mut line = MinimalFeature::<16>::new(FeatureType::Line);
417        line.add_point(MinimalCoordinate::new(0.0, 0.0))
418            .expect("add failed");
419        line.add_point(MinimalCoordinate::new(3.0, 4.0))
420            .expect("add failed");
421
422        let length = line.length();
423        assert!((length - 5.0).abs() < 0.001);
424    }
425
426    #[test]
427    fn test_feature_polygon() {
428        let mut poly = MinimalFeature::<16>::new(FeatureType::Polygon);
429        poly.add_point(MinimalCoordinate::new(0.0, 0.0))
430            .expect("add failed");
431        poly.add_point(MinimalCoordinate::new(10.0, 0.0))
432            .expect("add failed");
433        poly.add_point(MinimalCoordinate::new(10.0, 10.0))
434            .expect("add failed");
435        poly.add_point(MinimalCoordinate::new(0.0, 10.0))
436            .expect("add failed");
437
438        let area = poly.area().expect("area calculation failed");
439        assert!((area - 100.0).abs() < 0.001);
440    }
441}