Coverage Report

Created: 2026-02-26 07:07

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
122
static struct pj_opaque_affine *initQ() {
114
122
    struct pj_opaque_affine *Q = static_cast<struct pj_opaque_affine *>(
115
122
        calloc(1, sizeof(struct pj_opaque_affine)));
116
122
    if (nullptr == Q)
117
0
        return nullptr;
118
119
    /* default values */
120
122
    Q->forward.s11 = 1.0;
121
122
    Q->forward.s22 = 1.0;
122
122
    Q->forward.s33 = 1.0;
123
122
    Q->forward.tscale = 1.0;
124
125
122
    Q->reverse.s11 = 1.0;
126
122
    Q->reverse.s22 = 1.0;
127
122
    Q->reverse.s33 = 1.0;
128
122
    Q->reverse.tscale = 1.0;
129
130
122
    return Q;
131
122
}
132
133
72
static void computeReverseParameters(PJ *P) {
134
72
    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
72
    const double a = Q->forward.s11;
140
72
    const double b = Q->forward.s12;
141
72
    const double c = Q->forward.s13;
142
72
    const double d = Q->forward.s21;
143
72
    const double e = Q->forward.s22;
144
72
    const double f = Q->forward.s23;
145
72
    const double g = Q->forward.s31;
146
72
    const double h = Q->forward.s32;
147
72
    const double i = Q->forward.s33;
148
72
    const double A = e * i - f * h;
149
72
    const double B = -(d * i - f * g);
150
72
    const double C = (d * h - e * g);
151
72
    const double D = -(b * i - c * h);
152
72
    const double E = (a * i - c * g);
153
72
    const double F = -(a * h - b * g);
154
72
    const double G = b * f - c * e;
155
72
    const double H = -(a * f - c * d);
156
72
    const double I = a * e - b * d;
157
72
    const double det = a * A + b * B + c * C;
158
72
    if (det == 0.0 || Q->forward.tscale == 0.0) {
159
24
        if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) {
160
0
            proj_log_debug(P, "matrix non invertible");
161
0
        }
162
24
        P->inv4d = nullptr;
163
24
        P->inv3d = nullptr;
164
24
        P->inv = nullptr;
165
48
    } else {
166
48
        Q->reverse.s11 = A / det;
167
48
        Q->reverse.s12 = D / det;
168
48
        Q->reverse.s13 = G / det;
169
48
        Q->reverse.s21 = B / det;
170
48
        Q->reverse.s22 = E / det;
171
48
        Q->reverse.s23 = H / det;
172
48
        Q->reverse.s31 = C / det;
173
48
        Q->reverse.s32 = F / det;
174
48
        Q->reverse.s33 = I / det;
175
48
        Q->reverse.tscale = 1.0 / Q->forward.tscale;
176
48
    }
177
72
}
178
179
72
PJ *PJ_TRANSFORMATION(affine, 0 /* no need for ellipsoid */) {
180
72
    struct pj_opaque_affine *Q = initQ();
181
72
    if (nullptr == Q)
182
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
183
72
    P->opaque = (void *)Q;
184
185
72
    P->fwd4d = forward_4d;
186
72
    P->inv4d = reverse_4d;
187
72
    P->fwd3d = forward_3d;
188
72
    P->inv3d = reverse_3d;
189
72
    P->fwd = forward_2d;
190
72
    P->inv = reverse_2d;
191
192
72
    P->left = PJ_IO_UNITS_WHATEVER;
193
72
    P->right = PJ_IO_UNITS_WHATEVER;
194
195
    /* read args */
196
72
    Q->xoff = pj_param(P->ctx, P->params, "dxoff").f;
197
72
    Q->yoff = pj_param(P->ctx, P->params, "dyoff").f;
198
72
    Q->zoff = pj_param(P->ctx, P->params, "dzoff").f;
199
72
    Q->toff = pj_param(P->ctx, P->params, "dtoff").f;
200
201
72
    if (pj_param(P->ctx, P->params, "ts11").i) {
202
18
        Q->forward.s11 = pj_param(P->ctx, P->params, "ds11").f;
203
18
    }
204
72
    Q->forward.s12 = pj_param(P->ctx, P->params, "ds12").f;
205
72
    Q->forward.s13 = pj_param(P->ctx, P->params, "ds13").f;
206
72
    Q->forward.s21 = pj_param(P->ctx, P->params, "ds21").f;
207
72
    if (pj_param(P->ctx, P->params, "ts22").i) {
208
18
        Q->forward.s22 = pj_param(P->ctx, P->params, "ds22").f;
209
18
    }
210
72
    Q->forward.s23 = pj_param(P->ctx, P->params, "ds23").f;
211
72
    Q->forward.s31 = pj_param(P->ctx, P->params, "ds31").f;
212
72
    Q->forward.s32 = pj_param(P->ctx, P->params, "ds32").f;
213
72
    if (pj_param(P->ctx, P->params, "ts33").i) {
214
53
        Q->forward.s33 = pj_param(P->ctx, P->params, "ds33").f;
215
53
    }
216
72
    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
72
    computeReverseParameters(P);
221
222
72
    return P;
223
72
}
224
225
/* Arcsecond to radians */
226
100
#define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0)
227
228
50
PJ *PJ_TRANSFORMATION(geogoffset, 0 /* no need for ellipsoid */) {
229
50
    struct pj_opaque_affine *Q = initQ();
230
50
    if (nullptr == Q)
231
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
232
50
    P->opaque = (void *)Q;
233
234
50
    P->fwd4d = forward_4d;
235
50
    P->inv4d = reverse_4d;
236
50
    P->fwd3d = forward_3d;
237
50
    P->inv3d = reverse_3d;
238
50
    P->fwd = forward_2d;
239
50
    P->inv = reverse_2d;
240
241
50
    P->left = PJ_IO_UNITS_RADIANS;
242
50
    P->right = PJ_IO_UNITS_RADIANS;
243
244
    /* read args */
245
50
    Q->xoff = pj_param(P->ctx, P->params, "ddlon").f * ARCSEC_TO_RAD;
246
50
    Q->yoff = pj_param(P->ctx, P->params, "ddlat").f * ARCSEC_TO_RAD;
247
50
    Q->zoff = pj_param(P->ctx, P->params, "ddh").f;
248
249
50
    return P;
250
50
}