Skip to content

Latest commit

 

History

History
508 lines (392 loc) · 10.9 KB

File metadata and controls

508 lines (392 loc) · 10.9 KB

Migration Guide: From GDAL/OGR to OxiGeo

This guide helps you migrate from GDAL/OGR (C/C++ or Python bindings) to OxiGeo (Pure Rust).

Table of Contents

Why Migrate to OxiGeo?

Advantages of OxiGeo

  1. Memory Safety: Rust's ownership system prevents common errors

    • No buffer overflows
    • No null pointer dereferences
    • Thread safety guaranteed at compile time
  2. Performance:

    • Zero-cost abstractions
    • SIMD optimization
    • Parallel processing built-in
    • No GIL (unlike Python GDAL)
  3. Deployment:

    • Single binary (no dependencies)
    • Cross-compilation support
    • Smaller footprint
    • Easier containerization
  4. Type Safety:

    • Compile-time error checking
    • Better IDE support
    • Reduced runtime errors
  5. Modern Ecosystem:

    • Async/await support
    • Cloud-native features
    • WebAssembly support

Key Differences

Conceptual Differences

Aspect GDAL/OGR OxiGeo
Memory Manual/GC Automatic (ownership)
Errors Return codes/exceptions Result<T, E>
Null values NULL pointers Option
Concurrency Manual locking Compile-time safe
Drivers Plugin-based Feature-based

Installation

GDAL:

# System dependencies required
apt-get install gdal-bin libgdal-dev
pip install gdal

OxiGeo:

# Pure Rust, no system dependencies
cargo add oxigeo-core oxigeo-geotiff

API Mapping

Opening Files

GDAL (C++):

GDALDataset *dataset = (GDALDataset*)GDALOpen("file.tif", GA_ReadOnly);
if (dataset == NULL) {
    // Handle error
}

GDAL (Python):

from osgeo import gdal
dataset = gdal.Open("file.tif")
if not dataset:
    raise Exception("Failed to open")

OxiGeo:

use oxigeo_core::io::FileDataSource;
use oxigeo_geotiff::GeoTiffReader;

let source = FileDataSource::open("file.tif")?;
let reader = GeoTiffReader::open(source)?;
// Errors handled via Result type

Reading Metadata

GDAL (Python):

width = dataset.RasterXSize
height = dataset.RasterYSize
bands = dataset.RasterCount
transform = dataset.GetGeoTransform()
projection = dataset.GetProjection()

OxiGeo:

let width = reader.width();
let height = reader.height();
let bands = reader.band_count();
let transform = reader.geo_transform();
let epsg = reader.epsg_code();

Reading Raster Data

GDAL (Python):

band = dataset.GetRasterBand(1)
array = band.ReadAsArray()

# Read window
window = band.ReadAsArray(xoff=100, yoff=100, xsize=256, ysize=256)

OxiGeo:

// Read full raster
let buffer = reader.read_tile_buffer(0, 0, 0)?;

// Read window
let window = buffer.window(100, 100, 256, 256)?;

Writing Raster Data

GDAL (Python):

driver = gdal.GetDriverByName('GTiff')
dataset = driver.Create('output.tif', width, height, 1, gdal.GDT_Float32)
dataset.SetGeoTransform(transform)
dataset.SetProjection(projection)

band = dataset.GetRasterBand(1)
band.WriteArray(array)
band.FlushCache()

dataset = None  # Close

OxiGeo:

use oxigeo_geotiff::writer::{GeoTiffWriter, GeoTiffWriterOptions};
use std::fs::File;

let options = GeoTiffWriterOptions {
    geo_transform: Some(transform),
    epsg_code: Some(4326),
    ..Default::default()
};

let file = File::create("output.tif")?;
let writer = GeoTiffWriter::new(file, options)?;
writer.write_buffer(&buffer)?;
// Automatically closed via RAII

Reprojection

GDAL (Python):

from osgeo import osr, gdal

src_ds = gdal.Open('input.tif')
dst_ds = gdal.Warp('output.tif', src_ds,
                   srcSRS='EPSG:4326',
                   dstSRS='EPSG:3857')

OxiGeo:

use oxigeo_algorithms::reproject::{reproject, ReprojectOptions, Resampling};
use oxigeo_proj::Projection;

let src_proj = Projection::from_epsg(4326)?;
let dst_proj = Projection::from_epsg(3857)?;

let options = ReprojectOptions {
    src_projection: &src_proj,
    dst_projection: &dst_proj,
    src_geo_transform: geo_transform,
    dst_width: width,
    dst_height: height,
    resampling: Resampling::Bilinear,
    nodata: None,
};

let reprojected = reproject(&buffer, &options)?;

Vector Operations

GDAL (Python):

from osgeo import ogr

driver = ogr.GetDriverByName('GeoJSON')
datasource = driver.Open('input.geojson')
layer = datasource.GetLayer()

for feature in layer:
    geom = feature.GetGeometryRef()
    area = geom.GetArea()

OxiGeo:

use geo::Area;
use geojson::GeoJson;
use std::fs;

let geojson_str = fs::read_to_string("input.geojson")?;
let geojson = geojson_str.parse::<GeoJson>()?;

if let GeoJson::FeatureCollection(fc) = geojson {
    for feature in fc.features {
        if let Some(geom) = feature.geometry {
            // Convert to geo types and process
        }
    }
}

Common Patterns

Pattern 1: Tile Processing

GDAL (Python):

def process_tiles(dataset, tile_size=256):
    width = dataset.RasterXSize
    height = dataset.RasterYSize
    band = dataset.GetRasterBand(1)

    for y in range(0, height, tile_size):
        for x in range(0, width, tile_size):
            w = min(tile_size, width - x)
            h = min(tile_size, height - y)

            tile = band.ReadAsArray(x, y, w, h)
            # Process tile
            result = process(tile)
            # Write back if needed

OxiGeo:

use rayon::prelude::*;

fn process_tiles(reader: &GeoTiffReader, tile_size: u32) -> Result<(), Error> {
    let width = reader.width();
    let height = reader.height();

    let tiles: Vec<(u32, u32)> = (0..height/tile_size)
        .flat_map(|ty| (0..width/tile_size).map(move |tx| (tx, ty)))
        .collect();

    // Parallel processing
    tiles.par_iter().try_for_each(|(tx, ty)| {
        let tile = reader.read_tile_buffer(*tx, *ty, 0)?;
        // Process tile
        let result = process(&tile)?;
        Ok(())
    })
}

Pattern 2: Band Math

GDAL (Python):

import numpy as np

red = dataset.GetRasterBand(4).ReadAsArray()
nir = dataset.GetRasterBand(5).ReadAsArray()

ndvi = (nir - red) / (nir + red)

OxiGeo:

fn calculate_ndvi(nir: &RasterBuffer, red: &RasterBuffer)
    -> Result<RasterBuffer, Error>
{
    let mut ndvi = RasterBuffer::zeros(
        nir.width(),
        nir.height(),
        RasterDataType::Float32
    );

    for y in 0..nir.height() {
        for x in 0..nir.width() {
            let nir_val = nir.get_pixel(x, y)?;
            let red_val = red.get_pixel(x, y)?;

            let value = if (nir_val + red_val).abs() > 1e-10 {
                (nir_val - red_val) / (nir_val + red_val)
            } else {
                0.0
            };

            ndvi.set_pixel(x, y, value)?;
        }
    }

    Ok(ndvi)
}

Pattern 3: Cloud/HTTP Data

GDAL (Python):

# GDAL uses virtual file systems
dataset = gdal.Open('/vsicurl/https://example.com/data.tif')

OxiGeo:

use oxigeo_cloud::backends::HttpBackend;
use oxigeo_cloud::retry::RetryConfig;

let retry_config = RetryConfig::default();
let http_backend = HttpBackend::new(retry_config);

// Async operation
let data = http_backend.get("https://example.com/data.tif").await?;

Performance Considerations

Memory Usage

GDAL:

  • Manual memory management
  • Potential leaks with improper cleanup
  • Python GC overhead

OxiGeo:

  • Automatic cleanup (RAII)
  • No GC overhead
  • Stack allocation when possible

Parallelization

GDAL:

# Need to use multiprocessing
from multiprocessing import Pool

def worker(tile_coords):
    # Each process opens file independently
    ds = gdal.Open('input.tif')
    return process(ds, tile_coords)

with Pool() as pool:
    results = pool.map(worker, tile_list)

OxiGeo:

// Built-in parallel iterators
use rayon::prelude::*;

let results: Vec<_> = tile_list
    .par_iter()
    .map(|tile| process(tile))
    .collect();
// Compiler ensures thread safety

SIMD Operations

GDAL:

  • Limited SIMD support
  • Numpy can use BLAS/MKL

OxiGeo:

use oxigeo_core::simd_buffer::SimdBuffer;

let simd_buffer = SimdBuffer::from_buffer(&buffer)?;
let result = simd_buffer.add_scalar(10.0)?; // Vectorized automatically

Error Handling

GDAL Approach

C++:

CPLErr err = GDALRasterIO(...);
if (err != CE_None) {
    fprintf(stderr, "Error: %s\n", CPLGetLastErrorMsg());
}

Python:

try:
    dataset = gdal.Open('file.tif')
    if not dataset:
        raise Exception("Failed to open file")
except RuntimeError as e:
    print(f"Error: {e}")

OxiGeo Approach

use oxigeo_core::error::Result;

fn process_file(path: &str) -> Result<()> {
    let source = FileDataSource::open(path)?;
    let reader = GeoTiffReader::open(source)?;

    // Errors propagate via ?
    let buffer = reader.read_tile_buffer(0, 0, 0)?;

    Ok(())
}

// Using the function
match process_file("input.tif") {
    Ok(()) => println!("Success"),
    Err(e) => eprintln!("Error: {}", e),
}

Migration Checklist

  • Identify GDAL features used in your code
  • Check OxiGeo feature support
  • Replace file I/O with OxiGeo equivalents
  • Update error handling to use Result<T, E>
  • Replace NULL checks with Option
  • Convert array operations to RasterBuffer
  • Update build system (remove GDAL dependency)
  • Add appropriate Cargo features
  • Update tests
  • Profile performance

Troubleshooting

Issue: Missing Driver Support

Problem: GDAL driver not available in OxiGeo

Solution:

  • Check if driver is feature-gated: cargo add oxigeo-hdf5 --features hdf5
  • For unsupported formats, consider preprocessing with GDAL

Issue: Different Results

Problem: Numerical results differ slightly

Solution:

  • Floating point precision differences are normal
  • Check algorithm implementations
  • Use approx crate for fuzzy comparisons

Issue: Performance Regression

Problem: OxiGeo slower than GDAL for specific operation

Solution:

  • Enable release mode: cargo build --release
  • Use parallel processing with Rayon
  • Enable SIMD: RUSTFLAGS="-C target-cpu=native"
  • Profile with cargo flamegraph

Resources

Getting Help

Next Steps

After migration:

  1. Review the Performance Guide
  2. Check the Deployment Guide
  3. Explore advanced features in tutorials
  4. Join the community and share feedback