1use crate::error::{EmbeddedError, Result};
6use core::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct MinimalCoordinate {
11 pub x: f32,
13 pub y: f32,
15}
16
17impl MinimalCoordinate {
18 pub const fn new(x: f32, y: f32) -> Self {
20 Self { x, y }
21 }
22
23 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 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#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct MinimalBounds {
45 pub min_x: f32,
47 pub min_y: f32,
49 pub max_x: f32,
51 pub max_y: f32,
53}
54
55impl MinimalBounds {
56 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 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 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 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 pub fn width(&self) -> f32 {
97 self.max_x - self.min_x
98 }
99
100 pub fn height(&self) -> f32 {
102 self.max_y - self.min_y
103 }
104
105 pub fn area(&self) -> f32 {
107 self.width() * self.height()
108 }
109
110 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 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 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#[derive(Debug, Clone, Copy)]
147pub struct MinimalRasterMeta {
148 pub width: u16,
150 pub height: u16,
152 pub bands: u8,
154 pub pixel_size: u8,
156}
157
158impl MinimalRasterMeta {
159 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 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 pub const fn band_size(&self) -> usize {
176 self.width as usize * self.height as usize * self.pixel_size as usize
177 }
178
179 pub const fn row_size(&self) -> usize {
181 self.width as usize * self.pixel_size as usize
182 }
183}
184
185#[derive(Debug, Clone, Copy)]
187pub struct MinimalTransform {
188 pub x0: f32,
190 pub dx: f32,
192 pub rx: f32,
194 pub y0: f32,
196 pub ry: f32,
198 pub dy: f32,
200}
201
202impl MinimalTransform {
203 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 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 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 pub fn world_to_pixel(&self, coord: &MinimalCoordinate) -> Result<(u16, u16)> {
236 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#[derive(Debug, Clone)]
257pub struct MinimalFeature<const MAX_POINTS: usize> {
258 pub points: heapless::Vec<MinimalCoordinate, MAX_POINTS>,
260 pub feature_type: FeatureType,
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub enum FeatureType {
267 Point,
269 Line,
271 Polygon,
273}
274
275impl<const MAX_POINTS: usize> MinimalFeature<MAX_POINTS> {
276 pub const fn new(feature_type: FeatureType) -> Self {
278 Self {
279 points: heapless::Vec::new(),
280 feature_type,
281 }
282 }
283
284 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 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 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 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 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}