This guide helps you migrate from GDAL/OGR (C/C++ or Python bindings) to OxiGeo (Pure Rust).
- Why Migrate to OxiGeo?
- Key Differences
- API Mapping
- Common Patterns
- Performance Considerations
- Troubleshooting
-
Memory Safety: Rust's ownership system prevents common errors
- No buffer overflows
- No null pointer dereferences
- Thread safety guaranteed at compile time
-
Performance:
- Zero-cost abstractions
- SIMD optimization
- Parallel processing built-in
- No GIL (unlike Python GDAL)
-
Deployment:
- Single binary (no dependencies)
- Cross-compilation support
- Smaller footprint
- Easier containerization
-
Type Safety:
- Compile-time error checking
- Better IDE support
- Reduced runtime errors
-
Modern Ecosystem:
- Async/await support
- Cloud-native features
- WebAssembly support
| 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 |
GDAL:
# System dependencies required
apt-get install gdal-bin libgdal-dev
pip install gdalOxiGeo:
# Pure Rust, no system dependencies
cargo add oxigeo-core oxigeo-geotiffGDAL (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 typeGDAL (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();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)?;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 # CloseOxiGeo:
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 RAIIGDAL (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)?;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
}
}
}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 neededOxiGeo:
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(())
})
}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)
}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?;GDAL:
- Manual memory management
- Potential leaks with improper cleanup
- Python GC overhead
OxiGeo:
- Automatic cleanup (RAII)
- No GC overhead
- Stack allocation when possible
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 safetyGDAL:
- 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 automaticallyC++:
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}")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),
}- 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
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
Problem: Numerical results differ slightly
Solution:
- Floating point precision differences are normal
- Check algorithm implementations
- Use
approxcrate for fuzzy comparisons
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
- GitHub Issues: https://github.com/cool-japan/oxigeo/issues
- Discussions: https://github.com/cool-japan/oxigeo/discussions
- COOLJAPAN Community: Contact team
After migration:
- Review the Performance Guide
- Check the Deployment Guide
- Explore advanced features in tutorials
- Join the community and share feedback