rstiff

Crates.iorstiff
lib.rsrstiff
version0.2.0
created_at2025-12-19 09:38:55.911995+00
updated_at2026-01-14 14:54:46.469245+00
descriptionA Rust library for high-precision, type-preserving GeoTiff I/O powered by GDAL.
homepage
repositoryhttps://github.com/csuhqf/rstiff
max_upload_size
id1994472
size2,824,589
(csuhqf)

documentation

https://docs.rs/rstiff

README

rstiff

A high-precision, type-preserving Rust library for GeoTiff I/O and processing, powered by GDAL.

Crates.io Downloads License: MIT

Features

  • High Precision: Loads data into ndarray::Array3<f64> for accurate scientific computing.
  • Type-Preserving I/O: Automatically restores the original data type (e.g., Byte, UInt16, Float32) when writing.
  • Smart NoData Handling: Correctly handles NoData values and preserves transparency in output files.
  • Window Reading: Read only the data you need - by pixel coordinates, geographic bounds, or vector extent.
  • Reprojection: Robust coordinate transformation with automatic calculation of new bounds and pixel resolution.
  • Vector Cropping: Crop rasters using vector files (Shapefile, KML, GeoJSON) with optional masking.
  • Efficient: Uses optimized GDAL drivers with LZW compression.

Prerequisites

rstiff relies on GDAL bindings. You must have GDAL installed on your system.

macOS (Homebrew)

brew install gdal

Ubuntu/Debian

sudo apt-get install libgdal-dev

Windows

Download and install GDAL from GISInternals or use conda:

conda install -c conda-forge gdal

Installation

Add this to your Cargo.toml:

[dependencies]
rstiff = "0.1"

Or install via cargo:

cargo add rstiff

Quick Start

Basic Read and Write

use rstiff::GeoTiff;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Read a GeoTiff file
    let mut tif = GeoTiff::read("input.tif")?;

    println!("Dimensions: {:?}", tif.data.dim());  // (bands, height, width)
    println!("Projection: {}", tif.projection);

    // Modify pixel data (it's an ndarray::Array3<f64>)
    tif.data[[0, 100, 100]] = 255.0;

    // Write back - original data type is preserved
    tif.write("output.tif")?;

    Ok(())
}

API Reference

Core Types

Type Description
GeoTiff Main struct containing raster data, geotransform, projection, and metadata
RasterInfo Lightweight metadata struct (no pixel data loaded)
TiffError Error type for all operations

Reading Data

GeoTiff::read() - Read entire file

let tif = GeoTiff::read("input.tif")?;

GeoTiff::info() - Get metadata without loading pixels

let info = GeoTiff::info("large_file.tif")?;
println!("Size: {}x{}", info.width, info.height);
println!("Bands: {}", info.bands);
println!("Bounds: {:?}", info.bounds());  // (min_x, min_y, max_x, max_y)
println!("Resolution: {:?}", info.res()); // (pixel_width, pixel_height)

GeoTiff::read_window() - Read by pixel coordinates

Only load the pixels you need - perfect for large files:

// Read a 256x256 tile starting at pixel (1000, 2000)
let tile = GeoTiff::read_window("large.tif", 1000, 2000, 256, 256)?;

GeoTiff::read_bounds() - Read by geographic coordinates

// Read data within a geographic bounding box
let roi = GeoTiff::read_bounds("large.tif", (116.0, 39.0, 117.0, 40.0))?;
//                              path         min_x  min_y  max_x  max_y

GeoTiff::read_by_vector() - Read by vector file extent

// Read only the area covered by a KML/Shapefile/GeoJSON
let roi = GeoTiff::read_by_vector("large.tif", "area.kml", true)?;
//                                 raster      vector     apply_mask

// apply_mask = true  -> pixels outside polygon set to NoData
// apply_mask = false -> just clip to bounding box

Coordinate Conversion

Use RasterInfo for coordinate transformations:

let info = GeoTiff::info("input.tif")?;

// Pixel to geographic coordinates
let (x, y) = info.pixel_to_geo(100, 200);  // col, row -> x, y

// Geographic to pixel coordinates  
let (col, row) = info.geo_to_pixel(116.5, 39.5);  // x, y -> col, row

// Convert geographic bounds to pixel window
let (x_off, y_off, width, height) = info.bounds_to_window((116.0, 39.0, 117.0, 40.0))?;

Processing

crop() - Crop by pixel coordinates

let cropped = tif.crop(100, 100, 500, 500)?;  // x_off, y_off, width, height

crop_by_vector() - Crop by vector file (loads full raster first)

let cropped = tif.crop_by_vector("boundary.shp", true)?;
// Use read_by_vector() instead for large files!

reproject() - Reproject to different CRS

// Reproject from WGS84 to UTM Zone 50N
let utm = tif.reproject(32650)?;  // EPSG code

Writing

tif.write("output.tif")?;
// - Original data type is preserved (Byte, UInt16, Float32, etc.)
// - LZW compression is applied automatically
// - NoData values are handled correctly

Memory Comparison

For a 10GB raster file, window reading dramatically reduces memory usage:

Method Memory Usage
GeoTiff::read() ~10 GB (loads everything)
GeoTiff::read_window(..., 256, 256) ~0.5 MB (loads only needed area)
GeoTiff::read_by_vector() Variable (loads only ROI)

Examples

Run the included examples:

# Basic read/write
cargo run --example basic_io

# Reprojection
cargo run --example reproject

# Vector cropping (traditional)
cargo run --example vector_crop

# Window reading (memory efficient)
cargo run --example window_read

# Vector window reading
cargo run --example vector_window_read

Comparison with Python rasterio

rasterio rstiff
rasterio.open() GeoTiff::read()
dataset.read(window=Window(...)) GeoTiff::read_window()
dataset.read() with bounds GeoTiff::read_bounds()
rasterio.mask.mask() crop_by_vector() / read_by_vector()
rasterio.warp.reproject() reproject()
dataset.xy() RasterInfo::pixel_to_geo()
dataset.index() RasterInfo::geo_to_pixel()
dataset.bounds RasterInfo::bounds()
dataset.res RasterInfo::res()

Roadmap

See ROADMAP.md for planned features:

  • Resampling (Nearest, Bilinear, Cubic, Lanczos)
  • Band statistics (min, max, mean, std, histogram)
  • Band math helpers (NDVI, NDWI)
  • Memory file support
  • Raster merge/mosaic
  • Cloud storage support (S3, HTTP)

Acknowledgments

Built on these excellent projects:

License

MIT License - see LICENSE for details.

Commit count: 11

cargo fmt