Coverage Report

Created: 2025-07-18 07:18

/src/PROJ/src/transformations/hgridshift.cpp
Line
Count
Source (jump to first uncovered line)
1
2
3
#include <errno.h>
4
#include <mutex>
5
#include <stddef.h>
6
#include <string.h>
7
#include <time.h>
8
9
#include "grids.hpp"
10
#include "proj_internal.h"
11
12
PROJ_HEAD(hgridshift, "Horizontal grid shift");
13
14
static std::mutex gMutexHGridShift{};
15
static std::set<std::string> gKnownGridsHGridShift{};
16
17
using namespace NS_PROJ;
18
19
namespace { // anonymous namespace
20
struct hgridshiftData {
21
    double t_final = 0;
22
    double t_epoch = 0;
23
    ListOfHGrids grids{};
24
    bool defer_grid_opening = false;
25
    int error_code_in_defer_grid_opening = 0;
26
};
27
} // anonymous namespace
28
29
25.2k
static PJ_XYZ pj_hgridshift_forward_3d(PJ_LPZ lpz, PJ *P) {
30
25.2k
    auto Q = static_cast<hgridshiftData *>(P->opaque);
31
25.2k
    PJ_COORD point = {{0, 0, 0, 0}};
32
25.2k
    point.lpz = lpz;
33
34
25.2k
    if (Q->defer_grid_opening) {
35
10
        Q->defer_grid_opening = false;
36
10
        Q->grids = pj_hgrid_init(P, "grids");
37
10
        Q->error_code_in_defer_grid_opening = proj_errno(P);
38
10
    }
39
25.2k
    if (Q->error_code_in_defer_grid_opening) {
40
0
        proj_errno_set(P, Q->error_code_in_defer_grid_opening);
41
0
        return proj_coord_error().xyz;
42
0
    }
43
44
25.2k
    if (!Q->grids.empty()) {
45
        /* Only try the gridshift if at least one grid is loaded,
46
         * otherwise just pass the coordinate through unchanged. */
47
0
        point.lp = pj_hgrid_apply(P->ctx, Q->grids, point.lp, PJ_FWD);
48
0
    }
49
50
25.2k
    return point.xyz;
51
25.2k
}
52
53
63.9k
static PJ_LPZ pj_hgridshift_reverse_3d(PJ_XYZ xyz, PJ *P) {
54
63.9k
    auto Q = static_cast<hgridshiftData *>(P->opaque);
55
63.9k
    PJ_COORD point = {{0, 0, 0, 0}};
56
63.9k
    point.xyz = xyz;
57
58
63.9k
    if (Q->defer_grid_opening) {
59
19
        Q->defer_grid_opening = false;
60
19
        Q->grids = pj_hgrid_init(P, "grids");
61
19
        Q->error_code_in_defer_grid_opening = proj_errno(P);
62
19
    }
63
63.9k
    if (Q->error_code_in_defer_grid_opening) {
64
0
        proj_errno_set(P, Q->error_code_in_defer_grid_opening);
65
0
        return proj_coord_error().lpz;
66
0
    }
67
68
63.9k
    if (!Q->grids.empty()) {
69
        /* Only try the gridshift if at least one grid is loaded,
70
         * otherwise just pass the coordinate through unchanged. */
71
0
        point.lp = pj_hgrid_apply(P->ctx, Q->grids, point.lp, PJ_INV);
72
0
    }
73
74
63.9k
    return point.lpz;
75
63.9k
}
76
77
25.2k
static void pj_hgridshift_forward_4d(PJ_COORD &coo, PJ *P) {
78
25.2k
    struct hgridshiftData *Q = (struct hgridshiftData *)P->opaque;
79
80
    /* If transformation is not time restricted, we always call it */
81
25.2k
    if (Q->t_final == 0 || Q->t_epoch == 0) {
82
        // Assigning in 2 steps avoids cppcheck warning
83
        // "Overlapping read/write of union is undefined behavior"
84
        // Cf
85
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
86
25.2k
        const auto xyz = pj_hgridshift_forward_3d(coo.lpz, P);
87
25.2k
        coo.xyz = xyz;
88
25.2k
        return;
89
25.2k
    }
90
91
    /* Time restricted - only apply transform if within time bracket */
92
0
    if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) {
93
        // Assigning in 2 steps avoids cppcheck warning
94
        // "Overlapping read/write of union is undefined behavior"
95
        // Cf
96
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
97
0
        const auto xyz = pj_hgridshift_forward_3d(coo.lpz, P);
98
0
        coo.xyz = xyz;
99
0
    }
100
0
}
101
102
63.9k
static void pj_hgridshift_reverse_4d(PJ_COORD &coo, PJ *P) {
103
63.9k
    struct hgridshiftData *Q = (struct hgridshiftData *)P->opaque;
104
105
    /* If transformation is not time restricted, we always call it */
106
63.9k
    if (Q->t_final == 0 || Q->t_epoch == 0) {
107
        // Assigning in 2 steps avoids cppcheck warning
108
        // "Overlapping read/write of union is undefined behavior"
109
        // Cf
110
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
111
63.9k
        const auto lpz = pj_hgridshift_reverse_3d(coo.xyz, P);
112
63.9k
        coo.lpz = lpz;
113
63.9k
        return;
114
63.9k
    }
115
116
    /* Time restricted - only apply transform if within time bracket */
117
0
    if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) {
118
        // Assigning in 2 steps avoids cppcheck warning
119
        // "Overlapping read/write of union is undefined behavior"
120
        // Cf
121
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
122
0
        const auto lpz = pj_hgridshift_reverse_3d(coo.xyz, P);
123
0
        coo.lpz = lpz;
124
0
    }
125
0
}
126
127
6.87k
static PJ *pj_hgridshift_destructor(PJ *P, int errlev) {
128
6.87k
    if (nullptr == P)
129
0
        return nullptr;
130
131
6.87k
    delete static_cast<struct hgridshiftData *>(P->opaque);
132
6.87k
    P->opaque = nullptr;
133
134
6.87k
    return pj_default_destructor(P, errlev);
135
6.87k
}
136
137
0
static void pj_hgridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) {
138
0
    auto Q = (struct hgridshiftData *)P->opaque;
139
0
    for (auto &grid : Q->grids) {
140
0
        grid->reassign_context(ctx);
141
0
    }
142
0
}
143
144
6.87k
PJ *PJ_TRANSFORMATION(hgridshift, 0) {
145
6.87k
    auto Q = new hgridshiftData;
146
6.87k
    P->opaque = (void *)Q;
147
6.87k
    P->destructor = pj_hgridshift_destructor;
148
6.87k
    P->reassign_context = pj_hgridshift_reassign_context;
149
150
6.87k
    P->fwd4d = pj_hgridshift_forward_4d;
151
6.87k
    P->inv4d = pj_hgridshift_reverse_4d;
152
6.87k
    P->fwd3d = pj_hgridshift_forward_3d;
153
6.87k
    P->inv3d = pj_hgridshift_reverse_3d;
154
6.87k
    P->fwd = nullptr;
155
6.87k
    P->inv = nullptr;
156
157
6.87k
    P->left = PJ_IO_UNITS_RADIANS;
158
6.87k
    P->right = PJ_IO_UNITS_RADIANS;
159
160
6.87k
    if (0 == pj_param(P->ctx, P->params, "tgrids").i) {
161
14
        proj_log_error(P, _("+grids parameter missing."));
162
14
        return pj_hgridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
163
14
    }
164
165
    /* TODO: Refactor into shared function that can be used  */
166
    /*       by both vgridshift and hgridshift               */
167
6.85k
    if (pj_param(P->ctx, P->params, "tt_final").i) {
168
30
        Q->t_final = pj_param(P->ctx, P->params, "dt_final").f;
169
30
        if (Q->t_final == 0) {
170
            /* a number wasn't passed to +t_final, let's see if it was "now" */
171
            /* and set the time accordingly.                                 */
172
24
            if (!strcmp("now", pj_param(P->ctx, P->params, "st_final").s)) {
173
0
                time_t now;
174
0
                struct tm *date;
175
0
                time(&now);
176
0
                date = localtime(&now);
177
0
                Q->t_final = 1900.0 + date->tm_year + date->tm_yday / 365.0;
178
0
            }
179
24
        }
180
30
    }
181
182
6.85k
    if (pj_param(P->ctx, P->params, "tt_epoch").i)
183
5
        Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f;
184
185
6.85k
    if (P->ctx->defer_grid_opening) {
186
0
        Q->defer_grid_opening = true;
187
6.85k
    } else {
188
6.85k
        const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s;
189
6.85k
        gMutexHGridShift.lock();
190
6.85k
        const bool isKnownGrid = gKnownGridsHGridShift.find(gridnames) !=
191
6.85k
                                 gKnownGridsHGridShift.end();
192
6.85k
        gMutexHGridShift.unlock();
193
6.85k
        if (isKnownGrid) {
194
5.22k
            Q->defer_grid_opening = true;
195
5.22k
        } else {
196
1.63k
            Q->grids = pj_hgrid_init(P, "grids");
197
            /* Was gridlist compiled properly? */
198
1.63k
            if (proj_errno(P)) {
199
1.33k
                proj_log_error(P, _("could not find required grid(s)."));
200
1.33k
                return pj_hgridshift_destructor(
201
1.33k
                    P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
202
1.33k
            }
203
204
307
            gMutexHGridShift.lock();
205
307
            gKnownGridsHGridShift.insert(gridnames);
206
307
            gMutexHGridShift.unlock();
207
307
        }
208
6.85k
    }
209
210
5.52k
    return P;
211
6.85k
}
212
213
14.6k
void pj_clear_hgridshift_knowngrids_cache() {
214
14.6k
    std::lock_guard<std::mutex> lock(gMutexHGridShift);
215
14.6k
    gKnownGridsHGridShift.clear();
216
14.6k
}