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 #define PJ_LIB__
79 
80 #include <errno.h>
81 #include <math.h>
82 
83 #include "proj.h"
84 #include "proj_internal.h"
85 
86 PROJ_HEAD(krovak, "Krovak") "\n\tPCyl, Ell";
87 
88 #define EPS 1e-15
89 #define UQ  1.04216856380474   /* DU(2, 59, 42, 42.69689) */
90 #define S0  1.37008346281555   /* Latitude of pseudo standard parallel 78deg 30'00" N */
91 /* Not sure at all of the appropriate number for MAX_ITER... */
92 #define MAX_ITER 100
93 
94 namespace { // anonymous namespace
95 struct pj_opaque {
96     double alpha;
97     double k;
98     double n;
99     double rho0;
100     double ad;
101     int czech;
102 };
103 } // anonymous namespace
104 
105 
krovak_e_forward(PJ_LP lp,PJ * P)106 static PJ_XY krovak_e_forward (PJ_LP lp, PJ *P) {                /* Ellipsoidal, forward */
107     struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque);
108     PJ_XY xy = {0.0,0.0};
109 
110     double gfi, u, deltav, s, d, eps, rho;
111 
112     gfi = pow ( (1. + P->e * sin(lp.phi)) / (1. - P->e * sin(lp.phi)), Q->alpha * P->e / 2.);
113 
114     u = 2. * (atan(Q->k * pow( tan(lp.phi / 2. + M_PI_4), Q->alpha) / gfi)-M_PI_4);
115     deltav = -lp.lam * Q->alpha;
116 
117     s = asin(cos(Q->ad) * sin(u) + sin(Q->ad) * cos(u) * cos(deltav));
118     const double cos_s =  cos(s);
119     if( cos_s < 1e-12 )
120     {
121         xy.x = 0;
122         xy.y = 0;
123         return xy;
124     }
125     d = asin(cos(u) * sin(deltav) / cos_s);
126 
127     eps = Q->n * d;
128     rho = Q->rho0 * pow(tan(S0 / 2. + M_PI_4) , Q->n) / pow(tan(s / 2. + M_PI_4) , Q->n);
129 
130     xy.y = rho * cos(eps);
131     xy.x = rho * sin(eps);
132 
133     xy.y *= Q->czech;
134     xy.x *= Q->czech;
135 
136     return xy;
137 }
138 
139 
krovak_e_inverse(PJ_XY xy,PJ * P)140 static PJ_LP krovak_e_inverse (PJ_XY xy, PJ *P) {                /* Ellipsoidal, inverse */
141     struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque);
142     PJ_LP lp = {0.0,0.0};
143 
144     double u, deltav, s, d, eps, rho, fi1, xy0;
145     int i;
146 
147     xy0 = xy.x;
148     xy.x = xy.y;
149     xy.y = xy0;
150 
151     xy.x *= Q->czech;
152     xy.y *= Q->czech;
153 
154     rho = sqrt(xy.x * xy.x + xy.y * xy.y);
155     eps = atan2(xy.y, xy.x);
156 
157     d = eps / sin(S0);
158     if( rho == 0.0 ) {
159         s = M_PI_2;
160     }
161     else {
162         s = 2. * (atan(  pow(Q->rho0 / rho, 1. / Q->n) * tan(S0 / 2. + M_PI_4)) - M_PI_4);
163     }
164 
165     u = asin(cos(Q->ad) * sin(s) - sin(Q->ad) * cos(s) * cos(d));
166     deltav = asin(cos(s) * sin(d) / cos(u));
167 
168     lp.lam = P->lam0 - deltav / Q->alpha;
169 
170     /* ITERATION FOR lp.phi */
171     fi1 = u;
172 
173     for (i = MAX_ITER; i ; --i) {
174         lp.phi = 2. * ( atan( pow( Q->k, -1. / Q->alpha)  *
175                               pow( tan(u / 2. + M_PI_4) , 1. / Q->alpha)  *
176                               pow( (1. + P->e * sin(fi1)) / (1. - P->e * sin(fi1)) , P->e / 2.)
177                             )  - M_PI_4);
178 
179         if (fabs(fi1 - lp.phi) < EPS)
180             break;
181         fi1 = lp.phi;
182     }
183     if( i == 0 )
184         pj_ctx_set_errno( P->ctx, PJD_ERR_NON_CONVERGENT );
185 
186    lp.lam -= P->lam0;
187 
188    return lp;
189 }
190 
191 
PROJECTION(krovak)192 PJ *PROJECTION(krovak) {
193     double u0, n0, g;
194     struct pj_opaque *Q = static_cast<struct pj_opaque*>(pj_calloc (1, sizeof (struct pj_opaque)));
195     if (nullptr==Q)
196         return pj_default_destructor (P, ENOMEM);
197     P->opaque = Q;
198 
199     /* we want Bessel as fixed ellipsoid */
200     P->a = 6377397.155;
201     P->es = 0.006674372230614;
202     P->e = sqrt(P->es);
203 
204     /* if latitude of projection center is not set, use 49d30'N */
205     if (!pj_param(P->ctx, P->params, "tlat_0").i)
206             P->phi0 = 0.863937979737193;
207 
208     /* if center long is not set use 42d30'E of Ferro - 17d40' for Ferro */
209     /* that will correspond to using longitudes relative to greenwich    */
210     /* as input and output, instead of lat/long relative to Ferro */
211     if (!pj_param(P->ctx, P->params, "tlon_0").i)
212             P->lam0 = 0.7417649320975901 - 0.308341501185665;
213 
214     /* if scale not set default to 0.9999 */
215     if (!pj_param(P->ctx, P->params, "tk").i && !pj_param(P->ctx, P->params, "tk_0").i)
216         P->k0 = 0.9999;
217 
218     Q->czech = 1;
219     if( !pj_param(P->ctx, P->params, "tczech").i )
220         Q->czech = -1;
221 
222     /* Set up shared parameters between forward and inverse */
223     Q->alpha = sqrt(1. + (P->es * pow(cos(P->phi0), 4)) / (1. - P->es));
224     u0 = asin(sin(P->phi0) / Q->alpha);
225     g = pow( (1. + P->e * sin(P->phi0)) / (1. - P->e * sin(P->phi0)) , Q->alpha * P->e / 2. );
226     double tan_half_phi0_plus_pi_4 = tan(P->phi0 / 2. + M_PI_4);
227     if( tan_half_phi0_plus_pi_4 == 0.0 ) {
228         return pj_default_destructor(P, PJD_ERR_INVALID_ARG);
229     }
230     Q->k = tan( u0 / 2. + M_PI_4) / pow  (tan_half_phi0_plus_pi_4 , Q->alpha) * g;
231     n0 = sqrt(1. - P->es) / (1. - P->es * pow(sin(P->phi0), 2));
232     Q->n = sin(S0);
233     Q->rho0 = P->k0 * n0 / tan(S0);
234     Q->ad = M_PI_2 - UQ;
235 
236     P->inv = krovak_e_inverse;
237     P->fwd = krovak_e_forward;
238 
239     return P;
240 }
241