Coverage Report

Created: 2026-02-14 06:52

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
60.0k
enum pj_io_units pj_left(PJ *P) {
50
60.0k
    enum pj_io_units u = P->inverted ? P->right : P->left;
51
60.0k
    if (u == PJ_IO_UNITS_CLASSIC)
52
1.46k
        return PJ_IO_UNITS_PROJECTED;
53
58.5k
    return u;
54
60.0k
}
55
56
48.9k
enum pj_io_units pj_right(PJ *P) {
57
48.9k
    enum pj_io_units u = P->inverted ? P->left : P->right;
58
48.9k
    if (u == PJ_IO_UNITS_CLASSIC)
59
791
        return PJ_IO_UNITS_PROJECTED;
60
48.1k
    return u;
61
48.9k
}
62
63
/* Work around non-constness of MSVC HUGE_VAL by providing functions rather than
64
 * constants */
65
6
PJ_COORD proj_coord_error(void) {
66
6
    PJ_COORD c;
67
6
    c.v[0] = c.v[1] = c.v[2] = c.v[3] = HUGE_VAL;
68
6
    return c;
69
6
}
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
16.6k
int pj_has_inverse(PJ *P) {
131
    /***************************************************************************************
132
    Check if a a PJ has an inverse.
133
    ***************************************************************************************/
134
16.6k
    return ((P->inverted && (P->fwd || P->fwd3d || P->fwd4d)) ||
135
10.3k
            (P->inv || P->inv3d || P->inv4d));
136
16.6k
}
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
206k
char *pj_chomp(char *c) {
154
    /******************************************************************************
155
    Strip pre- and postfix whitespace. Inline comments (indicated by '#') are
156
    considered whitespace.
157
    ******************************************************************************/
158
206k
    size_t i, n;
159
206k
    char *comment;
160
206k
    char *start = c;
161
162
206k
    if (nullptr == c)
163
0
        return nullptr;
164
165
206k
    comment = strchr(c, '#');
166
206k
    if (comment)
167
2.09k
        *comment = 0;
168
169
206k
    n = strlen(c);
170
206k
    if (0 == n)
171
1.93k
        return c;
172
173
    /* Eliminate postfix whitespace */
174
247k
    for (i = n - 1; (i > 0) && (isspace(c[i]) || ';' == c[i]); i--)
175
43.8k
        c[i] = 0;
176
177
    /* Find start of non-whitespace */
178
214k
    while (0 != *start && (';' == *start || isspace(*start)))
179
9.97k
        start++;
180
181
204k
    n = strlen(start);
182
204k
    if (0 == n) {
183
1
        c[0] = 0;
184
1
        return c;
185
1
    }
186
187
204k
    memmove(c, start, n + 1);
188
204k
    return c;
189
204k
}
190
191
/*****************************************************************************/
192
200k
char *pj_shrink(char *c) {
193
    /******************************************************************************
194
    Collapse repeated whitespace. Remove '+' and ';'. Make ',' and '=' greedy,
195
    consuming their surrounding whitespace.
196
    ******************************************************************************/
197
200k
    size_t i, j, n;
198
199
    /* Flag showing that a whitespace (ws) has been written after last non-ws */
200
200k
    bool ws = false;
201
202
200k
    if (nullptr == c)
203
0
        return nullptr;
204
205
200k
    pj_chomp(c);
206
200k
    n = strlen(c);
207
200k
    if (n == 0)
208
1
        return c;
209
210
    /* First collapse repeated whitespace (including +/;) */
211
200k
    i = 0;
212
200k
    bool in_string = false;
213
38.8M
    for (j = 0; j < n; j++) {
214
215
38.6M
        if (in_string) {
216
7.37M
            if (c[j] == '"' && c[j + 1] == '"') {
217
624k
                c[i++] = c[j];
218
624k
                j++;
219
6.74M
            } else if (c[j] == '"') {
220
80.2k
                in_string = false;
221
80.2k
            }
222
7.37M
            c[i++] = c[j];
223
7.37M
            continue;
224
7.37M
        }
225
226
        /* Eliminate prefix '+', only if preceded by whitespace */
227
        /* (i.e. keep it in 1.23e+08) */
228
31.2M
        if ((i > 0) && ('+' == c[j]) && ws)
229
551k
            c[j] = ' ';
230
31.2M
        if ((i == 0) && ('+' == c[j]))
231
45.4k
            c[j] = ' ';
232
233
        // Detect a string beginning after '='
234
31.2M
        if (c[j] == '"' && i > 0 && c[i - 1] == '=') {
235
82.7k
            in_string = true;
236
82.7k
            ws = false;
237
82.7k
            c[i++] = c[j];
238
82.7k
            continue;
239
82.7k
        }
240
241
31.2M
        if (isspace(c[j]) || ';' == c[j]) {
242
3.86M
            if (false == ws && (i > 0))
243
2.32M
                c[i++] = ' ';
244
3.86M
            ws = true;
245
3.86M
            continue;
246
27.3M
        } else {
247
27.3M
            ws = false;
248
27.3M
            c[i++] = c[j];
249
27.3M
        }
250
31.2M
    }
251
200k
    c[i] = 0;
252
200k
    n = strlen(c);
253
254
    /* Then make ',' and '=' greedy */
255
200k
    i = 0;
256
37.9M
    for (j = 0; j < n; j++) {
257
37.7M
        if (i == 0) {
258
200k
            c[i++] = c[j];
259
200k
            continue;
260
200k
        }
261
262
        /* Skip space before '='/',' */
263
37.5M
        if ('=' == c[j] || ',' == c[j]) {
264
1.81M
            if (c[i - 1] == ' ')
265
19.6k
                c[i - 1] = c[j];
266
1.79M
            else
267
1.79M
                c[i++] = c[j];
268
1.81M
            continue;
269
1.81M
        }
270
271
35.7M
        if (' ' == c[j] && ('=' == c[i - 1] || ',' == c[i - 1]))
272
39.6k
            continue;
273
274
35.6M
        c[i++] = c[j];
275
35.6M
    }
276
200k
    c[i] = 0;
277
200k
    return c;
278
200k
}
279
280
/*****************************************************************************/
281
157k
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
157k
    size_t i, m, n;
287
157k
    pj_shrink(args);
288
157k
    n = strlen(args);
289
157k
    if (n == 0)
290
0
        return 0;
291
157k
    bool in_string = false;
292
28.3M
    for (i = m = 0; i < n; i++) {
293
28.1M
        if (in_string) {
294
4.68M
            if (args[i] == '"' && args[i + 1] == '"') {
295
422k
                i++;
296
4.26M
            } else if (args[i] == '"') {
297
51.9k
                in_string = false;
298
51.9k
            }
299
23.4M
        } else if (args[i] == '=' && args[i + 1] == '"') {
300
54.2k
            i++;
301
54.2k
            in_string = true;
302
23.4M
        } else if (' ' == args[i]) {
303
1.95M
            args[i] = 0;
304
1.95M
            m++;
305
1.95M
        }
306
28.1M
    }
307
157k
    return m + 1;
308
157k
}
309
310
2.10M
static void unquote_string(char *param_str) {
311
312
2.10M
    size_t len = strlen(param_str);
313
    // Remove leading and terminating spaces after equal sign
314
2.10M
    const char *equal = strstr(param_str, "=\"");
315
2.10M
    if (equal && equal - param_str + 1 >= 2 && param_str[len - 1] == '"') {
316
52.2k
        size_t dst = equal + 1 - param_str;
317
52.2k
        size_t src = dst + 1;
318
4.25M
        for (; param_str[src]; dst++, src++) {
319
4.25M
            if (param_str[src] == '"') {
320
269k
                if (param_str[src + 1] == '"') {
321
217k
                    src++;
322
217k
                } else {
323
51.8k
                    break;
324
51.8k
                }
325
269k
            }
326
4.19M
            param_str[dst] = param_str[src];
327
4.19M
        }
328
52.2k
        param_str[dst] = '\0';
329
52.2k
    }
330
2.10M
}
331
332
/*****************************************************************************/
333
157k
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
157k
    if (nullptr == args)
350
0
        return nullptr;
351
157k
    if (0 == argc)
352
0
        return nullptr;
353
354
    /* turn the input string into an array of strings */
355
157k
    char **argv = (char **)calloc(argc, sizeof(char *));
356
157k
    if (nullptr == argv)
357
0
        return nullptr;
358
2.26M
    for (size_t i = 0, j = 0; j < argc; j++) {
359
2.10M
        argv[j] = args + i;
360
2.10M
        char *str = argv[j];
361
2.10M
        size_t nLen = strlen(str);
362
2.10M
        i += nLen + 1;
363
2.10M
        unquote_string(str);
364
2.10M
    }
365
157k
    return argv;
366
157k
}
367
368
/*****************************************************************************/
369
371k
std::string pj_double_quote_string_param_if_needed(const std::string &str) {
370
    /*****************************************************************************/
371
371k
    if (str.find(' ') == std::string::npos) {
372
334k
        return str;
373
334k
    }
374
37.1k
    std::string ret;
375
37.1k
    ret += '"';
376
37.1k
    ret += replaceAll(str, "\"", "\"\"");
377
37.1k
    ret += '"';
378
37.1k
    return ret;
379
371k
}
380
381
/*****************************************************************************/
382
43.0k
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
43.0k
    try {
393
43.0k
        std::string s;
394
233k
        for (size_t i = 0; i < argc; i++) {
395
190k
            const char *equal = strchr(argv[i], '=');
396
190k
            if (equal) {
397
133k
                s += std::string(argv[i], equal - argv[i] + 1);
398
133k
                s += pj_double_quote_string_param_if_needed(equal + 1);
399
133k
            } else {
400
57.1k
                s += argv[i];
401
57.1k
            }
402
190k
            s += ' ';
403
190k
        }
404
405
43.0k
        char *p = pj_strdup(s.c_str());
406
43.0k
        return pj_shrink(p);
407
43.0k
    } catch (const std::exception &) {
408
0
        return nullptr;
409
0
    }
410
43.0k
}
411
412
/*****************************************************************************/
413
569k
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
569k
    if (nullptr == ctx)
419
0
        ctx = pj_get_default_ctx();
420
569k
    ctx->last_errno = err;
421
569k
    if (err == 0)
422
451k
        return;
423
569k
    errno = err;
424
117k
}