#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub use oxigdal_core::error::OxiGdalError;
pub use oxigdal_core::error::Result;
pub use oxigdal_core::types::{BoundingBox, GeoTransform, RasterDataType, RasterMetadata};
pub mod open;
pub mod builder;
pub mod streaming;
pub mod convert;
pub use builder::{
CompressionType, CreateOptions, DatasetCreateBuilder, DatasetOpenBuilder, DatasetWriter,
OutputFormat,
};
pub use open::{CloudScheme, OpenedDataset, open};
pub use streaming::{FeatureStream, RasterTile, StreamingExt, StreamingFeature, TileStream};
pub use oxigdal_core as core_types;
#[cfg(feature = "geotiff")]
#[cfg_attr(docsrs, doc(cfg(feature = "geotiff")))]
pub use oxigdal_geotiff as geotiff;
#[cfg(feature = "geojson")]
#[cfg_attr(docsrs, doc(cfg(feature = "geojson")))]
pub use oxigdal_geojson as geojson;
#[cfg(feature = "shapefile")]
#[cfg_attr(docsrs, doc(cfg(feature = "shapefile")))]
pub use oxigdal_shapefile as shapefile;
#[cfg(feature = "geoparquet")]
#[cfg_attr(docsrs, doc(cfg(feature = "geoparquet")))]
pub use oxigdal_geoparquet as geoparquet;
#[cfg(feature = "netcdf")]
#[cfg_attr(docsrs, doc(cfg(feature = "netcdf")))]
pub use oxigdal_netcdf as netcdf;
#[cfg(feature = "hdf5")]
#[cfg_attr(docsrs, doc(cfg(feature = "hdf5")))]
pub use oxigdal_hdf5 as hdf5;
#[cfg(feature = "zarr")]
#[cfg_attr(docsrs, doc(cfg(feature = "zarr")))]
pub use oxigdal_zarr as zarr;
#[cfg(feature = "grib")]
#[cfg_attr(docsrs, doc(cfg(feature = "grib")))]
pub use oxigdal_grib as grib;
#[cfg(feature = "stac")]
#[cfg_attr(docsrs, doc(cfg(feature = "stac")))]
pub use oxigdal_stac as stac;
#[cfg(feature = "terrain")]
#[cfg_attr(docsrs, doc(cfg(feature = "terrain")))]
pub use oxigdal_terrain as terrain;
#[cfg(feature = "vrt")]
#[cfg_attr(docsrs, doc(cfg(feature = "vrt")))]
pub use oxigdal_vrt as vrt;
#[cfg(feature = "flatgeobuf")]
#[cfg_attr(docsrs, doc(cfg(feature = "flatgeobuf")))]
pub use oxigdal_flatgeobuf as flatgeobuf;
#[cfg(feature = "jpeg2000")]
#[cfg_attr(docsrs, doc(cfg(feature = "jpeg2000")))]
pub use oxigdal_jpeg2000 as jpeg2000;
#[cfg(feature = "cloud")]
#[cfg_attr(docsrs, doc(cfg(feature = "cloud")))]
pub use oxigdal_cloud as cloud;
#[cfg(feature = "proj")]
#[cfg_attr(docsrs, doc(cfg(feature = "proj")))]
pub use oxigdal_proj as proj;
#[cfg(feature = "algorithms")]
#[cfg_attr(docsrs, doc(cfg(feature = "algorithms")))]
pub use oxigdal_algorithms as algorithms;
#[cfg(feature = "analytics")]
#[cfg_attr(docsrs, doc(cfg(feature = "analytics")))]
pub use oxigdal_analytics as analytics;
#[cfg(feature = "streaming")]
#[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
pub use oxigdal_streaming as streaming_ext;
#[cfg(feature = "ml")]
#[cfg_attr(docsrs, doc(cfg(feature = "ml")))]
pub use oxigdal_ml as ml;
#[cfg(feature = "gpu")]
#[cfg_attr(docsrs, doc(cfg(feature = "gpu")))]
pub use oxigdal_gpu as gpu;
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
pub use oxigdal_server as server;
#[cfg(feature = "temporal")]
#[cfg_attr(docsrs, doc(cfg(feature = "temporal")))]
pub use oxigdal_temporal as temporal;
#[cfg(feature = "gpkg")]
#[cfg_attr(docsrs, doc(cfg(feature = "gpkg")))]
pub use oxigdal_gpkg as gpkg;
#[cfg(feature = "pmtiles")]
#[cfg_attr(docsrs, doc(cfg(feature = "pmtiles")))]
pub use oxigdal_pmtiles as pmtiles;
#[cfg(feature = "mbtiles")]
#[cfg_attr(docsrs, doc(cfg(feature = "mbtiles")))]
pub use oxigdal_mbtiles as mbtiles;
#[cfg(feature = "copc")]
#[cfg_attr(docsrs, doc(cfg(feature = "copc")))]
pub use oxigdal_copc as copc;
#[cfg(feature = "index")]
#[cfg_attr(docsrs, doc(cfg(feature = "index")))]
pub use oxigdal_index as index;
#[cfg(feature = "noalloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "noalloc")))]
pub use oxigdal_noalloc as noalloc;
#[cfg(feature = "services")]
#[cfg_attr(docsrs, doc(cfg(feature = "services")))]
pub use oxigdal_services as services;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DatasetFormat {
GeoTiff,
GeoJson,
Shapefile,
GeoParquet,
NetCdf,
Hdf5,
Zarr,
Grib,
Stac,
Terrain,
Vrt,
FlatGeobuf,
Jpeg2000,
GeoPackage,
PMTiles,
MBTiles,
Copc,
Unknown,
}
impl DatasetFormat {
pub fn from_extension(path: &str) -> Self {
let lower = path.to_lowercase();
if lower.ends_with(".copc.laz") {
return Self::Copc;
}
let ext = std::path::Path::new(path)
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase())
.unwrap_or_default();
match ext.as_str() {
"tif" | "tiff" => Self::GeoTiff,
"geojson" => Self::GeoJson,
"shp" => Self::Shapefile,
"parquet" | "geoparquet" => Self::GeoParquet,
"nc" | "nc4" => Self::NetCdf,
"h5" | "hdf5" | "he5" => Self::Hdf5,
"zarr" => Self::Zarr,
"grib" | "grib2" | "grb" | "grb2" => Self::Grib,
"vrt" => Self::Vrt,
"fgb" => Self::FlatGeobuf,
"jp2" | "j2k" => Self::Jpeg2000,
"gpkg" => Self::GeoPackage,
"pmtiles" => Self::PMTiles,
"mbtiles" => Self::MBTiles,
"laz" | "las" => Self::Copc,
_ => Self::Unknown,
}
}
pub fn driver_name(&self) -> &'static str {
match self {
Self::GeoTiff => "GTiff",
Self::GeoJson => "GeoJSON",
Self::Shapefile => "ESRI Shapefile",
Self::GeoParquet => "GeoParquet",
Self::NetCdf => "netCDF",
Self::Hdf5 => "HDF5",
Self::Zarr => "Zarr",
Self::Grib => "GRIB",
Self::Stac => "STAC",
Self::Terrain => "Terrain",
Self::Vrt => "VRT",
Self::FlatGeobuf => "FlatGeobuf",
Self::Jpeg2000 => "JPEG2000",
Self::GeoPackage => "GPKG",
Self::PMTiles => "PMTiles",
Self::MBTiles => "MBTiles",
Self::Copc => "COPC",
Self::Unknown => "Unknown",
}
}
}
impl core::fmt::Display for DatasetFormat {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.driver_name())
}
}
#[derive(Debug, Clone)]
pub struct DatasetInfo {
pub format: DatasetFormat,
pub width: Option<u32>,
pub height: Option<u32>,
pub band_count: u32,
pub layer_count: u32,
pub crs: Option<String>,
pub geotransform: Option<GeoTransform>,
}
pub struct Dataset {
path: String,
info: DatasetInfo,
}
impl Dataset {
pub fn open(path: &str) -> Result<Self> {
let format = DatasetFormat::from_extension(path);
Self::open_with_format(path, format)
}
pub fn open_with_format(path: &str, format: DatasetFormat) -> Result<Self> {
match format {
#[cfg(feature = "geotiff")]
DatasetFormat::GeoTiff => Self::open_raster_stub(path, DatasetFormat::GeoTiff),
#[cfg(feature = "geojson")]
DatasetFormat::GeoJson => Self::open_vector_stub(path, DatasetFormat::GeoJson),
#[cfg(feature = "shapefile")]
DatasetFormat::Shapefile => Self::open_vector_stub(path, DatasetFormat::Shapefile),
#[cfg(feature = "geoparquet")]
DatasetFormat::GeoParquet => Self::open_vector_stub(path, DatasetFormat::GeoParquet),
#[cfg(feature = "netcdf")]
DatasetFormat::NetCdf => Self::open_raster_stub(path, DatasetFormat::NetCdf),
#[cfg(feature = "hdf5")]
DatasetFormat::Hdf5 => Self::open_raster_stub(path, DatasetFormat::Hdf5),
#[cfg(feature = "zarr")]
DatasetFormat::Zarr => Self::open_raster_stub(path, DatasetFormat::Zarr),
#[cfg(feature = "grib")]
DatasetFormat::Grib => Self::open_raster_stub(path, DatasetFormat::Grib),
#[cfg(feature = "flatgeobuf")]
DatasetFormat::FlatGeobuf => Self::open_vector_stub(path, DatasetFormat::FlatGeobuf),
#[cfg(feature = "jpeg2000")]
DatasetFormat::Jpeg2000 => Self::open_raster_stub(path, DatasetFormat::Jpeg2000),
#[cfg(feature = "vrt")]
DatasetFormat::Vrt => Self::open_raster_stub(path, DatasetFormat::Vrt),
#[cfg(feature = "gpkg")]
DatasetFormat::GeoPackage => Self::open_vector_stub(path, DatasetFormat::GeoPackage),
#[cfg(feature = "pmtiles")]
DatasetFormat::PMTiles => Self::open_raster_stub(path, DatasetFormat::PMTiles),
#[cfg(feature = "mbtiles")]
DatasetFormat::MBTiles => Self::open_raster_stub(path, DatasetFormat::MBTiles),
#[cfg(feature = "copc")]
DatasetFormat::Copc => Self::open_raster_stub(path, DatasetFormat::Copc),
_ => Err(OxiGdalError::NotSupported {
operation: format!(
"Format '{}' for '{}' — enable the corresponding feature flag or check the file extension",
format.driver_name(),
path,
),
}),
}
}
fn open_raster_stub(path: &str, format: DatasetFormat) -> Result<Self> {
if !std::path::Path::new(path).exists() {
return Err(OxiGdalError::Io(oxigdal_core::error::IoError::NotFound {
path: path.to_string(),
}));
}
Ok(Self {
path: path.to_string(),
info: DatasetInfo {
format,
width: None,
height: None,
band_count: 0,
layer_count: 0,
crs: None,
geotransform: None,
},
})
}
fn open_vector_stub(path: &str, format: DatasetFormat) -> Result<Self> {
if !std::path::Path::new(path).exists() {
return Err(OxiGdalError::Io(oxigdal_core::error::IoError::NotFound {
path: path.to_string(),
}));
}
Ok(Self {
path: path.to_string(),
info: DatasetInfo {
format,
width: None,
height: None,
band_count: 0,
layer_count: 0,
crs: None,
geotransform: None,
},
})
}
pub fn path(&self) -> &str {
&self.path
}
pub fn format(&self) -> DatasetFormat {
self.info.format
}
pub fn info(&self) -> &DatasetInfo {
&self.info
}
pub fn width(&self) -> u32 {
self.info.width.unwrap_or(0)
}
pub fn height(&self) -> u32 {
self.info.height.unwrap_or(0)
}
pub fn crs(&self) -> Option<&str> {
self.info.crs.as_deref()
}
pub fn band_count(&self) -> u32 {
self.info.band_count
}
pub fn layer_count(&self) -> u32 {
self.info.layer_count
}
pub fn geotransform(&self) -> Option<&GeoTransform> {
self.info.geotransform.as_ref()
}
}
impl core::fmt::Debug for Dataset {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Dataset")
.field("path", &self.path)
.field("format", &self.info.format)
.field("width", &self.info.width)
.field("height", &self.info.height)
.field("band_count", &self.info.band_count)
.field("layer_count", &self.info.layer_count)
.finish()
}
}
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[allow(clippy::vec_init_then_push)]
pub fn drivers() -> Vec<&'static str> {
let mut list = Vec::new();
#[cfg(feature = "geotiff")]
list.push("GTiff");
#[cfg(feature = "geojson")]
list.push("GeoJSON");
#[cfg(feature = "shapefile")]
list.push("ESRI Shapefile");
#[cfg(feature = "geoparquet")]
list.push("GeoParquet");
#[cfg(feature = "netcdf")]
list.push("netCDF");
#[cfg(feature = "hdf5")]
list.push("HDF5");
#[cfg(feature = "zarr")]
list.push("Zarr");
#[cfg(feature = "grib")]
list.push("GRIB");
#[cfg(feature = "stac")]
list.push("STAC");
#[cfg(feature = "terrain")]
list.push("Terrain");
#[cfg(feature = "vrt")]
list.push("VRT");
#[cfg(feature = "flatgeobuf")]
list.push("FlatGeobuf");
#[cfg(feature = "jpeg2000")]
list.push("JPEG2000");
#[cfg(feature = "gpkg")]
list.push("GPKG");
#[cfg(feature = "pmtiles")]
list.push("PMTiles");
#[cfg(feature = "mbtiles")]
list.push("MBTiles");
#[cfg(feature = "copc")]
list.push("COPC");
list
}
pub fn driver_count() -> usize {
drivers().len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version() {
let v = version();
assert!(!v.is_empty());
assert!(v.starts_with("0."));
}
#[test]
fn test_default_drivers() {
let d = drivers();
assert!(d.contains(&"GTiff"), "GeoTIFF should be a default driver");
assert!(d.contains(&"GeoJSON"), "GeoJSON should be a default driver");
assert!(
d.contains(&"ESRI Shapefile"),
"Shapefile should be a default driver"
);
}
#[test]
fn test_driver_count() {
assert!(driver_count() >= 3, "At least 3 default drivers");
}
#[test]
fn test_format_detection() {
assert_eq!(
DatasetFormat::from_extension("world.tif"),
DatasetFormat::GeoTiff
);
assert_eq!(
DatasetFormat::from_extension("data.geojson"),
DatasetFormat::GeoJson
);
assert_eq!(
DatasetFormat::from_extension("map.shp"),
DatasetFormat::Shapefile
);
assert_eq!(
DatasetFormat::from_extension("cloud.zarr"),
DatasetFormat::Zarr
);
assert_eq!(
DatasetFormat::from_extension("output.parquet"),
DatasetFormat::GeoParquet
);
assert_eq!(
DatasetFormat::from_extension("scene.vrt"),
DatasetFormat::Vrt
);
assert_eq!(
DatasetFormat::from_extension("README.md"),
DatasetFormat::Unknown
);
}
#[test]
fn test_format_display() {
assert_eq!(DatasetFormat::GeoTiff.to_string(), "GTiff");
assert_eq!(DatasetFormat::GeoJson.to_string(), "GeoJSON");
}
#[test]
fn test_open_nonexistent() {
let result = Dataset::open("/nonexistent/file.tif");
assert!(result.is_err());
}
#[test]
fn test_open_unsupported_extension() {
let result = Dataset::open("data.xyz");
assert!(result.is_err());
}
#[test]
fn test_open_with_format() {
let result = Dataset::open_with_format("/no/such/file.tif", DatasetFormat::GeoTiff);
assert!(result.is_err());
}
}