Skip to main content

oxigdal_proj/
lib.rs

1//! Pure Rust coordinate transformation and projection support for OxiGDAL.
2//!
3//! This crate provides comprehensive coordinate reference system (CRS) and projection
4//! capabilities for the OxiGDAL library. It includes support for:
5//!
6//! - EPSG code database with common coordinate reference systems
7//! - WKT (Well-Known Text) parsing
8//! - PROJ string support
9//! - Coordinate transformations between different CRS
10//! - Pure Rust implementation by default using proj4rs
11//! - Optional C bindings to PROJ library (feature-gated)
12//!
13//! # Features
14//!
15//! - `std` (default): Enable standard library support
16//! - `proj-sys`: Enable optional C bindings to PROJ library for full PROJ support
17//!
18//! # Examples
19//!
20//! ## Transform coordinates from WGS84 to Web Mercator
21//!
22//! ```
23//! use oxigdal_proj::{Crs, Coordinate, Transformer};
24//!
25//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
26//! // Create CRS from EPSG codes
27//! let wgs84 = Crs::from_epsg(4326)?;
28//! let web_mercator = Crs::from_epsg(3857)?;
29//!
30//! // Create transformer
31//! let transformer = Transformer::new(wgs84, web_mercator)?;
32//!
33//! // Transform a coordinate (London: 0°, 51.5°N)
34//! let london = Coordinate::from_lon_lat(0.0, 51.5);
35//! let transformed = transformer.transform(&london)?;
36//!
37//! println!("Transformed: {}", transformed);
38//! # Ok(())
39//! # }
40//! ```
41//!
42//! ## Use convenience functions
43//!
44//! ```
45//! use oxigdal_proj::{Coordinate, transform_epsg};
46//!
47//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
48//! let coord = Coordinate::from_lon_lat(-122.4194, 37.7749); // San Francisco
49//! let transformed = transform_epsg(&coord, 4326, 3857)?;
50//! println!("Transformed: {}", transformed);
51//! # Ok(())
52//! # }
53//! ```
54//!
55//! ## Work with bounding boxes
56//!
57//! ```
58//! use oxigdal_proj::{BoundingBox, Coordinate, Transformer, Crs};
59//!
60//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
61//! let bbox = BoundingBox::new(-10.0, -10.0, 10.0, 10.0)?;
62//!
63//! let transformer = Transformer::from_epsg(4326, 3857)?;
64//! let transformed_bbox = transformer.transform_bbox(&bbox)?;
65//!
66//! println!("Original: {:?}", bbox);
67//! println!("Transformed: {:?}", transformed_bbox);
68//! # Ok(())
69//! # }
70//! ```
71//!
72//! ## Use common CRS constants
73//!
74//! ```
75//! use oxigdal_proj::Crs;
76//!
77//! let wgs84 = Crs::wgs84();
78//! let web_mercator = Crs::web_mercator();
79//! let nad83 = Crs::nad83();
80//! let etrs89 = Crs::etrs89();
81//! ```
82//!
83//! # EPSG Database
84//!
85//! The crate includes an embedded database of ~140 common EPSG codes, including:
86//!
87//! - WGS84 (EPSG:4326)
88//! - Web Mercator (EPSG:3857)
89//! - All WGS84 UTM zones (EPSG:32601-32660 North, 32701-32760 South)
90//! - Common national datums (NAD83, ETRS89, GDA94, JGD2000, etc.)
91//! - Common projected systems (British National Grid, US National Atlas, etc.)
92//!
93//! # Pure Rust Implementation
94//!
95//! By default, this crate uses the pure Rust `proj4rs` library for coordinate transformations.
96//! This ensures:
97//!
98//! - No C/C++ dependencies
99//! - Cross-platform compatibility
100//! - Memory safety guarantees
101//! - Easy integration with Rust projects
102//!
103//! For applications requiring full PROJ library compatibility, enable the `proj-sys` feature:
104//!
105//! ```toml
106//! [dependencies]
107//! oxigdal-proj = { version = "0.1", features = ["proj-sys"] }
108//! ```
109//!
110//! # Accuracy and Limitations
111//!
112//! The pure Rust implementation using proj4rs provides accurate transformations for most
113//! common use cases. However, it may have limitations compared to the full PROJ library:
114//!
115//! - Limited support for some exotic projections
116//! - No dynamic datum grid shift support
117//! - Simplified datum transformations
118//!
119//! For high-accuracy geodetic applications, consider using the `proj-sys` feature.
120
121#![cfg_attr(not(feature = "std"), no_std)]
122#![warn(missing_docs)]
123#![deny(unsafe_code)]
124
125// When no_std is active, bring in alloc for heap allocation (Vec, String, etc.)
126#[cfg(not(feature = "std"))]
127extern crate alloc;
128#[cfg(not(feature = "std"))]
129use alloc::format;
130#[cfg(not(feature = "std"))]
131use alloc::string::String;
132
133pub mod crs;
134#[cfg(feature = "std")]
135pub mod crs_registry;
136pub mod datum_transform;
137pub mod epsg;
138pub mod error;
139#[cfg(feature = "std")]
140pub mod grid_shift;
141#[cfg(feature = "std")]
142pub mod proj_string;
143#[cfg(feature = "std")]
144pub mod projections;
145pub mod transform;
146pub mod wkt;
147
148// Re-export commonly used types
149pub use crs::{Crs, CrsSource};
150pub use epsg::{CrsType, EpsgDefinition};
151#[cfg(feature = "std")]
152pub use epsg::{available_epsg_codes, contains_epsg, lookup_epsg};
153pub use error::{Error, Result};
154#[cfg(feature = "std")]
155pub use grid_shift::{
156    DHDN_TO_ETRS89, Helmert7Params, NAD27_TO_NAD83, NTF_TO_RGF93, OSGB36_TO_ETRS89,
157    dhdn_etrs89_helmert, helmert_3d, helmert_7param, nad27_nad83_poly, ostn15_approx, rgf93_approx,
158};
159#[cfg(feature = "std")]
160pub use transform::{
161    AzimuthalEquidistant, CassineSoldner, EckertIV, EckertVI, EquidistantConic, GaussKruger,
162    Gnomonic, LambertAzimuthalEqualArea, LambertConformalConic, Mollweide, Robinson, Sinusoidal,
163    Transformer, TransverseMercator, transform_coordinate, transform_epsg,
164};
165pub use transform::{BoundingBox, Coordinate, Coordinate3D};
166pub use wkt::{WktNode, WktParser, parse_wkt};
167
168/// Library version
169pub const VERSION: &str = env!("CARGO_PKG_VERSION");
170
171/// Library name
172pub const NAME: &str = env!("CARGO_PKG_NAME");
173
174/// Returns library information.
175pub fn info() -> String {
176    format!("{} v{}", NAME, VERSION)
177}
178
179#[cfg(test)]
180#[allow(clippy::expect_used)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_version_info() {
186        let info = info();
187        assert!(info.contains("oxigdal-proj"));
188        assert!(info.contains(env!("CARGO_PKG_VERSION")));
189    }
190
191    #[test]
192    fn test_basic_workflow() {
193        // Create CRS
194        let wgs84 = Crs::wgs84();
195        let web_mercator = Crs::web_mercator();
196
197        // Create transformer
198        let transformer = Transformer::new(wgs84, web_mercator);
199        assert!(transformer.is_ok());
200
201        // Transform coordinate
202        let coord = Coordinate::from_lon_lat(0.0, 0.0);
203        let result = transformer.expect("should create").transform(&coord);
204        assert!(result.is_ok());
205    }
206
207    #[test]
208    fn test_epsg_lookup() {
209        let wgs84 = lookup_epsg(4326);
210        assert!(wgs84.is_ok());
211
212        assert!(contains_epsg(4326));
213        assert!(contains_epsg(3857));
214        assert!(!contains_epsg(99999));
215
216        let codes = available_epsg_codes();
217        assert!(!codes.is_empty());
218    }
219
220    #[test]
221    fn test_convenience_functions() {
222        let coord = Coordinate::from_lon_lat(0.0, 0.0);
223        let result = transform_epsg(&coord, 4326, 4326);
224        assert!(result.is_ok());
225    }
226
227    #[test]
228    fn test_bounding_box_workflow() {
229        let bbox = BoundingBox::new(0.0, 0.0, 10.0, 10.0);
230        assert!(bbox.is_ok());
231
232        let bbox = bbox.expect("valid bbox");
233        let transformer = Transformer::from_epsg(4326, 4326);
234        assert!(transformer.is_ok());
235
236        let result = transformer.expect("should create").transform_bbox(&bbox);
237        assert!(result.is_ok());
238    }
239
240    #[test]
241    fn test_wkt_parsing() {
242        let wkt = r#"GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]]]"#;
243        let result = parse_wkt(wkt);
244        assert!(result.is_ok());
245
246        let node = result.expect("should parse");
247        assert_eq!(node.node_type, "GEOGCS");
248    }
249
250    #[test]
251    fn test_crs_creation_methods() {
252        // From EPSG
253        let crs1 = Crs::from_epsg(4326);
254        assert!(crs1.is_ok());
255
256        // From PROJ string
257        let crs2 = Crs::from_proj("+proj=longlat +datum=WGS84 +no_defs");
258        assert!(crs2.is_ok());
259
260        // From WKT
261        let wkt = r#"GEOGCS["WGS 84"]"#;
262        let crs3 = Crs::from_wkt(wkt);
263        assert!(crs3.is_ok());
264
265        // Custom CRS
266        let crs4 = Crs::custom("My CRS", "+proj=longlat +datum=WGS84 +no_defs");
267        assert!(matches!(crs4.source(), CrsSource::Custom { .. }));
268    }
269
270    #[test]
271    fn test_coordinate_types() {
272        // 2D coordinate
273        let coord_2d = Coordinate::new(10.0, 20.0);
274        assert_eq!(coord_2d.x, 10.0);
275        assert_eq!(coord_2d.y, 20.0);
276
277        // 3D coordinate
278        let coord_3d = Coordinate3D::new(10.0, 20.0, 30.0);
279        assert_eq!(coord_3d.x, 10.0);
280        assert_eq!(coord_3d.y, 20.0);
281        assert_eq!(coord_3d.z, 30.0);
282
283        // Conversion
284        let coord_2d_from_3d = coord_3d.to_2d();
285        assert_eq!(coord_2d_from_3d.x, 10.0);
286        assert_eq!(coord_2d_from_3d.y, 20.0);
287    }
288}