Coverage Report

Created: 2025-07-18 07:18

/src/PROJ/src/projections/spilhaus.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Implementation of the Spilhaus projections based on Adams World in a Square
3
 * II.
4
 *
5
 * Explained in https://github.com/OSGeo/PROJ/issues/1851
6
 *
7
 * Copyright (c) 2025 Javier Jimenez Shaw
8
 *
9
 * Related material
10
 * ----------------
11
 *
12
 * Map Projections - A Working Manual. 1987. John P. Snyder
13
 * Sections 3 and 5.
14
 * https://doi.org/10.3133/pp1395
15
 * Online at https://neacsu.net/docs/geodesy/snyder/2-general/
16
 */
17
18
#include <errno.h>
19
#include <math.h>
20
21
#include "proj.h"
22
#include "proj_internal.h"
23
24
C_NAMESPACE PJ *pj_adams_ws2(PJ *);
25
26
PROJ_HEAD(spilhaus, "Spilhaus") "\n\tSph&Ell";
27
28
namespace { // anonymous namespace
29
struct pj_spilhaus_data {
30
    double cosalpha;
31
    double sinalpha;
32
    double beta;
33
    double lambda_0;
34
    double conformal_distortion;
35
    double cosrot;
36
    double sinrot;
37
    PJ *adams_ws2;
38
};
39
40
} // anonymous namespace
41
42
0
static PJ_XY spilhaus_forward(PJ_LP lp, PJ *P) {
43
0
    PJ_XY xy = {0.0, 0.0};
44
0
    struct pj_spilhaus_data *Q =
45
0
        static_cast<struct pj_spilhaus_data *>(P->opaque);
46
47
0
    const double phi_c = pj_conformal_lat(lp.phi, P);
48
0
    const double cosphi_c = cos(phi_c);
49
0
    const double sinphi_c = sin(phi_c);
50
51
0
    const double coslam = cos(lp.lam - Q->lambda_0);
52
0
    const double sinlam = sin(lp.lam - Q->lambda_0);
53
54
0
    PJ_LP lpadams;
55
    // Snyder, A working manual, formula 5-7
56
0
    lpadams.phi =
57
0
        aasin(P->ctx, Q->sinalpha * sinphi_c - Q->cosalpha * cosphi_c * coslam);
58
59
    // Snyder, A working manual, formula 5-8b
60
0
    lpadams.lam =
61
0
        (Q->beta + atan2(cosphi_c * sinlam, (Q->sinalpha * cosphi_c * coslam +
62
0
                                             Q->cosalpha * sinphi_c)));
63
64
0
    while (lpadams.lam > M_PI)
65
0
        lpadams.lam -= M_PI * 2;
66
0
    while (lpadams.lam < -M_PI)
67
0
        lpadams.lam += M_PI * 2;
68
69
0
    PJ_XY xyadams = Q->adams_ws2->fwd(lpadams, Q->adams_ws2);
70
0
    const double factor = Q->conformal_distortion * P->k0;
71
0
    xy.x = -(xyadams.x * Q->cosrot + xyadams.y * Q->sinrot) * factor;
72
0
    xy.y = -(xyadams.x * -Q->sinrot + xyadams.y * Q->cosrot) * factor;
73
74
0
    return xy;
75
0
}
76
77
0
static PJ_LP spilhaus_inverse(PJ_XY xy, PJ *P) {
78
0
    PJ_LP lp = {0.0, 0.0};
79
0
    struct pj_spilhaus_data *Q =
80
0
        static_cast<struct pj_spilhaus_data *>(P->opaque);
81
82
0
    PJ_XY xyadams;
83
0
    const double factor = 1.0 / (Q->conformal_distortion * P->k0);
84
0
    xyadams.x = -(xy.x * Q->cosrot + xy.y * -Q->sinrot) * factor;
85
0
    xyadams.y = -(xy.x * Q->sinrot + xy.y * Q->cosrot) * factor;
86
0
    PJ_LP lpadams = Q->adams_ws2->inv(xyadams, Q->adams_ws2);
87
88
0
    const double cosphi_s = cos(lpadams.phi);
89
0
    const double sinphi_s = sin(lpadams.phi);
90
0
    const double coslam_s = cos(lpadams.lam - Q->beta);
91
0
    const double sinlam_s = sin(lpadams.lam - Q->beta);
92
93
    // conformal latitude
94
0
    lp.phi = aasin(P->ctx,
95
0
                   Q->sinalpha * sinphi_s + Q->cosalpha * cosphi_s * coslam_s);
96
97
0
    lp.lam = Q->lambda_0 +
98
0
             aatan2(cosphi_s * sinlam_s,
99
0
                    Q->sinalpha * cosphi_s * coslam_s - Q->cosalpha * sinphi_s);
100
101
0
    lp.phi = pj_conformal_lat_inverse(lp.phi, P);
102
103
0
    return lp;
104
0
}
105
106
0
static PJ *spilhaus_destructor(PJ *P, int errlev) { /* Destructor */
107
0
    if (nullptr == P)
108
0
        return nullptr;
109
0
    if (nullptr == P->opaque)
110
0
        return pj_default_destructor(P, errlev);
111
0
    proj_destroy(static_cast<struct pj_spilhaus_data *>(P->opaque)->adams_ws2);
112
0
    return pj_default_destructor(P, errlev);
113
0
}
114
115
0
PJ *PJ_PROJECTION(spilhaus) {
116
0
    struct pj_spilhaus_data *Q = static_cast<struct pj_spilhaus_data *>(
117
0
        calloc(1, sizeof(struct pj_spilhaus_data)));
118
0
    if (nullptr == Q)
119
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
120
0
    P->opaque = Q;
121
0
    P->destructor = spilhaus_destructor;
122
123
0
    Q->adams_ws2 = pj_adams_ws2(nullptr);
124
0
    if (Q->adams_ws2 == nullptr)
125
0
        return spilhaus_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
126
0
    Q->adams_ws2->ctx = P->ctx;
127
0
    Q->adams_ws2->e = 0;
128
0
    Q->adams_ws2 = pj_adams_ws2(Q->adams_ws2);
129
0
    if (Q->adams_ws2 == nullptr)
130
0
        return spilhaus_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
131
132
0
    auto param_rad = [&P](const std::string &name, double def) {
133
0
        return pj_param(P->ctx, P->params, ("t" + name).c_str()).i
134
0
                   ? pj_param(P->ctx, P->params, ("r" + name).c_str()).f
135
0
                   : def * DEG_TO_RAD;
136
0
    };
137
138
    // Values from https://github.com/OSGeo/PROJ/issues/1851
139
0
    if (!pj_param(P->ctx, P->params, "tlon_0").i) {
140
0
        P->lam0 = 66.94970198 * DEG_TO_RAD;
141
0
    }
142
0
    if (!pj_param(P->ctx, P->params, "tlat_0").i) {
143
0
        P->phi0 = -49.56371678 * DEG_TO_RAD;
144
0
    }
145
0
    const double azimuth = param_rad("azi", 40.17823482);
146
147
0
    const double rotation = param_rad("rot", 45);
148
0
    Q->cosrot = cos(rotation);
149
0
    Q->sinrot = sin(rotation);
150
151
0
    const double conformal_lat_center = pj_conformal_lat(P->phi0, P);
152
153
0
    Q->sinalpha = -cos(conformal_lat_center) * cos(azimuth);
154
0
    Q->cosalpha = sqrt(1 - Q->sinalpha * Q->sinalpha);
155
0
    Q->lambda_0 = atan2(tan(azimuth), -sin(conformal_lat_center));
156
0
    Q->beta = M_PI + atan2(-sin(azimuth), -tan(conformal_lat_center));
157
158
0
    Q->conformal_distortion = cos(P->phi0) /
159
0
                              sqrt(1 - P->es * sin(P->phi0) * sin(P->phi0)) /
160
0
                              cos(conformal_lat_center);
161
162
0
    P->fwd = spilhaus_forward;
163
0
    P->inv = spilhaus_inverse;
164
165
0
    return P;
166
0
}