Skip to main content

Module tutorials

Module tutorials 

Source
Expand description

Getting Started with OxiGDAL Core

This module provides tutorials and examples for using OxiGDAL Core effectively.

§Table of Contents

  1. Basic Concepts
  2. Working with Raster Data
  3. Coordinate Systems
  4. Buffers and Memory Management
  5. Error Handling
  6. Performance Tips

§Basic Concepts

OxiGDAL Core provides fundamental abstractions for geospatial data processing. The main concepts are:

  • Bounding Box: Defines spatial extent in geographic/projected coordinates
  • Geo Transform: Maps between pixel and world coordinates
  • Raster Buffer: Stores pixel data with type safety
  • Data Types: Represents various pixel formats (UInt8, Float32, etc.)

§Example: Creating a Simple Raster

use oxigdal_core::types::{BoundingBox, GeoTransform, RasterDataType};
use oxigdal_core::buffer::RasterBuffer;

// Define spatial extent (global coverage)
let bbox = BoundingBox::new(-180.0, -90.0, 180.0, 90.0)?;

// Create geotransform for 1-degree resolution
let width = 360;
let height = 180;
let gt = GeoTransform::from_bounds(&bbox, width, height)?;

// Create raster buffer
let buffer = RasterBuffer::zeros(width as u64, height as u64, RasterDataType::Float32);

println!("Created {}x{} raster covering {:?}", width, height, bbox);

§Working with Raster Data

§Creating Buffers

use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::{RasterDataType, NoDataValue};

// Zero-filled buffer
let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);

// Buffer with nodata value
let nodata = NoDataValue::Float(-9999.0);
let buffer_with_nodata = RasterBuffer::nodata_filled(
    1000,
    1000,
    RasterDataType::Float32,
    nodata
);

§Reading and Writing Pixels

use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::RasterDataType;

let mut buffer = RasterBuffer::zeros(100, 100, RasterDataType::Float32);

// Write pixel value at (50, 50)
buffer.set_pixel(50, 50, 255.0)?;

// Read pixel value
let value = buffer.get_pixel(50, 50)?;
assert_eq!(value, 255.0);

§Computing Statistics

use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::RasterDataType;

let mut buffer = RasterBuffer::zeros(100, 100, RasterDataType::Float32);

// Fill with test data
for y in 0..100 {
    for x in 0..100 {
        buffer.set_pixel(x, y, (x + y) as f64)?;
    }
}

// Compute statistics
let stats = buffer.compute_statistics()?;
println!("Min: {}, Max: {}", stats.min, stats.max);
println!("Mean: {}, StdDev: {}", stats.mean, stats.std_dev);
println!("Valid pixels: {}", stats.valid_count);

§Coordinate Systems

§Creating a GeoTransform

GeoTransform defines the relationship between pixel coordinates and world coordinates.

use oxigdal_core::types::{BoundingBox, GeoTransform};

// Method 1: From bounds and dimensions
let bbox = BoundingBox::new(-180.0, -90.0, 180.0, 90.0)?;
let gt = GeoTransform::from_bounds(&bbox, 360, 180)?;

// Method 2: North-up image (no rotation)
let gt = GeoTransform::north_up(-180.0, 90.0, 1.0, -1.0);

// Method 3: Full specification (with rotation)
let gt = GeoTransform::new(
    -180.0,  // origin_x
    1.0,     // pixel_width
    0.0,     // row_rotation
    90.0,    // origin_y
    0.0,     // col_rotation
    -1.0,    // pixel_height (negative for north-up)
);

§Converting Between Pixel and World Coordinates

use oxigdal_core::types::GeoTransform;

let gt = GeoTransform::north_up(-180.0, 90.0, 1.0, -1.0);

// Pixel to world
let (lon, lat) = gt.pixel_to_world(180.0, 90.0);
println!("Pixel (180, 90) -> World ({}, {})", lon, lat);

// World to pixel
let (px, py) = gt.world_to_pixel(0.0, 0.0)?;
println!("World (0, 0) -> Pixel ({}, {})", px, py);

§Working with Bounding Boxes

use oxigdal_core::types::BoundingBox;

let bbox1 = BoundingBox::new(-180.0, -90.0, 0.0, 90.0)?;
let bbox2 = BoundingBox::new(-90.0, -45.0, 90.0, 45.0)?;

// Check intersection
if bbox1.intersects(&bbox2) {
    println!("Bounding boxes intersect!");

    // Compute intersection
    if let Some(intersection) = bbox1.intersection(&bbox2) {
        println!("Intersection: {:?}", intersection);
    }
}

// Compute union
let union = bbox1.union(&bbox2);
println!("Union: {:?}", union);

// Expand to include a point
let mut bbox = BoundingBox::new(0.0, 0.0, 10.0, 10.0)?;
bbox.expand_to_include(15.0, 15.0);

§Buffers and Memory Management

§Type Conversion

use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::RasterDataType;

// Create UInt8 buffer
let buffer_u8 = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);

// Convert to Float32
let buffer_f32 = buffer_u8.convert_to(RasterDataType::Float32)?;

assert_eq!(buffer_f32.data_type(), RasterDataType::Float32);

§Working with NoData

use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::{RasterDataType, NoDataValue};

let nodata = NoDataValue::Float(-9999.0);
let mut buffer = RasterBuffer::nodata_filled(
    100,
    100,
    RasterDataType::Float32,
    nodata
);

// Check if a value is nodata
assert!(buffer.is_nodata(-9999.0));
assert!(!buffer.is_nodata(100.0));

// Set valid pixel
buffer.set_pixel(50, 50, 100.0)?;

// Compute statistics (ignores nodata)
let stats = buffer.compute_statistics()?;
println!("Valid pixels: {}", stats.valid_count);

§SIMD-Aligned Buffers

For high-performance operations, use SIMD-aligned buffers:

use oxigdal_core::simd_buffer::AlignedBuffer;

// Create 64-byte aligned buffer (AVX-512)
let mut buffer = AlignedBuffer::<f32>::new(1024 * 1024, 64)?;

// Fill with data
for (i, val) in buffer.as_mut_slice().iter_mut().enumerate() {
    *val = i as f32;
}

// Access as slice
let slice = buffer.as_slice();
assert_eq!(slice.len(), 1024 * 1024);

§Error Handling

All OxiGDAL functions return Result<T, OxiGdalError>. The error types are:

use oxigdal_core::error::{OxiGdalError, IoError, FormatError};

fn example() -> oxigdal_core::Result<()> {
    // Use ? operator for error propagation
    let bbox = oxigdal_core::types::BoundingBox::new(0.0, 0.0, 10.0, 10.0)?;

    // Pattern match on errors
    match do_something() {
        Ok(result) => println!("Success: {:?}", result),
        Err(OxiGdalError::Io(io_err)) => {
            eprintln!("I/O error: {}", io_err);
        }
        Err(OxiGdalError::InvalidParameter { parameter, message }) => {
            eprintln!("Invalid {}: {}", parameter, message);
        }
        Err(e) => {
            eprintln!("Other error: {}", e);
        }
    }

    Ok(())
}

fn do_something() -> oxigdal_core::Result<()> {
    Ok(())
}

§Performance Tips

§1. Use Appropriate Data Types

Choose the smallest data type that meets your needs:

  • UInt8: 0-255 range (e.g., RGB images)
  • Int16: -32768 to 32767 (e.g., elevation data)
  • Float32: General-purpose floating point (good balance of precision and size)
  • Float64: High precision (use when necessary)

§2. Leverage SIMD Buffers

For performance-critical code, use SIMD-aligned buffers:

use oxigdal_core::simd_buffer::AlignedBuffer;

// 64-byte alignment for AVX-512
let buffer = AlignedBuffer::<f32>::new(1_000_000, 64)?;

§3. Minimize Type Conversions

Type conversions are expensive. Work in the native data type when possible.

§4. Batch Operations

Process multiple pixels at once rather than one at a time:

use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::RasterDataType;

let mut buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);

// Slow: pixel-by-pixel
for y in 0..1000 {
    for x in 0..1000 {
        buffer.set_pixel(x, y, 100.0).ok();
    }
}

// Fast: use fill_value
buffer.fill_value(100.0);

§5. Consider Memory Layout

OxiGDAL uses row-major order (scanline-based). Access pixels in row order for better cache performance:

// Good: row-major order (cache-friendly)
for y in 0..buffer.height() {
    for x in 0..buffer.width() {
        // process pixel (x, y)
    }
}

// Bad: column-major order (cache-unfriendly)
for x in 0..buffer.width() {
    for y in 0..buffer.height() {
        // process pixel (x, y)
    }
}

§6. Avoid Repeated Statistics Computation

Statistics computation is O(n). Cache results when possible:

// Compute once
let stats = buffer.compute_statistics()?;

// Use multiple times
println!("Min: {}, Max: {}", stats.min, stats.max);
println!("Mean: {}", stats.mean);