1use crate::authoring::*;
3
4fn fwd(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
8 let ellps = op.params.ellps(0);
9 let eps = ellps.second_eccentricity_squared();
10 let lat_0 = op.params.lat(0).to_radians();
11 let lon_0 = op.params.lon(0).to_radians();
12 let x_0 = op.params.x(0);
13 let y_0 = op.params.y(0);
14 let k_0 = op.params.k(0);
15
16 let mut successes = 0_usize;
17 let n = operands.len();
18 for i in 0..n {
19 let mut coord = operands.get_coord(i);
20
21 let lat = coord[1] + lat_0;
22 let (s, c) = lat.sin_cos();
23 let cc = c * c;
24 let ss = s * s;
25
26 let dlon = coord[0] - lon_0;
27 let oo = dlon * dlon;
28
29 #[allow(non_snake_case)]
30 let N = ellps.prime_vertical_radius_of_curvature(lat);
31 let z = eps * dlon.powi(3) * c.powi(5) / 6.;
32 let sd2 = (dlon / 2.).sin();
33
34 let theta_2 = (2. * s * c * sd2 * sd2).atan2(ss + cc * dlon.cos());
35
36 let sd = dlon.sin();
38 coord[0] = x_0 + k_0 * N * ((c * sd).atanh() + z * (1. + oo * (36. * cc - 29.) / 10.));
39
40 let m = ellps.meridian_latitude_to_distance(lat);
42 let znos4 = z * N * dlon * s / 4.;
43 let ecc = 4. * eps * cc;
44 coord[1] = y_0 + k_0 * (m + N * theta_2 + znos4 * (9. + ecc + oo * (20. * cc - 11.)));
45 operands.set_coord(i, &coord);
46 successes += 1;
47 }
48
49 successes
50}
51
52fn inv(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
56 let ellps = op.params.ellps(0);
57 let eps = ellps.second_eccentricity_squared();
58 let lat_0 = op.params.lat(0).to_radians();
59 let lon_0 = op.params.lon(0).to_radians();
60 let x_0 = op.params.x(0);
61 let y_0 = op.params.y(0);
62 let k_0 = op.params.k(0);
63
64 let mut successes = 0_usize;
65 let n = operands.len();
66 for i in 0..n {
67 let mut coord = operands.get_coord(i);
68 let lat = ellps.meridian_distance_to_latitude((coord[1] - y_0) / k_0);
71 let (s, c) = lat.sin_cos();
72 let t = s / c;
73 let cc = c * c;
74 #[allow(non_snake_case)]
75 let N = ellps.prime_vertical_radius_of_curvature(lat);
76 let x = (coord[0] - x_0) / (k_0 * N);
77 let xx = x * x;
78 let theta_4 = x.sinh().atan2(c);
79 let theta_5 = (t * theta_4.cos()).atan();
80
81 let xet = xx * xx * eps * t / 24.;
83 coord[1] = lat_0 + (1. + cc * eps) * (theta_5 - xet * (9. - 10. * cc)) - eps * cc * lat;
84
85 let approx = lon_0 + theta_4;
87 let coef = eps / 60. * xx * x * c;
88 coord[0] = approx - coef * (10. - 4. * xx / cc + xx * cc);
89 operands.set_coord(i, &coord);
90
91 successes += 1;
92 }
93
94 successes
95}
96
97#[rustfmt::skip]
100pub const GAMUT: [OpParameter; 7] = [
101 OpParameter::Flag { key: "inv" },
102 OpParameter::Text { key: "ellps", default: Some("GRS80") },
103
104 OpParameter::Real { key: "lat_0", default: Some(0_f64) },
105 OpParameter::Real { key: "lon_0", default: Some(0_f64) },
106 OpParameter::Real { key: "x_0", default: Some(0_f64) },
107 OpParameter::Real { key: "y_0", default: Some(0_f64) },
108
109 OpParameter::Real { key: "k_0", default: Some(1_f64) },
110];
111
112pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
113 Op::basic(parameters, InnerOp(fwd), Some(InnerOp(inv)), &GAMUT)
114}
115
116#[rustfmt::skip]
117pub const UTM_GAMUT: [OpParameter; 4] = [
118 OpParameter::Flag { key: "inv" },
119 OpParameter::Flag { key: "south" },
120 OpParameter::Text { key: "ellps", default: Some("GRS80") },
121 OpParameter::Natural { key: "zone", default: None },
122];
123
124pub fn utm(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
125 let def = ¶meters.instantiated_as;
126 let mut params = ParsedParameters::new(parameters, &UTM_GAMUT)?;
127
128 let zone = params.natural("zone")?;
130 if !(1..61).contains(&zone) {
131 return Err(Error::General(
132 "UTM: 'zone' must be an integer in the interval 1..60",
133 ));
134 }
135
136 params.real.insert("k_0", 0.9996);
138
139 params.real.insert("lon_0", -183. + 6. * zone as f64);
141
142 params.real.insert("lat_0", 0.);
144
145 params.real.insert("x_0", 500_000.);
147
148 params.real.insert("y_0", 0.);
150 if params.boolean("south") {
152 params.real.insert("y_0", 10_000_000.0);
153 }
154
155 let descriptor = OpDescriptor::new(def, InnerOp(fwd), Some(InnerOp(inv)));
156
157 Ok(Op {
158 descriptor,
159 params,
160 steps: None,
161 })
162}
163
164#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn btmerc() -> Result<(), Error> {
172 let mut ctx = Minimal::default();
173 let definition = "btmerc k_0=0.9996 lon_0=9 x_0=500000";
174 let op = ctx.op(definition)?;
175
176 #[rustfmt::skip]
179 let geo = [
180 Coor4D::geo( 55., 12., 0., 0.),
181 Coor4D::geo(-55., 12., 0., 0.),
182 Coor4D::geo( 55., -6., 0., 0.),
183 Coor4D::geo(-55., -6., 0., 0.)
184 ];
185
186 #[rustfmt::skip]
187 let projected = [
188 Coor4D::raw( 691_875.632_139_661, 6_098_907.825_005_012, 0., 0.),
189 Coor4D::raw( 691_875.632_139_661,-6_098_907.825_005_012, 0., 0.),
190 Coor4D::raw(-455_673.814_189_040, 6_198_246.671_090_279, 0., 0.),
191 Coor4D::raw(-455_673.814_189_040,-6_198_246.671_090_279, 0., 0.)
192 ];
193
194 let mut operands = geo;
195 ctx.apply(op, Fwd, &mut operands)?;
196 for i in 0..operands.len() {
197 assert!(operands[i].hypot2(&projected[i]) < 5e-3);
198 }
199
200 ctx.apply(op, Inv, &mut operands)?;
201 for i in 0..operands.len() {
202 assert!(operands[i].hypot2(&geo[i]) < 10e-8);
203 }
204 Ok(())
205 }
206
207 #[test]
208 fn butm() -> Result<(), Error> {
209 let mut ctx = Minimal::default();
210 let definition = "butm zone=32";
211 let op = ctx.op(definition)?;
212
213 #[rustfmt::skip]
216 let geo = [
217 Coor4D::geo( 55., 12., 0., 0.),
218 Coor4D::geo(-55., 12., 0., 0.),
219 Coor4D::geo( 55., -6., 0., 0.),
220 Coor4D::geo(-55., -6., 0., 0.)
221 ];
222
223 #[rustfmt::skip]
224 let projected = [
225 Coor4D::raw( 691_875.632_139_661, 6_098_907.825_005_012, 0., 0.),
226 Coor4D::raw( 691_875.632_139_661,-6_098_907.825_005_012, 0., 0.),
227 Coor4D::raw(-455_673.814_189_040, 6_198_246.671_090_279, 0., 0.),
228 Coor4D::raw(-455_673.814_189_040,-6_198_246.671_090_279, 0., 0.)
229 ];
230
231 let mut operands = geo;
232 ctx.apply(op, Fwd, &mut operands)?;
233 for i in 0..operands.len() {
234 assert!(operands[i].hypot2(&projected[i]) < 5e-3);
235 }
236
237 ctx.apply(op, Inv, &mut operands)?;
238 for i in 0..operands.len() {
239 assert!(operands[i].hypot2(&geo[i]) < 10e-8);
240 }
241 Ok(())
242 }
243
244 #[test]
245 fn butm_south() -> Result<(), Error> {
246 let mut ctx = Minimal::default();
247 let op = ctx.op("butm zone=32 south")?;
248
249 #[rustfmt::skip]
250 let geo = [
251 Coor4D::geo( 55., 12., 0., 0.),
252 Coor4D::geo(-55., 12., 0., 0.),
253 Coor4D::geo( 55., -6., 0., 0.),
254 Coor4D::geo(-55., -6., 0., 0.)
255 ];
256
257 #[rustfmt::skip]
258 let projected = [
259 Coor4D::raw( 691_875.632_139_661, 1e7+6_098_907.825_005_012, 0., 0.),
260 Coor4D::raw( 691_875.632_139_661, 1e7-6_098_907.825_005_012, 0., 0.),
261 Coor4D::raw(-455_673.814_189_040, 1e7+6_198_246.671_090_279, 0., 0.),
262 Coor4D::raw(-455_673.814_189_040, 1e7-6_198_246.671_090_279, 0., 0.)
263 ];
264
265 let mut operands = geo;
266 ctx.apply(op, Fwd, &mut operands)?;
267 for i in 0..operands.len() {
268 assert!(operands[i].hypot2(&projected[i]) < 5e-3);
269 }
270
271 ctx.apply(op, Inv, &mut operands)?;
272 for i in 0..operands.len() {
273 assert!(operands[i].hypot2(&geo[i]) < 10e-8);
274 }
275
276 Ok(())
277 }
278}