Skip to main content

oxigdal_stac/
lib.rs

1//! STAC (SpatioTemporal Asset Catalog) support for OxiGDAL.
2//!
3//! This crate provides Pure Rust implementation of the STAC specification,
4//! enabling cloud-native geospatial workflows with catalogs, collections,
5//! and items.
6//!
7//! # Features
8//!
9//! - **STAC 1.0.0 Specification**: Full compliance with STAC 1.0.0
10//! - **Core Models**: Catalog, Collection, Item, and Asset
11//! - **Extensions**: EO (Electro-Optical) and Projection extensions
12//! - **STAC API Client**: Async HTTP client for searching STAC APIs
13//! - **Builder Patterns**: Fluent APIs for easy object creation
14//! - **Validation**: Comprehensive validation of STAC objects
15//! - **Pure Rust**: No C/Fortran dependencies
16//!
17//! # Example
18//!
19//! ```rust
20//! use oxigdal_stac::{ItemBuilder, Asset};
21//! use chrono::Utc;
22//!
23//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
24//! // Create a STAC Item
25//! let item = ItemBuilder::new("my-item")
26//!     .datetime(Utc::now())
27//!     .bbox(-122.5, 37.5, -122.0, 38.0)
28//!     .simple_asset("visual", "https://example.com/image.tif")
29//!     .build()?;
30//!
31//! // Serialize to JSON
32//! let json = serde_json::to_string_pretty(&item)?;
33//! println!("{}", json);
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! # STAC API Search
39//!
40//! ```rust,no_run
41//! # #[cfg(all(feature = "reqwest", feature = "async"))]
42//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
43//! use oxigdal_stac::StacClient;
44//!
45//! // Create a STAC API client
46//! let client = StacClient::new("https://earth-search.aws.element84.com/v1")?;
47//!
48//! // Search for items
49//! let results = client.search()
50//!     .collections(vec!["sentinel-2-l2a"])
51//!     .bbox([-122.5, 37.5, -122.0, 38.0])
52//!     .limit(10)
53//!     .execute()
54//!     .await?;
55//!
56//! println!("Found {} items", results.features.len());
57//! # Ok(())
58//! # }
59//! ```
60
61#![deny(missing_docs)]
62#![cfg_attr(not(feature = "std"), no_std)]
63// Allow collapsible matches for clear STAC field handling
64#![allow(clippy::collapsible_match)]
65#![allow(clippy::collapsible_if)]
66// Allow dead code for future STAC extensions
67#![allow(dead_code)]
68// Allow stripping prefix manually for URL handling
69#![allow(clippy::manual_strip)]
70// Allow method name conflicts for builder patterns
71#![allow(clippy::should_implement_trait)]
72
73// Re-export common types
74pub use chrono;
75pub use geojson;
76pub use serde_json;
77
78// Core modules
79pub mod aggregation;
80pub mod api;
81pub mod asset;
82pub mod builder;
83pub mod catalog;
84pub mod client;
85pub mod collection;
86pub mod collection_aggregation;
87pub mod cql2;
88pub mod error;
89pub mod extensions;
90pub mod item;
91pub mod transaction;
92
93#[cfg(feature = "reqwest")]
94pub mod pagination;
95
96#[cfg(feature = "reqwest")]
97pub mod search;
98
99// Public exports
100pub use aggregation::{
101    Aggregation, AggregationRequest, AggregationResponse, AggregationResult, Bucket,
102};
103pub use api::{
104    CollectionSummary, CollectionsList, ConformanceDeclaration, FieldsSpec, ItemCollection,
105    LandingPage, SearchContext as ApiSearchContext, SearchRequest as ApiSearchRequest,
106    SortDirection as ApiSortDirection, SortField,
107};
108pub use asset::{Asset, media_types, roles};
109pub use builder::{CatalogBuilder, CollectionBuilder, ItemBuilder};
110pub use catalog::Catalog;
111pub use collection::{Collection, Extent, Provider, SpatialExtent, TemporalExtent};
112pub use collection_aggregation::{CollectionAggregator, CollectionStats, NumericStats};
113pub use cql2::{Cql2Filter, Cql2Operand};
114pub use error::{Result, StacError};
115pub use extensions::{
116    eo::{Band, CommonBandName, EoExtension},
117    projection::{ProjectionExtension, epsg_codes},
118    sar::{FrequencyBand, ObservationDirection, Polarization, SarExtension},
119    scientific::{Publication, ScientificExtension},
120    timestamps::TimestampsExtension,
121    version::VersionExtension,
122    view::ViewExtension,
123};
124pub use item::{Item, ItemProperties, Link, link_rel};
125pub use transaction::{StacItemStore, TransactionOp, TransactionResult};
126
127#[cfg(feature = "reqwest")]
128pub use pagination::{CursorPagination, PagePagination, Paginator, TokenPagination};
129
130#[cfg(feature = "reqwest")]
131pub use search::{SearchContext, SearchParams, SearchResults, SortBy, SortDirection, StacClient};
132
133/// STAC version supported by this crate.
134pub const STAC_VERSION: &str = "1.0.0";
135
136/// Helper function to create a bounding box from coordinates.
137///
138/// # Arguments
139///
140/// * `west` - Western longitude
141/// * `south` - Southern latitude
142/// * `east` - Eastern longitude
143/// * `north` - Northern latitude
144///
145/// # Returns
146///
147/// Bounding box vector [west, south, east, north]
148///
149/// # Example
150///
151/// ```
152/// use oxigdal_stac::bbox;
153///
154/// let bbox = bbox(-122.5, 37.5, -122.0, 38.0);
155/// assert_eq!(bbox, vec![-122.5, 37.5, -122.0, 38.0]);
156/// ```
157pub fn bbox(west: f64, south: f64, east: f64, north: f64) -> Vec<f64> {
158    vec![west, south, east, north]
159}
160
161/// Helper function to create a GeoJSON Point geometry.
162///
163/// # Arguments
164///
165/// * `lon` - Longitude
166/// * `lat` - Latitude
167///
168/// # Returns
169///
170/// GeoJSON Point geometry
171///
172/// # Example
173///
174/// ```
175/// use oxigdal_stac::point_geometry;
176///
177/// let geometry = point_geometry(-122.0, 37.0);
178/// assert_eq!(geometry.value, geojson::GeometryValue::new_point([-122.0, 37.0]));
179/// ```
180pub fn point_geometry(lon: f64, lat: f64) -> geojson::Geometry {
181    geojson::Geometry::new_point([lon, lat])
182}
183
184/// Helper function to create a GeoJSON Polygon geometry from a bounding box.
185///
186/// # Arguments
187///
188/// * `west` - Western longitude
189/// * `south` - Southern latitude
190/// * `east` - Eastern longitude
191/// * `north` - Northern latitude
192///
193/// # Returns
194///
195/// GeoJSON Polygon geometry
196///
197/// # Example
198///
199/// ```
200/// use oxigdal_stac::bbox_to_polygon;
201///
202/// let geometry = bbox_to_polygon(-122.5, 37.5, -122.0, 38.0);
203/// match geometry.value {
204///     geojson::GeometryValue::Polygon { coordinates: _ } => (),
205///     _ => panic!("Expected Polygon"),
206/// }
207/// ```
208pub fn bbox_to_polygon(west: f64, south: f64, east: f64, north: f64) -> geojson::Geometry {
209    let polygon = vec![vec![
210        vec![west, south],
211        vec![east, south],
212        vec![east, north],
213        vec![west, north],
214        vec![west, south], // Close the ring
215    ]];
216    geojson::Geometry::new_polygon(polygon)
217}
218
219#[cfg(test)]
220#[allow(clippy::panic)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn test_bbox_helper() {
226        let bbox = bbox(-122.5, 37.5, -122.0, 38.0);
227        assert_eq!(bbox, vec![-122.5, 37.5, -122.0, 38.0]);
228    }
229
230    #[test]
231    fn test_point_geometry() {
232        let geometry = point_geometry(-122.0, 37.0);
233        assert_eq!(
234            geometry.value,
235            geojson::GeometryValue::new_point([-122.0, 37.0])
236        );
237    }
238
239    #[test]
240    fn test_bbox_to_polygon() {
241        let geometry = bbox_to_polygon(-122.5, 37.5, -122.0, 38.0);
242        match geometry.value {
243            geojson::GeometryValue::Polygon {
244                coordinates: coords,
245            } => {
246                assert_eq!(coords.len(), 1);
247                assert_eq!(coords[0].len(), 5); // 4 corners + close
248            }
249            _ => panic!("Expected Polygon"),
250        }
251    }
252
253    #[test]
254    fn test_stac_version() {
255        assert_eq!(STAC_VERSION, "1.0.0");
256    }
257}