Skip to main content

oxigdal_core/
tutorials.rs

1//! Getting Started with `OxiGDAL` Core
2//!
3//! This module provides tutorials and examples for using `OxiGDAL` Core effectively.
4//!
5//! # Table of Contents
6//!
7//! 1. [Basic Concepts](#basic-concepts)
8//! 2. [Working with Raster Data](#working-with-raster-data)
9//! 3. [Coordinate Systems](#coordinate-systems)
10//! 4. [Buffers and Memory Management](#buffers-and-memory-management)
11//! 5. [Error Handling](#error-handling)
12//! 6. [Performance Tips](#performance-tips)
13//!
14//! # Basic Concepts
15//!
16//! `OxiGDAL` Core provides fundamental abstractions for geospatial data processing.
17//! The main concepts are:
18//!
19//! - **Bounding Box**: Defines spatial extent in geographic/projected coordinates
20//! - **Geo Transform**: Maps between pixel and world coordinates
21//! - **Raster Buffer**: Stores pixel data with type safety
22//! - **Data Types**: Represents various pixel formats (`UInt8`, Float32, etc.)
23//!
24//! ## Example: Creating a Simple Raster
25//!
26//! ```rust
27//! use oxigdal_core::types::{BoundingBox, GeoTransform, RasterDataType};
28//! use oxigdal_core::buffer::RasterBuffer;
29//!
30//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
31//! // Define spatial extent (global coverage)
32//! let bbox = BoundingBox::new(-180.0, -90.0, 180.0, 90.0)?;
33//!
34//! // Create geotransform for 1-degree resolution
35//! let width = 360;
36//! let height = 180;
37//! let gt = GeoTransform::from_bounds(&bbox, width, height)?;
38//!
39//! // Create raster buffer
40//! let buffer = RasterBuffer::zeros(width as u64, height as u64, RasterDataType::Float32);
41//!
42//! println!("Created {}x{} raster covering {:?}", width, height, bbox);
43//! # Ok(())
44//! # }
45//! ```
46//!
47//! # Working with Raster Data
48//!
49//! ## Creating Buffers
50//!
51//! ```rust
52//! use oxigdal_core::buffer::RasterBuffer;
53//! use oxigdal_core::types::{RasterDataType, NoDataValue};
54//!
55//! // Zero-filled buffer
56//! let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
57//!
58//! // Buffer with nodata value
59//! let nodata = NoDataValue::Float(-9999.0);
60//! let buffer_with_nodata = RasterBuffer::nodata_filled(
61//!     1000,
62//!     1000,
63//!     RasterDataType::Float32,
64//!     nodata
65//! );
66//! ```
67//!
68//! ## Reading and Writing Pixels
69//!
70//! ```rust
71//! use oxigdal_core::buffer::RasterBuffer;
72//! use oxigdal_core::types::RasterDataType;
73//!
74//! let mut buffer = RasterBuffer::zeros(100, 100, RasterDataType::Float32);
75//!
76//! // Write pixel value at (50, 50)
77//! buffer.set_pixel(50, 50, 255.0)?;
78//!
79//! // Read pixel value
80//! let value = buffer.get_pixel(50, 50)?;
81//! assert_eq!(value, 255.0);
82//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
83//! ```
84//!
85//! ## Computing Statistics
86//!
87//! ```rust
88//! use oxigdal_core::buffer::RasterBuffer;
89//! use oxigdal_core::types::RasterDataType;
90//!
91//! let mut buffer = RasterBuffer::zeros(100, 100, RasterDataType::Float32);
92//!
93//! // Fill with test data
94//! for y in 0..100 {
95//!     for x in 0..100 {
96//!         buffer.set_pixel(x, y, (x + y) as f64)?;
97//!     }
98//! }
99//!
100//! // Compute statistics
101//! let stats = buffer.compute_statistics()?;
102//! println!("Min: {}, Max: {}", stats.min, stats.max);
103//! println!("Mean: {}, StdDev: {}", stats.mean, stats.std_dev);
104//! println!("Valid pixels: {}", stats.valid_count);
105//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
106//! ```
107//!
108//! # Coordinate Systems
109//!
110//! ## Creating a `GeoTransform`
111//!
112//! `GeoTransform` defines the relationship between pixel coordinates and world coordinates.
113//!
114//! ```rust
115//! use oxigdal_core::types::{BoundingBox, GeoTransform};
116//!
117//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
118//! // Method 1: From bounds and dimensions
119//! let bbox = BoundingBox::new(-180.0, -90.0, 180.0, 90.0)?;
120//! let gt = GeoTransform::from_bounds(&bbox, 360, 180)?;
121//!
122//! // Method 2: North-up image (no rotation)
123//! let gt = GeoTransform::north_up(-180.0, 90.0, 1.0, -1.0);
124//!
125//! // Method 3: Full specification (with rotation)
126//! let gt = GeoTransform::new(
127//!     -180.0,  // origin_x
128//!     1.0,     // pixel_width
129//!     0.0,     // row_rotation
130//!     90.0,    // origin_y
131//!     0.0,     // col_rotation
132//!     -1.0,    // pixel_height (negative for north-up)
133//! );
134//! # Ok(())
135//! # }
136//! ```
137//!
138//! ## Converting Between Pixel and World Coordinates
139//!
140//! ```rust
141//! use oxigdal_core::types::GeoTransform;
142//!
143//! # fn main() -> Result<(), oxigdal_core::error::OxiGdalError> {
144//! let gt = GeoTransform::north_up(-180.0, 90.0, 1.0, -1.0);
145//!
146//! // Pixel to world
147//! let (lon, lat) = gt.pixel_to_world(180.0, 90.0);
148//! println!("Pixel (180, 90) -> World ({}, {})", lon, lat);
149//!
150//! // World to pixel
151//! let (px, py) = gt.world_to_pixel(0.0, 0.0)?;
152//! println!("World (0, 0) -> Pixel ({}, {})", px, py);
153//! # Ok(())
154//! # }
155//! ```
156//!
157//! ## Working with Bounding Boxes
158//!
159//! ```rust
160//! use oxigdal_core::types::BoundingBox;
161//!
162//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
163//! let bbox1 = BoundingBox::new(-180.0, -90.0, 0.0, 90.0)?;
164//! let bbox2 = BoundingBox::new(-90.0, -45.0, 90.0, 45.0)?;
165//!
166//! // Check intersection
167//! if bbox1.intersects(&bbox2) {
168//!     println!("Bounding boxes intersect!");
169//!
170//!     // Compute intersection
171//!     if let Some(intersection) = bbox1.intersection(&bbox2) {
172//!         println!("Intersection: {:?}", intersection);
173//!     }
174//! }
175//!
176//! // Compute union
177//! let union = bbox1.union(&bbox2);
178//! println!("Union: {:?}", union);
179//!
180//! // Expand to include a point
181//! let mut bbox = BoundingBox::new(0.0, 0.0, 10.0, 10.0)?;
182//! bbox.expand_to_include(15.0, 15.0);
183//! # Ok(())
184//! # }
185//! ```
186//!
187//! # Buffers and Memory Management
188//!
189//! ## Type Conversion
190//!
191//! ```rust
192//! use oxigdal_core::buffer::RasterBuffer;
193//! use oxigdal_core::types::RasterDataType;
194//!
195//! // Create UInt8 buffer
196//! let buffer_u8 = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
197//!
198//! // Convert to Float32
199//! let buffer_f32 = buffer_u8.convert_to(RasterDataType::Float32)?;
200//!
201//! assert_eq!(buffer_f32.data_type(), RasterDataType::Float32);
202//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
203//! ```
204//!
205//! ## Working with `NoData`
206//!
207//! ```rust
208//! use oxigdal_core::buffer::RasterBuffer;
209//! use oxigdal_core::types::{RasterDataType, NoDataValue};
210//!
211//! let nodata = NoDataValue::Float(-9999.0);
212//! let mut buffer = RasterBuffer::nodata_filled(
213//!     100,
214//!     100,
215//!     RasterDataType::Float32,
216//!     nodata
217//! );
218//!
219//! // Check if a value is nodata
220//! assert!(buffer.is_nodata(-9999.0));
221//! assert!(!buffer.is_nodata(100.0));
222//!
223//! // Set valid pixel
224//! buffer.set_pixel(50, 50, 100.0)?;
225//!
226//! // Compute statistics (ignores nodata)
227//! let stats = buffer.compute_statistics()?;
228//! println!("Valid pixels: {}", stats.valid_count);
229//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
230//! ```
231//!
232//! ## SIMD-Aligned Buffers
233//!
234//! For high-performance operations, use SIMD-aligned buffers:
235//!
236//! ```rust
237//! use oxigdal_core::simd_buffer::AlignedBuffer;
238//!
239//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
240//! // Create 64-byte aligned buffer (AVX-512)
241//! let mut buffer = AlignedBuffer::<f32>::new(1024 * 1024, 64)?;
242//!
243//! // Fill with data
244//! for (i, val) in buffer.as_mut_slice().iter_mut().enumerate() {
245//!     *val = i as f32;
246//! }
247//!
248//! // Access as slice
249//! let slice = buffer.as_slice();
250//! assert_eq!(slice.len(), 1024 * 1024);
251//! # Ok(())
252//! # }
253//! ```
254//!
255//! # Error Handling
256//!
257//! All `OxiGDAL` functions return `Result<T, OxiGdalError>`. The error types are:
258//!
259//! ```rust
260//! use oxigdal_core::error::{OxiGdalError, IoError, FormatError};
261//!
262//! fn example() -> oxigdal_core::Result<()> {
263//!     // Use ? operator for error propagation
264//!     let bbox = oxigdal_core::types::BoundingBox::new(0.0, 0.0, 10.0, 10.0)?;
265//!
266//!     // Pattern match on errors
267//!     match do_something() {
268//!         Ok(result) => println!("Success: {:?}", result),
269//!         Err(OxiGdalError::Io(io_err)) => {
270//!             eprintln!("I/O error: {}", io_err);
271//!         }
272//!         Err(OxiGdalError::InvalidParameter { parameter, message }) => {
273//!             eprintln!("Invalid {}: {}", parameter, message);
274//!         }
275//!         Err(e) => {
276//!             eprintln!("Other error: {}", e);
277//!         }
278//!     }
279//!
280//!     Ok(())
281//! }
282//!
283//! fn do_something() -> oxigdal_core::Result<()> {
284//!     Ok(())
285//! }
286//! ```
287//!
288//! # Performance Tips
289//!
290//! ## 1. Use Appropriate Data Types
291//!
292//! Choose the smallest data type that meets your needs:
293//!
294//! - `UInt8`: 0-255 range (e.g., RGB images)
295//! - `Int16`: -32768 to 32767 (e.g., elevation data)
296//! - `Float32`: General-purpose floating point (good balance of precision and size)
297//! - `Float64`: High precision (use when necessary)
298//!
299//! ## 2. Leverage SIMD Buffers
300//!
301//! For performance-critical code, use SIMD-aligned buffers:
302//!
303//! ```rust
304//! use oxigdal_core::simd_buffer::AlignedBuffer;
305//!
306//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
307//! // 64-byte alignment for AVX-512
308//! let buffer = AlignedBuffer::<f32>::new(1_000_000, 64)?;
309//! # Ok(())
310//! # }
311//! ```
312//!
313//! ## 3. Minimize Type Conversions
314//!
315//! Type conversions are expensive. Work in the native data type when possible.
316//!
317//! ## 4. Batch Operations
318//!
319//! Process multiple pixels at once rather than one at a time:
320//!
321//! ```rust
322//! use oxigdal_core::buffer::RasterBuffer;
323//! use oxigdal_core::types::RasterDataType;
324//!
325//! let mut buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
326//!
327//! // Slow: pixel-by-pixel
328//! for y in 0..1000 {
329//!     for x in 0..1000 {
330//!         buffer.set_pixel(x, y, 100.0).ok();
331//!     }
332//! }
333//!
334//! // Fast: use fill_value
335//! buffer.fill_value(100.0);
336//! ```
337//!
338//! ## 5. Consider Memory Layout
339//!
340//! `OxiGDAL` uses row-major order (scanline-based). Access pixels in row order for better cache performance:
341//!
342//! ```rust
343//! # use oxigdal_core::buffer::RasterBuffer;
344//! # use oxigdal_core::types::RasterDataType;
345//! # let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
346//! // Good: row-major order (cache-friendly)
347//! for y in 0..buffer.height() {
348//!     for x in 0..buffer.width() {
349//!         // process pixel (x, y)
350//!     }
351//! }
352//!
353//! // Bad: column-major order (cache-unfriendly)
354//! for x in 0..buffer.width() {
355//!     for y in 0..buffer.height() {
356//!         // process pixel (x, y)
357//!     }
358//! }
359//! ```
360//!
361//! ## 6. Avoid Repeated Statistics Computation
362//!
363//! Statistics computation is O(n). Cache results when possible:
364//!
365//! ```rust
366//! # use oxigdal_core::buffer::RasterBuffer;
367//! # use oxigdal_core::types::RasterDataType;
368//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
369//! # let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
370//! // Compute once
371//! let stats = buffer.compute_statistics()?;
372//!
373//! // Use multiple times
374//! println!("Min: {}, Max: {}", stats.min, stats.max);
375//! println!("Mean: {}", stats.mean);
376//! # Ok(())
377//! # }
378//! ```