This is an automated email from the ASF dual-hosted git repository.
kontinuation pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sedona-db.git
The following commit(s) were added to refs/heads/main by this push:
new 58c8d65f feat(sedona-gdal): add convenience facade and mem builder
(#697)
58c8d65f is described below
commit 58c8d65faa792907b27b903c6b83d6c86020b0dc
Author: Kristin Cowalcijk <[email protected]>
AuthorDate: Sun Apr 12 18:58:26 2026 +0800
feat(sedona-gdal): add convenience facade and mem builder (#697)
## Summary
- add the high-level `Gdal` facade and `with_global_gdal` convenience entry
point
- add the MEM dataset builder on top of the lower-level dataset and raster
wrappers
- keep the top-level API explicit by importing concrete raster/vector
modules instead of relying on wrapper re-export aliases
---
c/sedona-gdal/src/dataset.rs | 4 +-
c/sedona-gdal/src/gdal.rs | 253 +++++++++++++++++++++++
c/sedona-gdal/src/global.rs | 11 +
c/sedona-gdal/src/lib.rs | 2 +
c/sedona-gdal/src/mem.rs | 465 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 733 insertions(+), 2 deletions(-)
diff --git a/c/sedona-gdal/src/dataset.rs b/c/sedona-gdal/src/dataset.rs
index 36832d86..966f6798 100644
--- a/c/sedona-gdal/src/dataset.rs
+++ b/c/sedona-gdal/src/dataset.rs
@@ -300,11 +300,11 @@ impl Dataset {
///
/// # Safety
///
- /// `data_ptr` must point to valid band data that outlives this dataset.
+ /// `data_ptr` must point to valid mutable band data that outlives this
dataset.
pub unsafe fn add_band_with_data(
&self,
data_type: RustGdalDataType,
- data_ptr: *const u8,
+ data_ptr: *mut u8,
pixel_offset: Option<i64>,
line_offset: Option<i64>,
) -> Result<()> {
diff --git a/c/sedona-gdal/src/gdal.rs b/c/sedona-gdal/src/gdal.rs
new file mode 100644
index 00000000..481f9d27
--- /dev/null
+++ b/c/sedona-gdal/src/gdal.rs
@@ -0,0 +1,253 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! High-level convenience wrapper around [`GdalApi`].
+//!
+//! [`Gdal`] bundles a `&'static GdalApi` reference and exposes ergonomic
+//! methods that delegate to the lower-level constructors and free functions
+//! scattered across the crate, eliminating the need to pass `api` explicitly
+//! at every call site.
+
+use crate::config;
+use crate::dataset::Dataset;
+use crate::driver::{Driver, DriverManager};
+use crate::errors::Result;
+use crate::gdal_api::GdalApi;
+use crate::gdal_dyn_bindgen::{GDALOpenFlags, OGRFieldType};
+use crate::mem::create_mem_dataset;
+use crate::raster::polygonize::{polygonize, PolygonizeOptions};
+use crate::raster::rasterband::RasterBand;
+use crate::raster::rasterize::{rasterize, RasterizeOptions};
+use crate::raster::rasterize_affine::rasterize_affine;
+use crate::raster::types::DatasetOptions;
+use crate::raster::types::GdalDataType;
+use crate::spatial_ref::SpatialRef;
+use crate::vector::feature::FieldDefn;
+use crate::vector::geometry::Geometry;
+use crate::vector::layer::Layer;
+use crate::vrt::VrtDataset;
+use crate::vsi;
+
+/// High-level convenience wrapper around [`GdalApi`].
+///
+/// Stores a `&'static GdalApi` reference and provides ergonomic methods that
+/// delegate to the various constructors and free functions in the crate.
+pub struct Gdal {
+ api: &'static GdalApi,
+}
+
+impl Gdal {
+ /// Create a `Gdal` wrapper for the given API reference.
+ pub(crate) fn new(api: &'static GdalApi) -> Self {
+ Self { api }
+ }
+
+ // -- Info ----------------------------------------------------------------
+
+ /// Return the name of the loaded GDAL library.
+ pub fn name(&self) -> &str {
+ self.api.name()
+ }
+
+ /// Fetch GDAL version information for a standard request key.
+ /// See also [`GdalApi::version_info`].
+ pub fn version_info(&self, request: &str) -> String {
+ self.api.version_info(request)
+ }
+
+ // -- Config --------------------------------------------------------------
+
+ /// Set a thread-local GDAL configuration option.
+ /// See also [`config::set_thread_local_config_option`].
+ pub fn set_thread_local_config_option(&self, key: &str, value: &str) ->
Result<()> {
+ config::set_thread_local_config_option(self.api, key, value)
+ }
+
+ // -- Driver --------------------------------------------------------------
+
+ /// Fetch a GDAL driver by its short name.
+ /// See also [`DriverManager::get_driver_by_name`].
+ pub fn get_driver_by_name(&self, name: &str) -> Result<Driver> {
+ DriverManager::get_driver_by_name(self.api, name)
+ }
+
+ // -- Dataset -------------------------------------------------------------
+
+ /// Open a dataset with extended options.
+ /// See also [`Dataset::open_ex`].
+ pub fn open_ex(
+ &self,
+ path: &str,
+ open_flags: GDALOpenFlags,
+ allowed_drivers: Option<&[&str]>,
+ open_options: Option<&[&str]>,
+ sibling_files: Option<&[&str]>,
+ ) -> Result<Dataset> {
+ Dataset::open_ex(
+ self.api,
+ path,
+ open_flags,
+ allowed_drivers,
+ open_options,
+ sibling_files,
+ )
+ }
+
+ /// Open a dataset using a [`DatasetOptions`] struct.
+ /// See also [`Dataset::open_ex_with_options`].
+ pub fn open_ex_with_options(&self, path: &str, options:
DatasetOptions<'_>) -> Result<Dataset> {
+ Dataset::open_ex_with_options(self.api, path, options)
+ }
+
+ // -- Spatial Reference ---------------------------------------------------
+
+ /// Create a spatial reference from a WKT string.
+ /// See also [`SpatialRef::from_wkt`].
+ pub fn spatial_ref_from_wkt(&self, wkt: &str) -> Result<SpatialRef> {
+ SpatialRef::from_wkt(self.api, wkt)
+ }
+
+ // -- VRT -----------------------------------------------------------------
+
+ /// Create an empty VRT dataset with the given raster size.
+ /// See also [`VrtDataset::create`].
+ pub fn create_vrt(&self, x_size: usize, y_size: usize) ->
Result<VrtDataset> {
+ VrtDataset::create(self.api, x_size, y_size)
+ }
+
+ // -- Geometry ------------------------------------------------------------
+
+ /// Create a geometry from WKB bytes.
+ /// See also [`Geometry::from_wkb`].
+ pub fn geometry_from_wkb(&self, wkb: &[u8]) -> Result<Geometry> {
+ Geometry::from_wkb(self.api, wkb)
+ }
+
+ /// Create a geometry from a WKT string.
+ /// See also [`Geometry::from_wkt`].
+ pub fn geometry_from_wkt(&self, wkt: &str) -> Result<Geometry> {
+ Geometry::from_wkt(self.api, wkt)
+ }
+
+ // -- Vector --------------------------------------------------------------
+
+ /// Create an OGR field definition.
+ /// See also [`FieldDefn::new`].
+ pub fn create_field_defn(&self, name: &str, field_type: OGRFieldType) ->
Result<FieldDefn> {
+ FieldDefn::new(self.api, name, field_type)
+ }
+
+ // -- VSI (Virtual File System) -------------------------------------------
+
+ /// Create a VSI in-memory file from the given bytes.
+ /// See also [`vsi::create_mem_file`].
+ pub fn create_mem_file(&self, file_name: &str, data: &[u8]) -> Result<()> {
+ vsi::create_mem_file(self.api, file_name, data)
+ }
+
+ /// Delete a VSI in-memory file.
+ /// See also [`vsi::unlink_mem_file`].
+ pub fn unlink_mem_file(&self, file_name: &str) -> Result<()> {
+ vsi::unlink_mem_file(self.api, file_name)
+ }
+
+ /// Copy the bytes of a VSI in-memory file, taking ownership of the GDAL
buffer.
+ /// See also [`vsi::get_vsi_mem_file_bytes_owned`].
+ pub fn get_vsi_mem_file_bytes_owned(&self, file_name: &str) ->
Result<Vec<u8>> {
+ vsi::get_vsi_mem_file_bytes_owned(self.api, file_name)
+ }
+
+ // -- Raster operations ---------------------------------------------------
+
+ /// Create a bare in-memory MEM dataset with GDAL-owned bands.
+ /// See also [`crate::mem::MemDatasetBuilder`] for higher-level MEM
dataset construction.
+ pub fn create_mem_dataset(
+ &self,
+ width: usize,
+ height: usize,
+ n_owned_bands: usize,
+ owned_bands_data_type: GdalDataType,
+ ) -> Result<Dataset> {
+ create_mem_dataset(
+ self.api,
+ width,
+ height,
+ n_owned_bands,
+ owned_bands_data_type,
+ )
+ }
+
+ /// Rasterize geometries using the dataset geotransform as the transformer.
+ /// See also [`rasterize_affine`].
+ pub fn rasterize_affine(
+ &self,
+ dataset: &Dataset,
+ bands: &[usize],
+ geometries: &[Geometry],
+ burn_values: &[f64],
+ all_touched: bool,
+ ) -> Result<()> {
+ rasterize_affine(
+ self.api,
+ dataset,
+ bands,
+ geometries,
+ burn_values,
+ all_touched,
+ )
+ }
+
+ /// Rasterize geometries into the selected dataset bands.
+ /// See also [`rasterize`].
+ pub fn rasterize(
+ &self,
+ dataset: &Dataset,
+ band_list: &[i32],
+ geometries: &[&Geometry],
+ burn_values: &[f64],
+ options: Option<RasterizeOptions>,
+ ) -> Result<()> {
+ rasterize(
+ self.api,
+ dataset,
+ band_list,
+ geometries,
+ burn_values,
+ options,
+ )
+ }
+
+ /// Polygonize a raster band into a vector layer.
+ /// See also [`polygonize`].
+ pub fn polygonize(
+ &self,
+ src_band: &RasterBand<'_>,
+ mask_band: Option<&RasterBand<'_>>,
+ out_layer: &Layer<'_>,
+ pixel_value_field: i32,
+ options: &PolygonizeOptions,
+ ) -> Result<()> {
+ polygonize(
+ self.api,
+ src_band,
+ mask_band,
+ out_layer,
+ pixel_value_field,
+ options,
+ )
+ }
+}
diff --git a/c/sedona-gdal/src/global.rs b/c/sedona-gdal/src/global.rs
index 9365ef89..5b958116 100644
--- a/c/sedona-gdal/src/global.rs
+++ b/c/sedona-gdal/src/global.rs
@@ -16,6 +16,7 @@
// under the License.
use crate::errors::GdalInitLibraryError;
+use crate::gdal::Gdal;
use crate::gdal_api::GdalApi;
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};
@@ -203,6 +204,16 @@ where
Ok(func(api))
}
+/// Execute a closure with the process-global high-level [`Gdal`] handle.
+/// The global API is initialized lazily on first use and then reused.
+pub fn with_global_gdal<F, R>(func: F) -> Result<R, GdalInitLibraryError>
+where
+ F: FnOnce(&Gdal) -> R,
+{
+ let api = get_global_gdal_api()?;
+ Ok(func(&Gdal::new(api)))
+}
+
/// Verify that the GDAL library meets the minimum version requirement.
///
/// We use `GDALVersionInfo("VERSION_NUM")` instead of `GDALCheckVersion`
because
diff --git a/c/sedona-gdal/src/lib.rs b/c/sedona-gdal/src/lib.rs
index f6138451..6f8f7027 100644
--- a/c/sedona-gdal/src/lib.rs
+++ b/c/sedona-gdal/src/lib.rs
@@ -23,6 +23,7 @@ pub mod gdal_dyn_bindgen;
pub mod errors;
// --- Core API ---
+pub mod gdal;
pub mod gdal_api;
pub mod global;
@@ -32,6 +33,7 @@ pub mod cpl;
pub mod dataset;
pub mod driver;
pub mod geo_transform;
+pub mod mem;
pub mod raster;
pub mod spatial_ref;
pub mod vector;
diff --git a/c/sedona-gdal/src/mem.rs b/c/sedona-gdal/src/mem.rs
new file mode 100644
index 00000000..79a6e382
--- /dev/null
+++ b/c/sedona-gdal/src/mem.rs
@@ -0,0 +1,465 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! High-level builder for creating in-memory (MEM) GDAL datasets.
+//!
+//! [`MemDatasetBuilder`] provides a fluent, type-safe API for constructing
GDAL MEM
+//! datasets with zero-copy band attachment, optional geo-transform,
projection, and
+//! per-band nodata values.
+
+use crate::dataset::Dataset;
+use crate::errors::Result;
+use crate::gdal::Gdal;
+use crate::gdal_api::{call_gdal_api, GdalApi};
+use crate::gdal_dyn_bindgen::CE_Failure;
+use crate::raster::types::GdalDataType;
+
+/// Nodata value for a raster band.
+///
+/// GDAL has three separate APIs for setting nodata depending on the band data
type:
+/// - [`f64`] for most types (UInt8 through Float64, excluding Int64/UInt64)
+/// - [`i64`] for Int64 bands
+/// - [`u64`] for UInt64 bands
+///
+/// This enum encapsulates the three nodata value representations exposed by
GDAL.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Nodata {
+ F64(f64),
+ I64(i64),
+ U64(u64),
+}
+
+/// A band specification for [`MemDatasetBuilder`].
+struct MemBand {
+ data_type: GdalDataType,
+ data_ptr: *mut u8,
+ pixel_offset: Option<i64>,
+ line_offset: Option<i64>,
+ nodata: Option<Nodata>,
+}
+
+/// A builder for constructing in-memory (MEM) GDAL datasets.
+///
+/// This creates datasets using `MEMDataset::Create` (bypassing GDAL's
open-dataset-list
+/// mutex for better concurrency) and attaches bands via `GDALAddBand` with
`DATAPOINTER`
+/// options for zero-copy operation.
+///
+/// # Safety
+///
+/// All `add_band*` methods are `unsafe` because the caller must ensure that
the
+/// provided data pointers remain valid for the lifetime of the built
[`Dataset`],
+/// satisfy the alignment requirements of the band data type, and refer to
writable
+/// memory if GDAL may write through the attached `DATAPOINTER` band.
+pub struct MemDatasetBuilder {
+ width: usize,
+ height: usize,
+ n_owned_bands: usize,
+ owned_bands_data_type: Option<GdalDataType>,
+ bands: Vec<MemBand>,
+ geo_transform: Option<[f64; 6]>,
+ projection: Option<String>,
+}
+
+impl MemDatasetBuilder {
+ /// Create a builder for a MEM dataset with the given dimensions.
+ pub fn new(width: usize, height: usize) -> Self {
+ Self {
+ width,
+ height,
+ n_owned_bands: 0,
+ owned_bands_data_type: None,
+ bands: Vec::new(),
+ geo_transform: None,
+ projection: None,
+ }
+ }
+
+ /// Create a builder for a MEM dataset with GDAL-owned bands.
+ pub fn new_with_owned_bands(
+ width: usize,
+ height: usize,
+ n_owned_bands: usize,
+ owned_bands_data_type: GdalDataType,
+ ) -> Self {
+ Self {
+ width,
+ height,
+ n_owned_bands,
+ owned_bands_data_type: Some(owned_bands_data_type),
+ bands: Vec::new(),
+ geo_transform: None,
+ projection: None,
+ }
+ }
+
+ /// Create a MEM dataset with GDAL-owned bands.
+ /// This is a safe shortcut for `new_with_owned_bands(...).build(gdal)`.
+ pub fn create(
+ gdal: &Gdal,
+ width: usize,
+ height: usize,
+ n_owned_bands: usize,
+ owned_bands_data_type: GdalDataType,
+ ) -> Result<Dataset> {
+ // SAFETY: `new_with_owned_bands` creates a builder with zero external
bands,
+ // so no data pointers need to outlive the dataset.
+ unsafe {
+ Self::new_with_owned_bands(width, height, n_owned_bands,
owned_bands_data_type)
+ .build(gdal)
+ }
+ }
+
+ /// Add a zero-copy band from a raw data pointer.
+ /// Use default contiguous row-major pixel and line offsets.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure `data_ptr` points to a valid buffer of at least
+ /// `height * width * data_type.byte_size()` bytes, is properly aligned for
+ /// `data_type`, and outlives the built [`Dataset`].
+ pub unsafe fn add_band(self, data_type: GdalDataType, data_ptr: *mut u8)
-> Self {
+ self.add_band_with_options(data_type, data_ptr, None, None, None)
+ }
+
+ /// Add a zero-copy band with custom offsets and optional nodata.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure `data_ptr` points to a valid buffer of
sufficient size
+ /// for the given dimensions and offsets, is properly aligned for
`data_type`, and
+ /// outlives the built [`Dataset`].
+ pub unsafe fn add_band_with_options(
+ mut self,
+ data_type: GdalDataType,
+ data_ptr: *mut u8,
+ pixel_offset: Option<i64>,
+ line_offset: Option<i64>,
+ nodata: Option<Nodata>,
+ ) -> Self {
+ self.bands.push(MemBand {
+ data_type,
+ data_ptr,
+ pixel_offset,
+ line_offset,
+ nodata,
+ });
+ self
+ }
+
+ /// Set the dataset geotransform coefficients.
+ pub fn geo_transform(mut self, gt: [f64; 6]) -> Self {
+ self.geo_transform = Some(gt);
+ self
+ }
+
+ /// Set the dataset projection definition string.
+ pub fn projection(mut self, projection: impl Into<String>) -> Self {
+ self.projection = Some(projection.into());
+ self
+ }
+
+ /// Build the MEM dataset and attach the configured bands and metadata.
+ ///
+ /// # Safety
+ ///
+ /// This method is unsafe because the built dataset references memory
provided via
+ /// the `add_band*` methods. The caller must ensure all data pointers
remain valid
+ /// for the lifetime of the returned [`Dataset`] and satisfy the alignment
+ /// requirements of their band data types.
+ pub unsafe fn build(self, gdal: &Gdal) -> Result<Dataset> {
+ let dataset = gdal.create_mem_dataset(
+ self.width,
+ self.height,
+ self.n_owned_bands,
+ self.owned_bands_data_type.unwrap_or(GdalDataType::UInt8),
+ )?;
+
+ // Attach bands (zero-copy via DATAPOINTER).
+ for band_spec in &self.bands {
+ dataset.add_band_with_data(
+ band_spec.data_type,
+ band_spec.data_ptr,
+ band_spec.pixel_offset,
+ band_spec.line_offset,
+ )?;
+ }
+
+ // Set geo-transform.
+ if let Some(gt) = &self.geo_transform {
+ dataset.set_geo_transform(gt)?;
+ }
+
+ // Set projection/CRS.
+ if let Some(proj) = &self.projection {
+ dataset.set_projection(proj)?;
+ }
+
+ // Set per-band nodata values.
+ for (i, band_spec) in self.bands.iter().enumerate() {
+ if let Some(nodata) = &band_spec.nodata {
+ let raster_band = dataset.rasterband(i + 1 +
self.n_owned_bands)?;
+ match nodata {
+ Nodata::F64(v) => raster_band.set_no_data_value(Some(*v))?,
+ Nodata::I64(v) =>
raster_band.set_no_data_value_i64(Some(*v))?,
+ Nodata::U64(v) =>
raster_band.set_no_data_value_u64(Some(*v))?,
+ }
+ }
+ }
+
+ Ok(dataset)
+ }
+}
+
+/// Create a bare in-memory MEM dataset with GDAL-owned bands.
+/// For a higher-level builder with external bands and metadata, use
`MemDatasetBuilder`.
+pub(crate) fn create_mem_dataset(
+ api: &'static GdalApi,
+ width: usize,
+ height: usize,
+ n_owned_bands: usize,
+ owned_bands_data_type: GdalDataType,
+) -> Result<Dataset> {
+ let empty_filename = c"";
+ let c_data_type = owned_bands_data_type.to_c();
+ let handle = unsafe {
+ call_gdal_api!(
+ api,
+ MEMDatasetCreate,
+ empty_filename.as_ptr(),
+ width.try_into()?,
+ height.try_into()?,
+ n_owned_bands.try_into()?,
+ c_data_type,
+ std::ptr::null_mut()
+ )
+ };
+
+ if handle.is_null() {
+ return Err(api.last_cpl_err(CE_Failure as u32));
+ }
+ Ok(Dataset::new(api, handle))
+}
+
+#[cfg(all(test, feature = "gdal-sys"))]
+mod tests {
+ use crate::global::with_global_gdal;
+ use crate::mem::{MemDatasetBuilder, Nodata};
+ use crate::raster::types::GdalDataType;
+
+ #[test]
+ fn test_mem_builder_single_band() {
+ with_global_gdal(|gdal| {
+ let mut data = vec![42u8; 64 * 64];
+ let dataset = unsafe {
+ MemDatasetBuilder::new(64, 64)
+ .add_band(GdalDataType::UInt8, data.as_mut_ptr())
+ .build(gdal)
+ .unwrap()
+ };
+ assert_eq!(dataset.raster_size(), (64, 64));
+ assert_eq!(dataset.raster_count(), 1);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_mem_builder_multi_band() {
+ with_global_gdal(|gdal| {
+ let mut band1 = vec![1u16; 32 * 32];
+ let mut band2 = vec![2u16; 32 * 32];
+ let mut band3 = vec![3u16; 32 * 32];
+ let dataset = unsafe {
+ MemDatasetBuilder::new(32, 32)
+ .add_band(GdalDataType::UInt16, band1.as_mut_ptr() as *mut
u8)
+ .add_band(GdalDataType::UInt16, band2.as_mut_ptr() as *mut
u8)
+ .add_band(GdalDataType::UInt16, band3.as_mut_ptr() as *mut
u8)
+ .build(gdal)
+ .unwrap()
+ };
+ assert_eq!(dataset.raster_count(), 3);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_mem_builder_with_geo_transform() {
+ with_global_gdal(|gdal| {
+ let mut data = vec![0f32; 10 * 10];
+ let gt = [100.0, 0.5, 0.0, 200.0, 0.0, -0.5];
+ let dataset = unsafe {
+ MemDatasetBuilder::new(10, 10)
+ .add_band(GdalDataType::Float32, data.as_mut_ptr() as *mut
u8)
+ .geo_transform(gt)
+ .build(gdal)
+ .unwrap()
+ };
+ let got = dataset.geo_transform().unwrap();
+ assert_eq!(gt, got);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_mem_builder_with_wkt_projection() {
+ let projections = [
+ r#"GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS
84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]"#,
+
r#"{"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json","type":"GeographicCRS","name":"WGS
84 (CRS84)","datum_ensemble":{"name":"World Geodetic System 1984
ensemble","members":[{"name":"World Geodetic System 1984
(Transit)","id":{"authority":"EPSG","code":1166}},{"name":"World Geodetic
System 1984 (G730)","id":{"authority":"EPSG","code":1152}},{"name":"World
Geodetic System 1984
(G873)","id":{"authority":"EPSG","code":1153}},{"name":"World Geodetic System
1984 (G11 [...]
+ "EPSG:4326",
+ ];
+ for projection in projections {
+ with_global_gdal(|gdal| {
+ let mut data = [0u8; 8 * 8];
+ let dataset = unsafe {
+ MemDatasetBuilder::new(8, 8)
+ .add_band(GdalDataType::UInt8, data.as_mut_ptr())
+ .projection(projection)
+ .build(gdal)
+ .unwrap()
+ };
+ let proj = dataset.projection();
+ assert!(proj.contains("WGS 84"), "Expected WGS 84 in: {proj}");
+ })
+ .unwrap();
+ }
+ }
+
+ #[test]
+ fn test_mem_builder_with_epsg_code_projection() {
+ with_global_gdal(|gdal| {
+ let mut data = [0u8; 8 * 8];
+ let dataset = unsafe {
+ MemDatasetBuilder::new(8, 8)
+ .add_band(GdalDataType::UInt8, data.as_mut_ptr())
+ .projection("EPSG:4326")
+ .build(gdal)
+ .unwrap()
+ };
+ let proj = dataset.projection();
+ assert!(proj.contains("WGS 84"), "Expected WGS 84 in: {proj}");
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_mem_builder_with_nodata() {
+ with_global_gdal(|gdal| {
+ let mut data = [0f64; 4 * 4];
+ let dataset = unsafe {
+ MemDatasetBuilder::new(4, 4)
+ .add_band_with_options(
+ GdalDataType::Float64,
+ data.as_mut_ptr() as *mut u8,
+ None,
+ None,
+ Some(Nodata::F64(-9999.0)),
+ )
+ .build(gdal)
+ .unwrap()
+ };
+ let band = dataset.rasterband(1).unwrap();
+ let nodata = band.no_data_value();
+ assert_eq!(nodata, Some(-9999.0));
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_mem_builder_zero_bands() {
+ with_global_gdal(|gdal| {
+ let dataset = unsafe { MemDatasetBuilder::new(16,
16).build(gdal).unwrap() };
+ assert_eq!(dataset.raster_count(), 0);
+ assert_eq!(dataset.raster_size(), (16, 16));
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_mem_builder_mixed_band_types() {
+ with_global_gdal(|gdal| {
+ let mut band_u8 = [0u8; 8 * 8];
+ let mut band_f64 = vec![0f64; 8 * 8];
+ let dataset = unsafe {
+ MemDatasetBuilder::new(8, 8)
+ .add_band(GdalDataType::UInt8, band_u8.as_mut_ptr())
+ .add_band(GdalDataType::Float64, band_f64.as_mut_ptr() as
*mut u8)
+ .build(gdal)
+ .unwrap()
+ };
+ assert_eq!(dataset.raster_count(), 2);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ pub fn test_mem_builder_with_owned_bands() {
+ with_global_gdal(|gdal| {
+ let dataset = unsafe {
+ MemDatasetBuilder::new_with_owned_bands(16, 16, 2,
GdalDataType::UInt16)
+ .build(gdal)
+ .unwrap()
+ };
+ assert_eq!(dataset.raster_count(), 2);
+ assert_eq!(
+ dataset.rasterband(1).unwrap().band_type(),
+ GdalDataType::UInt16
+ );
+ assert_eq!(
+ dataset.rasterband(2).unwrap().band_type(),
+ GdalDataType::UInt16
+ );
+
+ let dataset = MemDatasetBuilder::create(gdal, 10, 8, 1,
GdalDataType::Float32).unwrap();
+ assert_eq!(dataset.raster_count(), 1);
+ assert_eq!(
+ dataset.rasterband(1).unwrap().band_type(),
+ GdalDataType::Float32
+ );
+ })
+ .unwrap();
+ }
+
+ #[test]
+ pub fn test_mem_builder_mixed_owned_and_external_bands() {
+ with_global_gdal(|gdal| {
+ let mut external_band = [0u8; 8 * 8];
+ let dataset = unsafe {
+ MemDatasetBuilder::new_with_owned_bands(8, 8, 1,
GdalDataType::Float32)
+ .add_band_with_options(
+ GdalDataType::UInt8,
+ external_band.as_mut_ptr(),
+ None,
+ None,
+ Some(Nodata::U64(255)),
+ )
+ .build(gdal)
+ .unwrap()
+ };
+ assert_eq!(dataset.raster_count(), 2);
+ assert_eq!(
+ dataset.rasterband(1).unwrap().band_type(),
+ GdalDataType::Float32
+ );
+ assert_eq!(
+ dataset.rasterband(2).unwrap().band_type(),
+ GdalDataType::UInt8
+ );
+ let nodata = dataset.rasterband(2).unwrap().no_data_value();
+ assert_eq!(nodata, Some(255.0));
+ })
+ .unwrap();
+ }
+}