Coverage Report

Created: 2026-08-31 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/conversions/axisswap.cpp
Line
Count
Source
1
/***********************************************************************
2
3
        Axis order operation for use with transformation pipelines.
4
5
                Kristian Evers, kreve@sdfe.dk, 2017-10-31
6
7
************************************************************************
8
9
Change the order and sign of 2,3 or 4 axes. Each of the possible four
10
axes are numbered with 1-4, such that the first input axis is 1, the
11
second is 2 and so on. The output ordering is controlled by a list of the
12
input axes re-ordered to the new mapping. Examples:
13
14
Reversing the order of the axes:
15
16
    +proj=axisswap +order=4,3,2,1
17
18
Swapping the first two axes (x and y):
19
20
    +proj=axisswap +order=2,1,3,4
21
22
The direction, or sign, of an axis can be changed by adding a minus in
23
front of the axis-number:
24
25
    +proj=axisswap +order=1,-2,3,4
26
27
It is only necessary to specify the axes that are affected by the swap
28
operation:
29
30
    +proj=axisswap +order=2,1
31
32
************************************************************************
33
* Copyright (c) 2017, Kristian Evers / SDFE
34
*
35
* Permission is hereby granted, free of charge, to any person obtaining a
36
* copy of this software and associated documentation files (the "Software"),
37
* to deal in the Software without restriction, including without limitation
38
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
39
* and/or sell copies of the Software, and to permit persons to whom the
40
* Software is furnished to do so, subject to the following conditions:
41
*
42
* The above copyright notice and this permission notice shall be included
43
* in all copies or substantial portions of the Software.
44
*
45
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
46
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
48
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
50
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
51
* DEALINGS IN THE SOFTWARE.
52
*
53
***********************************************************************/
54
55
#include <errno.h>
56
#include <stdlib.h>
57
#include <string.h>
58
59
#include <algorithm>
60
61
#include "proj.h"
62
#include "proj_internal.h"
63
64
PROJ_HEAD(axisswap, "Axis ordering");
65
66
namespace { // anonymous namespace
67
struct pj_axisswap_data {
68
    unsigned int axis[4];
69
    int sign[4];
70
};
71
} // anonymous namespace
72
73
10.9k
static int sign(int x) { return (x > 0) - (x < 0); }
74
75
33.4k
static PJ_XY pj_axisswap_forward_2d(PJ_LP lp, PJ *P) {
76
33.4k
    struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque;
77
33.4k
    PJ_XY xy;
78
79
33.4k
    double in[2] = {lp.lam, lp.phi};
80
33.4k
    xy.x = in[Q->axis[0]] * Q->sign[0];
81
33.4k
    xy.y = in[Q->axis[1]] * Q->sign[1];
82
33.4k
    return xy;
83
33.4k
}
84
85
0
static PJ_LP pj_axisswap_reverse_2d(PJ_XY xy, PJ *P) {
86
0
    struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque;
87
0
    unsigned int i;
88
0
    PJ_COORD out, in;
89
90
0
    in.v[0] = xy.x;
91
0
    in.v[1] = xy.y;
92
0
    out = proj_coord_error();
93
94
0
    for (i = 0; i < 2; i++)
95
0
        out.v[Q->axis[i]] = in.v[i] * Q->sign[i];
96
97
0
    return out.lp;
98
0
}
99
100
19.3k
static PJ_XYZ pj_axisswap_forward_3d(PJ_LPZ lpz, PJ *P) {
101
19.3k
    struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque;
102
19.3k
    unsigned int i;
103
19.3k
    PJ_COORD out, in;
104
105
19.3k
    in.v[0] = lpz.lam;
106
19.3k
    in.v[1] = lpz.phi;
107
19.3k
    in.v[2] = lpz.z;
108
19.3k
    out = proj_coord_error();
109
110
77.2k
    for (i = 0; i < 3; i++)
111
57.9k
        out.v[i] = in.v[Q->axis[i]] * Q->sign[i];
112
113
19.3k
    return out.xyz;
114
19.3k
}
115
116
0
static PJ_LPZ pj_axisswap_reverse_3d(PJ_XYZ xyz, PJ *P) {
117
0
    struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque;
118
0
    unsigned int i;
119
0
    PJ_COORD in, out;
120
121
0
    out = proj_coord_error();
122
0
    in.v[0] = xyz.x;
123
0
    in.v[1] = xyz.y;
124
0
    in.v[2] = xyz.z;
125
126
0
    for (i = 0; i < 3; i++)
127
0
        out.v[Q->axis[i]] = in.v[i] * Q->sign[i];
128
129
0
    return out.lpz;
130
0
}
131
132
284k
static void swap_xy_4d(PJ_COORD &coo, PJ *) {
133
284k
    std::swap(coo.xyzt.x, coo.xyzt.y);
134
284k
}
135
136
0
static void pj_axisswap_forward_4d(PJ_COORD &coo, PJ *P) {
137
0
    struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque;
138
0
    unsigned int i;
139
0
    PJ_COORD out;
140
141
0
    for (i = 0; i < 4; i++)
142
0
        out.v[i] = coo.v[Q->axis[i]] * Q->sign[i];
143
0
    coo = out;
144
0
}
145
146
0
static void pj_axisswap_reverse_4d(PJ_COORD &coo, PJ *P) {
147
0
    struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque;
148
0
    unsigned int i;
149
0
    PJ_COORD out;
150
151
0
    for (i = 0; i < 4; i++)
152
0
        out.v[Q->axis[i]] = coo.v[i] * Q->sign[i];
153
154
0
    coo = out;
155
0
}
156
157
/***********************************************************************/
158
7.43k
PJ *PJ_CONVERSION(axisswap, 0) {
159
    /***********************************************************************/
160
7.43k
    struct pj_axisswap_data *Q = static_cast<struct pj_axisswap_data *>(
161
7.43k
        calloc(1, sizeof(struct pj_axisswap_data)));
162
7.43k
    char *s;
163
7.43k
    unsigned int i, j, n = 0;
164
165
7.43k
    if (nullptr == Q)
166
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
167
7.43k
    P->opaque = (void *)Q;
168
169
    /* +order and +axis are mutually exclusive */
170
7.43k
    if (!pj_param_exists(P->params, "order") ==
171
7.43k
        !pj_param_exists(P->params, "axis")) {
172
8
        proj_log_error(P,
173
8
                       _("must provide EITHER 'order' OR 'axis' parameter."));
174
8
        return pj_default_destructor(
175
8
            P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS);
176
8
    }
177
178
    /* fill axis list with indices from 4-7 to simplify duplicate search further
179
     * down */
180
37.1k
    for (i = 0; i < 4; i++) {
181
29.6k
        Q->axis[i] = i + 4;
182
29.6k
        Q->sign[i] = 1;
183
29.6k
    }
184
185
    /* if the "order" parameter is used */
186
7.42k
    if (pj_param_exists(P->params, "order")) {
187
        /* read axis order */
188
5.45k
        char *order = pj_param(P->ctx, P->params, "sorder").s;
189
190
        /* check that all characters are valid */
191
24.3k
        for (i = 0; i < strlen(order); i++)
192
18.9k
            if (strchr("1234-,", order[i]) == nullptr) {
193
7
                proj_log_error(P, _("unknown axis '%c'"), order[i]);
194
7
                return pj_default_destructor(
195
7
                    P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
196
7
            }
197
198
        /* read axes numbers and signs */
199
5.44k
        s = order;
200
5.44k
        n = 0;
201
16.3k
        while (*s != '\0' && n < 4) {
202
10.9k
            Q->axis[n] = abs(atoi(s)) - 1;
203
10.9k
            if (Q->axis[n] > 3) {
204
8
                proj_log_error(P, _("invalid axis '%d'"), Q->axis[n]);
205
8
                return pj_default_destructor(
206
8
                    P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
207
8
            }
208
10.9k
            Q->sign[n++] = sign(atoi(s));
209
24.2k
            while (*s != '\0' && *s != ',')
210
13.3k
                s++;
211
10.9k
            if (*s == ',')
212
5.47k
                s++;
213
10.9k
        }
214
5.44k
    }
215
216
    /* if the "axis" parameter is used */
217
7.40k
    if (pj_param_exists(P->params, "axis")) {
218
        /* parse the classic PROJ.4 enu axis specification */
219
7.89k
        for (i = 0; i < 3; i++) {
220
5.91k
            switch (P->axis[i]) {
221
1.46k
            case 'w':
222
1.46k
                Q->sign[i] = -1;
223
1.46k
                Q->axis[i] = 0;
224
1.46k
                break;
225
511
            case 'e':
226
511
                Q->sign[i] = 1;
227
511
                Q->axis[i] = 0;
228
511
                break;
229
1.33k
            case 's':
230
1.33k
                Q->sign[i] = -1;
231
1.33k
                Q->axis[i] = 1;
232
1.33k
                break;
233
631
            case 'n':
234
631
                Q->sign[i] = 1;
235
631
                Q->axis[i] = 1;
236
631
                break;
237
1.08k
            case 'd':
238
1.08k
                Q->sign[i] = -1;
239
1.08k
                Q->axis[i] = 2;
240
1.08k
                break;
241
885
            case 'u':
242
885
                Q->sign[i] = 1;
243
885
                Q->axis[i] = 2;
244
885
                break;
245
0
            default:
246
0
                proj_log_error(P, _("unknown axis '%c'"), P->axis[i]);
247
0
                return pj_default_destructor(
248
0
                    P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
249
5.91k
            }
250
5.91k
        }
251
1.97k
        n = 3;
252
1.97k
    }
253
254
    /* check for duplicate axes */
255
36.9k
    for (i = 0; i < 4; i++)
256
147k
        for (j = 0; j < 4; j++) {
257
118k
            if (i == j)
258
29.6k
                continue;
259
88.7k
            if (Q->axis[i] == Q->axis[j]) {
260
14
                proj_log_error(P, _("axisswap: duplicate axes specified"));
261
14
                return pj_default_destructor(
262
14
                    P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
263
14
            }
264
88.7k
        }
265
266
    /* only map fwd/inv functions that are possible with the given axis setup */
267
7.39k
    if (n == 4) {
268
3
        P->fwd4d = pj_axisswap_forward_4d;
269
3
        P->inv4d = pj_axisswap_reverse_4d;
270
3
    }
271
7.39k
    if (n == 3 && Q->axis[0] < 3 && Q->axis[1] < 3 && Q->axis[2] < 3) {
272
1.97k
        P->fwd3d = pj_axisswap_forward_3d;
273
1.97k
        P->inv3d = pj_axisswap_reverse_3d;
274
1.97k
    }
275
7.39k
    if (n == 2) {
276
5.41k
        if (Q->axis[0] == 1 && Q->sign[0] == 1 && Q->axis[1] == 0 &&
277
4.12k
            Q->sign[1] == 1) {
278
4.09k
            P->fwd4d = swap_xy_4d;
279
4.09k
            P->inv4d = swap_xy_4d;
280
4.09k
        } else if (Q->axis[0] < 2 && Q->axis[1] < 2) {
281
1.32k
            P->fwd = pj_axisswap_forward_2d;
282
1.32k
            P->inv = pj_axisswap_reverse_2d;
283
1.32k
        }
284
5.41k
    }
285
286
7.39k
    if (P->fwd4d == nullptr && P->fwd3d == nullptr && P->fwd == nullptr) {
287
11
        proj_log_error(P, _("axisswap: bad axis order"));
288
11
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
289
11
    }
290
291
7.38k
    if (pj_param(P->ctx, P->params, "tangularunits").i) {
292
0
        P->left = PJ_IO_UNITS_RADIANS;
293
0
        P->right = PJ_IO_UNITS_RADIANS;
294
7.38k
    } else {
295
7.38k
        P->left = PJ_IO_UNITS_WHATEVER;
296
7.38k
        P->right = PJ_IO_UNITS_WHATEVER;
297
7.38k
    }
298
299
    /* Preparation and finalization steps are skipped, since the reason   */
300
    /* d'etre of axisswap is to bring input coordinates in line with the  */
301
    /* the internally expected order (ENU), such that handling of offsets */
302
    /* etc. can be done correctly in a later step of a pipeline */
303
7.38k
    P->skip_fwd_prepare = 1;
304
7.38k
    P->skip_fwd_finalize = 1;
305
7.38k
    P->skip_inv_prepare = 1;
306
7.38k
    P->skip_inv_finalize = 1;
307
308
7.38k
    return P;
309
7.39k
}