james-willis commented on code in PR #749:
URL: https://github.com/apache/sedona-db/pull/749#discussion_r3228825109


##########
rust/sedona-raster/src/builder.rs:
##########
@@ -350,500 +605,765 @@ impl RasterBuilder {
 mod tests {
     use super::*;
     use crate::array::RasterStructArray;
-    use crate::traits::{RasterMetadata, RasterRef};
-    use sedona_schema::raster::{BandDataType, StorageType};
+    use crate::traits::RasterRef;
 
     #[test]
-    fn test_iterator_basic_functionality() {
-        // Create a simple raster for testing using the correct API
-        let mut builder = RasterBuilder::new(10); // capacity
-
-        let metadata = RasterMetadata {
-            width: 10,
-            height: 10,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+    fn test_roundtrip_2d_raster() {
+        let mut builder = RasterBuilder::new(1);
+        builder
+            .start_raster_2d(
+                10,
+                20,
+                100.0,
+                200.0,
+                1.0,
+                -2.0,
+                0.25,
+                0.5,
+                Some("EPSG:4326"),
+            )
+            .unwrap();
+        builder
+            .start_band_2d(BandDataType::UInt8, Some(&[255u8]))
+            .unwrap();
+        builder.band_data_writer().append_value(vec![1u8; 200]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let epsg4326 = "EPSG:4326";
-        builder.start_raster(&metadata, Some(epsg4326)).unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        assert_eq!(rasters.len(), 1);
 
-        let band_metadata = BandMetadata {
-            nodata_value: Some(vec![255u8]),
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt8,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        let r = rasters.get(0).unwrap();
+        assert_eq!(r.width(), Some(10));
+        assert_eq!(r.height(), Some(20));
+        assert_eq!(r.transform(), &[100.0, 1.0, 0.25, 200.0, 0.5, -2.0]);
+        assert_eq!(r.x_dim(), "x");
+        assert_eq!(r.y_dim(), "y");
+        assert_eq!(r.crs(), Some("EPSG:4326"));
+        assert_eq!(r.num_bands(), 1);
+
+        let band = r.band(0).unwrap();
+        assert_eq!(band.ndim(), 2);
+        assert_eq!(band.dim_names(), vec!["y", "x"]);
+        assert_eq!(band.shape(), &[20, 10]);
+        assert_eq!(band.data_type(), BandDataType::UInt8);
+        assert_eq!(band.nodata(), Some(&[255u8][..]));
+        assert_eq!(band.contiguous_data().unwrap().len(), 200);
+    }
 
-        // Add a single band with some test data using the correct API
-        builder.start_band(band_metadata.clone()).unwrap();
-        let test_data = vec![1u8; 100]; // 10x10 raster with value 1
-        builder.band_data_writer().append_value(&test_data);
+    #[test]
+    fn test_roundtrip_multi_band() {
+        let mut builder = RasterBuilder::new(1);
+        builder
+            .start_raster_2d(2, 2, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, None)
+            .unwrap();
+
+        // Band 0: UInt8
+        builder
+            .start_band_2d(BandDataType::UInt8, Some(&[255u8]))
+            .unwrap();
+        builder.band_data_writer().append_value([1u8, 2, 3, 4]);
+        builder.finish_band().unwrap();
+
+        // Band 1: Float32
+        builder.start_band_2d(BandDataType::Float32, None).unwrap();
+        let f32_data: Vec<u8> = [1.5f32, 2.5, 3.5, 4.5]
+            .iter()
+            .flat_map(|v| v.to_le_bytes())
+            .collect();
+        builder.band_data_writer().append_value(&f32_data);
         builder.finish_band().unwrap();
-        let result = builder.finish_raster();
-        assert!(result.is_ok());
 
-        let raster_array = builder.finish().unwrap();
+        builder.finish_raster().unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        // Test the iterator
-        let rasters = RasterStructArray::new(&raster_array);
+        assert_eq!(r.num_bands(), 2);
 
-        assert_eq!(rasters.len(), 1);
-        assert!(!rasters.is_empty());
+        let b0 = r.band(0).unwrap();
+        assert_eq!(b0.data_type(), BandDataType::UInt8);
+        assert_eq!(b0.nodata(), Some(&[255u8][..]));
 
-        let raster = rasters.get(0).unwrap();
-        let metadata = raster.metadata();
+        let b1 = r.band(1).unwrap();
+        assert_eq!(b1.data_type(), BandDataType::Float32);
+        assert_eq!(b1.nodata(), None);
+    }
 
-        assert_eq!(metadata.width(), 10);
-        assert_eq!(metadata.height(), 10);
-        assert_eq!(metadata.scale_x(), 1.0);
-        assert_eq!(metadata.scale_y(), -1.0);
+    #[test]
+    fn test_null_raster() {
+        let mut builder = RasterBuilder::new(2);
+        builder
+            .start_raster_2d(1, 1, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, None)
+            .unwrap();
+        builder.start_band_2d(BandDataType::UInt8, None).unwrap();
+        builder.band_data_writer().append_value([0u8]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let bands = raster.bands();
-        assert_eq!(bands.len(), 1);
-        assert!(!bands.is_empty());
+        builder.append_null().unwrap();
 
-        // Access band with 1-based band_number
-        let band = bands.band(1).unwrap();
-        assert_eq!(band.data().len(), 100);
-        assert_eq!(band.data()[0], 1u8);
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        assert_eq!(rasters.len(), 2);
+        assert!(!rasters.is_null(0));
+        assert!(rasters.is_null(1));
+    }
 
-        let band_meta = band.metadata();
-        assert_eq!(band_meta.storage_type().unwrap(), StorageType::InDb);
-        assert_eq!(band_meta.data_type().unwrap(), BandDataType::UInt8);
+    #[test]
+    fn test_nd_band() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[5, 4], None)
+            .unwrap();
 
-        let crs = raster.crs().unwrap();
-        assert_eq!(crs, epsg4326);
+        // 3D band: [time=3, y=4, x=5]
+        builder
+            .start_band(
+                Some("temperature"),
+                &["time", "y", "x"],
+                &[3, 4, 5],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data = vec![0u8; 3 * 4 * 5 * 4]; // 3*4*5 Float32 elements
+        builder.band_data_writer().append_value(&data);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Test iterator over bands
-        let band_iter: Vec<_> = bands.iter().collect();
-        assert_eq!(band_iter.len(), 1);
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+
+        assert_eq!(r.band_name(0), Some("temperature"));
+        let band = r.band(0).unwrap();
+        assert_eq!(band.ndim(), 3);
+        assert_eq!(band.dim_names(), vec!["time", "y", "x"]);
+        assert_eq!(band.shape(), &[3, 4, 5]);
+        assert_eq!(band.dim_size("time"), Some(3));
+        assert_eq!(band.dim_size("y"), Some(4));
+        assert_eq!(band.dim_size("x"), Some(5));
+        assert_eq!(band.dim_size("z"), None);
+
+        // Verify strides are standard C-order: [4*5*4, 5*4, 4] = [80, 20, 4]
+        let buf = band.nd_buffer().unwrap();
+        assert_eq!(buf.strides, &[80, 20, 4]);
+        assert_eq!(buf.offset, 0);
     }
 
     #[test]
-    fn test_multi_band_iterator() {
-        let mut builder = RasterBuilder::new(3);
+    fn test_nonstandard_spatial_dim_names() {
+        // Zarr-style dataset with lat/lon instead of y/x
+        let mut builder = RasterBuilder::new(1);
+        let transform = [10.0, 0.01, 0.0, 50.0, 0.0, -0.01];
+        builder
+            .start_raster(
+                &transform,
+                &["longitude", "latitude"],
+                &[360, 180],
+                Some("EPSG:4326"),
+            )
+            .unwrap();
+        builder
+            .start_band(
+                Some("sst"),
+                &["latitude", "longitude"],
+                &[180, 360],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data = vec![0u8; 180 * 360 * 4];
+        builder.band_data_writer().append_value(&data);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let metadata = RasterMetadata {
-            width: 5,
-            height: 5,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        builder.start_raster(&metadata, None).unwrap();
-
-        // Add three bands using the correct API
-        for band_idx in 0..3 {
-            let band_metadata = BandMetadata {
-                nodata_value: Some(vec![255u8]),
-                storage_type: StorageType::InDb,
-                datatype: BandDataType::UInt8,
-                outdb_url: None,
-                outdb_band_id: None,
-            };
-
-            builder.start_band(band_metadata).unwrap();
-            let test_data = vec![band_idx as u8; 25]; // 5x5 raster
-            builder.band_data_writer().append_value(&test_data);
-            builder.finish_band().unwrap();
-        }
+        assert_eq!(r.x_dim(), "longitude");
+        assert_eq!(r.y_dim(), "latitude");
+        // width = size of "longitude" dim, height = size of "latitude" dim
+        assert_eq!(r.width(), Some(360));
+        assert_eq!(r.height(), Some(180));
+    }
+
+    #[test]
+    fn test_mixed_dimensionality_bands() {
+        // One 3D band and one 2D band in the same raster
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[64, 64], None)
+            .unwrap();
 
-        let result = builder.finish_raster();
-        assert!(result.is_ok());
+        // Band 0: 3D [time=12, y=64, x=64]
+        builder
+            .start_band(
+                Some("temperature"),
+                &["time", "y", "x"],
+                &[12, 64, 64],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data_3d = vec![0u8; 12 * 64 * 64 * 4];
+        builder.band_data_writer().append_value(&data_3d);
+        builder.finish_band().unwrap();
 
-        let raster_array = builder.finish().unwrap();
+        // Band 1: 2D [y=64, x=64]
+        builder
+            .start_band(
+                Some("elevation"),
+                &["y", "x"],
+                &[64, 64],
+                BandDataType::Float64,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data_2d = vec![0u8; 64 * 64 * 8];
+        builder.band_data_writer().append_value(&data_2d);
+        builder.finish_band().unwrap();
 
-        let rasters = RasterStructArray::new(&raster_array);
-        let raster = rasters.get(0).unwrap();
-        let bands = raster.bands();
+        builder.finish_raster().unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+
+        assert_eq!(r.num_bands(), 2);
+        // width/height derived from band(0) which is 3D
+        assert_eq!(r.width(), Some(64));
+        assert_eq!(r.height(), Some(64));
+
+        let b0 = r.band(0).unwrap();
+        assert_eq!(b0.ndim(), 3);
+        assert_eq!(b0.dim_names(), vec!["time", "y", "x"]);
+        assert_eq!(b0.shape(), &[12, 64, 64]);
+        assert_eq!(b0.dim_size("time"), Some(12));
+
+        let b1 = r.band(1).unwrap();
+        assert_eq!(b1.ndim(), 2);
+        assert_eq!(b1.dim_names(), vec!["y", "x"]);
+        assert_eq!(b1.shape(), &[64, 64]);
+        assert_eq!(b1.dim_size("time"), None);
+    }
 
-        assert_eq!(bands.len(), 3);
+    #[test]
+    fn test_dim_index_lookup() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[32, 32], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["time", "pressure", "y", "x"],
+                &[6, 10, 32, 32],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data = vec![0u8; 6 * 10 * 32 * 32 * 4];
+        builder.band_data_writer().append_value(&data);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Test each band has different data
-        // Use 1-based band numbers
-        for i in 0..3 {
-            // Access band with 1-based band_number
-            let band = bands.band(i + 1).unwrap();
-            let expected_value = i as u8;
-            assert!(band.data().iter().all(|&x| x == expected_value));
-        }
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+        let band = r.band(0).unwrap();
 
-        // Test iterator
-        let band_values: Vec<u8> = bands
-            .iter()
-            .enumerate()
-            .map(|(i, band)| {
-                assert_eq!(band.data()[0], i as u8);
-                band.data()[0]
-            })
-            .collect();
+        assert_eq!(band.dim_index("time"), Some(0));
+        assert_eq!(band.dim_index("pressure"), Some(1));
+        assert_eq!(band.dim_index("y"), Some(2));
+        assert_eq!(band.dim_index("x"), Some(3));
+        assert_eq!(band.dim_index("wavelength"), None);
 
-        assert_eq!(band_values, vec![0, 1, 2]);
+        assert_eq!(band.dim_size("time"), Some(6));
+        assert_eq!(band.dim_size("pressure"), Some(10));
+        assert_eq!(band.dim_size("wavelength"), None);
     }
 
     #[test]
-    fn test_copy_metadata_from_iterator() {
-        // Create an original raster
-        let mut source_builder = RasterBuilder::new(10);
-
-        let original_metadata = RasterMetadata {
-            width: 42,
-            height: 24,
-            upperleft_x: -122.0,
-            upperleft_y: 37.8,
-            scale_x: 0.1,
-            scale_y: -0.1,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+    fn test_contiguous_data_is_borrowed() {
+        use std::borrow::Cow;
 
-        source_builder
-            .start_raster(&original_metadata, None)
+        let mut builder = RasterBuilder::new(1);
+        builder
+            .start_raster_2d(4, 4, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, None)
             .unwrap();
+        builder.start_band_2d(BandDataType::UInt8, None).unwrap();
+        builder.band_data_writer().append_value([1u8; 16]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let band_metadata = BandMetadata {
-            nodata_value: Some(vec![255u8]),
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt8,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+        let band = r.band(0).unwrap();
+
+        let data = band.contiguous_data().unwrap();
+        // Identity-view bands are always contiguous, so should be 
Cow::Borrowed
+        assert!(matches!(data, Cow::Borrowed(_)));
+        assert_eq!(data.len(), 16);
+    }
 
-        source_builder.start_band(band_metadata).unwrap();
-        let test_data = vec![42u8; 1008]; // 42x24 raster
-        source_builder.band_data_writer().append_value(&test_data);
-        source_builder.finish_band().unwrap();
-        source_builder.finish_raster().unwrap();
+    #[test]
+    fn test_nd_buffer_strides_various_types() {
+        // Each raster exercises a different shape; strict spatial-grid
+        // validation forbids mixing bands of disagreeing spatial sizes within
+        // one raster.
+        let mut builder = RasterBuilder::new(3);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
 
-        let source_array = source_builder.finish().unwrap();
+        // Raster 0 — UInt8: element size = 1, shape [3, 4] → strides [4, 1]
+        builder
+            .start_raster(&transform, &["x", "y"], &[4, 3], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["y", "x"],
+                &[3, 4],
+                BandDataType::UInt8,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 12]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Create a new raster using metadata from the iterator
-        let mut target_builder = RasterBuilder::new(10);
-        let iterator = RasterStructArray::new(&source_array);
-        let source_raster = iterator.get(0).unwrap();
+        // Raster 1 — Float64: element size = 8, shape [2, 3, 5] → strides 
[120, 40, 8]
+        builder
+            .start_raster(&transform, &["x", "y"], &[5, 3], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["z", "y", "x"],
+                &[2, 3, 5],
+                BandDataType::Float64,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder
+            .band_data_writer()
+            .append_value(vec![0u8; 2 * 3 * 5 * 8]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        target_builder
-            .start_raster(source_raster.metadata(), source_raster.crs())
+        // Raster 2 — UInt16: element size = 2, shape [10] → strides [2].
+        // Only has an "x" dim, so declare spatial_dims=["x"].
+        builder
+            .start_raster(&transform, &["x"], &[10], None)
             .unwrap();
+        builder
+            .start_band(None, &["x"], &[10], BandDataType::UInt16, None, None, 
None)
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 20]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Add new band data while preserving original metadata
-        let new_band_metadata = BandMetadata {
-            nodata_value: None,
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt16,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
 
-        target_builder.start_band(new_band_metadata).unwrap();
-        let new_data = vec![100u16; 1008]; // Different data, same dimensions
-        let new_data_bytes: Vec<u8> = new_data.iter().flat_map(|&x| 
x.to_le_bytes()).collect();
+        let r0 = rasters.get(0).unwrap();
+        let b0 = r0.band(0).unwrap();
+        assert_eq!(b0.nd_buffer().unwrap().strides, &[4, 1]); // UInt8 [3, 4]
 
-        target_builder
-            .band_data_writer()
-            .append_value(&new_data_bytes);
-        target_builder.finish_band().unwrap();
-        target_builder.finish_raster().unwrap();
-
-        let target_array = target_builder.finish().unwrap();
-
-        // Verify the metadata was copied correctly
-        let target_iterator = RasterStructArray::new(&target_array);
-        let target_raster = target_iterator.get(0).unwrap();
-        let target_metadata = target_raster.metadata();
-
-        // All metadata should match the original
-        assert_eq!(target_metadata.width(), 42);
-        assert_eq!(target_metadata.height(), 24);
-        assert_eq!(target_metadata.upper_left_x(), -122.0);
-        assert_eq!(target_metadata.upper_left_y(), 37.8);
-        assert_eq!(target_metadata.scale_x(), 0.1);
-        assert_eq!(target_metadata.scale_y(), -0.1);
-
-        // But band data and metadata should be different
-        let target_band = target_raster.bands().band(1).unwrap();
-        let target_band_meta = target_band.metadata();
-        assert_eq!(target_band_meta.data_type().unwrap(), 
BandDataType::UInt16);
-        assert!(target_band_meta.nodata_value().is_none());
-        assert_eq!(target_band.data().len(), 2016); // 1008 * 2 bytes per u16
-
-        let result = target_raster.bands().band(0);
-        assert!(result.is_err(), "Band number 0 should be invalid");
-
-        let result = target_raster.bands().band(2);
-        assert!(result.is_err(), "Band number 2 should be out of range");
+        let r1 = rasters.get(1).unwrap();
+        let b1 = r1.band(0).unwrap();
+        assert_eq!(b1.nd_buffer().unwrap().strides, &[120, 40, 8]); // Float64 
[2, 3, 5]
+
+        let r2 = rasters.get(2).unwrap();
+        let b2 = r2.band(0).unwrap();
+        assert_eq!(b2.nd_buffer().unwrap().strides, &[2]); // UInt16 [10]
     }
 
     #[test]
-    fn test_band_data_types() {
-        // Create a test raster with bands of different data types
+    fn test_width_height_no_bands() {
+        // Zero-band raster — used as a "target grid" specification (GDAL warp
+        // pattern). Width/height come from the top-level spatial_shape, not
+        // band(0).
         let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[64, 32], None)
+            .unwrap();
+        builder.finish_raster().unwrap();
 
-        let metadata = RasterMetadata {
-            width: 2,
-            height: 2,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+
+        assert_eq!(r.num_bands(), 0);
+        assert_eq!(r.width(), Some(64));
+        assert_eq!(r.height(), Some(32));
+    }
+
+    #[test]
+    fn test_band_name_nullable() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[4, 4], None)
+            .unwrap();
 
-        builder.start_raster(&metadata, None).unwrap();
-
-        // Test all BandDataType variants
-        let test_cases = vec![
-            (BandDataType::UInt8, vec![1u8, 2u8, 3u8, 4u8]),
-            (BandDataType::Int8, vec![255u8, 254u8, 253u8, 252u8]), // -1, -2, 
-3, -4 as i8
-            (
-                BandDataType::UInt16,
-                vec![1u8, 0u8, 2u8, 0u8, 3u8, 0u8, 4u8, 0u8],
-            ), // little-endian u16
-            (
-                BandDataType::Int16,
-                vec![255u8, 255u8, 254u8, 255u8, 253u8, 255u8, 252u8, 255u8],
-            ), // little-endian i16
-            (
-                BandDataType::UInt32,
-                vec![
-                    1u8, 0u8, 0u8, 0u8, 2u8, 0u8, 0u8, 0u8, 3u8, 0u8, 0u8, 
0u8, 4u8, 0u8, 0u8, 0u8,
-                ],
-            ), // little-endian u32
-            (
-                BandDataType::Int32,
-                vec![
-                    255u8, 255u8, 255u8, 255u8, 254u8, 255u8, 255u8, 255u8, 
253u8, 255u8, 255u8,
-                    255u8, 252u8, 255u8, 255u8, 255u8,
-                ],
-            ), // little-endian i32
-            (
-                BandDataType::UInt64,
-                vec![
-                    1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 2u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8, 0u8,
-                    3u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8, 0u8,
-                ],
-            ), // little-endian u64
-            (
-                BandDataType::Int64,
-                vec![
-                    255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 
254u8, 255u8, 255u8,
-                    255u8, 255u8, 255u8, 255u8, 255u8, 253u8, 255u8, 255u8, 
255u8, 255u8, 255u8,
-                    255u8, 255u8, 252u8, 255u8, 255u8, 255u8, 255u8, 255u8, 
255u8, 255u8,
-                ],
-            ), // little-endian i64: -1, -2, -3, -4
-            (
+        // Named band
+        builder
+            .start_band(
+                Some("temperature"),
+                &["y", "x"],
+                &[4, 4],
                 BandDataType::Float32,
-                vec![
-                    0u8, 0u8, 128u8, 63u8, 0u8, 0u8, 0u8, 64u8, 0u8, 0u8, 
64u8, 64u8, 0u8, 0u8,
-                    128u8, 64u8,
-                ],
-            ), // little-endian f32: 1.0, 2.0, 3.0, 4.0
-            (
-                BandDataType::Float64,
-                vec![
-                    0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 240u8, 63u8, 0u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8,
-                    64u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 8u8, 64u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8,
-                    16u8, 64u8,
-                ],
-            ), // little-endian f64: 1.0, 2.0, 3.0, 4.0
-        ];
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 64]);
+        builder.finish_band().unwrap();
 
-        for (expected_data_type, test_data) in test_cases {
-            let band_metadata = BandMetadata {
-                nodata_value: None,
-                storage_type: StorageType::InDb,
-                datatype: expected_data_type,
-                outdb_url: None,
-                outdb_band_id: None,
-            };
-
-            builder.start_band(band_metadata).unwrap();
-            builder.band_data_writer().append_value(&test_data);
-            builder.finish_band().unwrap();
-        }
+        // Unnamed band (via start_band_2d which passes None for name)
+        builder.current_width = 4;
+        builder.current_height = 4;
+        builder.start_band_2d(BandDataType::UInt8, None).unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 16]);
+        builder.finish_band().unwrap();
 
         builder.finish_raster().unwrap();
-        let raster_array = builder.finish().unwrap();
-
-        // Test the data type conversion for each band
-        let iterator = RasterStructArray::new(&raster_array);
-        let raster = iterator.get(0).unwrap();
-        let bands = raster.bands();
-
-        assert_eq!(bands.len(), 10, "Expected 10 bands for all data types");
-
-        // Verify each band returns the correct data type
-        let expected_types = [
-            BandDataType::UInt8,
-            BandDataType::Int8,
-            BandDataType::UInt16,
-            BandDataType::Int16,
-            BandDataType::UInt32,
-            BandDataType::Int32,
-            BandDataType::UInt64,
-            BandDataType::Int64,
-            BandDataType::Float32,
-            BandDataType::Float64,
-        ];
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        // i is zero-based index
-        for (i, expected_type) in expected_types.iter().enumerate() {
-            // Bands are 1-based band_number
-            let band = bands.band(i + 1).unwrap();
-            let band_metadata = band.metadata();
-            let actual_type = band_metadata.data_type().unwrap();
-
-            assert_eq!(
-                actual_type, *expected_type,
-                "Band {i} expected data type {expected_type:?}, got 
{actual_type:?}"
-            );
-        }
+        assert_eq!(r.band_name(0), Some("temperature"));
+        assert_eq!(r.band_name(1), None); // unnamed
+        assert_eq!(r.band_name(99), None); // out of range
     }
 
     #[test]
-    fn test_outdb_metadata_fields() {
-        // Test creating raster with OutDb reference metadata
-        let mut builder = RasterBuilder::new(10);
-
-        let metadata = RasterMetadata {
-            width: 1024,
-            height: 1024,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+    fn test_spatial_dims_shape_roundtrip() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["longitude", "latitude"], &[360, 180], 
None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["latitude", "longitude"],
+                &[180, 360],
+                BandDataType::UInt8,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder
+            .band_data_writer()
+            .append_value(vec![0u8; 360 * 180]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        builder.start_raster(&metadata, None).unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        // Test InDb band (should have null OutDb fields)
-        let indb_band_metadata = BandMetadata {
-            nodata_value: Some(vec![255u8]),
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt8,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        assert_eq!(r.spatial_dims(), vec!["longitude", "latitude"]);
+        assert_eq!(r.spatial_shape(), &[360, 180]);
+        assert_eq!(r.x_dim(), "longitude");
+        assert_eq!(r.y_dim(), "latitude");
+        assert_eq!(r.width(), Some(360));
+        assert_eq!(r.height(), Some(180));
+    }
 
-        builder.start_band(indb_band_metadata).unwrap();
-        let test_data = vec![1u8; 100];
-        builder.band_data_writer().append_value(&test_data);
-        builder.finish_band().unwrap();
+    #[test]
+    fn test_zero_band_raster_roundtrip() {
+        // Zero-band rasters double as "target grid" specifications. They must
+        // round-trip through the builder cleanly.
+        let mut builder = RasterBuilder::new(1);
+        let transform = [10.0, 1.0, 0.0, 20.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[128, 64], 
Some("EPSG:3857"))
+            .unwrap();
+        builder.finish_raster().unwrap();
 
-        // Test OutDbRef band (should have OutDb fields populated)
-        let outdb_band_metadata = BandMetadata {
-            nodata_value: None,
-            storage_type: StorageType::OutDbRef,
-            datatype: BandDataType::Float32,
-            outdb_url: Some("s3://mybucket/satellite_image.tif".to_string()),
-            outdb_band_id: Some(2),
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        builder.start_band(outdb_band_metadata).unwrap();
-        // For OutDbRef, data field could be empty or contain 
metadata/thumbnail
-        builder.band_data_writer().append_value([]);
+        assert_eq!(r.num_bands(), 0);
+        assert_eq!(r.spatial_dims(), vec!["x", "y"]);
+        assert_eq!(r.spatial_shape(), &[128, 64]);
+        assert_eq!(r.width(), Some(128));
+        assert_eq!(r.height(), Some(64));
+        assert_eq!(r.crs(), Some("EPSG:3857"));
+    }
+
+    #[test]
+    fn test_band_missing_spatial_dim_errors() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[4, 4], None)
+            .unwrap();
+        // Band is missing "y" entirely.
+        builder
+            .start_band(None, &["x"], &[4], BandDataType::UInt8, None, None, 
None)
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 4]);
         builder.finish_band().unwrap();
 
-        builder.finish_raster().unwrap();
-        let raster_array = builder.finish().unwrap();
-
-        // Verify the band metadata
-        let iterator = RasterStructArray::new(&raster_array);
-        let raster = iterator.get(0).unwrap();
-        let bands = raster.bands();
-
-        assert_eq!(bands.len(), 2);
-
-        // Test InDb band
-        let indb_band = bands.band(1).unwrap();
-        let indb_metadata = indb_band.metadata();
-        assert_eq!(indb_metadata.storage_type().unwrap(), StorageType::InDb);
-        assert_eq!(indb_metadata.data_type().unwrap(), BandDataType::UInt8);
-        assert!(indb_metadata.outdb_url().is_none());
-        assert!(indb_metadata.outdb_band_id().is_none());
-        assert_eq!(indb_band.data().len(), 100);
-
-        // Test OutDbRef band
-        let outdb_band = bands.band(2).unwrap();
-        let outdb_metadata = outdb_band.metadata();
-        assert_eq!(
-            outdb_metadata.storage_type().unwrap(),
-            StorageType::OutDbRef
+        let err = builder.finish_raster().unwrap_err();
+        assert!(
+            err.to_string().contains("missing spatial dimension"),
+            "unexpected error: {err}"
         );
-        assert_eq!(outdb_metadata.data_type().unwrap(), BandDataType::Float32);
-        assert_eq!(
-            outdb_metadata.outdb_url().unwrap(),
-            "s3://mybucket/satellite_image.tif"
+    }
+
+    #[test]
+    fn test_start_band_rejects_zero_dim() {
+        // 0-D bands carry no spatial extent and no caller has a use for
+        // them. start_band must reject an empty dim_names slice eagerly so
+        // the malformed band never reaches the buffer layer.
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder.start_raster(&transform, &[], &[], None).unwrap();
+        let err = builder
+            .start_band(None, &[], &[], BandDataType::UInt8, None, None, None)
+            .unwrap_err();
+        assert!(
+            err.to_string().contains("0-dimensional"),
+            "unexpected error: {err}"
         );
-        assert_eq!(outdb_metadata.outdb_band_id().unwrap(), 2);
-        assert_eq!(outdb_band.data().len(), 0); // Empty data for OutDbRef
     }
 
     #[test]
-    fn test_band_access_errors() {
-        // Create a simple raster with one band
+    fn test_contiguous_data_identity_via_start_band_is_borrowed() {
+        // Canonical identity: the row's view list is null, and the read path
+        // synthesises the identity view. Should still hand the underlying
+        // bytes back without copying.
+        use std::borrow::Cow;

Review Comment:
   done



##########
rust/sedona-raster/src/builder.rs:
##########
@@ -350,500 +605,765 @@ impl RasterBuilder {
 mod tests {
     use super::*;
     use crate::array::RasterStructArray;
-    use crate::traits::{RasterMetadata, RasterRef};
-    use sedona_schema::raster::{BandDataType, StorageType};
+    use crate::traits::RasterRef;
 
     #[test]
-    fn test_iterator_basic_functionality() {
-        // Create a simple raster for testing using the correct API
-        let mut builder = RasterBuilder::new(10); // capacity
-
-        let metadata = RasterMetadata {
-            width: 10,
-            height: 10,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+    fn test_roundtrip_2d_raster() {
+        let mut builder = RasterBuilder::new(1);
+        builder
+            .start_raster_2d(
+                10,
+                20,
+                100.0,
+                200.0,
+                1.0,
+                -2.0,
+                0.25,
+                0.5,
+                Some("EPSG:4326"),
+            )
+            .unwrap();
+        builder
+            .start_band_2d(BandDataType::UInt8, Some(&[255u8]))
+            .unwrap();
+        builder.band_data_writer().append_value(vec![1u8; 200]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let epsg4326 = "EPSG:4326";
-        builder.start_raster(&metadata, Some(epsg4326)).unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        assert_eq!(rasters.len(), 1);
 
-        let band_metadata = BandMetadata {
-            nodata_value: Some(vec![255u8]),
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt8,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        let r = rasters.get(0).unwrap();
+        assert_eq!(r.width(), Some(10));
+        assert_eq!(r.height(), Some(20));
+        assert_eq!(r.transform(), &[100.0, 1.0, 0.25, 200.0, 0.5, -2.0]);
+        assert_eq!(r.x_dim(), "x");
+        assert_eq!(r.y_dim(), "y");
+        assert_eq!(r.crs(), Some("EPSG:4326"));
+        assert_eq!(r.num_bands(), 1);
+
+        let band = r.band(0).unwrap();
+        assert_eq!(band.ndim(), 2);
+        assert_eq!(band.dim_names(), vec!["y", "x"]);
+        assert_eq!(band.shape(), &[20, 10]);
+        assert_eq!(band.data_type(), BandDataType::UInt8);
+        assert_eq!(band.nodata(), Some(&[255u8][..]));
+        assert_eq!(band.contiguous_data().unwrap().len(), 200);
+    }
 
-        // Add a single band with some test data using the correct API
-        builder.start_band(band_metadata.clone()).unwrap();
-        let test_data = vec![1u8; 100]; // 10x10 raster with value 1
-        builder.band_data_writer().append_value(&test_data);
+    #[test]
+    fn test_roundtrip_multi_band() {
+        let mut builder = RasterBuilder::new(1);
+        builder
+            .start_raster_2d(2, 2, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, None)
+            .unwrap();
+
+        // Band 0: UInt8
+        builder
+            .start_band_2d(BandDataType::UInt8, Some(&[255u8]))
+            .unwrap();
+        builder.band_data_writer().append_value([1u8, 2, 3, 4]);
+        builder.finish_band().unwrap();
+
+        // Band 1: Float32
+        builder.start_band_2d(BandDataType::Float32, None).unwrap();
+        let f32_data: Vec<u8> = [1.5f32, 2.5, 3.5, 4.5]
+            .iter()
+            .flat_map(|v| v.to_le_bytes())
+            .collect();
+        builder.band_data_writer().append_value(&f32_data);
         builder.finish_band().unwrap();
-        let result = builder.finish_raster();
-        assert!(result.is_ok());
 
-        let raster_array = builder.finish().unwrap();
+        builder.finish_raster().unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        // Test the iterator
-        let rasters = RasterStructArray::new(&raster_array);
+        assert_eq!(r.num_bands(), 2);
 
-        assert_eq!(rasters.len(), 1);
-        assert!(!rasters.is_empty());
+        let b0 = r.band(0).unwrap();
+        assert_eq!(b0.data_type(), BandDataType::UInt8);
+        assert_eq!(b0.nodata(), Some(&[255u8][..]));
 
-        let raster = rasters.get(0).unwrap();
-        let metadata = raster.metadata();
+        let b1 = r.band(1).unwrap();
+        assert_eq!(b1.data_type(), BandDataType::Float32);
+        assert_eq!(b1.nodata(), None);
+    }
 
-        assert_eq!(metadata.width(), 10);
-        assert_eq!(metadata.height(), 10);
-        assert_eq!(metadata.scale_x(), 1.0);
-        assert_eq!(metadata.scale_y(), -1.0);
+    #[test]
+    fn test_null_raster() {
+        let mut builder = RasterBuilder::new(2);
+        builder
+            .start_raster_2d(1, 1, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, None)
+            .unwrap();
+        builder.start_band_2d(BandDataType::UInt8, None).unwrap();
+        builder.band_data_writer().append_value([0u8]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let bands = raster.bands();
-        assert_eq!(bands.len(), 1);
-        assert!(!bands.is_empty());
+        builder.append_null().unwrap();
 
-        // Access band with 1-based band_number
-        let band = bands.band(1).unwrap();
-        assert_eq!(band.data().len(), 100);
-        assert_eq!(band.data()[0], 1u8);
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        assert_eq!(rasters.len(), 2);
+        assert!(!rasters.is_null(0));
+        assert!(rasters.is_null(1));
+    }
 
-        let band_meta = band.metadata();
-        assert_eq!(band_meta.storage_type().unwrap(), StorageType::InDb);
-        assert_eq!(band_meta.data_type().unwrap(), BandDataType::UInt8);
+    #[test]
+    fn test_nd_band() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[5, 4], None)
+            .unwrap();
 
-        let crs = raster.crs().unwrap();
-        assert_eq!(crs, epsg4326);
+        // 3D band: [time=3, y=4, x=5]
+        builder
+            .start_band(
+                Some("temperature"),
+                &["time", "y", "x"],
+                &[3, 4, 5],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data = vec![0u8; 3 * 4 * 5 * 4]; // 3*4*5 Float32 elements
+        builder.band_data_writer().append_value(&data);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Test iterator over bands
-        let band_iter: Vec<_> = bands.iter().collect();
-        assert_eq!(band_iter.len(), 1);
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+
+        assert_eq!(r.band_name(0), Some("temperature"));
+        let band = r.band(0).unwrap();
+        assert_eq!(band.ndim(), 3);
+        assert_eq!(band.dim_names(), vec!["time", "y", "x"]);
+        assert_eq!(band.shape(), &[3, 4, 5]);
+        assert_eq!(band.dim_size("time"), Some(3));
+        assert_eq!(band.dim_size("y"), Some(4));
+        assert_eq!(band.dim_size("x"), Some(5));
+        assert_eq!(band.dim_size("z"), None);
+
+        // Verify strides are standard C-order: [4*5*4, 5*4, 4] = [80, 20, 4]
+        let buf = band.nd_buffer().unwrap();
+        assert_eq!(buf.strides, &[80, 20, 4]);
+        assert_eq!(buf.offset, 0);
     }
 
     #[test]
-    fn test_multi_band_iterator() {
-        let mut builder = RasterBuilder::new(3);
+    fn test_nonstandard_spatial_dim_names() {
+        // Zarr-style dataset with lat/lon instead of y/x
+        let mut builder = RasterBuilder::new(1);
+        let transform = [10.0, 0.01, 0.0, 50.0, 0.0, -0.01];
+        builder
+            .start_raster(
+                &transform,
+                &["longitude", "latitude"],
+                &[360, 180],
+                Some("EPSG:4326"),
+            )
+            .unwrap();
+        builder
+            .start_band(
+                Some("sst"),
+                &["latitude", "longitude"],
+                &[180, 360],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data = vec![0u8; 180 * 360 * 4];
+        builder.band_data_writer().append_value(&data);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let metadata = RasterMetadata {
-            width: 5,
-            height: 5,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        builder.start_raster(&metadata, None).unwrap();
-
-        // Add three bands using the correct API
-        for band_idx in 0..3 {
-            let band_metadata = BandMetadata {
-                nodata_value: Some(vec![255u8]),
-                storage_type: StorageType::InDb,
-                datatype: BandDataType::UInt8,
-                outdb_url: None,
-                outdb_band_id: None,
-            };
-
-            builder.start_band(band_metadata).unwrap();
-            let test_data = vec![band_idx as u8; 25]; // 5x5 raster
-            builder.band_data_writer().append_value(&test_data);
-            builder.finish_band().unwrap();
-        }
+        assert_eq!(r.x_dim(), "longitude");
+        assert_eq!(r.y_dim(), "latitude");
+        // width = size of "longitude" dim, height = size of "latitude" dim
+        assert_eq!(r.width(), Some(360));
+        assert_eq!(r.height(), Some(180));
+    }
+
+    #[test]
+    fn test_mixed_dimensionality_bands() {
+        // One 3D band and one 2D band in the same raster
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[64, 64], None)
+            .unwrap();
 
-        let result = builder.finish_raster();
-        assert!(result.is_ok());
+        // Band 0: 3D [time=12, y=64, x=64]
+        builder
+            .start_band(
+                Some("temperature"),
+                &["time", "y", "x"],
+                &[12, 64, 64],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data_3d = vec![0u8; 12 * 64 * 64 * 4];
+        builder.band_data_writer().append_value(&data_3d);
+        builder.finish_band().unwrap();
 
-        let raster_array = builder.finish().unwrap();
+        // Band 1: 2D [y=64, x=64]
+        builder
+            .start_band(
+                Some("elevation"),
+                &["y", "x"],
+                &[64, 64],
+                BandDataType::Float64,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data_2d = vec![0u8; 64 * 64 * 8];
+        builder.band_data_writer().append_value(&data_2d);
+        builder.finish_band().unwrap();
 
-        let rasters = RasterStructArray::new(&raster_array);
-        let raster = rasters.get(0).unwrap();
-        let bands = raster.bands();
+        builder.finish_raster().unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+
+        assert_eq!(r.num_bands(), 2);
+        // width/height derived from band(0) which is 3D
+        assert_eq!(r.width(), Some(64));
+        assert_eq!(r.height(), Some(64));
+
+        let b0 = r.band(0).unwrap();
+        assert_eq!(b0.ndim(), 3);
+        assert_eq!(b0.dim_names(), vec!["time", "y", "x"]);
+        assert_eq!(b0.shape(), &[12, 64, 64]);
+        assert_eq!(b0.dim_size("time"), Some(12));
+
+        let b1 = r.band(1).unwrap();
+        assert_eq!(b1.ndim(), 2);
+        assert_eq!(b1.dim_names(), vec!["y", "x"]);
+        assert_eq!(b1.shape(), &[64, 64]);
+        assert_eq!(b1.dim_size("time"), None);
+    }
 
-        assert_eq!(bands.len(), 3);
+    #[test]
+    fn test_dim_index_lookup() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[32, 32], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["time", "pressure", "y", "x"],
+                &[6, 10, 32, 32],
+                BandDataType::Float32,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let data = vec![0u8; 6 * 10 * 32 * 32 * 4];
+        builder.band_data_writer().append_value(&data);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Test each band has different data
-        // Use 1-based band numbers
-        for i in 0..3 {
-            // Access band with 1-based band_number
-            let band = bands.band(i + 1).unwrap();
-            let expected_value = i as u8;
-            assert!(band.data().iter().all(|&x| x == expected_value));
-        }
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+        let band = r.band(0).unwrap();
 
-        // Test iterator
-        let band_values: Vec<u8> = bands
-            .iter()
-            .enumerate()
-            .map(|(i, band)| {
-                assert_eq!(band.data()[0], i as u8);
-                band.data()[0]
-            })
-            .collect();
+        assert_eq!(band.dim_index("time"), Some(0));
+        assert_eq!(band.dim_index("pressure"), Some(1));
+        assert_eq!(band.dim_index("y"), Some(2));
+        assert_eq!(band.dim_index("x"), Some(3));
+        assert_eq!(band.dim_index("wavelength"), None);
 
-        assert_eq!(band_values, vec![0, 1, 2]);
+        assert_eq!(band.dim_size("time"), Some(6));
+        assert_eq!(band.dim_size("pressure"), Some(10));
+        assert_eq!(band.dim_size("wavelength"), None);
     }
 
     #[test]
-    fn test_copy_metadata_from_iterator() {
-        // Create an original raster
-        let mut source_builder = RasterBuilder::new(10);
-
-        let original_metadata = RasterMetadata {
-            width: 42,
-            height: 24,
-            upperleft_x: -122.0,
-            upperleft_y: 37.8,
-            scale_x: 0.1,
-            scale_y: -0.1,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+    fn test_contiguous_data_is_borrowed() {
+        use std::borrow::Cow;
 
-        source_builder
-            .start_raster(&original_metadata, None)
+        let mut builder = RasterBuilder::new(1);
+        builder
+            .start_raster_2d(4, 4, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, None)
             .unwrap();
+        builder.start_band_2d(BandDataType::UInt8, None).unwrap();
+        builder.band_data_writer().append_value([1u8; 16]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let band_metadata = BandMetadata {
-            nodata_value: Some(vec![255u8]),
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt8,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+        let band = r.band(0).unwrap();
+
+        let data = band.contiguous_data().unwrap();
+        // Identity-view bands are always contiguous, so should be 
Cow::Borrowed
+        assert!(matches!(data, Cow::Borrowed(_)));
+        assert_eq!(data.len(), 16);
+    }
 
-        source_builder.start_band(band_metadata).unwrap();
-        let test_data = vec![42u8; 1008]; // 42x24 raster
-        source_builder.band_data_writer().append_value(&test_data);
-        source_builder.finish_band().unwrap();
-        source_builder.finish_raster().unwrap();
+    #[test]
+    fn test_nd_buffer_strides_various_types() {
+        // Each raster exercises a different shape; strict spatial-grid
+        // validation forbids mixing bands of disagreeing spatial sizes within
+        // one raster.
+        let mut builder = RasterBuilder::new(3);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
 
-        let source_array = source_builder.finish().unwrap();
+        // Raster 0 — UInt8: element size = 1, shape [3, 4] → strides [4, 1]
+        builder
+            .start_raster(&transform, &["x", "y"], &[4, 3], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["y", "x"],
+                &[3, 4],
+                BandDataType::UInt8,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 12]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Create a new raster using metadata from the iterator
-        let mut target_builder = RasterBuilder::new(10);
-        let iterator = RasterStructArray::new(&source_array);
-        let source_raster = iterator.get(0).unwrap();
+        // Raster 1 — Float64: element size = 8, shape [2, 3, 5] → strides 
[120, 40, 8]
+        builder
+            .start_raster(&transform, &["x", "y"], &[5, 3], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["z", "y", "x"],
+                &[2, 3, 5],
+                BandDataType::Float64,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder
+            .band_data_writer()
+            .append_value(vec![0u8; 2 * 3 * 5 * 8]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        target_builder
-            .start_raster(source_raster.metadata(), source_raster.crs())
+        // Raster 2 — UInt16: element size = 2, shape [10] → strides [2].
+        // Only has an "x" dim, so declare spatial_dims=["x"].
+        builder
+            .start_raster(&transform, &["x"], &[10], None)
             .unwrap();
+        builder
+            .start_band(None, &["x"], &[10], BandDataType::UInt16, None, None, 
None)
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 20]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        // Add new band data while preserving original metadata
-        let new_band_metadata = BandMetadata {
-            nodata_value: None,
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt16,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
 
-        target_builder.start_band(new_band_metadata).unwrap();
-        let new_data = vec![100u16; 1008]; // Different data, same dimensions
-        let new_data_bytes: Vec<u8> = new_data.iter().flat_map(|&x| 
x.to_le_bytes()).collect();
+        let r0 = rasters.get(0).unwrap();
+        let b0 = r0.band(0).unwrap();
+        assert_eq!(b0.nd_buffer().unwrap().strides, &[4, 1]); // UInt8 [3, 4]
 
-        target_builder
-            .band_data_writer()
-            .append_value(&new_data_bytes);
-        target_builder.finish_band().unwrap();
-        target_builder.finish_raster().unwrap();
-
-        let target_array = target_builder.finish().unwrap();
-
-        // Verify the metadata was copied correctly
-        let target_iterator = RasterStructArray::new(&target_array);
-        let target_raster = target_iterator.get(0).unwrap();
-        let target_metadata = target_raster.metadata();
-
-        // All metadata should match the original
-        assert_eq!(target_metadata.width(), 42);
-        assert_eq!(target_metadata.height(), 24);
-        assert_eq!(target_metadata.upper_left_x(), -122.0);
-        assert_eq!(target_metadata.upper_left_y(), 37.8);
-        assert_eq!(target_metadata.scale_x(), 0.1);
-        assert_eq!(target_metadata.scale_y(), -0.1);
-
-        // But band data and metadata should be different
-        let target_band = target_raster.bands().band(1).unwrap();
-        let target_band_meta = target_band.metadata();
-        assert_eq!(target_band_meta.data_type().unwrap(), 
BandDataType::UInt16);
-        assert!(target_band_meta.nodata_value().is_none());
-        assert_eq!(target_band.data().len(), 2016); // 1008 * 2 bytes per u16
-
-        let result = target_raster.bands().band(0);
-        assert!(result.is_err(), "Band number 0 should be invalid");
-
-        let result = target_raster.bands().band(2);
-        assert!(result.is_err(), "Band number 2 should be out of range");
+        let r1 = rasters.get(1).unwrap();
+        let b1 = r1.band(0).unwrap();
+        assert_eq!(b1.nd_buffer().unwrap().strides, &[120, 40, 8]); // Float64 
[2, 3, 5]
+
+        let r2 = rasters.get(2).unwrap();
+        let b2 = r2.band(0).unwrap();
+        assert_eq!(b2.nd_buffer().unwrap().strides, &[2]); // UInt16 [10]
     }
 
     #[test]
-    fn test_band_data_types() {
-        // Create a test raster with bands of different data types
+    fn test_width_height_no_bands() {
+        // Zero-band raster — used as a "target grid" specification (GDAL warp
+        // pattern). Width/height come from the top-level spatial_shape, not
+        // band(0).
         let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[64, 32], None)
+            .unwrap();
+        builder.finish_raster().unwrap();
 
-        let metadata = RasterMetadata {
-            width: 2,
-            height: 2,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+
+        assert_eq!(r.num_bands(), 0);
+        assert_eq!(r.width(), Some(64));
+        assert_eq!(r.height(), Some(32));
+    }
+
+    #[test]
+    fn test_band_name_nullable() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[4, 4], None)
+            .unwrap();
 
-        builder.start_raster(&metadata, None).unwrap();
-
-        // Test all BandDataType variants
-        let test_cases = vec![
-            (BandDataType::UInt8, vec![1u8, 2u8, 3u8, 4u8]),
-            (BandDataType::Int8, vec![255u8, 254u8, 253u8, 252u8]), // -1, -2, 
-3, -4 as i8
-            (
-                BandDataType::UInt16,
-                vec![1u8, 0u8, 2u8, 0u8, 3u8, 0u8, 4u8, 0u8],
-            ), // little-endian u16
-            (
-                BandDataType::Int16,
-                vec![255u8, 255u8, 254u8, 255u8, 253u8, 255u8, 252u8, 255u8],
-            ), // little-endian i16
-            (
-                BandDataType::UInt32,
-                vec![
-                    1u8, 0u8, 0u8, 0u8, 2u8, 0u8, 0u8, 0u8, 3u8, 0u8, 0u8, 
0u8, 4u8, 0u8, 0u8, 0u8,
-                ],
-            ), // little-endian u32
-            (
-                BandDataType::Int32,
-                vec![
-                    255u8, 255u8, 255u8, 255u8, 254u8, 255u8, 255u8, 255u8, 
253u8, 255u8, 255u8,
-                    255u8, 252u8, 255u8, 255u8, 255u8,
-                ],
-            ), // little-endian i32
-            (
-                BandDataType::UInt64,
-                vec![
-                    1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 2u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8, 0u8,
-                    3u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8, 0u8,
-                ],
-            ), // little-endian u64
-            (
-                BandDataType::Int64,
-                vec![
-                    255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 
254u8, 255u8, 255u8,
-                    255u8, 255u8, 255u8, 255u8, 255u8, 253u8, 255u8, 255u8, 
255u8, 255u8, 255u8,
-                    255u8, 255u8, 252u8, 255u8, 255u8, 255u8, 255u8, 255u8, 
255u8, 255u8,
-                ],
-            ), // little-endian i64: -1, -2, -3, -4
-            (
+        // Named band
+        builder
+            .start_band(
+                Some("temperature"),
+                &["y", "x"],
+                &[4, 4],
                 BandDataType::Float32,
-                vec![
-                    0u8, 0u8, 128u8, 63u8, 0u8, 0u8, 0u8, 64u8, 0u8, 0u8, 
64u8, 64u8, 0u8, 0u8,
-                    128u8, 64u8,
-                ],
-            ), // little-endian f32: 1.0, 2.0, 3.0, 4.0
-            (
-                BandDataType::Float64,
-                vec![
-                    0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 240u8, 63u8, 0u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8,
-                    64u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 8u8, 64u8, 0u8, 0u8, 
0u8, 0u8, 0u8, 0u8,
-                    16u8, 64u8,
-                ],
-            ), // little-endian f64: 1.0, 2.0, 3.0, 4.0
-        ];
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 64]);
+        builder.finish_band().unwrap();
 
-        for (expected_data_type, test_data) in test_cases {
-            let band_metadata = BandMetadata {
-                nodata_value: None,
-                storage_type: StorageType::InDb,
-                datatype: expected_data_type,
-                outdb_url: None,
-                outdb_band_id: None,
-            };
-
-            builder.start_band(band_metadata).unwrap();
-            builder.band_data_writer().append_value(&test_data);
-            builder.finish_band().unwrap();
-        }
+        // Unnamed band (via start_band_2d which passes None for name)
+        builder.current_width = 4;
+        builder.current_height = 4;
+        builder.start_band_2d(BandDataType::UInt8, None).unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 16]);
+        builder.finish_band().unwrap();
 
         builder.finish_raster().unwrap();
-        let raster_array = builder.finish().unwrap();
-
-        // Test the data type conversion for each band
-        let iterator = RasterStructArray::new(&raster_array);
-        let raster = iterator.get(0).unwrap();
-        let bands = raster.bands();
-
-        assert_eq!(bands.len(), 10, "Expected 10 bands for all data types");
-
-        // Verify each band returns the correct data type
-        let expected_types = [
-            BandDataType::UInt8,
-            BandDataType::Int8,
-            BandDataType::UInt16,
-            BandDataType::Int16,
-            BandDataType::UInt32,
-            BandDataType::Int32,
-            BandDataType::UInt64,
-            BandDataType::Int64,
-            BandDataType::Float32,
-            BandDataType::Float64,
-        ];
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        // i is zero-based index
-        for (i, expected_type) in expected_types.iter().enumerate() {
-            // Bands are 1-based band_number
-            let band = bands.band(i + 1).unwrap();
-            let band_metadata = band.metadata();
-            let actual_type = band_metadata.data_type().unwrap();
-
-            assert_eq!(
-                actual_type, *expected_type,
-                "Band {i} expected data type {expected_type:?}, got 
{actual_type:?}"
-            );
-        }
+        assert_eq!(r.band_name(0), Some("temperature"));
+        assert_eq!(r.band_name(1), None); // unnamed
+        assert_eq!(r.band_name(99), None); // out of range
     }
 
     #[test]
-    fn test_outdb_metadata_fields() {
-        // Test creating raster with OutDb reference metadata
-        let mut builder = RasterBuilder::new(10);
-
-        let metadata = RasterMetadata {
-            width: 1024,
-            height: 1024,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+    fn test_spatial_dims_shape_roundtrip() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["longitude", "latitude"], &[360, 180], 
None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["latitude", "longitude"],
+                &[180, 360],
+                BandDataType::UInt8,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder
+            .band_data_writer()
+            .append_value(vec![0u8; 360 * 180]);
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        builder.start_raster(&metadata, None).unwrap();
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        // Test InDb band (should have null OutDb fields)
-        let indb_band_metadata = BandMetadata {
-            nodata_value: Some(vec![255u8]),
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt8,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        assert_eq!(r.spatial_dims(), vec!["longitude", "latitude"]);
+        assert_eq!(r.spatial_shape(), &[360, 180]);
+        assert_eq!(r.x_dim(), "longitude");
+        assert_eq!(r.y_dim(), "latitude");
+        assert_eq!(r.width(), Some(360));
+        assert_eq!(r.height(), Some(180));
+    }
 
-        builder.start_band(indb_band_metadata).unwrap();
-        let test_data = vec![1u8; 100];
-        builder.band_data_writer().append_value(&test_data);
-        builder.finish_band().unwrap();
+    #[test]
+    fn test_zero_band_raster_roundtrip() {
+        // Zero-band rasters double as "target grid" specifications. They must
+        // round-trip through the builder cleanly.
+        let mut builder = RasterBuilder::new(1);
+        let transform = [10.0, 1.0, 0.0, 20.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[128, 64], 
Some("EPSG:3857"))
+            .unwrap();
+        builder.finish_raster().unwrap();
 
-        // Test OutDbRef band (should have OutDb fields populated)
-        let outdb_band_metadata = BandMetadata {
-            nodata_value: None,
-            storage_type: StorageType::OutDbRef,
-            datatype: BandDataType::Float32,
-            outdb_url: Some("s3://mybucket/satellite_image.tif".to_string()),
-            outdb_band_id: Some(2),
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
 
-        builder.start_band(outdb_band_metadata).unwrap();
-        // For OutDbRef, data field could be empty or contain 
metadata/thumbnail
-        builder.band_data_writer().append_value([]);
+        assert_eq!(r.num_bands(), 0);
+        assert_eq!(r.spatial_dims(), vec!["x", "y"]);
+        assert_eq!(r.spatial_shape(), &[128, 64]);
+        assert_eq!(r.width(), Some(128));
+        assert_eq!(r.height(), Some(64));
+        assert_eq!(r.crs(), Some("EPSG:3857"));
+    }
+
+    #[test]
+    fn test_band_missing_spatial_dim_errors() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[4, 4], None)
+            .unwrap();
+        // Band is missing "y" entirely.
+        builder
+            .start_band(None, &["x"], &[4], BandDataType::UInt8, None, None, 
None)
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 4]);
         builder.finish_band().unwrap();
 
-        builder.finish_raster().unwrap();
-        let raster_array = builder.finish().unwrap();
-
-        // Verify the band metadata
-        let iterator = RasterStructArray::new(&raster_array);
-        let raster = iterator.get(0).unwrap();
-        let bands = raster.bands();
-
-        assert_eq!(bands.len(), 2);
-
-        // Test InDb band
-        let indb_band = bands.band(1).unwrap();
-        let indb_metadata = indb_band.metadata();
-        assert_eq!(indb_metadata.storage_type().unwrap(), StorageType::InDb);
-        assert_eq!(indb_metadata.data_type().unwrap(), BandDataType::UInt8);
-        assert!(indb_metadata.outdb_url().is_none());
-        assert!(indb_metadata.outdb_band_id().is_none());
-        assert_eq!(indb_band.data().len(), 100);
-
-        // Test OutDbRef band
-        let outdb_band = bands.band(2).unwrap();
-        let outdb_metadata = outdb_band.metadata();
-        assert_eq!(
-            outdb_metadata.storage_type().unwrap(),
-            StorageType::OutDbRef
+        let err = builder.finish_raster().unwrap_err();
+        assert!(
+            err.to_string().contains("missing spatial dimension"),
+            "unexpected error: {err}"
         );
-        assert_eq!(outdb_metadata.data_type().unwrap(), BandDataType::Float32);
-        assert_eq!(
-            outdb_metadata.outdb_url().unwrap(),
-            "s3://mybucket/satellite_image.tif"
+    }
+
+    #[test]
+    fn test_start_band_rejects_zero_dim() {
+        // 0-D bands carry no spatial extent and no caller has a use for
+        // them. start_band must reject an empty dim_names slice eagerly so
+        // the malformed band never reaches the buffer layer.
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder.start_raster(&transform, &[], &[], None).unwrap();
+        let err = builder
+            .start_band(None, &[], &[], BandDataType::UInt8, None, None, None)
+            .unwrap_err();
+        assert!(
+            err.to_string().contains("0-dimensional"),
+            "unexpected error: {err}"
         );
-        assert_eq!(outdb_metadata.outdb_band_id().unwrap(), 2);
-        assert_eq!(outdb_band.data().len(), 0); // Empty data for OutDbRef
     }
 
     #[test]
-    fn test_band_access_errors() {
-        // Create a simple raster with one band
+    fn test_contiguous_data_identity_via_start_band_is_borrowed() {
+        // Canonical identity: the row's view list is null, and the read path
+        // synthesises the identity view. Should still hand the underlying
+        // bytes back without copying.
+        use std::borrow::Cow;
+
         let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[3, 2], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["y", "x"],
+                &[2, 3],
+                BandDataType::UInt8,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        let pixels: Vec<u8> = (0..6).collect();
+        builder.band_data_writer().append_value(pixels.clone());
+        builder.finish_band().unwrap();
+        builder.finish_raster().unwrap();
 
-        let metadata = RasterMetadata {
-            width: 10,
-            height: 10,
-            upperleft_x: 0.0,
-            upperleft_y: 0.0,
-            scale_x: 1.0,
-            scale_y: -1.0,
-            skew_x: 0.0,
-            skew_y: 0.0,
-        };
+        let array = builder.finish().unwrap();
+        let rasters = RasterStructArray::new(&array);
+        let r = rasters.get(0).unwrap();
+        let band = r.band(0).unwrap();
 
-        builder.start_raster(&metadata, None).unwrap();
+        // Visible shape comes from the synthesised identity view.
+        assert_eq!(band.shape(), &[2, 3]);
+        assert_eq!(band.raw_source_shape(), &[2, 3]);
 
-        let band_metadata = BandMetadata {
-            nodata_value: None,
-            storage_type: StorageType::InDb,
-            datatype: BandDataType::UInt8,
-            outdb_url: None,
-            outdb_band_id: None,
-        };
+        let buf = band.nd_buffer().unwrap();
+        assert_eq!(buf.strides, &[3, 1]);
+        assert_eq!(buf.offset, 0);
+
+        let bytes = band.contiguous_data().unwrap();
+        assert!(matches!(bytes, Cow::Borrowed(_)));
+        assert_eq!(&*bytes, pixels.as_slice());
+    }
+
+    #[test]
+    fn test_view_field_is_null_for_identity_band() {
+        // Schema invariant: identity views are stored as null list rows so
+        // the canonical "no slice" case costs no Arrow space. Confirm by
+        // poking the raw column.
+        use arrow_array::Array;
 
-        builder.start_band(band_metadata).unwrap();
-        builder.band_data_writer().append_value([1u8; 100]);
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[2, 2], None)
+            .unwrap();
+        builder
+            .start_band(
+                None,
+                &["y", "x"],
+                &[2, 2],
+                BandDataType::UInt8,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 4]);
         builder.finish_band().unwrap();
         builder.finish_raster().unwrap();
 
-        let raster_array = builder.finish().unwrap();
-        let iterator = RasterStructArray::new(&raster_array);
-        let raster = iterator.get(0).unwrap();
-        let bands = raster.bands();
-
-        // Test invalid band number (0-based)
-        let result = bands.band(0);
-        assert!(result.is_err());
-        let err = result.err().unwrap().to_string();
-        assert!(err.contains("band numbers must be 1-based"));
-
-        // Test out of range band number
-        let result = bands.band(2);
-        assert!(result.is_err());
-        let err = result.err().unwrap().to_string();
-        assert!(err.contains("is out of range"));
-
-        // Test valid band number should still work
-        let result = bands.band(1);
-        assert!(result.is_ok());
-        let band = result.unwrap();
-        assert_eq!(band.data().len(), 100);
+        let array = builder.finish().unwrap();
+        let bands_list = array
+            .column(sedona_schema::raster::raster_indices::BANDS)
+            .as_any()
+            .downcast_ref::<ListArray>()
+            .unwrap();
+        let bands_struct = bands_list
+            .values()
+            .as_any()
+            .downcast_ref::<StructArray>()
+            .unwrap();
+        let view_list = bands_struct
+            .column(sedona_schema::raster::band_indices::VIEW)
+            .as_any()
+            .downcast_ref::<ListArray>()
+            .unwrap();
+        assert_eq!(view_list.len(), 1);
+        assert!(
+            view_list.is_null(0),
+            "identity-view band should serialise as a null view row"
+        );
+    }
+
+    #[test]
+    fn test_band_spatial_dim_size_mismatch_errors() {
+        let mut builder = RasterBuilder::new(1);
+        let transform = [0.0, 1.0, 0.0, 0.0, 0.0, -1.0];
+        builder
+            .start_raster(&transform, &["x", "y"], &[4, 4], None)
+            .unwrap();
+        // Band has "x" and "y" but x-size disagrees with top-level shape.
+        builder
+            .start_band(
+                None,
+                &["y", "x"],
+                &[4, 8],
+                BandDataType::UInt8,
+                None,
+                None,
+                None,
+            )
+            .unwrap();
+        builder.band_data_writer().append_value(vec![0u8; 32]);
+        builder.finish_band().unwrap();
+
+        let err = builder.finish_raster().unwrap_err();
+        let msg = err.to_string();
+        assert!(
+            msg.contains("has size 8") && msg.contains("expected 4"),
+            "unexpected error: {msg}"
+        );
+    }
+
+    #[test]
+    fn test_view_null_round_trips_through_arrow_ipc() {
+        // Schema invariant: a band built via start_band serialises with a
+        // null view row, and the null must survive an Arrow IPC round-trip.
+        // If a future change accidentally writes a non-null empty list
+        // instead, downstream readers (DuckDB, PyArrow, sedona-py) will
+        // disagree about whether the view is identity.
+        use arrow_array::RecordBatch;
+        use arrow_ipc::reader::StreamReader;
+        use arrow_ipc::writer::StreamWriter;
+        use arrow_schema::Schema;
+        use std::io::Cursor;

Review Comment:
   done



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to