use std::{
ffi::{CStr, CString, NulError},
path::Path,
ptr,
};
use gdal_sys::{CPLErr, GDALDatasetH, GDALMajorObjectH};
use crate::cpl::CslStringList;
use crate::errors::{GdalError, Result};
use crate::options::DatasetOptions;
use crate::raster::RasterCreationOptions;
use crate::utils::{_last_cpl_err, _last_null_pointer_err, _path_to_c_string, _string};
use crate::{
gdal_major_object::MajorObject, spatial_ref::SpatialRef, Driver, GeoTransform, Metadata,
};
pub struct DatasetCapability(&'static CStr);
impl DatasetCapability {
pub const CREATE_LAYER: DatasetCapability = DatasetCapability(c"CreateLayer");
pub const DELETE_LAYER: DatasetCapability = DatasetCapability(c"DeleteLayer");
pub const CREATE_GEOM_FIELD_AFTER_CREATE_LAYER: DatasetCapability =
DatasetCapability(c"CreateGeomFieldAfterCreateLayer");
pub const CURVE_GEOMETRIES: DatasetCapability = DatasetCapability(c"CurveGeometries");
pub const TRANSACTIONS: DatasetCapability = DatasetCapability(c"Transactions");
pub const EMULATED_TRANSACTIONS: DatasetCapability = DatasetCapability(c"EmulatedTransactions");
pub const RANDOM_LAYER_READ: DatasetCapability = DatasetCapability(c"RandomLayerRead");
pub const RANDOM_LAYER_WRITE: DatasetCapability = DatasetCapability(c"RandomLayerWrite");
}
#[derive(Debug)]
pub struct Dataset {
c_dataset: GDALDatasetH,
closed: bool,
}
unsafe impl Send for Dataset {}
impl Dataset {
pub fn c_dataset(&self) -> GDALDatasetH {
self.c_dataset
}
pub unsafe fn from_c_dataset(c_dataset: GDALDatasetH) -> Dataset {
Dataset {
c_dataset,
closed: false,
}
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Dataset> {
Self::_open_ex(path.as_ref(), DatasetOptions::default())
}
pub fn open_ex<P: AsRef<Path>>(path: P, options: DatasetOptions) -> Result<Dataset> {
Self::_open_ex(path.as_ref(), options)
}
fn _open_ex(path: &Path, options: DatasetOptions) -> Result<Dataset> {
crate::driver::_register_drivers();
let c_filename = _path_to_c_string(path)?;
let c_open_flags = options.open_flags.bits();
let c_allowed_drivers = options.allowed_drivers.map(|d| {
d.iter()
.map(|&s| CString::new(s))
.collect::<std::result::Result<Vec<CString>, NulError>>()
});
let c_drivers_vec = match c_allowed_drivers {
Some(Err(e)) => return Err(e.into()),
Some(Ok(c_drivers_vec)) => c_drivers_vec,
None => Vec::from([]),
};
let mut c_drivers_ptrs = c_drivers_vec.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
c_drivers_ptrs.push(ptr::null());
let c_drivers_ptr = if options.allowed_drivers.is_some() {
c_drivers_ptrs.as_ptr()
} else {
ptr::null()
};
let c_open_options = options.open_options.map(|d| {
d.iter()
.map(|&s| CString::new(s))
.collect::<std::result::Result<Vec<CString>, NulError>>()
});
let c_open_options_vec = match c_open_options {
Some(Err(e)) => return Err(e.into()),
Some(Ok(c_open_options_vec)) => c_open_options_vec,
None => Vec::from([]),
};
let mut c_open_options_ptrs = c_open_options_vec
.iter()
.map(|s| s.as_ptr())
.collect::<Vec<_>>();
c_open_options_ptrs.push(ptr::null());
let c_open_options_ptr = if options.open_options.is_some() {
c_open_options_ptrs.as_ptr()
} else {
ptr::null()
};
let c_sibling_files = options.sibling_files.map(|d| {
d.iter()
.map(|&s| CString::new(s))
.collect::<std::result::Result<Vec<CString>, NulError>>()
});
let c_sibling_files_vec = match c_sibling_files {
Some(Err(e)) => return Err(e.into()),
Some(Ok(c_sibling_files_vec)) => c_sibling_files_vec,
None => Vec::from([]),
};
let mut c_sibling_files_ptrs = c_sibling_files_vec
.iter()
.map(|s| s.as_ptr())
.collect::<Vec<_>>();
c_sibling_files_ptrs.push(ptr::null());
let c_sibling_files_ptr = if options.sibling_files.is_some() {
c_sibling_files_ptrs.as_ptr()
} else {
ptr::null()
};
let c_dataset = unsafe {
gdal_sys::GDALOpenEx(
c_filename.as_ptr(),
c_open_flags,
c_drivers_ptr,
c_open_options_ptr,
c_sibling_files_ptr,
)
};
if c_dataset.is_null() {
return Err(_last_null_pointer_err("GDALOpenEx"));
}
Ok(Dataset {
c_dataset,
closed: false,
})
}
pub fn flush_cache(&mut self) -> Result<()> {
#[cfg(any(all(major_ge_3, minor_ge_7), major_ge_4))]
{
let rv = unsafe { gdal_sys::GDALFlushCache(self.c_dataset) };
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));
}
}
#[cfg(not(any(all(major_is_3, minor_ge_7), major_ge_4)))]
{
unsafe {
gdal_sys::GDALFlushCache(self.c_dataset);
}
}
Ok(())
}
pub fn close(mut self) -> Result<()> {
self.closed = true;
#[cfg(any(all(major_ge_3, minor_ge_7), major_ge_4))]
{
let rv = unsafe { gdal_sys::GDALClose(self.c_dataset) };
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));
}
}
#[cfg(not(any(all(major_is_3, minor_ge_7), major_ge_4)))]
{
unsafe {
gdal_sys::GDALClose(self.c_dataset);
}
}
Ok(())
}
pub fn projection(&self) -> String {
let rv = unsafe { gdal_sys::GDALGetProjectionRef(self.c_dataset) };
_string(rv).unwrap_or_default()
}
pub fn set_projection(&mut self, projection: &str) -> Result<()> {
let c_projection = CString::new(projection)?;
unsafe { gdal_sys::GDALSetProjection(self.c_dataset, c_projection.as_ptr()) };
Ok(())
}
pub fn spatial_ref(&self) -> Result<SpatialRef> {
unsafe {
let spatial_ref = gdal_sys::GDALGetSpatialRef(self.c_dataset);
if spatial_ref.is_null() {
Err(GdalError::NullPointer {
method_name: "GDALGetSpatialRef",
msg: "Unable to get a spatial reference".to_string(),
})
} else {
SpatialRef::from_c_obj(spatial_ref)
}
}
}
pub fn set_spatial_ref(&mut self, spatial_ref: &SpatialRef) -> Result<()> {
let rv = unsafe { gdal_sys::GDALSetSpatialRef(self.c_dataset, spatial_ref.to_c_hsrs()) };
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));
}
Ok(())
}
pub fn create_copy<P: AsRef<Path>>(
&self,
driver: &Driver,
filename: P,
options: &RasterCreationOptions,
) -> Result<Dataset> {
fn _create_copy(
ds: &Dataset,
driver: &Driver,
filename: &Path,
options: &CslStringList,
) -> Result<Dataset> {
let c_filename = _path_to_c_string(filename)?;
let c_dataset = unsafe {
gdal_sys::GDALCreateCopy(
driver.c_driver(),
c_filename.as_ptr(),
ds.c_dataset,
0,
options.as_ptr(),
None,
ptr::null_mut(),
)
};
if c_dataset.is_null() {
return Err(_last_null_pointer_err("GDALCreateCopy"));
}
Ok(unsafe { Dataset::from_c_dataset(c_dataset) })
}
_create_copy(self, driver, filename.as_ref(), options)
}
pub fn driver(&self) -> Driver {
unsafe {
let c_driver = gdal_sys::GDALGetDatasetDriver(self.c_dataset);
Driver::from_c_driver(c_driver)
}
}
pub fn set_geo_transform(&mut self, transformation: &GeoTransform) -> Result<()> {
assert_eq!(transformation.len(), 6);
let rv = unsafe {
gdal_sys::GDALSetGeoTransform(self.c_dataset, transformation.as_ptr() as *mut f64)
};
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));
}
Ok(())
}
pub fn geo_transform(&self) -> Result<GeoTransform> {
let mut transformation = GeoTransform::default();
let rv =
unsafe { gdal_sys::GDALGetGeoTransform(self.c_dataset, transformation.as_mut_ptr()) };
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));
}
Ok(transformation)
}
pub fn has_capability(&self, capability: DatasetCapability) -> bool {
unsafe { gdal_sys::GDALDatasetTestCapability(self.c_dataset(), capability.0.as_ptr()) == 1 }
}
}
impl MajorObject for Dataset {
fn gdal_object_ptr(&self) -> GDALMajorObjectH {
self.c_dataset
}
}
impl Metadata for Dataset {}
impl Drop for Dataset {
fn drop(&mut self) {
if !self.closed {
unsafe {
gdal_sys::GDALClose(self.c_dataset);
}
}
}
}
#[cfg(test)]
mod tests {
use gdal_sys::GDALAccess;
use crate::dataset::DatasetCapability;
use crate::test_utils::{fixture, open_gpkg_for_update};
use crate::GdalOpenFlags;
use super::*;
#[test]
fn test_open_vector() {
let dataset = Dataset::open(fixture("roads.geojson")).unwrap();
dataset.close().unwrap();
}
#[test]
fn test_open_ex_ro_vector() {
Dataset::open_ex(
fixture("roads.geojson"),
DatasetOptions {
open_flags: GDALAccess::GA_ReadOnly.into(),
..DatasetOptions::default()
},
)
.unwrap();
}
#[test]
fn test_open_ex_update_vector() {
Dataset::open_ex(
fixture("roads.geojson"),
DatasetOptions {
open_flags: GDALAccess::GA_Update.into(),
..DatasetOptions::default()
},
)
.unwrap();
}
#[test]
fn test_open_ex_allowed_driver_vector() {
Dataset::open_ex(
fixture("roads.geojson"),
DatasetOptions {
allowed_drivers: Some(&["GeoJSON"]),
..DatasetOptions::default()
},
)
.unwrap();
}
#[test]
fn test_open_ex_allowed_driver_vector_fail() {
Dataset::open_ex(
fixture("roads.geojson"),
DatasetOptions {
allowed_drivers: Some(&["TIFF"]),
..DatasetOptions::default()
},
)
.unwrap_err();
}
#[test]
fn test_open_ex_open_option() {
Dataset::open_ex(
fixture("roads.geojson"),
DatasetOptions {
open_options: Some(&["FLATTEN_NESTED_ATTRIBUTES=YES"]),
..DatasetOptions::default()
},
)
.unwrap();
}
#[test]
fn test_open_ex_extended_flags_vector() {
Dataset::open_ex(
fixture("roads.geojson"),
DatasetOptions {
open_flags: GdalOpenFlags::GDAL_OF_UPDATE | GdalOpenFlags::GDAL_OF_VECTOR,
..DatasetOptions::default()
},
)
.unwrap();
}
#[test]
fn test_open_ex_extended_flags_vector_fail() {
Dataset::open_ex(
fixture("roads.geojson"),
DatasetOptions {
open_flags: GdalOpenFlags::GDAL_OF_UPDATE | GdalOpenFlags::GDAL_OF_RASTER,
..DatasetOptions::default()
},
)
.unwrap_err();
}
#[test]
fn test_dataset_capabilities() {
let ds = Dataset::open(fixture("poly.gpkg")).unwrap();
assert!(!ds.has_capability(DatasetCapability::CREATE_LAYER));
assert!(!ds.has_capability(DatasetCapability::DELETE_LAYER));
assert!(ds.has_capability(DatasetCapability::TRANSACTIONS));
let (_tmp_path, ds) = open_gpkg_for_update(&fixture("poly.gpkg"));
assert!(ds.has_capability(DatasetCapability::CREATE_LAYER));
assert!(ds.has_capability(DatasetCapability::DELETE_LAYER));
assert!(ds.has_capability(DatasetCapability::TRANSACTIONS));
}
#[test]
fn test_raster_count_on_vector() {
let ds = Dataset::open(fixture("roads.geojson")).unwrap();
assert_eq!(ds.raster_count(), 0);
}
}