use crate::dataset::{VectorDataset, VectorOpenOptions, finish_vector};
use crate::drivers::common;
use crate::formats::{RasterDriver, VectorDriver};
use crate::vfs::Location;
use crate::{Dataset, Error, RasterDataset, Result};
use gdal::raster::GdalDataType;
use gdal::vector::{FieldValue as GdalValue, LayerAccess, OGRFieldType, OGRwkbGeometryType};
use gdal::{Dataset as GdalDataset, DatasetOptions, DriverManager, GdalOpenFlags, Metadata};
use wbraster::raster::RasterData;
use wbvector::{Feature, FieldDef, FieldType, FieldValue, Geometry, GeometryType, Layer};
pub const PREFIX: &str = "GDAL:";
pub fn gdal_version() -> String {
gdal::version_info("RELEASE_NAME")
}
pub fn has_driver(name: &str) -> bool {
DriverManager::get_driver_by_name(name).is_ok()
}
pub fn strip_prefix(path: &str) -> Option<&str> {
path.get(..PREFIX.len()).filter(|p| p.eq_ignore_ascii_case(PREFIX)).map(|_| &path[PREFIX.len()..])
}
pub fn open(path: &str) -> Result<Dataset> {
open_with(path, &VectorOpenOptions::default())
}
pub(crate) fn open_with(path: &str, options: &VectorOpenOptions) -> Result<Dataset> {
let _quiet = Quiet::new();
let ds = gdal_open(path, GdalOpenFlags::GDAL_OF_RASTER | GdalOpenFlags::GDAL_OF_VECTOR)?;
if ds.raster_count() > 0 || !subdatasets_of(&ds).is_empty() {
return raster_from(&ds, path).map(Dataset::Raster);
}
vector_from(ds, path, options).map(Dataset::Vector)
}
pub fn open_raster(path: &str) -> Result<RasterDataset> {
let _quiet = Quiet::new();
let ds = gdal_open(path, GdalOpenFlags::GDAL_OF_RASTER)?;
raster_from(&ds, path)
}
pub fn open_vector(path: &str, options: &VectorOpenOptions) -> Result<VectorDataset> {
let _quiet = Quiet::new();
let ds = gdal_open(path, GdalOpenFlags::GDAL_OF_VECTOR)?;
vector_from(ds, path, options)
}
pub fn list_layers(path: &str) -> Result<Vec<String>> {
let _quiet = Quiet::new();
let ds = gdal_open(path, GdalOpenFlags::GDAL_OF_VECTOR)?;
Ok(ds.layers().map(|l| l.name()).collect())
}
pub fn subdatasets(path: &str) -> Result<Vec<(String, String)>> {
let _quiet = Quiet::new();
let ds = gdal_open(path, GdalOpenFlags::GDAL_OF_RASTER)?;
Ok(subdatasets_of(&ds))
}
pub fn write_raster(ds: &RasterDataset, path: &str, driver: &str, creation_options: &[(String, String)]) -> Result<()> {
write_raster_gt(&ds.raster, ds.nodata(), ds.complex, ds.affine, path, driver, creation_options)
}
fn complex_type(t: wbraster::DataType) -> Option<gdal_sys::GDALDataType::Type> {
use gdal_sys::GDALDataType::*;
Some(match t {
wbraster::DataType::I16 => GDT_CInt16,
wbraster::DataType::I32 => GDT_CInt32,
wbraster::DataType::F32 => GDT_CFloat32,
wbraster::DataType::F64 => GDT_CFloat64,
_ => return None,
})
}
fn complex_mem_dataset(r: &wbraster::Raster) -> Result<GdalDataset> {
let t = complex_type(r.data_type).ok_or_else(|| Error::Unsupported(format!("complex bands of {:?} parts", r.data_type)))?;
let bands = r.bands / 2;
let mem = DriverManager::get_driver_by_name("MEM").map_err(gdal_err)?;
let too_large = || Error::InvalidArgument("raster too large for GDAL".into());
let (cols, rows) = (i32::try_from(r.cols).map_err(|_| too_large())?, i32::try_from(r.rows).map_err(|_| too_large())?);
let n = r.cols * r.rows;
let size = r.data_type.size_bytes();
macro_rules! interleave {
($v:ident) => {
(0..bands)
.map(|b| {
let (re, im) = (&$v[2 * b * n..(2 * b + 1) * n], &$v[(2 * b + 1) * n..(2 * b + 2) * n]);
let mut out = Vec::with_capacity(n * 2 * size);
for i in 0..n {
out.extend_from_slice(&re[i].to_ne_bytes());
out.extend_from_slice(&im[i].to_ne_bytes());
}
out
})
.collect::<Vec<Vec<u8>>>()
};
}
let buffers = match &r.data {
RasterData::I16(v) => interleave!(v),
RasterData::I32(v) => interleave!(v),
RasterData::F32(v) => interleave!(v),
RasterData::F64(v) => interleave!(v),
_ => return Err(Error::Unsupported(format!("complex bands of {:?} parts", r.data_type))),
};
let name = std::ffi::CString::default();
unsafe {
let h = gdal_sys::GDALCreate(mem.c_driver(), name.as_ptr(), cols, rows, bands as i32, t, std::ptr::null_mut());
if h.is_null() {
return Err(Error::InvalidData(format!("GDAL: cannot create a complex MEM dataset: {}", last_error().unwrap_or_default())));
}
let ds = GdalDataset::from_c_dataset(h);
for (b, mut buf) in buffers.into_iter().enumerate() {
let band = gdal_sys::GDALGetRasterBand(h, b as i32 + 1);
let e = gdal_sys::GDALRasterIO(
band,
gdal_sys::GDALRWFlag::GF_Write,
0,
0,
cols,
rows,
buf.as_mut_ptr().cast(),
cols,
rows,
t,
0,
0,
);
if e != gdal_sys::CPLErr::CE_None {
return Err(Error::InvalidData(format!("GDAL: {}", last_error().unwrap_or_default())));
}
}
Ok(ds)
}
}
fn read_complex(ds: &GdalDataset, t: gdal_sys::GDALDataType::Type, cols: usize, rows: usize, bands: usize) -> Result<RasterData> {
use gdal_sys::GDALDataType::*;
let (ct, part) = if t == GDT_CInt16 {
(GDT_CInt16, wbraster::DataType::I16)
} else if t == GDT_CInt32 {
(GDT_CInt32, wbraster::DataType::I32)
} else if t == GDT_CFloat64 {
(GDT_CFloat64, wbraster::DataType::F64)
} else {
(GDT_CFloat32, wbraster::DataType::F32)
};
let size = part.size_bytes();
let n = cols * rows;
let mut raw = vec![0u8; n * 2 * size];
let mut parts: Vec<u8> = Vec::with_capacity(bands * 2 * n * size);
for b in 1..=bands {
let band = ds.rasterband(b).map_err(gdal_err)?;
let e = unsafe {
gdal_sys::GDALRasterIO(
band.c_rasterband(),
gdal_sys::GDALRWFlag::GF_Read,
0,
0,
cols as i32,
rows as i32,
raw.as_mut_ptr().cast(),
cols as i32,
rows as i32,
ct,
0,
0,
)
};
if e != gdal_sys::CPLErr::CE_None {
return Err(Error::InvalidData(format!("GDAL: {}", last_error().unwrap_or_default())));
}
for offset in [0, size] {
for i in 0..n {
let at = i * 2 * size + offset;
parts.extend_from_slice(&raw[at..at + size]);
}
}
}
let layout = common::Layout {
cols,
rows,
bands: bands * 2,
data_type: part,
little_endian: cfg!(target_endian = "little"),
interleave: common::Interleave::Bsq,
offset: 0,
bottom_up: false,
};
common::decode(&parts, &layout)
}
pub(crate) fn write_raster_with(
r: &wbraster::Raster,
nodata: Option<f64>,
complex: bool,
path: &str,
driver: &str,
creation_options: &[(String, String)],
) -> Result<()> {
write_raster_gt(r, nodata, complex, None, path, driver, creation_options)
}
pub(crate) fn write_raster_gt(
r: &wbraster::Raster,
nodata: Option<f64>,
complex: bool,
affine: Option<[f64; 6]>,
path: &str,
driver: &str,
creation_options: &[(String, String)],
) -> Result<()> {
let _quiet = Quiet::new();
let target =
DriverManager::get_driver_by_name(driver).map_err(|e| Error::Unsupported(format!("GDAL has no driver '{driver}': {e}")))?;
if complex {
let mut mem = complex_mem_dataset(r)?;
return finish_copy(&mut mem, r, nodata, affine, path, &target, creation_options);
}
let mem_driver = DriverManager::get_driver_by_name("MEM").map_err(gdal_err)?;
macro_rules! create {
($t:ty, $v:ident) => {{
let mem = mem_driver.create_with_band_type::<$t, _>("", r.cols, r.rows, r.bands).map_err(gdal_err)?;
if let RasterData::$v(values) = &r.data {
let n = r.cols * r.rows;
for b in 0..r.bands {
let mut band = mem.rasterband(b + 1).map_err(gdal_err)?;
let mut buffer = gdal::raster::Buffer::new((r.cols, r.rows), values[b * n..(b + 1) * n].to_vec());
band.write((0, 0), (r.cols, r.rows), &mut buffer).map_err(gdal_err)?;
}
}
mem
}};
}
let mut mem = match r.data_type {
wbraster::DataType::U8 => create!(u8, U8),
wbraster::DataType::I8 => create!(i8, I8),
wbraster::DataType::U16 => create!(u16, U16),
wbraster::DataType::I16 => create!(i16, I16),
wbraster::DataType::U32 => create!(u32, U32),
wbraster::DataType::I32 => create!(i32, I32),
wbraster::DataType::U64 => create!(u64, U64),
wbraster::DataType::I64 => create!(i64, I64),
wbraster::DataType::F32 => create!(f32, F32),
wbraster::DataType::F64 => create!(f64, F64),
};
finish_copy(&mut mem, r, nodata, affine, path, &target, creation_options)
}
fn finish_copy(
mem: &mut GdalDataset,
r: &wbraster::Raster,
nodata: Option<f64>,
affine: Option<[f64; 6]>,
path: &str,
target: &gdal::Driver,
creation_options: &[(String, String)],
) -> Result<()> {
let gt = affine.unwrap_or_else(|| crate::geo_transform(r));
mem.set_geo_transform(>).map_err(gdal_err)?;
let crs = crate::crs::SpatialRef::from_raster_crs(&r.crs);
let srs = match crs.epsg {
Some(code) => gdal::spatial_ref::SpatialRef::from_epsg(code).ok(),
None => crs.to_wkt().and_then(|wkt| gdal::spatial_ref::SpatialRef::from_wkt(&wkt).ok()),
};
if let Some(srs) = srs {
mem.set_spatial_ref(&srs).map_err(gdal_err)?;
}
if let Some(nodata) = nodata {
for b in 1..=mem.raster_count() {
let mut band = mem.rasterband(b).map_err(gdal_err)?;
match r.data_type {
wbraster::DataType::U64 => band.set_no_data_value_u64(Some(nodata as u64)),
wbraster::DataType::I64 => band.set_no_data_value_i64(Some(nodata as i64)),
_ => band.set_no_data_value(Some(nodata)),
}
.map_err(gdal_err)?;
}
}
let mut options = gdal::cpl::CslStringList::new();
for (k, v) in creation_options {
options.set_name_value(k, v).map_err(gdal_err)?;
}
mem.create_copy(target, path, &options).map_err(gdal_err)?;
Ok(())
}
pub fn driver_for_path(path: &str, vector: bool) -> Option<String> {
let ext = crate::formats::extension(path)?;
let capability = if vector { "DCAP_VECTOR" } else { "DCAP_RASTER" };
let mut found: Vec<String> = Vec::new();
for d in DriverManager::all() {
let yes = |key: &str| d.metadata_item(key, "").is_some_and(|v| v.eq_ignore_ascii_case("YES"));
let creates = yes("DCAP_CREATE") || (!vector && yes("DCAP_CREATECOPY"));
if !yes(capability) || !creates {
continue;
}
let extensions = d.metadata_item("DMD_EXTENSIONS", "").or_else(|| d.metadata_item("DMD_EXTENSION", ""));
if extensions.is_some_and(|e| e.split_whitespace().any(|x| x.eq_ignore_ascii_case(&ext))) {
found.push(d.short_name());
}
}
match found.as_slice() {
[one] => Some(one.clone()),
[first, ..] if ext.eq_ignore_ascii_case("gdb") => Some(first.clone()),
_ => None,
}
}
pub fn write_vector(
layers: &[Layer],
path: &str,
driver: &str,
dataset_options: &[(String, String)],
layer_options: &[(String, String)],
) -> Result<()> {
if layers.is_empty() {
return Err(Error::InvalidArgument("no layers to write".into()));
}
let _quiet = Quiet::new();
let target =
DriverManager::get_driver_by_name(driver).map_err(|e| Error::Unsupported(format!("GDAL has no driver '{driver}': {e}")))?;
let is_vector = target.metadata_item("DCAP_VECTOR", "").is_some_and(|v| v.eq_ignore_ascii_case("YES"));
if !is_vector || target.metadata_item("DCAP_CREATE", "").is_none() {
return Err(Error::Unsupported(format!("GDAL's {driver} driver cannot create vector datasets")));
}
let p = std::path::Path::new(path);
if p.exists() {
if target.delete(path).is_err() && p.exists() {
if !p.is_dir() {
std::fs::remove_file(p)?;
} else if std::fs::read_dir(p)?.next().is_none() {
std::fs::remove_dir(p)?;
} else {
return Err(Error::InvalidArgument(format!("'{path}' is a directory that is not a {driver} dataset: not replaced")));
}
}
}
let mut options = gdal::cpl::CslStringList::new();
for (k, v) in dataset_options {
options.set_name_value(k, v).map_err(gdal_err)?;
}
let c_path = std::ffi::CString::new(path).map_err(|_| Error::InvalidArgument(format!("invalid path '{path}'")))?;
let mut ds = unsafe {
let h = gdal_sys::GDALCreate(target.c_driver(), c_path.as_ptr(), 0, 0, 0, gdal_sys::GDALDataType::GDT_Unknown, options.as_ptr());
if h.is_null() {
return Err(Error::InvalidData(format!("GDAL cannot create '{path}': {}", last_error().unwrap_or_default())));
}
GdalDataset::from_c_dataset(h)
};
let lco: Vec<String> = layer_options.iter().map(|(k, v)| format!("{k}={v}")).collect();
let lco: Vec<&str> = lco.iter().map(String::as_str).collect();
for layer in layers {
write_layer(&mut ds, layer, &lco)?;
}
unsafe { gdal_sys::CPLErrorReset() };
ds.flush_cache().map_err(gdal_err)?;
drop(ds);
if let Some(e) = last_failure() {
return Err(Error::InvalidData(format!("GDAL: writing '{path}': {e}")));
}
Ok(())
}
fn last_failure() -> Option<String> {
let failed = unsafe { gdal_sys::CPLGetLastErrorType() } >= gdal_sys::CPLErr::CE_Failure;
failed.then(last_error).flatten()
}
fn layer_srs(layer: &Layer) -> Option<gdal::spatial_ref::SpatialRef> {
let crs = layer.crs.as_ref()?;
let mut srs = match (crs.epsg, crs.wkt.as_deref()) {
(Some(code), _) => gdal::spatial_ref::SpatialRef::from_epsg(code).ok(),
(None, Some(wkt)) => gdal::spatial_ref::SpatialRef::from_definition(wkt).ok(),
_ => None,
}?;
srs.set_axis_mapping_strategy(gdal::spatial_ref::AxisMappingStrategy::TraditionalGisOrder);
Some(srs)
}
fn common_geometry_type(layer: &Layer) -> Option<GeometryType> {
use GeometryType as G;
let mut found: Option<G> = None;
for g in layer.features.iter().filter_map(|f| f.geometry.as_ref()) {
let t = g.geom_type();
found = Some(match (found, t) {
(None, t) => t,
(Some(a), b) if a == b => a,
(Some(G::Point | G::MultiPoint), G::Point | G::MultiPoint) => G::MultiPoint,
(Some(G::LineString | G::MultiLineString), G::LineString | G::MultiLineString) => G::MultiLineString,
(Some(G::Polygon | G::MultiPolygon), G::Polygon | G::MultiPolygon) => G::MultiPolygon,
_ => return None,
});
}
found
}
fn promote(g: &Geometry, layer_type: Option<GeometryType>) -> Option<Geometry> {
Some(match (g, layer_type?) {
(Geometry::Point(c), GeometryType::MultiPoint) => Geometry::MultiPoint(vec![c.clone()]),
(Geometry::LineString(l), GeometryType::MultiLineString) => Geometry::MultiLineString(vec![l.clone()]),
(Geometry::Polygon { exterior, interiors }, GeometryType::MultiPolygon) => {
Geometry::MultiPolygon(vec![(exterior.clone(), interiors.clone())])
}
_ => return None,
})
}
fn ogr_geometry_type(layer: &Layer) -> OGRwkbGeometryType::Type {
use OGRwkbGeometryType as W;
let base = match layer.geom_type.or_else(|| common_geometry_type(layer)) {
Some(GeometryType::Point) => W::wkbPoint,
Some(GeometryType::LineString) => W::wkbLineString,
Some(GeometryType::Polygon) => W::wkbPolygon,
Some(GeometryType::MultiPoint) => W::wkbMultiPoint,
Some(GeometryType::MultiLineString) => W::wkbMultiLineString,
Some(GeometryType::MultiPolygon) => W::wkbMultiPolygon,
Some(GeometryType::GeometryCollection) => W::wkbGeometryCollection,
None => return W::wkbUnknown,
};
let has_z = layer.features.iter().any(|f| f.geometry.as_ref().is_some_and(Geometry::has_z));
if has_z { unsafe { gdal_sys::OGR_GT_SetZ(base) } } else { base }
}
fn write_layer(ds: &mut GdalDataset, layer: &Layer, lco: &[&str]) -> Result<()> {
let srs = layer_srs(layer);
let gdal_layer = ds
.create_layer(gdal::vector::LayerOptions {
name: &layer.name,
srs: srs.as_ref(),
ty: ogr_geometry_type(layer),
options: (!lco.is_empty()).then_some(lco),
})
.map_err(|e| {
Error::InvalidData(format!("GDAL cannot create layer '{}' ({}): {e}", layer.name, last_error().unwrap_or_default()))
})?;
let mut indices = Vec::with_capacity(layer.schema.fields().len());
for field in layer.schema.fields() {
let (t, subtype) = match field.field_type {
FieldType::Integer => (OGRFieldType::OFTInteger64, gdal_sys::OGRFieldSubType::OFSTNone),
FieldType::Float => (OGRFieldType::OFTReal, gdal_sys::OGRFieldSubType::OFSTNone),
FieldType::Boolean => (OGRFieldType::OFTInteger, gdal_sys::OGRFieldSubType::OFSTBoolean),
FieldType::Blob => (OGRFieldType::OFTBinary, gdal_sys::OGRFieldSubType::OFSTNone),
FieldType::Date => (OGRFieldType::OFTDate, gdal_sys::OGRFieldSubType::OFSTNone),
FieldType::DateTime => (OGRFieldType::OFTDateTime, gdal_sys::OGRFieldSubType::OFSTNone),
FieldType::Json => (OGRFieldType::OFTString, gdal_sys::OGRFieldSubType::OFSTJSON),
FieldType::Text => (OGRFieldType::OFTString, gdal_sys::OGRFieldSubType::OFSTNone),
};
let name =
std::ffi::CString::new(field.name.as_str()).map_err(|_| Error::InvalidArgument(format!("field name '{}'", field.name)))?;
unsafe {
let defn = gdal_sys::OGR_Fld_Create(name.as_ptr(), t);
if field.width > 0 {
gdal_sys::OGR_Fld_SetWidth(defn, i32::try_from(field.width).unwrap_or(0));
}
if field.precision > 0 {
gdal_sys::OGR_Fld_SetPrecision(defn, i32::try_from(field.precision).unwrap_or(0));
}
gdal_sys::OGR_Fld_SetSubType(defn, subtype);
gdal_sys::OGR_L_CreateField(gdal_layer.c_layer(), defn, 1);
gdal_sys::OGR_Fld_Destroy(defn);
}
indices.push(gdal_layer.defn().field_index(&field.name).ok());
}
let defn = gdal_layer.defn();
let layer_type = layer.geom_type.or_else(|| common_geometry_type(layer));
for (i, f) in layer.features.iter().enumerate() {
let mut feature = gdal::vector::Feature::new(defn).map_err(gdal_err)?;
if let Some(g) = &f.geometry {
let wkb = match promote(g, layer_type) {
Some(multi) => multi.to_wkb(),
None => g.to_wkb(),
};
let geometry = gdal::vector::Geometry::from_wkb(&wkb).map_err(gdal_err)?;
feature.set_geometry(geometry).map_err(gdal_err)?;
}
for (value, index) in f.attributes.iter().zip(&indices) {
let Some(index) = *index else { continue };
set_value(&mut feature, index, value)?;
}
feature.create(&gdal_layer).map_err(|e| {
Error::InvalidData(format!("GDAL cannot write feature {i} of '{}' ({}): {e}", layer.name, last_error().unwrap_or_default()))
})?;
}
Ok(())
}
fn set_value(feature: &mut gdal::vector::Feature, index: usize, value: &FieldValue) -> Result<()> {
match value {
FieldValue::Null => feature.set_field_null(index),
FieldValue::Integer(v) => feature.set_field_integer64(index, *v),
FieldValue::Float(v) => feature.set_field_double(index, *v),
FieldValue::Boolean(v) => feature.set_field_integer(index, i32::from(*v)),
FieldValue::Text(s) | FieldValue::Date(s) | FieldValue::DateTime(s) => feature.set_field_string(index, s),
FieldValue::Blob(b) => {
let (Ok(i), Ok(n)) = (i32::try_from(index), i32::try_from(b.len())) else { return Ok(()) };
unsafe { gdal_sys::OGR_F_SetFieldBinary(feature.c_feature(), i, n, b.as_ptr().cast()) };
Ok(())
}
}
.map_err(gdal_err)
}
fn mem_dir() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT: AtomicU64 = AtomicU64::new(0);
format!("/vsimem/terrio-{}-{}", std::process::id(), NEXT.fetch_add(1, Ordering::Relaxed))
}
fn remove_mem_dir(dir: &str) {
if let Ok(c) = std::ffi::CString::new(dir) {
unsafe { gdal_sys::VSIRmdirRecursive(c.as_ptr()) };
}
}
fn mem_file_name(name: &str) -> String {
match std::path::Path::new(name).file_name().and_then(|s| s.to_str()) {
Some(n) if !n.is_empty() && n != "." && n != ".." => n.to_string(),
_ => "data".to_string(),
}
}
fn with_mem_file<T>(bytes: Vec<u8>, name: &str, f: impl FnOnce(&str) -> Result<T>) -> Result<T> {
let dir = mem_dir();
let path = format!("{dir}/{}", mem_file_name(name));
gdal::vsi::create_mem_file(&path, bytes).map_err(gdal_err)?;
let result = f(&path);
remove_mem_dir(&dir);
result
}
pub fn open_bytes(bytes: Vec<u8>, name: &str) -> Result<Dataset> {
open_bytes_with(bytes, name, &VectorOpenOptions::default())
}
pub(crate) fn open_bytes_with(bytes: Vec<u8>, name: &str, options: &VectorOpenOptions) -> Result<Dataset> {
let zipped = bytes.starts_with(b"PK\x03\x04");
with_mem_file(bytes, name, |path| {
let result = open_with(path, options);
match result {
Err(_) if zipped => open_with(&format!("/vsizip/{path}"), options),
r => r,
}
})
.map(|ds| match ds {
Dataset::Vector(mut v) => {
let stem = std::path::Path::new(name).file_stem().and_then(|s| s.to_str()).unwrap_or("layer").to_string();
for l in &mut v.layers {
if l.name.starts_with("/vsimem/") || l.name.starts_with("terrio-") {
l.name = stem.clone();
}
}
Dataset::Vector(v)
}
r => r,
})
}
pub fn open_vector_bytes(bytes: Vec<u8>, name: &str, options: &VectorOpenOptions) -> Result<VectorDataset> {
match open_bytes_with(bytes, name, options)? {
Dataset::Vector(v) => Ok(v),
Dataset::Raster(_) => Err(Error::Unsupported(format!("'{name}' is a raster, not a vector dataset"))),
}
}
fn collect_mem_dir(dir: &str, main: &str) -> Result<Vec<u8>> {
use std::io::Write;
let mut entries: Vec<String> = gdal::vsi::read_dir(dir, true)
.map_err(gdal_err)?
.into_iter()
.filter_map(|p| p.to_str().map(|s| s.trim_end_matches('/').to_string()))
.collect();
entries.sort();
let mut files = Vec::new();
for rel in entries {
let full = format!("{dir}/{rel}");
if rel.ends_with(".aux.xml") {
continue;
}
if let Ok(bytes) = gdal::vsi::get_vsi_mem_file_bytes_owned(&full) {
files.push((rel, full, bytes));
}
}
if let [(_, full, _)] = files.as_slice()
&& full == main
{
return Ok(files.pop().map(|f| f.2).unwrap_or_default());
}
if files.is_empty() {
return Err(Error::InvalidData(format!("GDAL wrote nothing for '{}'", main.rsplit('/').next().unwrap_or(main))));
}
let mut zip = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
let opts = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);
for (rel, _, bytes) in files {
zip.start_file(rel, opts)?;
zip.write_all(&bytes)?;
}
Ok(zip.finish()?.into_inner())
}
pub fn write_raster_bytes(ds: &RasterDataset, name: &str, driver: &str, creation_options: &[(String, String)]) -> Result<Vec<u8>> {
let dir = mem_dir();
let path = format!("{dir}/{}", mem_file_name(name));
let result = write_raster(ds, &path, driver, creation_options).and_then(|()| collect_mem_dir(&dir, &path));
remove_mem_dir(&dir);
result
}
pub fn write_vector_bytes(
layers: &[Layer],
name: &str,
driver: &str,
dataset_options: &[(String, String)],
layer_options: &[(String, String)],
) -> Result<Vec<u8>> {
let dir = mem_dir();
let path = format!("{dir}/{}", mem_file_name(name));
let result = write_vector(layers, &path, driver, dataset_options, layer_options).and_then(|()| collect_mem_dir(&dir, &path));
remove_mem_dir(&dir);
result
}
struct Quiet;
impl Quiet {
fn new() -> Self {
unsafe { gdal_sys::CPLPushErrorHandler(Some(gdal_sys::CPLQuietErrorHandler)) };
Quiet
}
}
impl Drop for Quiet {
fn drop(&mut self) {
unsafe { gdal_sys::CPLPopErrorHandler() };
}
}
fn gdal_err(e: gdal::errors::GdalError) -> Error {
Error::InvalidData(format!("GDAL: {e}"))
}
fn gdal_name(path: &str) -> String {
fn name(loc: &Location) -> Option<String> {
Some(match loc {
Location::Local(p) => p.to_str()?.to_string(),
Location::Url(u) => format!("/vsicurl/{u}"),
Location::S3 { bucket, key } => format!("/vsis3/{bucket}/{key}"),
Location::Zip { archive, inner } => {
format!("/vsizip/{}{}", name(archive)?, inner.as_deref().map(|i| format!("/{i}")).unwrap_or_default())
}
})
}
if path.starts_with("/vsi") || path.contains(":\"") {
return path.to_string();
}
name(&Location::parse(path)).unwrap_or_else(|| path.to_string())
}
fn gdal_open(path: &str, flags: GdalOpenFlags) -> Result<GdalDataset> {
let name = gdal_name(path);
let options = DatasetOptions { open_flags: flags, ..Default::default() };
GdalDataset::open_ex(&name, options).map_err(|e| match e {
gdal::errors::GdalError::NullPointer { .. } => Error::Unsupported(format!(
"'{path}': GDAL cannot open it ({})",
last_error().unwrap_or_else(|| "format not recognized".into())
)),
e => gdal_err(e),
})
}
fn last_error() -> Option<String> {
let msg = unsafe { std::ffi::CStr::from_ptr(gdal_sys::CPLGetLastErrorMsg()) }.to_string_lossy().into_owned();
(!msg.is_empty()).then_some(msg)
}
fn subdatasets_of(ds: &GdalDataset) -> Vec<(String, String)> {
let items = ds.metadata_domain("SUBDATASETS").unwrap_or_default();
let get = |key: String| {
items.iter().find_map(|kv| kv.split_once('=').filter(|(k, _)| k.eq_ignore_ascii_case(&key)).map(|(_, v)| v.to_string()))
};
(1..).map_while(|i| get(format!("SUBDATASET_{i}_NAME")).map(|n| (n, get(format!("SUBDATASET_{i}_DESC")).unwrap_or_default()))).collect()
}
fn raster_from(ds: &GdalDataset, path: &str) -> Result<RasterDataset> {
if ds.raster_count() == 0 {
let subdatasets = subdatasets_of(ds);
let Some((first, _)) = subdatasets.first() else {
return Err(Error::Unsupported(format!("'{path}' has no raster bands")));
};
let sub = gdal_open(first, GdalOpenFlags::GDAL_OF_RASTER)?;
let mut out = raster_from(&sub, first)?;
out.raster.metadata.retain(|(k, _)| !k.starts_with("SUBDATASET_"));
for (i, (name, desc)) in subdatasets.iter().enumerate() {
out.raster.metadata.push((format!("SUBDATASET_{}_NAME", i + 1), name.clone()));
out.raster.metadata.push((format!("SUBDATASET_{}_DESC", i + 1), desc.clone()));
}
return Ok(out);
}
let (cols, rows) = ds.raster_size();
let bands = ds.raster_count();
let first = ds.rasterband(1).map_err(gdal_err)?;
let raw_type = unsafe { gdal_sys::GDALGetRasterDataType(first.c_rasterband()) };
if unsafe { gdal_sys::GDALDataTypeIsComplex(raw_type) } != 0 {
return complex_raster_from(ds, &first, raw_type, path);
}
let band_type = first.band_type();
macro_rules! read {
($t:ty, $v:ident) => {{
let mut all: Vec<$t> = Vec::with_capacity(cols * rows * bands);
for b in 1..=bands {
let band = ds.rasterband(b).map_err(gdal_err)?;
let buffer = band.read_band_as::<$t>().map_err(gdal_err)?;
all.extend_from_slice(buffer.data());
}
RasterData::$v(all)
}};
}
let data = match band_type {
GdalDataType::UInt8 => read!(u8, U8),
GdalDataType::Int8 => read!(i8, I8),
GdalDataType::UInt16 => read!(u16, U16),
GdalDataType::Int16 => read!(i16, I16),
GdalDataType::UInt32 => read!(u32, U32),
GdalDataType::Int32 => read!(i32, I32),
GdalDataType::UInt64 => read!(u64, U64),
GdalDataType::Int64 => read!(i64, I64),
GdalDataType::Float32 => read!(f32, F32),
GdalDataType::Float64 => read!(f64, F64),
_ => return Err(Error::Unsupported(format!("'{path}': GDAL band type {} is not supported", band_type.name()))),
};
let nodata = match band_type {
GdalDataType::UInt64 => first.no_data_value_u64().map(|v| v as f64),
GdalDataType::Int64 => first.no_data_value_i64().map(|v| v as f64),
_ => first.no_data_value(),
};
let raster =
common::make_raster(cols, rows, bands, data, ds.geo_transform().unwrap_or(common::NO_GEOTRANSFORM), dataset_crs(ds), nodata)?;
finish_raster(ds, &first, raster, nodata.is_some(), false)
}
fn dataset_crs(ds: &GdalDataset) -> wbraster::CrsInfo {
ds.spatial_ref().ok().and_then(|srs| srs_to_crs(&srs)).unwrap_or_default()
}
fn complex_raster_from(
ds: &GdalDataset,
first: &gdal::raster::RasterBand,
t: gdal_sys::GDALDataType::Type,
path: &str,
) -> Result<RasterDataset> {
let (cols, rows) = ds.raster_size();
let bands = ds.raster_count();
let data = read_complex(ds, t, cols, rows, bands).map_err(|e| Error::InvalidData(format!("'{path}': {e}")))?;
let nodata = first.no_data_value();
let raster =
common::make_raster(cols, rows, bands * 2, data, ds.geo_transform().unwrap_or(common::NO_GEOTRANSFORM), dataset_crs(ds), nodata)?;
finish_raster(ds, first, raster, nodata.is_some(), true)
}
fn finish_raster(
ds: &GdalDataset,
first: &gdal::raster::RasterBand,
mut raster: wbraster::Raster,
has_nodata: bool,
complex: bool,
) -> Result<RasterDataset> {
if let Some(scale) = first.scale().filter(|s| *s != 1.0) {
raster.metadata.push(("scale_factor".into(), format!("{scale}")));
}
if let Some(offset) = first.offset().filter(|o| *o != 0.0) {
raster.metadata.push(("add_offset".into(), format!("{offset}")));
}
for item in ds.metadata_domain("").unwrap_or_default() {
if let Some((k, v)) = item.split_once('=') {
raster.metadata.push((k.to_string(), v.to_string()));
}
}
for (i, (name, desc)) in subdatasets_of(ds).into_iter().enumerate() {
raster.metadata.push((format!("SUBDATASET_{}_NAME", i + 1), name));
raster.metadata.push((format!("SUBDATASET_{}_DESC", i + 1), desc));
}
let mut out = RasterDataset::with_driver(raster, RasterDriver::Gdal, has_nodata);
out.complex = complex;
let geolocation: Vec<(String, String)> = ds
.metadata_domain("GEOLOCATION")
.unwrap_or_default()
.iter()
.filter_map(|item| item.split_once('=').map(|(k, v)| (k.to_string(), v.to_string())))
.collect();
out.geolocation = crate::Geolocation::from_items(&geolocation);
Ok(out)
}
fn srs_to_crs(srs: &gdal::spatial_ref::SpatialRef) -> Option<wbraster::CrsInfo> {
let wkt = srs.to_wkt().ok().filter(|w| !w.is_empty())?;
let code = match (srs.auth_name().as_deref(), srs.auth_code()) {
(Some("EPSG"), Ok(code)) => u32::try_from(code).ok(),
_ => epsg_match(srs).or_else(|| {
let mut srs = srs.clone();
srs.auto_identify_epsg().ok()?;
(srs.auth_name().as_deref() == Some("EPSG")).then(|| srs.auth_code().ok().and_then(|c| u32::try_from(c).ok())).flatten()
}),
};
Some(match code {
Some(code) => {
let mut info = wbraster::CrsInfo::from_epsg(code);
if info.wkt.is_none() {
info.wkt = Some(wkt);
}
info
}
None => common::crs_from_text(&wkt).unwrap_or_else(|| wbraster::CrsInfo { wkt: Some(wkt), ..Default::default() }),
})
}
fn epsg_match(srs: &gdal::spatial_ref::SpatialRef) -> Option<u32> {
unsafe {
let mut n = 0;
let mut confidences: *mut std::ffi::c_int = std::ptr::null_mut();
let matches = gdal_sys::OSRFindMatches(srs.to_c_hsrs(), std::ptr::null_mut(), &mut n, &mut confidences);
if matches.is_null() {
return None;
}
let mut found = None;
for i in 0..usize::try_from(n).unwrap_or(0) {
if *confidences.add(i) < 90 {
continue;
}
let m = *matches.add(i);
let name = gdal_sys::OSRGetAuthorityName(m, std::ptr::null());
let code = gdal_sys::OSRGetAuthorityCode(m, std::ptr::null());
if !name.is_null() && !code.is_null() && std::ffi::CStr::from_ptr(name).to_bytes() == b"EPSG" {
found = std::ffi::CStr::from_ptr(code).to_str().ok().and_then(|c| c.parse().ok());
break;
}
}
gdal_sys::OSRFreeSRSArray(matches);
gdal_sys::VSIFree(confidences.cast());
found
}
}
fn vector_from(ds: GdalDataset, path: &str, options: &VectorOpenOptions) -> Result<VectorDataset> {
let names: Vec<String> = ds.layers().map(|l| l.name()).collect();
if names.is_empty() {
return Err(Error::Unsupported(format!("'{path}' has no vector layers")));
}
let mut layers = Vec::new();
for mut layer in ds.layers() {
let name = layer.name();
if !options.layers.is_empty() && !options.layers.contains(&name) {
continue;
}
if let Some((x0, y0, x1, y1)) = options.spatial_filter {
layer.set_spatial_filter_rect(x0, y0, x1, y1);
}
layers.push(layer_from(&mut layer, &name)?);
}
if layers.is_empty() {
return Err(Error::InvalidArgument(format!("no layer named {} (layers: {})", options.layers.join(", "), names.join(", "))));
}
Ok(finish_vector(layers, strip_prefix(path).unwrap_or(path), VectorDriver::Gdal))
}
fn layer_from(layer: &mut gdal::vector::Layer, name: &str) -> Result<Layer> {
let mut out = Layer::new(name);
let defn = layer.defn();
out.geom_type = geometry_type(defn.geometry_type());
let kinds: Vec<OGRFieldType::Type> = defn.fields().map(|f| f.field_type()).collect();
for field in defn.fields() {
let t = match field.field_type() {
OGRFieldType::OFTInteger | OGRFieldType::OFTInteger64 => FieldType::Integer,
OGRFieldType::OFTReal => FieldType::Float,
OGRFieldType::OFTDate => FieldType::Date,
OGRFieldType::OFTDateTime => FieldType::DateTime,
OGRFieldType::OFTBinary => FieldType::Blob,
OGRFieldType::OFTIntegerList | OGRFieldType::OFTInteger64List | OGRFieldType::OFTRealList | OGRFieldType::OFTStringList => {
FieldType::Json
}
_ => FieldType::Text,
};
let mut def = FieldDef::new(field.name(), t);
if field.width() > 0 {
def = def.width(field.width() as usize);
}
if field.precision() > 0 {
def = def.precision(field.precision() as usize);
}
out.add_field(def);
}
if let Some(srs) = layer.spatial_ref()
&& let Some(crs) = srs_to_crs(&srs)
{
match crs.epsg {
Some(code) => out.set_crs_epsg(Some(code)),
None => out.set_crs_wkt(crs.wkt),
}
}
for (i, feature) in layer.features().enumerate() {
let geometry = feature.geometry().map(geometry_from).transpose()?.flatten();
let attributes = (0..kinds.len())
.map(|k| match feature.field(k) {
Ok(Some(v)) => value_from(v),
Ok(None) if kinds[k] == OGRFieldType::OFTBinary => binary_field(&feature, k),
_ => FieldValue::Null,
})
.collect();
out.push(Feature { fid: feature.fid().unwrap_or(i as u64), geometry, attributes });
}
Ok(out)
}
fn geometry_type(t: OGRwkbGeometryType::Type) -> Option<GeometryType> {
Some(match gdal::vector::geometry_type_flatten(t) {
OGRwkbGeometryType::wkbPoint => GeometryType::Point,
OGRwkbGeometryType::wkbLineString | OGRwkbGeometryType::wkbCircularString | OGRwkbGeometryType::wkbCompoundCurve => {
GeometryType::LineString
}
OGRwkbGeometryType::wkbPolygon | OGRwkbGeometryType::wkbCurvePolygon => GeometryType::Polygon,
OGRwkbGeometryType::wkbMultiPoint => GeometryType::MultiPoint,
OGRwkbGeometryType::wkbMultiLineString | OGRwkbGeometryType::wkbMultiCurve => GeometryType::MultiLineString,
OGRwkbGeometryType::wkbMultiPolygon | OGRwkbGeometryType::wkbMultiSurface => GeometryType::MultiPolygon,
OGRwkbGeometryType::wkbGeometryCollection => GeometryType::GeometryCollection,
_ => return None,
})
}
fn geometry_from(g: &gdal::vector::Geometry) -> Result<Option<Geometry>> {
if g.is_empty() {
return Ok(None);
}
if let Ok(wkb) = g.wkb()
&& let Ok(geometry) = Geometry::from_wkb(&wkb)
{
return Ok(Some(geometry));
}
let wkb = unsafe {
let linear = gdal_sys::OGR_G_GetLinearGeometry(g.c_geometry(), 0.0, std::ptr::null_mut());
if linear.is_null() {
return Err(Error::InvalidData(format!("GDAL: cannot linearize a {}", g.geometry_name())));
}
let size = gdal_sys::OGR_G_WkbSize(linear) as usize;
let mut wkb = vec![0u8; size];
let rv = gdal_sys::OGR_G_ExportToWkb(linear, gdal_sys::OGRwkbByteOrder::wkbNDR, wkb.as_mut_ptr());
gdal_sys::OGR_G_DestroyGeometry(linear);
if rv != gdal_sys::OGRErr::OGRERR_NONE {
return Err(Error::InvalidData(format!("GDAL: cannot export a {} as WKB", g.geometry_name())));
}
wkb
};
Ok(Some(Geometry::from_wkb(&wkb)?))
}
fn value_from(v: GdalValue) -> FieldValue {
let json = |items: Vec<serde_json::Value>| FieldValue::Text(serde_json::Value::Array(items).to_string());
match v {
GdalValue::IntegerValue(i) => FieldValue::Integer(i.into()),
GdalValue::Integer64Value(i) => FieldValue::Integer(i),
GdalValue::RealValue(x) => FieldValue::Float(x),
GdalValue::StringValue(s) => FieldValue::Text(s),
GdalValue::DateValue(d) => FieldValue::Date(d.to_string()),
GdalValue::DateTimeValue(t) => {
if t.offset().local_minus_utc() == 0 {
FieldValue::DateTime(t.naive_local().format("%Y-%m-%dT%H:%M:%S%.f").to_string())
} else {
FieldValue::DateTime(t.to_rfc3339())
}
}
GdalValue::IntegerListValue(v) => json(v.into_iter().map(Into::into).collect()),
GdalValue::Integer64ListValue(v) => json(v.into_iter().map(Into::into).collect()),
GdalValue::RealListValue(v) => json(v.into_iter().map(Into::into).collect()),
GdalValue::StringListValue(v) => json(v.into_iter().map(Into::into).collect()),
}
}
fn binary_field(feature: &gdal::vector::Feature, field: usize) -> FieldValue {
let Ok(index) = i32::try_from(field) else { return FieldValue::Null };
unsafe {
let handle = feature.c_feature();
if gdal_sys::OGR_F_IsFieldSetAndNotNull(handle, index) == 0 {
return FieldValue::Null;
}
let mut len = 0;
let ptr = gdal_sys::OGR_F_GetFieldAsBinary(handle, index, &mut len);
if ptr.is_null() || len <= 0 {
return FieldValue::Blob(Vec::new());
}
FieldValue::Blob(std::slice::from_raw_parts(ptr, len as usize).to_vec())
}
}