/src/PROJ/src/projections/krovak.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Project: PROJ |
3 | | * Purpose: Implementation of the krovak (Krovak) projection. |
4 | | * Definition: http://www.ihsenergy.com/epsg/guid7.html#1.4.3 |
5 | | * Author: Thomas Flemming, tf@ttqv.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2001, Thomas Flemming, tf@ttqv.com |
9 | | * |
10 | | * Permission is hereby granted, free of charge, to any person obtaining a |
11 | | * copy of this software and associated documentation files (the "Software"), |
12 | | * to deal in the Software without restriction, including without limitation |
13 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
14 | | * and/or sell copies of the Software, and to permit persons to whom the |
15 | | * Software is furnished to do so, subject to the following conditions: |
16 | | * |
17 | | * The above copyright notice and this permission notice shall be included |
18 | | * in all copies or substantial portions of the Software. |
19 | | * |
20 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
21 | | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
22 | | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
23 | | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
24 | | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
25 | | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
26 | | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
27 | | * SOFTWARE. |
28 | | ****************************************************************************** |
29 | | * A description of the (forward) projection is found in: |
30 | | * |
31 | | * Bohuslav Veverka, |
32 | | * |
33 | | * KROVAK’S PROJECTION AND ITS USE FOR THE |
34 | | * CZECH REPUBLIC AND THE SLOVAK REPUBLIC, |
35 | | * |
36 | | * 50 years of the Research Institute of |
37 | | * and the Slovak Republic Geodesy, Topography and Cartography |
38 | | * |
39 | | * which can be found via the Wayback Machine: |
40 | | * |
41 | | * https://web.archive.org/web/20150216143806/https://www.vugtk.cz/odis/sborniky/sb2005/Sbornik_50_let_VUGTK/Part_1-Scientific_Contribution/16-Veverka.pdf |
42 | | * |
43 | | * Further info, including the inverse projection, is given by EPSG: |
44 | | * |
45 | | * Guidance Note 7 part 2 |
46 | | * Coordinate Conversions and Transformations including Formulas |
47 | | * |
48 | | * http://www.iogp.org/pubs/373-07-2.pdf |
49 | | * |
50 | | * Variable names in this file mostly follows what is used in the |
51 | | * paper by Veverka. |
52 | | * |
53 | | * According to EPSG the full Krovak projection method should have |
54 | | * the following parameters. Within PROJ the azimuth, and pseudo |
55 | | * standard parallel are hardcoded in the algorithm and can't be |
56 | | * altered from outside. The others all have defaults to match the |
57 | | * common usage with Krovak projection. |
58 | | * |
59 | | * lat_0 = latitude of centre of the projection |
60 | | * |
61 | | * lon_0 = longitude of centre of the projection |
62 | | * |
63 | | * ** = azimuth (true) of the centre line passing through the |
64 | | * centre of the projection |
65 | | * |
66 | | * ** = latitude of pseudo standard parallel |
67 | | * |
68 | | * k = scale factor on the pseudo standard parallel |
69 | | * |
70 | | * x_0 = False Easting of the centre of the projection at the |
71 | | * apex of the cone |
72 | | * |
73 | | * y_0 = False Northing of the centre of the projection at |
74 | | * the apex of the cone |
75 | | * |
76 | | *****************************************************************************/ |
77 | | |
78 | | #include <errno.h> |
79 | | #include <math.h> |
80 | | |
81 | | #include <algorithm> |
82 | | |
83 | | #include "proj.h" |
84 | | #include "proj_internal.h" |
85 | | |
86 | | PROJ_HEAD(krovak, "Krovak") "\n\tPCyl, Ell"; |
87 | | PROJ_HEAD(mod_krovak, "Modified Krovak") "\n\tPCyl, Ell"; |
88 | | |
89 | 0 | #define EPS 1e-15 |
90 | 2.12k | #define UQ 1.04216856380474 /* DU(2, 59, 42, 42.69689) */ |
91 | | #define S0 \ |
92 | 133k | 1.37008346281555 /* Latitude of pseudo standard parallel 78deg 30'00" N */ |
93 | | /* Not sure at all of the appropriate number for MAX_ITER... */ |
94 | 0 | #define MAX_ITER 100 |
95 | | |
96 | | namespace { // anonymous namespace |
97 | | struct pj_krovak_data { |
98 | | double alpha; |
99 | | double k; |
100 | | double n; |
101 | | double rho0; |
102 | | double ad; |
103 | | bool easting_northing; // true, in default mode. false when using +czech |
104 | | bool modified; |
105 | | }; |
106 | | } // anonymous namespace |
107 | | |
108 | | namespace pj_modified_krovak { |
109 | | constexpr double X0 = 1089000.0; |
110 | | constexpr double Y0 = 654000.0; |
111 | | constexpr double C1 = 2.946529277E-02; |
112 | | constexpr double C2 = 2.515965696E-02; |
113 | | constexpr double C3 = 1.193845912E-07; |
114 | | constexpr double C4 = -4.668270147E-07; |
115 | | constexpr double C5 = 9.233980362E-12; |
116 | | constexpr double C6 = 1.523735715E-12; |
117 | | constexpr double C7 = 1.696780024E-18; |
118 | | constexpr double C8 = 4.408314235E-18; |
119 | | constexpr double C9 = -8.331083518E-24; |
120 | | constexpr double C10 = -3.689471323E-24; |
121 | | |
122 | | // Correction terms to be applied to regular Krovak to obtain Modified Krovak. |
123 | | // Note that Xr is a Southing in metres and Yr a Westing in metres, |
124 | | // and output (dX, dY) is a corrective term in (Southing, Westing) in metres |
125 | | // Reference: |
126 | | // https://www.cuzk.cz/Zememerictvi/Geodeticke-zaklady-na-uzemi-CR/GNSS/Nova-realizace-systemu-ETRS89-v-CR/Metodika-prevodu-ETRF2000-vs-S-JTSK-var2(101208).aspx |
127 | | static void mod_krovak_compute_dx_dy(const double Xr, const double Yr, |
128 | 85.2k | double &dX, double &dY) { |
129 | 85.2k | const double Xr2 = Xr * Xr; |
130 | 85.2k | const double Yr2 = Yr * Yr; |
131 | 85.2k | const double Xr4 = Xr2 * Xr2; |
132 | 85.2k | const double Yr4 = Yr2 * Yr2; |
133 | | |
134 | 85.2k | dX = C1 + C3 * Xr - C4 * Yr - 2 * C6 * Xr * Yr + C5 * (Xr2 - Yr2) + |
135 | 85.2k | C7 * Xr * (Xr2 - 3 * Yr2) - C8 * Yr * (3 * Xr2 - Yr2) + |
136 | 85.2k | 4 * C9 * Xr * Yr * (Xr2 - Yr2) + C10 * (Xr4 + Yr4 - 6 * Xr2 * Yr2); |
137 | 85.2k | dY = C2 + C3 * Yr + C4 * Xr + 2 * C5 * Xr * Yr + C6 * (Xr2 - Yr2) + |
138 | 85.2k | C8 * Xr * (Xr2 - 3 * Yr2) + C7 * Yr * (3 * Xr2 - Yr2) - |
139 | 85.2k | 4 * C10 * Xr * Yr * (Xr2 - Yr2) + C9 * (Xr4 + Yr4 - 6 * Xr2 * Yr2); |
140 | 85.2k | } |
141 | | |
142 | | } // namespace pj_modified_krovak |
143 | | |
144 | 129k | static PJ_XY krovak_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ |
145 | 129k | struct pj_krovak_data *Q = static_cast<struct pj_krovak_data *>(P->opaque); |
146 | 129k | PJ_XY xy = {0.0, 0.0}; |
147 | | |
148 | 129k | const double gfi = |
149 | 129k | pow((1. + P->e * sin(lp.phi)) / (1. - P->e * sin(lp.phi)), |
150 | 129k | Q->alpha * P->e / 2.); |
151 | | |
152 | 129k | const double u = |
153 | 129k | 2. * |
154 | 129k | (atan(Q->k * pow(tan(lp.phi / 2. + M_PI_4), Q->alpha) / gfi) - M_PI_4); |
155 | 129k | const double deltav = -lp.lam * Q->alpha; |
156 | | |
157 | 129k | const double s = |
158 | 129k | asin(cos(Q->ad) * sin(u) + sin(Q->ad) * cos(u) * cos(deltav)); |
159 | 129k | const double cos_s = cos(s); |
160 | 129k | if (cos_s < 1e-12) { |
161 | 0 | xy.x = 0; |
162 | 0 | xy.y = 0; |
163 | 0 | return xy; |
164 | 0 | } |
165 | 129k | const double d = asin(cos(u) * sin(deltav) / cos_s); |
166 | | |
167 | 129k | const double eps = Q->n * d; |
168 | 129k | const double rho = Q->rho0 * pow(tan(S0 / 2. + M_PI_4), Q->n) / |
169 | 129k | pow(tan(s / 2. + M_PI_4), Q->n); |
170 | | |
171 | 129k | xy.x = rho * cos(eps); |
172 | 129k | xy.y = rho * sin(eps); |
173 | | |
174 | | // At this point, xy.x is a southing and xy.y is a westing |
175 | | |
176 | 129k | if (Q->modified) { |
177 | 85.2k | using namespace pj_modified_krovak; |
178 | | |
179 | 85.2k | const double Xp = xy.x; |
180 | 85.2k | const double Yp = xy.y; |
181 | | |
182 | | // Reduced X and Y |
183 | 85.2k | const double Xr = Xp * P->a - X0; |
184 | 85.2k | const double Yr = Yp * P->a - Y0; |
185 | | |
186 | 85.2k | double dX, dY; |
187 | 85.2k | mod_krovak_compute_dx_dy(Xr, Yr, dX, dY); |
188 | | |
189 | 85.2k | xy.x = Xp - dX / P->a; |
190 | 85.2k | xy.y = Yp - dY / P->a; |
191 | 85.2k | } |
192 | | |
193 | | // PROJ always return values in (easting, northing) (default mode) |
194 | | // or (westing, southing) (+czech mode), so swap X/Y |
195 | 129k | std::swap(xy.x, xy.y); |
196 | | |
197 | 129k | if (Q->easting_northing) { |
198 | | // The default non-Czech convention uses easting, northing, so we have |
199 | | // to reverse the sign of the coordinates. But to do so, we have to take |
200 | | // into account the false easting/northing. |
201 | 74.4k | xy.x = -xy.x - 2 * P->x0 / P->a; |
202 | 74.4k | xy.y = -xy.y - 2 * P->y0 / P->a; |
203 | 74.4k | } |
204 | | |
205 | 129k | return xy; |
206 | 129k | } |
207 | | |
208 | 0 | static PJ_LP krovak_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ |
209 | 0 | struct pj_krovak_data *Q = static_cast<struct pj_krovak_data *>(P->opaque); |
210 | 0 | PJ_LP lp = {0.0, 0.0}; |
211 | |
|
212 | 0 | if (Q->easting_northing) { |
213 | | // The default non-Czech convention uses easting, northing, so we have |
214 | | // to reverse the sign of the coordinates. But to do so, we have to take |
215 | | // into account the false easting/northing. |
216 | 0 | xy.y = -xy.y - 2 * P->x0 / P->a; |
217 | 0 | xy.x = -xy.x - 2 * P->y0 / P->a; |
218 | 0 | } |
219 | |
|
220 | 0 | std::swap(xy.x, xy.y); |
221 | |
|
222 | 0 | if (Q->modified) { |
223 | 0 | using namespace pj_modified_krovak; |
224 | | |
225 | | // Note: in EPSG guidance node 7-2, below Xr/Yr/dX/dY are actually |
226 | | // Xr'/Yr'/dX'/dY' |
227 | 0 | const double Xr = xy.x * P->a - X0; |
228 | 0 | const double Yr = xy.y * P->a - Y0; |
229 | |
|
230 | 0 | double dX, dY; |
231 | 0 | mod_krovak_compute_dx_dy(Xr, Yr, dX, dY); |
232 | |
|
233 | 0 | xy.x = xy.x + dX / P->a; |
234 | 0 | xy.y = xy.y + dY / P->a; |
235 | 0 | } |
236 | |
|
237 | 0 | const double rho = sqrt(xy.x * xy.x + xy.y * xy.y); |
238 | 0 | const double eps = atan2(xy.y, xy.x); |
239 | |
|
240 | 0 | const double d = eps / sin(S0); |
241 | 0 | double s; |
242 | 0 | if (rho == 0.0) { |
243 | 0 | s = M_PI_2; |
244 | 0 | } else { |
245 | 0 | s = 2. * (atan(pow(Q->rho0 / rho, 1. / Q->n) * tan(S0 / 2. + M_PI_4)) - |
246 | 0 | M_PI_4); |
247 | 0 | } |
248 | |
|
249 | 0 | const double u = asin(cos(Q->ad) * sin(s) - sin(Q->ad) * cos(s) * cos(d)); |
250 | 0 | const double deltav = asin(cos(s) * sin(d) / cos(u)); |
251 | |
|
252 | 0 | lp.lam = P->lam0 - deltav / Q->alpha; |
253 | | |
254 | | /* ITERATION FOR lp.phi */ |
255 | 0 | double fi1 = u; |
256 | |
|
257 | 0 | int i; |
258 | 0 | for (i = MAX_ITER; i; --i) { |
259 | 0 | lp.phi = 2. * (atan(pow(Q->k, -1. / Q->alpha) * |
260 | 0 | pow(tan(u / 2. + M_PI_4), 1. / Q->alpha) * |
261 | 0 | pow((1. + P->e * sin(fi1)) / (1. - P->e * sin(fi1)), |
262 | 0 | P->e / 2.)) - |
263 | 0 | M_PI_4); |
264 | |
|
265 | 0 | if (fabs(fi1 - lp.phi) < EPS) |
266 | 0 | break; |
267 | 0 | fi1 = lp.phi; |
268 | 0 | } |
269 | 0 | if (i == 0) |
270 | 0 | proj_context_errno_set( |
271 | 0 | P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
272 | |
|
273 | 0 | lp.lam -= P->lam0; |
274 | |
|
275 | 0 | return lp; |
276 | 0 | } |
277 | | |
278 | 2.12k | static PJ *krovak_setup(PJ *P, bool modified) { |
279 | 2.12k | double u0, n0, g; |
280 | 2.12k | struct pj_krovak_data *Q = static_cast<struct pj_krovak_data *>( |
281 | 2.12k | calloc(1, sizeof(struct pj_krovak_data))); |
282 | 2.12k | if (nullptr == Q) |
283 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
284 | 2.12k | P->opaque = Q; |
285 | | |
286 | | /* we want Bessel as fixed ellipsoid */ |
287 | 2.12k | P->a = 6377397.155; |
288 | 2.12k | P->es = 0.006674372230614; |
289 | 2.12k | P->e = sqrt(P->es); |
290 | | |
291 | | /* if latitude of projection center is not set, use 49d30'N */ |
292 | 2.12k | if (!pj_param(P->ctx, P->params, "tlat_0").i) |
293 | 838 | P->phi0 = 0.863937979737193; |
294 | | |
295 | | /* if center long is not set use 42d30'E of Ferro - 17d40' for Ferro */ |
296 | | /* that will correspond to using longitudes relative to greenwich */ |
297 | | /* as input and output, instead of lat/long relative to Ferro */ |
298 | 2.12k | if (!pj_param(P->ctx, P->params, "tlon_0").i) |
299 | 806 | P->lam0 = 0.7417649320975901 - 0.308341501185665; |
300 | | |
301 | | /* if scale not set default to 0.9999 */ |
302 | 2.12k | if (!pj_param(P->ctx, P->params, "tk").i && |
303 | 2.12k | !pj_param(P->ctx, P->params, "tk_0").i) |
304 | 836 | P->k0 = 0.9999; |
305 | | |
306 | 2.12k | Q->modified = modified; |
307 | | |
308 | 2.12k | Q->easting_northing = true; |
309 | 2.12k | if (pj_param(P->ctx, P->params, "tczech").i) |
310 | 62 | Q->easting_northing = false; |
311 | | |
312 | | /* Set up shared parameters between forward and inverse */ |
313 | 2.12k | Q->alpha = sqrt(1. + (P->es * pow(cos(P->phi0), 4)) / (1. - P->es)); |
314 | 2.12k | u0 = asin(sin(P->phi0) / Q->alpha); |
315 | 2.12k | g = pow((1. + P->e * sin(P->phi0)) / (1. - P->e * sin(P->phi0)), |
316 | 2.12k | Q->alpha * P->e / 2.); |
317 | 2.12k | double tan_half_phi0_plus_pi_4 = tan(P->phi0 / 2. + M_PI_4); |
318 | 2.12k | if (tan_half_phi0_plus_pi_4 == 0.0) { |
319 | 1 | proj_log_error(P, _("Invalid value for lat_0: lat_0 + PI/4 should be " |
320 | 1 | "different from 0")); |
321 | 1 | return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
322 | 1 | } |
323 | 2.12k | Q->k = tan(u0 / 2. + M_PI_4) / pow(tan_half_phi0_plus_pi_4, Q->alpha) * g; |
324 | 2.12k | n0 = sqrt(1. - P->es) / (1. - P->es * pow(sin(P->phi0), 2)); |
325 | 2.12k | Q->n = sin(S0); |
326 | 2.12k | Q->rho0 = P->k0 * n0 / tan(S0); |
327 | 2.12k | Q->ad = M_PI_2 - UQ; |
328 | | |
329 | 2.12k | P->inv = krovak_e_inverse; |
330 | 2.12k | P->fwd = krovak_e_forward; |
331 | | |
332 | 2.12k | return P; |
333 | 2.12k | } |
334 | | |
335 | 1.02k | PJ *PJ_PROJECTION(krovak) { return krovak_setup(P, false); } |
336 | | |
337 | 1.10k | PJ *PJ_PROJECTION(mod_krovak) { return krovak_setup(P, true); } |
338 | | |
339 | | #undef EPS |
340 | | #undef UQ |
341 | | #undef S0 |
342 | | #undef MAX_ITER |