1use crate::config::{ConfigError, LayerConfig};
24use oxigeo_core::buffer::RasterBuffer;
25use oxigeo_core::io::FileDataSource;
26use oxigeo_core::types::{GeoTransform, NoDataValue, RasterDataType};
27use oxigeo_geotiff::GeoTiffReader;
28use oxigeo_proj::{BoundingBox, Coordinate, Crs, Transformer};
29use std::collections::HashMap;
30use std::path::{Path, PathBuf};
31use std::sync::{Arc, RwLock};
32use thiserror::Error;
33use tracing::{debug, info, warn};
34
35pub struct Dataset {
37 path: PathBuf,
39 reader: RwLock<Option<GeoTiffReader<FileDataSource>>>,
41 width: u64,
43 height: u64,
44 band_count: u32,
45 data_type: RasterDataType,
46 geo_transform: Option<GeoTransform>,
47 nodata: NoDataValue,
48 tile_size: Option<(u32, u32)>,
49 overview_count: usize,
50}
51
52impl Dataset {
53 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, oxigeo_core::OxiGeoError> {
55 let path_buf = path.as_ref().to_path_buf();
56
57 let source = FileDataSource::open(&path_buf)?;
59
60 let reader = GeoTiffReader::open(source)?;
62
63 let width = reader.width();
65 let height = reader.height();
66 let band_count = reader.band_count();
67 let data_type = reader.data_type().unwrap_or(RasterDataType::UInt8);
68 let geo_transform = reader.geo_transform().cloned();
69 let nodata = reader.nodata();
70 let tile_size = reader.tile_size();
71 let overview_count = reader.overview_count();
72
73 Ok(Self {
74 path: path_buf,
75 reader: RwLock::new(Some(reader)),
76 width,
77 height,
78 band_count,
79 data_type,
80 geo_transform,
81 nodata,
82 tile_size,
83 overview_count,
84 })
85 }
86
87 #[must_use]
89 pub fn path(&self) -> &Path {
90 &self.path
91 }
92
93 #[must_use]
95 pub fn raster_size(&self) -> (usize, usize) {
96 (self.width as usize, self.height as usize)
97 }
98
99 #[must_use]
101 pub fn width(&self) -> u64 {
102 self.width
103 }
104
105 #[must_use]
107 pub fn height(&self) -> u64 {
108 self.height
109 }
110
111 #[must_use]
113 pub fn raster_count(&self) -> usize {
114 self.band_count as usize
115 }
116
117 #[must_use]
119 pub fn data_type(&self) -> RasterDataType {
120 self.data_type
121 }
122
123 pub fn projection(&self) -> Result<String, oxigeo_core::OxiGeoError> {
125 if let Ok(guard) = self.reader.read()
127 && let Some(ref reader) = *guard
128 && let Some(epsg) = reader.epsg_code()
129 {
130 return Ok(format!("EPSG:{}", epsg));
131 }
132 Ok("EPSG:4326".to_string())
133 }
134
135 pub fn geotransform(&self) -> Result<[f64; 6], oxigeo_core::OxiGeoError> {
137 if let Some(ref gt) = self.geo_transform {
138 Ok(gt.to_gdal_array())
139 } else {
140 Ok([0.0, 1.0, 0.0, 0.0, 0.0, -1.0])
142 }
143 }
144
145 #[must_use]
147 pub fn geo_transform_obj(&self) -> Option<&GeoTransform> {
148 self.geo_transform.as_ref()
149 }
150
151 #[must_use]
153 pub fn nodata(&self) -> NoDataValue {
154 self.nodata
155 }
156
157 #[must_use]
159 pub fn tile_size(&self) -> Option<(u32, u32)> {
160 self.tile_size
161 }
162
163 #[must_use]
165 pub fn overview_count(&self) -> usize {
166 self.overview_count
167 }
168
169 #[must_use]
171 pub fn bounds(&self) -> Option<oxigeo_core::types::BoundingBox> {
172 self.geo_transform
173 .as_ref()
174 .map(|gt| gt.compute_bounds(self.width, self.height))
175 }
176
177 pub fn read_tile(
184 &self,
185 level: usize,
186 tile_x: u32,
187 tile_y: u32,
188 ) -> Result<Vec<u8>, oxigeo_core::OxiGeoError> {
189 let guard = self
190 .reader
191 .read()
192 .map_err(|_| oxigeo_core::OxiGeoError::Internal {
193 message: "Failed to acquire read lock".to_string(),
194 })?;
195
196 if let Some(ref reader) = *guard {
197 reader.read_tile(level, tile_x, tile_y)
198 } else {
199 Err(oxigeo_core::OxiGeoError::Internal {
200 message: "Reader not initialized".to_string(),
201 })
202 }
203 }
204
205 pub fn level_size(&self, level: usize) -> Result<(u64, u64), oxigeo_core::OxiGeoError> {
218 let guard = self
219 .reader
220 .read()
221 .map_err(|_| oxigeo_core::OxiGeoError::Internal {
222 message: "Failed to acquire read lock".to_string(),
223 })?;
224
225 if let Some(ref reader) = *guard {
226 reader.level_size(level)
227 } else {
228 Err(oxigeo_core::OxiGeoError::Internal {
229 message: "Reader not initialized".to_string(),
230 })
231 }
232 }
233
234 pub fn read_tile_buffer(
238 &self,
239 level: usize,
240 tile_x: u32,
241 tile_y: u32,
242 ) -> Result<RasterBuffer, oxigeo_core::OxiGeoError> {
243 self.read_tile_band_buffer(level, 0, tile_x, tile_y)
244 }
245
246 pub fn read_tile_band_buffer(
259 &self,
260 level: usize,
261 band: usize,
262 tile_x: u32,
263 tile_y: u32,
264 ) -> Result<RasterBuffer, oxigeo_core::OxiGeoError> {
265 let guard = self
266 .reader
267 .read()
268 .map_err(|_| oxigeo_core::OxiGeoError::Internal {
269 message: "Failed to acquire read lock".to_string(),
270 })?;
271
272 if let Some(ref reader) = *guard {
273 reader.read_tile_band_buffer(level, band, tile_x, tile_y)
274 } else {
275 Err(oxigeo_core::OxiGeoError::Internal {
276 message: "Reader not initialized".to_string(),
277 })
278 }
279 }
280
281 pub fn read_band(
283 &self,
284 level: usize,
285 band: usize,
286 ) -> Result<Vec<u8>, oxigeo_core::OxiGeoError> {
287 let guard = self
288 .reader
289 .read()
290 .map_err(|_| oxigeo_core::OxiGeoError::Internal {
291 message: "Failed to acquire read lock".to_string(),
292 })?;
293
294 if let Some(ref reader) = *guard {
295 reader.read_band(level, band)
296 } else {
297 Err(oxigeo_core::OxiGeoError::Internal {
298 message: "Reader not initialized".to_string(),
299 })
300 }
301 }
302
303 pub fn read_window(
313 &self,
314 x_offset: u64,
315 y_offset: u64,
316 x_size: u64,
317 y_size: u64,
318 ) -> Result<RasterBuffer, oxigeo_core::OxiGeoError> {
319 self.read_band_window(0, 0, x_offset, y_offset, x_size, y_size)
320 }
321
322 pub fn read_band_window(
349 &self,
350 level: usize,
351 band: usize,
352 x_offset: u64,
353 y_offset: u64,
354 x_size: u64,
355 y_size: u64,
356 ) -> Result<RasterBuffer, oxigeo_core::OxiGeoError> {
357 let (level_width, level_height) = self.level_size(level)?;
360
361 if x_offset >= level_width || y_offset >= level_height {
363 return Err(oxigeo_core::OxiGeoError::OutOfBounds {
364 message: format!(
365 "Window offset ({}, {}) out of bounds ({}x{}) at level {}",
366 x_offset, y_offset, level_width, level_height, level
367 ),
368 });
369 }
370
371 let actual_x_size = x_size.min(level_width - x_offset);
373 let actual_y_size = y_size.min(level_height - y_offset);
374
375 let mut window_buffer = RasterBuffer::zeros(x_size, y_size, self.data_type);
376 if actual_x_size == 0 || actual_y_size == 0 {
377 return Ok(window_buffer);
378 }
379
380 let guard = self
381 .reader
382 .read()
383 .map_err(|_| oxigeo_core::OxiGeoError::Internal {
384 message: "Failed to acquire read lock".to_string(),
385 })?;
386 let reader = guard
387 .as_ref()
388 .ok_or_else(|| oxigeo_core::OxiGeoError::Internal {
389 message: "Reader not initialized".to_string(),
390 })?;
391
392 let bytes = reader.read_window(
393 level,
394 band,
395 x_offset,
396 y_offset,
397 actual_x_size,
398 actual_y_size,
399 )?;
400
401 let plane = RasterBuffer::new(
402 bytes,
403 actual_x_size,
404 actual_y_size,
405 self.data_type,
406 self.nodata,
407 )?;
408
409 if actual_x_size == x_size && actual_y_size == y_size {
410 return Ok(plane);
411 }
412
413 for y in 0..actual_y_size {
414 for x in 0..actual_x_size {
415 let value = plane.get_pixel(x, y)?;
416 window_buffer.set_pixel(x, y, value)?;
417 }
418 }
419
420 Ok(window_buffer)
421 }
422
423 #[must_use]
445 pub fn select_overview_level(
446 &self,
447 src_width: u64,
448 src_height: u64,
449 target_width: u64,
450 target_height: u64,
451 ) -> usize {
452 if self.overview_count == 0 {
453 return 0;
454 }
455
456 let ratio_x = if target_width > 0 {
457 src_width as f64 / target_width as f64
458 } else {
459 1.0
460 };
461 let ratio_y = if target_height > 0 {
462 src_height as f64 / target_height as f64
463 } else {
464 1.0
465 };
466 let request_ratio = ratio_x.max(ratio_y);
467 if request_ratio <= 1.0 {
468 return 0;
470 }
471
472 let Ok((base_width, base_height)) = self.level_size(0) else {
473 return 0;
474 };
475 if base_width == 0 || base_height == 0 {
476 return 0;
477 }
478
479 let mut best_level = 0;
480 for level in 1..=self.overview_count {
481 let Ok((level_width, level_height)) = self.level_size(level) else {
482 break;
483 };
484 if level_width == 0 || level_height == 0 {
485 break;
486 }
487 let factor = (base_width as f64 / level_width as f64)
488 .max(base_height as f64 / level_height as f64);
489 if factor <= request_ratio * 1.5 {
492 best_level = level;
493 } else {
494 break;
495 }
496 }
497
498 best_level
499 }
500
501 pub fn window_at_level(
512 &self,
513 level: usize,
514 x: u64,
515 y: u64,
516 width: u64,
517 height: u64,
518 ) -> Result<(u64, u64, u64, u64), oxigeo_core::OxiGeoError> {
519 let (level_width, level_height) = self.level_size(level)?;
520 if level_width == 0 || level_height == 0 {
521 return Err(oxigeo_core::OxiGeoError::OutOfBounds {
522 message: format!("Level {level} declares a zero-sized image"),
523 });
524 }
525
526 let (base_width, base_height) = self.level_size(0)?;
527 let scale_x = if base_width > 0 {
528 level_width as f64 / base_width as f64
529 } else {
530 1.0
531 };
532 let scale_y = if base_height > 0 {
533 level_height as f64 / base_height as f64
534 } else {
535 1.0
536 };
537
538 let scaled_x = ((x as f64) * scale_x).floor().max(0.0) as u64;
539 let scaled_y = ((y as f64) * scale_y).floor().max(0.0) as u64;
540 let scaled_x = scaled_x.min(level_width - 1);
541 let scaled_y = scaled_y.min(level_height - 1);
542
543 let scaled_width = (((width as f64) * scale_x).ceil().max(1.0) as u64)
544 .min(level_width - scaled_x)
545 .max(1);
546 let scaled_height = (((height as f64) * scale_y).ceil().max(1.0) as u64)
547 .min(level_height - scaled_y)
548 .max(1);
549
550 Ok((scaled_x, scaled_y, scaled_width, scaled_height))
551 }
552
553 pub fn get_pixel(&self, x: u64, y: u64) -> Result<f64, oxigeo_core::OxiGeoError> {
561 if x >= self.width || y >= self.height {
562 return Err(oxigeo_core::OxiGeoError::OutOfBounds {
563 message: format!(
564 "Pixel ({}, {}) out of bounds ({}x{})",
565 x, y, self.width, self.height
566 ),
567 });
568 }
569
570 self.read_band_window(0, 0, x, y, 1, 1)?.get_pixel(0, 0)
571 }
572
573 pub fn rasterband(&self, _band: usize) -> Result<RasterBandInfo, oxigeo_core::OxiGeoError> {
575 Ok(RasterBandInfo {
576 nodata: self.nodata,
577 data_type: self.data_type,
578 })
579 }
580}
581
582pub struct RasterBandInfo {
584 nodata: NoDataValue,
585 data_type: RasterDataType,
586}
587
588impl RasterBandInfo {
589 pub fn nodata(&self) -> Option<f64> {
591 self.nodata.as_f64()
592 }
593
594 pub fn datatype(&self) -> &str {
596 match self.data_type {
597 RasterDataType::UInt8 => "UInt8",
598 RasterDataType::Int8 => "Int8",
599 RasterDataType::UInt16 => "UInt16",
600 RasterDataType::Int16 => "Int16",
601 RasterDataType::UInt32 => "UInt32",
602 RasterDataType::Int32 => "Int32",
603 RasterDataType::Float32 => "Float32",
604 RasterDataType::Float64 => "Float64",
605 RasterDataType::UInt64 => "UInt64",
606 RasterDataType::Int64 => "Int64",
607 RasterDataType::CFloat32 => "CFloat32",
608 RasterDataType::CFloat64 => "CFloat64",
609 }
610 }
611}
612
613#[derive(Debug, Error)]
615pub enum RegistryError {
616 #[error("Layer not found: {0}")]
618 LayerNotFound(String),
619
620 #[error("Failed to open dataset: {0}")]
622 DatasetOpen(#[from] oxigeo_core::OxiGeoError),
623
624 #[error("Configuration error: {0}")]
626 Config(#[from] ConfigError),
627
628 #[error("CRS transformation failed: {0}")]
630 CrsTransformation(String),
631
632 #[error("Invalid CRS: {0}")]
634 InvalidCrs(String),
635
636 #[error("Lock poisoned")]
638 LockPoisoned,
639}
640
641impl From<oxigeo_proj::Error> for RegistryError {
642 fn from(err: oxigeo_proj::Error) -> Self {
643 RegistryError::CrsTransformation(err.to_string())
644 }
645}
646
647pub type RegistryResult<T> = Result<T, RegistryError>;
649
650#[derive(Debug, Clone)]
652pub struct LayerInfo {
653 pub name: String,
655
656 pub title: String,
658
659 pub abstract_: String,
661
662 pub config: LayerConfig,
664
665 pub metadata: DatasetMetadata,
667}
668
669#[derive(Debug, Clone)]
671pub struct DatasetMetadata {
672 pub width: usize,
674
675 pub height: usize,
677
678 pub band_count: usize,
680
681 pub data_type: String,
683
684 pub srs: Option<String>,
686
687 pub bbox: Option<(f64, f64, f64, f64)>,
689
690 pub geotransform: Option<[f64; 6]>,
692
693 pub nodata: Option<f64>,
695}
696
697pub struct DatasetRegistry {
699 layers: Arc<RwLock<HashMap<String, LayerInfo>>>,
701
702 datasets: Arc<RwLock<HashMap<String, Arc<Dataset>>>>,
704}
705
706impl DatasetRegistry {
707 pub fn new() -> Self {
709 Self {
710 layers: Arc::new(RwLock::new(HashMap::new())),
711 datasets: Arc::new(RwLock::new(HashMap::new())),
712 }
713 }
714
715 pub fn register_layer(&self, config: LayerConfig) -> RegistryResult<()> {
717 info!("Registering layer: {}", config.name);
718
719 let layer_name = config.name.clone();
721
722 let dataset = Self::open_dataset(&config.path)?;
724 let metadata = Self::extract_metadata(&dataset)?;
725
726 debug!(
727 "Layer {} metadata: {}x{}, {} bands",
728 layer_name, metadata.width, metadata.height, metadata.band_count
729 );
730
731 let layer_info = LayerInfo {
732 name: config.name.clone(),
733 title: config.title.clone().unwrap_or_else(|| config.name.clone()),
734 abstract_: config
735 .abstract_
736 .clone()
737 .unwrap_or_else(|| format!("Layer {}", config.name)),
738 config,
739 metadata,
740 };
741
742 let mut layers = self
744 .layers
745 .write()
746 .map_err(|_| RegistryError::LockPoisoned)?;
747 layers.insert(layer_info.name.clone(), layer_info);
748
749 let mut datasets = self
751 .datasets
752 .write()
753 .map_err(|_| RegistryError::LockPoisoned)?;
754 datasets.insert(layer_name, Arc::new(dataset));
755
756 Ok(())
757 }
758
759 pub fn register_layers(&self, configs: Vec<LayerConfig>) -> RegistryResult<()> {
761 for config in configs {
762 if !config.enabled {
763 debug!("Skipping disabled layer: {}", config.name);
764 continue;
765 }
766
767 if let Err(e) = self.register_layer(config) {
768 warn!("Failed to register layer: {}", e);
769 }
771 }
772 Ok(())
773 }
774
775 pub fn get_layer(&self, name: &str) -> RegistryResult<LayerInfo> {
777 let layers = self
778 .layers
779 .read()
780 .map_err(|_| RegistryError::LockPoisoned)?;
781
782 layers
783 .get(name)
784 .cloned()
785 .ok_or_else(|| RegistryError::LayerNotFound(name.to_string()))
786 }
787
788 pub fn get_dataset(&self, name: &str) -> RegistryResult<Arc<Dataset>> {
790 let datasets = self
791 .datasets
792 .read()
793 .map_err(|_| RegistryError::LockPoisoned)?;
794
795 datasets
796 .get(name)
797 .cloned()
798 .ok_or_else(|| RegistryError::LayerNotFound(name.to_string()))
799 }
800
801 pub fn list_layers(&self) -> RegistryResult<Vec<LayerInfo>> {
803 let layers = self
804 .layers
805 .read()
806 .map_err(|_| RegistryError::LockPoisoned)?;
807
808 Ok(layers.values().cloned().collect())
809 }
810
811 pub fn has_layer(&self, name: &str) -> bool {
813 self.layers
814 .read()
815 .map(|layers| layers.contains_key(name))
816 .unwrap_or(false)
817 }
818
819 pub fn unregister_layer(&self, name: &str) -> RegistryResult<()> {
821 let mut layers = self
822 .layers
823 .write()
824 .map_err(|_| RegistryError::LockPoisoned)?;
825
826 let mut datasets = self
827 .datasets
828 .write()
829 .map_err(|_| RegistryError::LockPoisoned)?;
830
831 layers.remove(name);
832 datasets.remove(name);
833
834 info!("Unregistered layer: {}", name);
835 Ok(())
836 }
837
838 pub fn layer_count(&self) -> usize {
840 self.layers.read().map(|l| l.len()).unwrap_or(0)
841 }
842
843 fn open_dataset<P: AsRef<Path>>(path: P) -> RegistryResult<Dataset> {
845 let dataset = Dataset::open(path.as_ref())?;
846 Ok(dataset)
847 }
848
849 fn extract_metadata(dataset: &Dataset) -> RegistryResult<DatasetMetadata> {
851 let raster_size = dataset.raster_size();
852 let band_count = dataset.raster_count();
853
854 let srs = dataset.projection().ok();
856
857 let geotransform = dataset.geotransform().ok();
859 let bbox = geotransform.map(|gt| {
860 let width = raster_size.0 as f64;
861 let height = raster_size.1 as f64;
862
863 let min_x = gt[0];
864 let max_x = gt[0] + gt[1] * width + gt[2] * height;
865 let max_y = gt[3];
866 let min_y = gt[3] + gt[4] * width + gt[5] * height;
867
868 let (min_x, max_x) = if min_x < max_x {
870 (min_x, max_x)
871 } else {
872 (max_x, min_x)
873 };
874 let (min_y, max_y) = if min_y < max_y {
875 (min_y, max_y)
876 } else {
877 (max_y, min_y)
878 };
879
880 (min_x, min_y, max_x, max_y)
881 });
882
883 let nodata = if band_count > 0 {
885 dataset.rasterband(1).ok().and_then(|band| band.nodata())
886 } else {
887 None
888 };
889
890 let data_type = if band_count > 0 {
892 dataset
893 .rasterband(1)
894 .ok()
895 .map(|band| format!("{:?}", band.datatype()))
896 .unwrap_or_else(|| "Unknown".to_string())
897 } else {
898 "Unknown".to_string()
899 };
900
901 Ok(DatasetMetadata {
902 width: raster_size.0,
903 height: raster_size.1,
904 band_count,
905 data_type,
906 srs,
907 bbox,
908 geotransform,
909 nodata,
910 })
911 }
912
913 pub fn reload_layer(&self, name: &str) -> RegistryResult<()> {
915 let config = {
916 let layers = self
917 .layers
918 .read()
919 .map_err(|_| RegistryError::LockPoisoned)?;
920
921 layers
922 .get(name)
923 .ok_or_else(|| RegistryError::LayerNotFound(name.to_string()))?
924 .config
925 .clone()
926 };
927
928 self.unregister_layer(name)?;
929 self.register_layer(config)?;
930
931 info!("Reloaded layer: {}", name);
932 Ok(())
933 }
934
935 pub fn get_layer_bbox(
966 &self,
967 name: &str,
968 target_crs: Option<&str>,
969 ) -> RegistryResult<Option<(f64, f64, f64, f64)>> {
970 let layer = self.get_layer(name)?;
971
972 let target_crs_str = match target_crs {
974 Some(crs) => crs,
975 None => return Ok(layer.metadata.bbox),
976 };
977
978 let native_bbox = match layer.metadata.bbox {
980 Some(bbox) => bbox,
981 None => return Ok(None),
982 };
983
984 let source_crs_str = layer.metadata.srs.as_deref().unwrap_or("EPSG:4326");
986
987 if Self::crs_strings_equivalent(source_crs_str, target_crs_str) {
989 return Ok(Some(native_bbox));
990 }
991
992 let source_crs = Self::parse_crs(source_crs_str)?;
994 let target_crs = Self::parse_crs(target_crs_str)?;
995
996 let transformed =
998 Self::transform_bbox_with_densification(native_bbox, &source_crs, &target_crs)?;
999
1000 Ok(Some(transformed))
1001 }
1002
1003 fn parse_crs(crs_str: &str) -> RegistryResult<Crs> {
1010 let trimmed = crs_str.trim();
1011
1012 if let Some(code_str) = trimmed.to_uppercase().strip_prefix("EPSG:") {
1014 let code: u32 = code_str.parse().map_err(|_| {
1015 RegistryError::InvalidCrs(format!("Invalid EPSG code: {}", code_str))
1016 })?;
1017 return Ok(Crs::from_epsg(code)?);
1018 }
1019
1020 if trimmed.starts_with("+proj=") || trimmed.contains("+proj=") {
1022 return Ok(Crs::from_proj(trimmed)?);
1023 }
1024
1025 if trimmed.starts_with("GEOGCS[")
1027 || trimmed.starts_with("PROJCS[")
1028 || trimmed.starts_with("GEOCCS[")
1029 {
1030 return Ok(Crs::from_wkt(trimmed)?);
1031 }
1032
1033 Err(RegistryError::InvalidCrs(format!(
1034 "Unrecognized CRS format: {}",
1035 crs_str
1036 )))
1037 }
1038
1039 fn crs_strings_equivalent(crs1: &str, crs2: &str) -> bool {
1041 let norm1 = crs1.trim().to_uppercase();
1042 let norm2 = crs2.trim().to_uppercase();
1043 norm1 == norm2
1044 }
1045
1046 fn transform_bbox_with_densification(
1063 bbox: (f64, f64, f64, f64),
1064 source_crs: &Crs,
1065 target_crs: &Crs,
1066 ) -> RegistryResult<(f64, f64, f64, f64)> {
1067 let (min_x, min_y, max_x, max_y) = bbox;
1068
1069 let transformer = Transformer::new(source_crs.clone(), target_crs.clone())?;
1071
1072 const DENSIFY_POINTS: usize = 21;
1076
1077 let edge_points = Self::densify_bbox_edges(min_x, min_y, max_x, max_y, DENSIFY_POINTS);
1079
1080 let transformed_points: Vec<Coordinate> = edge_points
1082 .iter()
1083 .filter_map(|coord| transformer.transform(coord).ok())
1084 .collect();
1085
1086 if transformed_points.is_empty() {
1088 return Err(RegistryError::CrsTransformation(
1089 "All points failed to transform".to_string(),
1090 ));
1091 }
1092
1093 let mut result_min_x = f64::INFINITY;
1095 let mut result_min_y = f64::INFINITY;
1096 let mut result_max_x = f64::NEG_INFINITY;
1097 let mut result_max_y = f64::NEG_INFINITY;
1098
1099 for point in &transformed_points {
1100 if point.x.is_finite() && point.y.is_finite() {
1101 result_min_x = result_min_x.min(point.x);
1102 result_min_y = result_min_y.min(point.y);
1103 result_max_x = result_max_x.max(point.x);
1104 result_max_y = result_max_y.max(point.y);
1105 }
1106 }
1107
1108 if !result_min_x.is_finite()
1110 || !result_min_y.is_finite()
1111 || !result_max_x.is_finite()
1112 || !result_max_y.is_finite()
1113 {
1114 return Err(RegistryError::CrsTransformation(
1115 "Transformation resulted in non-finite values".to_string(),
1116 ));
1117 }
1118
1119 Ok((result_min_x, result_min_y, result_max_x, result_max_y))
1120 }
1121
1122 fn densify_bbox_edges(
1127 min_x: f64,
1128 min_y: f64,
1129 max_x: f64,
1130 max_y: f64,
1131 points_per_edge: usize,
1132 ) -> Vec<Coordinate> {
1133 let mut points = Vec::with_capacity(points_per_edge * 4);
1134
1135 let n = points_per_edge.max(2);
1137
1138 for i in 0..n {
1140 let t = i as f64 / (n - 1) as f64;
1141 let x = min_x + t * (max_x - min_x);
1142 points.push(Coordinate::new(x, min_y));
1143 }
1144
1145 for i in 1..n {
1148 let t = i as f64 / (n - 1) as f64;
1149 let y = min_y + t * (max_y - min_y);
1150 points.push(Coordinate::new(max_x, y));
1151 }
1152
1153 for i in 1..n {
1156 let t = i as f64 / (n - 1) as f64;
1157 let x = max_x - t * (max_x - min_x);
1158 points.push(Coordinate::new(x, max_y));
1159 }
1160
1161 for i in 1..(n - 1) {
1164 let t = i as f64 / (n - 1) as f64;
1165 let y = max_y - t * (max_y - min_y);
1166 points.push(Coordinate::new(min_x, y));
1167 }
1168
1169 points
1170 }
1171
1172 #[allow(dead_code)]
1176 fn transform_point(
1177 x: f64,
1178 y: f64,
1179 source_crs: &Crs,
1180 target_crs: &Crs,
1181 ) -> RegistryResult<(f64, f64)> {
1182 let transformer = Transformer::new(source_crs.clone(), target_crs.clone())?;
1183 let coord = Coordinate::new(x, y);
1184 let transformed = transformer.transform(&coord)?;
1185 Ok((transformed.x, transformed.y))
1186 }
1187
1188 pub fn get_layer_bbox_web_mercator(
1192 &self,
1193 name: &str,
1194 ) -> RegistryResult<Option<(f64, f64, f64, f64)>> {
1195 self.get_layer_bbox(name, Some("EPSG:3857"))
1196 }
1197
1198 pub fn get_layer_bbox_wgs84(&self, name: &str) -> RegistryResult<Option<(f64, f64, f64, f64)>> {
1202 self.get_layer_bbox(name, Some("EPSG:4326"))
1203 }
1204
1205 #[allow(dead_code)]
1210 fn transform_bbox_simple(
1211 bbox: (f64, f64, f64, f64),
1212 source_crs: &Crs,
1213 target_crs: &Crs,
1214 ) -> RegistryResult<(f64, f64, f64, f64)> {
1215 let (min_x, min_y, max_x, max_y) = bbox;
1216
1217 let source_bbox = BoundingBox::new(min_x, min_y, max_x, max_y)
1219 .map_err(|e| RegistryError::CrsTransformation(e.to_string()))?;
1220
1221 let transformer = Transformer::new(source_crs.clone(), target_crs.clone())?;
1223 let transformed = transformer.transform_bbox(&source_bbox)?;
1224
1225 Ok((
1226 transformed.min_x,
1227 transformed.min_y,
1228 transformed.max_x,
1229 transformed.max_y,
1230 ))
1231 }
1232}
1233
1234impl Default for DatasetRegistry {
1235 fn default() -> Self {
1236 Self::new()
1237 }
1238}
1239
1240impl Clone for DatasetRegistry {
1241 fn clone(&self) -> Self {
1242 Self {
1243 layers: Arc::clone(&self.layers),
1244 datasets: Arc::clone(&self.datasets),
1245 }
1246 }
1247}
1248
1249#[cfg(test)]
1250mod tests {
1251 use super::*;
1252
1253 #[test]
1254 fn test_registry_creation() {
1255 let registry = DatasetRegistry::new();
1256 assert_eq!(registry.layer_count(), 0);
1257 assert!(!registry.has_layer("test"));
1258 }
1259
1260 #[test]
1261 fn test_layer_not_found() {
1262 let registry = DatasetRegistry::new();
1263 let result = registry.get_layer("nonexistent");
1264 assert!(matches!(result, Err(RegistryError::LayerNotFound(_))));
1265 }
1266
1267 #[test]
1268 fn test_registry_clone() {
1269 let registry1 = DatasetRegistry::new();
1270 let registry2 = registry1.clone();
1271
1272 assert_eq!(registry1.layer_count(), registry2.layer_count());
1273 }
1274
1275 #[test]
1277 fn test_parse_crs_epsg_uppercase() {
1278 let crs = DatasetRegistry::parse_crs("EPSG:4326");
1279 assert!(crs.is_ok());
1280 let crs = crs.expect("should parse");
1281 assert_eq!(crs.epsg_code(), Some(4326));
1282 }
1283
1284 #[test]
1285 fn test_parse_crs_epsg_lowercase() {
1286 let crs = DatasetRegistry::parse_crs("epsg:3857");
1287 assert!(crs.is_ok());
1288 let crs = crs.expect("should parse");
1289 assert_eq!(crs.epsg_code(), Some(3857));
1290 }
1291
1292 #[test]
1293 fn test_parse_crs_proj_string() {
1294 let crs = DatasetRegistry::parse_crs("+proj=longlat +datum=WGS84 +no_defs");
1295 assert!(crs.is_ok());
1296 }
1297
1298 #[test]
1299 fn test_parse_crs_wkt() {
1300 let wkt = r#"GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]]]"#;
1301 let crs = DatasetRegistry::parse_crs(wkt);
1302 assert!(crs.is_ok());
1303 }
1304
1305 #[test]
1306 fn test_parse_crs_invalid() {
1307 let crs = DatasetRegistry::parse_crs("invalid crs");
1308 assert!(matches!(crs, Err(RegistryError::InvalidCrs(_))));
1309 }
1310
1311 #[test]
1312 fn test_parse_crs_invalid_epsg() {
1313 let crs = DatasetRegistry::parse_crs("EPSG:abc");
1314 assert!(matches!(crs, Err(RegistryError::InvalidCrs(_))));
1315 }
1316
1317 #[test]
1319 fn test_crs_strings_equivalent_same() {
1320 assert!(DatasetRegistry::crs_strings_equivalent(
1321 "EPSG:4326",
1322 "EPSG:4326"
1323 ));
1324 }
1325
1326 #[test]
1327 fn test_crs_strings_equivalent_case_insensitive() {
1328 assert!(DatasetRegistry::crs_strings_equivalent(
1329 "EPSG:4326",
1330 "epsg:4326"
1331 ));
1332 assert!(DatasetRegistry::crs_strings_equivalent(
1333 "epsg:3857",
1334 "EPSG:3857"
1335 ));
1336 }
1337
1338 #[test]
1339 fn test_crs_strings_equivalent_with_whitespace() {
1340 assert!(DatasetRegistry::crs_strings_equivalent(
1341 " EPSG:4326 ",
1342 "EPSG:4326"
1343 ));
1344 }
1345
1346 #[test]
1347 fn test_crs_strings_not_equivalent() {
1348 assert!(!DatasetRegistry::crs_strings_equivalent(
1349 "EPSG:4326",
1350 "EPSG:3857"
1351 ));
1352 }
1353
1354 #[test]
1356 fn test_densify_bbox_edges_minimum() {
1357 let points = DatasetRegistry::densify_bbox_edges(0.0, 0.0, 10.0, 10.0, 2);
1358 assert_eq!(points.len(), 4);
1361
1362 assert!(
1364 points
1365 .iter()
1366 .any(|p| (p.x - 0.0).abs() < 1e-10 && (p.y - 0.0).abs() < 1e-10)
1367 );
1368 assert!(
1369 points
1370 .iter()
1371 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - 0.0).abs() < 1e-10)
1372 );
1373 assert!(
1374 points
1375 .iter()
1376 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10)
1377 );
1378 assert!(
1379 points
1380 .iter()
1381 .any(|p| (p.x - 0.0).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10)
1382 );
1383 }
1384
1385 #[test]
1386 fn test_densify_bbox_edges_5_points() {
1387 let points = DatasetRegistry::densify_bbox_edges(0.0, 0.0, 10.0, 10.0, 5);
1388 assert_eq!(points.len(), 16);
1390 }
1391
1392 #[test]
1393 fn test_densify_bbox_edges_21_points() {
1394 let points = DatasetRegistry::densify_bbox_edges(-10.0, -10.0, 10.0, 10.0, 21);
1395 assert_eq!(points.len(), 80);
1397
1398 let has_bottom_left = points
1400 .iter()
1401 .any(|p| (p.x - (-10.0)).abs() < 1e-10 && (p.y - (-10.0)).abs() < 1e-10);
1402 let has_bottom_right = points
1403 .iter()
1404 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - (-10.0)).abs() < 1e-10);
1405 let has_top_right = points
1406 .iter()
1407 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10);
1408 let has_top_left = points
1409 .iter()
1410 .any(|p| (p.x - (-10.0)).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10);
1411
1412 assert!(has_bottom_left, "Should have bottom-left corner");
1413 assert!(has_bottom_right, "Should have bottom-right corner");
1414 assert!(has_top_right, "Should have top-right corner");
1415 assert!(has_top_left, "Should have top-left corner");
1416 }
1417
1418 #[test]
1420 fn test_transform_bbox_same_crs() {
1421 let source_crs = Crs::wgs84();
1422 let target_crs = Crs::wgs84();
1423 let bbox = (0.0, 0.0, 10.0, 10.0);
1424
1425 let result =
1426 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1427 assert!(result.is_ok());
1428
1429 let (min_x, min_y, max_x, max_y) = result.expect("should transform");
1430 assert!((min_x - 0.0).abs() < 1e-6);
1431 assert!((min_y - 0.0).abs() < 1e-6);
1432 assert!((max_x - 10.0).abs() < 1e-6);
1433 assert!((max_y - 10.0).abs() < 1e-6);
1434 }
1435
1436 #[test]
1437 fn test_transform_bbox_wgs84_to_web_mercator() {
1438 let source_crs = Crs::wgs84();
1439 let target_crs = Crs::web_mercator();
1440
1441 let bbox = (-1.0, -1.0, 1.0, 1.0);
1443
1444 let result =
1445 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1446 assert!(result.is_ok());
1447
1448 let (min_x, min_y, max_x, max_y) = result.expect("should transform");
1449
1450 assert!(min_x < 0.0, "min_x should be negative");
1453 assert!(min_y < 0.0, "min_y should be negative");
1454 assert!(max_x > 0.0, "max_x should be positive");
1455 assert!(max_y > 0.0, "max_y should be positive");
1456
1457 assert!(min_x > -200_000.0, "min_x should be > -200000");
1459 assert!(max_x < 200_000.0, "max_x should be < 200000");
1460 assert!(min_y > -200_000.0, "min_y should be > -200000");
1461 assert!(max_y < 200_000.0, "max_y should be < 200000");
1462 }
1463
1464 #[test]
1465 fn test_transform_bbox_web_mercator_to_wgs84() {
1466 let source_crs = Crs::web_mercator();
1467 let target_crs = Crs::wgs84();
1468
1469 let bbox = (-1_000_000.0, -1_000_000.0, 1_000_000.0, 1_000_000.0);
1471
1472 let result =
1473 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1474 assert!(result.is_ok());
1475
1476 let (min_x, min_y, max_x, max_y) = result.expect("should transform");
1477
1478 assert!(
1480 min_x > -15.0 && min_x < -5.0,
1481 "min_x should be around -9 degrees"
1482 );
1483 assert!(
1484 max_x > 5.0 && max_x < 15.0,
1485 "max_x should be around 9 degrees"
1486 );
1487 assert!(
1488 min_y > -15.0 && min_y < -5.0,
1489 "min_y should be around -9 degrees"
1490 );
1491 assert!(
1492 max_y > 5.0 && max_y < 15.0,
1493 "max_y should be around 9 degrees"
1494 );
1495 }
1496
1497 #[test]
1498 fn test_transform_bbox_high_latitude() {
1499 let source_crs = Crs::wgs84();
1500 let target_crs = Crs::web_mercator();
1501
1502 let bbox = (0.0, 50.0, 10.0, 60.0);
1504
1505 let result =
1506 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1507 assert!(result.is_ok());
1508
1509 let (_min_x, min_y, _max_x, max_y) = result.expect("should transform");
1510
1511 assert!(min_y > 6_000_000.0, "min_y should be > 6M for 50 degrees N");
1513 assert!(max_y > 8_000_000.0, "max_y should be > 8M for 60 degrees N");
1514 }
1515
1516 #[test]
1517 fn test_transform_bbox_simple_vs_densified() {
1518 let source_crs = Crs::wgs84();
1519 let target_crs = Crs::web_mercator();
1520
1521 let bbox = (-20.0, 40.0, 20.0, 70.0);
1523
1524 let simple = DatasetRegistry::transform_bbox_simple(bbox, &source_crs, &target_crs);
1525 let densified =
1526 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1527
1528 assert!(simple.is_ok());
1529 assert!(densified.is_ok());
1530
1531 let simple = simple.expect("simple should work");
1533 let densified = densified.expect("densified should work");
1534
1535 assert!(
1538 (simple.0 - densified.0).abs() < 100.0,
1539 "min_x should be similar"
1540 );
1541 assert!(
1542 (simple.2 - densified.2).abs() < 100.0,
1543 "max_x should be similar"
1544 );
1545 }
1546
1547 #[test]
1549 fn test_transform_point() {
1550 let source_crs = Crs::wgs84();
1551 let target_crs = Crs::web_mercator();
1552
1553 let result = DatasetRegistry::transform_point(0.0, 0.0, &source_crs, &target_crs);
1554 assert!(result.is_ok());
1555
1556 let (x, y) = result.expect("should transform");
1557 assert!((x - 0.0).abs() < 1.0, "x should be close to 0");
1558 assert!((y - 0.0).abs() < 1.0, "y should be close to 0");
1559 }
1560
1561 #[test]
1562 fn test_transform_point_london() {
1563 let source_crs = Crs::wgs84();
1564 let target_crs = Crs::web_mercator();
1565
1566 let result = DatasetRegistry::transform_point(-0.1276, 51.5074, &source_crs, &target_crs);
1568 assert!(result.is_ok());
1569
1570 let (x, y) = result.expect("should transform");
1571 assert!(x > -20_000.0 && x < 0.0, "x should be slightly negative");
1573 assert!(
1574 y > 6_500_000.0 && y < 7_000_000.0,
1575 "y should be around 6.7M"
1576 );
1577 }
1578
1579 #[test]
1581 fn test_transform_bbox_to_utm() {
1582 let source_crs = Crs::wgs84();
1583 let target_crs = Crs::from_epsg(32632).expect("UTM 32N should exist");
1585
1586 let bbox = (8.0, 48.0, 10.0, 50.0);
1588
1589 let result =
1590 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1591 assert!(result.is_ok());
1592
1593 let (min_x, min_y, _max_x, _max_y) = result.expect("should transform");
1594
1595 assert!(
1599 min_x > 300_000.0 && min_x < 700_000.0,
1600 "easting should be in valid range"
1601 );
1602 assert!(
1603 min_y > 5_000_000.0 && min_y < 6_000_000.0,
1604 "northing should be in valid range"
1605 );
1606 }
1607
1608 #[test]
1610 fn test_transform_bbox_antimeridian() {
1611 let source_crs = Crs::wgs84();
1612 let target_crs = Crs::web_mercator();
1613
1614 let bbox = (170.0, -10.0, 179.0, 10.0);
1616
1617 let result =
1618 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1619 assert!(result.is_ok());
1620
1621 let (min_x, _min_y, max_x, _max_y) = result.expect("should transform");
1622 assert!(min_x > 0.0 && max_x > min_x, "should have valid x range");
1623 }
1624
1625 #[test]
1626 fn test_transform_bbox_polar_region() {
1627 let source_crs = Crs::wgs84();
1628 let target_crs = Crs::from_epsg(3413).expect("NSIDC Polar Stereographic should exist");
1630
1631 let bbox = (-10.0, 70.0, 10.0, 80.0);
1633
1634 let result =
1635 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1636 assert!(result.is_ok());
1637 }
1638
1639 #[test]
1641 fn test_transform_bbox_invalid_crs() {
1642 let crs = DatasetRegistry::parse_crs("EPSG:99999");
1644 assert!(crs.is_err());
1645 }
1646}