1use crate::config::{ConfigError, LayerConfig};
24use oxigdal_core::buffer::RasterBuffer;
25use oxigdal_core::io::FileDataSource;
26use oxigdal_core::types::{GeoTransform, NoDataValue, RasterDataType};
27use oxigdal_geotiff::GeoTiffReader;
28use oxigdal_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, oxigdal_core::OxiGdalError> {
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, oxigdal_core::OxiGdalError> {
125 if let Ok(guard) = self.reader.read() {
127 if let Some(ref reader) = *guard {
128 if let Some(epsg) = reader.epsg_code() {
129 return Ok(format!("EPSG:{}", epsg));
130 }
131 }
132 }
133 Ok("EPSG:4326".to_string())
134 }
135
136 pub fn geotransform(&self) -> Result<[f64; 6], oxigdal_core::OxiGdalError> {
138 if let Some(ref gt) = self.geo_transform {
139 Ok(gt.to_gdal_array())
140 } else {
141 Ok([0.0, 1.0, 0.0, 0.0, 0.0, -1.0])
143 }
144 }
145
146 #[must_use]
148 pub fn geo_transform_obj(&self) -> Option<&GeoTransform> {
149 self.geo_transform.as_ref()
150 }
151
152 #[must_use]
154 pub fn nodata(&self) -> NoDataValue {
155 self.nodata
156 }
157
158 #[must_use]
160 pub fn tile_size(&self) -> Option<(u32, u32)> {
161 self.tile_size
162 }
163
164 #[must_use]
166 pub fn overview_count(&self) -> usize {
167 self.overview_count
168 }
169
170 #[must_use]
172 pub fn bounds(&self) -> Option<oxigdal_core::types::BoundingBox> {
173 self.geo_transform
174 .as_ref()
175 .map(|gt| gt.compute_bounds(self.width, self.height))
176 }
177
178 pub fn read_tile(
185 &self,
186 level: usize,
187 tile_x: u32,
188 tile_y: u32,
189 ) -> Result<Vec<u8>, oxigdal_core::OxiGdalError> {
190 let guard = self
191 .reader
192 .read()
193 .map_err(|_| oxigdal_core::OxiGdalError::Internal {
194 message: "Failed to acquire read lock".to_string(),
195 })?;
196
197 if let Some(ref reader) = *guard {
198 reader.read_tile(level, tile_x, tile_y)
199 } else {
200 Err(oxigdal_core::OxiGdalError::Internal {
201 message: "Reader not initialized".to_string(),
202 })
203 }
204 }
205
206 pub fn read_tile_buffer(
208 &self,
209 level: usize,
210 tile_x: u32,
211 tile_y: u32,
212 ) -> Result<RasterBuffer, oxigdal_core::OxiGdalError> {
213 let guard = self
214 .reader
215 .read()
216 .map_err(|_| oxigdal_core::OxiGdalError::Internal {
217 message: "Failed to acquire read lock".to_string(),
218 })?;
219
220 if let Some(ref reader) = *guard {
221 reader.read_tile_buffer(level, tile_x, tile_y)
222 } else {
223 Err(oxigdal_core::OxiGdalError::Internal {
224 message: "Reader not initialized".to_string(),
225 })
226 }
227 }
228
229 pub fn read_band(
231 &self,
232 level: usize,
233 band: usize,
234 ) -> Result<Vec<u8>, oxigdal_core::OxiGdalError> {
235 let guard = self
236 .reader
237 .read()
238 .map_err(|_| oxigdal_core::OxiGdalError::Internal {
239 message: "Failed to acquire read lock".to_string(),
240 })?;
241
242 if let Some(ref reader) = *guard {
243 reader.read_band(level, band)
244 } else {
245 Err(oxigdal_core::OxiGdalError::Internal {
246 message: "Reader not initialized".to_string(),
247 })
248 }
249 }
250
251 pub fn read_window(
259 &self,
260 x_offset: u64,
261 y_offset: u64,
262 x_size: u64,
263 y_size: u64,
264 ) -> Result<RasterBuffer, oxigdal_core::OxiGdalError> {
265 if x_offset >= self.width || y_offset >= self.height {
267 return Err(oxigdal_core::OxiGdalError::OutOfBounds {
268 message: format!(
269 "Window offset ({}, {}) out of bounds ({}x{})",
270 x_offset, y_offset, self.width, self.height
271 ),
272 });
273 }
274
275 let actual_x_size = x_size.min(self.width - x_offset);
277 let actual_y_size = y_size.min(self.height - y_offset);
278
279 let mut window_buffer = RasterBuffer::zeros(x_size, y_size, self.data_type);
281
282 let (tile_w, tile_h) = self.tile_size.unwrap_or((256, 256));
284 let tile_w = tile_w as u64;
285 let tile_h = tile_h as u64;
286
287 let start_tile_x = x_offset / tile_w;
289 let start_tile_y = y_offset / tile_h;
290 let end_tile_x = (x_offset + actual_x_size).div_ceil(tile_w);
291 let end_tile_y = (y_offset + actual_y_size).div_ceil(tile_h);
292
293 for tile_y in start_tile_y..end_tile_y {
295 for tile_x in start_tile_x..end_tile_x {
296 let tile_pixel_x = tile_x * tile_w;
298 let tile_pixel_y = tile_y * tile_h;
299
300 let tile_buffer = match self.read_tile_buffer(0, tile_x as u32, tile_y as u32) {
302 Ok(buf) => buf,
303 Err(e) => {
304 debug!("Failed to read tile ({}, {}): {}", tile_x, tile_y, e);
306 continue;
307 }
308 };
309
310 let tile_width = tile_buffer.width();
311 let tile_height = tile_buffer.height();
312
313 let win_min_x = x_offset;
315 let win_min_y = y_offset;
316 let win_max_x = x_offset + actual_x_size;
317 let win_max_y = y_offset + actual_y_size;
318
319 let tile_max_x = (tile_pixel_x + tile_width).min(self.width);
320 let tile_max_y = (tile_pixel_y + tile_height).min(self.height);
321
322 let intersect_min_x = win_min_x.max(tile_pixel_x);
323 let intersect_min_y = win_min_y.max(tile_pixel_y);
324 let intersect_max_x = win_max_x.min(tile_max_x);
325 let intersect_max_y = win_max_y.min(tile_max_y);
326
327 for src_y in intersect_min_y..intersect_max_y {
329 for src_x in intersect_min_x..intersect_max_x {
330 let tile_local_x = src_x - tile_pixel_x;
332 let tile_local_y = src_y - tile_pixel_y;
333
334 let win_local_x = src_x - x_offset;
336 let win_local_y = src_y - y_offset;
337
338 if let Ok(value) = tile_buffer.get_pixel(tile_local_x, tile_local_y) {
340 let _ = window_buffer.set_pixel(win_local_x, win_local_y, value);
341 }
342 }
343 }
344 }
345 }
346
347 Ok(window_buffer)
348 }
349
350 pub fn get_pixel(&self, x: u64, y: u64) -> Result<f64, oxigdal_core::OxiGdalError> {
352 if x >= self.width || y >= self.height {
353 return Err(oxigdal_core::OxiGdalError::OutOfBounds {
354 message: format!(
355 "Pixel ({}, {}) out of bounds ({}x{})",
356 x, y, self.width, self.height
357 ),
358 });
359 }
360
361 let (tile_w, tile_h) = self.tile_size.unwrap_or((256, 256));
363 let tile_x = x / tile_w as u64;
364 let tile_y = y / tile_h as u64;
365 let local_x = x % tile_w as u64;
366 let local_y = y % tile_h as u64;
367
368 let tile_buffer = self.read_tile_buffer(0, tile_x as u32, tile_y as u32)?;
370
371 tile_buffer.get_pixel(local_x, local_y)
373 }
374
375 pub fn rasterband(&self, _band: usize) -> Result<RasterBandInfo, oxigdal_core::OxiGdalError> {
377 Ok(RasterBandInfo {
378 nodata: self.nodata,
379 data_type: self.data_type,
380 })
381 }
382}
383
384pub struct RasterBandInfo {
386 nodata: NoDataValue,
387 data_type: RasterDataType,
388}
389
390impl RasterBandInfo {
391 pub fn nodata(&self) -> Option<f64> {
393 self.nodata.as_f64()
394 }
395
396 pub fn datatype(&self) -> &str {
398 match self.data_type {
399 RasterDataType::UInt8 => "UInt8",
400 RasterDataType::Int8 => "Int8",
401 RasterDataType::UInt16 => "UInt16",
402 RasterDataType::Int16 => "Int16",
403 RasterDataType::UInt32 => "UInt32",
404 RasterDataType::Int32 => "Int32",
405 RasterDataType::Float32 => "Float32",
406 RasterDataType::Float64 => "Float64",
407 RasterDataType::UInt64 => "UInt64",
408 RasterDataType::Int64 => "Int64",
409 RasterDataType::CFloat32 => "CFloat32",
410 RasterDataType::CFloat64 => "CFloat64",
411 }
412 }
413}
414
415#[derive(Debug, Error)]
417pub enum RegistryError {
418 #[error("Layer not found: {0}")]
420 LayerNotFound(String),
421
422 #[error("Failed to open dataset: {0}")]
424 DatasetOpen(#[from] oxigdal_core::OxiGdalError),
425
426 #[error("Configuration error: {0}")]
428 Config(#[from] ConfigError),
429
430 #[error("CRS transformation failed: {0}")]
432 CrsTransformation(String),
433
434 #[error("Invalid CRS: {0}")]
436 InvalidCrs(String),
437
438 #[error("Lock poisoned")]
440 LockPoisoned,
441}
442
443impl From<oxigdal_proj::Error> for RegistryError {
444 fn from(err: oxigdal_proj::Error) -> Self {
445 RegistryError::CrsTransformation(err.to_string())
446 }
447}
448
449pub type RegistryResult<T> = Result<T, RegistryError>;
451
452#[derive(Debug, Clone)]
454pub struct LayerInfo {
455 pub name: String,
457
458 pub title: String,
460
461 pub abstract_: String,
463
464 pub config: LayerConfig,
466
467 pub metadata: DatasetMetadata,
469}
470
471#[derive(Debug, Clone)]
473pub struct DatasetMetadata {
474 pub width: usize,
476
477 pub height: usize,
479
480 pub band_count: usize,
482
483 pub data_type: String,
485
486 pub srs: Option<String>,
488
489 pub bbox: Option<(f64, f64, f64, f64)>,
491
492 pub geotransform: Option<[f64; 6]>,
494
495 pub nodata: Option<f64>,
497}
498
499pub struct DatasetRegistry {
501 layers: Arc<RwLock<HashMap<String, LayerInfo>>>,
503
504 datasets: Arc<RwLock<HashMap<String, Arc<Dataset>>>>,
506}
507
508impl DatasetRegistry {
509 pub fn new() -> Self {
511 Self {
512 layers: Arc::new(RwLock::new(HashMap::new())),
513 datasets: Arc::new(RwLock::new(HashMap::new())),
514 }
515 }
516
517 pub fn register_layer(&self, config: LayerConfig) -> RegistryResult<()> {
519 info!("Registering layer: {}", config.name);
520
521 let layer_name = config.name.clone();
523
524 let dataset = Self::open_dataset(&config.path)?;
526 let metadata = Self::extract_metadata(&dataset)?;
527
528 debug!(
529 "Layer {} metadata: {}x{}, {} bands",
530 layer_name, metadata.width, metadata.height, metadata.band_count
531 );
532
533 let layer_info = LayerInfo {
534 name: config.name.clone(),
535 title: config.title.clone().unwrap_or_else(|| config.name.clone()),
536 abstract_: config
537 .abstract_
538 .clone()
539 .unwrap_or_else(|| format!("Layer {}", config.name)),
540 config,
541 metadata,
542 };
543
544 let mut layers = self
546 .layers
547 .write()
548 .map_err(|_| RegistryError::LockPoisoned)?;
549 layers.insert(layer_info.name.clone(), layer_info);
550
551 let mut datasets = self
553 .datasets
554 .write()
555 .map_err(|_| RegistryError::LockPoisoned)?;
556 datasets.insert(layer_name, Arc::new(dataset));
557
558 Ok(())
559 }
560
561 pub fn register_layers(&self, configs: Vec<LayerConfig>) -> RegistryResult<()> {
563 for config in configs {
564 if !config.enabled {
565 debug!("Skipping disabled layer: {}", config.name);
566 continue;
567 }
568
569 if let Err(e) = self.register_layer(config) {
570 warn!("Failed to register layer: {}", e);
571 }
573 }
574 Ok(())
575 }
576
577 pub fn get_layer(&self, name: &str) -> RegistryResult<LayerInfo> {
579 let layers = self
580 .layers
581 .read()
582 .map_err(|_| RegistryError::LockPoisoned)?;
583
584 layers
585 .get(name)
586 .cloned()
587 .ok_or_else(|| RegistryError::LayerNotFound(name.to_string()))
588 }
589
590 pub fn get_dataset(&self, name: &str) -> RegistryResult<Arc<Dataset>> {
592 let datasets = self
593 .datasets
594 .read()
595 .map_err(|_| RegistryError::LockPoisoned)?;
596
597 datasets
598 .get(name)
599 .cloned()
600 .ok_or_else(|| RegistryError::LayerNotFound(name.to_string()))
601 }
602
603 pub fn list_layers(&self) -> RegistryResult<Vec<LayerInfo>> {
605 let layers = self
606 .layers
607 .read()
608 .map_err(|_| RegistryError::LockPoisoned)?;
609
610 Ok(layers.values().cloned().collect())
611 }
612
613 pub fn has_layer(&self, name: &str) -> bool {
615 self.layers
616 .read()
617 .map(|layers| layers.contains_key(name))
618 .unwrap_or(false)
619 }
620
621 pub fn unregister_layer(&self, name: &str) -> RegistryResult<()> {
623 let mut layers = self
624 .layers
625 .write()
626 .map_err(|_| RegistryError::LockPoisoned)?;
627
628 let mut datasets = self
629 .datasets
630 .write()
631 .map_err(|_| RegistryError::LockPoisoned)?;
632
633 layers.remove(name);
634 datasets.remove(name);
635
636 info!("Unregistered layer: {}", name);
637 Ok(())
638 }
639
640 pub fn layer_count(&self) -> usize {
642 self.layers.read().map(|l| l.len()).unwrap_or(0)
643 }
644
645 fn open_dataset<P: AsRef<Path>>(path: P) -> RegistryResult<Dataset> {
647 let dataset = Dataset::open(path.as_ref())?;
648 Ok(dataset)
649 }
650
651 fn extract_metadata(dataset: &Dataset) -> RegistryResult<DatasetMetadata> {
653 let raster_size = dataset.raster_size();
654 let band_count = dataset.raster_count();
655
656 let srs = dataset.projection().ok();
658
659 let geotransform = dataset.geotransform().ok();
661 let bbox = geotransform.map(|gt| {
662 let width = raster_size.0 as f64;
663 let height = raster_size.1 as f64;
664
665 let min_x = gt[0];
666 let max_x = gt[0] + gt[1] * width + gt[2] * height;
667 let max_y = gt[3];
668 let min_y = gt[3] + gt[4] * width + gt[5] * height;
669
670 let (min_x, max_x) = if min_x < max_x {
672 (min_x, max_x)
673 } else {
674 (max_x, min_x)
675 };
676 let (min_y, max_y) = if min_y < max_y {
677 (min_y, max_y)
678 } else {
679 (max_y, min_y)
680 };
681
682 (min_x, min_y, max_x, max_y)
683 });
684
685 let nodata = if band_count > 0 {
687 dataset.rasterband(1).ok().and_then(|band| band.nodata())
688 } else {
689 None
690 };
691
692 let data_type = if band_count > 0 {
694 dataset
695 .rasterband(1)
696 .ok()
697 .map(|band| format!("{:?}", band.datatype()))
698 .unwrap_or_else(|| "Unknown".to_string())
699 } else {
700 "Unknown".to_string()
701 };
702
703 Ok(DatasetMetadata {
704 width: raster_size.0,
705 height: raster_size.1,
706 band_count,
707 data_type,
708 srs,
709 bbox,
710 geotransform,
711 nodata,
712 })
713 }
714
715 pub fn reload_layer(&self, name: &str) -> RegistryResult<()> {
717 let config = {
718 let layers = self
719 .layers
720 .read()
721 .map_err(|_| RegistryError::LockPoisoned)?;
722
723 layers
724 .get(name)
725 .ok_or_else(|| RegistryError::LayerNotFound(name.to_string()))?
726 .config
727 .clone()
728 };
729
730 self.unregister_layer(name)?;
731 self.register_layer(config)?;
732
733 info!("Reloaded layer: {}", name);
734 Ok(())
735 }
736
737 pub fn get_layer_bbox(
768 &self,
769 name: &str,
770 target_crs: Option<&str>,
771 ) -> RegistryResult<Option<(f64, f64, f64, f64)>> {
772 let layer = self.get_layer(name)?;
773
774 let target_crs_str = match target_crs {
776 Some(crs) => crs,
777 None => return Ok(layer.metadata.bbox),
778 };
779
780 let native_bbox = match layer.metadata.bbox {
782 Some(bbox) => bbox,
783 None => return Ok(None),
784 };
785
786 let source_crs_str = layer.metadata.srs.as_deref().unwrap_or("EPSG:4326");
788
789 if Self::crs_strings_equivalent(source_crs_str, target_crs_str) {
791 return Ok(Some(native_bbox));
792 }
793
794 let source_crs = Self::parse_crs(source_crs_str)?;
796 let target_crs = Self::parse_crs(target_crs_str)?;
797
798 let transformed =
800 Self::transform_bbox_with_densification(native_bbox, &source_crs, &target_crs)?;
801
802 Ok(Some(transformed))
803 }
804
805 fn parse_crs(crs_str: &str) -> RegistryResult<Crs> {
812 let trimmed = crs_str.trim();
813
814 if let Some(code_str) = trimmed.to_uppercase().strip_prefix("EPSG:") {
816 let code: u32 = code_str.parse().map_err(|_| {
817 RegistryError::InvalidCrs(format!("Invalid EPSG code: {}", code_str))
818 })?;
819 return Ok(Crs::from_epsg(code)?);
820 }
821
822 if trimmed.starts_with("+proj=") || trimmed.contains("+proj=") {
824 return Ok(Crs::from_proj(trimmed)?);
825 }
826
827 if trimmed.starts_with("GEOGCS[")
829 || trimmed.starts_with("PROJCS[")
830 || trimmed.starts_with("GEOCCS[")
831 {
832 return Ok(Crs::from_wkt(trimmed)?);
833 }
834
835 Err(RegistryError::InvalidCrs(format!(
836 "Unrecognized CRS format: {}",
837 crs_str
838 )))
839 }
840
841 fn crs_strings_equivalent(crs1: &str, crs2: &str) -> bool {
843 let norm1 = crs1.trim().to_uppercase();
844 let norm2 = crs2.trim().to_uppercase();
845 norm1 == norm2
846 }
847
848 fn transform_bbox_with_densification(
865 bbox: (f64, f64, f64, f64),
866 source_crs: &Crs,
867 target_crs: &Crs,
868 ) -> RegistryResult<(f64, f64, f64, f64)> {
869 let (min_x, min_y, max_x, max_y) = bbox;
870
871 let transformer = Transformer::new(source_crs.clone(), target_crs.clone())?;
873
874 const DENSIFY_POINTS: usize = 21;
878
879 let edge_points = Self::densify_bbox_edges(min_x, min_y, max_x, max_y, DENSIFY_POINTS);
881
882 let transformed_points: Vec<Coordinate> = edge_points
884 .iter()
885 .filter_map(|coord| transformer.transform(coord).ok())
886 .collect();
887
888 if transformed_points.is_empty() {
890 return Err(RegistryError::CrsTransformation(
891 "All points failed to transform".to_string(),
892 ));
893 }
894
895 let mut result_min_x = f64::INFINITY;
897 let mut result_min_y = f64::INFINITY;
898 let mut result_max_x = f64::NEG_INFINITY;
899 let mut result_max_y = f64::NEG_INFINITY;
900
901 for point in &transformed_points {
902 if point.x.is_finite() && point.y.is_finite() {
903 result_min_x = result_min_x.min(point.x);
904 result_min_y = result_min_y.min(point.y);
905 result_max_x = result_max_x.max(point.x);
906 result_max_y = result_max_y.max(point.y);
907 }
908 }
909
910 if !result_min_x.is_finite()
912 || !result_min_y.is_finite()
913 || !result_max_x.is_finite()
914 || !result_max_y.is_finite()
915 {
916 return Err(RegistryError::CrsTransformation(
917 "Transformation resulted in non-finite values".to_string(),
918 ));
919 }
920
921 Ok((result_min_x, result_min_y, result_max_x, result_max_y))
922 }
923
924 fn densify_bbox_edges(
929 min_x: f64,
930 min_y: f64,
931 max_x: f64,
932 max_y: f64,
933 points_per_edge: usize,
934 ) -> Vec<Coordinate> {
935 let mut points = Vec::with_capacity(points_per_edge * 4);
936
937 let n = points_per_edge.max(2);
939
940 for i in 0..n {
942 let t = i as f64 / (n - 1) as f64;
943 let x = min_x + t * (max_x - min_x);
944 points.push(Coordinate::new(x, min_y));
945 }
946
947 for i in 1..n {
950 let t = i as f64 / (n - 1) as f64;
951 let y = min_y + t * (max_y - min_y);
952 points.push(Coordinate::new(max_x, y));
953 }
954
955 for i in 1..n {
958 let t = i as f64 / (n - 1) as f64;
959 let x = max_x - t * (max_x - min_x);
960 points.push(Coordinate::new(x, max_y));
961 }
962
963 for i in 1..(n - 1) {
966 let t = i as f64 / (n - 1) as f64;
967 let y = max_y - t * (max_y - min_y);
968 points.push(Coordinate::new(min_x, y));
969 }
970
971 points
972 }
973
974 #[allow(dead_code)]
978 fn transform_point(
979 x: f64,
980 y: f64,
981 source_crs: &Crs,
982 target_crs: &Crs,
983 ) -> RegistryResult<(f64, f64)> {
984 let transformer = Transformer::new(source_crs.clone(), target_crs.clone())?;
985 let coord = Coordinate::new(x, y);
986 let transformed = transformer.transform(&coord)?;
987 Ok((transformed.x, transformed.y))
988 }
989
990 pub fn get_layer_bbox_web_mercator(
994 &self,
995 name: &str,
996 ) -> RegistryResult<Option<(f64, f64, f64, f64)>> {
997 self.get_layer_bbox(name, Some("EPSG:3857"))
998 }
999
1000 pub fn get_layer_bbox_wgs84(&self, name: &str) -> RegistryResult<Option<(f64, f64, f64, f64)>> {
1004 self.get_layer_bbox(name, Some("EPSG:4326"))
1005 }
1006
1007 #[allow(dead_code)]
1012 fn transform_bbox_simple(
1013 bbox: (f64, f64, f64, f64),
1014 source_crs: &Crs,
1015 target_crs: &Crs,
1016 ) -> RegistryResult<(f64, f64, f64, f64)> {
1017 let (min_x, min_y, max_x, max_y) = bbox;
1018
1019 let source_bbox = BoundingBox::new(min_x, min_y, max_x, max_y)
1021 .map_err(|e| RegistryError::CrsTransformation(e.to_string()))?;
1022
1023 let transformer = Transformer::new(source_crs.clone(), target_crs.clone())?;
1025 let transformed = transformer.transform_bbox(&source_bbox)?;
1026
1027 Ok((
1028 transformed.min_x,
1029 transformed.min_y,
1030 transformed.max_x,
1031 transformed.max_y,
1032 ))
1033 }
1034}
1035
1036impl Default for DatasetRegistry {
1037 fn default() -> Self {
1038 Self::new()
1039 }
1040}
1041
1042impl Clone for DatasetRegistry {
1043 fn clone(&self) -> Self {
1044 Self {
1045 layers: Arc::clone(&self.layers),
1046 datasets: Arc::clone(&self.datasets),
1047 }
1048 }
1049}
1050
1051#[cfg(test)]
1052mod tests {
1053 use super::*;
1054
1055 #[test]
1056 fn test_registry_creation() {
1057 let registry = DatasetRegistry::new();
1058 assert_eq!(registry.layer_count(), 0);
1059 assert!(!registry.has_layer("test"));
1060 }
1061
1062 #[test]
1063 fn test_layer_not_found() {
1064 let registry = DatasetRegistry::new();
1065 let result = registry.get_layer("nonexistent");
1066 assert!(matches!(result, Err(RegistryError::LayerNotFound(_))));
1067 }
1068
1069 #[test]
1070 fn test_registry_clone() {
1071 let registry1 = DatasetRegistry::new();
1072 let registry2 = registry1.clone();
1073
1074 assert_eq!(registry1.layer_count(), registry2.layer_count());
1075 }
1076
1077 #[test]
1079 fn test_parse_crs_epsg_uppercase() {
1080 let crs = DatasetRegistry::parse_crs("EPSG:4326");
1081 assert!(crs.is_ok());
1082 let crs = crs.expect("should parse");
1083 assert_eq!(crs.epsg_code(), Some(4326));
1084 }
1085
1086 #[test]
1087 fn test_parse_crs_epsg_lowercase() {
1088 let crs = DatasetRegistry::parse_crs("epsg:3857");
1089 assert!(crs.is_ok());
1090 let crs = crs.expect("should parse");
1091 assert_eq!(crs.epsg_code(), Some(3857));
1092 }
1093
1094 #[test]
1095 fn test_parse_crs_proj_string() {
1096 let crs = DatasetRegistry::parse_crs("+proj=longlat +datum=WGS84 +no_defs");
1097 assert!(crs.is_ok());
1098 }
1099
1100 #[test]
1101 fn test_parse_crs_wkt() {
1102 let wkt = r#"GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]]]"#;
1103 let crs = DatasetRegistry::parse_crs(wkt);
1104 assert!(crs.is_ok());
1105 }
1106
1107 #[test]
1108 fn test_parse_crs_invalid() {
1109 let crs = DatasetRegistry::parse_crs("invalid crs");
1110 assert!(matches!(crs, Err(RegistryError::InvalidCrs(_))));
1111 }
1112
1113 #[test]
1114 fn test_parse_crs_invalid_epsg() {
1115 let crs = DatasetRegistry::parse_crs("EPSG:abc");
1116 assert!(matches!(crs, Err(RegistryError::InvalidCrs(_))));
1117 }
1118
1119 #[test]
1121 fn test_crs_strings_equivalent_same() {
1122 assert!(DatasetRegistry::crs_strings_equivalent(
1123 "EPSG:4326",
1124 "EPSG:4326"
1125 ));
1126 }
1127
1128 #[test]
1129 fn test_crs_strings_equivalent_case_insensitive() {
1130 assert!(DatasetRegistry::crs_strings_equivalent(
1131 "EPSG:4326",
1132 "epsg:4326"
1133 ));
1134 assert!(DatasetRegistry::crs_strings_equivalent(
1135 "epsg:3857",
1136 "EPSG:3857"
1137 ));
1138 }
1139
1140 #[test]
1141 fn test_crs_strings_equivalent_with_whitespace() {
1142 assert!(DatasetRegistry::crs_strings_equivalent(
1143 " EPSG:4326 ",
1144 "EPSG:4326"
1145 ));
1146 }
1147
1148 #[test]
1149 fn test_crs_strings_not_equivalent() {
1150 assert!(!DatasetRegistry::crs_strings_equivalent(
1151 "EPSG:4326",
1152 "EPSG:3857"
1153 ));
1154 }
1155
1156 #[test]
1158 fn test_densify_bbox_edges_minimum() {
1159 let points = DatasetRegistry::densify_bbox_edges(0.0, 0.0, 10.0, 10.0, 2);
1160 assert_eq!(points.len(), 4);
1163
1164 assert!(
1166 points
1167 .iter()
1168 .any(|p| (p.x - 0.0).abs() < 1e-10 && (p.y - 0.0).abs() < 1e-10)
1169 );
1170 assert!(
1171 points
1172 .iter()
1173 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - 0.0).abs() < 1e-10)
1174 );
1175 assert!(
1176 points
1177 .iter()
1178 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10)
1179 );
1180 assert!(
1181 points
1182 .iter()
1183 .any(|p| (p.x - 0.0).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10)
1184 );
1185 }
1186
1187 #[test]
1188 fn test_densify_bbox_edges_5_points() {
1189 let points = DatasetRegistry::densify_bbox_edges(0.0, 0.0, 10.0, 10.0, 5);
1190 assert_eq!(points.len(), 16);
1192 }
1193
1194 #[test]
1195 fn test_densify_bbox_edges_21_points() {
1196 let points = DatasetRegistry::densify_bbox_edges(-10.0, -10.0, 10.0, 10.0, 21);
1197 assert_eq!(points.len(), 80);
1199
1200 let has_bottom_left = points
1202 .iter()
1203 .any(|p| (p.x - (-10.0)).abs() < 1e-10 && (p.y - (-10.0)).abs() < 1e-10);
1204 let has_bottom_right = points
1205 .iter()
1206 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - (-10.0)).abs() < 1e-10);
1207 let has_top_right = points
1208 .iter()
1209 .any(|p| (p.x - 10.0).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10);
1210 let has_top_left = points
1211 .iter()
1212 .any(|p| (p.x - (-10.0)).abs() < 1e-10 && (p.y - 10.0).abs() < 1e-10);
1213
1214 assert!(has_bottom_left, "Should have bottom-left corner");
1215 assert!(has_bottom_right, "Should have bottom-right corner");
1216 assert!(has_top_right, "Should have top-right corner");
1217 assert!(has_top_left, "Should have top-left corner");
1218 }
1219
1220 #[test]
1222 fn test_transform_bbox_same_crs() {
1223 let source_crs = Crs::wgs84();
1224 let target_crs = Crs::wgs84();
1225 let bbox = (0.0, 0.0, 10.0, 10.0);
1226
1227 let result =
1228 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1229 assert!(result.is_ok());
1230
1231 let (min_x, min_y, max_x, max_y) = result.expect("should transform");
1232 assert!((min_x - 0.0).abs() < 1e-6);
1233 assert!((min_y - 0.0).abs() < 1e-6);
1234 assert!((max_x - 10.0).abs() < 1e-6);
1235 assert!((max_y - 10.0).abs() < 1e-6);
1236 }
1237
1238 #[test]
1239 fn test_transform_bbox_wgs84_to_web_mercator() {
1240 let source_crs = Crs::wgs84();
1241 let target_crs = Crs::web_mercator();
1242
1243 let bbox = (-1.0, -1.0, 1.0, 1.0);
1245
1246 let result =
1247 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1248 assert!(result.is_ok());
1249
1250 let (min_x, min_y, max_x, max_y) = result.expect("should transform");
1251
1252 assert!(min_x < 0.0, "min_x should be negative");
1255 assert!(min_y < 0.0, "min_y should be negative");
1256 assert!(max_x > 0.0, "max_x should be positive");
1257 assert!(max_y > 0.0, "max_y should be positive");
1258
1259 assert!(min_x > -200_000.0, "min_x should be > -200000");
1261 assert!(max_x < 200_000.0, "max_x should be < 200000");
1262 assert!(min_y > -200_000.0, "min_y should be > -200000");
1263 assert!(max_y < 200_000.0, "max_y should be < 200000");
1264 }
1265
1266 #[test]
1267 fn test_transform_bbox_web_mercator_to_wgs84() {
1268 let source_crs = Crs::web_mercator();
1269 let target_crs = Crs::wgs84();
1270
1271 let bbox = (-1_000_000.0, -1_000_000.0, 1_000_000.0, 1_000_000.0);
1273
1274 let result =
1275 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1276 assert!(result.is_ok());
1277
1278 let (min_x, min_y, max_x, max_y) = result.expect("should transform");
1279
1280 assert!(
1282 min_x > -15.0 && min_x < -5.0,
1283 "min_x should be around -9 degrees"
1284 );
1285 assert!(
1286 max_x > 5.0 && max_x < 15.0,
1287 "max_x should be around 9 degrees"
1288 );
1289 assert!(
1290 min_y > -15.0 && min_y < -5.0,
1291 "min_y should be around -9 degrees"
1292 );
1293 assert!(
1294 max_y > 5.0 && max_y < 15.0,
1295 "max_y should be around 9 degrees"
1296 );
1297 }
1298
1299 #[test]
1300 fn test_transform_bbox_high_latitude() {
1301 let source_crs = Crs::wgs84();
1302 let target_crs = Crs::web_mercator();
1303
1304 let bbox = (0.0, 50.0, 10.0, 60.0);
1306
1307 let result =
1308 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1309 assert!(result.is_ok());
1310
1311 let (_min_x, min_y, _max_x, max_y) = result.expect("should transform");
1312
1313 assert!(min_y > 6_000_000.0, "min_y should be > 6M for 50 degrees N");
1315 assert!(max_y > 8_000_000.0, "max_y should be > 8M for 60 degrees N");
1316 }
1317
1318 #[test]
1319 fn test_transform_bbox_simple_vs_densified() {
1320 let source_crs = Crs::wgs84();
1321 let target_crs = Crs::web_mercator();
1322
1323 let bbox = (-20.0, 40.0, 20.0, 70.0);
1325
1326 let simple = DatasetRegistry::transform_bbox_simple(bbox, &source_crs, &target_crs);
1327 let densified =
1328 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1329
1330 assert!(simple.is_ok());
1331 assert!(densified.is_ok());
1332
1333 let simple = simple.expect("simple should work");
1335 let densified = densified.expect("densified should work");
1336
1337 assert!(
1340 (simple.0 - densified.0).abs() < 100.0,
1341 "min_x should be similar"
1342 );
1343 assert!(
1344 (simple.2 - densified.2).abs() < 100.0,
1345 "max_x should be similar"
1346 );
1347 }
1348
1349 #[test]
1351 fn test_transform_point() {
1352 let source_crs = Crs::wgs84();
1353 let target_crs = Crs::web_mercator();
1354
1355 let result = DatasetRegistry::transform_point(0.0, 0.0, &source_crs, &target_crs);
1356 assert!(result.is_ok());
1357
1358 let (x, y) = result.expect("should transform");
1359 assert!((x - 0.0).abs() < 1.0, "x should be close to 0");
1360 assert!((y - 0.0).abs() < 1.0, "y should be close to 0");
1361 }
1362
1363 #[test]
1364 fn test_transform_point_london() {
1365 let source_crs = Crs::wgs84();
1366 let target_crs = Crs::web_mercator();
1367
1368 let result = DatasetRegistry::transform_point(-0.1276, 51.5074, &source_crs, &target_crs);
1370 assert!(result.is_ok());
1371
1372 let (x, y) = result.expect("should transform");
1373 assert!(x > -20_000.0 && x < 0.0, "x should be slightly negative");
1375 assert!(
1376 y > 6_500_000.0 && y < 7_000_000.0,
1377 "y should be around 6.7M"
1378 );
1379 }
1380
1381 #[test]
1383 fn test_transform_bbox_to_utm() {
1384 let source_crs = Crs::wgs84();
1385 let target_crs = Crs::from_epsg(32632).expect("UTM 32N should exist");
1387
1388 let bbox = (8.0, 48.0, 10.0, 50.0);
1390
1391 let result =
1392 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1393 assert!(result.is_ok());
1394
1395 let (min_x, min_y, _max_x, _max_y) = result.expect("should transform");
1396
1397 assert!(
1401 min_x > 300_000.0 && min_x < 700_000.0,
1402 "easting should be in valid range"
1403 );
1404 assert!(
1405 min_y > 5_000_000.0 && min_y < 6_000_000.0,
1406 "northing should be in valid range"
1407 );
1408 }
1409
1410 #[test]
1412 fn test_transform_bbox_antimeridian() {
1413 let source_crs = Crs::wgs84();
1414 let target_crs = Crs::web_mercator();
1415
1416 let bbox = (170.0, -10.0, 179.0, 10.0);
1418
1419 let result =
1420 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1421 assert!(result.is_ok());
1422
1423 let (min_x, _min_y, max_x, _max_y) = result.expect("should transform");
1424 assert!(min_x > 0.0 && max_x > min_x, "should have valid x range");
1425 }
1426
1427 #[test]
1428 fn test_transform_bbox_polar_region() {
1429 let source_crs = Crs::wgs84();
1430 let target_crs = Crs::from_epsg(3413).expect("NSIDC Polar Stereographic should exist");
1432
1433 let bbox = (-10.0, 70.0, 10.0, 80.0);
1435
1436 let result =
1437 DatasetRegistry::transform_bbox_with_densification(bbox, &source_crs, &target_crs);
1438 assert!(result.is_ok());
1439 }
1440
1441 #[test]
1443 fn test_transform_bbox_invalid_crs() {
1444 let crs = DatasetRegistry::parse_crs("EPSG:99999");
1446 assert!(crs.is_err());
1447 }
1448}