Coverage Report

Created: 2025-06-22 06:59

/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