Coverage Report

Created: 2025-07-11 06:33

/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