/src/proj/src/projections/lcca.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /***************************************************************************** |
2 | | |
3 | | Lambert Conformal Conic Alternative |
4 | | ----------------------------------- |
5 | | |
6 | | This is Gerald Evenden's 2003 implementation of an alternative |
7 | | "almost" LCC, which has been in use historically, but which |
8 | | should NOT be used for new projects - i.e: use this implementation |
9 | | if you need interoperability with old data represented in this |
10 | | projection, but not in any other case. |
11 | | |
12 | | The code was originally discussed on the PROJ.4 mailing list in |
13 | | a thread archived over at |
14 | | |
15 | | http://lists.maptools.org/pipermail/proj/2003-March/000644.html |
16 | | |
17 | | It was discussed again in the thread starting at |
18 | | |
19 | | http://lists.maptools.org/pipermail/proj/2017-October/007828.html |
20 | | and continuing at |
21 | | http://lists.maptools.org/pipermail/proj/2017-November/007831.html |
22 | | |
23 | | which prompted Clifford J. Mugnier to add these clarifying notes: |
24 | | |
25 | | The French Army Truncated Cubic Lambert (partially conformal) Conic |
26 | | projection is the Legal system for the projection in France between |
27 | | the late 1800s and 1948 when the French Legislature changed the law |
28 | | to recognize the fully conformal version. |
29 | | |
30 | | It was (might still be in one or two North African prior French |
31 | | Colonies) used in North Africa in Algeria, Tunisia, & Morocco, as |
32 | | well as in Syria during the Levant. |
33 | | |
34 | | Last time I have seen it used was about 30+ years ago in |
35 | | Algeria when it was used to define Lease Block boundaries for |
36 | | Petroleum Exploration & Production. |
37 | | |
38 | | (signed) |
39 | | |
40 | | Clifford J. Mugnier, c.p., c.m.s. |
41 | | Chief of Geodesy |
42 | | LSU Center for GeoInformatics |
43 | | Dept. of Civil Engineering |
44 | | LOUISIANA STATE UNIVERSITY |
45 | | |
46 | | *****************************************************************************/ |
47 | | |
48 | | #include <errno.h> |
49 | | #include <math.h> |
50 | | |
51 | | #include "proj.h" |
52 | | #include "proj_internal.h" |
53 | | |
54 | | PROJ_HEAD(lcca, "Lambert Conformal Conic Alternative") |
55 | | "\n\tConic, Sph&Ell\n\tlat_0="; |
56 | | |
57 | 0 | #define MAX_ITER 10 |
58 | 0 | #define DEL_TOL 1e-12 |
59 | | |
60 | | namespace { // anonymous namespace |
61 | | struct pj_lcca_data { |
62 | | double *en; |
63 | | double r0, l, M0; |
64 | | double C; |
65 | | }; |
66 | | } // anonymous namespace |
67 | | |
68 | 0 | static double fS(double S, double C) { /* func to compute dr */ |
69 | |
|
70 | 0 | return S * (1. + S * S * C); |
71 | 0 | } |
72 | | |
73 | 0 | static double fSp(double S, double C) { /* deriv of fs */ |
74 | |
|
75 | 0 | return 1. + 3. * S * S * C; |
76 | 0 | } |
77 | | |
78 | 0 | static PJ_XY lcca_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ |
79 | 0 | PJ_XY xy = {0.0, 0.0}; |
80 | 0 | struct pj_lcca_data *Q = static_cast<struct pj_lcca_data *>(P->opaque); |
81 | 0 | double S, r, dr; |
82 | |
|
83 | 0 | S = pj_mlfn(lp.phi, sin(lp.phi), cos(lp.phi), Q->en) - Q->M0; |
84 | 0 | dr = fS(S, Q->C); |
85 | 0 | r = Q->r0 - dr; |
86 | 0 | const double lam_mul_l = lp.lam * Q->l; |
87 | 0 | xy.x = P->k0 * (r * sin(lam_mul_l)); |
88 | 0 | xy.y = P->k0 * (Q->r0 - r * cos(lam_mul_l)); |
89 | 0 | return xy; |
90 | 0 | } |
91 | | |
92 | 0 | static PJ_LP lcca_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ |
93 | 0 | PJ_LP lp = {0.0, 0.0}; |
94 | 0 | struct pj_lcca_data *Q = static_cast<struct pj_lcca_data *>(P->opaque); |
95 | 0 | double theta, dr, S, dif; |
96 | 0 | int i; |
97 | |
|
98 | 0 | xy.x /= P->k0; |
99 | 0 | xy.y /= P->k0; |
100 | 0 | theta = atan2(xy.x, Q->r0 - xy.y); |
101 | 0 | dr = xy.y - xy.x * tan(0.5 * theta); |
102 | 0 | lp.lam = theta / Q->l; |
103 | 0 | S = dr; |
104 | 0 | for (i = MAX_ITER; i; --i) { |
105 | 0 | S -= (dif = (fS(S, Q->C) - dr) / fSp(S, Q->C)); |
106 | 0 | if (fabs(dif) < DEL_TOL) |
107 | 0 | break; |
108 | 0 | } |
109 | 0 | if (!i) { |
110 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
111 | 0 | return lp; |
112 | 0 | } |
113 | 0 | lp.phi = pj_inv_mlfn(S + Q->M0, Q->en); |
114 | |
|
115 | 0 | return lp; |
116 | 0 | } |
117 | | |
118 | 0 | static PJ *pj_lcca_destructor(PJ *P, int errlev) { |
119 | 0 | if (nullptr == P) |
120 | 0 | return nullptr; |
121 | | |
122 | 0 | if (nullptr == P->opaque) |
123 | 0 | return pj_default_destructor(P, errlev); |
124 | | |
125 | 0 | free(static_cast<struct pj_lcca_data *>(P->opaque)->en); |
126 | 0 | return pj_default_destructor(P, errlev); |
127 | 0 | } |
128 | | |
129 | 0 | PJ *PJ_PROJECTION(lcca) { |
130 | 0 | double s2p0, N0, R0, tan0; |
131 | 0 | struct pj_lcca_data *Q = static_cast<struct pj_lcca_data *>( |
132 | 0 | calloc(1, sizeof(struct pj_lcca_data))); |
133 | 0 | if (nullptr == Q) |
134 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
135 | 0 | P->opaque = Q; |
136 | |
|
137 | 0 | (Q->en = pj_enfn(P->n)); |
138 | 0 | if (!Q->en) |
139 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
140 | | |
141 | 0 | if (P->phi0 == 0.) { |
142 | 0 | proj_log_error( |
143 | 0 | P, _("Invalid value for lat_0: it should be different from 0.")); |
144 | 0 | return pj_lcca_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
145 | 0 | } |
146 | 0 | Q->l = sin(P->phi0); |
147 | 0 | Q->M0 = pj_mlfn(P->phi0, Q->l, cos(P->phi0), Q->en); |
148 | 0 | s2p0 = Q->l * Q->l; |
149 | 0 | R0 = 1. / (1. - P->es * s2p0); |
150 | 0 | N0 = sqrt(R0); |
151 | 0 | R0 *= P->one_es * N0; |
152 | 0 | tan0 = tan(P->phi0); |
153 | 0 | Q->r0 = N0 / tan0; |
154 | 0 | Q->C = 1. / (6. * R0 * N0); |
155 | |
|
156 | 0 | P->inv = lcca_e_inverse; |
157 | 0 | P->fwd = lcca_e_forward; |
158 | 0 | P->destructor = pj_lcca_destructor; |
159 | |
|
160 | 0 | return P; |
161 | 0 | } |
162 | | |
163 | | #undef MAX_ITER |
164 | | #undef DEL_TOL |