Coverage Report

Created: 2026-02-03 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/projections/eqc.cpp
Line
Count
Source
1
/******************************************************************************
2
 * Project:  PROJ
3
 * Purpose:  Implementation of the eqc (Equidistant Cylindrical) projection.
4
 *           Also known as Plate Carree when lat_ts=0.
5
 *
6
 * This file implements both the spherical (EPSG:1029) and ellipsoidal
7
 * (EPSG:1028) forms of the Equidistant Cylindrical projection.
8
 *
9
 * Spherical formulas (EPSG:1029):
10
 *   E = FE + R cos(lat_ts) (lon - lon_0)
11
 *   N = FN + R lat
12
 *
13
 * Ellipsoidal formulas (EPSG:1028) from IOGP Guidance Note 7-2:
14
 *   E = FE + nu1 cos(lat_ts) (lon - lon_0)
15
 *   N = FN + M
16
 *
17
 *
18
 * Reference: IOGP Publication 373-7-2, Section 3.2.5
19
 *
20
21
 *****************************************************************************/
22
23
#include <errno.h>
24
#include <math.h>
25
#include <stdlib.h>
26
27
#include "proj.h"
28
#include "proj_internal.h"
29
30
namespace { // anonymous namespace
31
struct pj_eqc_data {
32
    double rc;  // Spherical: cos(lat_ts); Ellipsoidal: nu1 * cos(lat_ts)
33
    double M0;  // Meridional arc at latitude of origin (lat_0)
34
    double *en; // Coefficients for meridional arc computation
35
};
36
} // anonymous namespace
37
38
PROJ_HEAD(eqc, "Equidistant Cylindrical (Plate Carree)")
39
"\n\tCyl, Sph&Ell\n\tlat_ts=[, lat_0=0]";
40
41
0
static PJ_XY eqc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */
42
0
    PJ_XY xy = {0.0, 0.0};
43
0
    struct pj_eqc_data *Q = static_cast<struct pj_eqc_data *>(P->opaque);
44
45
0
    xy.x = Q->rc * lp.lam;
46
0
    xy.y = lp.phi - P->phi0;
47
48
0
    return xy;
49
0
}
50
51
0
static PJ_LP eqc_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */
52
0
    PJ_LP lp = {0.0, 0.0};
53
0
    struct pj_eqc_data *Q = static_cast<struct pj_eqc_data *>(P->opaque);
54
55
0
    lp.lam = xy.x / Q->rc;
56
0
    lp.phi = xy.y + P->phi0;
57
58
0
    return lp;
59
0
}
60
61
// Ellipsoidal forward (EPSG method 1028)
62
// E = FE + nu1 cos(lat_ts) (lon - lon_0)
63
// N = FN + M - M0
64
// where M is the meridional arc from equator to latitude
65
7.56k
static PJ_XY eqc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */
66
7.56k
    PJ_XY xy = {0.0, 0.0};
67
7.56k
    struct pj_eqc_data *Q = static_cast<struct pj_eqc_data *>(P->opaque);
68
69
7.56k
    const double sinphi = sin(lp.phi);
70
7.56k
    const double cosphi = cos(lp.phi);
71
72
7.56k
    xy.x = Q->rc * lp.lam;
73
7.56k
    xy.y = pj_mlfn(lp.phi, sinphi, cosphi, Q->en) - Q->M0;
74
75
7.56k
    return xy;
76
7.56k
}
77
78
// Ellipsoidal inverse (EPSG method 1028)
79
// lon = lon_0 + (E - FE) / (nu1 cos lat_ts)
80
// lat is found by inverting M = N - FN + M0
81
0
static PJ_LP eqc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */
82
0
    PJ_LP lp = {0.0, 0.0};
83
0
    struct pj_eqc_data *Q = static_cast<struct pj_eqc_data *>(P->opaque);
84
85
0
    lp.lam = xy.x / Q->rc;
86
0
    lp.phi = pj_inv_mlfn(xy.y + Q->M0, Q->en);
87
88
0
    return lp;
89
0
}
90
91
74
static PJ *pj_eqc_destructor(PJ *P, int errlev) { /* Destructor */
92
74
    if (nullptr == P)
93
0
        return nullptr;
94
95
74
    if (nullptr == P->opaque)
96
0
        return pj_default_destructor(P, errlev);
97
98
74
    free(static_cast<struct pj_eqc_data *>(P->opaque)->en);
99
74
    return pj_default_destructor(P, errlev);
100
74
}
101
102
75
PJ *PJ_PROJECTION(eqc) {
103
75
    struct pj_eqc_data *Q = static_cast<struct pj_eqc_data *>(
104
75
        calloc(1, sizeof(struct pj_eqc_data)));
105
75
    if (nullptr == Q)
106
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
107
75
    P->opaque = Q;
108
75
    P->destructor = pj_eqc_destructor;
109
110
75
    const double phi1 = pj_param(P->ctx, P->params, "rlat_ts").f;
111
75
    const double cos_phi1 = cos(phi1);
112
113
75
    if (cos_phi1 <= 0.) {
114
1
        proj_log_error(
115
1
            P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°"));
116
1
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
117
1
    }
118
119
74
    if (P->es != 0.0) {
120
        // Ellipsoidal case (EPSG:1028)
121
71
        const double sin_phi1 = sin(phi1);
122
        // nu1 = a / sqrt(1 - e^2 sin^2(lat_ts)), normalized by a
123
71
        const double nu1 = 1.0 / sqrt(1.0 - P->es * sin_phi1 * sin_phi1);
124
71
        Q->rc = nu1 * cos_phi1;
125
126
71
        Q->en = pj_enfn(P->n);
127
71
        if (nullptr == Q->en)
128
0
            return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
129
130
71
        Q->M0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en);
131
132
71
        P->inv = eqc_e_inverse;
133
71
        P->fwd = eqc_e_forward;
134
71
    } else {
135
        // Spheroidal case (EPSG:1029)
136
3
        Q->rc = cos_phi1;
137
3
        Q->en = nullptr;
138
3
        Q->M0 = 0.0;
139
140
3
        P->inv = eqc_s_inverse;
141
3
        P->fwd = eqc_s_forward;
142
3
    }
143
144
74
    return P;
145
74
}