Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proj/src/transformations/affine.cpp
Line
Count
Source
1
/************************************************************************
2
 * Copyright (c) 2018, Even Rouault <even.rouault at spatialys.com>
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a
5
 * copy of this software and associated documentation files (the "Software"),
6
 * to deal in the Software without restriction, including without limitation
7
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
 * and/or sell copies of the Software, and to permit persons to whom the
9
 * Software is furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included
12
 * in all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20
 * DEALINGS IN THE SOFTWARE.
21
 *
22
 ***********************************************************************/
23
24
#include <errno.h>
25
#include <math.h>
26
27
#include "proj.h"
28
#include "proj_internal.h"
29
30
PROJ_HEAD(affine, "Affine transformation");
31
PROJ_HEAD(geogoffset, "Geographic Offset");
32
33
namespace { // anonymous namespace
34
struct pj_affine_coeffs {
35
    double s11;
36
    double s12;
37
    double s13;
38
    double s21;
39
    double s22;
40
    double s23;
41
    double s31;
42
    double s32;
43
    double s33;
44
    double tscale;
45
};
46
} // anonymous namespace
47
48
namespace { // anonymous namespace
49
struct pj_opaque_affine {
50
    double xoff;
51
    double yoff;
52
    double zoff;
53
    double toff;
54
    struct pj_affine_coeffs forward;
55
    struct pj_affine_coeffs reverse;
56
};
57
} // anonymous namespace
58
59
0
static void forward_4d(PJ_COORD &coo, PJ *P) {
60
0
    const struct pj_opaque_affine *Q =
61
0
        (const struct pj_opaque_affine *)P->opaque;
62
0
    const struct pj_affine_coeffs *C = &(Q->forward);
63
0
    const double x = coo.xyz.x;
64
0
    const double y = coo.xyz.y;
65
0
    const double z = coo.xyz.z;
66
0
    coo.xyzt.x = Q->xoff + C->s11 * x + C->s12 * y + C->s13 * z;
67
0
    coo.xyzt.y = Q->yoff + C->s21 * x + C->s22 * y + C->s23 * z;
68
0
    coo.xyzt.z = Q->zoff + C->s31 * x + C->s32 * y + C->s33 * z;
69
0
    coo.xyzt.t = Q->toff + C->tscale * coo.xyzt.t;
70
0
}
71
72
0
static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
73
0
    PJ_COORD point = {{0, 0, 0, 0}};
74
0
    point.lpz = lpz;
75
0
    forward_4d(point, P);
76
0
    return point.xyz;
77
0
}
78
79
0
static PJ_XY forward_2d(PJ_LP lp, PJ *P) {
80
0
    PJ_COORD point = {{0, 0, 0, 0}};
81
0
    point.lp = lp;
82
0
    forward_4d(point, P);
83
0
    return point.xy;
84
0
}
85
86
0
static void reverse_4d(PJ_COORD &coo, PJ *P) {
87
0
    const struct pj_opaque_affine *Q =
88
0
        (const struct pj_opaque_affine *)P->opaque;
89
0
    const struct pj_affine_coeffs *C = &(Q->reverse);
90
0
    double x = coo.xyzt.x - Q->xoff;
91
0
    double y = coo.xyzt.y - Q->yoff;
92
0
    double z = coo.xyzt.z - Q->zoff;
93
0
    coo.xyzt.x = C->s11 * x + C->s12 * y + C->s13 * z;
94
0
    coo.xyzt.y = C->s21 * x + C->s22 * y + C->s23 * z;
95
0
    coo.xyzt.z = C->s31 * x + C->s32 * y + C->s33 * z;
96
0
    coo.xyzt.t = C->tscale * (coo.xyzt.t - Q->toff);
97
0
}
98
99
0
static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
100
0
    PJ_COORD point = {{0, 0, 0, 0}};
101
0
    point.xyz = xyz;
102
0
    reverse_4d(point, P);
103
0
    return point.lpz;
104
0
}
105
106
0
static PJ_LP reverse_2d(PJ_XY xy, PJ *P) {
107
0
    PJ_COORD point = {{0, 0, 0, 0}};
108
0
    point.xy = xy;
109
0
    reverse_4d(point, P);
110
0
    return point.lp;
111
0
}
112
113
143
static struct pj_opaque_affine *initQ() {
114
143
    struct pj_opaque_affine *Q = static_cast<struct pj_opaque_affine *>(
115
143
        calloc(1, sizeof(struct pj_opaque_affine)));
116
143
    if (nullptr == Q)
117
0
        return nullptr;
118
119
    /* default values */
120
143
    Q->forward.s11 = 1.0;
121
143
    Q->forward.s22 = 1.0;
122
143
    Q->forward.s33 = 1.0;
123
143
    Q->forward.tscale = 1.0;
124
125
143
    Q->reverse.s11 = 1.0;
126
143
    Q->reverse.s22 = 1.0;
127
143
    Q->reverse.s33 = 1.0;
128
143
    Q->reverse.tscale = 1.0;
129
130
143
    return Q;
131
143
}
132
133
108
static void computeReverseParameters(PJ *P) {
134
108
    struct pj_opaque_affine *Q = (struct pj_opaque_affine *)P->opaque;
135
136
    /* cf
137
     * https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices
138
     */
139
108
    const double a = Q->forward.s11;
140
108
    const double b = Q->forward.s12;
141
108
    const double c = Q->forward.s13;
142
108
    const double d = Q->forward.s21;
143
108
    const double e = Q->forward.s22;
144
108
    const double f = Q->forward.s23;
145
108
    const double g = Q->forward.s31;
146
108
    const double h = Q->forward.s32;
147
108
    const double i = Q->forward.s33;
148
108
    const double A = e * i - f * h;
149
108
    const double B = -(d * i - f * g);
150
108
    const double C = (d * h - e * g);
151
108
    const double D = -(b * i - c * h);
152
108
    const double E = (a * i - c * g);
153
108
    const double F = -(a * h - b * g);
154
108
    const double G = b * f - c * e;
155
108
    const double H = -(a * f - c * d);
156
108
    const double I = a * e - b * d;
157
108
    const double det = a * A + b * B + c * C;
158
108
    if (det == 0.0 || Q->forward.tscale == 0.0) {
159
107
        if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) {
160
0
            proj_log_debug(P, "matrix non invertible");
161
0
        }
162
107
        P->inv4d = nullptr;
163
107
        P->inv3d = nullptr;
164
107
        P->inv = nullptr;
165
107
    } else {
166
1
        Q->reverse.s11 = A / det;
167
1
        Q->reverse.s12 = D / det;
168
1
        Q->reverse.s13 = G / det;
169
1
        Q->reverse.s21 = B / det;
170
1
        Q->reverse.s22 = E / det;
171
1
        Q->reverse.s23 = H / det;
172
1
        Q->reverse.s31 = C / det;
173
1
        Q->reverse.s32 = F / det;
174
1
        Q->reverse.s33 = I / det;
175
1
        Q->reverse.tscale = 1.0 / Q->forward.tscale;
176
1
    }
177
108
}
178
179
108
PJ *PJ_TRANSFORMATION(affine, 0 /* no need for ellipsoid */) {
180
108
    struct pj_opaque_affine *Q = initQ();
181
108
    if (nullptr == Q)
182
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
183
108
    P->opaque = (void *)Q;
184
185
108
    P->fwd4d = forward_4d;
186
108
    P->inv4d = reverse_4d;
187
108
    P->fwd3d = forward_3d;
188
108
    P->inv3d = reverse_3d;
189
108
    P->fwd = forward_2d;
190
108
    P->inv = reverse_2d;
191
192
108
    P->left = PJ_IO_UNITS_WHATEVER;
193
108
    P->right = PJ_IO_UNITS_WHATEVER;
194
195
    /* read args */
196
108
    Q->xoff = pj_param(P->ctx, P->params, "dxoff").f;
197
108
    Q->yoff = pj_param(P->ctx, P->params, "dyoff").f;
198
108
    Q->zoff = pj_param(P->ctx, P->params, "dzoff").f;
199
108
    Q->toff = pj_param(P->ctx, P->params, "dtoff").f;
200
201
108
    if (pj_param(P->ctx, P->params, "ts11").i) {
202
105
        Q->forward.s11 = pj_param(P->ctx, P->params, "ds11").f;
203
105
    }
204
108
    Q->forward.s12 = pj_param(P->ctx, P->params, "ds12").f;
205
108
    Q->forward.s13 = pj_param(P->ctx, P->params, "ds13").f;
206
108
    Q->forward.s21 = pj_param(P->ctx, P->params, "ds21").f;
207
108
    if (pj_param(P->ctx, P->params, "ts22").i) {
208
105
        Q->forward.s22 = pj_param(P->ctx, P->params, "ds22").f;
209
105
    }
210
108
    Q->forward.s23 = pj_param(P->ctx, P->params, "ds23").f;
211
108
    Q->forward.s31 = pj_param(P->ctx, P->params, "ds31").f;
212
108
    Q->forward.s32 = pj_param(P->ctx, P->params, "ds32").f;
213
108
    if (pj_param(P->ctx, P->params, "ts33").i) {
214
3
        Q->forward.s33 = pj_param(P->ctx, P->params, "ds33").f;
215
3
    }
216
108
    if (pj_param(P->ctx, P->params, "ttscale").i) {
217
0
        Q->forward.tscale = pj_param(P->ctx, P->params, "dtscale").f;
218
0
    }
219
220
108
    computeReverseParameters(P);
221
222
108
    return P;
223
108
}
224
225
/* Arcsecond to radians */
226
70
#define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0)
227
228
35
PJ *PJ_TRANSFORMATION(geogoffset, 0 /* no need for ellipsoid */) {
229
35
    struct pj_opaque_affine *Q = initQ();
230
35
    if (nullptr == Q)
231
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
232
35
    P->opaque = (void *)Q;
233
234
35
    P->fwd4d = forward_4d;
235
35
    P->inv4d = reverse_4d;
236
35
    P->fwd3d = forward_3d;
237
35
    P->inv3d = reverse_3d;
238
35
    P->fwd = forward_2d;
239
35
    P->inv = reverse_2d;
240
241
35
    P->left = PJ_IO_UNITS_RADIANS;
242
35
    P->right = PJ_IO_UNITS_RADIANS;
243
244
    /* read args */
245
35
    Q->xoff = pj_param(P->ctx, P->params, "ddlon").f * ARCSEC_TO_RAD;
246
35
    Q->yoff = pj_param(P->ctx, P->params, "ddlat").f * ARCSEC_TO_RAD;
247
35
    Q->zoff = pj_param(P->ctx, P->params, "ddh").f;
248
249
35
    return P;
250
35
}