1use crate::error::{AlgorithmError, Result};
37
38#[derive(Debug, Clone, Copy)]
41pub struct AffineMatrix2D {
42 pub a: f64,
44 pub b: f64,
46 pub c: f64,
48 pub d: f64,
50 pub e: f64,
52 pub f: f64,
54}
55
56impl AffineMatrix2D {
57 #[must_use]
59 pub const fn identity() -> Self {
60 Self {
61 a: 1.0,
62 b: 0.0,
63 c: 0.0,
64 d: 1.0,
65 e: 0.0,
66 f: 0.0,
67 }
68 }
69
70 #[must_use]
72 pub const fn translation(dx: f64, dy: f64) -> Self {
73 Self {
74 a: 1.0,
75 b: 0.0,
76 c: 0.0,
77 d: 1.0,
78 e: dx,
79 f: dy,
80 }
81 }
82
83 #[must_use]
85 pub const fn scale(sx: f64, sy: f64) -> Self {
86 Self {
87 a: sx,
88 b: 0.0,
89 c: 0.0,
90 d: sy,
91 e: 0.0,
92 f: 0.0,
93 }
94 }
95
96 #[must_use]
98 pub fn rotation(angle: f64) -> Self {
99 let cos = angle.cos();
100 let sin = angle.sin();
101 Self {
102 a: cos,
103 b: -sin,
104 c: sin,
105 d: cos,
106 e: 0.0,
107 f: 0.0,
108 }
109 }
110
111 pub fn invert(&self) -> Result<Self> {
117 let det = self.a * self.d - self.b * self.c;
118 if det.abs() < 1e-10 {
119 return Err(AlgorithmError::NumericalError {
120 operation: "matrix_invert",
121 message: "Matrix is singular (determinant near zero)".to_string(),
122 });
123 }
124
125 let inv_det = 1.0 / det;
126 Ok(Self {
127 a: self.d * inv_det,
128 b: -self.b * inv_det,
129 c: -self.c * inv_det,
130 d: self.a * inv_det,
131 e: (self.b * self.f - self.d * self.e) * inv_det,
132 f: (self.c * self.e - self.a * self.f) * inv_det,
133 })
134 }
135}
136
137pub fn affine_transform_2d(
155 matrix: &AffineMatrix2D,
156 x: &[f64],
157 y: &[f64],
158 out_x: &mut [f64],
159 out_y: &mut [f64],
160) -> Result<()> {
161 if x.len() != y.len() || x.len() != out_x.len() || x.len() != out_y.len() {
162 return Err(AlgorithmError::InvalidParameter {
163 parameter: "coordinates",
164 message: format!(
165 "Array length mismatch: x={}, y={}, out_x={}, out_y={}",
166 x.len(),
167 y.len(),
168 out_x.len(),
169 out_y.len()
170 ),
171 });
172 }
173
174 if x.is_empty() {
175 return Ok(());
176 }
177
178 const LANES: usize = 4;
180 let chunks = x.len() / LANES;
181
182 let a = matrix.a;
184 let b = matrix.b;
185 let c = matrix.c;
186 let d = matrix.d;
187 let e = matrix.e;
188 let f = matrix.f;
189
190 for i in 0..chunks {
192 let start = i * LANES;
193 let end = start + LANES;
194
195 for j in start..end {
197 let x_in = x[j];
198 let y_in = y[j];
199 out_x[j] = a * x_in + b * y_in + e;
200 out_y[j] = c * x_in + d * y_in + f;
201 }
202 }
203
204 let remainder_start = chunks * LANES;
206 for i in remainder_start..x.len() {
207 let x_in = x[i];
208 let y_in = y[i];
209 out_x[i] = a * x_in + b * y_in + e;
210 out_y[i] = c * x_in + d * y_in + f;
211 }
212
213 Ok(())
214}
215
216pub fn affine_transform_2d_inplace(
228 matrix: &AffineMatrix2D,
229 x: &mut [f64],
230 y: &mut [f64],
231) -> Result<()> {
232 if x.len() != y.len() {
233 return Err(AlgorithmError::InvalidParameter {
234 parameter: "coordinates",
235 message: format!("Array length mismatch: x={}, y={}", x.len(), y.len()),
236 });
237 }
238
239 if x.is_empty() {
240 return Ok(());
241 }
242
243 let a = matrix.a;
245 let b = matrix.b;
246 let c = matrix.c;
247 let d = matrix.d;
248 let e = matrix.e;
249 let f = matrix.f;
250
251 const LANES: usize = 4;
252 let chunks = x.len() / LANES;
253
254 for i in 0..chunks {
256 let start = i * LANES;
257 let end = start + LANES;
258
259 for j in start..end {
260 let x_in = x[j];
261 let y_in = y[j];
262 x[j] = a * x_in + b * y_in + e;
263 y[j] = c * x_in + d * y_in + f;
264 }
265 }
266
267 let remainder_start = chunks * LANES;
269 for i in remainder_start..x.len() {
270 let x_in = x[i];
271 let y_in = y[i];
272 x[i] = a * x_in + b * y_in + e;
273 y[i] = c * x_in + d * y_in + f;
274 }
275
276 Ok(())
277}
278
279pub fn latlon_to_web_mercator(
298 lon: &[f64],
299 lat: &[f64],
300 out_x: &mut [f64],
301 out_y: &mut [f64],
302) -> Result<()> {
303 if lon.len() != lat.len() || lon.len() != out_x.len() || lon.len() != out_y.len() {
304 return Err(AlgorithmError::InvalidParameter {
305 parameter: "coordinates",
306 message: format!(
307 "Array length mismatch: lon={}, lat={}, out_x={}, out_y={}",
308 lon.len(),
309 lat.len(),
310 out_x.len(),
311 out_y.len()
312 ),
313 });
314 }
315
316 if lon.is_empty() {
317 return Ok(());
318 }
319
320 const EARTH_RADIUS: f64 = 6_378_137.0;
321 const DEG_TO_RAD: f64 = std::f64::consts::PI / 180.0;
322 const MAX_LAT: f64 = 85.0511288; const LANES: usize = 4;
325 let chunks = lon.len() / LANES;
326
327 for i in 0..chunks {
329 let start = i * LANES;
330 let end = start + LANES;
331
332 for j in start..end {
333 let lat_deg = lat[j];
334
335 let lat_clamped = lat_deg.clamp(-MAX_LAT, MAX_LAT);
337 let lat_rad = lat_clamped * DEG_TO_RAD;
338 let lon_rad = lon[j] * DEG_TO_RAD;
339
340 out_x[j] = EARTH_RADIUS * lon_rad;
341
342 let tan_arg = std::f64::consts::FRAC_PI_4 + lat_rad / 2.0;
344 out_y[j] = EARTH_RADIUS * tan_arg.tan().ln();
345 }
346 }
347
348 let remainder_start = chunks * LANES;
350 for i in remainder_start..lon.len() {
351 let lat_deg = lat[i];
352 let lat_clamped = lat_deg.clamp(-MAX_LAT, MAX_LAT);
353 let lat_rad = lat_clamped * DEG_TO_RAD;
354 let lon_rad = lon[i] * DEG_TO_RAD;
355
356 out_x[i] = EARTH_RADIUS * lon_rad;
357 let tan_arg = std::f64::consts::FRAC_PI_4 + lat_rad / 2.0;
358 out_y[i] = EARTH_RADIUS * tan_arg.tan().ln();
359 }
360
361 Ok(())
362}
363
364pub fn web_mercator_to_latlon(
377 x: &[f64],
378 y: &[f64],
379 out_lon: &mut [f64],
380 out_lat: &mut [f64],
381) -> Result<()> {
382 if x.len() != y.len() || x.len() != out_lon.len() || x.len() != out_lat.len() {
383 return Err(AlgorithmError::InvalidParameter {
384 parameter: "coordinates",
385 message: format!(
386 "Array length mismatch: x={}, y={}, out_lon={}, out_lat={}",
387 x.len(),
388 y.len(),
389 out_lon.len(),
390 out_lat.len()
391 ),
392 });
393 }
394
395 if x.is_empty() {
396 return Ok(());
397 }
398
399 const EARTH_RADIUS: f64 = 6_378_137.0;
400 const RAD_TO_DEG: f64 = 180.0 / std::f64::consts::PI;
401
402 const LANES: usize = 4;
403 let chunks = x.len() / LANES;
404
405 for i in 0..chunks {
407 let start = i * LANES;
408 let end = start + LANES;
409
410 for j in start..end {
411 out_lon[j] = (x[j] / EARTH_RADIUS) * RAD_TO_DEG;
412
413 let exp_term = (y[j] / EARTH_RADIUS).exp();
415 out_lat[j] = (2.0 * exp_term.atan() - std::f64::consts::FRAC_PI_2) * RAD_TO_DEG;
416 }
417 }
418
419 let remainder_start = chunks * LANES;
421 for i in remainder_start..x.len() {
422 out_lon[i] = (x[i] / EARTH_RADIUS) * RAD_TO_DEG;
423 let exp_term = (y[i] / EARTH_RADIUS).exp();
424 out_lat[i] = (2.0 * exp_term.atan() - std::f64::consts::FRAC_PI_2) * RAD_TO_DEG;
425 }
426
427 Ok(())
428}
429
430pub fn degrees_to_radians(degrees: &[f64], radians: &mut [f64]) -> Result<()> {
432 if degrees.len() != radians.len() {
433 return Err(AlgorithmError::InvalidParameter {
434 parameter: "arrays",
435 message: format!(
436 "Array length mismatch: degrees={}, radians={}",
437 degrees.len(),
438 radians.len()
439 ),
440 });
441 }
442
443 const DEG_TO_RAD: f64 = std::f64::consts::PI / 180.0;
444 const LANES: usize = 8;
445 let chunks = degrees.len() / LANES;
446
447 for i in 0..chunks {
448 let start = i * LANES;
449 let end = start + LANES;
450 for j in start..end {
451 radians[j] = degrees[j] * DEG_TO_RAD;
452 }
453 }
454
455 let remainder_start = chunks * LANES;
456 for i in remainder_start..degrees.len() {
457 radians[i] = degrees[i] * DEG_TO_RAD;
458 }
459
460 Ok(())
461}
462
463pub fn radians_to_degrees(radians: &[f64], degrees: &mut [f64]) -> Result<()> {
465 if radians.len() != degrees.len() {
466 return Err(AlgorithmError::InvalidParameter {
467 parameter: "arrays",
468 message: format!(
469 "Array length mismatch: radians={}, degrees={}",
470 radians.len(),
471 degrees.len()
472 ),
473 });
474 }
475
476 const RAD_TO_DEG: f64 = 180.0 / std::f64::consts::PI;
477 const LANES: usize = 8;
478 let chunks = radians.len() / LANES;
479
480 for i in 0..chunks {
481 let start = i * LANES;
482 let end = start + LANES;
483 for j in start..end {
484 degrees[j] = radians[j] * RAD_TO_DEG;
485 }
486 }
487
488 let remainder_start = chunks * LANES;
489 for i in remainder_start..radians.len() {
490 degrees[i] = radians[i] * RAD_TO_DEG;
491 }
492
493 Ok(())
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499
500 #[test]
501 fn test_affine_identity() {
502 let matrix = AffineMatrix2D::identity();
503 let x = vec![1.0, 2.0, 3.0, 4.0];
504 let y = vec![5.0, 6.0, 7.0, 8.0];
505 let mut out_x = vec![0.0; 4];
506 let mut out_y = vec![0.0; 4];
507
508 affine_transform_2d(&matrix, &x, &y, &mut out_x, &mut out_y)
509 .expect("affine identity transformation should succeed");
510
511 assert_eq!(out_x, x);
512 assert_eq!(out_y, y);
513 }
514
515 #[test]
516 fn test_affine_translation() {
517 let matrix = AffineMatrix2D::translation(10.0, 20.0);
518 let x = vec![0.0, 1.0, 2.0];
519 let y = vec![0.0, 1.0, 2.0];
520 let mut out_x = vec![0.0; 3];
521 let mut out_y = vec![0.0; 3];
522
523 affine_transform_2d(&matrix, &x, &y, &mut out_x, &mut out_y)
524 .expect("affine translation should succeed");
525
526 assert_eq!(out_x, vec![10.0, 11.0, 12.0]);
527 assert_eq!(out_y, vec![20.0, 21.0, 22.0]);
528 }
529
530 #[test]
531 fn test_affine_scale() {
532 let matrix = AffineMatrix2D::scale(2.0, 3.0);
533 let x = vec![1.0, 2.0, 3.0];
534 let y = vec![1.0, 2.0, 3.0];
535 let mut out_x = vec![0.0; 3];
536 let mut out_y = vec![0.0; 3];
537
538 affine_transform_2d(&matrix, &x, &y, &mut out_x, &mut out_y)
539 .expect("affine scale should succeed");
540
541 assert_eq!(out_x, vec![2.0, 4.0, 6.0]);
542 assert_eq!(out_y, vec![3.0, 6.0, 9.0]);
543 }
544
545 #[test]
546 fn test_affine_invert() {
547 let matrix = AffineMatrix2D {
548 a: 2.0,
549 b: 0.0,
550 c: 0.0,
551 d: 3.0,
552 e: 10.0,
553 f: 20.0,
554 };
555
556 let inverse = matrix.invert().expect("matrix inversion should succeed");
557
558 let x = vec![1.0, 2.0, 3.0];
560 let y = vec![1.0, 2.0, 3.0];
561 let mut temp_x = vec![0.0; 3];
562 let mut temp_y = vec![0.0; 3];
563 let mut out_x = vec![0.0; 3];
564 let mut out_y = vec![0.0; 3];
565
566 affine_transform_2d(&matrix, &x, &y, &mut temp_x, &mut temp_y)
567 .expect("forward transformation should succeed");
568 affine_transform_2d(&inverse, &temp_x, &temp_y, &mut out_x, &mut out_y)
569 .expect("inverse transformation should succeed");
570
571 for i in 0..3 {
572 assert!((out_x[i] - x[i]).abs() < 1e-10);
573 assert!((out_y[i] - y[i]).abs() < 1e-10);
574 }
575 }
576
577 #[test]
578 fn test_web_mercator_roundtrip() {
579 let lon = vec![-122.4194, 139.6917, 2.3522]; let lat = vec![37.7749, 35.6762, 48.8566];
581 let mut x = vec![0.0; 3];
582 let mut y = vec![0.0; 3];
583 let mut out_lon = vec![0.0; 3];
584 let mut out_lat = vec![0.0; 3];
585
586 latlon_to_web_mercator(&lon, &lat, &mut x, &mut y)
587 .expect("latlon to web mercator conversion should succeed");
588 web_mercator_to_latlon(&x, &y, &mut out_lon, &mut out_lat)
589 .expect("web mercator to latlon conversion should succeed");
590
591 for i in 0..3 {
592 assert!((out_lon[i] - lon[i]).abs() < 1e-6);
593 assert!((out_lat[i] - lat[i]).abs() < 1e-6);
594 }
595 }
596
597 #[test]
598 fn test_degrees_radians_conversion() {
599 let degrees = vec![0.0, 45.0, 90.0, 180.0, 270.0, 360.0];
600 let mut radians = vec![0.0; 6];
601 let mut back_to_degrees = vec![0.0; 6];
602
603 degrees_to_radians(°rees, &mut radians)
604 .expect("degrees to radians conversion should succeed");
605 radians_to_degrees(&radians, &mut back_to_degrees)
606 .expect("radians to degrees conversion should succeed");
607
608 for i in 0..6 {
609 assert!((back_to_degrees[i] - degrees[i]).abs() < 1e-10);
610 }
611 }
612
613 #[test]
614 fn test_empty_arrays() {
615 let matrix = AffineMatrix2D::identity();
616 let x: Vec<f64> = vec![];
617 let y: Vec<f64> = vec![];
618 let mut out_x: Vec<f64> = vec![];
619 let mut out_y: Vec<f64> = vec![];
620
621 let result = affine_transform_2d(&matrix, &x, &y, &mut out_x, &mut out_y);
622 assert!(result.is_ok());
623 }
624
625 #[test]
626 fn test_mismatched_lengths() {
627 let matrix = AffineMatrix2D::identity();
628 let x = vec![1.0, 2.0];
629 let y = vec![1.0, 2.0, 3.0]; let mut out_x = vec![0.0; 2];
631 let mut out_y = vec![0.0; 2];
632
633 let result = affine_transform_2d(&matrix, &x, &y, &mut out_x, &mut out_y);
634 assert!(result.is_err());
635 }
636}