Coverage Report

Created: 2026-03-22 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/transformations/vgridshift.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(vgridshift, "Vertical grid shift");
12
13
static std::mutex gMutexVGridShift{};
14
static std::set<std::string> gKnownGridsVGridShift{};
15
16
using namespace NS_PROJ;
17
18
namespace { // anonymous namespace
19
struct vgridshiftData {
20
    double t_final = 0;
21
    double t_epoch = 0;
22
    double forward_multiplier = 0;
23
    ListOfVGrids grids{};
24
    bool defer_grid_opening = false;
25
    int error_code_in_defer_grid_opening = 0;
26
};
27
} // anonymous namespace
28
29
7
static void deal_with_vertcon_gtx_hack(PJ *P) {
30
7
    struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque;
31
    // The .gtx VERTCON files stored millimeters, but the .tif files
32
    // are in metres.
33
7
    if (Q->forward_multiplier != 0.001) {
34
7
        return;
35
7
    }
36
0
    const char *gridname = pj_param(P->ctx, P->params, "sgrids").s;
37
0
    if (!gridname) {
38
0
        return;
39
0
    }
40
0
    if (strcmp(gridname, "vertconw.gtx") != 0 &&
41
0
        strcmp(gridname, "vertconc.gtx") != 0 &&
42
0
        strcmp(gridname, "vertcone.gtx") != 0) {
43
0
        return;
44
0
    }
45
0
    if (Q->grids.empty()) {
46
0
        return;
47
0
    }
48
0
    const auto &grids = Q->grids[0]->grids();
49
0
    if (!grids.empty() && grids[0]->name().find(".tif") != std::string::npos) {
50
0
        Q->forward_multiplier = 1.0;
51
0
    }
52
0
}
53
54
7.14k
static PJ_XYZ pj_vgridshift_forward_3d(PJ_LPZ lpz, PJ *P) {
55
7.14k
    struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque;
56
7.14k
    PJ_COORD point = {{0, 0, 0, 0}};
57
7.14k
    point.lpz = lpz;
58
59
7.14k
    if (Q->defer_grid_opening) {
60
7
        Q->defer_grid_opening = false;
61
7
        Q->grids = pj_vgrid_init(P, "grids");
62
7
        deal_with_vertcon_gtx_hack(P);
63
7
        Q->error_code_in_defer_grid_opening = proj_errno(P);
64
7
    }
65
7.14k
    if (Q->error_code_in_defer_grid_opening) {
66
0
        proj_errno_set(P, Q->error_code_in_defer_grid_opening);
67
0
        return proj_coord_error().xyz;
68
0
    }
69
70
7.14k
    if (!Q->grids.empty()) {
71
        /* Only try the gridshift if at least one grid is loaded,
72
         * otherwise just pass the coordinate through unchanged. */
73
0
        point.xyz.z +=
74
0
            pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier);
75
0
    }
76
77
7.14k
    return point.xyz;
78
7.14k
}
79
80
0
static PJ_LPZ pj_vgridshift_reverse_3d(PJ_XYZ xyz, PJ *P) {
81
0
    struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque;
82
0
    PJ_COORD point = {{0, 0, 0, 0}};
83
0
    point.xyz = xyz;
84
85
0
    if (Q->defer_grid_opening) {
86
0
        Q->defer_grid_opening = false;
87
0
        Q->grids = pj_vgrid_init(P, "grids");
88
0
        deal_with_vertcon_gtx_hack(P);
89
0
        Q->error_code_in_defer_grid_opening = proj_errno(P);
90
0
    }
91
0
    if (Q->error_code_in_defer_grid_opening) {
92
0
        proj_errno_set(P, Q->error_code_in_defer_grid_opening);
93
0
        return proj_coord_error().lpz;
94
0
    }
95
96
0
    if (!Q->grids.empty()) {
97
        /* Only try the gridshift if at least one grid is loaded,
98
         * otherwise just pass the coordinate through unchanged. */
99
0
        point.xyz.z -=
100
0
            pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier);
101
0
    }
102
103
0
    return point.lpz;
104
0
}
105
106
7.14k
static void pj_vgridshift_forward_4d(PJ_COORD &coo, PJ *P) {
107
7.14k
    struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque;
108
109
    /* If transformation is not time restricted, we always call it */
110
7.14k
    if (Q->t_final == 0 || Q->t_epoch == 0) {
111
        // Assigning in 2 steps avoids cppcheck warning
112
        // "Overlapping read/write of union is undefined behavior"
113
        // Cf
114
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
115
7.14k
        const auto xyz = pj_vgridshift_forward_3d(coo.lpz, P);
116
7.14k
        coo.xyz = xyz;
117
7.14k
        return;
118
7.14k
    }
119
120
    /* Time restricted - only apply transform if within time bracket */
121
0
    if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) {
122
        // Assigning in 2 steps avoids cppcheck warning
123
        // "Overlapping read/write of union is undefined behavior"
124
        // Cf
125
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
126
0
        const auto xyz = pj_vgridshift_forward_3d(coo.lpz, P);
127
0
        coo.xyz = xyz;
128
0
    }
129
0
}
130
131
0
static void pj_vgridshift_reverse_4d(PJ_COORD &coo, PJ *P) {
132
0
    struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque;
133
134
    /* If transformation is not time restricted, we always call it */
135
0
    if (Q->t_final == 0 || Q->t_epoch == 0) {
136
        // Assigning in 2 steps avoids cppcheck warning
137
        // "Overlapping read/write of union is undefined behavior"
138
        // Cf
139
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
140
0
        const auto lpz = pj_vgridshift_reverse_3d(coo.xyz, P);
141
0
        coo.lpz = lpz;
142
0
        return;
143
0
    }
144
145
    /* Time restricted - only apply transform if within time bracket */
146
0
    if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) {
147
        // Assigning in 2 steps avoids cppcheck warning
148
        // "Overlapping read/write of union is undefined behavior"
149
        // Cf
150
        // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710
151
0
        const auto lpz = pj_vgridshift_reverse_3d(coo.xyz, P);
152
0
        coo.lpz = lpz;
153
0
    }
154
0
}
155
156
11.5k
static PJ *pj_vgridshift_destructor(PJ *P, int errlev) {
157
11.5k
    if (nullptr == P)
158
0
        return nullptr;
159
160
11.5k
    delete static_cast<struct vgridshiftData *>(P->opaque);
161
11.5k
    P->opaque = nullptr;
162
163
11.5k
    return pj_default_destructor(P, errlev);
164
11.5k
}
165
166
0
static void pj_vgridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) {
167
0
    auto Q = (struct vgridshiftData *)P->opaque;
168
0
    for (auto &grid : Q->grids) {
169
0
        grid->reassign_context(ctx);
170
0
    }
171
0
}
172
173
11.5k
PJ *PJ_TRANSFORMATION(vgridshift, 0) {
174
11.5k
    auto Q = new vgridshiftData;
175
11.5k
    P->opaque = (void *)Q;
176
11.5k
    P->destructor = pj_vgridshift_destructor;
177
11.5k
    P->reassign_context = pj_vgridshift_reassign_context;
178
179
11.5k
    if (!pj_param(P->ctx, P->params, "tgrids").i) {
180
36
        proj_log_error(P, _("+grids parameter missing."));
181
36
        return pj_vgridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
182
36
    }
183
184
11.5k
    Q->t_final = pj_parse_t_final(P);
185
186
11.5k
    if (pj_param(P->ctx, P->params, "tt_epoch").i)
187
0
        Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f;
188
189
    /* historical: the forward direction subtracts the grid offset. */
190
11.5k
    Q->forward_multiplier = -1.0;
191
11.5k
    if (pj_param(P->ctx, P->params, "tmultiplier").i) {
192
9.44k
        Q->forward_multiplier = pj_param(P->ctx, P->params, "dmultiplier").f;
193
9.44k
    }
194
195
11.5k
    if (P->ctx->defer_grid_opening) {
196
0
        Q->defer_grid_opening = true;
197
11.5k
    } else {
198
11.5k
        const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s;
199
11.5k
        gMutexVGridShift.lock();
200
11.5k
        const bool isKnownGrid = gKnownGridsVGridShift.find(gridnames) !=
201
11.5k
                                 gKnownGridsVGridShift.end();
202
11.5k
        gMutexVGridShift.unlock();
203
204
11.5k
        if (isKnownGrid) {
205
648
            Q->defer_grid_opening = true;
206
10.8k
        } else {
207
            /* Build gridlist. P->vgridlist_geoid can be empty if +grids only
208
             * ask for optional grids. */
209
10.8k
            Q->grids = pj_vgrid_init(P, "grids");
210
211
            /* Was gridlist compiled properly? */
212
10.8k
            if (proj_errno(P)) {
213
10.6k
                proj_log_error(P, _("could not find required grid(s)."));
214
10.6k
                return pj_vgridshift_destructor(
215
10.6k
                    P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
216
10.6k
            }
217
218
179
            gMutexVGridShift.lock();
219
179
            gKnownGridsVGridShift.insert(gridnames);
220
179
            gMutexVGridShift.unlock();
221
179
        }
222
11.5k
    }
223
224
827
    P->fwd4d = pj_vgridshift_forward_4d;
225
827
    P->inv4d = pj_vgridshift_reverse_4d;
226
827
    P->fwd3d = pj_vgridshift_forward_3d;
227
827
    P->inv3d = pj_vgridshift_reverse_3d;
228
827
    P->fwd = nullptr;
229
827
    P->inv = nullptr;
230
231
827
    P->left = PJ_IO_UNITS_RADIANS;
232
827
    P->right = PJ_IO_UNITS_RADIANS;
233
234
827
    return P;
235
11.5k
}
236
237
10.9k
void pj_clear_vgridshift_knowngrids_cache() {
238
10.9k
    std::lock_guard<std::mutex> lock(gMutexVGridShift);
239
10.9k
    gKnownGridsVGridShift.clear();
240
10.9k
}