Coverage Report

Created: 2026-04-09 06:50

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