() - 1.);
120 | }
121 | transform::transform(&src, &dst, &mut point)?;
122 | }
123 |
124 | let elapsed = start.elapsed();
125 |
126 | println!("Duration: {} ms", (elapsed - noise_elapsed).as_millis());
127 | println!(
128 | "Throughput: {:.2} million coordinates/s",
129 | 1e-3 * (loops as f64) / (elapsed - noise_elapsed).as_millis() as f64
130 | );
131 | }
132 | Ok(())
133 | }
134 |
135 | //
136 | // Logger
137 | //
138 | fn init_logger(verbose: u8) {
139 | use env_logger::Env;
140 | use log::LevelFilter;
141 |
142 | let mut builder = env_logger::Builder::from_env(Env::default().default_filter_or("info"));
143 |
144 | match verbose {
145 | 1 => builder.filter_level(LevelFilter::Debug),
146 | _ if verbose > 1 => builder.filter_level(LevelFilter::Trace),
147 | _ => &mut builder,
148 | }
149 | .init();
150 | }
151 |
--------------------------------------------------------------------------------
/js/ol-proj4rs-demo-app/wms-image-custom-proj.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Single Image WMS with Proj4rs
6 |
7 |
8 |
9 |
10 |
11 |
12 | Single Image WMS with Proj4rs
13 |
14 |
15 | With Proj4rs integration, OpenLayers can transform coordinates between arbitrary projections. This demo is the same as the
16 | OpenLayers one but here, it uses
17 | the proj4rs library instead of the proj4js one.
18 |
19 |
20 |
21 | proj4 OpenLayers
22 |
23 | import ImageLayer from 'ol/layer/Image.js' ;
24 | import ImageWMS from 'ol/source/ImageWMS.js' ;
25 | import Map from 'ol/Map.js' ;
26 | import Projection from 'ol/proj/Projection.js' ;
27 | import View from 'ol/View.js' ;
28 | import proj4 from 'proj4' ;
29 | import {ScaleLine, defaults as defaultControls} from 'ol/control.js' ;
30 | import {fromLonLat} from 'ol/proj.js' ;
31 | import {register} from 'ol/proj/proj4.js' ;
32 | ...
33 |
34 |
35 | proj4rs
36 |
37 | import ImageLayer from 'ol/layer/Image.js' ;
38 | import ImageWMS from 'ol/source/ImageWMS.js' ;
39 | import Map from 'ol/Map.js' ;
40 | import Projection from 'ol/proj/Projection.js' ;
41 | import View from 'ol/View.js' ;
42 | import {proj4} from 'proj4rs/proj4.js' ;
43 | import {ScaleLine, defaults as defaultControls} from 'ol/control.js' ;
44 | import {fromLonLat} from 'ol/proj.js' ;
45 | import {register} from 'ol/proj/proj4.js' ;
46 | ...
47 |
48 |
49 |
50 |
51 | Home
52 | Single Image WMS
53 | Image reprojection
54 | Raster reprojection
55 | Sphere Mollweide
56 | Vector projections
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/js/ol-proj4rs-demo-app/style.css:
--------------------------------------------------------------------------------
1 | @import "node_modules/ol/ol.css";
2 |
3 | /* Main page */
4 |
5 | :root {
6 | --main-transition: ease 200ms;
7 | }
8 |
9 | html, body {
10 | font-family: "Roboto Light", sans-serif;
11 | text-align: center;
12 | margin: 0;
13 | }
14 |
15 | header {
16 | font-weight: bold;
17 | font-size: 28px;
18 | padding-top: 20px;
19 | }
20 |
21 | h4 {
22 | font-size: 23px;
23 | }
24 |
25 | container {
26 | display: flex;
27 | flex-wrap: wrap;
28 | justify-content: center;
29 | }
30 |
31 | container > div {
32 | width: 370px;
33 | height: 210px;
34 | margin: 5px;
35 | display: flex;
36 | justify-content: center;
37 | align-items: center;
38 | }
39 |
40 | .example {
41 | -webkit-user-drag: none;
42 | background-color: #ffffff;
43 | color: black;
44 | width: 90%;
45 | height: 90%;
46 | display: flex;
47 | flex-direction: column;
48 | justify-content: center;
49 | align-items: center;
50 | text-decoration: none;
51 | border-radius: 23px;
52 | box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);
53 | transition: var(--main-transition);
54 | }
55 |
56 | .example:hover {
57 | box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.35);
58 | transition: var(--main-transition);
59 | width: 95%;
60 | height: 95%;
61 |
62 | strong {
63 | font-size: 135%;
64 | transition: var(--main-transition);
65 | }
66 |
67 | small {
68 | font-size: 84%;
69 | transition: var(--main-transition);
70 | }
71 |
72 | p {
73 | font-size: 100%;
74 | transition: var(--main-transition);
75 | }
76 | }
77 |
78 | .example:active {
79 | background-color: rgba(246, 246, 246, 0.36);
80 | }
81 |
82 | .example > strong {
83 | width: 90%;
84 | font-size: 125%;
85 | transition: var(--main-transition);
86 | }
87 |
88 | .example > small {
89 | width: 90%;
90 | font-size: 78%;
91 | font-style: italic;
92 | transition: var(--main-transition);
93 | }
94 |
95 | .example > p {
96 | width: 90%;
97 | font-size: 95%;
98 | transition: var(--main-transition);
99 | }
100 |
101 | /* Demos */
102 |
103 | #map {
104 | margin: 0 12.5%;
105 | width: 75%;
106 | height: 650px;
107 | min-width: 580px;
108 | border: #3a3a3a solid 2px;
109 | border-radius: 10px 10px 0 0;
110 | border-bottom: none;
111 | }
112 |
113 | #map.simple {
114 | border-radius: 10px;
115 | border-bottom: #3a3a3a solid 2px;;
116 | .ol-layer > canvas {
117 | border-radius: 8px;
118 | }
119 | }
120 |
121 | .ol-layer > canvas {
122 | border-radius: 8px 8px 0 0;
123 | }
124 |
125 | .reprojection-form {
126 | background: linear-gradient(#f8f8f8, #ffffff);
127 | margin: 0 12.5%;
128 | width: 75%;
129 | min-width: 580px;
130 | padding: 15px 0;
131 | border: #3a3a3a solid 2px;
132 | display: grid;
133 | grid-template-columns: max-content max-content;
134 | grid-gap: 5px;
135 | column-gap: 20px;
136 | row-gap: 25px;
137 | align-items: center;
138 | justify-content: center;
139 | border-radius: 0 0 10px 10px;
140 | }
141 |
142 | .reprojection-form > select {
143 | background-color: white;
144 | border: none;
145 | height: 30px;
146 | padding: 0 15px;
147 | border-radius: 15px;
148 | box-shadow: inset 0px 0px 5px 0px rgba(0,0,0,0.2);
149 | transition: ease 80ms;
150 | }
151 |
152 | .reprojection-form > select:hover {
153 | box-shadow: inset 0px 0px 5px 0px rgba(0,0,0,0.35);
154 | transition: ease 80ms;
155 | }
156 |
157 | .reprojection-form > select:active {
158 | box-shadow: inset 0px 0px 5px 0px rgba(0,0,0,0.6);
159 | }
160 |
161 | .demo-description {
162 | width: 95%;
163 | margin: 25px auto;
164 | }
165 |
166 | /* Nav bar */
167 |
168 | nav {
169 | background-color: white;
170 | border-radius: 0 7px 7px 0;
171 | position: fixed;
172 | width: 40px;
173 | height: 250px;
174 | top: 15vh;
175 | display: flex;
176 | flex-direction: column;
177 | text-indent: -460%;
178 | transition: ease 200ms;
179 | }
180 |
181 | nav > a:first-child {
182 | border-radius: 0 10px 0 0;
183 | border-top: solid 2px #000000;
184 | font-weight: bold;
185 | }
186 |
187 | nav > a:last-child {
188 | border-radius: 0 0 10px 0;
189 | border-bottom: solid 2px #000000;
190 | }
191 |
192 | nav:hover {
193 | width: 200px;
194 | text-indent: 0;
195 | .navLabel {
196 | width: 200px;
197 | color: black;
198 | }
199 | #navLabelActive {
200 | color: black;
201 | }
202 | }
203 |
204 | .navLabel {
205 | border-right: solid 3px #000000;
206 | color: black;
207 | background-color: white;
208 | width: 40px;
209 | height: 50px;
210 | transition: ease 200ms;
211 | text-decoration: none;
212 | text-align: center;
213 | line-height: 50px;
214 | }
215 |
216 | .navLabel:hover {
217 | background-color: rgb(237, 237, 237);
218 | }
219 |
220 | .navLabel:active {
221 | background-color: rgb(220, 220, 220);
222 | }
223 |
224 | #navLabelActive {
225 | background-color: rgb(220, 220, 220);
226 | }
227 |
228 | #navLabelActive:hover {
229 | background-color: rgb(237, 237, 237);
230 | }
231 |
--------------------------------------------------------------------------------
/js/ol-proj4rs-demo-app/reprojection-image.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Image Reprojection
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 | Image Reprojection
19 |
20 |
21 |
22 | Interpolate
23 |
24 |
25 | This example shows client-side reprojection of single image source. This demo is the same as the
26 | OpenLayers one but here, it uses
27 | the proj4rs library instead of the proj4js one.
28 |
29 |
30 |
31 | proj4 OpenLayers
32 |
33 | import Map from 'ol/Map.js' ;
34 | import OSM from 'ol/source/OSM.js' ;
35 | import Static from 'ol/source/ImageStatic.js' ;
36 | import View from 'ol/View.js' ;
37 | import proj4 from 'proj4' ;
38 | import {Image as , Tile as TileLayer} from 'ol/layer.js' ;
39 | import {getCenter} from 'ol/extent.js' ;
40 | import {register} from 'ol/proj/proj4.js' ;
41 | import {transform} from 'ol/proj.js' ;
42 | ...
43 |
44 |
45 | proj4rs
46 |
47 | import Map from 'ol/Map.js' ;
48 | import OSM from 'ol/source/OSM.js' ;
49 | import Static from 'ol/source/ImageStatic.js' ;
50 | import View from 'ol/View.js' ;
51 | import {proj4} from 'proj4rs/proj4.js' ;
52 | import {Image as , Tile as TileLayer} from 'ol/layer.js' ;
53 | import {getCenter} from 'ol/extent.js' ;
54 | import {register} from 'ol/proj/proj4.js' ;
55 | import {transform} from 'ol/proj.js' ;
56 | ...
57 |
58 |
59 |
60 |
61 | Home
62 | Single Image WMS
63 | Image reprojection
64 | Raster reprojection
65 | Sphere Mollweide
66 | Vector projections
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/proj4rs/src/lib.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Coordinate transformation library
3 | //!
4 | //! Based on Proj4 implementation
5 | //!
6 | //! References:
7 | //! *
8 | //! *
9 | //!
10 | //! The aim of Proj4rs is to provide at short term the same functionality as the original
11 | //! proj4 library.
12 | //!
13 | //! The long term project is to integrate feature from the proj library in its latest
14 | //! version.
15 | //!
16 | //! The goal of proj4rs is not to be a remplacement of proj, but instead being a light
17 | //! weight implementation of transformations from crs to crs that could be used
18 | //! in WASM environment
19 | //!
20 | //! ## Usage
21 | //!
22 | //! Note that angular units are in radians, not degrees !
23 | //!
24 | //! Radian is natural unit for trigonometric opérations, like proj, proj4rs use radians
25 | //! for its operation while degrees are mostly used as end user input/output.
26 | //!
27 | //! Example:
28 | //! ```
29 | //! use proj4rs::proj::Proj;
30 | //!
31 | //! // EPSG:5174 - Example
32 | //! let from = Proj::from_proj_string(concat!(
33 | //! "+proj=tmerc +lat_0=38 +lon_0=127.002890277778",
34 | //! " +k=1 +x_0=200000 +y_0=500000 +ellps=bessel",
35 | //! " +towgs84=-145.907,505.034,685.756,-1.162,2.347,1.592,6.342",
36 | //! " +units=m +no_defs +type=crs"
37 | //! ))
38 | //! .unwrap();
39 | //!
40 | //! // EPSG:4326 - WGS84, known to us as basic longitude and latitude.
41 | //! let to = Proj::from_proj_string(concat!(
42 | //! "+proj=longlat +ellps=WGS84",
43 | //! " +datum=WGS84 +no_defs"
44 | //! ))
45 | //! .unwrap();
46 | //!
47 | //! let mut point_3d = (198236.3200000003, 453407.8560000006, 0.0);
48 | //! proj4rs::transform::transform(&from, &to, &mut point_3d).unwrap();
49 | //!
50 | //! // XXX Note that angular unit is radians, not degrees !
51 | //! point_3d.0 = point_3d.0.to_degrees();
52 | //! point_3d.1 = point_3d.1.to_degrees();
53 | //!
54 | //! // Output in longitude, latitude, and height.
55 | //! println!("{} {}",point_3d.0, point_3d.1); // 126.98069676435814, 37.58308534678718
56 | //! ```
57 | //!
58 | //! ## Optional features
59 | //!
60 | //! * **geo-types**: [geo-types]( ) support
61 | //! * **logging**: support for logging with [log](https://docs.rs/log/latest/log/) crate.
62 | //! If activated for WASM, it will use the [console-log](https://docs.rs/console_log/latest/console_log/)
63 | //! adaptor.
64 | //! * **wasm-strict**: used with WASM; Transformation operation will return exception as soon as we
65 | //! have invalid coordinates or that the reprojection failed.
66 | //! The default is to use a relaxed-mode that return NaN in case of projection failure: this is expected
67 | //! mostly from js app (at least with OpenLayer).
68 | //! * **multi-thread**: Support for multi-thread with NAD Grid processing, this is activated by
69 | //! default and disabled when compiling for WASM.
70 | //! * **crs-definitions**: Support for initializing projections from EPSG codes with the
71 | //! [crs_definitions](https://docs.rs/crs-definitions/latest/crs_definitions/) crate.
72 | //!
73 | //! ## WKT Support
74 | //!
75 | //! There is no actual default support for WKT in proj4rs
76 | //! If you are looking for WTK/Proje string conversion support in Rust,
77 | //! then have a look at:
78 | //!
79 | //! -
80 | //! -
81 | //!
82 | //! Note that the proj library provides a great implementation of the standard.
83 | //!
84 | //! ## Grid shift supports
85 | //!
86 | //! Nadgrid support is still experimental.
87 | //! Currently, only Ntv2 multi grids are supported for native build and WASM.
88 | //!
89 |
90 | mod datum_params;
91 | mod datum_transform;
92 | mod datums;
93 | mod ellipsoids;
94 | mod ellps;
95 | mod geocent;
96 | mod math;
97 | mod parameters;
98 | mod parse;
99 | mod prime_meridians;
100 | mod projstring;
101 | mod units;
102 |
103 | pub mod adaptors;
104 | pub mod errors;
105 | pub mod nadgrids;
106 | pub mod proj;
107 | pub mod projections;
108 | pub mod transform;
109 |
110 | // Reexport
111 | pub use proj::Proj;
112 |
113 | // Include wasm entry point for wasm32-unknown-unknown
114 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
115 | mod wasm;
116 |
117 | #[cfg(test)]
118 | mod tests;
119 |
120 | // log for logging (optional).
121 | #[cfg(feature = "logging")]
122 | use log;
123 |
124 | #[cfg(not(feature = "logging"))]
125 | mod log {
126 | // Use __XXX__ to prevent 'ambiguous name' error
127 | // when exporting
128 | macro_rules! __trace__ ( ($($tt:tt)*) => {{}} );
129 | macro_rules! __debug__ ( ($($tt:tt)*) => {{}} );
130 | macro_rules! __error__ ( ($($tt:tt)*) => {{}} );
131 | macro_rules! __info__ ( ($($tt:tt)*) => {{}} );
132 | macro_rules! __warn__ ( ($($tt:tt)*) => {{}} );
133 |
134 | #[allow(unused_imports)]
135 | pub(crate) use {
136 | __debug__ as debug, __error__ as error, __info__ as info, __trace__ as trace,
137 | __warn__ as warn,
138 | };
139 | }
140 |
--------------------------------------------------------------------------------
/proj4rs/tests/proj4js_tests.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Tests from proj4js
3 | //!
4 | //! Note: projection results may differs from proj by 10^-4 due to difference
5 | //! in math functions implementations (asinh, log1py...)
6 | //!
7 | use approx::assert_abs_diff_eq;
8 | use proj4rs::{proj, transform};
9 |
10 | #[test]
11 | fn test_transform_with_datum() {
12 | //EPSG:3006 Definition - Sweden coordinate reference system
13 | let sweref99tm = concat!(
14 | "+proj=utm +zone=33 +ellps=GRS80 ",
15 | "+towgs84=0,0,0,0,0,0,0 +units=m +no_defs"
16 | );
17 | // EPSG:3021 Definition - Sweden coordinate reference system
18 | let rt90 = concat!(
19 | "+proj=tmerc +lon_0=15.808277777799999 +lat_0=0.0 +k=1.0 ",
20 | "+x_0=1500000.0 +y_0=0.0 +ellps=bessel ",
21 | "+units=m +towgs84=414.1,41.3,603.1,-0.855,2.141,-7.023,0 ",
22 | "+no_defs"
23 | );
24 |
25 | let from = proj::Proj::from_user_string(sweref99tm).unwrap();
26 | let to = proj::Proj::from_user_string(rt90).unwrap();
27 |
28 | let mut inp = (319180., 6399862., 0.);
29 |
30 | transform::transform(&from, &to, &mut inp).unwrap();
31 | assert_abs_diff_eq!(inp.0, 1271137.92755580, epsilon = 1.0e-6);
32 | assert_abs_diff_eq!(inp.1, 6404230.29136189, epsilon = 1.0e-6);
33 | }
34 |
35 | #[test]
36 | fn test_transform_null_datum() {
37 | // Test when nadgrid list is empty
38 | // ESPG:2154 definition
39 | let epsg2154 = concat!(
40 | "+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 ",
41 | "+x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 ",
42 | "+units=m +no_defs +type=crs"
43 | );
44 | // ESPG:3857 definition
45 | let epsg3857 = concat!(
46 | "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 ",
47 | "+units=m +nadgrids=@null +wktext +no_defs +type=crs",
48 | );
49 |
50 | let from = proj::Proj::from_user_string(epsg2154).unwrap();
51 | let to = proj::Proj::from_user_string(epsg3857).unwrap();
52 |
53 | let mut inp = (489353.59, 6587552.2, 0.);
54 | transform::transform(&from, &to, &mut inp).unwrap();
55 | // Check against cs2cs output
56 | assert_abs_diff_eq!(inp.0, 28943.07106251, epsilon = 1.0e-6);
57 | assert_abs_diff_eq!(inp.1, 5837421.86634143, epsilon = 1.0e-6);
58 | }
59 |
60 | #[test]
61 | fn test_longlat_alias() {
62 | let wgs84 = concat!(
63 | "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 ",
64 | "+datum=WGS84 +units=degrees",
65 | );
66 |
67 | let projection = proj::Proj::from_user_string(wgs84);
68 | assert!(projection.is_ok());
69 | }
70 |
71 | #[test]
72 | fn test_transform_epsg3044() {
73 | // ESPG:3044 definition
74 | let epsg3044 = concat!("+proj=utm +zone=32 +ellps=GRS80 +units=m +towgs84=0,0,0,0,0,0,0 ",);
75 | // ESPG:3857 definition
76 | let epsg3857 = concat!(
77 | "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 ",
78 | "+units=m +nadgrids=@null",
79 | );
80 |
81 | let from = proj::Proj::from_user_string(epsg3044).unwrap();
82 | let to = proj::Proj::from_user_string(epsg3857).unwrap();
83 |
84 | let mut inp = (580900., 5625000., 0.);
85 | transform::transform(&from, &to, &mut inp).unwrap();
86 | assert_abs_diff_eq!(inp.0, 1129592.3568078864, epsilon = 1.0e-6);
87 | assert_abs_diff_eq!(inp.1, 6580906.077194334, epsilon = 1.0e-6);
88 | }
89 |
90 | #[test]
91 | fn test_axis_denormalize() {
92 | // ESPG:3044 definition
93 | let epsg3044 = concat!("+proj=utm +zone=32 +ellps=GRS80 +units=m +towgs84=0,0,0,0,0,0,0 ",);
94 | // ESPG:3857 definition
95 | let epsg3857 = concat!(
96 | "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 ",
97 | "+units=m +nadgrids=@null +axis=neu",
98 | );
99 |
100 | let from = proj::Proj::from_user_string(epsg3044).unwrap();
101 | let to = proj::Proj::from_user_string(epsg3857).unwrap();
102 |
103 | let mut inp = (580900., 5625000., 0.);
104 | transform::transform(&from, &to, &mut inp).unwrap();
105 | assert_abs_diff_eq!(inp.0, 6580906.077194334, epsilon = 1.0e-6);
106 | assert_abs_diff_eq!(inp.1, 1129592.3568078864, epsilon = 1.0e-6);
107 | }
108 |
109 | #[test]
110 | fn test_transform_epsg3844() {
111 | // ESPG:3844 definition
112 | let epsg3844 = concat!(
113 | "+proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 +x_0=500000 +y_0=500000 ",
114 | "+ellps=krass ",
115 | //"+towgs84=2.329,-147.042,-92.08,0.309,-0.325,-0.497,5.69 ",
116 | //"+towgs84=44.107,-116.147,-54.648 ",
117 | //"+towgs84=28,-121,-77 ",
118 | "+units=m +no_defs +type=crs"
119 | );
120 | // ESPG:3857 definition
121 | let epsg3857 = concat!(
122 | "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 ",
123 | "+units=m",
124 | );
125 |
126 | // ESPG:3857 definition 2
127 | //let epsg3857 = concat!(
128 | // "+proj=webmerc +ellps=WGS84 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 ",
129 | // "+units=m +towgs84=0,0,0",
130 | //);
131 |
132 | let from = proj::Proj::from_user_string(epsg3844).unwrap();
133 | let to = proj::Proj::from_user_string(epsg3857).unwrap();
134 |
135 | let mut inp = (505000., 500000., 0.);
136 | transform::transform(&from, &to, &mut inp).unwrap();
137 | // Compare results from cs2cs output
138 | assert_abs_diff_eq!(inp.0, 2790174.2500622645, epsilon = 1.0e-6);
139 | assert_abs_diff_eq!(inp.1, 5780346.2980352566, epsilon = 1.0e-6);
140 | }
141 |
--------------------------------------------------------------------------------
/proj4rs/src/datum_transform.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Datum transformation
3 | //!
4 | //! As with proj4/5 the datum transformation use WGS84 as hub for
5 | //! converting data from one crs to another
6 | //!
7 | //! Datum shifts are carried out with the following steps:
8 | //!
9 | //! 1. Convert (latitude, longitude, ellipsoidal height) to
10 | //! 3D geocentric cartesian coordinates (X, Y, Z)
11 | //! 2. Transform the (X, Y, Z) coordinates to the new datum, using a
12 | //! 7 parameter Helmert transformation.
13 | //! 3. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height)
14 | //!
15 | //! Actually, the step 2 use WGS84 as conversion *hub* which leads to apply
16 | //! 2 Helmert transformations.
17 | //!
18 | //! With natgrids the steps are slightly different:
19 | //! 1. Apply nadgrid transformation with source datum
20 | //! 2. Convert to geocentric with source ellipsoid parameters
21 | //! 3. Convert to geodetic with dest ellipsoid.
22 | //! 4. Apply inverse nadgrids transformation with destination datum
23 | //!
24 | use crate::datum_params::DatumParams;
25 | use crate::ellps::Ellipsoid;
26 | use crate::errors::Result;
27 | use crate::geocent::{geocentric_to_geodetic, geodetic_to_geocentric};
28 | use crate::transform::Direction;
29 |
30 | use DatumParams::*;
31 |
32 | const SRS_WGS84_SEMIMAJOR: f64 = 6378137.0;
33 | const SRS_WGS84_SEMIMINOR: f64 = 6356752.314;
34 | const SRS_WGS84_ES: f64 = 0.0066943799901413165;
35 |
36 | /// Hold datum Informations
37 | #[derive(Debug, Clone)]
38 | pub(crate) struct Datum {
39 | params: DatumParams,
40 | pub a: f64,
41 | pub b: f64,
42 | pub es: f64,
43 | }
44 |
45 | impl Datum {
46 | pub fn new(ellps: &Ellipsoid, params: DatumParams) -> Self {
47 | // Change ellipse parameters to wgs84
48 | // when using nadgrids
49 | let (a, b, es) = if params.use_nadgrids() {
50 | (SRS_WGS84_SEMIMAJOR, SRS_WGS84_SEMIMINOR, SRS_WGS84_ES)
51 | } else {
52 | (ellps.a, ellps.b, ellps.es)
53 | };
54 |
55 | Self {
56 | // check for WGS84/GRS80
57 | params: if params == ToWGS84_3(0., 0., 0.)
58 | && ellps.a == SRS_WGS84_SEMIMAJOR
59 | && (ellps.es - SRS_WGS84_ES).abs() < 0.000000000050
60 | {
61 | ToWGS84_0
62 | } else {
63 | params
64 | },
65 | a,
66 | b,
67 | es,
68 | }
69 | }
70 |
71 | /// Convert from geodetic coordinates to wgs84/geocentric
72 | fn towgs84(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
73 | match &self.params {
74 | ToWGS84_0 => geodetic_to_geocentric(x, y, z, self.a, self.es),
75 | ToWGS84_3(dx, dy, dz) => geodetic_to_geocentric(x, y, z, self.a, self.es)
76 | .map(|(x, y, z)| (x + dx, y + dy, z + dz)),
77 | ToWGS84_7(dx, dy, dz, rx, ry, rz, s) => {
78 | geodetic_to_geocentric(x, y, z, self.a, self.es).map(|(x, y, z)| {
79 | (
80 | dx + s * (x - rz * y + ry * z),
81 | dy + s * (rz * x + y - rx * z),
82 | dz + s * (-ry * x + rx * y + z),
83 | )
84 | })
85 | }
86 | NadGrids(grids) => grids
87 | .apply_shift(Direction::Forward, x, y, z)
88 | .and_then(|(x, y, z)| geodetic_to_geocentric(x, y, z, self.a, self.es)),
89 | NoDatum => Ok((x, y, z)),
90 | }
91 | }
92 |
93 | /// Convert from geocentric/wgs84 to geodetic coordinates
94 | fn fromwgs84(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
95 | match &self.params {
96 | ToWGS84_0 => geocentric_to_geodetic(x, y, z, self.a, self.es, self.b),
97 | ToWGS84_3(dx, dy, dz) => {
98 | geocentric_to_geodetic(x - dx, y - dy, z - dz, self.a, self.es, self.b)
99 | }
100 | ToWGS84_7(dx, dy, dz, rx, ry, rz, s) => {
101 | let (x, y, z) = ((x - dx) / s, (y - dy) / s, (z - dz) / s);
102 | geocentric_to_geodetic(
103 | x + rz * y - ry * z,
104 | -rz * x + y + rx * z,
105 | ry * x - rx * y + z,
106 | self.a,
107 | self.es,
108 | self.b,
109 | )
110 | }
111 | NadGrids(grids) => geocentric_to_geodetic(x, y, z, self.a, self.es, self.b)
112 | .and_then(|(x, y, z)| grids.apply_shift(Direction::Inverse, x, y, z)),
113 | NoDatum => Ok((x, y, z)),
114 | }
115 | }
116 |
117 | #[inline]
118 | pub fn no_datum(&self) -> bool {
119 | self.params.no_datum()
120 | }
121 |
122 | /// Return true if the datum are identical
123 | pub fn is_identical_to(&self, other: &Self) -> bool {
124 | // the tolerance for es is to ensure that GRS80 and WGS84
125 | // are considered identical
126 | (self.params == other.params)
127 | && self.a == other.a
128 | && (self.es - other.es).abs() < 0.000000000050
129 | }
130 |
131 | /// Transform geographic coordinates between datums
132 | ///
133 | /// No identity checking is done
134 | #[inline]
135 | pub fn transform(src: &Self, dst: &Self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
136 | src.towgs84(x, y, z)
137 | .and_then(|(x, y, z)| dst.fromwgs84(x, y, z))
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/proj4rs/src/projections/somerc.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Swiss Oblique Mercator
3 | //!
4 | //! ref:
5 | //!
6 | //! somerc: "Swiss. Obl. Mercator" "\n\tCyl, Ell\n\tFor CH1903";
7 | //!
8 | use crate::errors::{Error, Result};
9 | use crate::math::{
10 | aasin,
11 | consts::{EPS_10, FRAC_PI_2, FRAC_PI_4},
12 | };
13 | use crate::parameters::ParamList;
14 | use crate::proj::ProjData;
15 |
16 | // Projection stub
17 | super::projection! { somerc }
18 |
19 | #[derive(Debug, Clone)]
20 | pub(crate) struct Projection {
21 | e: f64,
22 | rone_es: f64,
23 | k: f64,
24 | c: f64,
25 | hlf_e: f64,
26 | k_r: f64,
27 | cosp0: f64,
28 | sinp0: f64,
29 | }
30 |
31 | #[allow(non_snake_case)]
32 | impl Projection {
33 | pub fn somerc(p: &mut ProjData, _: &ParamList) -> Result {
34 | let el = &p.ellps;
35 | let hlf_e = 0.5 * el.e;
36 |
37 | let (sinphi, cosphi) = p.phi0.sin_cos();
38 |
39 | let cp = cosphi * cosphi;
40 | let c = (1. + el.es * cp * cp * el.rone_es).sqrt();
41 | let sinp0 = sinphi / c;
42 | let phip0 = aasin(sinp0)?;
43 | let cosp0 = phip0.cos();
44 | let sp = sinphi * el.e;
45 | let k = (FRAC_PI_4 + 0.5 * phip0).tan().ln()
46 | - c * ((FRAC_PI_4 + 0.5 * p.phi0).tan().ln() - hlf_e * ((1. + sp) / (1. - sp)).ln());
47 | let k_r = p.k0 * el.one_es.sqrt() / (1. - sp * sp);
48 | Ok(Self {
49 | e: el.e,
50 | rone_es: el.rone_es,
51 | k,
52 | c,
53 | hlf_e,
54 | k_r,
55 | cosp0,
56 | sinp0,
57 | })
58 | }
59 |
60 | #[inline(always)]
61 | pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
62 | let sp = self.e * phi.sin();
63 | let phip = 2.
64 | * ((self.c
65 | * ((FRAC_PI_4 + 0.5 * phi).tan().ln()
66 | - self.hlf_e * ((1. + sp) / (1. - sp)).ln())
67 | + self.k)
68 | .exp())
69 | .atan()
70 | - FRAC_PI_2;
71 |
72 | let lamp = self.c * lam;
73 | let cp = phip.cos();
74 | let phipp = aasin(self.cosp0 * phip.sin() - self.sinp0 * cp * lamp.cos())?;
75 | let lampp = aasin(cp * lamp.sin() / phipp.cos())?;
76 |
77 | Ok((
78 | self.k_r * lampp,
79 | self.k_r * (FRAC_PI_4 + 0.5 * phipp).tan().ln(),
80 | z,
81 | ))
82 | }
83 |
84 | #[inline(always)]
85 | pub fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
86 | const NITER: isize = 6;
87 |
88 | let phipp = 2. * (((y / self.k_r).exp()).atan() - FRAC_PI_4);
89 | let lampp = x / self.k_r;
90 | let cp = phipp.cos();
91 | let mut phip = aasin(self.cosp0 * phipp.sin() + self.sinp0 * cp * lampp.cos())?;
92 | let lamp = aasin(cp * lampp.sin() / phip.cos())?;
93 | let con = (self.k - (FRAC_PI_4 + 0.5 * phip).tan().ln()) / self.c;
94 |
95 | let mut i = NITER;
96 | while i > 0 {
97 | let esp = self.e * phip.sin();
98 | let delp = (con + (FRAC_PI_4 + 0.5 * phip).tan().ln()
99 | - self.hlf_e * ((1. + esp) / (1. - esp)).ln())
100 | * (1. - esp * esp)
101 | * phip.cos()
102 | * self.rone_es;
103 | phip -= delp;
104 | if delp.abs() < EPS_10 {
105 | break;
106 | }
107 | i -= 1;
108 | }
109 | if i <= 0 {
110 | Err(Error::ToleranceConditionError)
111 | } else {
112 | Ok((lamp / self.c, phip, z))
113 | }
114 | }
115 |
116 | pub const fn has_inverse() -> bool {
117 | true
118 | }
119 |
120 | pub const fn has_forward() -> bool {
121 | true
122 | }
123 | }
124 |
125 | #[cfg(test)]
126 | mod tests {
127 | use crate::math::consts::EPS_10;
128 | use crate::proj::Proj;
129 | use crate::tests::utils::{test_proj_forward, test_proj_inverse};
130 |
131 | #[test]
132 | fn proj_somerc_el() {
133 | let p = Proj::from_proj_string("+proj=somerc +ellps=GRS80").unwrap();
134 |
135 | println!("{:#?}", p.projection());
136 |
137 | let inputs = [
138 | ((2., 1., 0.), (222638.98158654713, 110579.96521824898, 0.)),
139 | ((2., -1., 0.), (222638.98158654713, -110579.96521825089, 0.)),
140 | ((-2., 1., 0.), (-222638.98158654713, 110579.96521824898, 0.)),
141 | (
142 | (-2., -1., 0.),
143 | (-222638.98158654713, -110579.96521825089, 0.),
144 | ),
145 | ];
146 |
147 | test_proj_forward(&p, &inputs, EPS_10);
148 | test_proj_inverse(&p, &inputs, EPS_10);
149 | }
150 |
151 | #[test]
152 | fn proj_somerc_sp() {
153 | let p = Proj::from_proj_string("+proj=somerc +a=6400000").unwrap();
154 |
155 | println!("{:#?}", p.projection());
156 |
157 | let inputs = [
158 | ((2., 1., 0.), (223402.14425527418, 111706.74357494408, 0.)),
159 | ((2., -1., 0.), (223402.14425527418, -111706.74357494518, 0.)),
160 | ((-2., 1., 0.), (-223402.14425527418, 111706.74357494408, 0.)),
161 | (
162 | (-2., -1., 0.),
163 | (-223402.14425527418, -111706.74357494518, 0.),
164 | ),
165 | ];
166 |
167 | test_proj_forward(&p, &inputs, EPS_10);
168 | test_proj_inverse(&p, &inputs, EPS_10);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/proj4rs/src/datums.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Proj4 datum definitions
3 | //!
4 | use crate::ellipsoids::{constants as ellps, EllipsoidDefn};
5 |
6 | /// Shift method is either
7 | /// defined by Helmert transforms or nadgrids
8 | pub enum DatumParamDefn {
9 | ToWGS84_0,
10 | ToWGS84_3(f64, f64, f64),
11 | ToWGS84_7(f64, f64, f64, f64, f64, f64, f64),
12 | NadGrids(&'static str),
13 | }
14 |
15 | pub struct DatumDefn {
16 | pub id: &'static str,
17 | pub params: DatumParamDefn,
18 | pub ellps: &'static EllipsoidDefn,
19 | //pub comment: &'static str,
20 | }
21 |
22 | //#[rustfmt::skip]
23 | pub mod constants {
24 | use super::*;
25 |
26 | macro_rules! nadgrids {
27 | ($grids:expr) => {
28 | DatumParamDefn::NadGrids($grids)
29 | };
30 | }
31 |
32 | macro_rules! towgs84 {
33 | ($x:expr, $y:expr, $z:expr) => {
34 | DatumParamDefn::ToWGS84_3($x, $y, $z)
35 | };
36 | ($x:expr, $y:expr, $z:expr, $rx:expr, $ry:expr, $rz:expr, $s:expr) => {
37 | DatumParamDefn::ToWGS84_7($x, $y, $z, $rx, $ry, $rz, $s)
38 | };
39 | () => {
40 | DatumParamDefn::ToWGS84_0
41 | };
42 | }
43 |
44 | macro_rules! datum {
45 | ($name:ident, $id:expr, $params:expr, $ellps:ident, $c:expr $(,)?) => {
46 | pub(crate) const $name: DatumDefn = DatumDefn {
47 | id: $id,
48 | params: $params,
49 | ellps: &ellps::$ellps,
50 | //comment: $c,
51 | };
52 | };
53 | }
54 |
55 | // ---------------------------
56 | //
57 | // Datum definitions
58 | //
59 | // ---------------------------
60 | datum!(WGS84, "WGS84", towgs84!(), WGS84, "");
61 | datum!(
62 | GGRS87,
63 | "GGRS87",
64 | towgs84!(-199.87, 74.79, 246.62),
65 | GRS80,
66 | "Greek_Geodetic_Reference_System_1987",
67 | );
68 | datum!(
69 | NAD83,
70 | "NAD83",
71 | towgs84!(),
72 | GRS80,
73 | "North_American_Datum_1983"
74 | );
75 | datum!(
76 | NAD27,
77 | "NAD27",
78 | nadgrids!("@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat"),
79 | CLRK66,
80 | "North_American_Datum_1927",
81 | );
82 | // defn is "nadgrids=@BETA2007.gsb" in proj 9
83 | datum!(
84 | POTSDAM,
85 | "potsdam",
86 | towgs84!(598.1, 73.7, 418.2, 0.202, 0.045, -2.455, 6.7),
87 | BESSEL,
88 | "Potsdam Rauenberg 1950 DHDN",
89 | );
90 | datum!(
91 | CARTHAGE,
92 | "carthage",
93 | towgs84!(-263.0, 6.0, 431.0),
94 | CLRK80IGN,
95 | "Carthage 1934 Tunisia",
96 | );
97 | datum!(
98 | HERMANNSKOGEL,
99 | "hermannskogel",
100 | towgs84!(577.326, 90.129, 463.919, 5.137, 1.474, 5.297, 2.4232),
101 | BESSEL,
102 | "Hermannskogel",
103 | );
104 | datum!(
105 | IRE65,
106 | "ire65",
107 | towgs84!(482.530, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15),
108 | MOD_AIRY,
109 | "Ireland 1965",
110 | );
111 | datum!(
112 | NZGD49,
113 | "nzgd49",
114 | towgs84!(59.47, -5.04, 187.44, 0.47, -0.1, 1.024, -4.5993),
115 | INTL,
116 | "New Zealand Geodetic Datum 1949",
117 | );
118 | datum!(
119 | OSGB36,
120 | "OSGB36",
121 | towgs84!(446.448, -125.157, 542.060, 0.1502, 0.2470, 0.8421, -20.4894),
122 | AIRY,
123 | "Airy 1830",
124 | );
125 | // Added from proj4js
126 | datum!(
127 | CH1903,
128 | "ch1903",
129 | towgs84!(674.374, 15.056, 405.346),
130 | BESSEL,
131 | "swiss",
132 | );
133 | datum!(
134 | OSNI52,
135 | "osni52",
136 | towgs84!(482.530, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15),
137 | AIRY,
138 | "Irish National",
139 | );
140 | datum!(
141 | RASSADIRAN,
142 | "rassadiran",
143 | towgs84!(-133.63, -157.5, -158.62),
144 | INTL,
145 | "Rassadiran",
146 | );
147 | datum!(
148 | S_JTSK,
149 | "s_jtsk",
150 | towgs84!(589., 76., 480.),
151 | BESSEL,
152 | "S-JTSK (Ferro)",
153 | );
154 | datum!(
155 | BEDUARAM,
156 | "beduaram",
157 | towgs84!(-106., -87., 188.),
158 | CLRK80,
159 | "Beduaram",
160 | );
161 | datum!(
162 | GUNUNG_SEGARA,
163 | "gunung_segara",
164 | towgs84!(-403., 684., 41.),
165 | BESSEL,
166 | "Gunung Segara Jakarta",
167 | );
168 | datum!(
169 | RNB72,
170 | "rnb72",
171 | towgs84!(106.869, -52.2978, 103.724, -0.33657, 0.456955, -1.84218, 1.),
172 | INTL,
173 | "Reseau National Belge 1972",
174 | );
175 |
176 | /// Static datums table
177 | pub(super) const DATUMS: [&DatumDefn; 17] = [
178 | &WGS84,
179 | &GGRS87,
180 | &NAD83,
181 | &NAD27,
182 | &POTSDAM,
183 | &CARTHAGE,
184 | &HERMANNSKOGEL,
185 | &IRE65,
186 | &NZGD49,
187 | &OSGB36,
188 | &CH1903,
189 | &OSNI52,
190 | &RASSADIRAN,
191 | &S_JTSK,
192 | &BEDUARAM,
193 | &GUNUNG_SEGARA,
194 | &RNB72,
195 | ];
196 | }
197 |
198 | /// Return the datum definition
199 | pub fn find_datum(name: &str) -> Option<&DatumDefn> {
200 | constants::DATUMS
201 | .iter()
202 | .find(|d| d.id.eq_ignore_ascii_case(name))
203 | .copied()
204 | }
205 |
--------------------------------------------------------------------------------
/proj4rs/src/projections/cea.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Equal Area Cylindrical Cylindrical (Spherical)
3 | //!
4 | //! Parameters:
5 | //!
6 | //! * lat_ts:
7 | //!
8 | //! See https://proj.org/en/stable/operations/projections/cea.html
9 | //!
10 |
11 | use crate::errors::{Error, Result};
12 | use crate::math::consts::{EPS_10, FRAC_PI_2};
13 | use crate::math::{authlat, authset, qsfn};
14 | use crate::parameters::ParamList;
15 | use crate::proj::ProjData;
16 |
17 | super::projection! { cea }
18 |
19 | #[derive(Debug, Clone)]
20 | pub(crate) enum Projection {
21 | Sph {
22 | k0: f64,
23 | },
24 | Ell {
25 | k0: f64,
26 | e: f64,
27 | one_es: f64,
28 | qp: f64,
29 | apa: (f64, f64, f64),
30 | },
31 | }
32 |
33 | impl Projection {
34 | pub fn cea(p: &mut ProjData, params: &ParamList) -> Result {
35 | let (mut k0, t) = match params.try_angular_value("lat_ts")? {
36 | Some(t) => {
37 | let k0 = t.cos();
38 | if k0 < 0. {
39 | return Err(Error::InvalidParameterValue(
40 | "Invalid value for lat_ts: |lat_ts| should be <= 90\u{00b0}",
41 | ));
42 | }
43 | (k0, t)
44 | }
45 | None => (p.k0, 0.0),
46 | };
47 |
48 | Ok(if p.ellps.is_ellipsoid() {
49 | let sint = t.sin();
50 | k0 /= (1. - p.ellps.es * sint * sint).sqrt();
51 | Self::Ell {
52 | k0,
53 | e: p.ellps.e,
54 | one_es: p.ellps.one_es,
55 | qp: qsfn(1., p.ellps.e, p.ellps.one_es),
56 | apa: authset(p.ellps.es),
57 | }
58 | } else {
59 | Self::Sph { k0 }
60 | })
61 | }
62 |
63 | pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
64 | match self {
65 | Self::Ell { k0, e, one_es, .. } => {
66 | Ok((k0 * lam, 0.5 * qsfn(phi.sin(), *e, *one_es) / k0, z))
67 | }
68 | Self::Sph { k0 } => Ok((k0 * lam, phi.sin() / k0, z)),
69 | }
70 | }
71 |
72 | pub fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
73 | match self {
74 | Self::Ell { k0, qp, apa, .. } => {
75 | Ok((x / k0, authlat((2. * y * k0 / qp).asin(), *apa), z))
76 | }
77 | Self::Sph { k0 } => {
78 | let y = y * k0;
79 | let t = y.abs();
80 | if t - EPS_10 > 1. {
81 | Err(Error::CoordTransOutsideProjectionDomain)
82 | } else {
83 | let phi = if t >= 1. {
84 | if y < 0. {
85 | -FRAC_PI_2
86 | } else {
87 | FRAC_PI_2
88 | }
89 | } else {
90 | y.asin()
91 | };
92 | Ok((x / k0, phi, z))
93 | }
94 | }
95 | }
96 | }
97 |
98 | pub const fn has_inverse() -> bool {
99 | true
100 | }
101 |
102 | pub const fn has_forward() -> bool {
103 | true
104 | }
105 | }
106 |
107 | //============
108 | // Tests
109 | //============
110 |
111 | #[cfg(test)]
112 | mod tests {
113 | use crate::proj::Proj;
114 | use crate::tests::utils::{test_proj_forward, test_proj_inverse};
115 |
116 | // lat_ts_0: Lambert cylindrical equal-area
117 |
118 | #[test]
119 | fn proj_cea_lat_ts_0_e() {
120 | let p = Proj::from_proj_string("+proj=cea +ellps=GRS80").unwrap();
121 |
122 | // NOTE proj 9 use GRS80 as default ellipsoid
123 |
124 | println!("{:#?}", p.projection());
125 |
126 | let inputs = [(
127 | (12.09, 47.73, 0.),
128 | (1345852.643690677360, 4699614.507911851630, 0.),
129 | )];
130 |
131 | test_proj_forward(&p, &inputs, 1e-8);
132 | test_proj_inverse(&p, &inputs, 1e-8);
133 | }
134 |
135 | #[test]
136 | fn proj_cea_lat_ts_0_s() {
137 | let p = Proj::from_proj_string("+proj=cea +R_a +ellps=GRS80").unwrap();
138 |
139 | println!("{:#?}", p.projection());
140 |
141 | let inputs = [(
142 | (12.09, 47.73, 0.),
143 | (1343596.449131145841, 4711803.232695742510, 0.),
144 | )];
145 |
146 | test_proj_forward(&p, &inputs, 1e-8);
147 | test_proj_inverse(&p, &inputs, 1e-8);
148 | }
149 |
150 | // lat_ts_30: Berhmann
151 |
152 | #[test]
153 | fn proj_cea_lat_ts_30_e() {
154 | let p = Proj::from_proj_string("+proj=cea +lat_ts=30 +ellps=GRS80").unwrap();
155 |
156 | // NOTE proj 9 use GRS80 as default ellipsoid
157 |
158 | println!("{:#?}", p.projection());
159 |
160 | let inputs = [(
161 | (12.09, 47.73, 0.),
162 | (1166519.128238123609, 5422104.495923101902, 0.),
163 | )];
164 |
165 | test_proj_forward(&p, &inputs, 1e-8);
166 | test_proj_inverse(&p, &inputs, 1e-8);
167 | }
168 |
169 | #[test]
170 | fn proj_cea_lat_ts_30_s() {
171 | let p = Proj::from_proj_string("+proj=cea +lat_ts=30 +R_a +ellps=GRS80").unwrap();
172 |
173 | println!("{:#?}", p.projection());
174 |
175 | let inputs = [(
176 | (12.09, 47.73, 0.),
177 | (1163588.657382138772, 5440721.729530871846, 0.),
178 | )];
179 |
180 | test_proj_forward(&p, &inputs, 1e-8);
181 | test_proj_inverse(&p, &inputs, 1e-8);
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/proj4rs/src/projections/moll.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Mollweide Pseudocylindrical and derivatives
3 | //!
4 | //! ref:
5 | //!
6 | //! moll: "Mollweide" "\n\tPCyl., Sph.";
7 | //! wag4: "Wagner IV" "\n\tPCyl., Sph.";
8 | //! wag5: "Wagner V" "\n\tPCyl., Sph.";
9 | //!
10 | use crate::ellps::Ellipsoid;
11 | #[allow(unused_imports)]
12 | use crate::errors::{Error, Result};
13 | use crate::math::{
14 | aasin,
15 | consts::{FRAC_PI_2, PI, TAU},
16 | };
17 | use crate::parameters::ParamList;
18 | use crate::proj::ProjData;
19 |
20 | // Projection stub
21 | super::projection! { moll, wag4, wag5 }
22 |
23 | #[derive(Debug, Clone)]
24 | pub(crate) struct Projection {
25 | c_x: f64,
26 | c_y: f64,
27 | c_p: f64,
28 | }
29 |
30 | impl Projection {
31 | pub fn moll(p: &mut ProjData, _: &ParamList) -> Result {
32 | Self::new(p, FRAC_PI_2)
33 | }
34 |
35 | pub fn wag4(p: &mut ProjData, _: &ParamList) -> Result {
36 | Self::new(p, PI / 3.)
37 | }
38 |
39 | pub fn wag5(p: &mut ProjData, _: &ParamList) -> Result {
40 | // Map from sphere
41 | p.ellps = Ellipsoid::sphere(p.ellps.a)?;
42 |
43 | Ok(Self {
44 | c_x: 0.90977,
45 | c_y: 1.65014,
46 | c_p: 3.00896,
47 | })
48 | }
49 |
50 | fn new(p: &mut ProjData, pp: f64) -> Result {
51 | // Map from sphere
52 | p.ellps = Ellipsoid::sphere(p.ellps.a)?;
53 |
54 | let p2 = pp + pp;
55 | let sp = pp.sin();
56 | let c_p = p2 + p2.sin();
57 | let r = (TAU * sp / c_p).sqrt();
58 |
59 | Ok(Self {
60 | c_x: 2. * r / PI,
61 | c_y: r / sp,
62 | c_p,
63 | })
64 | }
65 |
66 | #[inline(always)]
67 | pub fn forward(&self, lam: f64, mut phi: f64, z: f64) -> Result<(f64, f64, f64)> {
68 | const NITER: isize = 10;
69 | const TOL: f64 = 1e-7;
70 |
71 | let k = self.c_p * phi.sin();
72 | let mut i = NITER;
73 | while i > 0 {
74 | let v = (phi + phi.sin() - k) / (1. + phi.cos());
75 | phi -= v;
76 | if v.abs() < TOL {
77 | break;
78 | }
79 | i -= 1;
80 | }
81 | if i == 0 {
82 | phi = FRAC_PI_2 * phi.signum();
83 | } else {
84 | phi *= 0.5;
85 | }
86 | Ok((self.c_x * lam * phi.cos(), self.c_y * phi.sin(), z))
87 | }
88 |
89 | #[cfg(not(feature = "proj4js-compat"))]
90 | #[inline(always)]
91 | pub fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
92 | let mut phi = aasin(y / self.c_y)?;
93 | let lam = x / (self.c_x * phi.cos());
94 | if lam.abs() < PI {
95 | phi += phi;
96 | phi = aasin((phi + phi.sin()) / self.c_p)?;
97 | Ok((lam, phi, z))
98 | } else {
99 | Err(Error::CoordinateOutOfRange)
100 | }
101 | }
102 |
103 | /// This is an infaillible version of Mollwey
104 | /// While proj version is faillible
105 | #[cfg(feature = "proj4js-compat")]
106 | #[inline(always)]
107 | pub fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
108 | let mut phi = aasin(y / self.c_y)?;
109 | let mut lam = x / (self.c_x * phi.cos());
110 | if lam.abs() > PI {
111 | lam = PI * lam.signum();
112 | }
113 | phi += phi;
114 | phi = aasin((phi + phi.sin()) / self.c_p)?;
115 | Ok((lam, phi, z))
116 | }
117 |
118 | pub const fn has_inverse() -> bool {
119 | true
120 | }
121 |
122 | pub const fn has_forward() -> bool {
123 | true
124 | }
125 | }
126 |
127 | #[cfg(test)]
128 | mod tests {
129 | use crate::math::consts::EPS_10;
130 | use crate::proj::Proj;
131 | use crate::tests::utils::{test_proj_forward, test_proj_inverse};
132 |
133 | #[test]
134 | fn proj_moll() {
135 | let p = Proj::from_proj_string("+proj=moll").unwrap();
136 |
137 | println!("{:#?}", p.projection());
138 |
139 | let inputs = [
140 | ((2., 1., 0.), (200426.67539284358, 123642.46137843542, 0.)),
141 | ((2., -1., 0.), (200426.67539284358, -123642.46137843542, 0.)),
142 | ((-2., 1., 0.), (-200426.67539284358, 123642.46137843542, 0.)),
143 | (
144 | (-2., -1., 0.),
145 | (-200426.67539284358, -123642.46137843542, 0.),
146 | ),
147 | ];
148 |
149 | test_proj_forward(&p, &inputs, EPS_10);
150 | test_proj_inverse(&p, &inputs, EPS_10);
151 | }
152 |
153 | #[test]
154 | fn proj_wag4() {
155 | let p = Proj::from_proj_string("+proj=wag4").unwrap();
156 |
157 | println!("{:#?}", p.projection());
158 |
159 | let inputs = [
160 | ((2., 1., 0.), (192142.59162431932, 128974.11846682805, 0.)),
161 | ((2., -1., 0.), (192142.59162431932, -128974.11846682805, 0.)),
162 | ((-2., 1., 0.), (-192142.59162431932, 128974.11846682805, 0.)),
163 | (
164 | (-2., -1., 0.),
165 | (-192142.59162431932, -128974.11846682805, 0.),
166 | ),
167 | ];
168 |
169 | test_proj_forward(&p, &inputs, EPS_10);
170 | test_proj_inverse(&p, &inputs, EPS_10);
171 | }
172 |
173 | #[test]
174 | fn proj_wag5() {
175 | let p = Proj::from_proj_string("+proj=wag5").unwrap();
176 |
177 | println!("{:#?}", p.projection());
178 |
179 | let inputs = [
180 | ((2., 1., 0.), (202532.80926341165, 138177.98447111444, 0.)),
181 | ((2., -1., 0.), (202532.80926341165, -138177.98447111444, 0.)),
182 | ((-2., 1., 0.), (-202532.80926341165, 138177.98447111444, 0.)),
183 | (
184 | (-2., -1., 0.),
185 | (-202532.80926341165, -138177.98447111444, 0.),
186 | ),
187 | ];
188 |
189 | test_proj_forward(&p, &inputs, EPS_10);
190 | test_proj_inverse(&p, &inputs, EPS_10);
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/js/ol-proj4rs-demo-app/vector-projections.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vector Projections
6 |
7 |
8 |
9 |
10 |
11 |
12 | Vector Projections
13 |
14 |
29 |
30 | This example shows a GeoJSON layer that is well converted between various projections.
31 |
32 |
33 |
34 |
35 | proj4 OpenLayers
36 |
37 | import Map from 'ol/Map.js' ;import TileGrid from 'ol/tilegrid/TileGrid.js' ;
38 | import View from 'ol/View.js' ;
39 | import proj4 from 'proj4' ;
40 | import {getCenter} from 'ol/extent.js' ;
41 | import {get as getProjection} from 'ol/proj.js' ;
42 | import {register} from 'ol/proj/proj4.js' ;
43 | import GeoJSON from 'ol/format/GeoJSON.js' ;
44 | import Graticule from 'ol/layer/Graticule.js' ;
45 | import VectorLayer from 'ol/layer/Vector.js' ;
46 | import VectorSource from 'ol/source/Vector.js' ;
47 | import {Fill, Style} from 'ol/style.js' ;
48 | ...
49 |
50 |
51 | proj4rs
52 |
53 | import Map from 'ol/Map.js' ;import TileGrid from 'ol/tilegrid/TileGrid.js' ;
54 | import View from 'ol/View.js' ;
55 | import {proj4} from 'proj4rs/proj4.js' ;
56 | import {getCenter} from 'ol/extent.js' ;
57 | import {get as getProjection} from 'ol/proj.js' ;
58 | import {register} from 'ol/proj/proj4.js' ;
59 | import GeoJSON from 'ol/format/GeoJSON.js' ;
60 | import Graticule from 'ol/layer/Graticule.js' ;
61 | import VectorLayer from 'ol/layer/Vector.js' ;
62 | import VectorSource from 'ol/source/Vector.js' ;
63 | import {Fill, Style} from 'ol/style.js' ;
64 | ...
65 |
66 |
67 |
68 |
69 | Home
70 | Single Image WMS
71 | Image reprojection
72 | Raster reprojection
73 | Sphere Mollweide
74 | Vector projections
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/proj4rs/src/nadgrids/files/ntv2.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Nadgrid parser
3 | //!
4 | use crate::errors::{Error, Result};
5 | use crate::log::trace;
6 | use crate::math::consts::SEC_TO_RAD;
7 | use crate::nadgrids::grid::{Grid, GridId, Lp, REL_TOLERANCE_HGRIDSHIFT};
8 | use crate::nadgrids::header::error_str::*;
9 | use crate::nadgrids::header::{Endianness, Header};
10 | use crate::nadgrids::Catalog;
11 | use std::io::Read;
12 |
13 | const NTV2_HEADER_SIZE: usize = 11 * 16;
14 |
15 | /// Ntv2 reader
16 | pub(super) fn read_ntv2(catalog: &Catalog, key: &str, read: &mut R) -> Result<()> {
17 | let mut head = Header::::new();
18 |
19 | trace!("Reading ntv2 {}", key);
20 |
21 | // Read overview header
22 | head.read(read)?;
23 | // Check endianness
24 | head.endian = if head.get_u8(8) == 11 {
25 | Endianness::native()
26 | } else {
27 | Endianness::other()
28 | };
29 |
30 | let nsubgrids = head.get_u32(40) as usize;
31 |
32 | trace!("Reading ntv2 {} subgrids {}", key, nsubgrids);
33 |
34 | // Read subsequent grids
35 | (0..nsubgrids).try_for_each(|_| read_ntv2_grid(catalog, key, head.read(read)?, read))
36 | }
37 |
38 | /// Read ntv2 grid data
39 | fn read_ntv2_grid(
40 | catalog: &Catalog,
41 | key: &str,
42 | head: &Header,
43 | read: &mut R,
44 | ) -> Result<()> {
45 | match head.get_str(0, 8) {
46 | Ok("SUB_NAME") => Ok(()),
47 | _ => Err(Error::InvalidNtv2GridFormat(ERR_INVALID_HEADER)),
48 | }?;
49 |
50 | let id = head.get_id(8);
51 | let mut lineage = head.get_id(24);
52 | if lineage.as_str().trim() == "NONE" {
53 | lineage = GridId::root();
54 | }
55 |
56 | let mut ll = Lp {
57 | lam: -head.get_f64(120), // W_LONG
58 | phi: head.get_f64(72), // S_LAT
59 | };
60 |
61 | let mut ur = Lp {
62 | lam: -head.get_f64(104), // E_LONG
63 | phi: head.get_f64(88), // N_LAT
64 | };
65 |
66 | let mut del = Lp {
67 | lam: head.get_f64(152), // longitude interval
68 | phi: head.get_f64(136), // latitude interval
69 | };
70 |
71 | let lim = Lp {
72 | lam: (((ur.lam - ll.lam).abs() / del.lam + 0.5) + 1.).floor(),
73 | phi: (((ur.phi - ll.phi).abs() / del.phi + 0.5) + 1.).floor(),
74 | };
75 |
76 | // units are in seconds of degree.
77 | ll.lam *= SEC_TO_RAD;
78 | ll.phi *= SEC_TO_RAD;
79 | ur.lam *= SEC_TO_RAD;
80 | ur.phi *= SEC_TO_RAD;
81 | del.lam *= SEC_TO_RAD;
82 | del.phi *= SEC_TO_RAD;
83 |
84 | // Read matrix data
85 | let nrows = lim.phi as usize;
86 | let rowsize = lim.lam as usize;
87 |
88 | let gs_count = head.get_u32(168) as usize;
89 | if gs_count != nrows * rowsize {
90 | return Err(Error::InvalidNtv2GridFormat(ERR_GSCOUNT_NOT_MATCHING));
91 | }
92 |
93 | // Read grid data
94 | trace!(
95 | "Reading data for grid {}:{}:{}",
96 | key,
97 | id.as_str(),
98 | lineage.as_str()
99 | );
100 |
101 | let mut buf = head.rebind::<16>();
102 | let mut cvs: Vec = (0..gs_count)
103 | .map(|_| {
104 | buf.read(read)?;
105 | // NOTE: phi and lam are inverted
106 | Ok(Lp {
107 | phi: SEC_TO_RAD * (buf.get_f32(0) as f64),
108 | lam: -(SEC_TO_RAD * (buf.get_f32(4) as f64)), // NOTE: Compensate NT convention
109 | })
110 | })
111 | .collect::>>()?;
112 |
113 | // See https://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/notice/NT111_V1_HARMEL_TransfoNTF-RGF93_FormatGrilleNTV2.pdf
114 |
115 | // In proj4, rows are stored in reverse order
116 | for i in 0..nrows {
117 | let offs = i * rowsize;
118 | cvs[offs..(offs + rowsize)].reverse();
119 | }
120 |
121 | let epsilon = (del.lam.abs() + del.phi.abs()) * REL_TOLERANCE_HGRIDSHIFT;
122 |
123 | catalog.add_grid(
124 | key.into(),
125 | Grid {
126 | id,
127 | lineage,
128 | ll,
129 | ur,
130 | del,
131 | lim,
132 | epsilon,
133 | cvs: cvs.into_boxed_slice(),
134 | },
135 | )
136 | }
137 |
138 | #[cfg(test)]
139 | mod tests {
140 | use super::*;
141 | use crate::nadgrids::Catalog;
142 | use crate::tests::setup;
143 | use std::env;
144 | use std::fs::File;
145 | use std::io::BufReader;
146 | use std::path::Path;
147 |
148 | macro_rules! fixture {
149 | ($name:expr) => {
150 | Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
151 | .join("fixtures")
152 | .as_path()
153 | .join($name)
154 | .as_path()
155 | };
156 | }
157 |
158 | macro_rules! load_ntv2 {
159 | ($cat:expr, $name:expr) => {
160 | // Use a BufReader or efficiency
161 | let file = File::open(fixture!($name)).unwrap();
162 | let mut read = BufReader::new(file);
163 | read_ntv2($cat, $name, &mut read).unwrap();
164 | };
165 | }
166 |
167 | #[test]
168 | fn ntv2_100800401_gsb() {
169 | setup();
170 |
171 | let catalog = Catalog::default();
172 | load_ntv2!(&catalog, "100800401.gsb");
173 |
174 | let grids = catalog.find("100800401.gsb").unwrap().collect::>();
175 | assert_eq!(grids.len(), 1);
176 |
177 | let grid = grids[0];
178 | assert!(grid.is_root());
179 | assert_eq!(grid.id.as_str(), "0INT2GRS");
180 | assert_eq!(grid.cvs.len(), 1591);
181 | }
182 |
183 | #[test]
184 | #[cfg(feature = "local_tests")]
185 | fn ntv2_bwta2017_gsb() {
186 | setup();
187 |
188 | let catalog = Catalog::default();
189 | load_ntv2!(&catalog, "BWTA2017.gsb");
190 |
191 | let grids = catalog.find("BWTA2017.gsb").unwrap().collect::>();
192 | assert_eq!(grids.len(), 1);
193 |
194 | let grid = grids[0];
195 | assert!(grid.is_root());
196 | assert_eq!(grid.id.as_str(), "DHDN90 ");
197 | assert_eq!(grid.cvs.len(), 24514459);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/proj4rs/src/projections/merc.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Pseudo Mercator
3 | //!
4 | //! merc: "Mercator" "\n\tCyl, Sph&Ell\n\tlat_ts="
5 | //! webmerc: "Web Mercator / Pseudo Mercator" "\n\tCyl, Ell\n\t"
6 | //!
7 |
8 | // Projection stub
9 | super::projection! { merc, webmerc }
10 |
11 | use crate::errors::{Error, Result};
12 | use crate::math::{
13 | asinh,
14 | consts::{EPS_10, FRAC_PI_2},
15 | msfn, phi2,
16 | };
17 | use crate::parameters::ParamList;
18 | use crate::proj::ProjData;
19 |
20 | #[derive(Debug, Clone)]
21 | pub(crate) struct Projection {
22 | is_ellps: bool,
23 | k0: f64,
24 | e: f64,
25 | }
26 |
27 | impl Projection {
28 | pub fn merc(p: &mut ProjData, params: &ParamList) -> Result {
29 | let phits: Option = params.try_angular_value("lat_ts")?;
30 | if let Some(phits) = phits {
31 | if phits >= FRAC_PI_2 {
32 | return Err(Error::InvalidParameterValue(
33 | "lat_ts larger than 90 degrees",
34 | ));
35 | }
36 | }
37 |
38 | if p.ellps.is_ellipsoid() {
39 | if let Some(phits) = phits {
40 | p.k0 = msfn(phits.sin(), phits.cos(), p.ellps.es);
41 | }
42 | } else if let Some(phits) = phits {
43 | p.k0 = phits.cos();
44 | }
45 |
46 | Ok(Self {
47 | is_ellps: p.ellps.is_ellipsoid(),
48 | k0: p.k0,
49 | e: p.ellps.e,
50 | })
51 | }
52 |
53 | pub fn webmerc(p: &mut ProjData, _params: &ParamList) -> Result {
54 | p.k0 = 1.0;
55 | Ok(Self {
56 | is_ellps: false,
57 | k0: p.k0,
58 | e: p.ellps.e,
59 | })
60 | }
61 |
62 | pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
63 | if (phi.abs() - FRAC_PI_2).abs() <= EPS_10 {
64 | return Err(Error::ToleranceConditionError);
65 | }
66 | if self.is_ellps {
67 | let (sphi, cphi) = phi.sin_cos();
68 | Ok((
69 | self.k0 * lam,
70 | self.k0 * (asinh(sphi / cphi) - self.e * (self.e * sphi).atanh()),
71 | z,
72 | ))
73 | } else {
74 | Ok((self.k0 * lam, self.k0 * asinh(phi.tan()), z))
75 | }
76 | }
77 |
78 | pub fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
79 | if self.is_ellps {
80 | Ok((x / self.k0, phi2((-y / self.k0).exp(), self.e)?, z))
81 | } else {
82 | Ok((x / self.k0, (y / self.k0).sinh().atan(), z))
83 | }
84 | }
85 |
86 | pub const fn has_inverse() -> bool {
87 | true
88 | }
89 |
90 | pub const fn has_forward() -> bool {
91 | true
92 | }
93 | }
94 |
95 | #[cfg(test)]
96 | mod tests {
97 | use crate::math::consts::EPS_10;
98 | use crate::proj::Proj;
99 | use crate::tests::utils::{test_proj_forward, test_proj_inverse};
100 |
101 | #[test]
102 | fn proj_merc_merc_ellps() {
103 | let p = Proj::from_proj_string("+proj=merc +ellps=GRS80").unwrap();
104 |
105 | println!("{:#?}", p.data());
106 | println!("{:#?}", p.projection());
107 |
108 | // XXX: this differs in y from Proj output: 110579.96521824962
109 | // because of asinh definition: using the same asinh definition
110 | // leads to same results up to 1e-11m.
111 | let inputs = [
112 | ((2., 1., 0.), (222638.98158654713, 110579.96521825077, 0.)),
113 | ((2., -1., 0.), (222638.98158654713, -110579.96521825077, 0.)),
114 | ((-2., 1., 0.), (-222638.98158654713, 110579.96521825077, 0.)),
115 | (
116 | (-2., -1., 0.),
117 | (-222638.98158654713, -110579.96521825077, 0.),
118 | ),
119 | ];
120 |
121 | test_proj_forward(&p, &inputs, EPS_10);
122 | test_proj_inverse(&p, &inputs, EPS_10);
123 | }
124 |
125 | #[test]
126 | fn proj_merc_merc_sph() {
127 | let p = Proj::from_proj_string("+proj=merc +R=6400000").unwrap();
128 |
129 | println!("{:#?}", p.projection());
130 |
131 | let inputs = [
132 | ((2., 1., 0.), (223402.14425527418, 111706.74357494547, 0.)),
133 | ((2., -1., 0.), (223402.14425527418, -111706.74357494547, 0.)),
134 | ((-2., 1., 0.), (-223402.14425527418, 111706.74357494547, 0.)),
135 | (
136 | (-2., -1., 0.),
137 | (-223402.14425527418, -111706.74357494547, 0.),
138 | ),
139 | ];
140 |
141 | test_proj_forward(&p, &inputs, EPS_10);
142 | test_proj_inverse(&p, &inputs, EPS_10);
143 | }
144 |
145 | #[test]
146 | fn proj_merc_webmerc_ellps() {
147 | let p = Proj::from_proj_string("+proj=webmerc +ellps=GRS80").unwrap();
148 |
149 | println!("{:#?}", p.projection());
150 |
151 | let inputs = [
152 | ((2., 1., 0.), (222638.98158654713, 111325.14286638626, 0.)),
153 | ((2., -1., 0.), (222638.98158654713, -111325.14286638626, 0.)),
154 | ((-2., 1., 0.), (-222638.98158654713, 111325.14286638626, 0.)),
155 | (
156 | (-2., -1., 0.),
157 | (-222638.98158654713, -111325.14286638626, 0.),
158 | ),
159 | ];
160 |
161 | test_proj_forward(&p, &inputs, EPS_10);
162 | test_proj_inverse(&p, &inputs, EPS_10);
163 | }
164 |
165 | #[test]
166 | fn proj_merc_webmerc_sph() {
167 | let p = Proj::from_proj_string("+proj=webmerc +R=6400000").unwrap();
168 |
169 | println!("{:#?}", p.projection());
170 |
171 | let inputs = [
172 | ((2., 1., 0.), (223402.14425527418, 111706.74357494547, 0.)),
173 | ((2., -1., 0.), (223402.14425527418, -111706.74357494547, 0.)),
174 | ((-2., 1., 0.), (-223402.14425527418, 111706.74357494547, 0.)),
175 | (
176 | (-2., -1., 0.),
177 | (-223402.14425527418, -111706.74357494547, 0.),
178 | ),
179 | ];
180 |
181 | test_proj_forward(&p, &inputs, EPS_10);
182 | test_proj_inverse(&p, &inputs, EPS_10);
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/proj4rs/src/geocent.rs:
--------------------------------------------------------------------------------
1 | //!
2 | //! Geodetic to/from geocentrique conversion
3 | //!
4 | use crate::errors::{Error, Result};
5 | use crate::math::consts::{FRAC_PI_2, PI, TAU};
6 |
7 | const GENAU: f64 = 1.0e-12;
8 | const GENAU2: f64 = GENAU * GENAU;
9 | const MAXITER: usize = 30;
10 | const FRAC_PI_2_EPS: f64 = 1.001 * FRAC_PI_2;
11 |
12 | /// Convert geodetic coordinates to geocentric coordinatesa
13 | ///
14 | /// The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
15 | /// (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
16 | /// according to the current ellipsoid parameters.
17 | ///
18 | /// Latitude : Geodetic latitude in radians (input)
19 | /// Longitude : Geodetic longitude in radians (input)
20 | /// Height : Geodetic height, in meters (input)
21 | /// X : Calculated Geocentric X coordinate, in meters (output)
22 | /// Y : Calculated Geocentric Y coordinate, in meters (output)
23 | /// Z : Calculated Geocentric Z coordinate, in meters (output)
24 | ///
25 | /// This conversion converts geodetic coordinate values (longitude, latitude, elevation above ellipsoid)
26 | /// to their geocentric (X, Y, Z) representation, where the first axis (X) points from the Earth centre
27 | /// to the point of longitude=0, latitude=0, the second axis (Y) points from the
28 | /// Earth centre to the point of longitude=90, latitude=0 and the third axis (Z) points to the North pole
29 | ///
30 | pub fn geodetic_to_geocentric(x: f64, y: f64, z: f64, a: f64, es: f64) -> Result<(f64, f64, f64)> {
31 | let mut lon = x;
32 | let mut lat = y;
33 |
34 | if lat < -FRAC_PI_2 && lat > -FRAC_PI_2_EPS {
35 | lat = -FRAC_PI_2
36 | } else if lat > FRAC_PI_2 && lat < FRAC_PI_2_EPS {
37 | lat = FRAC_PI_2
38 | } else if !(-FRAC_PI_2..=FRAC_PI_2).contains(&lat) {
39 | return Err(Error::LatitudeOutOfRange);
40 | };
41 |
42 | if lon > PI {
43 | // TAU is 2PI
44 | lon -= TAU;
45 | }
46 |
47 | let (sin_lat, cos_lat) = lat.sin_cos();
48 | // Earth radius at location
49 | let rn = a / (1. - es * (sin_lat * sin_lat)).sqrt();
50 | Ok((
51 | (rn + z) * cos_lat * lon.cos(),
52 | (rn + z) * cos_lat * lon.sin(),
53 | ((rn * (1. - es)) + z) * sin_lat,
54 | ))
55 | }
56 |
57 | /// Convert geocentric coordinates to geodetic coordinates
58 | ///
59 | /// ### Reference...
60 | ///
61 | /// Wenzel, H.-G.(1985): Hochauflösende Kugelfunktionsmodelle für
62 | /// das Gravitationspotential der Erde. Wiss. Arb. Univ. Hannover
63 | /// Nr. 137, p. 130-131.
64 | ///
65 | /// Programmed by GGA- Leibniz-Institute of Applied Geophysics
66 | /// Stilleweg 2
67 | /// D-30655 Hannover
68 | /// Federal Republic of Germany
69 | /// Internet: www.gga-hannover.de
70 | ///
71 | /// Hannover, March 1999, April 2004.
72 | /// see also: comments in statements
73 | ///
74 | /// remarks:
75 | /// Mathematically exact and because of symmetry of rotation-ellipsoid,
76 | /// each point (X,Y,Z) has at least two solutions (Latitude1,Longitude1,Height1) and
77 | /// (Latitude2,Longitude2,Height2). Is point=(0.,0.,Z) (P=0.), so you get even
78 | /// four solutions,» every two symmetrical to the semi-minor axis.
79 | /// Here Height1 and Height2 have at least a difference in order of
80 | /// radius of curvature (e.g. (0,0,b)=> (90.,0.,0.) or (-90.,0.,-2b);
81 | /// (a+100.)*(sqrt(2.)/2.,sqrt(2.)/2.,0.) => (0.,45.,100.) or
82 | /// (0.,225.,-(2a+100.))).
83 | /// The algorithm always computes (Latitude,Longitude) with smallest |Height|.
84 | /// For normal computations, that means |Height|<10000.m, algorithm normally
85 | /// converges after to 2-3 steps!!!
86 | /// But if |Height| has the amount of length of ellipsoid's axis
87 | /// (e.g. -6300000.m),» algorithm needs about 15 steps.
88 | pub fn geocentric_to_geodetic(
89 | x: f64,
90 | y: f64,
91 | z: f64,
92 | a: f64,
93 | es: f64,
94 | b: f64,
95 | ) -> Result<(f64, f64, f64)> {
96 | let d2 = (x * x) + (y * y);
97 |
98 | // distance between semi-minor axis and location
99 | let p = d2.sqrt();
100 | // distance between center and location
101 | let rr = (d2 + z * z).sqrt();
102 |
103 | // if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
104 | // of ellipsoid (=center of mass), Latitude becomes PI/2
105 | let lon = if p / a < GENAU {
106 | if rr / a < GENAU {
107 | return Ok((0., FRAC_PI_2, -b));
108 | }
109 | 0.
110 | } else {
111 | y.atan2(x)
112 | };
113 |
114 | //--------------------------------------------------------------
115 | // Following iterative algorithm was developed by
116 | // Institut for Erdmessung", University of Hannover, July 1988.
117 | // Internet: www.ife.uni-hannover.de
118 | // Iterative computation of CPHI,SPHI and Height.
119 | // Iteration of CPHI and SPHI to 10**-12 radian resp.
120 | // 2*10**-7 arcsec.
121 | // --------------------------------------------------------------
122 | let ct = z / rr;
123 | let st = p / rr;
124 | let mut rx = 1.0 / (1.0 - es * (2.0 - es) * st * st).sqrt();
125 | let mut cphi0 = st * (1.0 - es) * rx;
126 | let mut sphi0 = ct * rx;
127 | let (mut rk, mut rn, mut cphi, mut sphi, mut sdphi, mut height);
128 |
129 | // loop to find sin(Latitude) resp. Latitude
130 | // until |sin(Latitude(iter)-Latitude(iter-1))| < genau
131 |
132 | // Note: using `for _ in 0..MAXITER { ... }` lead to compiler error
133 | // about unitialized variables
134 | let mut iter = 0;
135 | loop {
136 | iter += 1;
137 | rn = a / (1.0 - es * sphi0 * sphi0).sqrt();
138 | // ellipsoidal (geodetic) height
139 | height = p * cphi0 + z * sphi0 - rn * (1.0 - es * sphi0 * sphi0);
140 |
141 | // avoid zero division
142 | if (rn + height) == 0. {
143 | return Ok((lon, 0., height));
144 | }
145 |
146 | rk = es * rn / (rn + height);
147 | rx = 1.0 / (1.0 - rk * (2.0 - rk) * st * st).sqrt();
148 | cphi = st * (1.0 - rk) * rx;
149 | sphi = ct * rx;
150 | sdphi = sphi * cphi0 - cphi * sphi0;
151 | cphi0 = cphi;
152 | sphi0 = sphi;
153 |
154 | if sdphi * sdphi <= GENAU2 {
155 | break;
156 | }
157 |
158 | if iter >= MAXITER {
159 | break;
160 | }
161 | }
162 |
163 | // ellipsoidal (geodetic) latitude
164 | Ok((lon, sphi.atan2(cphi.abs()), height))
165 | }
166 |
--------------------------------------------------------------------------------