Skip to main content

wbprojection/
lib.rs

1//! # wbprojection
2//!
3//! A map projection library for Rust, inspired by the PROJ library.
4//!
5//! ## Overview
6//!
7//! `wbprojection` provides forward and inverse transformations between geographic
8//! coordinates (longitude/latitude) and projected coordinates (easting/northing)
9//! for a wide range of map projections and datums.
10//!
11//! ## Quick Start
12//!
13//! ```rust
14//! use wbprojection::{Projection, ProjectionParams, Ellipsoid};
15//!
16//! // Create a UTM Zone 32N projection
17//! let params = ProjectionParams::utm(32, false);
18//! let proj = Projection::new(params).unwrap();
19//!
20//! // Forward: lon/lat (degrees) → easting/northing (meters)
21//! let (easting, northing) = proj.forward(9.0, 48.0).unwrap();
22//! println!("Easting: {:.2}, Northing: {:.2}", easting, northing);
23//!
24//! // Inverse: easting/northing → lon/lat
25//! let (lon, lat) = proj.inverse(easting, northing).unwrap();
26//! println!("Lon: {:.6}, Lat: {:.6}", lon, lat);
27//! ```
28//!
29//! ## Supported Projections
30//!
31//! - **Mercator** – Standard and Web Mercator (EPSG:3857)
32//! - **Transverse Mercator** – Foundation for UTM
33//! - **UTM** – Universal Transverse Mercator (all zones)
34//! - **Lambert Conformal Conic** – One and two standard parallels
35//! - **Albers Equal-Area Conic** – Equal-area conic
36//! - **Azimuthal Equidistant** – Distances true from center point
37//! - **Lambert Azimuthal Equal-Area** – Equal-area azimuthal
38//! - **Krovak** – Oblique conformal conic (Czech/Slovak systems)
39//! - **Central Conic** – Conic projection with one standard parallel
40//! - **Lagrange** – Conformal spherical projection
41//! - **Loximuthal** – Rhumb-line based pseudocylindrical projection
42//! - **Euler** – Conic projection with two standard parallels
43//! - **Tissot** – Conic projection with two standard parallels
44//! - **Murdoch I** – Conic projection with two standard parallels
45//! - **Murdoch II** – Conic projection with two standard parallels
46//! - **Murdoch III** – Conic projection with two standard parallels
47//! - **Perspective Conic** – Conic projection with two standard parallels
48//! - **Vitkovsky I** – Conic projection with two standard parallels
49//! - **Tobler-Mercator** – Equal-area cylindrical projection
50//! - **Winkel II** – Compromise pseudocylindrical world projection
51//! - **Kavrayskiy V** – Pseudocylindrical world projection
52//! - **Stereographic** – Polar and oblique variants
53//! - **Orthographic** – Globe-view projection
54//! - **Sinusoidal** – Equal-area pseudocylindrical
55//! - **Mollweide** – Equal-area pseudocylindrical
56//! - **McBryde-Thomas Flat-Pole Sine (No. 2)** – Pseudocylindrical projection
57//! - **McBryde-Thomas Flat-Polar Sine (No. 1)** – Pseudocylindrical projection
58//! - **McBryde-Thomas Flat-Polar Parabolic** – Pseudocylindrical projection
59//! - **McBryde-Thomas Flat-Polar Quartic** – Pseudocylindrical projection
60//! - **Nell** – Pseudocylindrical projection
61//! - **Equal Earth** – Equal-area compromise world projection
62//! - **Cylindrical Equal-Area** – Equal-area cylindrical
63//! - **Equirectangular** (Plate Carrée) – Simple cylindrical
64//! - **Robinson** – Compromise pseudocylindrical
65//! - **Gnomonic** – Great circles map to straight lines
66//! - **Aitoff** – Compromise world projection
67//! - **Van der Grinten** – Circular world projection
68//! - **Winkel Tripel** – National Geographic world projection
69//! - **Hammer** – Equal-area world projection
70//! - **Hatano** – Asymmetrical equal-area pseudocylindrical projection
71//! - **Eckert I** – Pseudocylindrical world projection
72//! - **Eckert II** – Pseudocylindrical world projection
73//! - **Eckert III** – Pseudocylindrical world projection
74//! - **Eckert IV** – Equal-area pseudocylindrical world projection
75//! - **Eckert V** – Pseudocylindrical world projection
76//! - **Miller Cylindrical** – Modified Mercator world projection
77//! - **Gall Stereographic** – Cylindrical stereographic world projection
78//! - **Gall-Peters** – Equal-area cylindrical world projection
79//! - **Behrmann** – Equal-area cylindrical projection (30° standard parallel)
80//! - **Hobo-Dyer** – Equal-area cylindrical projection (37.5° standard parallel)
81//! - **Wagner I** – Pseudocylindrical world projection
82//! - **Wagner II** – Pseudocylindrical world projection
83//! - **Wagner III** – Pseudocylindrical world projection
84//! - **Wagner IV** – Equal-area pseudocylindrical world projection
85//! - **Wagner V** – Equal-area pseudocylindrical world projection
86//! - **Natural Earth** – Compromise pseudocylindrical world projection
87//! - **Natural Earth II** – Compromise pseudocylindrical world projection
88//! - **Wagner VI** – Compromise pseudocylindrical world projection
89//! - **Eckert VI** – Equal-area pseudocylindrical world projection
90//! - **Transverse Cylindrical Equal Area** – Spherical equal-area cylindrical projection
91//! - **Polyconic** – American polyconic projection
92//! - **Bonne** – Equal-area pseudoconical projection
93//! - **Craster** – Craster Parabolic (Putnins P4) projection
94//! - **Putnins P4'** – Pseudocylindrical compromise projection
95//! - **Fahey** – Pseudocylindrical projection
96//! - **Times** – Cylindrical compromise projection
97//! - **Patterson** – Cylindrical compromise projection
98//! - **Putnins P3** – Pseudocylindrical compromise projection
99//! - **Putnins P3'** – Modified pseudocylindrical compromise projection
100//! - **Putnins P5** – Pseudocylindrical compromise projection
101//! - **Putnins P5'** – Modified pseudocylindrical compromise projection
102//! - **Putnins P1** – Pseudocylindrical projection
103//! - **Putnins P2** – Pseudocylindrical projection
104//! - **Putnins P6** – Pseudocylindrical projection
105//! - **Putnins P6'** – Pseudocylindrical projection
106//! - **Quartic Authalic** – Equal-area pseudocylindrical projection
107//! - **Foucaut** – Pseudocylindrical projection
108//! - **Winkel I** – Compromise pseudocylindrical world projection
109//! - **Werenskiold I** – Pseudocylindrical projection
110//! - **Collignon** – Equal-area pseudocylindrical projection
111//! - **Nell-Hammer** – Pseudocylindrical projection
112//! - **Kavrayskiy VII** – Pseudocylindrical world projection
113//!
114//! ## Coordinate Reference Systems
115//!
116//! Use the [`Crs`] type to perform datum transformations between common
117//! coordinate reference systems including WGS84, NAD83, NAD27, and ETRS89.
118//! Grid-shift workflows are also supported via [`datum::DatumTransform::GridShift`]
119//! with loaders in [`grid_formats`] and runtime registration in [`grid_shift`].
120//!
121//! ## 3D CRS workflows
122//!
123//! `wbprojection` now supports geocentric (ECEF XYZ) and minimal vertical CRS workflows.
124//!
125//! - Use [`Crs::transform_to_3d`] for strict 3D transforms.
126//!   - Geographic/Projected <-> Geocentric is supported.
127//!   - Vertical <-> Vertical is supported as passthrough.
128//!   - Mixed Vertical <-> Geographic/Projected is rejected in strict mode.
129//! - Use [`Crs::transform_to_3d_preserve_horizontal`] for explicit mixed-mode
130//!   Vertical <-> Geographic/Projected workflows where horizontal context should be
131//!   preserved unchanged.
132//! - Use [`Crs::transform_to_3d_preserve_horizontal_with_vertical_offsets`] (or its
133//!   policy-aware variant) when vertical offsets are available from external models.
134//! - Use [`Crs::transform_to_3d_preserve_horizontal_with_provider`] when offsets
135//!   should be resolved dynamically from coordinate/CRS context.
136//! - Use [`ConstantVerticalOffsetProvider`] for simple fixed-offset workflows.
137//! - Use [`GridVerticalOffsetProvider`] with registered [`VerticalOffsetGrid`]
138//!   models for native bilinear-sampled vertical offsets.
139//!
140//! ```rust
141//! use wbprojection::Crs;
142//!
143//! // Geographic <-> Geocentric
144//! let geog = Crs::from_epsg(7843).unwrap();
145//! let geoc = Crs::from_epsg(7842).unwrap();
146//! let (x, y, z) = geog.transform_to_3d(147.0, -35.0, 120.0, &geoc).unwrap();
147//! let (_lon, _lat, _h) = geoc.transform_to_3d(x, y, z, &geog).unwrap();
148//!
149//! // Explicit mixed vertical workflow (preserve horizontal context)
150//! let vertical = Crs::from_epsg(7841).unwrap();
151//! let utm = Crs::from_epsg(7846).unwrap();
152//! let (_x2, _y2, _z2) = utm
153//!     .transform_to_3d_preserve_horizontal(500_000.0, 6_120_000.0, 42.0, &vertical)
154//!     .unwrap();
155//! ```
156
157#![deny(missing_docs)]
158#![warn(rust_2018_idioms)]
159
160#[cfg(test)]
161mod tests;
162pub mod compound_crs;
163pub mod crs;
164pub mod datum;
165pub mod ellipsoid;
166pub mod epsg;
167pub mod error;
168pub mod grid_formats;
169pub mod grid_shift;
170pub mod projections;
171pub mod transform;
172pub mod vertical_grid;
173mod wkt;
174
175pub use compound_crs::CompoundCrs;
176pub use crs::{
177    ConstantVerticalOffsetProvider,
178    Crs,
179    CrsTransformPolicy,
180    CrsTransformTrace,
181    GridVerticalOffsetProvider,
182    VerticalOffsetProvider,
183};
184pub use datum::Datum;
185pub use ellipsoid::Ellipsoid;
186pub use epsg::{
187    EpsgAliasEntry,
188    EpsgIdentifyCandidate,
189    EpsgIdentifyPolicy,
190    EpsgIdentifyReport,
191    EpsgResolution,
192    EpsgResolutionPolicy,
193    clear_runtime_epsg_aliases,
194    epsg_alias_catalog,
195    epsg_from_srs_reference,
196    epsg_from_wkt,
197    compound_from_wkt,
198    from_epsg,
199    from_epsg_with_catalog,
200    from_epsg_with_policy,
201    identify_epsg_from_crs,
202    identify_epsg_from_crs_report,
203    identify_epsg_from_crs_with_policy,
204    identify_epsg_from_wkt,
205    identify_epsg_from_wkt_report,
206    identify_epsg_from_wkt_with_policy,
207    from_wkt,
208    register_epsg_alias,
209    resolve_epsg_with_catalog,
210    resolve_epsg_with_policy,
211    runtime_epsg_aliases,
212    to_esri_wkt,
213    to_geotiff_info,
214    to_ogc_wkt,
215    unregister_epsg_alias,
216    vertical_offset_grid_name,
217};
218pub use error::{ProjectionError, Result};
219pub use grid_formats::{
220    list_ntv2_subgrids, load_nadcon_ascii_pair, load_ntv2_gsb, load_ntv2_gsb_subgrid,
221    register_nadcon_ascii_pair, register_ntv2_gsb, register_ntv2_gsb_hierarchy,
222    register_ntv2_gsb_subgrid, resolve_ntv2_hierarchy_grid_name,
223    resolve_ntv2_hierarchy_subgrid,
224};
225pub use grid_shift::{GridShiftGrid, GridShiftSample, get_grid, has_grid, register_grid, unregister_grid};
226pub use projections::{Projection, ProjectionKind, ProjectionParams};
227pub use transform::{CoordTransform, Point2D, Point3D};
228pub use vertical_grid::{
229    VerticalOffsetGrid,
230    get_vertical_offset_grid,
231    has_vertical_offset_grid,
232    load_vertical_grid_from_gtx,
233    load_vertical_grid_from_isg,
234    load_vertical_grid_from_simple_header_grid,
235    register_vertical_offset_grid,
236    unregister_vertical_offset_grid,
237};
238
239/// Convert degrees to radians.
240#[inline]
241pub fn to_radians(deg: f64) -> f64 {
242    deg * std::f64::consts::PI / 180.0
243}
244
245/// Convert radians to degrees.
246#[inline]
247pub fn to_degrees(rad: f64) -> f64 {
248    rad * 180.0 / std::f64::consts::PI
249}
250
251/// Normalize a longitude value to the range [-180, 180).
252pub fn normalize_longitude(lon: f64) -> f64 {
253    let mut l = lon % 360.0;
254    if l > 180.0 {
255        l -= 360.0;
256    } else if l < -180.0 {
257        l += 360.0;
258    }
259    l
260}