use ptr::null_mut;
use std::convert::TryInto;
use std::{
ffi::NulError,
ffi::{CStr, CString},
path::Path,
ptr,
};
use crate::errors::*;
use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string};
use crate::vector::sql;
use crate::vector::Geometry;
use crate::{
gdal_major_object::MajorObject, raster::RasterBand, spatial_ref::SpatialRef, vector::Layer,
Driver, Metadata,
};
use gdal_sys::OGRGeometryH;
use gdal_sys::{
self, CPLErr, GDALAccess, GDALDatasetH, GDALMajorObjectH, OGRErr, OGRLayerH, OGRwkbGeometryType,
};
use libc::{c_double, c_int, c_uint};
use bitflags::bitflags;
pub type GeoTransform = [c_double; 6];
#[derive(Debug)]
pub struct Dataset {
c_dataset: GDALDatasetH,
}
#[cfg(major_ge_2)]
bitflags! {
pub struct GdalOpenFlags: c_uint {
const GDAL_OF_READONLY = 0x00;
const GDAL_OF_UPDATE = 0x01;
const GDAL_OF_ALL = 0x00;
const GDAL_OF_RASTER = 0x02;
const GDAL_OF_VECTOR = 0x04;
#[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_GNM = 0x08;
#[cfg(all(major_ge_3,minor_ge_1))]
const GDAL_OF_MULTIDIM_RASTER = 0x10;
const GDAL_OF_VERBOSE_ERROR = 0x40;
const GDAL_OF_INTERNAL = 0x80;
#[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_DEFAULT_BLOCK_ACCESS = 0;
#[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_ARRAY_BLOCK_ACCESS = 0x100;
#[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_HASHSET_BLOCK_ACCESS = 0x200;
}
}
impl Default for GdalOpenFlags {
fn default() -> GdalOpenFlags {
GdalOpenFlags::GDAL_OF_READONLY
}
}
impl From<GDALAccess::Type> for GdalOpenFlags {
fn from(val: GDALAccess::Type) -> GdalOpenFlags {
if val == GDALAccess::GA_Update {
GdalOpenFlags::GDAL_OF_UPDATE
} else {
GdalOpenFlags::GDAL_OF_READONLY
}
}
}
#[derive(Debug, Default)]
pub struct DatasetOptions<'a> {
pub open_flags: GdalOpenFlags,
pub allowed_drivers: Option<&'a [&'a str]>,
pub open_options: Option<&'a [&'a str]>,
pub sibling_files: Option<&'a [&'a str]>,
}
#[cfg(any(all(major_is_2, minor_ge_3), major_ge_3))]
unsafe impl Send for Dataset {}
impl Dataset {
pub unsafe fn c_dataset(&self) -> GDALDatasetH {
self.c_dataset
}
pub fn open(path: &Path) -> Result<Dataset> {
Self::open_ex(path, DatasetOptions::default())
}
pub fn open_ex(path: &Path, options: DatasetOptions) -> Result<Dataset> {
crate::driver::_register_drivers();
let filename = path.to_string_lossy();
let c_filename = CString::new(filename.as_ref())?;
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 })
}
pub unsafe fn from_c_dataset(c_dataset: GDALDatasetH) -> Dataset {
Dataset { c_dataset }
}
pub fn projection(&self) -> String {
let rv = unsafe { gdal_sys::GDALGetProjectionRef(self.c_dataset) };
_string(rv)
}
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(())
}
#[cfg(major_ge_3)]
pub fn spatial_ref(&self) -> Result<SpatialRef> {
unsafe { SpatialRef::from_c_obj(gdal_sys::GDALGetSpatialRef(self.c_dataset)) }
}
#[cfg(major_ge_3)]
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(&self, driver: &Driver, filename: &str) -> Result<Dataset> {
let c_filename = CString::new(filename)?;
let c_dataset = unsafe {
gdal_sys::GDALCreateCopy(
driver.c_driver(),
c_filename.as_ptr(),
self.c_dataset,
0,
ptr::null_mut(),
None,
ptr::null_mut(),
)
};
if c_dataset.is_null() {
return Err(_last_null_pointer_err("GDALCreateCopy"));
}
Ok(unsafe { Dataset::from_c_dataset(c_dataset) })
}
pub fn driver(&self) -> Driver {
unsafe {
let c_driver = gdal_sys::GDALGetDatasetDriver(self.c_dataset);
Driver::from_c_driver(c_driver)
}
}
pub fn rasterband(&self, band_index: isize) -> Result<RasterBand> {
unsafe {
let c_band = gdal_sys::GDALGetRasterBand(self.c_dataset, band_index as c_int);
if c_band.is_null() {
return Err(_last_null_pointer_err("GDALGetRasterBand"));
}
Ok(RasterBand::from_c_rasterband(self, c_band))
}
}
pub fn build_overviews(
&mut self,
resampling: &str,
overviews: &[i32],
bands: &[i32],
) -> Result<()> {
let c_resampling = CString::new(resampling)?;
let rv = unsafe {
gdal_sys::GDALBuildOverviews(
self.c_dataset,
c_resampling.as_ptr(),
overviews.len() as i32,
overviews.as_ptr() as *mut i32,
bands.len() as i32,
bands.as_ptr() as *mut i32,
None,
null_mut(),
)
};
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));
}
Ok(())
}
fn child_layer(&self, c_layer: OGRLayerH) -> Layer {
unsafe { Layer::from_c_layer(self, c_layer) }
}
pub fn layer_count(&self) -> isize {
(unsafe { gdal_sys::OGR_DS_GetLayerCount(self.c_dataset) }) as isize
}
pub fn layer(&self, idx: isize) -> Result<Layer> {
let c_layer = unsafe { gdal_sys::OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
if c_layer.is_null() {
return Err(_last_null_pointer_err("OGR_DS_GetLayer"));
}
Ok(self.child_layer(c_layer))
}
pub fn layer_by_name(&self, name: &str) -> Result<Layer> {
let c_name = CString::new(name)?;
let c_layer = unsafe { gdal_sys::OGR_DS_GetLayerByName(self.c_dataset(), c_name.as_ptr()) };
if c_layer.is_null() {
return Err(_last_null_pointer_err("OGR_DS_GetLayerByName"));
}
Ok(self.child_layer(c_layer))
}
pub fn layers(&self) -> LayerIterator {
LayerIterator::with_dataset(self)
}
pub fn raster_count(&self) -> isize {
(unsafe { gdal_sys::GDALGetRasterCount(self.c_dataset) }) as isize
}
pub fn raster_size(&self) -> (usize, usize) {
let size_x = unsafe { gdal_sys::GDALGetRasterXSize(self.c_dataset) } as usize;
let size_y = unsafe { gdal_sys::GDALGetRasterYSize(self.c_dataset) } as usize;
(size_x, size_y)
}
pub fn create_layer_blank(&mut self) -> Result<Layer> {
self.create_layer("", None, OGRwkbGeometryType::wkbUnknown)
}
pub fn create_layer(
&mut self,
name: &str,
srs: Option<&SpatialRef>,
ty: OGRwkbGeometryType::Type,
) -> Result<Layer> {
let c_name = CString::new(name)?;
let c_srs = match srs {
Some(srs) => srs.to_c_hsrs(),
None => null_mut(),
};
let c_layer = unsafe {
gdal_sys::OGR_DS_CreateLayer(self.c_dataset, c_name.as_ptr(), c_srs, ty, null_mut())
};
if c_layer.is_null() {
return Err(_last_null_pointer_err("OGR_DS_CreateLayer"));
};
Ok(self.child_layer(c_layer))
}
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 start_transaction(&mut self) -> Result<Transaction<'_>> {
let force = 1;
let rv = unsafe { gdal_sys::GDALDatasetStartTransaction(self.c_dataset, force) };
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "GDALDatasetStartTransaction",
});
}
Ok(Transaction::new(self))
}
pub fn execute_sql<S: AsRef<str>>(
&self,
query: S,
spatial_filter: Option<&Geometry>,
dialect: sql::Dialect,
) -> Result<Option<sql::ResultSet>> {
let query = CString::new(query.as_ref())?;
let dialect_c_str = match dialect {
sql::Dialect::DEFAULT => None,
sql::Dialect::OGR => Some(unsafe { CStr::from_bytes_with_nul_unchecked(sql::OGRSQL) }),
sql::Dialect::SQLITE => {
Some(unsafe { CStr::from_bytes_with_nul_unchecked(sql::SQLITE) })
}
};
self._execute_sql(query, spatial_filter, dialect_c_str)
}
fn _execute_sql(
&self,
query: CString,
spatial_filter: Option<&Geometry>,
dialect_c_str: Option<&CStr>,
) -> Result<Option<sql::ResultSet>> {
let mut filter_geom: OGRGeometryH = std::ptr::null_mut();
let dialect_ptr = match dialect_c_str {
None => std::ptr::null(),
Some(ref d) => d.as_ptr(),
};
if let Some(spatial_filter) = spatial_filter {
filter_geom = unsafe { spatial_filter.c_geometry() };
}
let c_dataset = unsafe { self.c_dataset() };
unsafe { gdal_sys::CPLErrorReset() };
let c_layer = unsafe {
gdal_sys::GDALDatasetExecuteSQL(c_dataset, query.as_ptr(), filter_geom, dialect_ptr)
};
let cpl_err = unsafe { gdal_sys::CPLGetLastErrorType() };
if cpl_err != CPLErr::CE_None {
return Err(_last_cpl_err(cpl_err));
}
if c_layer.is_null() {
return Ok(None);
}
let layer = unsafe { Layer::from_c_layer(self, c_layer) };
Ok(Some(sql::ResultSet {
layer,
dataset: c_dataset,
}))
}
}
pub struct LayerIterator<'a> {
dataset: &'a Dataset,
idx: isize,
count: isize,
}
impl<'a> Iterator for LayerIterator<'a> {
type Item = Layer<'a>;
#[inline]
fn next(&mut self) -> Option<Layer<'a>> {
let idx = self.idx;
if idx < self.count {
self.idx += 1;
let c_layer =
unsafe { gdal_sys::OGR_DS_GetLayer(self.dataset.c_dataset, idx as c_int) };
if !c_layer.is_null() {
let layer = unsafe { Layer::from_c_layer(self.dataset, c_layer) };
return Some(layer);
}
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
match Some(self.count).map(|s| s.try_into().ok()).flatten() {
Some(size) => (size, Some(size)),
None => (0, None),
}
}
}
impl<'a> LayerIterator<'a> {
pub fn with_dataset(dataset: &'a Dataset) -> LayerIterator<'a> {
LayerIterator {
dataset,
idx: 0,
count: dataset.layer_count(),
}
}
}
impl MajorObject for Dataset {
unsafe fn gdal_object_ptr(&self) -> GDALMajorObjectH {
self.c_dataset
}
}
impl Metadata for Dataset {}
impl Drop for Dataset {
fn drop(&mut self) {
unsafe {
gdal_sys::GDALClose(self.c_dataset);
}
}
}
#[derive(Debug)]
pub struct Transaction<'a> {
dataset: &'a mut Dataset,
rollback_on_drop: bool,
}
impl<'a> Transaction<'a> {
fn new(dataset: &'a mut Dataset) -> Self {
Transaction {
dataset,
rollback_on_drop: true,
}
}
pub fn dataset(&self) -> &Dataset {
self.dataset
}
pub fn dataset_mut(&mut self) -> &mut Dataset {
self.dataset
}
pub fn commit(mut self) -> Result<()> {
let rv = unsafe { gdal_sys::GDALDatasetCommitTransaction(self.dataset.c_dataset) };
self.rollback_on_drop = false;
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "GDALDatasetCommitTransaction",
});
}
Ok(())
}
pub fn rollback(mut self) -> Result<()> {
let rv = unsafe { gdal_sys::GDALDatasetRollbackTransaction(self.dataset.c_dataset) };
self.rollback_on_drop = false;
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "GDALDatasetRollbackTransaction",
});
}
Ok(())
}
}
impl<'a> Drop for Transaction<'a> {
fn drop(&mut self) {
if self.rollback_on_drop {
unsafe { gdal_sys::GDALDatasetRollbackTransaction(self.dataset.c_dataset) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vector::Geometry;
use tempfile::TempPath;
macro_rules! fixture {
($name:expr) => {
Path::new(file!())
.parent()
.unwrap()
.parent()
.unwrap()
.join("fixtures")
.as_path()
.join($name)
.as_path()
};
}
fn open_gpkg_for_update(path: &Path) -> (TempPath, Dataset) {
use std::fs;
use std::io::Write;
let input_data = fs::read(path).unwrap();
let (mut file, temp_path) = tempfile::Builder::new()
.suffix(".gpkg")
.tempfile()
.unwrap()
.into_parts();
file.write_all(&input_data).unwrap();
drop(file);
let ds = Dataset::open_ex(
&temp_path,
DatasetOptions {
open_flags: GDALAccess::GA_Update.into(),
allowed_drivers: Some(&["GPKG"]),
..DatasetOptions::default()
},
)
.unwrap();
(temp_path, ds)
}
fn polygon() -> Geometry {
Geometry::from_wkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))").unwrap()
}
#[test]
fn test_open_vector() {
Dataset::open(fixture!("roads.geojson")).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_layer_count() {
let ds = Dataset::open(fixture!("roads.geojson")).unwrap();
assert_eq!(ds.layer_count(), 1);
}
#[test]
fn test_raster_count_on_vector() {
let ds = Dataset::open(fixture!("roads.geojson")).unwrap();
assert_eq!(ds.raster_count(), 0);
}
#[test]
fn test_start_transaction() {
let (_temp_path, mut ds) = open_gpkg_for_update(fixture!("poly.gpkg"));
let txn = ds.start_transaction();
assert!(txn.is_ok());
}
#[test]
fn test_transaction_commit() {
let (_temp_path, mut ds) = open_gpkg_for_update(fixture!("poly.gpkg"));
let orig_feature_count = ds.layer(0).unwrap().feature_count();
let mut txn = ds.start_transaction().unwrap();
let mut layer = txn.dataset_mut().layer(0).unwrap();
layer.create_feature(polygon()).unwrap();
assert!(txn.commit().is_ok());
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1);
}
#[test]
fn test_transaction_rollback() {
let (_temp_path, mut ds) = open_gpkg_for_update(fixture!("poly.gpkg"));
let orig_feature_count = ds.layer(0).unwrap().feature_count();
let mut txn = ds.start_transaction().unwrap();
let mut layer = txn.dataset_mut().layer(0).unwrap();
layer.create_feature(polygon()).unwrap();
assert!(txn.rollback().is_ok());
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count);
}
#[test]
fn test_transaction_implicit_rollback() {
let (_temp_path, mut ds) = open_gpkg_for_update(fixture!("poly.gpkg"));
let orig_feature_count = ds.layer(0).unwrap().feature_count();
{
let mut txn = ds.start_transaction().unwrap();
let mut layer = txn.dataset_mut().layer(0).unwrap();
layer.create_feature(polygon()).unwrap();
}
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count);
}
#[test]
fn test_start_transaction_unsupported() {
let mut ds = Dataset::open(fixture!("roads.geojson")).unwrap();
assert!(ds.start_transaction().is_err());
}
}