oxigdal/lib.rs
1//! # OxiGDAL — Pure Rust Geospatial Data Abstraction Library
2//!
3//! OxiGDAL is the Rust-native alternative to [GDAL](https://gdal.org/),
4//! providing a comprehensive geospatial data abstraction layer
5//! with **zero C/Fortran dependencies**. 100% Pure Rust.
6//!
7//! ## Quick Start
8//!
9//! ```toml
10//! [dependencies]
11//! oxigdal = "0.1" # includes GeoTIFF, GeoJSON, Shapefile by default
12//! ```
13//!
14//! ```rust
15//! use oxigdal::Dataset;
16//!
17//! # fn main() -> oxigdal::Result<()> {
18//! let drivers = oxigdal::drivers();
19//! println!("Enabled drivers: {:?}", drivers);
20//! println!("OxiGDAL version: {}", oxigdal::version());
21//! # Ok(())
22//! # }
23//! ```
24//!
25//! ## Feature Flags
26//!
27//! | Feature | Default | Description |
28//! |---------|---------|-------------|
29//! | `geotiff` | ✅ | GeoTIFF raster format (COG support) |
30//! | `geojson` | ✅ | GeoJSON vector format |
31//! | `shapefile` | ✅ | ESRI Shapefile |
32//! | `geoparquet` | ❌ | GeoParquet (Apache Arrow columnar) |
33//! | `netcdf` | ❌ | NetCDF scientific data format |
34//! | `hdf5` | ❌ | HDF5 hierarchical data format |
35//! | `zarr` | ❌ | Zarr cloud-native arrays |
36//! | `grib` | ❌ | GRIB meteorological data format |
37//! | `stac` | ❌ | SpatioTemporal Asset Catalog |
38//! | `terrain` | ❌ | Terrain/elevation data |
39//! | `vrt` | ❌ | Virtual Raster Tiles |
40//! | `flatgeobuf` | ❌ | FlatGeobuf vector format |
41//! | `jpeg2000` | ❌ | JPEG2000 raster format |
42//! | `full` | ❌ | **All formats above** |
43//! | `cloud` | ❌ | Cloud storage (S3, GCS, Azure) |
44//! | `proj` | ❌ | CRS transformations (Pure Rust proj) |
45//! | `algorithms` | ❌ | Raster/vector algorithms |
46//! | `analytics` | ❌ | Geospatial analytics |
47//! | `streaming` | ❌ | Stream processing |
48//! | `ml` | ❌ | Machine learning integration |
49//! | `gpu` | ❌ | GPU-accelerated processing |
50//! | `server` | ❌ | OGC-compliant tile server |
51//! | `temporal` | ❌ | Temporal/time-series analysis |
52//!
53//! ## GDAL Compatibility
54//!
55//! OxiGDAL aims to provide familiar concepts for GDAL users:
56//!
57//! | GDAL (C/C++) | OxiGDAL (Rust) |
58//! |---|---|
59//! | `GDALOpen()` | [`Dataset::open()`] |
60//! | `GDALGetRasterBand()` | `dataset.raster_band(n)` |
61//! | `GDALGetGeoTransform()` | [`Dataset::geotransform()`] |
62//! | `GDALGetProjectionRef()` | [`Dataset::crs()`] |
63//! | `GDALAllRegister()` | [`drivers()`] |
64//! | `GDALVersionInfo()` | [`version()`] |
65//! | `GDALWarp()` | `oxigdal::algorithms::warp()` (feature `algorithms`) |
66//! | `ogr2ogr` | `oxigdal-cli convert` (crate `oxigdal-cli`) |
67//!
68//! ## Architecture
69//!
70//! ```text
71//! ┌──────────────────────────────────────────────────┐
72//! │ oxigdal (this crate) — Unified API │
73//! │ Dataset::open() → auto-detect format │
74//! ├──────────────────────────────────────────────────┤
75//! │ Drivers (feature-gated) │
76//! │ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
77//! │ │ GeoTIFF │ │ GeoJSON │ │ Shapefile │ ... │
78//! │ └──────────┘ └──────────┘ └─────────────┘ │
79//! ├──────────────────────────────────────────────────┤
80//! │ oxigdal-core — Types, Buffers, Error, I/O │
81//! └──────────────────────────────────────────────────┘
82//! ```
83//!
84//! ## Crate Ecosystem
85//!
86//! OxiGDAL is a workspace of 65+ crates. This `oxigdal` crate serves as
87//! the **unified entry point**. Individual crates can also be used directly:
88//!
89//! ```toml
90//! # Use the unified API (recommended for most users)
91//! oxigdal = { version = "0.1", features = ["full", "cloud", "proj"] }
92//!
93//! # Or pick individual crates for minimal dependencies
94//! oxigdal-core = "0.1"
95//! oxigdal-geotiff = "0.1"
96//! ```
97//!
98//! ## Pure Rust — No C/Fortran Dependencies
99//!
100//! Unlike the original GDAL which requires C/C++ compilation and system
101//! libraries (PROJ, GEOS, etc.), OxiGDAL is **100% Pure Rust**:
102//!
103//! - No `bindgen`, no `cc`, no `cmake`
104//! - Cross-compiles to WASM, embedded, mobile
105//! - `cargo add oxigdal` — that's it
106//!
107//! Part of the [COOLJAPAN](https://github.com/cool-japan) ecosystem.
108
109#![cfg_attr(docsrs, feature(doc_cfg))]
110#![forbid(unsafe_code)]
111#![warn(missing_docs)]
112
113// Re-export core types — always available
114pub use oxigdal_core::error::OxiGdalError;
115pub use oxigdal_core::error::Result;
116pub use oxigdal_core::types::{BoundingBox, GeoTransform, RasterDataType, RasterMetadata};
117
118/// Re-export the core crate for advanced usage
119pub use oxigdal_core as core_types;
120
121// ─── Driver re-exports (feature-gated) ──────────────────────────────────────
122
123/// GeoTIFF raster driver (Cloud-Optimized GeoTIFF support)
124#[cfg(feature = "geotiff")]
125#[cfg_attr(docsrs, doc(cfg(feature = "geotiff")))]
126pub use oxigdal_geotiff as geotiff;
127
128/// GeoJSON vector driver
129#[cfg(feature = "geojson")]
130#[cfg_attr(docsrs, doc(cfg(feature = "geojson")))]
131pub use oxigdal_geojson as geojson;
132
133/// ESRI Shapefile driver
134#[cfg(feature = "shapefile")]
135#[cfg_attr(docsrs, doc(cfg(feature = "shapefile")))]
136pub use oxigdal_shapefile as shapefile;
137
138/// GeoParquet columnar format driver
139#[cfg(feature = "geoparquet")]
140#[cfg_attr(docsrs, doc(cfg(feature = "geoparquet")))]
141pub use oxigdal_geoparquet as geoparquet;
142
143/// NetCDF scientific format driver
144#[cfg(feature = "netcdf")]
145#[cfg_attr(docsrs, doc(cfg(feature = "netcdf")))]
146pub use oxigdal_netcdf as netcdf;
147
148/// HDF5 hierarchical data driver
149#[cfg(feature = "hdf5")]
150#[cfg_attr(docsrs, doc(cfg(feature = "hdf5")))]
151pub use oxigdal_hdf5 as hdf5;
152
153/// Zarr cloud-native array driver
154#[cfg(feature = "zarr")]
155#[cfg_attr(docsrs, doc(cfg(feature = "zarr")))]
156pub use oxigdal_zarr as zarr;
157
158/// GRIB meteorological data driver
159#[cfg(feature = "grib")]
160#[cfg_attr(docsrs, doc(cfg(feature = "grib")))]
161pub use oxigdal_grib as grib;
162
163/// SpatioTemporal Asset Catalog driver
164#[cfg(feature = "stac")]
165#[cfg_attr(docsrs, doc(cfg(feature = "stac")))]
166pub use oxigdal_stac as stac;
167
168/// Terrain/elevation data driver
169#[cfg(feature = "terrain")]
170#[cfg_attr(docsrs, doc(cfg(feature = "terrain")))]
171pub use oxigdal_terrain as terrain;
172
173/// Virtual Raster Tiles driver
174#[cfg(feature = "vrt")]
175#[cfg_attr(docsrs, doc(cfg(feature = "vrt")))]
176pub use oxigdal_vrt as vrt;
177
178/// FlatGeobuf vector format driver
179#[cfg(feature = "flatgeobuf")]
180#[cfg_attr(docsrs, doc(cfg(feature = "flatgeobuf")))]
181pub use oxigdal_flatgeobuf as flatgeobuf;
182
183/// JPEG2000 raster format driver
184#[cfg(feature = "jpeg2000")]
185#[cfg_attr(docsrs, doc(cfg(feature = "jpeg2000")))]
186pub use oxigdal_jpeg2000 as jpeg2000;
187
188// ─── Advanced capability re-exports (feature-gated) ─────────────────────────
189
190/// Cloud storage backends (S3, GCS, Azure Blob)
191#[cfg(feature = "cloud")]
192#[cfg_attr(docsrs, doc(cfg(feature = "cloud")))]
193pub use oxigdal_cloud as cloud;
194
195/// Coordinate reference system transformations (Pure Rust proj)
196#[cfg(feature = "proj")]
197#[cfg_attr(docsrs, doc(cfg(feature = "proj")))]
198pub use oxigdal_proj as proj;
199
200/// Raster and vector algorithms (resampling, reprojection, etc.)
201#[cfg(feature = "algorithms")]
202#[cfg_attr(docsrs, doc(cfg(feature = "algorithms")))]
203pub use oxigdal_algorithms as algorithms;
204
205/// Geospatial analytics and statistics
206#[cfg(feature = "analytics")]
207#[cfg_attr(docsrs, doc(cfg(feature = "analytics")))]
208pub use oxigdal_analytics as analytics;
209
210/// Stream processing for large datasets
211#[cfg(feature = "streaming")]
212#[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
213pub use oxigdal_streaming as streaming;
214
215/// Machine learning integration
216#[cfg(feature = "ml")]
217#[cfg_attr(docsrs, doc(cfg(feature = "ml")))]
218pub use oxigdal_ml as ml;
219
220/// GPU-accelerated geospatial processing
221#[cfg(feature = "gpu")]
222#[cfg_attr(docsrs, doc(cfg(feature = "gpu")))]
223pub use oxigdal_gpu as gpu;
224
225/// OGC-compliant geospatial tile/feature server
226#[cfg(feature = "server")]
227#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
228pub use oxigdal_server as server;
229
230/// Temporal/time-series geospatial analysis
231#[cfg(feature = "temporal")]
232#[cfg_attr(docsrs, doc(cfg(feature = "temporal")))]
233pub use oxigdal_temporal as temporal;
234
235// ─── Unified Dataset API ────────────────────────────────────────────────────
236
237/// Detected format of a geospatial dataset.
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub enum DatasetFormat {
240 /// GeoTIFF / Cloud-Optimized GeoTIFF (.tif, .tiff)
241 GeoTiff,
242 /// GeoJSON (.geojson, .json)
243 GeoJson,
244 /// ESRI Shapefile (.shp)
245 Shapefile,
246 /// GeoParquet (.parquet, .geoparquet)
247 GeoParquet,
248 /// NetCDF (.nc, .nc4)
249 NetCdf,
250 /// HDF5 (.h5, .hdf5, .he5)
251 Hdf5,
252 /// Zarr (.zarr directory)
253 Zarr,
254 /// GRIB/GRIB2 (.grib, .grib2, .grb, .grb2)
255 Grib,
256 /// STAC catalog (.json with STAC metadata)
257 Stac,
258 /// Terrain formats
259 Terrain,
260 /// Virtual Raster Tiles (.vrt)
261 Vrt,
262 /// FlatGeobuf (.fgb)
263 FlatGeobuf,
264 /// JPEG2000 (.jp2, .j2k)
265 Jpeg2000,
266 /// Unknown / user-specified
267 Unknown,
268}
269
270impl DatasetFormat {
271 /// Detect format from file extension.
272 ///
273 /// Returns `DatasetFormat::Unknown` if the extension is not recognized.
274 pub fn from_extension(path: &str) -> Self {
275 let ext = std::path::Path::new(path)
276 .extension()
277 .and_then(|e| e.to_str())
278 .map(|e| e.to_lowercase())
279 .unwrap_or_default();
280
281 match ext.as_str() {
282 "tif" | "tiff" => Self::GeoTiff,
283 "geojson" => Self::GeoJson,
284 "shp" => Self::Shapefile,
285 "parquet" | "geoparquet" => Self::GeoParquet,
286 "nc" | "nc4" => Self::NetCdf,
287 "h5" | "hdf5" | "he5" => Self::Hdf5,
288 "zarr" => Self::Zarr,
289 "grib" | "grib2" | "grb" | "grb2" => Self::Grib,
290 "vrt" => Self::Vrt,
291 "fgb" => Self::FlatGeobuf,
292 "jp2" | "j2k" => Self::Jpeg2000,
293 _ => Self::Unknown,
294 }
295 }
296
297 /// Human-readable driver name (matches GDAL naming convention).
298 pub fn driver_name(&self) -> &'static str {
299 match self {
300 Self::GeoTiff => "GTiff",
301 Self::GeoJson => "GeoJSON",
302 Self::Shapefile => "ESRI Shapefile",
303 Self::GeoParquet => "GeoParquet",
304 Self::NetCdf => "netCDF",
305 Self::Hdf5 => "HDF5",
306 Self::Zarr => "Zarr",
307 Self::Grib => "GRIB",
308 Self::Stac => "STAC",
309 Self::Terrain => "Terrain",
310 Self::Vrt => "VRT",
311 Self::FlatGeobuf => "FlatGeobuf",
312 Self::Jpeg2000 => "JPEG2000",
313 Self::Unknown => "Unknown",
314 }
315 }
316}
317
318impl core::fmt::Display for DatasetFormat {
319 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
320 f.write_str(self.driver_name())
321 }
322}
323
324/// Basic dataset metadata — analogous to `GDALDataset` info.
325#[derive(Debug, Clone)]
326pub struct DatasetInfo {
327 /// Detected format
328 pub format: DatasetFormat,
329 /// Width in pixels (raster) or `None` (vector-only)
330 pub width: Option<u32>,
331 /// Height in pixels (raster) or `None` (vector-only)
332 pub height: Option<u32>,
333 /// Number of raster bands
334 pub band_count: u32,
335 /// Number of vector layers
336 pub layer_count: u32,
337 /// Coordinate reference system (WKT, EPSG code, or PROJ string)
338 pub crs: Option<String>,
339 /// Geotransform: `[origin_x, pixel_width, rotation_x, origin_y, rotation_y, pixel_height]`
340 pub geotransform: Option<GeoTransform>,
341}
342
343/// Unified dataset handle — the central abstraction (analogous to `GDALDataset`).
344///
345/// Opens any supported geospatial format and provides uniform access
346/// to raster bands, vector layers, and metadata.
347///
348/// # Example
349///
350/// ```rust,no_run
351/// use oxigdal::Dataset;
352///
353/// let ds = Dataset::open("elevation.tif").expect("failed to open");
354/// println!("{}×{} pixels, {} bands", ds.width(), ds.height(), ds.band_count());
355/// println!("Format: {}", ds.format());
356/// if let Some(crs) = ds.crs() {
357/// println!("CRS: {crs}");
358/// }
359/// ```
360pub struct Dataset {
361 path: String,
362 info: DatasetInfo,
363}
364
365impl Dataset {
366 /// Open a geospatial dataset from a file path — the universal entry point.
367 ///
368 /// Format is auto-detected from file extension (and in the future, magic bytes),
369 /// just like `GDALOpen()` in C GDAL.
370 ///
371 /// # Supported Formats
372 ///
373 /// Which formats are available depends on enabled feature flags.
374 /// With default features: GeoTIFF, GeoJSON, Shapefile.
375 ///
376 /// # Errors
377 ///
378 /// Returns [`OxiGdalError::NotSupported`] if the format is not recognized
379 /// or the corresponding feature flag is not enabled.
380 ///
381 /// Returns [`OxiGdalError::Io`] if the file cannot be read.
382 pub fn open(path: &str) -> Result<Self> {
383 let format = DatasetFormat::from_extension(path);
384 Self::open_with_format(path, format)
385 }
386
387 /// Open a dataset with an explicitly specified format.
388 ///
389 /// Use this when auto-detection from extension is insufficient
390 /// (e.g., `.json` files that could be GeoJSON or STAC).
391 ///
392 /// # Errors
393 ///
394 /// Returns error if the format's feature flag is not enabled or file is unreadable.
395 pub fn open_with_format(path: &str, format: DatasetFormat) -> Result<Self> {
396 match format {
397 #[cfg(feature = "geotiff")]
398 DatasetFormat::GeoTiff => Self::open_raster_stub(path, DatasetFormat::GeoTiff),
399
400 #[cfg(feature = "geojson")]
401 DatasetFormat::GeoJson => Self::open_vector_stub(path, DatasetFormat::GeoJson),
402
403 #[cfg(feature = "shapefile")]
404 DatasetFormat::Shapefile => Self::open_vector_stub(path, DatasetFormat::Shapefile),
405
406 #[cfg(feature = "geoparquet")]
407 DatasetFormat::GeoParquet => Self::open_vector_stub(path, DatasetFormat::GeoParquet),
408
409 #[cfg(feature = "netcdf")]
410 DatasetFormat::NetCdf => Self::open_raster_stub(path, DatasetFormat::NetCdf),
411
412 #[cfg(feature = "hdf5")]
413 DatasetFormat::Hdf5 => Self::open_raster_stub(path, DatasetFormat::Hdf5),
414
415 #[cfg(feature = "zarr")]
416 DatasetFormat::Zarr => Self::open_raster_stub(path, DatasetFormat::Zarr),
417
418 #[cfg(feature = "grib")]
419 DatasetFormat::Grib => Self::open_raster_stub(path, DatasetFormat::Grib),
420
421 #[cfg(feature = "flatgeobuf")]
422 DatasetFormat::FlatGeobuf => Self::open_vector_stub(path, DatasetFormat::FlatGeobuf),
423
424 #[cfg(feature = "jpeg2000")]
425 DatasetFormat::Jpeg2000 => Self::open_raster_stub(path, DatasetFormat::Jpeg2000),
426
427 #[cfg(feature = "vrt")]
428 DatasetFormat::Vrt => Self::open_raster_stub(path, DatasetFormat::Vrt),
429
430 _ => Err(OxiGdalError::NotSupported {
431 operation: format!(
432 "Format '{}' for '{}' — enable the corresponding feature flag or check the file extension",
433 format.driver_name(),
434 path,
435 ),
436 }),
437 }
438 }
439
440 // -- Stub openers (delegate to driver crates in the future) ---------------
441
442 fn open_raster_stub(path: &str, format: DatasetFormat) -> Result<Self> {
443 // Verify file exists
444 if !std::path::Path::new(path).exists() {
445 return Err(OxiGdalError::Io(oxigdal_core::error::IoError::NotFound {
446 path: path.to_string(),
447 }));
448 }
449
450 Ok(Self {
451 path: path.to_string(),
452 info: DatasetInfo {
453 format,
454 width: None,
455 height: None,
456 band_count: 0,
457 layer_count: 0,
458 crs: None,
459 geotransform: None,
460 },
461 })
462 }
463
464 fn open_vector_stub(path: &str, format: DatasetFormat) -> Result<Self> {
465 if !std::path::Path::new(path).exists() {
466 return Err(OxiGdalError::Io(oxigdal_core::error::IoError::NotFound {
467 path: path.to_string(),
468 }));
469 }
470
471 Ok(Self {
472 path: path.to_string(),
473 info: DatasetInfo {
474 format,
475 width: None,
476 height: None,
477 band_count: 0,
478 layer_count: 0,
479 crs: None,
480 geotransform: None,
481 },
482 })
483 }
484
485 // -- Accessors (GDAL-like API) ------------------------------------------
486
487 /// File path this dataset was opened from.
488 pub fn path(&self) -> &str {
489 &self.path
490 }
491
492 /// Detected dataset format.
493 pub fn format(&self) -> DatasetFormat {
494 self.info.format
495 }
496
497 /// Full dataset info.
498 pub fn info(&self) -> &DatasetInfo {
499 &self.info
500 }
501
502 /// Width in pixels (raster datasets). Returns 0 for vector-only datasets.
503 pub fn width(&self) -> u32 {
504 self.info.width.unwrap_or(0)
505 }
506
507 /// Height in pixels (raster datasets). Returns 0 for vector-only datasets.
508 pub fn height(&self) -> u32 {
509 self.info.height.unwrap_or(0)
510 }
511
512 /// Coordinate reference system (WKT, EPSG code, or PROJ string).
513 pub fn crs(&self) -> Option<&str> {
514 self.info.crs.as_deref()
515 }
516
517 /// Number of raster bands.
518 pub fn band_count(&self) -> u32 {
519 self.info.band_count
520 }
521
522 /// Number of vector layers.
523 pub fn layer_count(&self) -> u32 {
524 self.info.layer_count
525 }
526
527 /// Geotransform coefficients.
528 ///
529 /// `[origin_x, pixel_width, rotation_x, origin_y, rotation_y, pixel_height]`
530 pub fn geotransform(&self) -> Option<&GeoTransform> {
531 self.info.geotransform.as_ref()
532 }
533}
534
535impl core::fmt::Debug for Dataset {
536 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
537 f.debug_struct("Dataset")
538 .field("path", &self.path)
539 .field("format", &self.info.format)
540 .field("width", &self.info.width)
541 .field("height", &self.info.height)
542 .field("band_count", &self.info.band_count)
543 .field("layer_count", &self.info.layer_count)
544 .finish()
545 }
546}
547
548// ─── Top-level functions ────────────────────────────────────────────────────
549
550/// OxiGDAL version string.
551///
552/// Equivalent to `GDALVersionInfo("RELEASE_NAME")` in C GDAL.
553pub fn version() -> &'static str {
554 env!("CARGO_PKG_VERSION")
555}
556
557/// List all enabled format drivers.
558///
559/// Equivalent to `GDALAllRegister()` + iterating registered drivers in C GDAL.
560///
561/// Returns a list of human-readable driver names for all features
562/// currently compiled in.
563///
564/// # Example
565///
566/// ```rust
567/// let drivers = oxigdal::drivers();
568/// assert!(drivers.contains(&"GTiff")); // default feature
569/// assert!(drivers.contains(&"GeoJSON")); // default feature
570/// assert!(drivers.contains(&"ESRI Shapefile")); // default feature
571/// ```
572pub fn drivers() -> Vec<&'static str> {
573 let mut list = Vec::new();
574
575 #[cfg(feature = "geotiff")]
576 list.push("GTiff");
577 #[cfg(feature = "geojson")]
578 list.push("GeoJSON");
579 #[cfg(feature = "shapefile")]
580 list.push("ESRI Shapefile");
581 #[cfg(feature = "geoparquet")]
582 list.push("GeoParquet");
583 #[cfg(feature = "netcdf")]
584 list.push("netCDF");
585 #[cfg(feature = "hdf5")]
586 list.push("HDF5");
587 #[cfg(feature = "zarr")]
588 list.push("Zarr");
589 #[cfg(feature = "grib")]
590 list.push("GRIB");
591 #[cfg(feature = "stac")]
592 list.push("STAC");
593 #[cfg(feature = "terrain")]
594 list.push("Terrain");
595 #[cfg(feature = "vrt")]
596 list.push("VRT");
597 #[cfg(feature = "flatgeobuf")]
598 list.push("FlatGeobuf");
599 #[cfg(feature = "jpeg2000")]
600 list.push("JPEG2000");
601
602 list
603}
604
605/// Number of registered (enabled) format drivers.
606///
607/// Equivalent to `GDALGetDriverCount()` in C GDAL.
608pub fn driver_count() -> usize {
609 drivers().len()
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
617 fn test_version() {
618 let v = version();
619 assert!(!v.is_empty());
620 assert!(v.starts_with("0."));
621 }
622
623 #[test]
624 fn test_default_drivers() {
625 let d = drivers();
626 // Default features: geotiff, geojson, shapefile
627 assert!(d.contains(&"GTiff"), "GeoTIFF should be a default driver");
628 assert!(d.contains(&"GeoJSON"), "GeoJSON should be a default driver");
629 assert!(
630 d.contains(&"ESRI Shapefile"),
631 "Shapefile should be a default driver"
632 );
633 }
634
635 #[test]
636 fn test_driver_count() {
637 assert!(driver_count() >= 3, "At least 3 default drivers");
638 }
639
640 #[test]
641 fn test_format_detection() {
642 assert_eq!(DatasetFormat::from_extension("world.tif"), DatasetFormat::GeoTiff);
643 assert_eq!(DatasetFormat::from_extension("data.geojson"), DatasetFormat::GeoJson);
644 assert_eq!(DatasetFormat::from_extension("map.shp"), DatasetFormat::Shapefile);
645 assert_eq!(DatasetFormat::from_extension("cloud.zarr"), DatasetFormat::Zarr);
646 assert_eq!(DatasetFormat::from_extension("output.parquet"), DatasetFormat::GeoParquet);
647 assert_eq!(DatasetFormat::from_extension("scene.vrt"), DatasetFormat::Vrt);
648 assert_eq!(DatasetFormat::from_extension("README.md"), DatasetFormat::Unknown);
649 }
650
651 #[test]
652 fn test_format_display() {
653 assert_eq!(DatasetFormat::GeoTiff.to_string(), "GTiff");
654 assert_eq!(DatasetFormat::GeoJson.to_string(), "GeoJSON");
655 }
656
657 #[test]
658 fn test_open_nonexistent() {
659 let result = Dataset::open("/nonexistent/file.tif");
660 assert!(result.is_err());
661 }
662
663 #[test]
664 fn test_open_unsupported_extension() {
665 let result = Dataset::open("data.xyz");
666 assert!(result.is_err());
667 }
668
669 #[test]
670 fn test_open_with_format() {
671 // Opening with explicit format for a nonexistent file should give IoError
672 let result = Dataset::open_with_format("/no/such/file.tif", DatasetFormat::GeoTiff);
673 assert!(result.is_err());
674 }
675}