1use crate::advanced::kriging::{CovarianceFunction, KrigingInterpolator};
46use crate::advanced::rbf::{RBFInterpolator, RBFKernel};
47use crate::advanced::thinplate::ThinPlateSpline;
48use crate::error::{InterpolateError, InterpolateResult};
49use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ScalarOperand};
50use scirs2_core::numeric::{Float, FromPrimitive, ToPrimitive};
51use std::fmt::{Debug, Display, LowerExp};
52use std::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign};
53
54#[derive(Debug, Clone, Copy, PartialEq)]
56pub enum CoordinateSystem {
57 WGS84,
59 UTM(u8), WebMercator,
63 LocalCartesian,
65 Custom,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq)]
71pub enum InterpolationModel {
72 InverseDistanceWeighting,
74 Kriging,
76 SphericalRBF,
78 ThinPlateSpline,
80 NaturalNeighbor,
82 Bilinear,
84}
85
86#[derive(Debug, Clone)]
88pub struct GeospatialConfig<T> {
89 pub coordinate_system: CoordinateSystem,
91 pub model: InterpolationModel,
93 pub use_spherical_distance: bool,
95 pub earth_radius_km: T,
97 pub search_radius_km: Option<T>,
99 pub max_neighbors: Option<usize>,
101 pub anisotropy_angle: Option<T>,
103 pub anisotropy_ratio: Option<T>,
104 pub elevation_weight: Option<T>,
106}
107
108impl<T: Float + FromPrimitive> Default for GeospatialConfig<T> {
109 fn default() -> Self {
110 Self {
111 coordinate_system: CoordinateSystem::WGS84,
112 model: InterpolationModel::Kriging,
113 use_spherical_distance: true,
114 earth_radius_km: T::from_f64(6371.0).expect("Operation failed"), search_radius_km: None,
116 max_neighbors: Some(20),
117 anisotropy_angle: None,
118 anisotropy_ratio: None,
119 elevation_weight: None,
120 }
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct GeospatialResult<T> {
127 pub values: Array1<T>,
129 pub variance: Option<Array1<T>>,
131 pub neighbor_counts: Option<Array1<usize>>,
133 pub spatial_correlation: Option<T>,
135}
136
137#[derive(Debug)]
139pub struct GeospatialInterpolator<T>
140where
141 T: Float
142 + FromPrimitive
143 + ToPrimitive
144 + Debug
145 + Display
146 + LowerExp
147 + ScalarOperand
148 + AddAssign
149 + SubAssign
150 + MulAssign
151 + DivAssign
152 + RemAssign
153 + Copy
154 + 'static,
155{
156 config: GeospatialConfig<T>,
158 train_latitudes: Array1<T>,
160 train_longitudes: Array1<T>,
162 train_values: Array1<T>,
164 #[allow(dead_code)]
166 train_elevations: Option<Array1<T>>,
167 interpolator: Option<Box<dyn GeospatialInterpolatorTrait<T>>>,
169 is_trained: bool,
171 spatial_stats: SpatialStats<T>,
173}
174
175trait GeospatialInterpolatorTrait<T>: Debug
177where
178 T: Float + FromPrimitive + ToPrimitive + Debug + Display + Copy + 'static,
179{
180 fn interpolate_spatial(
182 &self,
183 latitudes: &ArrayView1<T>,
184 longitudes: &ArrayView1<T>,
185 ) -> InterpolateResult<GeospatialResult<T>>;
186
187 #[allow(dead_code)]
189 fn get_parameters(&self) -> Vec<(String, String)>;
190}
191
192#[derive(Debug, Clone)]
194pub struct SpatialStats<T> {
195 pub spatial_range_km: Option<T>,
197 pub spatial_variance: Option<T>,
199 pub morans_i: Option<T>,
201 pub effective_dof: Option<T>,
203 pub clustering_index: Option<T>,
205}
206
207impl<T> Default for SpatialStats<T> {
208 fn default() -> Self {
209 Self {
210 spatial_range_km: None,
211 spatial_variance: None,
212 morans_i: None,
213 effective_dof: None,
214 clustering_index: None,
215 }
216 }
217}
218
219impl<T> Default for GeospatialInterpolator<T>
220where
221 T: Float
222 + FromPrimitive
223 + ToPrimitive
224 + Debug
225 + Display
226 + LowerExp
227 + ScalarOperand
228 + AddAssign
229 + SubAssign
230 + MulAssign
231 + DivAssign
232 + RemAssign
233 + Copy
234 + Send
235 + Sync
236 + std::iter::Sum
237 + 'static,
238{
239 fn default() -> Self {
240 Self::new()
241 }
242}
243
244impl<T> GeospatialInterpolator<T>
245where
246 T: Float
247 + FromPrimitive
248 + ToPrimitive
249 + Debug
250 + Display
251 + LowerExp
252 + ScalarOperand
253 + AddAssign
254 + SubAssign
255 + MulAssign
256 + DivAssign
257 + RemAssign
258 + Copy
259 + Send
260 + Sync
261 + std::iter::Sum
262 + 'static,
263{
264 pub fn new() -> Self {
266 Self {
267 config: GeospatialConfig::default(),
268 train_latitudes: Array1::zeros(0),
269 train_longitudes: Array1::zeros(0),
270 train_values: Array1::zeros(0),
271 train_elevations: None,
272 interpolator: None,
273 is_trained: false,
274 spatial_stats: SpatialStats::default(),
275 }
276 }
277
278 pub fn with_coordinate_system(mut self, coordsys: CoordinateSystem) -> Self {
280 self.config.coordinate_system = coordsys;
281 self
282 }
283
284 pub fn with_interpolation_model(mut self, model: InterpolationModel) -> Self {
286 self.config.model = model;
287 self
288 }
289
290 pub fn with_spherical_distance(mut self, usespherical: bool) -> Self {
292 self.config.use_spherical_distance = usespherical;
293 self
294 }
295
296 pub fn with_search_radius_km(mut self, radius: T) -> Self {
298 self.config.search_radius_km = Some(radius);
299 self
300 }
301
302 pub fn with_max_neighbors(mut self, maxneighbors: usize) -> Self {
304 self.config.max_neighbors = Some(maxneighbors);
305 self
306 }
307
308 pub fn with_anisotropy(mut self, angle: T, ratio: T) -> Self {
310 self.config.anisotropy_angle = Some(angle);
311 self.config.anisotropy_ratio = Some(ratio);
312 self
313 }
314
315 pub fn fit(
327 &mut self,
328 latitudes: &ArrayView1<T>,
329 longitudes: &ArrayView1<T>,
330 values: &ArrayView1<T>,
331 ) -> InterpolateResult<()> {
332 if latitudes.len() != longitudes.len() || latitudes.len() != values.len() {
333 return Err(InterpolateError::DimensionMismatch(format!(
334 "latitudes ({}), longitudes ({}), and values ({}) must have the same length",
335 latitudes.len(),
336 longitudes.len(),
337 values.len()
338 )));
339 }
340
341 if latitudes.len() < 3 {
342 return Err(InterpolateError::InvalidValue(
343 "At least 3 data points required for geospatial interpolation".to_string(),
344 ));
345 }
346
347 self.train_latitudes = latitudes.to_owned();
349 self.train_longitudes = longitudes.to_owned();
350 self.train_values = values.to_owned();
351
352 let (x_coords, y_coords) = self.project_coordinates(latitudes, longitudes)?;
354
355 self.compute_spatial_statistics(&x_coords, &y_coords, values)?;
357
358 match self.config.model {
360 InterpolationModel::Kriging => {
361 self.fit_kriging(&x_coords, &y_coords, values)?;
362 }
363 InterpolationModel::SphericalRBF => {
364 self.fit_spherical_rbf(&x_coords, &y_coords, values)?;
365 }
366 InterpolationModel::ThinPlateSpline => {
367 self.fit_thin_plate_spline(&x_coords, &y_coords, values)?;
368 }
369 _ => {
370 self.fit_default_rbf(&x_coords, &y_coords, values)?;
372 }
373 }
374
375 self.is_trained = true;
376 Ok(())
377 }
378
379 pub fn interpolate(
390 &self,
391 latitudes: &ArrayView1<T>,
392 longitudes: &ArrayView1<T>,
393 ) -> InterpolateResult<GeospatialResult<T>> {
394 if !self.is_trained {
395 return Err(InterpolateError::InvalidState(
396 "Interpolator must be fitted before interpolation".to_string(),
397 ));
398 }
399
400 if latitudes.len() != longitudes.len() {
401 return Err(InterpolateError::DimensionMismatch(
402 "latitudes and longitudes must have the same length".to_string(),
403 ));
404 }
405
406 let _x_coords_y_coords = self.project_coordinates(latitudes, longitudes)?;
408
409 if let Some(ref interpolator) = self.interpolator {
411 interpolator.interpolate_spatial(latitudes, longitudes)
412 } else {
413 Err(InterpolateError::InvalidState(
414 "No interpolator has been fitted".to_string(),
415 ))
416 }
417 }
418
419 fn project_coordinates(
421 &self,
422 latitudes: &ArrayView1<T>,
423 longitudes: &ArrayView1<T>,
424 ) -> InterpolateResult<(Array1<T>, Array1<T>)> {
425 match self.config.coordinate_system {
426 CoordinateSystem::WGS84 => {
427 if self.config.use_spherical_distance {
429 let lat_rad = latitudes.mapv(|lat| {
431 lat * T::from_f64(std::f64::consts::PI / 180.0).expect("Operation failed")
432 });
433 let lon_rad = longitudes.mapv(|lon| {
434 lon * T::from_f64(std::f64::consts::PI / 180.0).expect("Operation failed")
435 });
436 Ok((lat_rad, lon_rad))
437 } else {
438 Ok((latitudes.to_owned(), longitudes.to_owned()))
440 }
441 }
442 CoordinateSystem::WebMercator => self.project_to_web_mercator(latitudes, longitudes),
443 CoordinateSystem::LocalCartesian => {
444 self.project_equirectangular(latitudes, longitudes)
446 }
447 _ => {
448 Ok((latitudes.to_owned(), longitudes.to_owned()))
450 }
451 }
452 }
453
454 fn project_to_web_mercator(
456 &self,
457 latitudes: &ArrayView1<T>,
458 longitudes: &ArrayView1<T>,
459 ) -> InterpolateResult<(Array1<T>, Array1<T>)> {
460 let earth_radius =
461 self.config.earth_radius_km * T::from_f64(1000.0).expect("Operation failed"); let deg_to_rad = T::from_f64(std::f64::consts::PI / 180.0).expect("Operation failed");
463
464 let x_coords = longitudes.mapv(|lon| lon * deg_to_rad * earth_radius);
465 let y_coords = latitudes.mapv(|lat| {
466 let lat_rad = lat * deg_to_rad;
467 earth_radius
468 * (lat_rad + T::from_f64(std::f64::consts::PI / 4.0).expect("Operation failed"))
469 .tan()
470 .ln()
471 });
472
473 Ok((x_coords, y_coords))
474 }
475
476 fn project_equirectangular(
478 &self,
479 latitudes: &ArrayView1<T>,
480 longitudes: &ArrayView1<T>,
481 ) -> InterpolateResult<(Array1<T>, Array1<T>)> {
482 let deg_to_rad = T::from_f64(std::f64::consts::PI / 180.0).expect("Operation failed");
483 let earth_radius = self.config.earth_radius_km;
484
485 let mean_lat = latitudes.sum() / T::from_usize(latitudes.len()).expect("Operation failed");
487 let cos_mean_lat = (mean_lat * deg_to_rad).cos();
488
489 let x_coords = longitudes.mapv(|lon| lon * deg_to_rad * earth_radius * cos_mean_lat);
490 let y_coords = latitudes.mapv(|lat| lat * deg_to_rad * earth_radius);
491
492 Ok((x_coords, y_coords))
493 }
494
495 fn compute_spatial_statistics(
497 &mut self,
498 _x_coords: &Array1<T>,
499 _y_coords: &Array1<T>,
500 values: &ArrayView1<T>,
501 ) -> InterpolateResult<()> {
502 let n = values.len();
510 if n > 1 {
511 let mean_val = values.sum() / T::from_usize(n).expect("Operation failed");
512 let variance = values
513 .iter()
514 .map(|&x| (x - mean_val) * (x - mean_val))
515 .sum::<T>()
516 / T::from_usize(n - 1).expect("Operation failed");
517
518 self.spatial_stats.spatial_variance = Some(variance);
519 self.spatial_stats.effective_dof = Some(T::from_usize(n).expect("Operation failed"));
520 }
521
522 Ok(())
523 }
524
525 fn fit_kriging(
527 &mut self,
528 x_coords: &Array1<T>,
529 y_coords: &Array1<T>,
530 values: &ArrayView1<T>,
531 ) -> InterpolateResult<()> {
532 let n = x_coords.len();
534 let mut coords_2d = Array2::zeros((n, 2));
535 for i in 0..n {
536 coords_2d[[i, 0]] = x_coords[i];
537 coords_2d[[i, 1]] = y_coords[i];
538 }
539
540 let kriging = KrigingInterpolator::new(
541 &coords_2d.view(),
542 values,
543 CovarianceFunction::Exponential,
544 T::from_f64(1.0).expect("Operation failed"), T::from_f64(1.0).expect("Operation failed"), T::from_f64(0.01).expect("Operation failed"), T::from_f64(1.0).expect("Operation failed"), )?;
549
550 self.interpolator = Some(Box::new(GeospatialKrigingWrapper { kriging }));
551 Ok(())
552 }
553
554 fn fit_spherical_rbf(
556 &mut self,
557 x_coords: &Array1<T>,
558 y_coords: &Array1<T>,
559 values: &ArrayView1<T>,
560 ) -> InterpolateResult<()> {
561 let n = x_coords.len();
562 let mut coords_2d = Array2::zeros((n, 2));
563 for i in 0..n {
564 coords_2d[[i, 0]] = x_coords[i];
565 coords_2d[[i, 1]] = y_coords[i];
566 }
567
568 let rbf = RBFInterpolator::new(
569 &coords_2d.view(),
570 values,
571 RBFKernel::Gaussian,
572 T::from_f64(1.0).expect("Operation failed"),
573 )?;
574
575 self.interpolator = Some(Box::new(GeospatialRBFWrapper { rbf }));
576 Ok(())
577 }
578
579 fn fit_thin_plate_spline(
581 &mut self,
582 x_coords: &Array1<T>,
583 y_coords: &Array1<T>,
584 values: &ArrayView1<T>,
585 ) -> InterpolateResult<()> {
586 let n = x_coords.len();
587 let mut coords_2d = Array2::zeros((n, 2));
588 for i in 0..n {
589 coords_2d[[i, 0]] = x_coords[i];
590 coords_2d[[i, 1]] = y_coords[i];
591 }
592
593 let tps = ThinPlateSpline::new(
594 &coords_2d.view(),
595 values,
596 T::from_f64(0.0).expect("Operation failed"),
597 )?;
598 self.interpolator = Some(Box::new(GeospatialTPSWrapper { tps }));
599 Ok(())
600 }
601
602 fn fit_default_rbf(
604 &mut self,
605 x_coords: &Array1<T>,
606 y_coords: &Array1<T>,
607 values: &ArrayView1<T>,
608 ) -> InterpolateResult<()> {
609 self.fit_spherical_rbf(x_coords, y_coords, values)
610 }
611
612 pub fn great_circle_distance(&self, lat1: T, lon1: T, lat2: T, lon2: T) -> T {
614 let deg_to_rad = T::from_f64(std::f64::consts::PI / 180.0).expect("Operation failed");
615 let lat1_rad = lat1 * deg_to_rad;
616 let lon1_rad = lon1 * deg_to_rad;
617 let lat2_rad = lat2 * deg_to_rad;
618 let lon2_rad = lon2 * deg_to_rad;
619
620 let dlat = lat2_rad - lat1_rad;
621 let dlon = lon2_rad - lon1_rad;
622
623 let a = (dlat / T::from_f64(2.0).expect("Operation failed"))
624 .sin()
625 .powi(2)
626 + lat1_rad.cos()
627 * lat2_rad.cos()
628 * (dlon / T::from_f64(2.0).expect("Operation failed"))
629 .sin()
630 .powi(2);
631 let c = T::from_f64(2.0).expect("Operation failed") * a.sqrt().asin();
632
633 self.config.earth_radius_km * c
634 }
635
636 pub fn get_spatial_stats(&self) -> &SpatialStats<T> {
638 &self.spatial_stats
639 }
640
641 pub fn is_trained(&self) -> bool {
643 self.is_trained
644 }
645}
646
647#[derive(Debug)]
649struct GeospatialKrigingWrapper<T>
650where
651 T: Float
652 + FromPrimitive
653 + Debug
654 + Display
655 + std::ops::AddAssign
656 + std::ops::SubAssign
657 + 'static,
658{
659 kriging: KrigingInterpolator<T>,
660}
661
662impl<T> GeospatialInterpolatorTrait<T> for GeospatialKrigingWrapper<T>
663where
664 T: Float
665 + FromPrimitive
666 + ToPrimitive
667 + Debug
668 + Display
669 + Copy
670 + std::ops::AddAssign
671 + std::ops::SubAssign
672 + 'static,
673{
674 fn interpolate_spatial(
675 &self,
676 latitudes: &ArrayView1<T>,
677 longitudes: &ArrayView1<T>,
678 ) -> InterpolateResult<GeospatialResult<T>> {
679 let n = latitudes.len();
680 let mut coords_2d = Array2::zeros((n, 2));
681 for i in 0..n {
682 coords_2d[[i, 0]] = latitudes[i];
683 coords_2d[[i, 1]] = longitudes[i];
684 }
685
686 let result = self.kriging.predict(&coords_2d.view())?;
687 let values = result.value;
688
689 Ok(GeospatialResult {
690 values,
691 variance: None,
692 neighbor_counts: None,
693 spatial_correlation: None,
694 })
695 }
696
697 fn get_parameters(&self) -> Vec<(String, String)> {
698 vec![
699 ("model".to_string(), "Kriging".to_string()),
700 ("covariance".to_string(), "Exponential".to_string()),
701 ]
702 }
703}
704
705#[derive(Debug)]
707struct GeospatialRBFWrapper<T>
708where
709 T: Float
710 + FromPrimitive
711 + Debug
712 + Display
713 + std::ops::AddAssign
714 + std::ops::SubAssign
715 + std::ops::MulAssign
716 + 'static,
717{
718 rbf: RBFInterpolator<T>,
719}
720
721impl<T> GeospatialInterpolatorTrait<T> for GeospatialRBFWrapper<T>
722where
723 T: Float
724 + FromPrimitive
725 + ToPrimitive
726 + Debug
727 + Display
728 + LowerExp
729 + Copy
730 + Send
731 + Sync
732 + std::ops::AddAssign
733 + std::ops::SubAssign
734 + std::ops::MulAssign
735 + std::ops::DivAssign
736 + 'static,
737{
738 fn interpolate_spatial(
739 &self,
740 latitudes: &ArrayView1<T>,
741 longitudes: &ArrayView1<T>,
742 ) -> InterpolateResult<GeospatialResult<T>> {
743 let n = latitudes.len();
744 let mut coords_2d = Array2::zeros((n, 2));
745 for i in 0..n {
746 coords_2d[[i, 0]] = latitudes[i];
747 coords_2d[[i, 1]] = longitudes[i];
748 }
749
750 let values = self.rbf.interpolate(&coords_2d.view())?;
751
752 Ok(GeospatialResult {
753 values,
754 variance: None,
755 neighbor_counts: None,
756 spatial_correlation: None,
757 })
758 }
759
760 fn get_parameters(&self) -> Vec<(String, String)> {
761 vec![
762 ("model".to_string(), "RBF".to_string()),
763 ("kernel".to_string(), "Gaussian".to_string()),
764 ]
765 }
766}
767
768#[derive(Debug)]
770struct GeospatialTPSWrapper<T>
771where
772 T: Float
773 + FromPrimitive
774 + Debug
775 + Display
776 + std::ops::AddAssign
777 + std::ops::SubAssign
778 + 'static,
779{
780 tps: ThinPlateSpline<T>,
781}
782
783impl<T> GeospatialInterpolatorTrait<T> for GeospatialTPSWrapper<T>
784where
785 T: Float
786 + FromPrimitive
787 + ToPrimitive
788 + Debug
789 + Display
790 + Copy
791 + std::ops::AddAssign
792 + std::ops::SubAssign
793 + 'static,
794{
795 fn interpolate_spatial(
796 &self,
797 latitudes: &ArrayView1<T>,
798 longitudes: &ArrayView1<T>,
799 ) -> InterpolateResult<GeospatialResult<T>> {
800 let n = latitudes.len();
801 let mut coords_2d = Array2::zeros((n, 2));
802 for i in 0..n {
803 coords_2d[[i, 0]] = latitudes[i];
804 coords_2d[[i, 1]] = longitudes[i];
805 }
806
807 let values = self.tps.evaluate(&coords_2d.view())?;
808
809 Ok(GeospatialResult {
810 values,
811 variance: None,
812 neighbor_counts: None,
813 spatial_correlation: None,
814 })
815 }
816
817 fn get_parameters(&self) -> Vec<(String, String)> {
818 vec![("model".to_string(), "ThinPlateSpline".to_string())]
819 }
820}
821
822#[allow(dead_code)]
824pub fn make_climate_interpolator<T>() -> GeospatialInterpolator<T>
825where
826 T: Float
827 + FromPrimitive
828 + ToPrimitive
829 + Debug
830 + Display
831 + LowerExp
832 + ScalarOperand
833 + AddAssign
834 + SubAssign
835 + MulAssign
836 + DivAssign
837 + RemAssign
838 + Copy
839 + Send
840 + Sync
841 + std::iter::Sum
842 + 'static,
843{
844 GeospatialInterpolator::new()
845 .with_coordinate_system(CoordinateSystem::WGS84)
846 .with_interpolation_model(InterpolationModel::Kriging)
847 .with_spherical_distance(true)
848 .with_max_neighbors(50)
849}
850
851#[allow(dead_code)]
853pub fn make_elevation_interpolator<T>() -> GeospatialInterpolator<T>
854where
855 T: Float
856 + FromPrimitive
857 + ToPrimitive
858 + Debug
859 + Display
860 + LowerExp
861 + ScalarOperand
862 + AddAssign
863 + SubAssign
864 + MulAssign
865 + DivAssign
866 + RemAssign
867 + Copy
868 + Send
869 + Sync
870 + std::iter::Sum
871 + 'static,
872{
873 GeospatialInterpolator::new()
874 .with_coordinate_system(CoordinateSystem::WGS84)
875 .with_interpolation_model(InterpolationModel::ThinPlateSpline)
876 .with_spherical_distance(true)
877 .with_max_neighbors(30)
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883 use scirs2_core::ndarray::Array1;
884
885 #[test]
886 fn test_geospatial_interpolator_creation() {
887 let interpolator = GeospatialInterpolator::<f64>::new();
888 assert!(!interpolator.is_trained());
889 assert_eq!(
890 interpolator.config.coordinate_system,
891 CoordinateSystem::WGS84
892 );
893 assert_eq!(interpolator.config.model, InterpolationModel::Kriging);
894 }
895
896 #[test]
897 fn test_geospatial_interpolator_configuration() {
898 let interpolator = GeospatialInterpolator::<f64>::new()
899 .with_coordinate_system(CoordinateSystem::WebMercator)
900 .with_interpolation_model(InterpolationModel::SphericalRBF)
901 .with_spherical_distance(false)
902 .with_max_neighbors(10);
903
904 assert_eq!(
905 interpolator.config.coordinate_system,
906 CoordinateSystem::WebMercator
907 );
908 assert_eq!(interpolator.config.model, InterpolationModel::SphericalRBF);
909 assert!(!interpolator.config.use_spherical_distance);
910 assert_eq!(interpolator.config.max_neighbors, Some(10));
911 }
912
913 #[test]
914 fn test_geospatial_fitting() {
915 let latitudes = Array1::from_vec(vec![40.0, 41.0, 42.0, 43.0]);
916 let longitudes = Array1::from_vec(vec![-74.0, -75.0, -76.0, -77.0]);
917 let temperatures = Array1::from_vec(vec![15.0, 12.0, 10.0, 8.0]);
918
919 let mut interpolator = GeospatialInterpolator::new()
920 .with_interpolation_model(InterpolationModel::SphericalRBF);
921
922 let result = interpolator.fit(&latitudes.view(), &longitudes.view(), &temperatures.view());
923 assert!(result.is_ok());
924 assert!(interpolator.is_trained());
925 }
926
927 #[test]
928 fn test_geospatial_interpolation() {
929 let latitudes = Array1::from_vec(vec![40.0, 41.0, 42.0, 43.0]);
930 let longitudes = Array1::from_vec(vec![-74.0, -75.0, -76.0, -77.0]);
931 let temperatures = Array1::from_vec(vec![15.0, 12.0, 10.0, 8.0]);
932
933 let mut interpolator = GeospatialInterpolator::new()
934 .with_interpolation_model(InterpolationModel::SphericalRBF);
935
936 interpolator
937 .fit(&latitudes.view(), &longitudes.view(), &temperatures.view())
938 .expect("Operation failed");
939
940 let query_lats = Array1::from_vec(vec![40.5, 41.5]);
941 let query_lons = Array1::from_vec(vec![-74.5, -75.5]);
942 let result = interpolator
943 .interpolate(&query_lats.view(), &query_lons.view())
944 .expect("Operation failed");
945
946 assert_eq!(result.values.len(), 2);
947 assert!(result.values.iter().all(|&v| v.is_finite()));
948 }
949
950 #[test]
951 fn test_great_circle_distance() {
952 let interpolator = GeospatialInterpolator::<f64>::new();
953
954 let nyc_lat = 40.7128;
956 let nyc_lon = -74.0060;
957 let la_lat = 34.0522;
958 let la_lon = -118.2437;
959
960 let distance = interpolator.great_circle_distance(nyc_lat, nyc_lon, la_lat, la_lon);
961
962 assert!((distance - 3935.0).abs() < 400.0);
964 }
965
966 #[test]
967 fn test_coordinate_projection() {
968 let interpolator =
969 GeospatialInterpolator::<f64>::new().with_coordinate_system(CoordinateSystem::WGS84);
970
971 let latitudes = Array1::from_vec(vec![40.0, 41.0]);
972 let longitudes = Array1::from_vec(vec![-74.0, -75.0]);
973
974 let result = interpolator.project_coordinates(&latitudes.view(), &longitudes.view());
975 assert!(result.is_ok());
976
977 let (x_coords, y_coords) = result.expect("Operation failed");
978 assert_eq!(x_coords.len(), 2);
979 assert_eq!(y_coords.len(), 2);
980 }
981
982 #[test]
983 fn test_make_climate_interpolator() {
984 let interpolator = make_climate_interpolator::<f64>();
985 assert_eq!(
986 interpolator.config.coordinate_system,
987 CoordinateSystem::WGS84
988 );
989 assert_eq!(interpolator.config.model, InterpolationModel::Kriging);
990 assert!(interpolator.config.use_spherical_distance);
991 assert_eq!(interpolator.config.max_neighbors, Some(50));
992 }
993
994 #[test]
995 fn test_make_elevation_interpolator() {
996 let interpolator = make_elevation_interpolator::<f64>();
997 assert_eq!(
998 interpolator.config.coordinate_system,
999 CoordinateSystem::WGS84
1000 );
1001 assert_eq!(
1002 interpolator.config.model,
1003 InterpolationModel::ThinPlateSpline
1004 );
1005 assert!(interpolator.config.use_spherical_distance);
1006 assert_eq!(interpolator.config.max_neighbors, Some(30));
1007 }
1008
1009 #[test]
1010 fn test_kriging_model() {
1011 let latitudes = Array1::from_vec(vec![40.0, 41.0, 42.0, 43.0, 44.0]);
1012 let longitudes = Array1::from_vec(vec![-74.0, -75.0, -76.0, -77.0, -78.0]);
1013 let temperatures = Array1::from_vec(vec![15.0, 12.0, 10.0, 8.0, 6.0]);
1014
1015 let mut interpolator =
1016 GeospatialInterpolator::new().with_interpolation_model(InterpolationModel::Kriging);
1017
1018 let fit_result =
1019 interpolator.fit(&latitudes.view(), &longitudes.view(), &temperatures.view());
1020 assert!(fit_result.is_ok());
1021
1022 let query_lats = Array1::from_vec(vec![40.5]);
1023 let query_lons = Array1::from_vec(vec![-74.5]);
1024 let result = interpolator.interpolate(&query_lats.view(), &query_lons.view());
1025 assert!(result.is_ok());
1026 }
1027
1028 #[test]
1029 fn test_thin_plate_spline_model() {
1030 let latitudes = Array1::from_vec(vec![40.0, 41.0, 40.5, 41.5, 40.8]);
1032 let longitudes = Array1::from_vec(vec![-74.0, -74.5, -75.0, -74.2, -74.8]);
1033 let elevations = Array1::from_vec(vec![100.0, 200.0, 150.0, 250.0, 180.0]);
1034
1035 let mut interpolator = GeospatialInterpolator::new()
1036 .with_interpolation_model(InterpolationModel::ThinPlateSpline);
1037
1038 let fit_result =
1039 interpolator.fit(&latitudes.view(), &longitudes.view(), &elevations.view());
1040 assert!(
1041 fit_result.is_ok(),
1042 "Failed to fit thin plate spline: {:?}",
1043 fit_result.err()
1044 );
1045
1046 let query_lats = Array1::from_vec(vec![40.5]);
1047 let query_lons = Array1::from_vec(vec![-74.5]);
1048 let result = interpolator.interpolate(&query_lats.view(), &query_lons.view());
1049 assert!(result.is_ok());
1050 }
1051}