Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proj/src/internal.cpp
Line
Count
Source
1
/******************************************************************************
2
 * Project:  PROJ.4
3
 * Purpose:  This is primarily material originating from pj_obs_api.c
4
 *           (now proj_4D_api.c), that does not fit into the API
5
 *           category. Hence this pile of tubings and fittings for
6
 *           PROJ.4 internal plumbing.
7
 *
8
 * Author:   Thomas Knudsen,  thokn@sdfe.dk,  2017-07-05
9
 *
10
 ******************************************************************************
11
 * Copyright (c) 2016, 2017, 2018, Thomas Knudsen/SDFE
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a
14
 * copy of this software and associated documentation files (the "Software"),
15
 * to deal in the Software without restriction, including without limitation
16
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17
 * and/or sell copies of the Software, and to permit persons to whom the
18
 * Software is furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included
21
 * in all copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29
 * DEALINGS IN THE SOFTWARE.
30
 *****************************************************************************/
31
32
#define FROM_PROJ_CPP
33
34
#include <ctype.h>
35
#include <errno.h>
36
#include <math.h>
37
#include <stdarg.h>
38
#include <stddef.h>
39
#include <stdlib.h>
40
#include <string.h>
41
42
#include "geodesic.h"
43
#include "proj_internal.h"
44
45
#include "proj/internal/internal.hpp"
46
47
using namespace NS_PROJ::internal;
48
49
0
enum pj_io_units pj_left(PJ *P) {
50
0
    enum pj_io_units u = P->inverted ? P->right : P->left;
51
0
    if (u == PJ_IO_UNITS_CLASSIC)
52
0
        return PJ_IO_UNITS_PROJECTED;
53
0
    return u;
54
0
}
55
56
0
enum pj_io_units pj_right(PJ *P) {
57
0
    enum pj_io_units u = P->inverted ? P->left : P->right;
58
0
    if (u == PJ_IO_UNITS_CLASSIC)
59
0
        return PJ_IO_UNITS_PROJECTED;
60
0
    return u;
61
0
}
62
63
/* Work around non-constness of MSVC HUGE_VAL by providing functions rather than
64
 * constants */
65
0
PJ_COORD proj_coord_error(void) {
66
0
    PJ_COORD c;
67
0
    c.v[0] = c.v[1] = c.v[2] = c.v[3] = HUGE_VAL;
68
0
    return c;
69
0
}
70
71
/**************************************************************************************/
72
0
PJ_COORD pj_approx_2D_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coo) {
73
    /***************************************************************************************
74
    Behave mostly as proj_trans, but attempt to use 2D interfaces only.
75
    Used in gie.c, to enforce testing 2D code, and by PJ_pipeline.c to implement
76
    chained calls starting out with a call to its 2D interface.
77
    ***************************************************************************************/
78
0
    if (nullptr == P)
79
0
        return coo;
80
0
    if (P->inverted)
81
0
        direction = static_cast<PJ_DIRECTION>(-direction);
82
0
    switch (direction) {
83
0
    case PJ_FWD: {
84
0
        const auto xy = pj_fwd(coo.lp, P);
85
0
        coo.xy = xy;
86
0
        return coo;
87
0
    }
88
0
    case PJ_INV: {
89
0
        const auto lp = pj_inv(coo.xy, P);
90
0
        coo.lp = lp;
91
0
        return coo;
92
0
    }
93
0
    case PJ_IDENT:
94
0
        break;
95
0
    }
96
0
    return coo;
97
0
}
98
99
/**************************************************************************************/
100
0
PJ_COORD pj_approx_3D_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coo) {
101
    /***************************************************************************************
102
    Companion to pj_approx_2D_trans.
103
104
    Behave mostly as proj_trans, but attempt to use 3D interfaces only.
105
    Used in gie.c, to enforce testing 3D code, and by PJ_pipeline.c to implement
106
    chained calls starting out with a call to its 3D interface.
107
    ***************************************************************************************/
108
0
    if (nullptr == P)
109
0
        return coo;
110
0
    if (P->inverted)
111
0
        direction = static_cast<PJ_DIRECTION>(-direction);
112
0
    switch (direction) {
113
0
    case PJ_FWD: {
114
0
        const auto xyz = pj_fwd3d(coo.lpz, P);
115
0
        coo.xyz = xyz;
116
0
        return coo;
117
0
    }
118
0
    case PJ_INV: {
119
0
        const auto lpz = pj_inv3d(coo.xyz, P);
120
0
        coo.lpz = lpz;
121
0
        return coo;
122
0
    }
123
0
    case PJ_IDENT:
124
0
        break;
125
0
    }
126
0
    return coo;
127
0
}
128
129
/**************************************************************************************/
130
0
int pj_has_inverse(PJ *P) {
131
    /***************************************************************************************
132
    Check if a a PJ has an inverse.
133
    ***************************************************************************************/
134
0
    return ((P->inverted && (P->fwd || P->fwd3d || P->fwd4d)) ||
135
0
            (P->inv || P->inv3d || P->inv4d));
136
0
}
137
138
/* Move P to a new context - or to the default context if 0 is specified */
139
0
void proj_context_set(PJ *P, PJ_CONTEXT *ctx) {
140
0
    if (nullptr == ctx)
141
0
        ctx = pj_get_default_ctx();
142
0
    proj_assign_context(P, ctx);
143
0
}
144
145
0
void proj_context_inherit(PJ *parent, PJ *child) {
146
0
    if (nullptr == parent)
147
0
        proj_assign_context(child, pj_get_default_ctx());
148
0
    else
149
0
        proj_assign_context(child, pj_get_ctx(parent));
150
0
}
151
152
/*****************************************************************************/
153
29.2k
char *pj_chomp(char *c) {
154
    /******************************************************************************
155
    Strip pre- and postfix whitespace. Inline comments (indicated by '#') are
156
    considered whitespace.
157
    ******************************************************************************/
158
29.2k
    size_t i, n;
159
29.2k
    char *comment;
160
29.2k
    char *start = c;
161
162
29.2k
    if (nullptr == c)
163
0
        return nullptr;
164
165
29.2k
    comment = strchr(c, '#');
166
29.2k
    if (comment)
167
0
        *comment = 0;
168
169
29.2k
    n = strlen(c);
170
29.2k
    if (0 == n)
171
0
        return c;
172
173
    /* Eliminate postfix whitespace */
174
39.0k
    for (i = n - 1; (i > 0) && (isspace(c[i]) || ';' == c[i]); i--)
175
9.76k
        c[i] = 0;
176
177
    /* Find start of non-whitespace */
178
29.2k
    while (0 != *start && (';' == *start || isspace(*start)))
179
0
        start++;
180
181
29.2k
    n = strlen(start);
182
29.2k
    if (0 == n) {
183
0
        c[0] = 0;
184
0
        return c;
185
0
    }
186
187
29.2k
    memmove(c, start, n + 1);
188
29.2k
    return c;
189
29.2k
}
190
191
/*****************************************************************************/
192
29.2k
char *pj_shrink(char *c) {
193
    /******************************************************************************
194
    Collapse repeated whitespace. Remove '+' and ';'. Make ',' and '=' greedy,
195
    consuming their surrounding whitespace.
196
    ******************************************************************************/
197
29.2k
    size_t i, j, n;
198
199
    /* Flag showing that a whitespace (ws) has been written after last non-ws */
200
29.2k
    bool ws = false;
201
202
29.2k
    if (nullptr == c)
203
0
        return nullptr;
204
205
29.2k
    pj_chomp(c);
206
29.2k
    n = strlen(c);
207
29.2k
    if (n == 0)
208
0
        return c;
209
210
    /* First collapse repeated whitespace (including +/;) */
211
29.2k
    i = 0;
212
29.2k
    bool in_string = false;
213
1.27M
    for (j = 0; j < n; j++) {
214
215
1.24M
        if (in_string) {
216
0
            if (c[j] == '"' && c[j + 1] == '"') {
217
0
                c[i++] = c[j];
218
0
                j++;
219
0
            } else if (c[j] == '"') {
220
0
                in_string = false;
221
0
            }
222
0
            c[i++] = c[j];
223
0
            continue;
224
0
        }
225
226
        /* Eliminate prefix '+', only if preceded by whitespace */
227
        /* (i.e. keep it in 1.23e+08) */
228
1.24M
        if ((i > 0) && ('+' == c[j]) && ws)
229
9.76k
            c[j] = ' ';
230
1.24M
        if ((i == 0) && ('+' == c[j]))
231
0
            c[j] = ' ';
232
233
        // Detect a string beginning after '='
234
1.24M
        if (c[j] == '"' && i > 0 && c[i - 1] == '=') {
235
0
            in_string = true;
236
0
            ws = false;
237
0
            c[i++] = c[j];
238
0
            continue;
239
0
        }
240
241
1.24M
        if (isspace(c[j]) || ';' == c[j]) {
242
78.0k
            if (false == ws && (i > 0))
243
68.3k
                c[i++] = ' ';
244
78.0k
            ws = true;
245
78.0k
            continue;
246
1.17M
        } else {
247
1.17M
            ws = false;
248
1.17M
            c[i++] = c[j];
249
1.17M
        }
250
1.24M
    }
251
29.2k
    c[i] = 0;
252
29.2k
    n = strlen(c);
253
254
    /* Then make ',' and '=' greedy */
255
29.2k
    i = 0;
256
1.26M
    for (j = 0; j < n; j++) {
257
1.23M
        if (i == 0) {
258
29.2k
            c[i++] = c[j];
259
29.2k
            continue;
260
29.2k
        }
261
262
        /* Skip space before '='/',' */
263
1.21M
        if ('=' == c[j] || ',' == c[j]) {
264
87.8k
            if (c[i - 1] == ' ')
265
0
                c[i - 1] = c[j];
266
87.8k
            else
267
87.8k
                c[i++] = c[j];
268
87.8k
            continue;
269
87.8k
        }
270
271
1.12M
        if (' ' == c[j] && ('=' == c[i - 1] || ',' == c[i - 1]))
272
0
            continue;
273
274
1.12M
        c[i++] = c[j];
275
1.12M
    }
276
29.2k
    c[i] = 0;
277
29.2k
    return c;
278
29.2k
}
279
280
/*****************************************************************************/
281
19.5k
size_t pj_trim_argc(char *args) {
282
    /******************************************************************************
283
    Trim all unnecessary whitespace (and non-essential syntactic tokens) from
284
    the argument string, args, and count its number of elements.
285
    ******************************************************************************/
286
19.5k
    size_t i, m, n;
287
19.5k
    pj_shrink(args);
288
19.5k
    n = strlen(args);
289
19.5k
    if (n == 0)
290
0
        return 0;
291
19.5k
    bool in_string = false;
292
936k
    for (i = m = 0; i < n; i++) {
293
917k
        if (in_string) {
294
0
            if (args[i] == '"' && args[i + 1] == '"') {
295
0
                i++;
296
0
            } else if (args[i] == '"') {
297
0
                in_string = false;
298
0
            }
299
917k
        } else if (args[i] == '=' && args[i + 1] == '"') {
300
0
            i++;
301
0
            in_string = true;
302
917k
        } else if (' ' == args[i]) {
303
48.8k
            args[i] = 0;
304
48.8k
            m++;
305
48.8k
        }
306
917k
    }
307
19.5k
    return m + 1;
308
19.5k
}
309
310
68.3k
static void unquote_string(char *param_str) {
311
312
68.3k
    size_t len = strlen(param_str);
313
    // Remove leading and terminating spaces after equal sign
314
68.3k
    const char *equal = strstr(param_str, "=\"");
315
68.3k
    if (equal && equal - param_str + 1 >= 2 && param_str[len - 1] == '"') {
316
0
        size_t dst = equal + 1 - param_str;
317
0
        size_t src = dst + 1;
318
0
        for (; param_str[src]; dst++, src++) {
319
0
            if (param_str[src] == '"') {
320
0
                if (param_str[src + 1] == '"') {
321
0
                    src++;
322
0
                } else {
323
0
                    break;
324
0
                }
325
0
            }
326
0
            param_str[dst] = param_str[src];
327
0
        }
328
0
        param_str[dst] = '\0';
329
0
    }
330
68.3k
}
331
332
/*****************************************************************************/
333
19.5k
char **pj_trim_argv(size_t argc, char *args) {
334
    /******************************************************************************
335
    Create an argv-style array from elements placed in the argument string,
336
    args.
337
338
    args is a trimmed string as returned by pj_trim_argc(), and argc is the
339
    number of trimmed strings found (i.e. the return value of pj_trim_args()).
340
    Hence, int argc    = pj_trim_argc (args); char **argv = pj_trim_argv (argc,
341
    args); will produce a classic style (argc, argv) pair from a string of
342
    whitespace separated args. No new memory is allocated for storing the
343
    individual args (they stay in the args string), but for the pointers to the
344
    args a new array is allocated and returned.
345
346
    It is the duty of the caller to free this array.
347
    ******************************************************************************/
348
349
19.5k
    if (nullptr == args)
350
0
        return nullptr;
351
19.5k
    if (0 == argc)
352
0
        return nullptr;
353
354
    /* turn the input string into an array of strings */
355
19.5k
    char **argv = (char **)calloc(argc, sizeof(char *));
356
19.5k
    if (nullptr == argv)
357
0
        return nullptr;
358
87.8k
    for (size_t i = 0, j = 0; j < argc; j++) {
359
68.3k
        argv[j] = args + i;
360
68.3k
        char *str = argv[j];
361
68.3k
        size_t nLen = strlen(str);
362
68.3k
        i += nLen + 1;
363
68.3k
        unquote_string(str);
364
68.3k
    }
365
19.5k
    return argv;
366
19.5k
}
367
368
/*****************************************************************************/
369
39.0k
std::string pj_double_quote_string_param_if_needed(const std::string &str) {
370
    /*****************************************************************************/
371
39.0k
    if (str.find(' ') == std::string::npos) {
372
39.0k
        return str;
373
39.0k
    }
374
0
    std::string ret;
375
0
    ret += '"';
376
0
    ret += replaceAll(str, "\"", "\"\"");
377
0
    ret += '"';
378
0
    return ret;
379
39.0k
}
380
381
/*****************************************************************************/
382
9.76k
char *pj_make_args(size_t argc, char **argv) {
383
    /******************************************************************************
384
    pj_make_args is the inverse of the pj_trim_argc/pj_trim_argv combo: It
385
    converts free format command line input to something proj_create can
386
    consume.
387
388
    Allocates, and returns, an array of char, large enough to hold a whitespace
389
    separated copy of the args in argv. It is the duty of the caller to free
390
    this array.
391
    ******************************************************************************/
392
9.76k
    try {
393
9.76k
        std::string s;
394
39.0k
        for (size_t i = 0; i < argc; i++) {
395
29.2k
            const char *equal = strchr(argv[i], '=');
396
29.2k
            if (equal) {
397
29.2k
                s += std::string(argv[i], equal - argv[i] + 1);
398
29.2k
                s += pj_double_quote_string_param_if_needed(equal + 1);
399
29.2k
            } else {
400
0
                s += argv[i];
401
0
            }
402
29.2k
            s += ' ';
403
29.2k
        }
404
405
9.76k
        char *p = pj_strdup(s.c_str());
406
9.76k
        return pj_shrink(p);
407
9.76k
    } catch (const std::exception &) {
408
0
        return nullptr;
409
0
    }
410
9.76k
}
411
412
/*****************************************************************************/
413
48.8k
void proj_context_errno_set(PJ_CONTEXT *ctx, int err) {
414
    /******************************************************************************
415
    Raise an error directly on a context, without going through a PJ belonging
416
    to that context.
417
    ******************************************************************************/
418
48.8k
    if (nullptr == ctx)
419
0
        ctx = pj_get_default_ctx();
420
48.8k
    ctx->last_errno = err;
421
48.8k
    if (err == 0)
422
48.8k
        return;
423
48.8k
    errno = err;
424
0
}