terrio/lib.rs
1//! Pure-Rust geospatial raster, vector and point cloud data I/O.
2//!
3//! terrio opens rasters, vector datasets and point clouds in many formats through one API,
4//! converts between them, reprojects, and runs GDAL-style operations, without C dependencies: it
5//! builds anywhere Rust does, WebAssembly included. Format codecs come from the `wbraster`,
6//! `wbvector`, `wbprojection` and `laz` crates; terrio adds format detection, remote and archived
7//! inputs, its own drivers where those libraries are wrong or missing, and the operations. Results
8//! are compared with GDAL's on real-world files.
9//!
10//! # A tour
11//!
12//! ## Opening, summarizing and writing
13//!
14//! [`open`] detects the format and returns a [`Dataset`], a raster or a vector dataset read into
15//! memory; [`open_raster`] and [`open_vector`] return one kind. Paths can be local files, URLs,
16//! `s3://`, `gs://` and `az://` objects or zip archive members ([`vfs`]). [`info`] summarizes a
17//! dataset as `gdalinfo` and `ogrinfo` do.
18//!
19//! ```no_run
20//! let ds = terrio::open("countries.gpkg")?;
21//! println!("{}", terrio::info::info(&ds, &Default::default()).to_text());
22//!
23//! let dem = terrio::open_raster("dem.tif")?;
24//! let options = terrio::RasterWriteOptions {
25//! driver: Some(terrio::formats::RasterDriver::Cog),
26//! compression: Some("DEFLATE".into()),
27//! ..Default::default()
28//! };
29//! terrio::write_raster_dataset(&dem, "dem_cog.tif", &options)?;
30//!
31//! let countries = terrio::open_vector("countries.gpkg", &Default::default())?;
32//! terrio::write_vector(&countries.layers, "countries.fgb", &Default::default())?;
33//! # Ok::<(), terrio::Error>(())
34//! ```
35//!
36//! ## Reading windows lazily
37//!
38//! [`open_raster_lazy`] opens a GeoTIFF or COG, local or remote, by its headers; a
39//! [`LazyRaster::read_window`] decodes only the tiles it needs, fetched with HTTP range requests.
40//!
41//! ```no_run
42//! let mut cog = terrio::open_raster_lazy("https://example.com/cog.tif")?;
43//! let options = terrio::WindowOptions { bands: vec![1], overview: Some(0) };
44//! let tile = cog.read_window(terrio::Window::new(0, 0, 512, 512), &options)?;
45//! # Ok::<(), terrio::Error>(())
46//! ```
47//!
48//! ## Operations
49//!
50//! [`ops`] holds the operations behind the command-line tool, each with an options struct named
51//! after the GDAL utility's options: [`ops::warp`], [`ops::translate`], [`ops::mosaic`],
52//! [`ops::rasterize`], [`ops::polygonize`], [`ops::dem`], [`ops::calc`], [`ops::proximity`],
53//! [`ops::sieve`], [`ops::geometry`] (centroids, singleparts and vertices), [`ops::footprint`],
54//! [`ops::sample`], [`ops::contour`], [`ops::zonal`] and the vector filters of `ogr2ogr`
55//! ([`ops::vector_filter`], [`ops::sql::query`]).
56//!
57//! ```no_run
58//! use terrio::crs::SpatialRef;
59//! use terrio::ops::{Resampling, dem, warp};
60//!
61//! let dem = terrio::open_raster("dem.tif")?;
62//! let options = warp::WarpOptions {
63//! dst_srs: Some(SpatialRef::parse("EPSG:32613")?),
64//! resolution: Some((30.0, 30.0)),
65//! resampling: Resampling::Bilinear,
66//! ..Default::default()
67//! };
68//! let utm = warp::warp(&[&dem], &options)?;
69//! let shade = dem::hillshade(&utm, &Default::default())?;
70//! terrio::write_raster_dataset(&shade, "hillshade.tif", &Default::default())?;
71//! # Ok::<(), terrio::Error>(())
72//! ```
73//!
74//! ## Coordinate reference systems
75//!
76//! [`crs::SpatialRef`] parses EPSG codes, WKT and PROJ strings; [`crs::Transformer`] transforms
77//! coordinates as GDAL does, picking the datum transformation GDAL picks.
78//!
79//! ```no_run
80//! use terrio::crs::{SpatialRef, Transformer};
81//!
82//! let wgs84 = SpatialRef::parse("EPSG:4326")?;
83//! let utm = SpatialRef::parse("EPSG:32613")?;
84//! let (x, y) = Transformer::new(&wgs84, &utm)?.transform(-105.5, 39.5)?;
85//! # Ok::<(), terrio::Error>(())
86//! ```
87//!
88//! ## Bytes in memory
89//!
90//! [`memory`] opens datasets from bytes and writes them to bytes, for data already in memory and
91//! for WebAssembly, which has no file system.
92//!
93//! ```no_run
94//! let bytes = std::fs::read("dem.tif")?;
95//! let dem = terrio::memory::open_raster_bytes(bytes, "dem.tif")?;
96//! let asc = terrio::memory::write_raster_bytes(&dem, "dem.asc", &Default::default())?;
97//! # Ok::<(), terrio::Error>(())
98//! ```
99//!
100//! ## Point clouds
101//!
102//! [`pointcloud`] reads LAS, LAZ and COPC into columns (COPC in place, by bounds and octree
103//! level), writes them, and grids them into rasters.
104//!
105//! ```no_run
106//! use terrio::pointcloud::{self, PointFilter, PointReadOptions, grid};
107//!
108//! let filter = PointFilter { classes: vec![2], ..Default::default() };
109//! let ground = pointcloud::read_points("tile.laz", &PointReadOptions { filter, ..Default::default() })?;
110//! let dtm = grid::grid(&ground, &grid::GridOptions { resolution: 1.0, ..Default::default() })?;
111//! terrio::write_raster_dataset(&dtm, "dtm.tif", &Default::default())?;
112//! # Ok::<(), terrio::Error>(())
113//! ```
114//!
115//! # The data model
116//!
117//! Rasters are [`wbraster::Raster`]s (re-exported as [`Raster`]): band-major typed pixels with
118//! georeferencing and a CRS, wrapped in a [`RasterDataset`] that records the driver and whether
119//! the nodata value is declared. Vector layers are [`wbvector::Layer`]s (re-exported as
120//! [`Layer`]), with [`Feature`]s, a field schema and [`Geometry`]s. These types belong to
121//! terrio's public API: use them through terrio's re-exports (here, [`raster`] and [`vector`])
122//! rather than depending on `wbraster` or `wbvector` directly, so that the versions always match.
123//! A breaking release of either crate that terrio adopts is a breaking release of terrio.
124//!
125//! # Features
126//!
127//! Every driver is on by default. With `default-features = false`, terrio keeps its core: GeoTIFF
128//! and COG, the raw and ASCII rasters, PNG, JPEG, VRT, STAC, GeoJSON, TopoJSON, Shapefile,
129//! KML/KMZ, GML, GPX, CSV and MapInfo, the CRS engine, the operations and the streams. The other
130//! drivers are features:
131//!
132//! - `geotiff`: GeoTIFF and COG, always built (the feature names what a build needs:
133//! `features = ["geotiff"]` is a COG reader and writer);
134//! - `parquet`: GeoParquet, the [`arrow`] module and Arrow streams;
135//! - `remote`: URLs, `s3://`, `gs://` and `az://` objects, read whole or by range (the [`http`]
136//! and [`s3`] modules), and datum grids from the PROJ CDN; native builds only;
137//! - `netcdf-hdf5`: NetCDF, HDF5 and HDF-EOS;
138//! - `sqlite`: GeoPackage and SQLite/SpatiaLite;
139//! - `tiles`: MBTiles, PMTiles and MVT ([`tiles`]); `avif`: their AVIF tiles;
140//! - `zarr`, `jp2` (JPEG 2000), `flatgeobuf`, `osm` (OpenStreetMap);
141//! - `pointcloud`: LAS, LAZ, COPC, EPT, PLY and E57 ([`pointcloud`]);
142//! - `lerc` and `lzma`: GeoTIFFs with those compressions.
143//!
144//! The drivers a build leaves out stay in [`formats::RasterDriver`] and
145//! [`formats::VectorDriver`], listed as neither read nor written: opening or writing their files
146//! fails with [`Error::Unsupported`] ("built without the `zarr` feature").
147//!
148//! - `gdal` (off by default): the optional GDAL backend (`gdal_backend`), which opens the formats
149//! terrio has no driver for through GDAL, and writes rasters with GDAL's drivers. It is the only
150//! C dependency, opt-in: GDAL must be installed.
151//!
152//! # Minimum supported Rust version
153//!
154//! Rust 1.90 (the `rust-version` in `Cargo.toml`, checked in CI). The minimum is raised only when
155//! a dependency or a language feature needs it, never to a release less than six months old, and
156//! only in a minor release (before 1.0, in any release, noted in the changelog).
157
158#![warn(missing_docs)]
159#![cfg_attr(docsrs, feature(doc_cfg))]
160// Builds without some drivers leave helpers that only those drivers use.
161#![cfg_attr(
162 not(all(
163 feature = "parquet",
164 feature = "remote",
165 feature = "avif",
166 feature = "netcdf-hdf5",
167 feature = "osm",
168 feature = "lzma",
169 feature = "flatgeobuf",
170 feature = "jp2",
171 feature = "lerc",
172 feature = "pointcloud",
173 feature = "tiles",
174 feature = "zarr",
175 feature = "sqlite"
176 )),
177 allow(dead_code)
178)]
179
180#[cfg(feature = "parquet")]
181#[cfg_attr(docsrs, doc(cfg(feature = "parquet")))]
182pub mod arrow;
183#[cfg(feature = "avif")]
184mod avif;
185mod cog;
186pub mod crs;
187mod dataset;
188mod drivers;
189mod error;
190pub mod formats;
191#[cfg(feature = "gdal")]
192#[cfg_attr(docsrs, doc(cfg(feature = "gdal")))]
193pub mod gdal_backend;
194mod geotiff;
195mod histogram;
196#[cfg(remote)]
197#[cfg_attr(docsrs, doc(cfg(feature = "remote")))]
198pub mod http;
199pub mod info;
200mod jpeg;
201mod memfs;
202pub mod memory;
203pub mod ops;
204mod par;
205mod pixel;
206#[cfg(feature = "pointcloud")]
207#[cfg_attr(docsrs, doc(cfg(feature = "pointcloud")))]
208pub mod pointcloud;
209#[cfg(remote)]
210#[cfg_attr(docsrs, doc(cfg(feature = "remote")))]
211pub mod s3;
212pub mod sparse;
213mod tiff_codecs;
214pub mod tiles;
215pub mod vector;
216
217/// Raster types from `wbraster`, terrio's raster data model: pixel storage, CRS records and
218/// reprojection options.
219pub mod raster {
220 pub use wbraster::raster::{BandView, RasterData};
221 pub use wbraster::{CrsInfo, RasterError, ReprojectOptions, ResampleMethod};
222}
223pub mod stac;
224mod stacta;
225pub mod stream;
226mod tiff_mask;
227pub mod vfs;
228pub mod warnings;
229
230pub use cog::{LazyRaster, Window, WindowOptions, open_raster_lazy};
231pub use dataset::{
232 BandMetadata, Dataset, Gcp, Geolocation, RasterDataset, RasterWriteOptions, VectorDataset, VectorOpenOptions, VectorWriteOptions,
233 geo_transform, is_north_up, list_layers, open, open_raster, open_vector, read_gcps, write_netcdf_groups, write_raster,
234 write_raster_dataset, write_vector,
235};
236pub use drivers::gtiff_write::GeoTiffWriter;
237pub use drivers::vector::csv::{CsvGeometry, CsvOptions};
238pub use drivers::vector::geojson::GeoJsonOptions;
239pub use error::{Error, Result};
240
241/// The raster type: pixels, georeferencing and CRS, in memory.
242pub use wbraster::{DataType, Raster, RasterConfig};
243/// The vector types: layers of features with geometries and attributes.
244pub use wbvector::{Feature, FieldDef, FieldType, FieldValue, Geometry, GeometryType, Layer};
245
246/// The system allocator, ignoring the release of null pointers.
247///
248/// OpenJPEG's Rust port, terrio's JPEG 2000 codec (the `jp2` feature), releases code-block
249/// buffers it never allocated: null pointers. The C allocators of native builds ignore those; the
250/// allocator of `wasm32-unknown-unknown` does not, and traps. Programs built for WebAssembly that
251/// read or write JPEG 2000 make this their global allocator (terrio's npm package does); builds
252/// without `jp2` need neither it nor the link flag below:
253///
254/// ```
255/// #[global_allocator]
256/// static ALLOCATOR: terrio::NullSafeAllocator = terrio::NullSafeAllocator;
257/// ```
258///
259/// Their link also needs `-C link-arg=--allow-undefined` (see `.cargo/config.toml` in terrio's
260/// repository): the port is built as a C library too, which leaves the C allocator undefined.
261pub struct NullSafeAllocator;
262
263// SAFETY: every call goes to the system allocator, except releases of null pointers, which
264// allocate nothing.
265unsafe impl std::alloc::GlobalAlloc for NullSafeAllocator {
266 unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 {
267 // SAFETY: as the caller guarantees.
268 unsafe { std::alloc::System.alloc(layout) }
269 }
270
271 unsafe fn alloc_zeroed(&self, layout: std::alloc::Layout) -> *mut u8 {
272 // SAFETY: as the caller guarantees.
273 unsafe { std::alloc::System.alloc_zeroed(layout) }
274 }
275
276 unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) {
277 if !ptr.is_null() {
278 // SAFETY: a pointer this allocator returned, as the caller guarantees.
279 unsafe { std::alloc::System.dealloc(ptr, layout) }
280 }
281 }
282
283 unsafe fn realloc(&self, ptr: *mut u8, layout: std::alloc::Layout, new_size: usize) -> *mut u8 {
284 // SAFETY: as the caller guarantees.
285 unsafe { std::alloc::System.realloc(ptr, layout, new_size) }
286 }
287}
288
289/// The version of this crate.
290pub const VERSION: &str = env!("CARGO_PKG_VERSION");
291
292/// The thread pool of WebAssembly builds with threads: see [`par::set_thread_pool`].
293#[cfg(all(threads, target_family = "wasm"))]
294pub use par::set_thread_pool;