Coverage Report

Created: 2024-02-25 06:14

/src/PROJ/src/4D_api.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * Project:  PROJ.4
3
 * Purpose:  Implement a (currently minimalistic) proj API based primarily
4
 *           on the PJ_COORD 4D geodetic spatiotemporal data type.
5
 *
6
 * Author:   Thomas Knudsen,  thokn@sdfe.dk,  2016-06-09/2016-11-06
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included
19
 * in all copies or substantial portions of the Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 * DEALINGS IN THE SOFTWARE.
28
 *****************************************************************************/
29
30
#define FROM_PROJ_CPP
31
32
#include <assert.h>
33
#include <errno.h>
34
#include <stddef.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#ifndef _MSC_VER
39
#include <strings.h>
40
#endif
41
42
#include <algorithm>
43
#include <limits>
44
45
#include "filemanager.hpp"
46
#include "geodesic.h"
47
#include "grids.hpp"
48
#include "proj.h"
49
#include "proj_experimental.h"
50
#include "proj_internal.h"
51
#include <cmath> /* for isnan */
52
#include <math.h>
53
54
#include "proj/common.hpp"
55
#include "proj/coordinateoperation.hpp"
56
#include "proj/internal/internal.hpp"
57
#include "proj/internal/io_internal.hpp"
58
59
using namespace NS_PROJ::internal;
60
61
/* Initialize PJ_COORD struct */
62
0
PJ_COORD proj_coord(double x, double y, double z, double t) {
63
0
    PJ_COORD res;
64
0
    res.v[0] = x;
65
0
    res.v[1] = y;
66
0
    res.v[2] = z;
67
0
    res.v[3] = t;
68
0
    return res;
69
0
}
70
71
0
static PJ_DIRECTION opposite_direction(PJ_DIRECTION dir) {
72
0
    return static_cast<PJ_DIRECTION>(-dir);
73
0
}
74
75
/*****************************************************************************/
76
0
int proj_angular_input(PJ *P, enum PJ_DIRECTION dir) {
77
    /******************************************************************************
78
        Returns 1 if the operator P expects angular input coordinates when
79
        operating in direction dir, 0 otherwise.
80
        dir: {PJ_FWD, PJ_INV}
81
    ******************************************************************************/
82
0
    if (PJ_FWD == dir)
83
0
        return pj_left(P) == PJ_IO_UNITS_RADIANS;
84
0
    return pj_right(P) == PJ_IO_UNITS_RADIANS;
85
0
}
86
87
/*****************************************************************************/
88
0
int proj_angular_output(PJ *P, enum PJ_DIRECTION dir) {
89
    /******************************************************************************
90
        Returns 1 if the operator P provides angular output coordinates when
91
        operating in direction dir, 0 otherwise.
92
        dir: {PJ_FWD, PJ_INV}
93
    ******************************************************************************/
94
0
    return proj_angular_input(P, opposite_direction(dir));
95
0
}
96
97
/*****************************************************************************/
98
0
int proj_degree_input(PJ *P, enum PJ_DIRECTION dir) {
99
    /******************************************************************************
100
        Returns 1 if the operator P expects degree input coordinates when
101
        operating in direction dir, 0 otherwise.
102
        dir: {PJ_FWD, PJ_INV}
103
    ******************************************************************************/
104
0
    if (PJ_FWD == dir)
105
0
        return pj_left(P) == PJ_IO_UNITS_DEGREES;
106
0
    return pj_right(P) == PJ_IO_UNITS_DEGREES;
107
0
}
108
109
/*****************************************************************************/
110
0
int proj_degree_output(PJ *P, enum PJ_DIRECTION dir) {
111
    /******************************************************************************
112
        Returns 1 if the operator P provides degree output coordinates when
113
        operating in direction dir, 0 otherwise.
114
        dir: {PJ_FWD, PJ_INV}
115
    ******************************************************************************/
116
0
    return proj_degree_input(P, opposite_direction(dir));
117
0
}
118
119
/* Geodesic distance (in meter) + fwd and rev azimuth between two points on the
120
 * ellipsoid */
121
0
PJ_COORD proj_geod(const PJ *P, PJ_COORD a, PJ_COORD b) {
122
0
    PJ_COORD c;
123
0
    if (!P->geod) {
124
0
        return proj_coord_error();
125
0
    }
126
    /* Note: the geodesic code takes arguments in degrees */
127
0
    geod_inverse(P->geod, PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam),
128
0
                 PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), c.v, c.v + 1,
129
0
                 c.v + 2);
130
131
    // cppcheck-suppress uninitvar
132
0
    return c;
133
0
}
134
135
/* Geodesic distance (in meter) between two points with angular 2D coordinates
136
 */
137
0
double proj_lp_dist(const PJ *P, PJ_COORD a, PJ_COORD b) {
138
0
    double s12, azi1, azi2;
139
    /* Note: the geodesic code takes arguments in degrees */
140
0
    if (!P->geod) {
141
0
        return HUGE_VAL;
142
0
    }
143
0
    geod_inverse(P->geod, PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam),
144
0
                 PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), &s12, &azi1, &azi2);
145
0
    return s12;
146
0
}
147
148
/* The geodesic distance AND the vertical offset */
149
0
double proj_lpz_dist(const PJ *P, PJ_COORD a, PJ_COORD b) {
150
0
    if (HUGE_VAL == a.lpz.lam || HUGE_VAL == b.lpz.lam)
151
0
        return HUGE_VAL;
152
0
    return hypot(proj_lp_dist(P, a, b), a.lpz.z - b.lpz.z);
153
0
}
154
155
/* Euclidean distance between two points with linear 2D coordinates */
156
0
double proj_xy_dist(PJ_COORD a, PJ_COORD b) {
157
0
    return hypot(a.xy.x - b.xy.x, a.xy.y - b.xy.y);
158
0
}
159
160
/* Euclidean distance between two points with linear 3D coordinates */
161
0
double proj_xyz_dist(PJ_COORD a, PJ_COORD b) {
162
0
    return hypot(proj_xy_dist(a, b), a.xyz.z - b.xyz.z);
163
0
}
164
165
0
static bool inline coord_is_all_nans(PJ_COORD coo) {
166
0
    return std::isnan(coo.v[0]) && std::isnan(coo.v[1]) &&
167
0
           std::isnan(coo.v[2]) && std::isnan(coo.v[3]);
168
0
}
169
170
0
static bool inline coord_has_nans(PJ_COORD coo) {
171
0
    return std::isnan(coo.v[0]) || std::isnan(coo.v[1]) ||
172
0
           std::isnan(coo.v[2]) || std::isnan(coo.v[3]);
173
0
}
174
175
/* Measure numerical deviation after n roundtrips fwd-inv (or inv-fwd) */
176
0
double proj_roundtrip(PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) {
177
0
    int i;
178
0
    PJ_COORD t, org;
179
180
0
    if (nullptr == P)
181
0
        return HUGE_VAL;
182
183
0
    if (n < 1) {
184
0
        proj_log_error(P, _("n should be >= 1"));
185
0
        proj_errno_set(P, PROJ_ERR_OTHER_API_MISUSE);
186
0
        return HUGE_VAL;
187
0
    }
188
189
    /* in the first half-step, we generate the output value */
190
0
    org = *coord;
191
0
    *coord = proj_trans(P, direction, org);
192
0
    t = *coord;
193
194
    /* now we take n-1 full steps in inverse direction: We are */
195
    /* out of phase due to the half step already taken */
196
0
    for (i = 0; i < n - 1; i++)
197
0
        t = proj_trans(P, direction,
198
0
                       proj_trans(P, opposite_direction(direction), t));
199
200
    /* finally, we take the last half-step */
201
0
    t = proj_trans(P, opposite_direction(direction), t);
202
203
    /* if we start with any NaN, we expect all NaN as output */
204
0
    if (coord_has_nans(org) && coord_is_all_nans(t)) {
205
0
        return 0.0;
206
0
    }
207
208
    /* checking for angular *input* since we do a roundtrip, and end where we
209
     * begin */
210
0
    if (proj_angular_input(P, direction))
211
0
        return proj_lpz_dist(P, org, t);
212
213
0
    return proj_xyz_dist(org, t);
214
0
}
215
216
/**************************************************************************************/
217
int pj_get_suggested_operation(PJ_CONTEXT *,
218
                               const std::vector<PJCoordOperation> &opList,
219
                               const int iExcluded[2], bool skipNonInstantiable,
220
                               PJ_DIRECTION direction, PJ_COORD coord)
221
/**************************************************************************************/
222
0
{
223
0
    const auto normalizeLongitude = [](double x) {
224
0
        if (x > 180.0) {
225
0
            x -= 360.0;
226
0
            if (x > 180.0)
227
0
                x = fmod(x + 180.0, 360.0) - 180.0;
228
0
        } else if (x < -180.0) {
229
0
            x += 360.0;
230
0
            if (x < -180.0)
231
0
                x = fmod(x + 180.0, 360.0) - 180.0;
232
0
        }
233
0
        return x;
234
0
    };
235
236
    // Select the operations that match the area of use
237
    // and has the best accuracy.
238
0
    int iBest = -1;
239
0
    double bestAccuracy = std::numeric_limits<double>::max();
240
0
    const int nOperations = static_cast<int>(opList.size());
241
0
    for (int i = 0; i < nOperations; i++) {
242
0
        if (i == iExcluded[0] || i == iExcluded[1]) {
243
0
            continue;
244
0
        }
245
0
        const auto &alt = opList[i];
246
0
        bool spatialCriterionOK = false;
247
0
        if (direction == PJ_FWD) {
248
0
            if (alt.pjSrcGeocentricToLonLat) {
249
0
                if (alt.minxSrc == -180 && alt.minySrc == -90 &&
250
0
                    alt.maxxSrc == 180 && alt.maxySrc == 90) {
251
0
                    spatialCriterionOK = true;
252
0
                } else {
253
0
                    PJ_COORD tmp = coord;
254
0
                    pj_fwd4d(tmp, alt.pjSrcGeocentricToLonLat);
255
0
                    if (tmp.xyzt.x >= alt.minxSrc &&
256
0
                        tmp.xyzt.y >= alt.minySrc &&
257
0
                        tmp.xyzt.x <= alt.maxxSrc &&
258
0
                        tmp.xyzt.y <= alt.maxySrc) {
259
0
                        spatialCriterionOK = true;
260
0
                    }
261
0
                }
262
0
            } else if (coord.xyzt.x >= alt.minxSrc &&
263
0
                       coord.xyzt.y >= alt.minySrc &&
264
0
                       coord.xyzt.x <= alt.maxxSrc &&
265
0
                       coord.xyzt.y <= alt.maxySrc) {
266
0
                spatialCriterionOK = true;
267
0
            } else if (alt.srcIsLonLatDegree && coord.xyzt.y >= alt.minySrc &&
268
0
                       coord.xyzt.y <= alt.maxySrc) {
269
0
                const double normalizedLon = normalizeLongitude(coord.xyzt.x);
270
0
                if (normalizedLon >= alt.minxSrc &&
271
0
                    normalizedLon <= alt.maxxSrc) {
272
0
                    spatialCriterionOK = true;
273
0
                }
274
0
            } else if (alt.srcIsLatLonDegree && coord.xyzt.x >= alt.minxSrc &&
275
0
                       coord.xyzt.x <= alt.maxxSrc) {
276
0
                const double normalizedLon = normalizeLongitude(coord.xyzt.y);
277
0
                if (normalizedLon >= alt.minySrc &&
278
0
                    normalizedLon <= alt.maxySrc) {
279
0
                    spatialCriterionOK = true;
280
0
                }
281
0
            }
282
0
        } else {
283
0
            if (alt.pjDstGeocentricToLonLat) {
284
0
                if (alt.minxDst == -180 && alt.minyDst == -90 &&
285
0
                    alt.maxxDst == 180 && alt.maxyDst == 90) {
286
0
                    spatialCriterionOK = true;
287
0
                } else {
288
0
                    PJ_COORD tmp = coord;
289
0
                    pj_fwd4d(tmp, alt.pjDstGeocentricToLonLat);
290
0
                    if (tmp.xyzt.x >= alt.minxDst &&
291
0
                        tmp.xyzt.y >= alt.minyDst &&
292
0
                        tmp.xyzt.x <= alt.maxxDst &&
293
0
                        tmp.xyzt.y <= alt.maxyDst) {
294
0
                        spatialCriterionOK = true;
295
0
                    }
296
0
                }
297
0
            } else if (coord.xyzt.x >= alt.minxDst &&
298
0
                       coord.xyzt.y >= alt.minyDst &&
299
0
                       coord.xyzt.x <= alt.maxxDst &&
300
0
                       coord.xyzt.y <= alt.maxyDst) {
301
0
                spatialCriterionOK = true;
302
0
            } else if (alt.dstIsLonLatDegree && coord.xyzt.y >= alt.minyDst &&
303
0
                       coord.xyzt.y <= alt.maxyDst) {
304
0
                const double normalizedLon = normalizeLongitude(coord.xyzt.x);
305
0
                if (normalizedLon >= alt.minxDst &&
306
0
                    normalizedLon <= alt.maxxDst) {
307
0
                    spatialCriterionOK = true;
308
0
                }
309
0
            } else if (alt.dstIsLatLonDegree && coord.xyzt.x >= alt.minxDst &&
310
0
                       coord.xyzt.x <= alt.maxxDst) {
311
0
                const double normalizedLon = normalizeLongitude(coord.xyzt.y);
312
0
                if (normalizedLon >= alt.minyDst &&
313
0
                    normalizedLon <= alt.maxyDst) {
314
0
                    spatialCriterionOK = true;
315
0
                }
316
0
            }
317
0
        }
318
319
0
        if (spatialCriterionOK) {
320
            // The offshore test is for the "Test bug 245 (use +datum=carthage)"
321
            // of testvarious. The long=10 lat=34 point belongs both to the
322
            // onshore and offshore Tunisia area of uses, but is slightly
323
            // onshore. So in a general way, prefer a onshore area to a
324
            // offshore one.
325
0
            if (iBest < 0 ||
326
0
                (((alt.accuracy >= 0 && alt.accuracy < bestAccuracy) ||
327
                  // If two operations have the same accuracy, use
328
                  // the one that has the smallest area
329
0
                  (alt.accuracy == bestAccuracy &&
330
0
                   alt.pseudoArea < opList[iBest].pseudoArea &&
331
0
                   !(alt.isUnknownAreaName &&
332
0
                     !opList[iBest].isUnknownAreaName) &&
333
0
                   !opList[iBest].isPriorityOp)) &&
334
0
                 !alt.isOffshore)) {
335
336
0
                if (skipNonInstantiable && !alt.isInstantiable()) {
337
0
                    continue;
338
0
                }
339
0
                iBest = i;
340
0
                bestAccuracy = alt.accuracy;
341
0
            }
342
0
        }
343
0
    }
344
345
0
    return iBest;
346
0
}
347
348
//! @cond Doxygen_Suppress
349
/**************************************************************************************/
350
0
PJCoordOperation::~PJCoordOperation() {
351
    /**************************************************************************************/
352
0
    proj_destroy(pj);
353
0
    proj_destroy(pjSrcGeocentricToLonLat);
354
0
    proj_destroy(pjDstGeocentricToLonLat);
355
0
}
356
357
/**************************************************************************************/
358
0
bool PJCoordOperation::isInstantiable() const {
359
    /**************************************************************************************/
360
0
    if (isInstantiableCached == INSTANTIABLE_STATUS_UNKNOWN)
361
0
        isInstantiableCached = proj_coordoperation_is_instantiable(pj->ctx, pj);
362
0
    return (isInstantiableCached == 1);
363
0
}
364
//! @endcond
365
366
/**************************************************************************************/
367
static void warnAboutMissingGrid(PJ *P)
368
/**************************************************************************************/
369
0
{
370
0
    std::string msg("Attempt to use coordinate operation ");
371
0
    msg += proj_get_name(P);
372
0
    msg += " failed.";
373
0
    int gridUsed = proj_coordoperation_get_grid_used_count(P->ctx, P);
374
0
    for (int i = 0; i < gridUsed; ++i) {
375
0
        const char *gridName = "";
376
0
        int available = FALSE;
377
0
        if (proj_coordoperation_get_grid_used(P->ctx, P, i, &gridName, nullptr,
378
0
                                              nullptr, nullptr, nullptr,
379
0
                                              nullptr, &available) &&
380
0
            !available) {
381
0
            msg += " Grid ";
382
0
            msg += gridName;
383
0
            msg += " is not available. "
384
0
                   "Consult https://proj.org/resource_files.html for guidance.";
385
0
        }
386
0
    }
387
0
    if (!P->errorIfBestTransformationNotAvailable &&
388
0
        P->warnIfBestTransformationNotAvailable) {
389
0
        msg += " This might become an error in a future PROJ major release. "
390
0
               "Set the ONLY_BEST option to YES or NO. "
391
0
               "This warning will no longer be emitted (for the current "
392
0
               "transformation instance).";
393
0
        P->warnIfBestTransformationNotAvailable = false;
394
0
    }
395
0
    pj_log(P->ctx,
396
0
           P->errorIfBestTransformationNotAvailable ? PJ_LOG_ERROR
397
0
                                                    : PJ_LOG_DEBUG,
398
0
           msg.c_str());
399
0
}
400
401
/**************************************************************************************/
402
0
PJ_COORD proj_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coord) {
403
    /***************************************************************************************
404
    Apply the transformation P to the coordinate coord, preferring the 4D
405
    interfaces if available.
406
407
    See also pj_approx_2D_trans and pj_approx_3D_trans in pj_internal.c, which
408
    work similarly, but prefers the 2D resp. 3D interfaces if available.
409
    ***************************************************************************************/
410
0
    if (nullptr == P || direction == PJ_IDENT)
411
0
        return coord;
412
0
    if (P->inverted)
413
0
        direction = opposite_direction(direction);
414
415
0
    if (P->iso_obj != nullptr && !P->iso_obj_is_coordinate_operation) {
416
0
        pj_log(P->ctx, PJ_LOG_ERROR, "Object is not a coordinate operation");
417
0
        proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
418
0
        return proj_coord_error();
419
0
    }
420
421
0
    if (!P->alternativeCoordinateOperations.empty()) {
422
0
        constexpr int N_MAX_RETRY = 2;
423
0
        int iExcluded[N_MAX_RETRY] = {-1, -1};
424
425
0
        bool skipNonInstantiable = P->skipNonInstantiable &&
426
0
                                   !P->warnIfBestTransformationNotAvailable &&
427
0
                                   !P->errorIfBestTransformationNotAvailable;
428
0
        const int nOperations =
429
0
            static_cast<int>(P->alternativeCoordinateOperations.size());
430
431
        // We may need several attempts. For example the point at
432
        // long=-111.5 lat=45.26 falls into the bounding box of the Canadian
433
        // ntv2_0.gsb grid, except that it is not in any of the subgrids, being
434
        // in the US. We thus need another retry that will select the conus
435
        // grid.
436
0
        for (int iRetry = 0; iRetry <= N_MAX_RETRY; iRetry++) {
437
            // Do a first pass and select the operations that match the area of
438
            // use and has the best accuracy.
439
0
            int iBest = pj_get_suggested_operation(
440
0
                P->ctx, P->alternativeCoordinateOperations, iExcluded,
441
0
                skipNonInstantiable, direction, coord);
442
0
            if (iBest < 0) {
443
0
                break;
444
0
            }
445
0
            if (iRetry > 0) {
446
0
                const int oldErrno = proj_errno_reset(P);
447
0
                if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) {
448
0
                    pj_log(P->ctx, PJ_LOG_DEBUG,
449
0
                           proj_context_errno_string(P->ctx, oldErrno));
450
0
                }
451
0
                pj_log(P->ctx, PJ_LOG_DEBUG,
452
0
                       "Did not result in valid result. "
453
0
                       "Attempting a retry with another operation.");
454
0
            }
455
456
0
            const auto &alt = P->alternativeCoordinateOperations[iBest];
457
0
            if (P->iCurCoordOp != iBest) {
458
0
                if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) {
459
0
                    std::string msg("Using coordinate operation ");
460
0
                    msg += alt.name;
461
0
                    pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str());
462
0
                }
463
0
                P->iCurCoordOp = iBest;
464
0
            }
465
0
            PJ_COORD res = coord;
466
0
            if (alt.pj->hasCoordinateEpoch)
467
0
                coord.xyzt.t = alt.pj->coordinateEpoch;
468
0
            if (direction == PJ_FWD)
469
0
                pj_fwd4d(res, alt.pj);
470
0
            else
471
0
                pj_inv4d(res, alt.pj);
472
0
            if (proj_errno(alt.pj) == PROJ_ERR_OTHER_NETWORK_ERROR) {
473
0
                return proj_coord_error();
474
0
            }
475
0
            if (res.xyzt.x != HUGE_VAL) {
476
0
                return res;
477
0
            } else if (P->errorIfBestTransformationNotAvailable ||
478
0
                       P->warnIfBestTransformationNotAvailable) {
479
0
                warnAboutMissingGrid(alt.pj);
480
0
                if (P->errorIfBestTransformationNotAvailable) {
481
0
                    proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION);
482
0
                    return res;
483
0
                }
484
0
                P->warnIfBestTransformationNotAvailable = false;
485
0
                skipNonInstantiable = true;
486
0
            }
487
0
            if (iRetry == N_MAX_RETRY) {
488
0
                break;
489
0
            }
490
0
            iExcluded[iRetry] = iBest;
491
0
        }
492
493
        // In case we did not find an operation whose area of use is compatible
494
        // with the input coordinate, then goes through again the list, and
495
        // use the first operation that does not require grids.
496
0
        NS_PROJ::io::DatabaseContextPtr dbContext;
497
0
        try {
498
0
            if (P->ctx->cpp_context) {
499
0
                dbContext =
500
0
                    P->ctx->cpp_context->getDatabaseContext().as_nullable();
501
0
            }
502
0
        } catch (const std::exception &) {
503
0
        }
504
0
        for (int i = 0; i < nOperations; i++) {
505
0
            const auto &alt = P->alternativeCoordinateOperations[i];
506
0
            auto coordOperation =
507
0
                dynamic_cast<NS_PROJ::operation::CoordinateOperation *>(
508
0
                    alt.pj->iso_obj.get());
509
0
            if (coordOperation) {
510
0
                if (coordOperation->gridsNeeded(dbContext, true).empty()) {
511
0
                    if (P->iCurCoordOp != i) {
512
0
                        if (proj_log_level(P->ctx, PJ_LOG_TELL) >=
513
0
                            PJ_LOG_DEBUG) {
514
0
                            std::string msg("Using coordinate operation ");
515
0
                            msg += alt.name;
516
0
                            msg += " as a fallback due to lack of more "
517
0
                                   "appropriate operations";
518
0
                            pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str());
519
0
                        }
520
0
                        P->iCurCoordOp = i;
521
0
                    }
522
0
                    if (direction == PJ_FWD) {
523
0
                        pj_fwd4d(coord, alt.pj);
524
0
                    } else {
525
0
                        pj_inv4d(coord, alt.pj);
526
0
                    }
527
0
                    return coord;
528
0
                }
529
0
            }
530
0
        }
531
532
0
        proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION);
533
0
        return proj_coord_error();
534
0
    }
535
536
0
    P->iCurCoordOp =
537
0
        0; // dummy value, to be used by proj_trans_get_last_used_operation()
538
0
    if (P->hasCoordinateEpoch)
539
0
        coord.xyzt.t = P->coordinateEpoch;
540
0
    if (coord_has_nans(coord))
541
0
        coord.v[0] = coord.v[1] = coord.v[2] = coord.v[3] =
542
0
            std::numeric_limits<double>::quiet_NaN();
543
0
    else if (direction == PJ_FWD)
544
0
        pj_fwd4d(coord, P);
545
0
    else
546
0
        pj_inv4d(coord, P);
547
0
    return coord;
548
0
}
549
550
/*****************************************************************************/
551
PJ *proj_trans_get_last_used_operation(PJ *P)
552
/******************************************************************************
553
    Return the operation used during the last invocation of proj_trans().
554
    This is especially useful when P has been created with
555
proj_create_crs_to_crs() and has several alternative operations. The returned
556
object must be freed with proj_destroy().
557
******************************************************************************/
558
0
{
559
0
    if (nullptr == P || P->iCurCoordOp < 0)
560
0
        return nullptr;
561
0
    if (P->alternativeCoordinateOperations.empty())
562
0
        return proj_clone(P->ctx, P);
563
0
    return proj_clone(P->ctx,
564
0
                      P->alternativeCoordinateOperations[P->iCurCoordOp].pj);
565
0
}
566
567
/*****************************************************************************/
568
0
int proj_trans_array(PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord) {
569
    /******************************************************************************
570
        Batch transform an array of PJ_COORD.
571
572
        Performs transformation on all points, even if errors occur on some
573
    points.
574
575
        Individual points that fail to transform will have their components set
576
    to HUGE_VAL
577
578
        Returns 0 if all coordinates are transformed without error, otherwise
579
        returns a precise error number if all coordinates that fail to transform
580
        for the same reason, or a generic error code if they fail for different
581
        reasons.
582
    ******************************************************************************/
583
0
    size_t i;
584
0
    int retErrno = 0;
585
0
    bool hasSetRetErrno = false;
586
0
    bool sameRetErrno = true;
587
588
0
    for (i = 0; i < n; i++) {
589
0
        proj_context_errno_set(P->ctx, 0);
590
0
        coord[i] = proj_trans(P, direction, coord[i]);
591
0
        int thisErrno = proj_errno(P);
592
0
        if (thisErrno != 0) {
593
0
            if (!hasSetRetErrno) {
594
0
                retErrno = thisErrno;
595
0
                hasSetRetErrno = true;
596
0
            } else if (sameRetErrno && retErrno != thisErrno) {
597
0
                sameRetErrno = false;
598
0
                retErrno = PROJ_ERR_COORD_TRANSFM;
599
0
            }
600
0
        }
601
0
    }
602
603
0
    proj_context_errno_set(P->ctx, retErrno);
604
605
0
    return retErrno;
606
0
}
607
608
/*************************************************************************************/
609
size_t proj_trans_generic(PJ *P, PJ_DIRECTION direction, double *x, size_t sx,
610
                          size_t nx, double *y, size_t sy, size_t ny, double *z,
611
                          size_t sz, size_t nz, double *t, size_t st,
612
0
                          size_t nt) {
613
    /**************************************************************************************
614
615
        Transform a series of coordinates, where the individual coordinate
616
    dimension may be represented by an array that is either
617
618
            1. fully populated
619
            2. a null pointer and/or a length of zero, which will be treated as
620
    a fully populated array of zeroes
621
            3. of length one, i.e. a constant, which will be treated as a fully
622
               populated array of that constant value
623
624
        The strides, sx, sy, sz, st, represent the step length, in bytes,
625
    between consecutive elements of the corresponding array. This makes it
626
    possible for proj_transform to handle transformation of a large class of
627
    application specific data structures, without necessarily understanding the
628
    data structure format, as in:
629
630
            typedef struct {double x, y; int quality_level; char
631
    surveyor_name[134];} XYQS; XYQS survey[345]; double height = 23.45; PJ *P =
632
    {...}; size_t stride = sizeof (XYQS);
633
            ...
634
            proj_transform (
635
                P, PJ_INV, sizeof(XYQS),
636
                &(survey[0].x), stride, 345,  (*  We have 345 eastings  *)
637
                &(survey[0].y), stride, 345,  (*  ...and 345 northings. *)
638
                &height, 1,                   (*  The height is the
639
    constant  23.45 m *) 0, 0                          (*  and the time is the
640
    constant 0.00 s *)
641
            );
642
643
        This is similar to the inner workings of the pj_transform function, but
644
    the stride functionality has been generalized to work for any size of basic
645
    unit, not just a fixed number of doubles.
646
647
        In most cases, the stride will be identical for x, y,z, and t, since
648
    they will typically be either individual arrays (stride = sizeof(double)),
649
    or strided views into an array of application specific data structures
650
    (stride = sizeof (...)).
651
652
        But in order to support cases where x, y, z, and t come from
653
    heterogeneous sources, individual strides, sx, sy, sz, st, are used.
654
655
        Caveat: Since proj_transform does its work *in place*, this means that
656
    even the supposedly constants (i.e. length 1 arrays) will return from the
657
    call in altered state. Hence, remember to reinitialize between repeated
658
    calls.
659
660
        Return value: Number of transformations completed.
661
662
    **************************************************************************************/
663
0
    PJ_COORD coord = {{0, 0, 0, 0}};
664
0
    size_t i, nmin;
665
0
    double null_broadcast = 0;
666
0
    double invalid_time = HUGE_VAL;
667
668
0
    if (nullptr == P)
669
0
        return 0;
670
671
0
    if (P->inverted)
672
0
        direction = opposite_direction(direction);
673
674
    /* ignore lengths of null arrays */
675
0
    if (nullptr == x)
676
0
        nx = 0;
677
0
    if (nullptr == y)
678
0
        ny = 0;
679
0
    if (nullptr == z)
680
0
        nz = 0;
681
0
    if (nullptr == t)
682
0
        nt = 0;
683
684
    /* and make the nullities point to some real world memory for broadcasting
685
     * nulls */
686
0
    if (0 == nx)
687
0
        x = &null_broadcast;
688
0
    if (0 == ny)
689
0
        y = &null_broadcast;
690
0
    if (0 == nz)
691
0
        z = &null_broadcast;
692
0
    if (0 == nt)
693
0
        t = &invalid_time;
694
695
    /* nothing to do? */
696
0
    if (0 == nx + ny + nz + nt)
697
0
        return 0;
698
699
    /* arrays of length 1 are constants, which we broadcast along the longer
700
     * arrays */
701
    /* so we need to find the length of the shortest non-unity array to figure
702
     * out
703
     */
704
    /* how many coordinate pairs we must transform */
705
0
    nmin = (nx > 1) ? nx : (ny > 1) ? ny : (nz > 1) ? nz : (nt > 1) ? nt : 1;
706
0
    if ((nx > 1) && (nx < nmin))
707
0
        nmin = nx;
708
0
    if ((ny > 1) && (ny < nmin))
709
0
        nmin = ny;
710
0
    if ((nz > 1) && (nz < nmin))
711
0
        nmin = nz;
712
0
    if ((nt > 1) && (nt < nmin))
713
0
        nmin = nt;
714
715
    /* Check validity of direction flag */
716
0
    switch (direction) {
717
0
    case PJ_FWD:
718
0
    case PJ_INV:
719
0
        break;
720
0
    case PJ_IDENT:
721
0
        return nmin;
722
0
    }
723
724
    /* Arrays of length==0 are broadcast as the constant 0               */
725
    /* Arrays of length==1 are broadcast as their single value           */
726
    /* Arrays of length >1 are iterated over (for the first nmin values) */
727
    /* The slightly convolved incremental indexing is used due           */
728
    /* to the stride, which may be any size supported by the platform    */
729
0
    for (i = 0; i < nmin; i++) {
730
0
        coord.xyzt.x = *x;
731
0
        coord.xyzt.y = *y;
732
0
        coord.xyzt.z = *z;
733
0
        coord.xyzt.t = *t;
734
735
0
        coord = proj_trans(P, direction, coord);
736
737
        /* in all full length cases, we overwrite the input with the output,  */
738
        /* and step on to the next element.                                   */
739
        /* The casts are somewhat funky, but they compile down to no-ops and  */
740
        /* they tell compilers and static analyzers that we know what we do   */
741
0
        if (nx > 1) {
742
0
            *x = coord.xyzt.x;
743
0
            x = (double *)((void *)(((char *)x) + sx));
744
0
        }
745
0
        if (ny > 1) {
746
0
            *y = coord.xyzt.y;
747
0
            y = (double *)((void *)(((char *)y) + sy));
748
0
        }
749
0
        if (nz > 1) {
750
0
            *z = coord.xyzt.z;
751
0
            z = (double *)((void *)(((char *)z) + sz));
752
0
        }
753
0
        if (nt > 1) {
754
0
            *t = coord.xyzt.t;
755
0
            t = (double *)((void *)(((char *)t) + st));
756
0
        }
757
0
    }
758
759
    /* Last time around, we update the length 1 cases with their transformed
760
     * alter egos */
761
0
    if (nx == 1)
762
0
        *x = coord.xyzt.x;
763
0
    if (ny == 1)
764
0
        *y = coord.xyzt.y;
765
0
    if (nz == 1)
766
0
        *z = coord.xyzt.z;
767
0
    if (nt == 1)
768
0
        *t = coord.xyzt.t;
769
770
0
    return i;
771
0
}
772
773
/*************************************************************************************/
774
PJ_COORD pj_geocentric_latitude(const PJ *P, PJ_DIRECTION direction,
775
0
                                PJ_COORD coord) {
776
    /**************************************************************************************
777
        Convert geographical latitude to geocentric (or the other way round if
778
        direction = PJ_INV)
779
780
        The conversion involves a call to the tangent function, which goes
781
    through the roof at the poles, so very close (the last centimeter) to the
782
    poles no conversion takes place and the input latitude is copied directly to
783
    the output.
784
785
        Fortunately, the geocentric latitude converges to the geographical at
786
    the poles, so the difference is negligible.
787
788
        For the spherical case, the geographical latitude equals the geocentric,
789
    and consequently, the input is copied directly to the output.
790
    **************************************************************************************/
791
0
    const double limit = M_HALFPI - 1e-9;
792
0
    PJ_COORD res = coord;
793
0
    if ((coord.lp.phi > limit) || (coord.lp.phi < -limit) || (P->es == 0))
794
0
        return res;
795
0
    if (direction == PJ_FWD)
796
0
        res.lp.phi = atan(P->one_es * tan(coord.lp.phi));
797
0
    else
798
0
        res.lp.phi = atan(P->rone_es * tan(coord.lp.phi));
799
800
0
    return res;
801
0
}
802
803
0
double proj_torad(double angle_in_degrees) {
804
0
    return PJ_TORAD(angle_in_degrees);
805
0
}
806
0
double proj_todeg(double angle_in_radians) {
807
0
    return PJ_TODEG(angle_in_radians);
808
0
}
809
810
732
double proj_dmstor(const char *is, char **rs) { return dmstor(is, rs); }
811
812
0
char *proj_rtodms(char *s, double r, int pos, int neg) {
813
    // 40 is the size used for the buffer in proj.cpp
814
0
    size_t arbitrary_size = 40;
815
0
    return rtodms(s, arbitrary_size, r, pos, neg);
816
0
}
817
818
0
char *proj_rtodms2(char *s, size_t sizeof_s, double r, int pos, int neg) {
819
0
    return rtodms(s, sizeof_s, r, pos, neg);
820
0
}
821
822
/*************************************************************************************/
823
73.8k
static PJ *skip_prep_fin(PJ *P) {
824
    /**************************************************************************************
825
    Skip prepare and finalize function for the various "helper operations" added
826
    to P when in cs2cs compatibility mode.
827
    **************************************************************************************/
828
73.8k
    P->skip_fwd_prepare = 1;
829
73.8k
    P->skip_fwd_finalize = 1;
830
73.8k
    P->skip_inv_prepare = 1;
831
73.8k
    P->skip_inv_finalize = 1;
832
73.8k
    return P;
833
73.8k
}
834
835
/*************************************************************************************/
836
183k
static int cs2cs_emulation_setup(PJ *P) {
837
    /**************************************************************************************
838
    If any cs2cs style modifiers are given (axis=..., towgs84=..., ) create the
839
    4D API equivalent operations, so the preparation and finalization steps in
840
    the pj_inv/pj_fwd invocators can emulate the behavior of pj_transform and
841
    the cs2cs app.
842
843
    Returns 1 on success, 0 on failure
844
    **************************************************************************************/
845
183k
    PJ *Q;
846
183k
    paralist *p;
847
183k
    int do_cart = 0;
848
183k
    if (nullptr == P)
849
17.5k
        return 0;
850
851
    /* Don't recurse when calling proj_create (which calls us back) */
852
165k
    if (pj_param_exists(P->params, "break_cs2cs_recursion"))
853
73.8k
        return 1;
854
855
    /* Swap axes? */
856
92.1k
    p = pj_param_exists(P->params, "axis");
857
858
92.1k
    const bool disable_grid_presence_check =
859
92.1k
        pj_param_exists(P->params, "disable_grid_presence_check") != nullptr;
860
861
    /* Don't axisswap if data are already in "enu" order */
862
92.1k
    if (p && (0 != strcmp("enu", p->param))) {
863
1.48k
        size_t def_size = 100 + strlen(P->axis);
864
1.48k
        char *def = static_cast<char *>(malloc(def_size));
865
1.48k
        if (nullptr == def)
866
0
            return 0;
867
1.48k
        snprintf(def, def_size,
868
1.48k
                 "break_cs2cs_recursion     proj=axisswap  axis=%s", P->axis);
869
1.48k
        Q = pj_create_internal(P->ctx, def);
870
1.48k
        free(def);
871
1.48k
        if (nullptr == Q)
872
5
            return 0;
873
1.48k
        P->axisswap = skip_prep_fin(Q);
874
1.48k
    }
875
876
    /* Geoid grid(s) given? */
877
92.1k
    p = pj_param_exists(P->params, "geoidgrids");
878
92.1k
    if (!disable_grid_presence_check && p &&
879
92.1k
        strlen(p->param) > strlen("geoidgrids=")) {
880
935
        char *gridnames = p->param + strlen("geoidgrids=");
881
935
        size_t def_size = 100 + 2 * strlen(gridnames);
882
935
        char *def = static_cast<char *>(malloc(def_size));
883
935
        if (nullptr == def)
884
0
            return 0;
885
935
        snprintf(def, def_size,
886
935
                 "break_cs2cs_recursion     proj=vgridshift  grids=%s",
887
935
                 pj_double_quote_string_param_if_needed(gridnames).c_str());
888
935
        Q = pj_create_internal(P->ctx, def);
889
935
        free(def);
890
935
        if (nullptr == Q)
891
590
            return 0;
892
345
        P->vgridshift = skip_prep_fin(Q);
893
345
    }
894
895
    /* Datum shift grid(s) given? */
896
91.5k
    p = pj_param_exists(P->params, "nadgrids");
897
91.5k
    if (!disable_grid_presence_check && p &&
898
91.5k
        strlen(p->param) > strlen("nadgrids=")) {
899
2.37k
        char *gridnames = p->param + strlen("nadgrids=");
900
2.37k
        size_t def_size = 100 + 2 * strlen(gridnames);
901
2.37k
        char *def = static_cast<char *>(malloc(def_size));
902
2.37k
        if (nullptr == def)
903
0
            return 0;
904
2.37k
        snprintf(def, def_size,
905
2.37k
                 "break_cs2cs_recursion     proj=hgridshift  grids=%s",
906
2.37k
                 pj_double_quote_string_param_if_needed(gridnames).c_str());
907
2.37k
        Q = pj_create_internal(P->ctx, def);
908
2.37k
        free(def);
909
2.37k
        if (nullptr == Q)
910
415
            return 0;
911
1.95k
        P->hgridshift = skip_prep_fin(Q);
912
1.95k
    }
913
914
    /* We ignore helmert if we have grid shift */
915
91.1k
    p = P->hgridshift ? nullptr : pj_param_exists(P->params, "towgs84");
916
91.1k
    while (p) {
917
25.9k
        const char *const s = p->param;
918
25.9k
        const double *const d = P->datum_params;
919
920
        /* We ignore null helmert shifts (common in auto-translated resource
921
         * files, e.g. epsg) */
922
25.9k
        if (0 == d[0] && 0 == d[1] && 0 == d[2] && 0 == d[3] && 0 == d[4] &&
923
25.9k
            0 == d[5] && 0 == d[6]) {
924
            /* If the current ellipsoid is not WGS84, then make sure the */
925
            /* change in ellipsoid is still done. */
926
5.37k
            if (!(fabs(P->a_orig - 6378137.0) < 1e-8 &&
927
5.37k
                  fabs(P->es_orig - 0.0066943799901413) < 1e-15)) {
928
4.10k
                do_cart = 1;
929
4.10k
            }
930
5.37k
            break;
931
5.37k
        }
932
933
20.5k
        const size_t n = strlen(s);
934
20.5k
        if (n <= 8) /* 8==strlen ("towgs84=") */
935
0
            return 0;
936
937
20.5k
        const size_t def_max_size = 100 + n;
938
20.5k
        std::string def;
939
20.5k
        def.reserve(def_max_size);
940
20.5k
        def += "break_cs2cs_recursion     proj=helmert exact ";
941
20.5k
        def += s;
942
20.5k
        def += " convention=position_vector";
943
20.5k
        Q = pj_create_internal(P->ctx, def.c_str());
944
20.5k
        if (nullptr == Q)
945
4
            return 0;
946
20.5k
        pj_inherit_ellipsoid_def(P, Q);
947
20.5k
        P->helmert = skip_prep_fin(Q);
948
949
20.5k
        break;
950
20.5k
    }
951
952
    /* We also need cartesian/geographical transformations if we are working in
953
     */
954
    /* geocentric/cartesian space or we need to do a Helmert transform. */
955
91.1k
    if (P->is_geocent || P->helmert || do_cart) {
956
24.8k
        char def[150];
957
24.8k
        snprintf(def, sizeof(def),
958
24.8k
                 "break_cs2cs_recursion     proj=cart   a=%40.20g  es=%40.20g",
959
24.8k
                 P->a_orig, P->es_orig);
960
24.8k
        {
961
            /* In case the current locale does not use dot but comma as decimal
962
             */
963
            /* separator, replace it with dot, so that proj_atof() behaves */
964
            /* correctly. */
965
            /* TODO later: use C++ ostringstream with
966
             * imbue(std::locale::classic()) */
967
            /* to be locale unaware */
968
24.8k
            char *next_pos;
969
24.8k
            for (next_pos = def; (next_pos = strchr(next_pos, ',')) != nullptr;
970
24.8k
                 next_pos++) {
971
0
                *next_pos = '.';
972
0
            }
973
24.8k
        }
974
24.8k
        Q = pj_create_internal(P->ctx, def);
975
24.8k
        if (nullptr == Q)
976
0
            return 0;
977
24.8k
        P->cart = skip_prep_fin(Q);
978
979
24.8k
        if (!P->is_geocent) {
980
24.6k
            snprintf(def, sizeof(def),
981
24.6k
                     "break_cs2cs_recursion     proj=cart  ellps=WGS84");
982
24.6k
            Q = pj_create_internal(P->ctx, def);
983
24.6k
            if (nullptr == Q)
984
0
                return 0;
985
24.6k
            P->cart_wgs84 = skip_prep_fin(Q);
986
24.6k
        }
987
24.8k
    }
988
989
91.1k
    return 1;
990
91.1k
}
991
992
/*************************************************************************************/
993
120k
PJ *pj_create_internal(PJ_CONTEXT *ctx, const char *definition) {
994
    /*************************************************************************************/
995
996
    /**************************************************************************************
997
        Create a new PJ object in the context ctx, using the given definition.
998
    If ctx==0, the default context is used, if definition==0, or invalid, a
999
    null-pointer is returned. The definition may use '+' as argument start
1000
    indicator, as in
1001
        "+proj=utm +zone=32", or leave it out, as in "proj=utm zone=32".
1002
1003
        It may even use free formatting "proj  =  utm;  zone  =32  ellps=
1004
    GRS80". Note that the semicolon separator is allowed, but not required.
1005
    **************************************************************************************/
1006
120k
    char *args, **argv;
1007
120k
    size_t argc, n;
1008
1009
120k
    if (nullptr == ctx)
1010
0
        ctx = pj_get_default_ctx();
1011
1012
    /* Make a copy that we can manipulate */
1013
120k
    n = strlen(definition);
1014
120k
    args = (char *)malloc(n + 1);
1015
120k
    if (nullptr == args) {
1016
0
        proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/);
1017
0
        return nullptr;
1018
0
    }
1019
120k
    strcpy(args, definition);
1020
1021
120k
    argc = pj_trim_argc(args);
1022
120k
    if (argc == 0) {
1023
11
        free(args);
1024
11
        proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG);
1025
11
        return nullptr;
1026
11
    }
1027
1028
120k
    argv = pj_trim_argv(argc, args);
1029
120k
    if (!argv) {
1030
0
        free(args);
1031
0
        proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/);
1032
0
        return nullptr;
1033
0
    }
1034
1035
120k
    PJ *P = pj_create_argv_internal(ctx, (int)argc, argv);
1036
1037
120k
    free(argv);
1038
120k
    free(args);
1039
1040
120k
    return P;
1041
120k
}
1042
1043
/*************************************************************************************/
1044
0
PJ *proj_create_argv(PJ_CONTEXT *ctx, int argc, char **argv) {
1045
    /**************************************************************************************
1046
    Create a new PJ object in the context ctx, using the given definition
1047
    argument array argv. If ctx==0, the default context is used, if
1048
    definition==0, or invalid, a null-pointer is returned. The definition
1049
    arguments may use '+' as argument start indicator, as in {"+proj=utm",
1050
    "+zone=32"}, or leave it out, as in {"proj=utm", "zone=32"}.
1051
    **************************************************************************************/
1052
1053
0
    if (nullptr == ctx)
1054
0
        ctx = pj_get_default_ctx();
1055
0
    if (nullptr == argv) {
1056
0
        proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG);
1057
0
        return nullptr;
1058
0
    }
1059
1060
    /* We assume that free format is used, and build a full proj_create
1061
     * compatible string */
1062
0
    char *c = pj_make_args(argc, argv);
1063
0
    if (nullptr == c) {
1064
0
        proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP /* ENOMEM */);
1065
0
        return nullptr;
1066
0
    }
1067
1068
0
    PJ *P = proj_create(ctx, c);
1069
1070
0
    free((char *)c);
1071
0
    return P;
1072
0
}
1073
1074
/*************************************************************************************/
1075
183k
PJ *pj_create_argv_internal(PJ_CONTEXT *ctx, int argc, char **argv) {
1076
    /**************************************************************************************
1077
    For use by pipeline init function.
1078
    **************************************************************************************/
1079
183k
    if (nullptr == ctx)
1080
0
        ctx = pj_get_default_ctx();
1081
183k
    if (nullptr == argv) {
1082
0
        proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG);
1083
0
        return nullptr;
1084
0
    }
1085
1086
    /* ...and let pj_init_ctx do the hard work */
1087
    /* New interface: forbid init=epsg:XXXX syntax by default */
1088
183k
    const int allow_init_epsg =
1089
183k
        proj_context_get_use_proj4_init_rules(ctx, FALSE);
1090
183k
    PJ *P = pj_init_ctx_with_allow_init_epsg(ctx, argc, argv, allow_init_epsg);
1091
1092
    /* Support cs2cs-style modifiers */
1093
183k
    int ret = cs2cs_emulation_setup(P);
1094
183k
    if (0 == ret)
1095
18.5k
        return proj_destroy(P);
1096
1097
164k
    return P;
1098
183k
}
1099
1100
/** Create an area of use */
1101
0
PJ_AREA *proj_area_create(void) { return new PJ_AREA(); }
1102
1103
/** Assign a bounding box to an area of use. */
1104
void proj_area_set_bbox(PJ_AREA *area, double west_lon_degree,
1105
                        double south_lat_degree, double east_lon_degree,
1106
0
                        double north_lat_degree) {
1107
0
    area->bbox_set = TRUE;
1108
0
    area->west_lon_degree = west_lon_degree;
1109
0
    area->south_lat_degree = south_lat_degree;
1110
0
    area->east_lon_degree = east_lon_degree;
1111
0
    area->north_lat_degree = north_lat_degree;
1112
0
}
1113
1114
/** Assign the name of an area of use. */
1115
0
void proj_area_set_name(PJ_AREA *area, const char *name) { area->name = name; }
1116
1117
/** Free an area of use */
1118
0
void proj_area_destroy(PJ_AREA *area) { delete area; }
1119
1120
/************************************************************************/
1121
/*                  proj_context_use_proj4_init_rules()                 */
1122
/************************************************************************/
1123
1124
550
void proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable) {
1125
550
    if (ctx == nullptr) {
1126
0
        ctx = pj_get_default_ctx();
1127
0
    }
1128
550
    ctx->use_proj4_init_rules = enable;
1129
550
}
1130
1131
/************************************************************************/
1132
/*                              EQUAL()                                 */
1133
/************************************************************************/
1134
1135
0
static int EQUAL(const char *a, const char *b) {
1136
#ifdef _MSC_VER
1137
    return _stricmp(a, b) == 0;
1138
#else
1139
0
    return strcasecmp(a, b) == 0;
1140
0
#endif
1141
0
}
1142
1143
/************************************************************************/
1144
/*                  proj_context_get_use_proj4_init_rules()             */
1145
/************************************************************************/
1146
1147
int proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx,
1148
217k
                                          int from_legacy_code_path) {
1149
217k
    const char *val = getenv("PROJ_USE_PROJ4_INIT_RULES");
1150
1151
217k
    if (ctx == nullptr) {
1152
0
        ctx = pj_get_default_ctx();
1153
0
    }
1154
1155
217k
    if (val) {
1156
0
        if (EQUAL(val, "yes") || EQUAL(val, "on") || EQUAL(val, "true")) {
1157
0
            return TRUE;
1158
0
        }
1159
0
        if (EQUAL(val, "no") || EQUAL(val, "off") || EQUAL(val, "false")) {
1160
0
            return FALSE;
1161
0
        }
1162
0
        pj_log(ctx, PJ_LOG_ERROR,
1163
0
               "Invalid value for PROJ_USE_PROJ4_INIT_RULES");
1164
0
    }
1165
1166
217k
    if (ctx->use_proj4_init_rules >= 0) {
1167
1.39k
        return ctx->use_proj4_init_rules;
1168
1.39k
    }
1169
215k
    return from_legacy_code_path;
1170
217k
}
1171
1172
/** Adds a " +type=crs" suffix to a PROJ string (if it is a PROJ string) */
1173
57.8k
std::string pj_add_type_crs_if_needed(const std::string &str) {
1174
57.8k
    std::string ret(str);
1175
57.8k
    if ((starts_with(str, "proj=") || starts_with(str, "+proj=") ||
1176
57.8k
         starts_with(str, "+init=") || starts_with(str, "+title=")) &&
1177
57.8k
        str.find("type=crs") == std::string::npos) {
1178
21.1k
        ret += " +type=crs";
1179
21.1k
    }
1180
57.8k
    return ret;
1181
57.8k
}
1182
1183
// ---------------------------------------------------------------------------
1184
0
static double simple_min(const double *data, const int arr_len) {
1185
0
    double min_value = data[0];
1186
0
    for (int iii = 1; iii < arr_len; iii++) {
1187
0
        if (data[iii] < min_value)
1188
0
            min_value = data[iii];
1189
0
    }
1190
0
    return min_value;
1191
0
}
1192
1193
// ---------------------------------------------------------------------------
1194
0
static double simple_max(const double *data, const int arr_len) {
1195
0
    double max_value = data[0];
1196
0
    for (int iii = 1; iii < arr_len; iii++) {
1197
0
        if ((data[iii] > max_value || max_value == HUGE_VAL) &&
1198
0
            data[iii] != HUGE_VAL)
1199
0
            max_value = data[iii];
1200
0
    }
1201
0
    return max_value;
1202
0
}
1203
1204
// ---------------------------------------------------------------------------
1205
static int find_previous_index(const int iii, const double *data,
1206
0
                               const int arr_len) {
1207
    // find index of nearest valid previous value if exists
1208
0
    int prev_iii = iii - 1;
1209
0
    if (prev_iii == -1) // handle wraparound
1210
0
        prev_iii = arr_len - 1;
1211
0
    while (data[prev_iii] == HUGE_VAL && prev_iii != iii) {
1212
0
        prev_iii--;
1213
0
        if (prev_iii == -1) // handle wraparound
1214
0
            prev_iii = arr_len - 1;
1215
0
    }
1216
0
    return prev_iii;
1217
0
}
1218
1219
// ---------------------------------------------------------------------------
1220
/******************************************************************************
1221
Handles the case when longitude values cross the antimeridian
1222
when calculating the minimum.
1223
Note: The data array must be in a linear ring.
1224
Note: This requires a densified ring with at least 2 additional
1225
        points per edge to correctly handle global extents.
1226
If only 1 additional point:
1227
    |        |
1228
    |RL--x0--|RL--
1229
    |        |
1230
-180    180|-180
1231
If they are evenly spaced and it crosses the antimeridian:
1232
x0 - L = 180
1233
R - x0 = -180
1234
For example:
1235
Let R = -179.9, x0 = 0.1, L = -179.89
1236
x0 - L = 0.1 - -179.9 = 180
1237
R - x0 = -179.89 - 0.1 ~= -180
1238
This is the same in the case when it didn't cross the antimeridian.
1239
If you have 2 additional points:
1240
    |            |
1241
    |RL--x0--x1--|RL--
1242
    |            |
1243
-180        180|-180
1244
If they are evenly spaced and it crosses the antimeridian:
1245
x0 - L = 120
1246
x1 - x0 = 120
1247
R - x1 = -240
1248
For example:
1249
Let R = -179.9, x0 = -59.9, x1 = 60.1 L = -179.89
1250
x0 - L = 59.9 - -179.9 = 120
1251
x1 - x0 = 60.1 - 59.9 = 120
1252
R - x1 = -179.89 - 60.1 ~= -240
1253
However, if they are evenly spaced and it didn't cross the antimeridian:
1254
x0 - L = 120
1255
x1 - x0 = 120
1256
R - x1 = 120
1257
From this, we have a delta that is guaranteed to be significantly
1258
large enough to tell the difference reguarless of the direction
1259
the antimeridian was crossed.
1260
However, even though the spacing was even in the source projection, it isn't
1261
guaranteed in the target geographic projection. So, instead of 240, 200 is used
1262
as it significantly larger than 120 to be sure that the antimeridian was crossed
1263
but smalller than 240 to account for possible irregularities in distances
1264
when re-projecting. Also, 200 ensures latitudes are ignored for axis order
1265
handling.
1266
******************************************************************************/
1267
0
static double antimeridian_min(const double *data, const int arr_len) {
1268
0
    double positive_min = HUGE_VAL;
1269
0
    double min_value = HUGE_VAL;
1270
0
    int crossed_meridian_count = 0;
1271
0
    bool positive_meridian = false;
1272
1273
0
    for (int iii = 0; iii < arr_len; iii++) {
1274
0
        if (data[iii] == HUGE_VAL)
1275
0
            continue;
1276
0
        int prev_iii = find_previous_index(iii, data, arr_len);
1277
        // check if crossed meridian
1278
0
        double delta = data[prev_iii] - data[iii];
1279
        // 180 -> -180
1280
0
        if (delta >= 200 && delta != HUGE_VAL) {
1281
0
            if (crossed_meridian_count == 0)
1282
0
                positive_min = min_value;
1283
0
            crossed_meridian_count++;
1284
0
            positive_meridian = false;
1285
            // -180 -> 180
1286
0
        } else if (delta <= -200 && delta != HUGE_VAL) {
1287
0
            if (crossed_meridian_count == 0)
1288
0
                positive_min = data[iii];
1289
0
            crossed_meridian_count++;
1290
0
            positive_meridian = true;
1291
0
        }
1292
        // positive meridian side min
1293
0
        if (positive_meridian && data[iii] < positive_min)
1294
0
            positive_min = data[iii];
1295
        // track general min value
1296
0
        if (data[iii] < min_value)
1297
0
            min_value = data[iii];
1298
0
    }
1299
1300
0
    if (crossed_meridian_count == 2)
1301
0
        return positive_min;
1302
0
    else if (crossed_meridian_count == 4)
1303
        // bounds extends beyond -180/180
1304
0
        return -180;
1305
0
    return min_value;
1306
0
}
1307
1308
// ---------------------------------------------------------------------------
1309
// Handles the case when longitude values cross the antimeridian
1310
// when calculating the minimum.
1311
// Note: The data array must be in a linear ring.
1312
// Note: This requires a densified ring with at least 2 additional
1313
//       points per edge to correctly handle global extents.
1314
// See antimeridian_min docstring for reasoning.
1315
0
static double antimeridian_max(const double *data, const int arr_len) {
1316
0
    double negative_max = -HUGE_VAL;
1317
0
    double max_value = -HUGE_VAL;
1318
0
    bool negative_meridian = false;
1319
0
    int crossed_meridian_count = 0;
1320
1321
0
    for (int iii = 0; iii < arr_len; iii++) {
1322
0
        if (data[iii] == HUGE_VAL)
1323
0
            continue;
1324
0
        int prev_iii = find_previous_index(iii, data, arr_len);
1325
        // check if crossed meridian
1326
0
        double delta = data[prev_iii] - data[iii];
1327
        // 180 -> -180
1328
0
        if (delta >= 200 && delta != HUGE_VAL) {
1329
0
            if (crossed_meridian_count == 0)
1330
0
                negative_max = data[iii];
1331
0
            crossed_meridian_count++;
1332
0
            negative_meridian = true;
1333
            // -180 -> 180
1334
0
        } else if (delta <= -200 && delta != HUGE_VAL) {
1335
0
            if (crossed_meridian_count == 0)
1336
0
                negative_max = max_value;
1337
0
            negative_meridian = false;
1338
0
            crossed_meridian_count++;
1339
0
        }
1340
        // negative meridian side max
1341
0
        if (negative_meridian &&
1342
0
            (data[iii] > negative_max || negative_max == HUGE_VAL) &&
1343
0
            data[iii] != HUGE_VAL)
1344
0
            negative_max = data[iii];
1345
        // track general max value
1346
0
        if ((data[iii] > max_value || max_value == HUGE_VAL) &&
1347
0
            data[iii] != HUGE_VAL)
1348
0
            max_value = data[iii];
1349
0
    }
1350
0
    if (crossed_meridian_count == 2)
1351
0
        return negative_max;
1352
0
    else if (crossed_meridian_count == 4)
1353
        // bounds extends beyond -180/180
1354
0
        return 180;
1355
0
    return max_value;
1356
0
}
1357
1358
// ---------------------------------------------------------------------------
1359
// Check if the original projected bounds contains
1360
// the north pole.
1361
// This assumes that the destination CRS is geographic.
1362
static bool contains_north_pole(PJ *projobj, PJ_DIRECTION pj_direction,
1363
                                const double xmin, const double ymin,
1364
                                const double xmax, const double ymax,
1365
0
                                bool lon_lat_order) {
1366
0
    double pole_y = 90;
1367
0
    double pole_x = 0;
1368
0
    if (!lon_lat_order) {
1369
0
        pole_y = 0;
1370
0
        pole_x = 90;
1371
0
    }
1372
0
    proj_trans_generic(projobj, opposite_direction(pj_direction), &pole_x,
1373
0
                       sizeof(double), 1, &pole_y, sizeof(double), 1, nullptr,
1374
0
                       sizeof(double), 0, nullptr, sizeof(double), 0);
1375
0
    if (xmin < pole_x && pole_x < xmax && ymax > pole_y && pole_y > ymin)
1376
0
        return true;
1377
0
    return false;
1378
0
}
1379
1380
// ---------------------------------------------------------------------------
1381
// Check if the original projected bounds contains
1382
// the south pole.
1383
// This assumes that the destination CRS is geographic.
1384
static bool contains_south_pole(PJ *projobj, PJ_DIRECTION pj_direction,
1385
                                const double xmin, const double ymin,
1386
                                const double xmax, const double ymax,
1387
0
                                bool lon_lat_order) {
1388
0
    double pole_y = -90;
1389
0
    double pole_x = 0;
1390
0
    if (!lon_lat_order) {
1391
0
        pole_y = 0;
1392
0
        pole_x = -90;
1393
0
    }
1394
0
    proj_trans_generic(projobj, opposite_direction(pj_direction), &pole_x,
1395
0
                       sizeof(double), 1, &pole_y, sizeof(double), 1, nullptr,
1396
0
                       sizeof(double), 0, nullptr, sizeof(double), 0);
1397
0
    if (xmin < pole_x && pole_x < xmax && ymax > pole_y && pole_y > ymin)
1398
0
        return true;
1399
0
    return false;
1400
0
}
1401
1402
// ---------------------------------------------------------------------------
1403
// Check if the target CRS of the transformation
1404
// has the longitude latitude axis order.
1405
// This assumes that the destination CRS is geographic.
1406
static int target_crs_lon_lat_order(PJ_CONTEXT *transformer_ctx,
1407
                                    PJ *transformer_pj,
1408
0
                                    PJ_DIRECTION pj_direction) {
1409
0
    PJ *target_crs = nullptr;
1410
0
    if (pj_direction == PJ_FWD)
1411
0
        target_crs = proj_get_target_crs(transformer_ctx, transformer_pj);
1412
0
    else if (pj_direction == PJ_INV)
1413
0
        target_crs = proj_get_source_crs(transformer_ctx, transformer_pj);
1414
0
    if (target_crs == nullptr) {
1415
0
        proj_context_log_debug(transformer_ctx,
1416
0
                               "Unable to retrieve target CRS");
1417
0
        return -1;
1418
0
    }
1419
0
    PJ *coord_system_pj =
1420
0
        proj_crs_get_coordinate_system(transformer_ctx, target_crs);
1421
0
    proj_destroy(target_crs);
1422
0
    if (coord_system_pj == nullptr) {
1423
0
        proj_context_log_debug(transformer_ctx,
1424
0
                               "Unable to get target CRS coordinate system.");
1425
0
        return -1;
1426
0
    }
1427
0
    const char *abbrev = nullptr;
1428
0
    int success = proj_cs_get_axis_info(transformer_ctx, coord_system_pj, 0,
1429
0
                                        nullptr, &abbrev, nullptr, nullptr,
1430
0
                                        nullptr, nullptr, nullptr);
1431
0
    proj_destroy(coord_system_pj);
1432
0
    if (success != 1)
1433
0
        return -1;
1434
0
    return strcmp(abbrev, "lon") == 0 || strcmp(abbrev, "Lon") == 0;
1435
0
}
1436
1437
// ---------------------------------------------------------------------------
1438
1439
/** \brief Transform boundary,
1440
 *
1441
 * Transform boundary densifying the edges to account for nonlinear
1442
 * transformations along these edges and extracting the outermost bounds.
1443
 *
1444
 * If the destination CRS is geographic, the first axis is longitude,
1445
 * and xmax < xmin then the bounds crossed the antimeridian.
1446
 * In this scenario there are two polygons, one on each side of the
1447
 * antimeridian. The first polygon should be constructed with (xmin, ymin, 180,
1448
 * ymax) and the second with (-180, ymin, xmax, ymax).
1449
 *
1450
 * If the destination CRS is geographic, the first axis is latitude,
1451
 * and ymax < ymin then the bounds crossed the antimeridian.
1452
 * In this scenario there are two polygons, one on each side of the
1453
 * antimeridian. The first polygon should be constructed with (ymin, xmin, ymax,
1454
 * 180) and the second with (ymin, -180, ymax, xmax).
1455
 *
1456
 * @param context The PJ_CONTEXT object.
1457
 * @param P The PJ object representing the transformation.
1458
 * @param direction The direction of the transformation.
1459
 * @param xmin Minimum bounding coordinate of the first axis in source CRS
1460
 *             (target CRS if direction is inverse).
1461
 * @param ymin Minimum bounding coordinate of the second axis in source CRS.
1462
 *             (target CRS if direction is inverse).
1463
 * @param xmax Maximum bounding coordinate of the first axis in source CRS.
1464
 *             (target CRS if direction is inverse).
1465
 * @param ymax Maximum bounding coordinate of the second axis in source CRS.
1466
 *             (target CRS if direction is inverse).
1467
 * @param out_xmin Minimum bounding coordinate of the first axis in target CRS
1468
 *             (source CRS if direction is inverse).
1469
 * @param out_ymin Minimum bounding coordinate of the second axis in target CRS.
1470
 *             (source CRS if direction is inverse).
1471
 * @param out_xmax Maximum bounding coordinate of the first axis in target CRS.
1472
 *             (source CRS if direction is inverse).
1473
 * @param out_ymax Maximum bounding coordinate of the second axis in target CRS.
1474
 *             (source CRS if direction is inverse).
1475
 * @param densify_pts Recommended to use 21. This is the number of points
1476
 *     to use to densify the bounding polygon in the transformation.
1477
 * @return an integer. 1 if successful. 0 if failures encountered.
1478
 * @since 8.2
1479
 */
1480
int proj_trans_bounds(PJ_CONTEXT *context, PJ *P, PJ_DIRECTION direction,
1481
                      const double xmin, const double ymin, const double xmax,
1482
                      const double ymax, double *out_xmin, double *out_ymin,
1483
                      double *out_xmax, double *out_ymax,
1484
0
                      const int densify_pts) {
1485
0
    *out_xmin = HUGE_VAL;
1486
0
    *out_ymin = HUGE_VAL;
1487
0
    *out_xmax = HUGE_VAL;
1488
0
    *out_ymax = HUGE_VAL;
1489
1490
0
    if (P == nullptr) {
1491
0
        proj_log_error(P, _("NULL P object not allowed."));
1492
0
        proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
1493
0
        return false;
1494
0
    }
1495
0
    if (densify_pts < 0 || densify_pts > 10000) {
1496
0
        proj_log_error(P, _("densify_pts must be between 0-10000."));
1497
0
        proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
1498
0
        return false;
1499
0
    }
1500
1501
0
    PJ_PROJ_INFO pj_info = proj_pj_info(P);
1502
0
    if (pj_info.id == nullptr) {
1503
0
        proj_log_error(P, _("NULL transformation not allowed,"));
1504
0
        proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
1505
0
        return false;
1506
0
    }
1507
0
    if (strcmp(pj_info.id, "noop") == 0 || direction == PJ_IDENT) {
1508
0
        *out_xmin = xmin;
1509
0
        *out_xmax = xmax;
1510
0
        *out_ymin = ymin;
1511
0
        *out_ymax = ymax;
1512
0
        return true;
1513
0
    }
1514
1515
0
    bool degree_output = proj_degree_output(P, direction) != 0;
1516
0
    bool degree_input = proj_degree_input(P, direction) != 0;
1517
0
    if (degree_output && densify_pts < 2) {
1518
0
        proj_log_error(
1519
0
            P,
1520
0
            _("densify_pts must be at least 2 if the output is geographic."));
1521
0
        proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
1522
0
        return false;
1523
0
    }
1524
1525
0
    int side_pts = densify_pts + 1; // add one because we are densifying
1526
0
    const int boundary_len = side_pts * 4;
1527
0
    std::vector<double> x_boundary_array;
1528
0
    std::vector<double> y_boundary_array;
1529
0
    try {
1530
0
        x_boundary_array.resize(boundary_len);
1531
0
        y_boundary_array.resize(boundary_len);
1532
0
    } catch (const std::exception &e) // memory allocation failure
1533
0
    {
1534
0
        proj_log_error(P, e.what());
1535
0
        proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
1536
0
        return false;
1537
0
    }
1538
0
    double delta_x = 0;
1539
0
    double delta_y = 0;
1540
0
    bool north_pole_in_bounds = false;
1541
0
    bool south_pole_in_bounds = false;
1542
0
    bool input_lon_lat_order = false;
1543
0
    bool output_lon_lat_order = false;
1544
0
    if (degree_input) {
1545
0
        int in_order_lon_lat =
1546
0
            target_crs_lon_lat_order(context, P, opposite_direction(direction));
1547
0
        if (in_order_lon_lat == -1)
1548
0
            return false;
1549
0
        input_lon_lat_order = in_order_lon_lat != 0;
1550
0
    }
1551
0
    if (degree_output) {
1552
0
        int out_order_lon_lat = target_crs_lon_lat_order(context, P, direction);
1553
0
        if (out_order_lon_lat == -1)
1554
0
            return false;
1555
0
        output_lon_lat_order = out_order_lon_lat != 0;
1556
0
        north_pole_in_bounds = contains_north_pole(
1557
0
            P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order);
1558
0
        south_pole_in_bounds = contains_south_pole(
1559
0
            P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order);
1560
0
    }
1561
1562
0
    if (degree_input && xmax < xmin) {
1563
0
        if (!input_lon_lat_order) {
1564
0
            proj_log_error(P, _("latitude max < latitude min."));
1565
0
            proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
1566
0
            return false;
1567
0
        }
1568
        // handle antimeridian
1569
0
        delta_x = (xmax - xmin + 360.0) / side_pts;
1570
0
    } else {
1571
0
        delta_x = (xmax - xmin) / side_pts;
1572
0
    }
1573
0
    if (degree_input && ymax < ymin) {
1574
0
        if (input_lon_lat_order) {
1575
0
            proj_log_error(P, _("latitude max < latitude min."));
1576
0
            proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
1577
0
            return false;
1578
0
        }
1579
        // handle antimeridian
1580
0
        delta_y = (ymax - ymin + 360.0) / side_pts;
1581
0
    } else {
1582
0
        delta_y = (ymax - ymin) / side_pts;
1583
0
    }
1584
1585
    // build densified bounding box
1586
    // Note: must be a linear ring for antimeridian logic
1587
0
    for (int iii = 0; iii < side_pts; iii++) {
1588
        // xmin boundary
1589
0
        y_boundary_array[iii] = ymax - iii * delta_y;
1590
0
        x_boundary_array[iii] = xmin;
1591
        // ymin boundary
1592
0
        y_boundary_array[iii + side_pts] = ymin;
1593
0
        x_boundary_array[iii + side_pts] = xmin + iii * delta_x;
1594
        // xmax boundary
1595
0
        y_boundary_array[iii + side_pts * 2] = ymin + iii * delta_y;
1596
0
        x_boundary_array[iii + side_pts * 2] = xmax;
1597
        // ymax boundary
1598
0
        y_boundary_array[iii + side_pts * 3] = ymax;
1599
0
        x_boundary_array[iii + side_pts * 3] = xmax - iii * delta_x;
1600
0
    }
1601
0
    proj_trans_generic(P, direction, &x_boundary_array[0], sizeof(double),
1602
0
                       boundary_len, &y_boundary_array[0], sizeof(double),
1603
0
                       boundary_len, nullptr, 0, 0, nullptr, 0, 0);
1604
1605
0
    if (!degree_output) {
1606
0
        *out_xmin = simple_min(&x_boundary_array[0], boundary_len);
1607
0
        *out_xmax = simple_max(&x_boundary_array[0], boundary_len);
1608
0
        *out_ymin = simple_min(&y_boundary_array[0], boundary_len);
1609
0
        *out_ymax = simple_max(&y_boundary_array[0], boundary_len);
1610
0
    } else if (north_pole_in_bounds && output_lon_lat_order) {
1611
0
        *out_xmin = -180;
1612
0
        *out_ymin = simple_min(&y_boundary_array[0], boundary_len);
1613
0
        *out_xmax = 180;
1614
0
        *out_ymax = 90;
1615
0
    } else if (north_pole_in_bounds) {
1616
0
        *out_xmin = simple_min(&x_boundary_array[0], boundary_len);
1617
0
        *out_ymin = -180;
1618
0
        *out_xmax = 90;
1619
0
        *out_ymax = 180;
1620
0
    } else if (south_pole_in_bounds && output_lon_lat_order) {
1621
0
        *out_xmin = -180;
1622
0
        *out_ymin = -90;
1623
0
        *out_xmax = 180;
1624
0
        *out_ymax = simple_max(&y_boundary_array[0], boundary_len);
1625
0
    } else if (south_pole_in_bounds) {
1626
0
        *out_xmin = -90;
1627
0
        *out_ymin = -180;
1628
0
        *out_xmax = simple_max(&x_boundary_array[0], boundary_len);
1629
0
        *out_ymax = 180;
1630
0
    } else if (output_lon_lat_order) {
1631
0
        *out_xmin = antimeridian_min(&x_boundary_array[0], boundary_len);
1632
0
        *out_xmax = antimeridian_max(&x_boundary_array[0], boundary_len);
1633
0
        *out_ymin = simple_min(&y_boundary_array[0], boundary_len);
1634
0
        *out_ymax = simple_max(&y_boundary_array[0], boundary_len);
1635
0
    } else {
1636
0
        *out_xmin = simple_min(&x_boundary_array[0], boundary_len);
1637
0
        *out_xmax = simple_max(&x_boundary_array[0], boundary_len);
1638
0
        *out_ymin = antimeridian_min(&y_boundary_array[0], boundary_len);
1639
0
        *out_ymax = antimeridian_max(&y_boundary_array[0], boundary_len);
1640
0
    }
1641
0
    return true;
1642
0
}
1643
1644
/*****************************************************************************/
1645
static void reproject_bbox(PJ *pjGeogToCrs, double west_lon, double south_lat,
1646
                           double east_lon, double north_lat, double &minx,
1647
0
                           double &miny, double &maxx, double &maxy) {
1648
    /*****************************************************************************/
1649
1650
0
    minx = -std::numeric_limits<double>::max();
1651
0
    miny = -std::numeric_limits<double>::max();
1652
0
    maxx = std::numeric_limits<double>::max();
1653
0
    maxy = std::numeric_limits<double>::max();
1654
1655
0
    if (!(west_lon == -180.0 && east_lon == 180.0 && south_lat == -90.0 &&
1656
0
          north_lat == 90.0)) {
1657
0
        minx = -minx;
1658
0
        miny = -miny;
1659
0
        maxx = -maxx;
1660
0
        maxy = -maxy;
1661
1662
0
        constexpr int N_STEPS = 20;
1663
0
        constexpr int N_STEPS_P1 = N_STEPS + 1;
1664
0
        constexpr int XY_SIZE = N_STEPS_P1 * 4;
1665
0
        std::vector<double> x(XY_SIZE);
1666
0
        std::vector<double> y(XY_SIZE);
1667
0
        const double step_lon = (east_lon - west_lon) / N_STEPS;
1668
0
        const double step_lat = (north_lat - south_lat) / N_STEPS;
1669
0
        for (int j = 0; j <= N_STEPS; j++) {
1670
0
            x[j] = west_lon + j * step_lon;
1671
0
            y[j] = south_lat;
1672
0
            x[N_STEPS_P1 + j] = x[j];
1673
0
            y[N_STEPS_P1 + j] = north_lat;
1674
0
            x[N_STEPS_P1 * 2 + j] = west_lon;
1675
0
            y[N_STEPS_P1 * 2 + j] = south_lat + j * step_lat;
1676
0
            x[N_STEPS_P1 * 3 + j] = east_lon;
1677
0
            y[N_STEPS_P1 * 3 + j] = y[N_STEPS_P1 * 2 + j];
1678
0
        }
1679
0
        proj_trans_generic(pjGeogToCrs, PJ_FWD, &x[0], sizeof(double), XY_SIZE,
1680
0
                           &y[0], sizeof(double), XY_SIZE, nullptr, 0, 0,
1681
0
                           nullptr, 0, 0);
1682
0
        for (int j = 0; j < XY_SIZE; j++) {
1683
0
            if (x[j] != HUGE_VAL && y[j] != HUGE_VAL) {
1684
0
                minx = std::min(minx, x[j]);
1685
0
                miny = std::min(miny, y[j]);
1686
0
                maxx = std::max(maxx, x[j]);
1687
0
                maxy = std::max(maxy, y[j]);
1688
0
            }
1689
0
        }
1690
0
    }
1691
0
}
1692
1693
/*****************************************************************************/
1694
static PJ *add_coord_op_to_list(
1695
    int idxInOriginalList, PJ *op, double west_lon, double south_lat,
1696
    double east_lon, double north_lat, PJ *pjGeogToSrc, PJ *pjGeogToDst,
1697
    const PJ *pjSrcGeocentricToLonLat, const PJ *pjDstGeocentricToLonLat,
1698
0
    const char *areaName, std::vector<PJCoordOperation> &altCoordOps) {
1699
    /*****************************************************************************/
1700
1701
0
    double minxSrc;
1702
0
    double minySrc;
1703
0
    double maxxSrc;
1704
0
    double maxySrc;
1705
0
    double minxDst;
1706
0
    double minyDst;
1707
0
    double maxxDst;
1708
0
    double maxyDst;
1709
1710
0
    double w = west_lon / 180 * M_PI;
1711
0
    double s = south_lat / 180 * M_PI;
1712
0
    double e = east_lon / 180 * M_PI;
1713
0
    double n = north_lat / 180 * M_PI;
1714
0
    if (w > e) {
1715
0
        e += 2 * M_PI;
1716
0
    }
1717
    // Integrate cos(lat) between south_lat and north_lat
1718
0
    const double pseudoArea = (e - w) * (std::sin(n) - std::sin(s));
1719
1720
0
    if (pjSrcGeocentricToLonLat) {
1721
0
        minxSrc = west_lon;
1722
0
        minySrc = south_lat;
1723
0
        maxxSrc = east_lon;
1724
0
        maxySrc = north_lat;
1725
0
    } else {
1726
0
        reproject_bbox(pjGeogToSrc, west_lon, south_lat, east_lon, north_lat,
1727
0
                       minxSrc, minySrc, maxxSrc, maxySrc);
1728
0
    }
1729
1730
0
    if (pjDstGeocentricToLonLat) {
1731
0
        minxDst = west_lon;
1732
0
        minyDst = south_lat;
1733
0
        maxxDst = east_lon;
1734
0
        maxyDst = north_lat;
1735
0
    } else {
1736
0
        reproject_bbox(pjGeogToDst, west_lon, south_lat, east_lon, north_lat,
1737
0
                       minxDst, minyDst, maxxDst, maxyDst);
1738
0
    }
1739
1740
0
    if (minxSrc <= maxxSrc && minxDst <= maxxDst) {
1741
0
        const char *c_name = proj_get_name(op);
1742
0
        std::string name(c_name ? c_name : "");
1743
1744
0
        const double accuracy = proj_coordoperation_get_accuracy(op->ctx, op);
1745
0
        altCoordOps.emplace_back(
1746
0
            idxInOriginalList, minxSrc, minySrc, maxxSrc, maxySrc, minxDst,
1747
0
            minyDst, maxxDst, maxyDst, op, name, accuracy, pseudoArea, areaName,
1748
0
            pjSrcGeocentricToLonLat, pjDstGeocentricToLonLat);
1749
0
        op = nullptr;
1750
0
    }
1751
0
    return op;
1752
0
}
1753
1754
namespace {
1755
struct ObjectKeeper {
1756
    PJ *m_obj = nullptr;
1757
0
    explicit ObjectKeeper(PJ *obj) : m_obj(obj) {}
1758
0
    ~ObjectKeeper() { proj_destroy(m_obj); }
1759
    ObjectKeeper(const ObjectKeeper &) = delete;
1760
    ObjectKeeper &operator=(const ObjectKeeper &) = delete;
1761
};
1762
} // namespace
1763
1764
/*****************************************************************************/
1765
0
static PJ *create_operation_to_geog_crs(PJ_CONTEXT *ctx, const PJ *crs) {
1766
    /*****************************************************************************/
1767
1768
0
    std::unique_ptr<ObjectKeeper> keeper;
1769
0
    if (proj_get_type(crs) == PJ_TYPE_COORDINATE_METADATA) {
1770
0
        auto tmp = proj_get_source_crs(ctx, crs);
1771
0
        assert(tmp);
1772
0
        keeper.reset(new ObjectKeeper(tmp));
1773
0
        crs = tmp;
1774
0
    }
1775
0
    (void)keeper;
1776
1777
    // Create a geographic 2D long-lat degrees CRS that is related to the
1778
    // CRS
1779
0
    auto geodetic_crs = proj_crs_get_geodetic_crs(ctx, crs);
1780
0
    if (!geodetic_crs) {
1781
0
        proj_context_log_debug(ctx, "Cannot find geodetic CRS matching CRS");
1782
0
        return nullptr;
1783
0
    }
1784
1785
0
    auto geodetic_crs_type = proj_get_type(geodetic_crs);
1786
0
    if (geodetic_crs_type == PJ_TYPE_GEOCENTRIC_CRS ||
1787
0
        geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
1788
0
        geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
1789
0
        auto datum = proj_crs_get_datum_forced(ctx, geodetic_crs);
1790
0
        assert(datum);
1791
0
        auto cs = proj_create_ellipsoidal_2D_cs(
1792
0
            ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
1793
0
        auto ellps = proj_get_ellipsoid(ctx, datum);
1794
0
        proj_destroy(datum);
1795
0
        double semi_major_metre = 0;
1796
0
        double inv_flattening = 0;
1797
0
        proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, nullptr,
1798
0
                                      nullptr, &inv_flattening);
1799
        // It is critical to set the prime meridian to 0
1800
0
        auto temp = proj_create_geographic_crs(
1801
0
            ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps),
1802
0
            semi_major_metre, inv_flattening, "Reference prime meridian", 0,
1803
0
            nullptr, 0, cs);
1804
0
        proj_destroy(ellps);
1805
0
        proj_destroy(cs);
1806
0
        proj_destroy(geodetic_crs);
1807
0
        geodetic_crs = temp;
1808
0
        geodetic_crs_type = proj_get_type(geodetic_crs);
1809
0
    }
1810
0
    if (geodetic_crs_type != PJ_TYPE_GEOGRAPHIC_2D_CRS) {
1811
        // Shouldn't happen
1812
0
        proj_context_log_debug(ctx, "Cannot find geographic CRS matching CRS");
1813
0
        proj_destroy(geodetic_crs);
1814
0
        return nullptr;
1815
0
    }
1816
1817
    // Create the transformation from this geographic 2D CRS to the source CRS
1818
0
    auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr);
1819
0
    proj_operation_factory_context_set_spatial_criterion(
1820
0
        ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1821
0
    proj_operation_factory_context_set_grid_availability_use(
1822
0
        ctx, operation_ctx,
1823
0
        PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
1824
0
    auto target_crs_2D = proj_crs_demote_to_2D(ctx, nullptr, crs);
1825
0
    auto op_list_to_geodetic =
1826
0
        proj_create_operations(ctx, geodetic_crs, target_crs_2D, operation_ctx);
1827
0
    proj_destroy(target_crs_2D);
1828
0
    proj_operation_factory_context_destroy(operation_ctx);
1829
0
    proj_destroy(geodetic_crs);
1830
1831
0
    const int nOpCount = op_list_to_geodetic == nullptr
1832
0
                             ? 0
1833
0
                             : proj_list_get_count(op_list_to_geodetic);
1834
0
    if (nOpCount == 0) {
1835
0
        proj_context_log_debug(
1836
0
            ctx, "Cannot compute transformation from geographic CRS to CRS");
1837
0
        proj_list_destroy(op_list_to_geodetic);
1838
0
        return nullptr;
1839
0
    }
1840
0
    PJ *opGeogToCrs = nullptr;
1841
    // Use in priority operations *without* grids
1842
0
    for (int i = 0; i < nOpCount; i++) {
1843
0
        auto op = proj_list_get(ctx, op_list_to_geodetic, i);
1844
0
        assert(op);
1845
0
        if (proj_coordoperation_get_grid_used_count(ctx, op) == 0) {
1846
0
            opGeogToCrs = op;
1847
0
            break;
1848
0
        }
1849
0
        proj_destroy(op);
1850
0
    }
1851
0
    if (opGeogToCrs == nullptr) {
1852
0
        opGeogToCrs = proj_list_get(ctx, op_list_to_geodetic, 0);
1853
0
        assert(opGeogToCrs);
1854
0
    }
1855
0
    proj_list_destroy(op_list_to_geodetic);
1856
0
    return opGeogToCrs;
1857
0
}
1858
1859
/*****************************************************************************/
1860
static PJ *create_operation_geocentric_crs_to_geog_crs(PJ_CONTEXT *ctx,
1861
                                                       const PJ *geocentric_crs)
1862
/*****************************************************************************/
1863
0
{
1864
0
    assert(proj_get_type(geocentric_crs) == PJ_TYPE_GEOCENTRIC_CRS);
1865
1866
0
    auto datum = proj_crs_get_datum_forced(ctx, geocentric_crs);
1867
0
    assert(datum);
1868
0
    auto cs = proj_create_ellipsoidal_2D_cs(ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE,
1869
0
                                            nullptr, 0);
1870
0
    auto ellps = proj_get_ellipsoid(ctx, datum);
1871
0
    proj_destroy(datum);
1872
0
    double semi_major_metre = 0;
1873
0
    double inv_flattening = 0;
1874
0
    proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, nullptr,
1875
0
                                  nullptr, &inv_flattening);
1876
    // It is critical to set the prime meridian to 0
1877
0
    auto lon_lat_crs = proj_create_geographic_crs(
1878
0
        ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps),
1879
0
        semi_major_metre, inv_flattening, "Reference prime meridian", 0,
1880
0
        nullptr, 0, cs);
1881
0
    proj_destroy(ellps);
1882
0
    proj_destroy(cs);
1883
1884
    // Create the transformation from this geocentric CRS to the lon-lat one
1885
0
    auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr);
1886
0
    proj_operation_factory_context_set_spatial_criterion(
1887
0
        ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1888
0
    proj_operation_factory_context_set_grid_availability_use(
1889
0
        ctx, operation_ctx,
1890
0
        PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
1891
0
    auto op_list =
1892
0
        proj_create_operations(ctx, geocentric_crs, lon_lat_crs, operation_ctx);
1893
0
    proj_operation_factory_context_destroy(operation_ctx);
1894
0
    proj_destroy(lon_lat_crs);
1895
1896
0
    const int nOpCount = op_list == nullptr ? 0 : proj_list_get_count(op_list);
1897
0
    if (nOpCount != 1) {
1898
0
        proj_context_log_debug(ctx, "Cannot compute transformation from "
1899
0
                                    "geocentric CRS to geographic CRS");
1900
0
        proj_list_destroy(op_list);
1901
0
        return nullptr;
1902
0
    }
1903
1904
0
    auto op = proj_list_get(ctx, op_list, 0);
1905
0
    assert(op);
1906
0
    proj_list_destroy(op_list);
1907
0
    return op;
1908
0
}
1909
1910
/*****************************************************************************/
1911
PJ *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *source_crs,
1912
28.9k
                           const char *target_crs, PJ_AREA *area) {
1913
    /******************************************************************************
1914
        Create a transformation pipeline between two known coordinate reference
1915
        systems.
1916
1917
        See docs/source/development/reference/functions.rst
1918
1919
    ******************************************************************************/
1920
28.9k
    if (!ctx) {
1921
28.9k
        ctx = pj_get_default_ctx();
1922
28.9k
    }
1923
1924
28.9k
    PJ *src;
1925
28.9k
    PJ *dst;
1926
28.9k
    try {
1927
28.9k
        std::string source_crs_modified(pj_add_type_crs_if_needed(source_crs));
1928
28.9k
        std::string target_crs_modified(pj_add_type_crs_if_needed(target_crs));
1929
1930
28.9k
        src = proj_create(ctx, source_crs_modified.c_str());
1931
28.9k
        if (!src) {
1932
12.3k
            proj_context_log_debug(ctx, "Cannot instantiate source_crs");
1933
12.3k
            return nullptr;
1934
12.3k
        }
1935
1936
16.5k
        dst = proj_create(ctx, target_crs_modified.c_str());
1937
16.5k
        if (!dst) {
1938
7.43k
            proj_context_log_debug(ctx, "Cannot instantiate target_crs");
1939
7.43k
            proj_destroy(src);
1940
7.43k
            return nullptr;
1941
7.43k
        }
1942
16.5k
    } catch (const std::exception &) {
1943
0
        return nullptr;
1944
0
    }
1945
1946
9.08k
    auto ret = proj_create_crs_to_crs_from_pj(ctx, src, dst, area, nullptr);
1947
9.08k
    proj_destroy(src);
1948
9.08k
    proj_destroy(dst);
1949
9.08k
    return ret;
1950
28.9k
}
1951
1952
/*****************************************************************************/
1953
std::vector<PJCoordOperation>
1954
pj_create_prepared_operations(PJ_CONTEXT *ctx, const PJ *source_crs,
1955
                              const PJ *target_crs, PJ_OBJ_LIST *op_list)
1956
/*****************************************************************************/
1957
0
{
1958
0
    PJ *pjGeogToSrc = nullptr;
1959
0
    PJ *pjSrcGeocentricToLonLat = nullptr;
1960
0
    if (proj_get_type(source_crs) == PJ_TYPE_GEOCENTRIC_CRS) {
1961
0
        pjSrcGeocentricToLonLat =
1962
0
            create_operation_geocentric_crs_to_geog_crs(ctx, source_crs);
1963
0
        if (!pjSrcGeocentricToLonLat) {
1964
            // shouldn't happen
1965
0
            return {};
1966
0
        }
1967
0
    } else {
1968
0
        pjGeogToSrc = create_operation_to_geog_crs(ctx, source_crs);
1969
0
        if (!pjGeogToSrc) {
1970
0
            proj_context_log_debug(
1971
0
                ctx, "Cannot create transformation from geographic "
1972
0
                     "CRS of source CRS to source CRS");
1973
0
            return {};
1974
0
        }
1975
0
    }
1976
1977
0
    PJ *pjGeogToDst = nullptr;
1978
0
    PJ *pjDstGeocentricToLonLat = nullptr;
1979
0
    if (proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS) {
1980
0
        pjDstGeocentricToLonLat =
1981
0
            create_operation_geocentric_crs_to_geog_crs(ctx, target_crs);
1982
0
        if (!pjDstGeocentricToLonLat) {
1983
            // shouldn't happen
1984
0
            proj_destroy(pjSrcGeocentricToLonLat);
1985
0
            proj_destroy(pjGeogToSrc);
1986
0
            return {};
1987
0
        }
1988
0
    } else {
1989
0
        pjGeogToDst = create_operation_to_geog_crs(ctx, target_crs);
1990
0
        if (!pjGeogToDst) {
1991
0
            proj_context_log_debug(
1992
0
                ctx, "Cannot create transformation from geographic "
1993
0
                     "CRS of target CRS to target CRS");
1994
0
            proj_destroy(pjSrcGeocentricToLonLat);
1995
0
            proj_destroy(pjGeogToSrc);
1996
0
            return {};
1997
0
        }
1998
0
    }
1999
2000
0
    try {
2001
0
        std::vector<PJCoordOperation> preparedOpList;
2002
2003
        // Iterate over source->target candidate transformations and reproject
2004
        // their long-lat bounding box into the source CRS.
2005
0
        const auto op_count = proj_list_get_count(op_list);
2006
0
        for (int i = 0; i < op_count; i++) {
2007
0
            auto op = proj_list_get(ctx, op_list, i);
2008
0
            assert(op);
2009
0
            double west_lon = 0.0;
2010
0
            double south_lat = 0.0;
2011
0
            double east_lon = 0.0;
2012
0
            double north_lat = 0.0;
2013
2014
0
            const char *areaName = nullptr;
2015
0
            if (!proj_get_area_of_use(ctx, op, &west_lon, &south_lat, &east_lon,
2016
0
                                      &north_lat, &areaName)) {
2017
0
                west_lon = -180;
2018
0
                south_lat = -90;
2019
0
                east_lon = 180;
2020
0
                north_lat = 90;
2021
0
            }
2022
2023
0
            if (west_lon <= east_lon) {
2024
0
                op = add_coord_op_to_list(
2025
0
                    i, op, west_lon, south_lat, east_lon, north_lat,
2026
0
                    pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat,
2027
0
                    pjDstGeocentricToLonLat, areaName, preparedOpList);
2028
0
            } else {
2029
0
                auto op_clone = proj_clone(ctx, op);
2030
2031
0
                op = add_coord_op_to_list(
2032
0
                    i, op, west_lon, south_lat, 180, north_lat, pjGeogToSrc,
2033
0
                    pjGeogToDst, pjSrcGeocentricToLonLat,
2034
0
                    pjDstGeocentricToLonLat, areaName, preparedOpList);
2035
0
                op_clone = add_coord_op_to_list(
2036
0
                    i, op_clone, -180, south_lat, east_lon, north_lat,
2037
0
                    pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat,
2038
0
                    pjDstGeocentricToLonLat, areaName, preparedOpList);
2039
0
                proj_destroy(op_clone);
2040
0
            }
2041
2042
0
            proj_destroy(op);
2043
0
        }
2044
2045
0
        proj_destroy(pjGeogToSrc);
2046
0
        proj_destroy(pjGeogToDst);
2047
0
        proj_destroy(pjSrcGeocentricToLonLat);
2048
0
        proj_destroy(pjDstGeocentricToLonLat);
2049
0
        return preparedOpList;
2050
0
    } catch (const std::exception &) {
2051
0
        proj_destroy(pjGeogToSrc);
2052
0
        proj_destroy(pjGeogToDst);
2053
0
        proj_destroy(pjSrcGeocentricToLonLat);
2054
0
        proj_destroy(pjDstGeocentricToLonLat);
2055
0
        return {};
2056
0
    }
2057
0
}
2058
2059
// ---------------------------------------------------------------------------
2060
2061
//! @cond Doxygen_Suppress
2062
static const char *getOptionValue(const char *option,
2063
0
                                  const char *keyWithEqual) noexcept {
2064
0
    if (ci_starts_with(option, keyWithEqual)) {
2065
0
        return option + strlen(keyWithEqual);
2066
0
    }
2067
0
    return nullptr;
2068
0
}
2069
//! @endcond
2070
2071
/*****************************************************************************/
2072
PJ *proj_create_crs_to_crs_from_pj(PJ_CONTEXT *ctx, const PJ *source_crs,
2073
                                   const PJ *target_crs, PJ_AREA *area,
2074
9.08k
                                   const char *const *options) {
2075
    /******************************************************************************
2076
        Create a transformation pipeline between two known coordinate reference
2077
        systems.
2078
2079
        See docs/source/development/reference/functions.rst
2080
2081
    ******************************************************************************/
2082
9.08k
    if (!ctx) {
2083
0
        ctx = pj_get_default_ctx();
2084
0
    }
2085
9.08k
    pj_load_ini(
2086
9.08k
        ctx); // to set ctx->errorIfBestTransformationNotAvailableDefault
2087
2088
9.08k
    const char *authority = nullptr;
2089
9.08k
    double accuracy = -1;
2090
9.08k
    bool allowBallparkTransformations = true;
2091
9.08k
    bool forceOver = false;
2092
9.08k
    bool warnIfBestTransformationNotAvailable =
2093
9.08k
        ctx->warnIfBestTransformationNotAvailableDefault;
2094
9.08k
    bool errorIfBestTransformationNotAvailable =
2095
9.08k
        ctx->errorIfBestTransformationNotAvailableDefault;
2096
9.08k
    for (auto iter = options; iter && iter[0]; ++iter) {
2097
0
        const char *value;
2098
0
        if ((value = getOptionValue(*iter, "AUTHORITY="))) {
2099
0
            authority = value;
2100
0
        } else if ((value = getOptionValue(*iter, "ACCURACY="))) {
2101
0
            accuracy = pj_atof(value);
2102
0
        } else if ((value = getOptionValue(*iter, "ALLOW_BALLPARK="))) {
2103
0
            if (ci_equal(value, "yes"))
2104
0
                allowBallparkTransformations = true;
2105
0
            else if (ci_equal(value, "no"))
2106
0
                allowBallparkTransformations = false;
2107
0
            else {
2108
0
                ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR,
2109
0
                            "Invalid value for ALLOW_BALLPARK option.");
2110
0
                return nullptr;
2111
0
            }
2112
0
        } else if ((value = getOptionValue(*iter, "ONLY_BEST="))) {
2113
0
            warnIfBestTransformationNotAvailable = false;
2114
0
            if (ci_equal(value, "yes"))
2115
0
                errorIfBestTransformationNotAvailable = true;
2116
0
            else if (ci_equal(value, "no"))
2117
0
                errorIfBestTransformationNotAvailable = false;
2118
0
            else {
2119
0
                ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR,
2120
0
                            "Invalid value for ONLY_BEST option.");
2121
0
                return nullptr;
2122
0
            }
2123
0
        } else if ((value = getOptionValue(*iter, "FORCE_OVER="))) {
2124
0
            if (ci_equal(value, "yes")) {
2125
0
                forceOver = true;
2126
0
            }
2127
0
        } else {
2128
0
            std::string msg("Unknown option :");
2129
0
            msg += *iter;
2130
0
            ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, msg.c_str());
2131
0
            return nullptr;
2132
0
        }
2133
0
    }
2134
2135
9.08k
    auto operation_ctx = proj_create_operation_factory_context(ctx, authority);
2136
9.08k
    if (!operation_ctx) {
2137
0
        return nullptr;
2138
0
    }
2139
2140
9.08k
    proj_operation_factory_context_set_allow_ballpark_transformations(
2141
9.08k
        ctx, operation_ctx, allowBallparkTransformations);
2142
2143
9.08k
    if (accuracy >= 0) {
2144
0
        proj_operation_factory_context_set_desired_accuracy(ctx, operation_ctx,
2145
0
                                                            accuracy);
2146
0
    }
2147
2148
9.08k
    if (area && area->bbox_set) {
2149
0
        proj_operation_factory_context_set_area_of_interest(
2150
0
            ctx, operation_ctx, area->west_lon_degree, area->south_lat_degree,
2151
0
            area->east_lon_degree, area->north_lat_degree);
2152
2153
0
        if (!area->name.empty()) {
2154
0
            proj_operation_factory_context_set_area_of_interest_name(
2155
0
                ctx, operation_ctx, area->name.c_str());
2156
0
        }
2157
0
    }
2158
2159
9.08k
    proj_operation_factory_context_set_spatial_criterion(
2160
9.08k
        ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
2161
9.08k
    proj_operation_factory_context_set_grid_availability_use(
2162
9.08k
        ctx, operation_ctx,
2163
9.08k
        (errorIfBestTransformationNotAvailable ||
2164
9.08k
         warnIfBestTransformationNotAvailable ||
2165
9.08k
         proj_context_is_network_enabled(ctx))
2166
9.08k
            ? PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE
2167
9.08k
            : PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
2168
2169
9.08k
    auto op_list =
2170
9.08k
        proj_create_operations(ctx, source_crs, target_crs, operation_ctx);
2171
9.08k
    proj_operation_factory_context_destroy(operation_ctx);
2172
2173
9.08k
    if (!op_list) {
2174
1.29k
        return nullptr;
2175
1.29k
    }
2176
2177
7.79k
    auto op_count = proj_list_get_count(op_list);
2178
7.79k
    if (op_count == 0) {
2179
176
        proj_list_destroy(op_list);
2180
2181
176
        proj_context_log_debug(ctx, "No operation found matching criteria");
2182
176
        return nullptr;
2183
176
    }
2184
2185
7.61k
    ctx->forceOver = forceOver;
2186
2187
7.61k
    const int old_debug_level = ctx->debug_level;
2188
7.61k
    if (errorIfBestTransformationNotAvailable ||
2189
7.61k
        warnIfBestTransformationNotAvailable)
2190
0
        ctx->debug_level = PJ_LOG_NONE;
2191
7.61k
    PJ *P = proj_list_get(ctx, op_list, 0);
2192
7.61k
    ctx->debug_level = old_debug_level;
2193
7.61k
    assert(P);
2194
2195
7.61k
    if (P != nullptr) {
2196
7.61k
        P->errorIfBestTransformationNotAvailable =
2197
7.61k
            errorIfBestTransformationNotAvailable;
2198
7.61k
        P->warnIfBestTransformationNotAvailable =
2199
7.61k
            warnIfBestTransformationNotAvailable;
2200
7.61k
        P->skipNonInstantiable = warnIfBestTransformationNotAvailable;
2201
7.61k
    }
2202
2203
7.61k
    const bool mayNeedToReRunWithDiscardMissing =
2204
7.61k
        (errorIfBestTransformationNotAvailable ||
2205
7.61k
         warnIfBestTransformationNotAvailable) &&
2206
7.61k
        !proj_context_is_network_enabled(ctx);
2207
7.61k
    int singleOpIsInstanciable = -1;
2208
7.61k
    if (P != nullptr && op_count == 1 && mayNeedToReRunWithDiscardMissing) {
2209
0
        singleOpIsInstanciable = proj_coordoperation_is_instantiable(ctx, P);
2210
0
    }
2211
2212
7.61k
    const auto backup_errno = proj_context_errno(ctx);
2213
7.61k
    if (P == nullptr ||
2214
7.61k
        (op_count == 1 && (!mayNeedToReRunWithDiscardMissing ||
2215
7.61k
                           errorIfBestTransformationNotAvailable ||
2216
7.61k
                           singleOpIsInstanciable == static_cast<int>(true)))) {
2217
7.61k
        proj_list_destroy(op_list);
2218
7.61k
        ctx->forceOver = false;
2219
2220
7.61k
        if (P != nullptr && (errorIfBestTransformationNotAvailable ||
2221
7.61k
                             warnIfBestTransformationNotAvailable)) {
2222
0
            if (singleOpIsInstanciable < 0) {
2223
0
                singleOpIsInstanciable =
2224
0
                    proj_coordoperation_is_instantiable(ctx, P);
2225
0
            }
2226
0
            if (!singleOpIsInstanciable) {
2227
0
                warnAboutMissingGrid(P);
2228
0
                if (errorIfBestTransformationNotAvailable) {
2229
0
                    proj_destroy(P);
2230
0
                    return nullptr;
2231
0
                }
2232
0
            }
2233
0
        }
2234
2235
7.61k
        if (P != nullptr) {
2236
7.61k
            P->over = forceOver;
2237
7.61k
        }
2238
7.61k
        return P;
2239
7.61k
    } else if (op_count == 1 && mayNeedToReRunWithDiscardMissing &&
2240
0
               !singleOpIsInstanciable) {
2241
0
        warnAboutMissingGrid(P);
2242
0
    }
2243
2244
0
    if (errorIfBestTransformationNotAvailable ||
2245
0
        warnIfBestTransformationNotAvailable)
2246
0
        ctx->debug_level = PJ_LOG_NONE;
2247
0
    auto preparedOpList =
2248
0
        pj_create_prepared_operations(ctx, source_crs, target_crs, op_list);
2249
0
    ctx->debug_level = old_debug_level;
2250
2251
0
    ctx->forceOver = false;
2252
0
    proj_list_destroy(op_list);
2253
2254
0
    if (preparedOpList.empty()) {
2255
0
        proj_destroy(P);
2256
0
        return nullptr;
2257
0
    }
2258
2259
0
    bool foundInstanciableAndNonBallpark = false;
2260
2261
0
    for (auto &op : preparedOpList) {
2262
0
        op.pj->over = forceOver;
2263
0
        op.pj->errorIfBestTransformationNotAvailable =
2264
0
            errorIfBestTransformationNotAvailable;
2265
0
        op.pj->warnIfBestTransformationNotAvailable =
2266
0
            warnIfBestTransformationNotAvailable;
2267
0
        if (mayNeedToReRunWithDiscardMissing &&
2268
0
            !foundInstanciableAndNonBallpark) {
2269
0
            if (!proj_coordoperation_has_ballpark_transformation(op.pj->ctx,
2270
0
                                                                 op.pj) &&
2271
0
                op.isInstantiable()) {
2272
0
                foundInstanciableAndNonBallpark = true;
2273
0
            }
2274
0
        }
2275
0
    }
2276
0
    if (mayNeedToReRunWithDiscardMissing && !foundInstanciableAndNonBallpark) {
2277
        // Re-run proj_create_operations with
2278
        // PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID
2279
        // Can happen for example for NAD27->NAD83 transformation when we
2280
        // have no grid and thus have fallback to Helmert transformation and
2281
        // a WGS84 intermediate.
2282
0
        operation_ctx = proj_create_operation_factory_context(ctx, authority);
2283
0
        if (operation_ctx) {
2284
0
            proj_operation_factory_context_set_allow_ballpark_transformations(
2285
0
                ctx, operation_ctx, allowBallparkTransformations);
2286
2287
0
            if (accuracy >= 0) {
2288
0
                proj_operation_factory_context_set_desired_accuracy(
2289
0
                    ctx, operation_ctx, accuracy);
2290
0
            }
2291
2292
0
            if (area && area->bbox_set) {
2293
0
                proj_operation_factory_context_set_area_of_interest(
2294
0
                    ctx, operation_ctx, area->west_lon_degree,
2295
0
                    area->south_lat_degree, area->east_lon_degree,
2296
0
                    area->north_lat_degree);
2297
2298
0
                if (!area->name.empty()) {
2299
0
                    proj_operation_factory_context_set_area_of_interest_name(
2300
0
                        ctx, operation_ctx, area->name.c_str());
2301
0
                }
2302
0
            }
2303
2304
0
            proj_operation_factory_context_set_spatial_criterion(
2305
0
                ctx, operation_ctx,
2306
0
                PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
2307
0
            proj_operation_factory_context_set_grid_availability_use(
2308
0
                ctx, operation_ctx,
2309
0
                PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
2310
2311
0
            op_list = proj_create_operations(ctx, source_crs, target_crs,
2312
0
                                             operation_ctx);
2313
0
            proj_operation_factory_context_destroy(operation_ctx);
2314
2315
0
            if (op_list) {
2316
0
                ctx->forceOver = forceOver;
2317
0
                ctx->debug_level = PJ_LOG_NONE;
2318
0
                auto preparedOpList2 = pj_create_prepared_operations(
2319
0
                    ctx, source_crs, target_crs, op_list);
2320
0
                ctx->debug_level = old_debug_level;
2321
0
                ctx->forceOver = false;
2322
0
                proj_list_destroy(op_list);
2323
2324
0
                if (!preparedOpList2.empty()) {
2325
                    // Append new lists of operations to previous one
2326
0
                    std::vector<PJCoordOperation> newOpList;
2327
0
                    for (auto &&op : preparedOpList) {
2328
0
                        if (!proj_coordoperation_has_ballpark_transformation(
2329
0
                                op.pj->ctx, op.pj)) {
2330
0
                            newOpList.emplace_back(std::move(op));
2331
0
                        }
2332
0
                    }
2333
0
                    for (auto &&op : preparedOpList2) {
2334
0
                        op.pj->over = forceOver;
2335
0
                        op.pj->errorIfBestTransformationNotAvailable =
2336
0
                            errorIfBestTransformationNotAvailable;
2337
0
                        op.pj->warnIfBestTransformationNotAvailable =
2338
0
                            warnIfBestTransformationNotAvailable;
2339
0
                        newOpList.emplace_back(std::move(op));
2340
0
                    }
2341
0
                    preparedOpList = std::move(newOpList);
2342
0
                } else {
2343
                    // We get there in "cs2cs --only-best --no-ballpark
2344
                    // EPSG:4326+3855 EPSG:4979" use case, where the initial
2345
                    // create_operations returned 1 operation, and the retry
2346
                    // with
2347
                    // PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID
2348
                    // returned 0.
2349
0
                    if (op_count == 1 &&
2350
0
                        errorIfBestTransformationNotAvailable) {
2351
0
                        if (singleOpIsInstanciable < 0) {
2352
0
                            singleOpIsInstanciable =
2353
0
                                proj_coordoperation_is_instantiable(ctx, P);
2354
0
                        }
2355
0
                        if (!singleOpIsInstanciable) {
2356
0
                            proj_destroy(P);
2357
0
                            proj_context_errno_set(ctx, backup_errno);
2358
0
                            return nullptr;
2359
0
                        }
2360
0
                    }
2361
0
                }
2362
0
            }
2363
0
        }
2364
0
    }
2365
2366
    // If there's finally juste a single result, return it directly
2367
0
    if (preparedOpList.size() == 1) {
2368
0
        auto retP = preparedOpList[0].pj;
2369
0
        preparedOpList[0].pj = nullptr;
2370
0
        proj_destroy(P);
2371
0
        return retP;
2372
0
    }
2373
2374
0
    P->alternativeCoordinateOperations = std::move(preparedOpList);
2375
    // The returned P is rather dummy
2376
0
    P->descr = "Set of coordinate operations";
2377
0
    P->over = forceOver;
2378
0
    P->iso_obj = nullptr;
2379
0
    P->fwd = nullptr;
2380
0
    P->inv = nullptr;
2381
0
    P->fwd3d = nullptr;
2382
0
    P->inv3d = nullptr;
2383
0
    P->fwd4d = nullptr;
2384
0
    P->inv4d = nullptr;
2385
2386
0
    return P;
2387
0
}
2388
2389
/*****************************************************************************/
2390
1.48M
int proj_errno(const PJ *P) {
2391
    /******************************************************************************
2392
        Read an error level from the context of a PJ.
2393
    ******************************************************************************/
2394
1.48M
    return proj_context_errno(pj_get_ctx((PJ *)P));
2395
1.48M
}
2396
2397
/*****************************************************************************/
2398
1.54M
int proj_context_errno(PJ_CONTEXT *ctx) {
2399
    /******************************************************************************
2400
        Read an error directly from a context, without going through a PJ
2401
        belonging to that context.
2402
    ******************************************************************************/
2403
1.54M
    if (nullptr == ctx)
2404
0
        ctx = pj_get_default_ctx();
2405
1.54M
    return ctx->last_errno;
2406
1.54M
}
2407
2408
/*****************************************************************************/
2409
13.1k
int proj_errno_set(const PJ *P, int err) {
2410
    /******************************************************************************
2411
        Set context-errno, bubble it up to the thread local errno, return err
2412
    ******************************************************************************/
2413
    /* Use proj_errno_reset to explicitly clear the error status */
2414
13.1k
    if (0 == err)
2415
0
        return 0;
2416
2417
    /* For P==0 err goes to the default context */
2418
13.1k
    proj_context_errno_set(pj_get_ctx((PJ *)P), err);
2419
13.1k
    errno = err;
2420
2421
13.1k
    return err;
2422
13.1k
}
2423
2424
/*****************************************************************************/
2425
550k
int proj_errno_restore(const PJ *P, int err) {
2426
    /******************************************************************************
2427
        Use proj_errno_restore when the current function succeeds, but the
2428
        error flag was set on entry, and stored/reset using proj_errno_reset
2429
        in order to monitor for new errors.
2430
2431
        See usage example under proj_errno_reset ()
2432
    ******************************************************************************/
2433
550k
    if (0 == err)
2434
549k
        return 0;
2435
1.23k
    proj_errno_set(P, err);
2436
1.23k
    return 0;
2437
550k
}
2438
2439
/*****************************************************************************/
2440
591k
int proj_errno_reset(const PJ *P) {
2441
    /******************************************************************************
2442
        Clears errno in the context and thread local levels
2443
        through the low level pj_ctx interface.
2444
2445
        Returns the previous value of the errno, for convenient reset/restore
2446
        operations:
2447
2448
        int foo (PJ *P) {
2449
            // errno may be set on entry, but we need to reset it to be able to
2450
            // check for errors from "do_something_with_P(P)"
2451
            int last_errno = proj_errno_reset (P);
2452
2453
            // local failure
2454
            if (0==P)
2455
                return proj_errno_set (P, 42);
2456
2457
            // call to function that may fail
2458
            do_something_with_P (P);
2459
2460
            // failure in do_something_with_P? - keep latest error status
2461
            if (proj_errno(P))
2462
                return proj_errno (P);
2463
2464
            // success - restore previous error status, return 0
2465
            return proj_errno_restore (P, last_errno);
2466
        }
2467
    ******************************************************************************/
2468
591k
    int last_errno;
2469
591k
    last_errno = proj_errno(P);
2470
2471
591k
    proj_context_errno_set(pj_get_ctx((PJ *)P), 0);
2472
591k
    errno = 0;
2473
591k
    return last_errno;
2474
591k
}
2475
2476
/* Create a new context based on the default context */
2477
14.6k
PJ_CONTEXT *proj_context_create(void) {
2478
14.6k
    return new (std::nothrow) pj_ctx(*pj_get_default_ctx());
2479
14.6k
}
2480
2481
14.6k
PJ_CONTEXT *proj_context_destroy(PJ_CONTEXT *ctx) {
2482
14.6k
    if (nullptr == ctx)
2483
0
        return nullptr;
2484
2485
    /* Trying to free the default context is a no-op (since it is statically
2486
     * allocated) */
2487
14.6k
    if (pj_get_default_ctx() == ctx)
2488
0
        return nullptr;
2489
2490
14.6k
    delete ctx;
2491
14.6k
    return nullptr;
2492
14.6k
}
2493
2494
/*****************************************************************************/
2495
0
static char *path_append(char *buf, const char *app, size_t *buf_size) {
2496
    /******************************************************************************
2497
        Helper for proj_info() below. Append app to buf, separated by a
2498
        semicolon. Also handle allocation of longer buffer if needed.
2499
2500
        Returns buffer and adjusts *buf_size through provided pointer arg.
2501
    ******************************************************************************/
2502
0
    char *p;
2503
0
    size_t len, applen = 0, buflen = 0;
2504
#ifdef _WIN32
2505
    const char *delim = ";";
2506
#else
2507
0
    const char *delim = ":";
2508
0
#endif
2509
2510
    /* Nothing to do? */
2511
0
    if (nullptr == app)
2512
0
        return buf;
2513
0
    applen = strlen(app);
2514
0
    if (0 == applen)
2515
0
        return buf;
2516
2517
    /* Start checking whether buf is long enough */
2518
0
    if (nullptr != buf)
2519
0
        buflen = strlen(buf);
2520
0
    len = buflen + applen + strlen(delim) + 1;
2521
2522
    /* "pj_realloc", so to speak */
2523
0
    if (*buf_size < len) {
2524
0
        p = static_cast<char *>(calloc(2 * len, sizeof(char)));
2525
0
        if (nullptr == p) {
2526
0
            free(buf);
2527
0
            return nullptr;
2528
0
        }
2529
0
        *buf_size = 2 * len;
2530
0
        if (buf != nullptr)
2531
0
            strcpy(p, buf);
2532
0
        free(buf);
2533
0
        buf = p;
2534
0
    }
2535
0
    assert(buf);
2536
2537
    /* Only append a delimiter if something's already there */
2538
0
    if (0 != buflen)
2539
0
        strcat(buf, delim);
2540
0
    strcat(buf, app);
2541
0
    return buf;
2542
0
}
2543
2544
static const char *empty = {""};
2545
static char version[64] = {""};
2546
static PJ_INFO info = {0, 0, 0, nullptr, nullptr, nullptr, nullptr, 0};
2547
2548
/*****************************************************************************/
2549
0
PJ_INFO proj_info(void) {
2550
    /******************************************************************************
2551
        Basic info about the current instance of the PROJ.4 library.
2552
2553
        Returns PJ_INFO struct.
2554
    ******************************************************************************/
2555
0
    size_t buf_size = 0;
2556
0
    char *buf = nullptr;
2557
2558
0
    pj_acquire_lock();
2559
2560
0
    info.major = PROJ_VERSION_MAJOR;
2561
0
    info.minor = PROJ_VERSION_MINOR;
2562
0
    info.patch = PROJ_VERSION_PATCH;
2563
2564
    /* A normal version string is xx.yy.zz which is 8 characters
2565
    long and there is room for 64 bytes in the version string. */
2566
0
    snprintf(version, sizeof(version), "%d.%d.%d", info.major, info.minor,
2567
0
             info.patch);
2568
2569
0
    info.version = version;
2570
0
    info.release = pj_get_release();
2571
2572
    /* build search path string */
2573
0
    auto ctx = pj_get_default_ctx();
2574
0
    if (ctx->search_paths.empty()) {
2575
0
        const auto searchpaths = pj_get_default_searchpaths(ctx);
2576
0
        for (const auto &path : searchpaths) {
2577
0
            buf = path_append(buf, path.c_str(), &buf_size);
2578
0
        }
2579
0
    } else {
2580
0
        for (const auto &path : ctx->search_paths) {
2581
0
            buf = path_append(buf, path.c_str(), &buf_size);
2582
0
        }
2583
0
    }
2584
2585
0
    if (info.searchpath != empty)
2586
0
        free(const_cast<char *>(info.searchpath));
2587
0
    info.searchpath = buf ? buf : empty;
2588
2589
0
    info.paths = ctx->c_compat_paths;
2590
0
    info.path_count = static_cast<int>(ctx->search_paths.size());
2591
2592
0
    pj_release_lock();
2593
0
    return info;
2594
0
}
2595
2596
/*****************************************************************************/
2597
0
PJ_PROJ_INFO proj_pj_info(PJ *P) {
2598
    /******************************************************************************
2599
        Basic info about a particular instance of a projection object.
2600
2601
        Returns PJ_PROJ_INFO struct.
2602
    ******************************************************************************/
2603
0
    PJ_PROJ_INFO pjinfo;
2604
0
    char *def;
2605
2606
0
    memset(&pjinfo, 0, sizeof(PJ_PROJ_INFO));
2607
2608
0
    pjinfo.accuracy = -1.0;
2609
2610
0
    if (nullptr == P)
2611
0
        return pjinfo;
2612
2613
    /* coordinate operation description */
2614
0
    if (!P->alternativeCoordinateOperations.empty()) {
2615
0
        if (P->iCurCoordOp >= 0) {
2616
0
            P = P->alternativeCoordinateOperations[P->iCurCoordOp].pj;
2617
0
        } else {
2618
0
            PJ *candidateOp = nullptr;
2619
            // If there's just a single coordinate operation which is
2620
            // instanciable, use it.
2621
0
            for (const auto &op : P->alternativeCoordinateOperations) {
2622
0
                if (op.isInstantiable()) {
2623
0
                    if (candidateOp == nullptr) {
2624
0
                        candidateOp = op.pj;
2625
0
                    } else {
2626
0
                        candidateOp = nullptr;
2627
0
                        break;
2628
0
                    }
2629
0
                }
2630
0
            }
2631
0
            if (candidateOp) {
2632
0
                P = candidateOp;
2633
0
            } else {
2634
0
                pjinfo.id = "unknown";
2635
0
                pjinfo.description = "unavailable until proj_trans is called";
2636
0
                pjinfo.definition = "unavailable until proj_trans is called";
2637
0
                return pjinfo;
2638
0
            }
2639
0
        }
2640
0
    }
2641
2642
    /* projection id */
2643
0
    if (pj_param(P->ctx, P->params, "tproj").i)
2644
0
        pjinfo.id = pj_param(P->ctx, P->params, "sproj").s;
2645
2646
0
    pjinfo.description = P->descr;
2647
0
    if (P->iso_obj) {
2648
0
        auto identifiedObj =
2649
0
            dynamic_cast<NS_PROJ::common::IdentifiedObject *>(P->iso_obj.get());
2650
0
        if (identifiedObj) {
2651
0
            pjinfo.description = identifiedObj->nameStr().c_str();
2652
0
        }
2653
0
    }
2654
2655
    // accuracy
2656
0
    if (P->iso_obj) {
2657
0
        auto conv = dynamic_cast<const NS_PROJ::operation::Conversion *>(
2658
0
            P->iso_obj.get());
2659
0
        if (conv) {
2660
0
            pjinfo.accuracy = 0.0;
2661
0
        } else {
2662
0
            auto op =
2663
0
                dynamic_cast<const NS_PROJ::operation::CoordinateOperation *>(
2664
0
                    P->iso_obj.get());
2665
0
            if (op) {
2666
0
                const auto &accuracies = op->coordinateOperationAccuracies();
2667
0
                if (!accuracies.empty()) {
2668
0
                    try {
2669
0
                        pjinfo.accuracy = std::stod(accuracies[0]->value());
2670
0
                    } catch (const std::exception &) {
2671
0
                    }
2672
0
                }
2673
0
            }
2674
0
        }
2675
0
    }
2676
2677
    /* projection definition */
2678
0
    if (P->def_full)
2679
0
        def = P->def_full;
2680
0
    else
2681
0
        def = pj_get_def(P, 0); /* pj_get_def takes a non-const PJ pointer */
2682
0
    if (nullptr == def)
2683
0
        pjinfo.definition = empty;
2684
0
    else
2685
0
        pjinfo.definition = pj_shrink(def);
2686
    /* Make proj_destroy clean this up eventually */
2687
0
    P->def_full = def;
2688
2689
0
    pjinfo.has_inverse = pj_has_inverse(P);
2690
0
    return pjinfo;
2691
0
}
2692
2693
/*****************************************************************************/
2694
0
PJ_GRID_INFO proj_grid_info(const char *gridname) {
2695
    /******************************************************************************
2696
        Information about a named datum grid.
2697
2698
        Returns PJ_GRID_INFO struct.
2699
    ******************************************************************************/
2700
0
    PJ_GRID_INFO grinfo;
2701
2702
    /*PJ_CONTEXT *ctx = proj_context_create(); */
2703
0
    PJ_CONTEXT *ctx = pj_get_default_ctx();
2704
0
    memset(&grinfo, 0, sizeof(PJ_GRID_INFO));
2705
2706
0
    const auto fillGridInfo = [&grinfo, ctx,
2707
0
                               gridname](const NS_PROJ::Grid &grid,
2708
0
                                         const std::string &format) {
2709
0
        const auto &extent = grid.extentAndRes();
2710
2711
        /* name of grid */
2712
0
        strncpy(grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1);
2713
2714
        /* full path of grid */
2715
0
        if (!pj_find_file(ctx, gridname, grinfo.filename,
2716
0
                          sizeof(grinfo.filename) - 1)) {
2717
            // Can happen when using a remote grid
2718
0
            grinfo.filename[0] = 0;
2719
0
        }
2720
2721
        /* grid format */
2722
0
        strncpy(grinfo.format, format.c_str(), sizeof(grinfo.format) - 1);
2723
2724
        /* grid size */
2725
0
        grinfo.n_lon = grid.width();
2726
0
        grinfo.n_lat = grid.height();
2727
2728
        /* cell size */
2729
0
        grinfo.cs_lon = extent.resX;
2730
0
        grinfo.cs_lat = extent.resY;
2731
2732
        /* bounds of grid */
2733
0
        grinfo.lowerleft.lam = extent.west;
2734
0
        grinfo.lowerleft.phi = extent.south;
2735
0
        grinfo.upperright.lam = extent.east;
2736
0
        grinfo.upperright.phi = extent.north;
2737
0
    };
2738
2739
0
    {
2740
0
        const auto gridSet = NS_PROJ::VerticalShiftGridSet::open(ctx, gridname);
2741
0
        if (gridSet) {
2742
0
            const auto &grids = gridSet->grids();
2743
0
            if (!grids.empty()) {
2744
0
                const auto &grid = grids.front();
2745
0
                fillGridInfo(*grid, gridSet->format());
2746
0
                return grinfo;
2747
0
            }
2748
0
        }
2749
0
    }
2750
2751
0
    {
2752
0
        const auto gridSet =
2753
0
            NS_PROJ::HorizontalShiftGridSet::open(ctx, gridname);
2754
0
        if (gridSet) {
2755
0
            const auto &grids = gridSet->grids();
2756
0
            if (!grids.empty()) {
2757
0
                const auto &grid = grids.front();
2758
0
                fillGridInfo(*grid, gridSet->format());
2759
0
                return grinfo;
2760
0
            }
2761
0
        }
2762
0
    }
2763
0
    strcpy(grinfo.format, "missing");
2764
0
    return grinfo;
2765
0
}
2766
2767
/*****************************************************************************/
2768
0
PJ_INIT_INFO proj_init_info(const char *initname) {
2769
    /******************************************************************************
2770
        Information about a named init file.
2771
2772
        Maximum length of initname is 64.
2773
2774
        Returns PJ_INIT_INFO struct.
2775
2776
        If the init file is not found all members of the return struct are set
2777
        to the empty string.
2778
2779
        If the init file is found, but the metadata is missing, the value is
2780
        set to "Unknown".
2781
    ******************************************************************************/
2782
0
    int file_found;
2783
0
    char param[80], key[74];
2784
0
    paralist *start, *next;
2785
0
    PJ_INIT_INFO ininfo;
2786
0
    PJ_CONTEXT *ctx = pj_get_default_ctx();
2787
2788
0
    memset(&ininfo, 0, sizeof(PJ_INIT_INFO));
2789
2790
0
    file_found =
2791
0
        pj_find_file(ctx, initname, ininfo.filename, sizeof(ininfo.filename));
2792
0
    if (!file_found || strlen(initname) > 64) {
2793
0
        if (strcmp(initname, "epsg") == 0 || strcmp(initname, "EPSG") == 0) {
2794
0
            const char *val;
2795
2796
0
            proj_context_errno_set(ctx, 0);
2797
2798
0
            strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1);
2799
0
            strcpy(ininfo.origin, "EPSG");
2800
0
            val = proj_context_get_database_metadata(ctx, "EPSG.VERSION");
2801
0
            if (val) {
2802
0
                strncpy(ininfo.version, val, sizeof(ininfo.version) - 1);
2803
0
            }
2804
0
            val = proj_context_get_database_metadata(ctx, "EPSG.DATE");
2805
0
            if (val) {
2806
0
                strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1);
2807
0
            }
2808
0
            return ininfo;
2809
0
        }
2810
2811
0
        if (strcmp(initname, "IGNF") == 0) {
2812
0
            const char *val;
2813
2814
0
            proj_context_errno_set(ctx, 0);
2815
2816
0
            strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1);
2817
0
            strcpy(ininfo.origin, "IGNF");
2818
0
            val = proj_context_get_database_metadata(ctx, "IGNF.VERSION");
2819
0
            if (val) {
2820
0
                strncpy(ininfo.version, val, sizeof(ininfo.version) - 1);
2821
0
            }
2822
0
            val = proj_context_get_database_metadata(ctx, "IGNF.DATE");
2823
0
            if (val) {
2824
0
                strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1);
2825
0
            }
2826
0
            return ininfo;
2827
0
        }
2828
2829
0
        return ininfo;
2830
0
    }
2831
2832
    /* The initial memset (0) makes strncpy safe here */
2833
0
    strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1);
2834
0
    strcpy(ininfo.origin, "Unknown");
2835
0
    strcpy(ininfo.version, "Unknown");
2836
0
    strcpy(ininfo.lastupdate, "Unknown");
2837
2838
0
    strncpy(key, initname, 64); /* make room for ":metadata\0" at the end */
2839
0
    key[64] = 0;
2840
0
    memcpy(key + strlen(key), ":metadata", 9 + 1);
2841
0
    strcpy(param, "+init=");
2842
    /* The +strlen(param) avoids a cppcheck false positive warning */
2843
0
    strncat(param + strlen(param), key, sizeof(param) - 1 - strlen(param));
2844
2845
0
    start = pj_mkparam(param);
2846
0
    pj_expand_init(ctx, start);
2847
2848
0
    if (pj_param(ctx, start, "tversion").i)
2849
0
        strncpy(ininfo.version, pj_param(ctx, start, "sversion").s,
2850
0
                sizeof(ininfo.version) - 1);
2851
2852
0
    if (pj_param(ctx, start, "torigin").i)
2853
0
        strncpy(ininfo.origin, pj_param(ctx, start, "sorigin").s,
2854
0
                sizeof(ininfo.origin) - 1);
2855
2856
0
    if (pj_param(ctx, start, "tlastupdate").i)
2857
0
        strncpy(ininfo.lastupdate, pj_param(ctx, start, "slastupdate").s,
2858
0
                sizeof(ininfo.lastupdate) - 1);
2859
2860
0
    for (; start; start = next) {
2861
0
        next = start->next;
2862
0
        free(start);
2863
0
    }
2864
2865
0
    return ininfo;
2866
0
}
2867
2868
/*****************************************************************************/
2869
0
PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) {
2870
    /******************************************************************************
2871
        Cartographic characteristics at point lp.
2872
2873
        Characteristics include meridian, parallel and areal scales, angular
2874
        distortion, meridian/parallel, meridian convergence and scale error.
2875
2876
        returns PJ_FACTORS. If unsuccessful, error number is set and the
2877
        struct returned contains NULL data.
2878
    ******************************************************************************/
2879
0
    PJ_FACTORS factors = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
2880
0
    struct FACTORS f;
2881
2882
0
    if (nullptr == P)
2883
0
        return factors;
2884
2885
0
    const auto type = proj_get_type(P);
2886
2887
0
    if (type == PJ_TYPE_COMPOUND_CRS) {
2888
0
        auto ctx = P->ctx;
2889
0
        auto horiz = proj_crs_get_sub_crs(ctx, P, 0);
2890
0
        if (horiz) {
2891
0
            auto ret = proj_factors(horiz, lp);
2892
0
            proj_destroy(horiz);
2893
0
            return ret;
2894
0
        }
2895
0
    }
2896
2897
0
    if (type == PJ_TYPE_PROJECTED_CRS) {
2898
        // If it is a projected CRS, then compute the factors on the conversion
2899
        // associated to it. We need to start from a temporary geographic CRS
2900
        // using the same datum as the one of the projected CRS, and with
2901
        // input coordinates being in longitude, latitude order in radian,
2902
        // to be consistent with the expectations of the lp input parameter.
2903
        // We also need to create a modified projected CRS with a normalized
2904
        // easting/northing axis order in metre, so the resulting operation is
2905
        // just a single step pipeline with no axisswap or unitconvert steps.
2906
2907
0
        auto ctx = P->ctx;
2908
0
        auto geodetic_crs = proj_get_source_crs(ctx, P);
2909
0
        assert(geodetic_crs);
2910
0
        auto pm = proj_get_prime_meridian(ctx, geodetic_crs);
2911
0
        double pm_longitude = 0;
2912
0
        proj_prime_meridian_get_parameters(ctx, pm, &pm_longitude, nullptr,
2913
0
                                           nullptr);
2914
0
        proj_destroy(pm);
2915
0
        PJ *geogCRSNormalized;
2916
0
        auto cs = proj_create_ellipsoidal_2D_cs(
2917
0
            ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Radian", 1.0);
2918
0
        if (pm_longitude != 0) {
2919
0
            auto ellipsoid = proj_get_ellipsoid(ctx, geodetic_crs);
2920
0
            double semi_major_metre = 0;
2921
0
            double inv_flattening = 0;
2922
0
            proj_ellipsoid_get_parameters(ctx, ellipsoid, &semi_major_metre,
2923
0
                                          nullptr, nullptr, &inv_flattening);
2924
0
            geogCRSNormalized = proj_create_geographic_crs(
2925
0
                ctx, "unname crs", "unnamed datum", proj_get_name(ellipsoid),
2926
0
                semi_major_metre, inv_flattening, "reference prime meridian", 0,
2927
0
                nullptr, 0, cs);
2928
0
            proj_destroy(ellipsoid);
2929
0
        } else {
2930
0
            auto datum = proj_crs_get_datum(ctx, geodetic_crs);
2931
0
            auto datum_ensemble =
2932
0
                proj_crs_get_datum_ensemble(ctx, geodetic_crs);
2933
0
            geogCRSNormalized = proj_create_geographic_crs_from_datum(
2934
0
                ctx, "unnamed crs", datum ? datum : datum_ensemble, cs);
2935
0
            proj_destroy(datum);
2936
0
            proj_destroy(datum_ensemble);
2937
0
        }
2938
0
        proj_destroy(cs);
2939
0
        auto conversion = proj_crs_get_coordoperation(ctx, P);
2940
0
        auto projCS = proj_create_cartesian_2D_cs(
2941
0
            ctx, PJ_CART2D_EASTING_NORTHING, "metre", 1.0);
2942
0
        auto projCRSNormalized = proj_create_projected_crs(
2943
0
            ctx, nullptr, geodetic_crs, conversion, projCS);
2944
0
        assert(projCRSNormalized);
2945
0
        proj_destroy(geodetic_crs);
2946
0
        proj_destroy(conversion);
2947
0
        proj_destroy(projCS);
2948
0
        auto newOp = proj_create_crs_to_crs_from_pj(
2949
0
            ctx, geogCRSNormalized, projCRSNormalized, nullptr, nullptr);
2950
0
        proj_destroy(geogCRSNormalized);
2951
0
        proj_destroy(projCRSNormalized);
2952
0
        assert(newOp);
2953
        // For debugging:
2954
        // printf("%s\n", proj_as_proj_string(ctx, newOp, PJ_PROJ_5, nullptr));
2955
0
        auto ret = proj_factors(newOp, lp);
2956
0
        proj_destroy(newOp);
2957
0
        return ret;
2958
0
    }
2959
2960
0
    if (type != PJ_TYPE_CONVERSION && type != PJ_TYPE_TRANSFORMATION &&
2961
0
        type != PJ_TYPE_CONCATENATED_OPERATION &&
2962
0
        type != PJ_TYPE_OTHER_COORDINATE_OPERATION) {
2963
0
        proj_log_error(P, _("Invalid type for P object"));
2964
0
        proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
2965
0
        return factors;
2966
0
    }
2967
2968
0
    if (pj_factors(lp.lp, P, 0.0, &f))
2969
0
        return factors;
2970
2971
0
    factors.meridional_scale = f.h;
2972
0
    factors.parallel_scale = f.k;
2973
0
    factors.areal_scale = f.s;
2974
2975
0
    factors.angular_distortion = f.omega;
2976
0
    factors.meridian_parallel_angle = f.thetap;
2977
0
    factors.meridian_convergence = f.conv;
2978
2979
0
    factors.tissot_semimajor = f.a;
2980
0
    factors.tissot_semiminor = f.b;
2981
2982
    /* Raw derivatives, for completeness's sake */
2983
0
    factors.dx_dlam = f.der.x_l;
2984
0
    factors.dx_dphi = f.der.x_p;
2985
0
    factors.dy_dlam = f.der.y_l;
2986
0
    factors.dy_dphi = f.der.y_p;
2987
2988
0
    return factors;
2989
0
}
2990
2991
// ---------------------------------------------------------------------------
2992
2993
//! @cond Doxygen_Suppress
2994
2995
// Returns true if the passed operation uses NADCON5 grids for NAD83 to
2996
// NAD83(HARN)
2997
0
static bool isSpecialCaseForNAD83_to_NAD83HARN(const std::string &opName) {
2998
0
    return opName.find("NAD83 to NAD83(HARN) (47)") != std::string::npos ||
2999
0
           opName.find("NAD83 to NAD83(HARN) (48)") != std::string::npos ||
3000
0
           opName.find("NAD83 to NAD83(HARN) (49)") != std::string::npos ||
3001
0
           opName.find("NAD83 to NAD83(HARN) (50)") != std::string::npos;
3002
0
}
3003
3004
// Returns true if the passed operation uses "GDA94 to WGS 84 (1)", which
3005
// is the null transformation
3006
0
static bool isSpecialCaseForGDA94_to_WGS84(const std::string &opName) {
3007
0
    return opName.find("GDA94 to WGS 84 (1)") != std::string::npos;
3008
0
}
3009
3010
// Returns true if the passed operation uses "GDA2020 to WGS 84 (2)", which
3011
// is the null transformation
3012
0
static bool isSpecialCaseForWGS84_to_GDA2020(const std::string &opName) {
3013
0
    return opName.find("GDA2020 to WGS 84 (2)") != std::string::npos;
3014
0
}
3015
3016
PJCoordOperation::PJCoordOperation(
3017
    int idxInOriginalListIn, double minxSrcIn, double minySrcIn,
3018
    double maxxSrcIn, double maxySrcIn, double minxDstIn, double minyDstIn,
3019
    double maxxDstIn, double maxyDstIn, PJ *pjIn, const std::string &nameIn,
3020
    double accuracyIn, double pseudoAreaIn, const char *areaNameIn,
3021
    const PJ *pjSrcGeocentricToLonLatIn, const PJ *pjDstGeocentricToLonLatIn)
3022
    : idxInOriginalList(idxInOriginalListIn), minxSrc(minxSrcIn),
3023
      minySrc(minySrcIn), maxxSrc(maxxSrcIn), maxySrc(maxySrcIn),
3024
      minxDst(minxDstIn), minyDst(minyDstIn), maxxDst(maxxDstIn),
3025
      maxyDst(maxyDstIn), pj(pjIn), name(nameIn), accuracy(accuracyIn),
3026
      pseudoArea(pseudoAreaIn), areaName(areaNameIn ? areaNameIn : ""),
3027
      isOffshore(areaName.find("- offshore") != std::string::npos),
3028
      isUnknownAreaName(areaName.empty() || areaName == "unknown"),
3029
      isPriorityOp(isSpecialCaseForNAD83_to_NAD83HARN(name) ||
3030
                   isSpecialCaseForGDA94_to_WGS84(name) ||
3031
                   isSpecialCaseForWGS84_to_GDA2020(name)),
3032
      pjSrcGeocentricToLonLat(pjSrcGeocentricToLonLatIn
3033
                                  ? proj_clone(pjSrcGeocentricToLonLatIn->ctx,
3034
                                               pjSrcGeocentricToLonLatIn)
3035
                                  : nullptr),
3036
      pjDstGeocentricToLonLat(pjDstGeocentricToLonLatIn
3037
                                  ? proj_clone(pjDstGeocentricToLonLatIn->ctx,
3038
                                               pjDstGeocentricToLonLatIn)
3039
0
                                  : nullptr) {
3040
0
    const auto IsLonLatOrLatLon = [](const PJ *crs, bool &isLonLatDegreeOut,
3041
0
                                     bool &isLatLonDegreeOut) {
3042
0
        const auto eType = proj_get_type(crs);
3043
0
        if (eType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
3044
0
            eType == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
3045
0
            const auto cs = proj_crs_get_coordinate_system(crs->ctx, crs);
3046
0
            const char *direction = "";
3047
0
            double conv_factor = 0;
3048
0
            constexpr double EPS = 1e-14;
3049
0
            if (proj_cs_get_axis_info(crs->ctx, cs, 0, nullptr, nullptr,
3050
0
                                      &direction, &conv_factor, nullptr,
3051
0
                                      nullptr, nullptr) &&
3052
0
                ci_equal(direction, "East")) {
3053
0
                isLonLatDegreeOut = fabs(conv_factor - M_PI / 180) < EPS;
3054
0
            } else if (proj_cs_get_axis_info(crs->ctx, cs, 1, nullptr, nullptr,
3055
0
                                             &direction, &conv_factor, nullptr,
3056
0
                                             nullptr, nullptr) &&
3057
0
                       ci_equal(direction, "East")) {
3058
0
                isLatLonDegreeOut = fabs(conv_factor - M_PI / 180) < EPS;
3059
0
            }
3060
0
            proj_destroy(cs);
3061
0
        }
3062
0
    };
3063
3064
0
    const auto source = proj_get_source_crs(pj->ctx, pj);
3065
0
    if (source) {
3066
0
        IsLonLatOrLatLon(source, srcIsLonLatDegree, srcIsLatLonDegree);
3067
0
        proj_destroy(source);
3068
0
    }
3069
3070
0
    const auto target = proj_get_target_crs(pj->ctx, pj);
3071
0
    if (target) {
3072
0
        IsLonLatOrLatLon(target, dstIsLonLatDegree, dstIsLatLonDegree);
3073
0
        proj_destroy(target);
3074
0
    }
3075
0
}
3076
3077
//! @endcond