Coverage Report

Created: 2026-04-01 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrct.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  The OGRSCoordinateTransformation class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2000, Frank Warmerdam
9
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "ogr_spatialref.h"
16
17
#include <algorithm>
18
#include <cassert>
19
#include <cmath>
20
#include <cstring>
21
#include <limits>
22
#include <list>
23
#include <mutex>
24
25
#include "cpl_conv.h"
26
#include "cpl_error.h"
27
#include "cpl_mem_cache.h"
28
#include "cpl_string.h"
29
#include "ogr_core.h"
30
#include "ogr_srs_api.h"
31
#include "ogr_proj_p.h"
32
#include "ogrct_priv.h"
33
34
#include "proj.h"
35
#include "proj_experimental.h"
36
37
#ifdef DEBUG_PERF
38
static double g_dfTotalTimeCRStoCRS = 0;
39
static double g_dfTotalTimeReprojection = 0;
40
41
/************************************************************************/
42
/*                          CPLGettimeofday()                           */
43
/************************************************************************/
44
45
#if defined(_WIN32) && !defined(__CYGWIN__)
46
#include <sys/timeb.h>
47
48
namespace
49
{
50
struct CPLTimeVal
51
{
52
    time_t tv_sec; /* seconds */
53
    long tv_usec;  /* and microseconds */
54
};
55
}  // namespace
56
57
static void CPLGettimeofday(struct CPLTimeVal *tp, void * /* timezonep*/)
58
{
59
    struct _timeb theTime;
60
61
    _ftime(&theTime);
62
    tp->tv_sec = static_cast<time_t>(theTime.time);
63
    tp->tv_usec = theTime.millitm * 1000;
64
}
65
#else
66
#include <sys/time.h> /* for gettimeofday() */
67
#define CPLTimeVal timeval
68
#define CPLGettimeofday(t, u) gettimeofday(t, u)
69
#endif
70
71
#endif  // DEBUG_PERF
72
73
// Cache of OGRProjCT objects
74
static std::mutex g_oCTCacheMutex;
75
class OGRProjCT;
76
typedef std::string CTCacheKey;
77
typedef std::unique_ptr<OGRProjCT> CTCacheValue;
78
static lru11::Cache<CTCacheKey, CTCacheValue> *g_poCTCache = nullptr;
79
80
/************************************************************************/
81
/*             OGRCoordinateTransformationOptions::Private              */
82
/************************************************************************/
83
84
struct OGRCoordinateTransformationOptions::Private
85
{
86
    bool bHasAreaOfInterest = false;
87
    double dfWestLongitudeDeg = 0.0;
88
    double dfSouthLatitudeDeg = 0.0;
89
    double dfEastLongitudeDeg = 0.0;
90
    double dfNorthLatitudeDeg = 0.0;
91
92
    CPLString osCoordOperation{};
93
    bool bReverseCO = false;
94
95
    bool bAllowBallpark = true;
96
    double dfAccuracy = -1;  // no constraint
97
98
    bool bOnlyBest = false;
99
    bool bOnlyBestOptionSet = false;
100
101
    bool bHasSourceCenterLong = false;
102
    double dfSourceCenterLong = 0.0;
103
104
    bool bHasTargetCenterLong = false;
105
    double dfTargetCenterLong = 0.0;
106
107
    bool bCheckWithInvertProj = false;
108
109
    Private();
110
0
    Private(const Private &) = default;
111
    Private(Private &&) = default;
112
0
    Private &operator=(const Private &) = default;
113
    Private &operator=(Private &&) = default;
114
115
    std::string GetKey() const;
116
    void RefreshCheckWithInvertProj();
117
};
118
119
/************************************************************************/
120
/*                              Private()                               */
121
/************************************************************************/
122
123
OGRCoordinateTransformationOptions::Private::Private()
124
0
{
125
0
    RefreshCheckWithInvertProj();
126
0
}
127
128
/************************************************************************/
129
/*                               GetKey()                               */
130
/************************************************************************/
131
132
std::string OGRCoordinateTransformationOptions::Private::GetKey() const
133
0
{
134
0
    std::string ret;
135
0
    ret += std::to_string(static_cast<int>(bHasAreaOfInterest));
136
0
    ret += std::to_string(dfWestLongitudeDeg);
137
0
    ret += std::to_string(dfSouthLatitudeDeg);
138
0
    ret += std::to_string(dfEastLongitudeDeg);
139
0
    ret += std::to_string(dfNorthLatitudeDeg);
140
0
    ret += osCoordOperation;
141
0
    ret += std::to_string(static_cast<int>(bReverseCO));
142
0
    ret += std::to_string(static_cast<int>(bAllowBallpark));
143
0
    ret += std::to_string(dfAccuracy);
144
0
    ret += std::to_string(static_cast<int>(bOnlyBestOptionSet));
145
0
    ret += std::to_string(static_cast<int>(bOnlyBest));
146
0
    ret += std::to_string(static_cast<int>(bHasSourceCenterLong));
147
0
    ret += std::to_string(dfSourceCenterLong);
148
0
    ret += std::to_string(static_cast<int>(bHasTargetCenterLong));
149
0
    ret += std::to_string(dfTargetCenterLong);
150
0
    ret += std::to_string(static_cast<int>(bCheckWithInvertProj));
151
0
    return ret;
152
0
}
153
154
/************************************************************************/
155
/*                     RefreshCheckWithInvertProj()                     */
156
/************************************************************************/
157
158
void OGRCoordinateTransformationOptions::Private::RefreshCheckWithInvertProj()
159
0
{
160
0
    bCheckWithInvertProj =
161
0
        CPLTestBool(CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", "NO"));
162
0
}
163
164
/************************************************************************/
165
/*                    GetAsAProjRecognizableString()                    */
166
/************************************************************************/
167
168
static char *GetAsAProjRecognizableString(const OGRSpatialReference *poSRS)
169
0
{
170
0
    CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
171
    // If there's a PROJ4 EXTENSION node in WKT1, then use
172
    // it. For example when dealing with "+proj=longlat +lon_wrap=180"
173
0
    char *pszText = nullptr;
174
0
    if (poSRS->GetExtension(nullptr, "PROJ4", nullptr))
175
0
    {
176
0
        poSRS->exportToProj4(&pszText);
177
0
        if (strstr(pszText, " +type=crs") == nullptr)
178
0
        {
179
0
            auto tmpText = std::string(pszText) + " +type=crs";
180
0
            CPLFree(pszText);
181
0
            pszText = CPLStrdup(tmpText.c_str());
182
0
        }
183
0
    }
184
0
    else if (poSRS->IsEmpty())
185
0
    {
186
0
        pszText = CPLStrdup("");
187
0
    }
188
0
    else
189
0
    {
190
        // We export to PROJJSON rather than WKT2:2019 because PROJJSON
191
        // is a bit more verbose, which helps in situations like
192
        // https://github.com/OSGeo/gdal/issues/9732 /
193
        // https://github.com/OSGeo/PROJ/pull/4124 where we want to export
194
        // a DerivedProjectedCRS whose base ProjectedCRS has non-metre axis.
195
0
        poSRS->exportToPROJJSON(&pszText, nullptr);
196
0
    }
197
198
0
    return pszText;
199
0
}
200
201
/************************************************************************/
202
/*                       GetTextRepresentation()                        */
203
/************************************************************************/
204
205
static char *GetTextRepresentation(const OGRSpatialReference *poSRS)
206
0
{
207
0
    const auto CanUseAuthorityDef = [](const OGRSpatialReference *poSRS1,
208
0
                                       OGRSpatialReference *poSRSFromAuth,
209
0
                                       const char *pszAuth)
210
0
    {
211
0
        if (EQUAL(pszAuth, "EPSG") &&
212
0
            CPLTestBool(
213
0
                CPLGetConfigOption("OSR_CT_USE_DEFAULT_EPSG_TOWGS84", "NO")))
214
0
        {
215
            // We don't want by default to honour 'default' TOWGS84 terms that
216
            // come with the EPSG code because there might be a better
217
            // transformation from that Typical case if EPSG:31468 "DHDN /
218
            // 3-degree Gauss-Kruger zone 4" where the DHDN->TOWGS84
219
            // transformation can use the BETA2007.gsb grid instead of
220
            // TOWGS84[598.1,73.7,418.2,0.202,0.045,-2.455,6.7] But if the user
221
            // really wants it, it can set the OSR_CT_USE_DEFAULT_EPSG_TOWGS84
222
            // configuration option to YES
223
0
            double adfTOWGS84_1[7];
224
0
            double adfTOWGS84_2[7];
225
226
0
            poSRSFromAuth->AddGuessedTOWGS84();
227
228
0
            if (poSRS1->GetTOWGS84(adfTOWGS84_1) == OGRERR_NONE &&
229
0
                poSRSFromAuth->GetTOWGS84(adfTOWGS84_2) == OGRERR_NONE &&
230
0
                memcmp(adfTOWGS84_1, adfTOWGS84_2, sizeof(adfTOWGS84_1)) == 0)
231
0
            {
232
0
                return false;
233
0
            }
234
0
        }
235
0
        return true;
236
0
    };
237
238
0
    char *pszText = nullptr;
239
    // If we have a AUTH:CODE attached, use it to retrieve the full
240
    // definition in case a trip to WKT1 has lost the area of use.
241
    // unless OGR_CT_PREFER_OFFICIAL_SRS_DEF=NO (see
242
    // https://github.com/OSGeo/PROJ/issues/2955)
243
0
    const char *pszAuth = poSRS->GetAuthorityName(nullptr);
244
0
    const char *pszCode = poSRS->GetAuthorityCode(nullptr);
245
0
    if (pszAuth && pszCode &&
246
0
        CPLTestBool(
247
0
            CPLGetConfigOption("OGR_CT_PREFER_OFFICIAL_SRS_DEF", "YES")))
248
0
    {
249
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
250
251
0
        CPLString osAuthCode(pszAuth);
252
0
        osAuthCode += ':';
253
0
        osAuthCode += pszCode;
254
0
        OGRSpatialReference oTmpSRS;
255
0
        if (oTmpSRS.SetFromUserInput(osAuthCode) == OGRERR_NONE)
256
0
        {
257
0
            const char *pszAuthAfter = oTmpSRS.GetAuthorityName(nullptr);
258
0
            const char *pszCodeAfter = oTmpSRS.GetAuthorityCode(nullptr);
259
0
            if (pszAuthAfter && pszCodeAfter && EQUAL(pszAuthAfter, pszAuth) &&
260
0
                EQUAL(pszCodeAfter, pszCode))
261
0
            {
262
0
                oTmpSRS.SetDataAxisToSRSAxisMapping(
263
0
                    poSRS->GetDataAxisToSRSAxisMapping());
264
0
                const char *const apszOptionsIsSame[] = {"CRITERION=EQUIVALENT",
265
0
                                                         nullptr};
266
0
                if (oTmpSRS.IsSame(poSRS, apszOptionsIsSame))
267
0
                {
268
0
                    if (CanUseAuthorityDef(poSRS, &oTmpSRS, pszAuth))
269
0
                    {
270
0
                        pszText = CPLStrdup(osAuthCode);
271
0
                    }
272
0
                }
273
0
            }
274
0
        }
275
0
    }
276
0
    if (pszText == nullptr)
277
0
    {
278
0
        pszText = GetAsAProjRecognizableString(poSRS);
279
0
    }
280
0
    return pszText;
281
0
}
282
283
/************************************************************************/
284
/*                 OGRCoordinateTransformationOptions()                 */
285
/************************************************************************/
286
287
/** \brief Constructs a new OGRCoordinateTransformationOptions.
288
 *
289
 * @since GDAL 3.0
290
 */
291
OGRCoordinateTransformationOptions::OGRCoordinateTransformationOptions()
292
0
    : d(new Private())
293
0
{
294
0
}
295
296
/************************************************************************/
297
/*                 OGRCoordinateTransformationOptions()                 */
298
/************************************************************************/
299
300
/** \brief Copy constructor
301
 *
302
 * @since GDAL 3.1
303
 */
304
OGRCoordinateTransformationOptions::OGRCoordinateTransformationOptions(
305
    const OGRCoordinateTransformationOptions &other)
306
0
    : d(new Private(*(other.d)))
307
0
{
308
0
}
309
310
/************************************************************************/
311
/*                             operator =()                             */
312
/************************************************************************/
313
314
/** \brief Assignment operator
315
 *
316
 * @since GDAL 3.1
317
 */
318
OGRCoordinateTransformationOptions &
319
OGRCoordinateTransformationOptions::operator=(
320
    const OGRCoordinateTransformationOptions &other)
321
0
{
322
0
    if (this != &other)
323
0
    {
324
0
        *d = *(other.d);
325
0
    }
326
0
    return *this;
327
0
}
328
329
/************************************************************************/
330
/*                 OGRCoordinateTransformationOptions()                 */
331
/************************************************************************/
332
333
/** \brief Destroys a OGRCoordinateTransformationOptions.
334
 *
335
 * @since GDAL 3.0
336
 */
337
OGRCoordinateTransformationOptions::~OGRCoordinateTransformationOptions()
338
0
{
339
0
}
340
341
/************************************************************************/
342
/*               OCTNewCoordinateTransformationOptions()                */
343
/************************************************************************/
344
345
/** \brief Create coordinate transformation options.
346
 *
347
 * To be freed with OCTDestroyCoordinateTransformationOptions()
348
 *
349
 * @since GDAL 3.0
350
 */
351
OGRCoordinateTransformationOptionsH OCTNewCoordinateTransformationOptions(void)
352
0
{
353
0
    return new OGRCoordinateTransformationOptions();
354
0
}
355
356
/************************************************************************/
357
/*             OCTDestroyCoordinateTransformationOptions()              */
358
/************************************************************************/
359
360
/** \brief Destroy coordinate transformation options.
361
 *
362
 * @since GDAL 3.0
363
 */
364
void OCTDestroyCoordinateTransformationOptions(
365
    OGRCoordinateTransformationOptionsH hOptions)
366
0
{
367
0
    delete hOptions;
368
0
}
369
370
/************************************************************************/
371
/*                         SetAreaOfInterest()                          */
372
/************************************************************************/
373
374
/** \brief Sets an area of interest.
375
 *
376
 * The west longitude is generally lower than the east longitude, except for
377
 * areas of interest that go across the anti-meridian.
378
 *
379
 * @param dfWestLongitudeDeg West longitude (in degree). Must be in [-180,180]
380
 * @param dfSouthLatitudeDeg South latitude (in degree). Must be in [-90,90]
381
 * @param dfEastLongitudeDeg East longitude (in degree). Must be in [-180,180]
382
 * @param dfNorthLatitudeDeg North latitude (in degree). Must be in [-90,90]
383
 * @return true in case of success.
384
 *
385
 * @since GDAL 3.0
386
 */
387
bool OGRCoordinateTransformationOptions::SetAreaOfInterest(
388
    double dfWestLongitudeDeg, double dfSouthLatitudeDeg,
389
    double dfEastLongitudeDeg, double dfNorthLatitudeDeg)
390
0
{
391
0
    if (std::fabs(dfWestLongitudeDeg) > 180)
392
0
    {
393
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfWestLongitudeDeg");
394
0
        return false;
395
0
    }
396
0
    if (std::fabs(dfSouthLatitudeDeg) > 90)
397
0
    {
398
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfSouthLatitudeDeg");
399
0
        return false;
400
0
    }
401
0
    if (std::fabs(dfEastLongitudeDeg) > 180)
402
0
    {
403
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfEastLongitudeDeg");
404
0
        return false;
405
0
    }
406
0
    if (std::fabs(dfNorthLatitudeDeg) > 90)
407
0
    {
408
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfNorthLatitudeDeg");
409
0
        return false;
410
0
    }
411
0
    if (dfSouthLatitudeDeg > dfNorthLatitudeDeg)
412
0
    {
413
0
        CPLError(CE_Failure, CPLE_AppDefined,
414
0
                 "dfSouthLatitudeDeg should be lower than dfNorthLatitudeDeg");
415
0
        return false;
416
0
    }
417
0
    d->bHasAreaOfInterest = true;
418
0
    d->dfWestLongitudeDeg = dfWestLongitudeDeg;
419
0
    d->dfSouthLatitudeDeg = dfSouthLatitudeDeg;
420
0
    d->dfEastLongitudeDeg = dfEastLongitudeDeg;
421
0
    d->dfNorthLatitudeDeg = dfNorthLatitudeDeg;
422
0
    return true;
423
0
}
424
425
/************************************************************************/
426
/*        OCTCoordinateTransformationOptionsSetAreaOfInterest()         */
427
/************************************************************************/
428
429
/** \brief Sets an area of interest.
430
 *
431
 * See OGRCoordinateTransformationOptions::SetAreaOfInterest()
432
 *
433
 * @since GDAL 3.0
434
 */
435
int OCTCoordinateTransformationOptionsSetAreaOfInterest(
436
    OGRCoordinateTransformationOptionsH hOptions, double dfWestLongitudeDeg,
437
    double dfSouthLatitudeDeg, double dfEastLongitudeDeg,
438
    double dfNorthLatitudeDeg)
439
0
{
440
0
    return hOptions->SetAreaOfInterest(dfWestLongitudeDeg, dfSouthLatitudeDeg,
441
0
                                       dfEastLongitudeDeg, dfNorthLatitudeDeg);
442
0
}
443
444
/************************************************************************/
445
/*                       SetCoordinateOperation()                       */
446
/************************************************************************/
447
448
/** \brief Sets a coordinate operation.
449
 *
450
 * This is a user override to be used instead of the normally computed pipeline.
451
 *
452
 * The pipeline must take into account the axis order of the source and target
453
 * SRS.
454
 *
455
 * The pipeline may be provided as a PROJ string (single step operation or
456
 * multiple step string starting with +proj=pipeline), a WKT2 string describing
457
 * a CoordinateOperation, or a "urn:ogc:def:coordinateOperation:EPSG::XXXX" URN
458
 *
459
 * @param pszCO PROJ or WKT string describing a coordinate operation
460
 * @param bReverseCO Whether the PROJ or WKT string should be evaluated in the
461
 * reverse path
462
 * @return true in case of success.
463
 *
464
 * @since GDAL 3.0
465
 */
466
bool OGRCoordinateTransformationOptions::SetCoordinateOperation(
467
    const char *pszCO, bool bReverseCO)
468
0
{
469
0
    d->osCoordOperation = pszCO ? pszCO : "";
470
0
    d->bReverseCO = bReverseCO;
471
0
    return true;
472
0
}
473
474
/************************************************************************/
475
/*                        SetSourceCenterLong()                         */
476
/************************************************************************/
477
478
/*! @cond Doxygen_Suppress */
479
void OGRCoordinateTransformationOptions::SetSourceCenterLong(
480
    double dfCenterLong)
481
0
{
482
0
    d->dfSourceCenterLong = dfCenterLong;
483
0
    d->bHasSourceCenterLong = true;
484
0
}
485
486
/*! @endcond */
487
488
/************************************************************************/
489
/*                        SetTargetCenterLong()                         */
490
/************************************************************************/
491
492
/*! @cond Doxygen_Suppress */
493
void OGRCoordinateTransformationOptions::SetTargetCenterLong(
494
    double dfCenterLong)
495
0
{
496
0
    d->dfTargetCenterLong = dfCenterLong;
497
0
    d->bHasTargetCenterLong = true;
498
0
}
499
500
/*! @endcond */
501
502
/************************************************************************/
503
/*           OCTCoordinateTransformationOptionsSetOperation()           */
504
/************************************************************************/
505
506
/** \brief Sets a coordinate operation.
507
 *
508
 * See OGRCoordinateTransformationOptions::SetCoordinateTransformation()
509
 *
510
 * @since GDAL 3.0
511
 */
512
int OCTCoordinateTransformationOptionsSetOperation(
513
    OGRCoordinateTransformationOptionsH hOptions, const char *pszCO,
514
    int bReverseCO)
515
0
{
516
    // cppcheck-suppress knownConditionTrueFalse
517
0
    return hOptions->SetCoordinateOperation(pszCO, CPL_TO_BOOL(bReverseCO));
518
0
}
519
520
/************************************************************************/
521
/*                         SetDesiredAccuracy()                         */
522
/************************************************************************/
523
524
/** \brief Sets the desired accuracy for coordinate operations.
525
 *
526
 * Only coordinate operations that offer an accuracy of at least the one
527
 * specified will be considered.
528
 *
529
 * An accuracy of 0 is valid and means a coordinate operation made only of one
530
 * or several conversions (map projections, unit conversion, etc.) Operations
531
 * involving ballpark transformations have a unknown accuracy, and will be
532
 * filtered out by any dfAccuracy >= 0 value.
533
 *
534
 * If this option is specified with PROJ < 8, the OGR_CT_OP_SELECTION
535
 * configuration option will default to BEST_ACCURACY.
536
 *
537
 * @param dfAccuracy accuracy in meters (or a negative value to disable this
538
 * filter)
539
 *
540
 * @since GDAL 3.3
541
 */
542
bool OGRCoordinateTransformationOptions::SetDesiredAccuracy(double dfAccuracy)
543
0
{
544
0
    d->dfAccuracy = dfAccuracy;
545
0
    return true;
546
0
}
547
548
/************************************************************************/
549
/*        OCTCoordinateTransformationOptionsSetDesiredAccuracy()        */
550
/************************************************************************/
551
552
/** \brief Sets the desired accuracy for coordinate operations.
553
 *
554
 * See OGRCoordinateTransformationOptions::SetDesiredAccuracy()
555
 *
556
 * @since GDAL 3.3
557
 */
558
int OCTCoordinateTransformationOptionsSetDesiredAccuracy(
559
    OGRCoordinateTransformationOptionsH hOptions, double dfAccuracy)
560
0
{
561
    // cppcheck-suppress knownConditionTrueFalse
562
0
    return hOptions->SetDesiredAccuracy(dfAccuracy);
563
0
}
564
565
/************************************************************************/
566
/*                         SetBallparkAllowed()                         */
567
/************************************************************************/
568
569
/** \brief Sets whether ballpark transformations are allowed.
570
 *
571
 * By default, PROJ may generate "ballpark transformations" (see
572
 * https://proj.org/glossary.html) when precise datum transformations are
573
 * missing. For high accuracy use cases, such transformations might not be
574
 * allowed.
575
 *
576
 * If this option is specified with PROJ < 8, the OGR_CT_OP_SELECTION
577
 * configuration option will default to BEST_ACCURACY.
578
 *
579
 * @param bAllowBallpark false to disable the user of ballpark transformations
580
 *
581
 * @since GDAL 3.3
582
 */
583
bool OGRCoordinateTransformationOptions::SetBallparkAllowed(bool bAllowBallpark)
584
0
{
585
0
    d->bAllowBallpark = bAllowBallpark;
586
0
    return true;
587
0
}
588
589
/************************************************************************/
590
/*        OCTCoordinateTransformationOptionsSetBallparkAllowed()        */
591
/************************************************************************/
592
593
/** \brief Sets whether ballpark transformations are allowed.
594
 *
595
 * See OGRCoordinateTransformationOptions::SetDesiredAccuracy()
596
 *
597
 * @since GDAL 3.3 and PROJ 8
598
 */
599
int OCTCoordinateTransformationOptionsSetBallparkAllowed(
600
    OGRCoordinateTransformationOptionsH hOptions, int bAllowBallpark)
601
0
{
602
    // cppcheck-suppress knownConditionTrueFalse
603
0
    return hOptions->SetBallparkAllowed(CPL_TO_BOOL(bAllowBallpark));
604
0
}
605
606
/************************************************************************/
607
/*                            SetOnlyBest()                             */
608
/************************************************************************/
609
610
/** \brief Sets whether only the "best" operation should be used.
611
 *
612
 * By default (at least in the PROJ 9.x series), PROJ may use coordinate
613
 * operations that are not the "best" if resources (typically grids) needed
614
 * to use them are missing. It will then fallback to other coordinate operations
615
 * that have a lesser accuracy, for example using Helmert transformations,
616
 * or in the absence of such operations, to ones with potential very rough
617
 * accuracy, using "ballpark" transformations
618
 * (see https://proj.org/glossary.html).
619
 *
620
 * When calling this method with bOnlyBest = true, PROJ will only consider the
621
 * "best" operation, and error out (at Transform() time) if they cannot be
622
 * used.
623
 * This method may be used together with SetBallparkAllowed(false) to
624
 * only allow best operations that have a known accuracy.
625
 *
626
 * Note that this method has no effect on PROJ versions before 9.2.
627
 *
628
 * The default value for this option can be also set with the
629
 * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default"
630
 * setting of proj.ini. Calling SetOnlyBest() overrides such default value.
631
 *
632
 * @param bOnlyBest set to true to ask PROJ to use only the best operation(s)
633
 *
634
 * @since GDAL 3.8 and PROJ 9.2
635
 */
636
bool OGRCoordinateTransformationOptions::SetOnlyBest(bool bOnlyBest)
637
0
{
638
0
    d->bOnlyBest = bOnlyBest;
639
0
    d->bOnlyBestOptionSet = true;
640
0
    return true;
641
0
}
642
643
/************************************************************************/
644
/*           OCTCoordinateTransformationOptionsSetOnlyBest()            */
645
/************************************************************************/
646
647
/** \brief Sets whether only the "best" operation(s) should be used.
648
 *
649
 * See OGRCoordinateTransformationOptions::SetOnlyBest()
650
 *
651
 * @since GDAL 3.8 and PROJ 9.2
652
 */
653
int OCTCoordinateTransformationOptionsSetOnlyBest(
654
    OGRCoordinateTransformationOptionsH hOptions, bool bOnlyBest)
655
0
{
656
    // cppcheck-suppress knownConditionTrueFalse
657
0
    return hOptions->SetOnlyBest(bOnlyBest);
658
0
}
659
660
/************************************************************************/
661
/*                              OGRProjCT                               */
662
/************************************************************************/
663
664
//! @cond Doxygen_Suppress
665
class OGRProjCT final : public OGRCoordinateTransformation
666
{
667
    friend void
668
    OGRProjCTDifferentOperationsStart(OGRCoordinateTransformation *poCT);
669
    friend void
670
    OGRProjCTDifferentOperationsStop(OGRCoordinateTransformation *poCT);
671
    friend bool
672
    OGRProjCTDifferentOperationsUsed(OGRCoordinateTransformation *poCT);
673
674
    class PjPtr
675
    {
676
        PJ *m_pj = nullptr;
677
678
        void reset()
679
0
        {
680
0
            if (m_pj)
681
0
            {
682
0
                proj_assign_context(m_pj, OSRGetProjTLSContext());
683
0
                proj_destroy(m_pj);
684
0
            }
685
0
        }
686
687
      public:
688
0
        PjPtr() : m_pj(nullptr)
689
0
        {
690
0
        }
691
692
0
        explicit PjPtr(PJ *pjIn) : m_pj(pjIn)
693
0
        {
694
0
        }
695
696
        ~PjPtr()
697
0
        {
698
0
            reset();
699
0
        }
700
701
        PjPtr(const PjPtr &other)
702
0
            : m_pj((other.m_pj != nullptr)
703
0
                       ? (proj_clone(OSRGetProjTLSContext(), other.m_pj))
704
0
                       : (nullptr))
705
0
        {
706
0
        }
707
708
        PjPtr(PjPtr &&other) : m_pj(other.m_pj)
709
0
        {
710
0
            other.m_pj = nullptr;
711
0
        }
712
713
        PjPtr &operator=(const PjPtr &other)
714
0
        {
715
0
            if (this != &other)
716
0
            {
717
0
                reset();
718
0
                m_pj = (other.m_pj != nullptr)
719
0
                           ? (proj_clone(OSRGetProjTLSContext(), other.m_pj))
720
0
                           : (nullptr);
721
0
            }
722
0
            return *this;
723
0
        }
724
725
        PjPtr &operator=(PJ *pjIn)
726
0
        {
727
0
            if (m_pj != pjIn)
728
0
            {
729
0
                reset();
730
0
                m_pj = pjIn;
731
0
            }
732
0
            return *this;
733
0
        }
734
735
        operator PJ *()
736
0
        {
737
0
            return m_pj;
738
0
        }
739
740
        operator const PJ *() const
741
0
        {
742
0
            return m_pj;
743
0
        }
744
    };
745
746
    OGRSpatialReference *poSRSSource = nullptr;
747
    OGRAxisOrientation m_eSourceFirstAxisOrient = OAO_Other;
748
    bool bSourceLatLong = false;
749
    bool bSourceWrap = false;
750
    double dfSourceWrapLong = 0.0;
751
    bool bSourceIsDynamicCRS = false;
752
    double dfSourceCoordinateEpoch = 0.0;
753
    std::string m_osSrcSRS{};  // WKT, PROJ4 or AUTH:CODE
754
755
    OGRSpatialReference *poSRSTarget = nullptr;
756
    OGRAxisOrientation m_eTargetFirstAxisOrient = OAO_Other;
757
    bool bTargetLatLong = false;
758
    bool bTargetWrap = false;
759
    double dfTargetWrapLong = 0.0;
760
    bool bTargetIsDynamicCRS = false;
761
    double dfTargetCoordinateEpoch = 0.0;
762
    std::string m_osTargetSRS{};  // WKT, PROJ4 or AUTH:CODE
763
764
    bool bWebMercatorToWGS84LongLat = false;
765
766
    size_t nErrorCount = 0;
767
768
    double dfThreshold = 0.0;
769
770
    PjPtr m_pj{};
771
    bool m_bReversePj = false;
772
773
    bool m_bEmitErrors = true;
774
775
    bool bNoTransform = false;
776
777
    enum class Strategy
778
    {
779
        PROJ,
780
        BEST_ACCURACY,
781
        FIRST_MATCHING
782
    };
783
    Strategy m_eStrategy = Strategy::PROJ;
784
785
    bool
786
    ListCoordinateOperations(const char *pszSrcSRS, const char *pszTargetSRS,
787
                             const OGRCoordinateTransformationOptions &options);
788
789
    struct Transformation
790
    {
791
        double minx = 0.0;
792
        double miny = 0.0;
793
        double maxx = 0.0;
794
        double maxy = 0.0;
795
        PjPtr pj{};
796
        CPLString osName{};
797
        CPLString osProjString{};
798
        double accuracy = 0.0;
799
800
        Transformation(double minxIn, double minyIn, double maxxIn,
801
                       double maxyIn, PJ *pjIn, const CPLString &osNameIn,
802
                       const CPLString &osProjStringIn, double accuracyIn)
803
0
            : minx(minxIn), miny(minyIn), maxx(maxxIn), maxy(maxyIn), pj(pjIn),
804
0
              osName(osNameIn), osProjString(osProjStringIn),
805
0
              accuracy(accuracyIn)
806
0
        {
807
0
        }
808
    };
809
810
    std::vector<Transformation> m_oTransformations{};
811
    int m_iCurTransformation = -1;
812
    OGRCoordinateTransformationOptions m_options{};
813
814
    bool m_recordDifferentOperationsUsed = false;
815
    std::string m_lastPjUsedPROJString{};
816
    bool m_differentOperationsUsed = false;
817
818
    void ComputeThreshold();
819
    void DetectWebMercatorToWGS84();
820
821
    OGRProjCT &operator=(const OGRProjCT &) = delete;
822
823
    static CTCacheKey
824
    MakeCacheKey(const OGRSpatialReference *poSRS1, const char *pszSrcSRS,
825
                 const OGRSpatialReference *poSRS2, const char *pszTargetSRS,
826
                 const OGRCoordinateTransformationOptions &options);
827
    bool ContainsNorthPole(const double xmin, const double ymin,
828
                           const double xmax, const double ymax,
829
                           bool lon_lat_order);
830
    bool ContainsSouthPole(const double xmin, const double ymin,
831
                           const double xmax, const double ymax,
832
                           bool lon_lat_order);
833
834
  public:
835
    OGRProjCT();
836
    ~OGRProjCT() override;
837
    OGRProjCT(const OGRProjCT &other);
838
839
    int Initialize(const OGRSpatialReference *poSource, const char *pszSrcSRS,
840
                   const OGRSpatialReference *poTarget,
841
                   const char *pszTargetSRS,
842
                   const OGRCoordinateTransformationOptions &options);
843
844
    const OGRSpatialReference *GetSourceCS() const override;
845
    const OGRSpatialReference *GetTargetCS() const override;
846
847
    int Transform(size_t nCount, double *x, double *y, double *z, double *t,
848
                  int *pabSuccess) override;
849
850
    int TransformWithErrorCodes(size_t nCount, double *x, double *y, double *z,
851
                                double *t, int *panErrorCodes) override;
852
853
    int TransformBounds(const double xmin, const double ymin, const double xmax,
854
                        const double ymax, double *out_xmin, double *out_ymin,
855
                        double *out_xmax, double *out_ymax,
856
                        const int densify_pts) override;
857
858
    bool GetEmitErrors() const override
859
0
    {
860
0
        return m_bEmitErrors;
861
0
    }
862
863
    void SetEmitErrors(bool bEmitErrors) override
864
0
    {
865
0
        m_bEmitErrors = bEmitErrors;
866
0
    }
867
868
    OGRCoordinateTransformation *Clone() const override;
869
870
    OGRCoordinateTransformation *GetInverse() const override;
871
872
    static void InsertIntoCache(OGRProjCT *poCT);
873
874
    static OGRProjCT *
875
    FindFromCache(const OGRSpatialReference *poSource, const char *pszSrcSRS,
876
                  const OGRSpatialReference *poTarget, const char *pszTargetSRS,
877
                  const OGRCoordinateTransformationOptions &options);
878
};
879
880
/************************************************************************/
881
/*                 OGRProjCTDifferentOperationsStart()                  */
882
/************************************************************************/
883
884
void OGRProjCTDifferentOperationsStart(OGRCoordinateTransformation *poCT)
885
0
{
886
0
    auto poOGRCT = dynamic_cast<OGRProjCT *>(poCT);
887
0
    if (poOGRCT)
888
0
    {
889
0
        poOGRCT->m_recordDifferentOperationsUsed = true;
890
0
        poOGRCT->m_differentOperationsUsed = false;
891
0
        poOGRCT->m_lastPjUsedPROJString.clear();
892
0
    }
893
0
    else
894
0
    {
895
0
        CPLError(CE_Failure, CPLE_AppDefined,
896
0
                 "OGRProjCTDifferentOperationsStart() called with a non "
897
0
                 "OGRProjCT instance");
898
0
    }
899
0
}
900
901
/************************************************************************/
902
/*                  OGRProjCTDifferentOperationsStop()                  */
903
/************************************************************************/
904
905
void OGRProjCTDifferentOperationsStop(OGRCoordinateTransformation *poCT)
906
0
{
907
0
    auto poOGRCT = dynamic_cast<OGRProjCT *>(poCT);
908
0
    if (poOGRCT)
909
0
    {
910
0
        poOGRCT->m_recordDifferentOperationsUsed = false;
911
0
    }
912
0
    else
913
0
    {
914
0
        CPLError(CE_Failure, CPLE_AppDefined,
915
0
                 "OGRProjCTDifferentOperationsStop() called with a non "
916
0
                 "OGRProjCT instance");
917
0
    }
918
0
}
919
920
/************************************************************************/
921
/*                  OGRProjCTDifferentOperationsUsed()                  */
922
/************************************************************************/
923
924
bool OGRProjCTDifferentOperationsUsed(OGRCoordinateTransformation *poCT)
925
0
{
926
0
    auto poOGRCT = dynamic_cast<OGRProjCT *>(poCT);
927
0
    if (poOGRCT)
928
0
    {
929
0
        return poOGRCT->m_differentOperationsUsed;
930
0
    }
931
0
    else
932
0
    {
933
0
        CPLError(CE_Failure, CPLE_AppDefined,
934
0
                 "OGRProjCTDifferentOperationsReset() called with a non "
935
0
                 "OGRProjCT instance");
936
0
        return false;
937
0
    }
938
0
}
939
940
//! @endcond
941
942
/************************************************************************/
943
/*                    ~OGRCoordinateTransformation()                    */
944
/************************************************************************/
945
946
0
OGRCoordinateTransformation::~OGRCoordinateTransformation() = default;
947
948
/************************************************************************/
949
/*                 OCTDestroyCoordinateTransformation()                 */
950
/************************************************************************/
951
952
/**
953
 * \brief OGRCoordinateTransformation destructor.
954
 *
955
 * This function is the same as OGRCoordinateTransformation::DestroyCT()
956
 *
957
 * @param hCT the object to delete
958
 */
959
960
void CPL_STDCALL
961
OCTDestroyCoordinateTransformation(OGRCoordinateTransformationH hCT)
962
963
0
{
964
0
    OGRCoordinateTransformation::DestroyCT(
965
0
        OGRCoordinateTransformation::FromHandle(hCT));
966
0
}
967
968
/************************************************************************/
969
/*                             DestroyCT()                              */
970
/************************************************************************/
971
972
/**
973
 * \brief OGRCoordinateTransformation destructor.
974
 *
975
 * This function is the same as
976
 * OGRCoordinateTransformation::~OGRCoordinateTransformation()
977
 * and OCTDestroyCoordinateTransformation()
978
 *
979
 * This static method will destroy a OGRCoordinateTransformation.  It is
980
 * equivalent to calling delete on the object, but it ensures that the
981
 * deallocation is properly executed within the OGR libraries heap on
982
 * platforms where this can matter (win32).
983
 *
984
 * @param poCT the object to delete
985
 *
986
 */
987
988
void OGRCoordinateTransformation::DestroyCT(OGRCoordinateTransformation *poCT)
989
0
{
990
0
    auto poProjCT = dynamic_cast<OGRProjCT *>(poCT);
991
0
    if (poProjCT)
992
0
    {
993
0
        OGRProjCT::InsertIntoCache(poProjCT);
994
0
    }
995
0
    else
996
0
    {
997
0
        delete poCT;
998
0
    }
999
0
}
1000
1001
/************************************************************************/
1002
/*                 OGRCreateCoordinateTransformation()                  */
1003
/************************************************************************/
1004
1005
/**
1006
 * Create transformation object.
1007
 *
1008
 * This is the same as the C function OCTNewCoordinateTransformation().
1009
 *
1010
 * Input spatial reference system objects are assigned
1011
 * by copy (calling clone() method) and no ownership transfer occurs.
1012
 *
1013
 * The delete operator, or OCTDestroyCoordinateTransformation() should
1014
 * be used to destroy transformation objects.
1015
 *
1016
 * This will honour the axis order advertized by the source and target SRS,
1017
 * as well as their "data axis to SRS axis mapping".
1018
 * To have a behavior similar to GDAL &lt; 3.0, the
1019
 * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES.
1020
 *
1021
 * @param poSource source spatial reference system.
1022
 * @param poTarget target spatial reference system.
1023
 * @return NULL on failure or a ready to use transformation object.
1024
 */
1025
1026
OGRCoordinateTransformation *
1027
OGRCreateCoordinateTransformation(const OGRSpatialReference *poSource,
1028
                                  const OGRSpatialReference *poTarget)
1029
1030
0
{
1031
0
    return OGRCreateCoordinateTransformation(
1032
0
        poSource, poTarget, OGRCoordinateTransformationOptions());
1033
0
}
1034
1035
/**
1036
 * Create transformation object.
1037
 *
1038
 * This is the same as the C function OCTNewCoordinateTransformationEx().
1039
 *
1040
 * Input spatial reference system objects are assigned
1041
 * by copy (calling clone() method) and no ownership transfer occurs.
1042
 *
1043
 * The delete operator, or OCTDestroyCoordinateTransformation() should
1044
 * be used to destroy transformation objects.
1045
 *
1046
 * This will honour the axis order advertized by the source and target SRS,
1047
 * as well as their "data axis to SRS axis mapping".
1048
 * To have a behavior similar to GDAL &lt; 3.0, the
1049
 * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES.
1050
 *
1051
 * The source SRS and target SRS should generally not be NULL. This is only
1052
 * allowed if a custom coordinate operation is set through the hOptions
1053
 * argument.
1054
 *
1055
 * Starting with GDAL 3.0.3, the OGR_CT_OP_SELECTION configuration option can be
1056
 * set to PROJ (default if PROJ >= 6.3), BEST_ACCURACY or FIRST_MATCHING to
1057
 * decide of the strategy to select the operation to use among candidates, whose
1058
 * area of use is compatible with the points to transform. It is only taken into
1059
 * account if no user defined coordinate transformation pipeline has been
1060
 * specified. <ul> <li>PROJ means the default behavior used by PROJ
1061
 * proj_create_crs_to_crs(). In particular the operation to use among several
1062
 * initial candidates is evaluated for each point to transform.</li>
1063
 * <li>BEST_ACCURACY means the operation whose accuracy is best. It should be
1064
 *     close to PROJ behavior, except that the operation to select is decided
1065
 *     for the average point of the coordinates passed in a single Transform()
1066
 * call. Note: if the OGRCoordinateTransformationOptions::SetDesiredAccuracy()
1067
 * or OGRCoordinateTransformationOptions::SetBallparkAllowed() methods are
1068
 * called with PROJ < 8, this strategy will be selected instead of PROJ.
1069
 * </li>
1070
 * <li>FIRST_MATCHING is the operation ordered first in the list of candidates:
1071
 *     it will not necessarily have the best accuracy, but generally a larger
1072
 * area of use.  It is evaluated for the average point of the coordinates passed
1073
 * in a single Transform() call. This was the default behavior for GDAL 3.0.0 to
1074
 *     3.0.2</li>
1075
 * </ul>
1076
 *
1077
 * By default, if the source or target SRS definition refers to an official
1078
 * CRS through a code, GDAL will use the official definition if the official
1079
 * definition and the source/target SRS definition are equivalent. Note that
1080
 * TOWGS84[] clauses are ignored when checking equivalence. Starting with
1081
 * GDAL 3.4.1, if you set the OGR_CT_PREFER_OFFICIAL_SRS_DEF configuration
1082
 * option to NO, the source or target SRS definition will be always used.
1083
 *
1084
 * If options contains a user defined coordinate transformation pipeline, it
1085
 * will be unconditionally used.
1086
 * If options has an area of interest defined, it will be used to research the
1087
 * best fitting coordinate transformation (which will be used for all coordinate
1088
 * transformations, even if they don't fall into the declared area of interest)
1089
 * If no options are set, then a list of candidate coordinate operations will be
1090
 * researched, and at each call to Transform(), the best of those candidate
1091
 * regarding the centroid of the coordinate set will be dynamically selected.
1092
 *
1093
 * @param poSource source spatial reference system.
1094
 * @param poTarget target spatial reference system.
1095
 * @param options Coordinate transformation options.
1096
 * @return NULL on failure or a ready to use transformation object.
1097
 * @since GDAL 3.0
1098
 */
1099
1100
OGRCoordinateTransformation *OGRCreateCoordinateTransformation(
1101
    const OGRSpatialReference *poSource, const OGRSpatialReference *poTarget,
1102
    const OGRCoordinateTransformationOptions &options)
1103
1104
0
{
1105
0
    char *pszSrcSRS = poSource ? GetTextRepresentation(poSource) : nullptr;
1106
0
    char *pszTargetSRS = poTarget ? GetTextRepresentation(poTarget) : nullptr;
1107
    // Try to find if we have a match in the case
1108
0
    OGRProjCT *poCT = OGRProjCT::FindFromCache(poSource, pszSrcSRS, poTarget,
1109
0
                                               pszTargetSRS, options);
1110
0
    if (poCT == nullptr)
1111
0
    {
1112
0
        poCT = new OGRProjCT();
1113
0
        if (!poCT->Initialize(poSource, pszSrcSRS, poTarget, pszTargetSRS,
1114
0
                              options))
1115
0
        {
1116
0
            delete poCT;
1117
0
            poCT = nullptr;
1118
0
        }
1119
0
    }
1120
0
    CPLFree(pszSrcSRS);
1121
0
    CPLFree(pszTargetSRS);
1122
1123
0
    return poCT;
1124
0
}
1125
1126
/************************************************************************/
1127
/*                   OCTNewCoordinateTransformation()                   */
1128
/************************************************************************/
1129
1130
/**
1131
 * Create transformation object.
1132
 *
1133
 * This is the same as the C++ function OGRCreateCoordinateTransformation(const
1134
 * OGRSpatialReference *, const OGRSpatialReference *)
1135
 *
1136
 * Input spatial reference system objects are assigned
1137
 * by copy (calling clone() method) and no ownership transfer occurs.
1138
 *
1139
 * OCTDestroyCoordinateTransformation() should
1140
 * be used to destroy transformation objects.
1141
 *
1142
 * This will honour the axis order advertized by the source and target SRS,
1143
 * as well as their "data axis to SRS axis mapping".
1144
 * To have a behavior similar to GDAL &lt; 3.0, the
1145
 * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES.
1146
 *
1147
 * @param hSourceSRS source spatial reference system.
1148
 * @param hTargetSRS target spatial reference system.
1149
 * @return NULL on failure or a ready to use transformation object.
1150
 */
1151
1152
OGRCoordinateTransformationH CPL_STDCALL OCTNewCoordinateTransformation(
1153
    OGRSpatialReferenceH hSourceSRS, OGRSpatialReferenceH hTargetSRS)
1154
1155
0
{
1156
0
    return reinterpret_cast<OGRCoordinateTransformationH>(
1157
0
        OGRCreateCoordinateTransformation(
1158
0
            reinterpret_cast<OGRSpatialReference *>(hSourceSRS),
1159
0
            reinterpret_cast<OGRSpatialReference *>(hTargetSRS)));
1160
0
}
1161
1162
/************************************************************************/
1163
/*                  OCTNewCoordinateTransformationEx()                  */
1164
/************************************************************************/
1165
1166
/**
1167
 * Create transformation object.
1168
 *
1169
 * This is the same as the C++ function OGRCreateCoordinateTransformation(const
1170
 * OGRSpatialReference *, const OGRSpatialReference *, const
1171
 * OGRCoordinateTransformationOptions& )
1172
 *
1173
 * Input spatial reference system objects are assigned
1174
 * by copy (calling clone() method) and no ownership transfer occurs.
1175
 *
1176
 * OCTDestroyCoordinateTransformation() should
1177
 * be used to destroy transformation objects.
1178
 *
1179
 * The source SRS and target SRS should generally not be NULL. This is only
1180
 * allowed if a custom coordinate operation is set through the hOptions
1181
 * argument.
1182
 *
1183
 * This will honour the axis order advertized by the source and target SRS,
1184
 * as well as their "data axis to SRS axis mapping".
1185
 * To have a behavior similar to GDAL &lt; 3.0, the
1186
 * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES.
1187
 *
1188
 * If options contains a user defined coordinate transformation pipeline, it
1189
 * will be unconditionally used.
1190
 * If options has an area of interest defined, it will be used to research the
1191
 * best fitting coordinate transformation (which will be used for all coordinate
1192
 * transformations, even if they don't fall into the declared area of interest)
1193
 * If no options are set, then a list of candidate coordinate operations will be
1194
 * researched, and at each call to Transform(), the best of those candidate
1195
 * regarding the centroid of the coordinate set will be dynamically selected.
1196
 *
1197
 * @param hSourceSRS source spatial reference system.
1198
 * @param hTargetSRS target spatial reference system.
1199
 * @param hOptions Coordinate transformation options.
1200
 * @return NULL on failure or a ready to use transformation object.
1201
 * @since GDAL 3.0
1202
 */
1203
1204
OGRCoordinateTransformationH
1205
OCTNewCoordinateTransformationEx(OGRSpatialReferenceH hSourceSRS,
1206
                                 OGRSpatialReferenceH hTargetSRS,
1207
                                 OGRCoordinateTransformationOptionsH hOptions)
1208
1209
0
{
1210
0
    return reinterpret_cast<OGRCoordinateTransformationH>(
1211
0
        OGRCreateCoordinateTransformation(
1212
0
            reinterpret_cast<OGRSpatialReference *>(hSourceSRS),
1213
0
            reinterpret_cast<OGRSpatialReference *>(hTargetSRS),
1214
0
            hOptions ? *hOptions : OGRCoordinateTransformationOptions()));
1215
0
}
1216
1217
/************************************************************************/
1218
/*                              OCTClone()                              */
1219
/************************************************************************/
1220
1221
/**
1222
 * Clone transformation object.
1223
 *
1224
 * This is the same as the C++ function OGRCreateCoordinateTransformation::Clone
1225
 *
1226
 * @return handle to transformation's clone or NULL on error,
1227
 *         must be freed with OCTDestroyCoordinateTransformation
1228
 *
1229
 * @since GDAL 3.4
1230
 */
1231
1232
OGRCoordinateTransformationH OCTClone(OGRCoordinateTransformationH hTransform)
1233
1234
0
{
1235
0
    VALIDATE_POINTER1(hTransform, "OCTClone", nullptr);
1236
0
    return OGRCoordinateTransformation::ToHandle(
1237
0
        OGRCoordinateTransformation::FromHandle(hTransform)->Clone());
1238
0
}
1239
1240
/************************************************************************/
1241
/*                           OCTGetSourceCS()                           */
1242
/************************************************************************/
1243
1244
/**
1245
 * Transformation's source coordinate system reference.
1246
 *
1247
 * This is the same as the C++ function
1248
 * OGRCreateCoordinateTransformation::GetSourceCS
1249
 *
1250
 * @return handle to transformation's source coordinate system or NULL if not
1251
 * present.
1252
 *
1253
 * The ownership of the returned SRS belongs to the transformation object, and
1254
 * the returned SRS should not be modified.
1255
 *
1256
 * @since GDAL 3.4
1257
 */
1258
1259
OGRSpatialReferenceH OCTGetSourceCS(OGRCoordinateTransformationH hTransform)
1260
1261
0
{
1262
0
    VALIDATE_POINTER1(hTransform, "OCTGetSourceCS", nullptr);
1263
0
    return OGRSpatialReference::ToHandle(const_cast<OGRSpatialReference *>(
1264
0
        OGRCoordinateTransformation::FromHandle(hTransform)->GetSourceCS()));
1265
0
}
1266
1267
/************************************************************************/
1268
/*                           OCTGetTargetCS()                           */
1269
/************************************************************************/
1270
1271
/**
1272
 * Transformation's target coordinate system reference.
1273
 *
1274
 * This is the same as the C++ function
1275
 * OGRCreateCoordinateTransformation::GetTargetCS
1276
 *
1277
 * @return handle to transformation's target coordinate system or NULL if not
1278
 * present.
1279
 *
1280
 * The ownership of the returned SRS belongs to the transformation object, and
1281
 * the returned SRS should not be modified.
1282
 *
1283
 * @since GDAL 3.4
1284
 */
1285
1286
OGRSpatialReferenceH OCTGetTargetCS(OGRCoordinateTransformationH hTransform)
1287
1288
0
{
1289
0
    VALIDATE_POINTER1(hTransform, "OCTGetTargetCS", nullptr);
1290
0
    return OGRSpatialReference::ToHandle(const_cast<OGRSpatialReference *>(
1291
0
        OGRCoordinateTransformation::FromHandle(hTransform)->GetTargetCS()));
1292
0
}
1293
1294
/************************************************************************/
1295
/*                           OCTGetInverse()                            */
1296
/************************************************************************/
1297
1298
/**
1299
 * Inverse transformation object.
1300
 *
1301
 * This is the same as the C++ function
1302
 * OGRCreateCoordinateTransformation::GetInverse
1303
 *
1304
 * @return handle to inverse transformation or NULL on error,
1305
 *         must be freed with OCTDestroyCoordinateTransformation
1306
 *
1307
 * @since GDAL 3.4
1308
 */
1309
1310
OGRCoordinateTransformationH CPL_DLL
1311
OCTGetInverse(OGRCoordinateTransformationH hTransform)
1312
1313
0
{
1314
0
    VALIDATE_POINTER1(hTransform, "OCTGetInverse", nullptr);
1315
0
    return OGRCoordinateTransformation::ToHandle(
1316
0
        OGRCoordinateTransformation::FromHandle(hTransform)->GetInverse());
1317
0
}
1318
1319
/************************************************************************/
1320
/*                             OGRProjCT()                              */
1321
/************************************************************************/
1322
1323
//! @cond Doxygen_Suppress
1324
OGRProjCT::OGRProjCT()
1325
0
{
1326
0
}
1327
1328
/************************************************************************/
1329
/*                  OGRProjCT(const OGRProjCT& other)                   */
1330
/************************************************************************/
1331
1332
OGRProjCT::OGRProjCT(const OGRProjCT &other)
1333
0
    : poSRSSource((other.poSRSSource != nullptr) ? (other.poSRSSource->Clone())
1334
0
                                                 : (nullptr)),
1335
0
      m_eSourceFirstAxisOrient(other.m_eSourceFirstAxisOrient),
1336
0
      bSourceLatLong(other.bSourceLatLong), bSourceWrap(other.bSourceWrap),
1337
0
      dfSourceWrapLong(other.dfSourceWrapLong),
1338
0
      bSourceIsDynamicCRS(other.bSourceIsDynamicCRS),
1339
0
      dfSourceCoordinateEpoch(other.dfSourceCoordinateEpoch),
1340
0
      m_osSrcSRS(other.m_osSrcSRS),
1341
0
      poSRSTarget((other.poSRSTarget != nullptr) ? (other.poSRSTarget->Clone())
1342
0
                                                 : (nullptr)),
1343
0
      m_eTargetFirstAxisOrient(other.m_eTargetFirstAxisOrient),
1344
0
      bTargetLatLong(other.bTargetLatLong), bTargetWrap(other.bTargetWrap),
1345
0
      dfTargetWrapLong(other.dfTargetWrapLong),
1346
0
      bTargetIsDynamicCRS(other.bTargetIsDynamicCRS),
1347
0
      dfTargetCoordinateEpoch(other.dfTargetCoordinateEpoch),
1348
0
      m_osTargetSRS(other.m_osTargetSRS),
1349
0
      bWebMercatorToWGS84LongLat(other.bWebMercatorToWGS84LongLat),
1350
0
      nErrorCount(other.nErrorCount), dfThreshold(other.dfThreshold),
1351
0
      m_pj(other.m_pj), m_bReversePj(other.m_bReversePj),
1352
0
      m_bEmitErrors(other.m_bEmitErrors), bNoTransform(other.bNoTransform),
1353
0
      m_eStrategy(other.m_eStrategy),
1354
0
      m_oTransformations(other.m_oTransformations),
1355
0
      m_iCurTransformation(other.m_iCurTransformation),
1356
0
      m_options(other.m_options), m_recordDifferentOperationsUsed(false),
1357
0
      m_lastPjUsedPROJString(std::string()), m_differentOperationsUsed(false)
1358
0
{
1359
0
}
1360
1361
/************************************************************************/
1362
/*                             ~OGRProjCT()                             */
1363
/************************************************************************/
1364
1365
OGRProjCT::~OGRProjCT()
1366
1367
0
{
1368
0
    if (poSRSSource != nullptr)
1369
0
    {
1370
0
        poSRSSource->Release();
1371
0
    }
1372
1373
0
    if (poSRSTarget != nullptr)
1374
0
    {
1375
0
        poSRSTarget->Release();
1376
0
    }
1377
0
}
1378
1379
/************************************************************************/
1380
/*                          ComputeThreshold()                          */
1381
/************************************************************************/
1382
1383
void OGRProjCT::ComputeThreshold()
1384
0
{
1385
    // The threshold is experimental. Works well with the cases of ticket #2305.
1386
0
    if (bSourceLatLong)
1387
0
    {
1388
        // coverity[tainted_data]
1389
0
        dfThreshold = CPLAtof(CPLGetConfigOption("THRESHOLD", ".1"));
1390
0
    }
1391
0
    else
1392
0
    {
1393
        // 1 works well for most projections, except for +proj=aeqd that
1394
        // requires a tolerance of 10000.
1395
        // coverity[tainted_data]
1396
0
        dfThreshold = CPLAtof(CPLGetConfigOption("THRESHOLD", "10000"));
1397
0
    }
1398
0
}
1399
1400
/************************************************************************/
1401
/*                      DetectWebMercatorToWGS84()                      */
1402
/************************************************************************/
1403
1404
void OGRProjCT::DetectWebMercatorToWGS84()
1405
0
{
1406
    // Detect webmercator to WGS84
1407
0
    if (m_options.d->osCoordOperation.empty() && poSRSSource && poSRSTarget &&
1408
0
        poSRSSource->IsProjected() && poSRSTarget->IsGeographic() &&
1409
0
        ((m_eTargetFirstAxisOrient == OAO_North &&
1410
0
          poSRSTarget->GetDataAxisToSRSAxisMapping() ==
1411
0
              std::vector<int>{2, 1}) ||
1412
0
         (m_eTargetFirstAxisOrient == OAO_East &&
1413
0
          poSRSTarget->GetDataAxisToSRSAxisMapping() ==
1414
0
              std::vector<int>{1, 2})))
1415
0
    {
1416
        // Examine SRS ID before going to Proj4 string for faster execution
1417
        // This assumes that the SRS definition is "not lying", that is, it
1418
        // is equivalent to the resolution of the official EPSG code.
1419
0
        const char *pszSourceAuth = poSRSSource->GetAuthorityName(nullptr);
1420
0
        const char *pszSourceCode = poSRSSource->GetAuthorityCode(nullptr);
1421
0
        const char *pszTargetAuth = poSRSTarget->GetAuthorityName(nullptr);
1422
0
        const char *pszTargetCode = poSRSTarget->GetAuthorityCode(nullptr);
1423
0
        if (pszSourceAuth && pszSourceCode && pszTargetAuth && pszTargetCode &&
1424
0
            EQUAL(pszSourceAuth, "EPSG") && EQUAL(pszTargetAuth, "EPSG"))
1425
0
        {
1426
0
            bWebMercatorToWGS84LongLat =
1427
0
                (EQUAL(pszSourceCode, "3857") ||
1428
0
                 EQUAL(pszSourceCode, "3785") ||     // deprecated
1429
0
                 EQUAL(pszSourceCode, "900913")) &&  // deprecated
1430
0
                EQUAL(pszTargetCode, "4326");
1431
0
        }
1432
0
        else
1433
0
        {
1434
0
            CPLPushErrorHandler(CPLQuietErrorHandler);
1435
0
            char *pszSrcProj4Defn = nullptr;
1436
0
            poSRSSource->exportToProj4(&pszSrcProj4Defn);
1437
1438
0
            char *pszDstProj4Defn = nullptr;
1439
0
            poSRSTarget->exportToProj4(&pszDstProj4Defn);
1440
0
            CPLPopErrorHandler();
1441
1442
0
            if (pszSrcProj4Defn && pszDstProj4Defn)
1443
0
            {
1444
0
                if (pszSrcProj4Defn[0] != '\0' &&
1445
0
                    pszSrcProj4Defn[strlen(pszSrcProj4Defn) - 1] == ' ')
1446
0
                    pszSrcProj4Defn[strlen(pszSrcProj4Defn) - 1] = 0;
1447
0
                if (pszDstProj4Defn[0] != '\0' &&
1448
0
                    pszDstProj4Defn[strlen(pszDstProj4Defn) - 1] == ' ')
1449
0
                    pszDstProj4Defn[strlen(pszDstProj4Defn) - 1] = 0;
1450
0
                char *pszNeedle = strstr(pszSrcProj4Defn, "  ");
1451
0
                if (pszNeedle)
1452
0
                    memmove(pszNeedle, pszNeedle + 1,
1453
0
                            strlen(pszNeedle + 1) + 1);
1454
0
                pszNeedle = strstr(pszDstProj4Defn, "  ");
1455
0
                if (pszNeedle)
1456
0
                    memmove(pszNeedle, pszNeedle + 1,
1457
0
                            strlen(pszNeedle + 1) + 1);
1458
1459
0
                if ((strstr(pszDstProj4Defn, "+datum=WGS84") != nullptr ||
1460
0
                     strstr(pszDstProj4Defn,
1461
0
                            "+ellps=WGS84 +towgs84=0,0,0,0,0,0,0 ") !=
1462
0
                         nullptr) &&
1463
0
                    strstr(pszSrcProj4Defn, "+nadgrids=@null ") != nullptr &&
1464
0
                    strstr(pszSrcProj4Defn, "+towgs84") == nullptr)
1465
0
                {
1466
0
                    char *pszDst =
1467
0
                        strstr(pszDstProj4Defn, "+towgs84=0,0,0,0,0,0,0 ");
1468
0
                    if (pszDst != nullptr)
1469
0
                    {
1470
0
                        char *pszSrc =
1471
0
                            pszDst + strlen("+towgs84=0,0,0,0,0,0,0 ");
1472
0
                        memmove(pszDst, pszSrc, strlen(pszSrc) + 1);
1473
0
                    }
1474
0
                    else
1475
0
                    {
1476
0
                        memcpy(strstr(pszDstProj4Defn, "+datum=WGS84"),
1477
0
                               "+ellps", 6);
1478
0
                    }
1479
1480
0
                    pszDst = strstr(pszSrcProj4Defn, "+nadgrids=@null ");
1481
0
                    char *pszSrc = pszDst + strlen("+nadgrids=@null ");
1482
0
                    memmove(pszDst, pszSrc, strlen(pszSrc) + 1);
1483
1484
0
                    pszDst = strstr(pszSrcProj4Defn, "+wktext ");
1485
0
                    if (pszDst)
1486
0
                    {
1487
0
                        pszSrc = pszDst + strlen("+wktext ");
1488
0
                        memmove(pszDst, pszSrc, strlen(pszSrc) + 1);
1489
0
                    }
1490
0
                    bWebMercatorToWGS84LongLat =
1491
0
                        strcmp(pszDstProj4Defn,
1492
0
                               "+proj=longlat +ellps=WGS84 +no_defs") == 0 &&
1493
0
                        (strcmp(pszSrcProj4Defn,
1494
0
                                "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 "
1495
0
                                "+lon_0=0.0 "
1496
0
                                "+x_0=0.0 +y_0=0 +k=1.0 +units=m +no_defs") ==
1497
0
                             0 ||
1498
0
                         strcmp(pszSrcProj4Defn,
1499
0
                                "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 "
1500
0
                                "+lon_0=0 "
1501
0
                                "+x_0=0 +y_0=0 +k=1 +units=m +no_defs") == 0);
1502
0
                }
1503
0
            }
1504
1505
0
            CPLFree(pszSrcProj4Defn);
1506
0
            CPLFree(pszDstProj4Defn);
1507
0
        }
1508
1509
0
        if (bWebMercatorToWGS84LongLat)
1510
0
        {
1511
0
            CPLDebug("OGRCT", "Using WebMercator to WGS84 optimization");
1512
0
        }
1513
0
    }
1514
0
}
1515
1516
/************************************************************************/
1517
/*                             Initialize()                             */
1518
/************************************************************************/
1519
1520
int OGRProjCT::Initialize(const OGRSpatialReference *poSourceIn,
1521
                          const char *pszSrcSRS,
1522
                          const OGRSpatialReference *poTargetIn,
1523
                          const char *pszTargetSRS,
1524
                          const OGRCoordinateTransformationOptions &options)
1525
1526
0
{
1527
0
    m_options = options;
1528
1529
0
    if (poSourceIn == nullptr || poTargetIn == nullptr)
1530
0
    {
1531
0
        if (options.d->osCoordOperation.empty())
1532
0
        {
1533
0
            CPLError(CE_Failure, CPLE_AppDefined,
1534
0
                     "OGRProjCT::Initialize(): if source and/or target CRS "
1535
0
                     "are null, a coordinate operation must be specified");
1536
0
            return FALSE;
1537
0
        }
1538
0
    }
1539
1540
0
    if (poSourceIn)
1541
0
    {
1542
0
        poSRSSource = poSourceIn->Clone();
1543
0
        m_osSrcSRS = pszSrcSRS;
1544
0
    }
1545
0
    if (poTargetIn)
1546
0
    {
1547
0
        poSRSTarget = poTargetIn->Clone();
1548
0
        m_osTargetSRS = pszTargetSRS;
1549
0
    }
1550
1551
    // To easy quick&dirty compatibility with GDAL < 3.0
1552
0
    if (CPLTestBool(
1553
0
            CPLGetConfigOption("OGR_CT_FORCE_TRADITIONAL_GIS_ORDER", "NO")))
1554
0
    {
1555
0
        if (poSRSSource)
1556
0
            poSRSSource->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1557
0
        if (poSRSTarget)
1558
0
            poSRSTarget->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1559
0
    }
1560
1561
0
    if (poSRSSource)
1562
0
    {
1563
0
        bSourceLatLong = CPL_TO_BOOL(poSRSSource->IsGeographic());
1564
0
        bSourceIsDynamicCRS = poSRSSource->IsDynamic();
1565
0
        dfSourceCoordinateEpoch = poSRSSource->GetCoordinateEpoch();
1566
0
        if (!bSourceIsDynamicCRS && dfSourceCoordinateEpoch > 0)
1567
0
        {
1568
0
            bSourceIsDynamicCRS = poSRSSource->HasPointMotionOperation();
1569
0
        }
1570
0
        poSRSSource->GetAxis(nullptr, 0, &m_eSourceFirstAxisOrient);
1571
0
    }
1572
0
    if (poSRSTarget)
1573
0
    {
1574
0
        bTargetLatLong = CPL_TO_BOOL(poSRSTarget->IsGeographic());
1575
0
        bTargetIsDynamicCRS = poSRSTarget->IsDynamic();
1576
0
        dfTargetCoordinateEpoch = poSRSTarget->GetCoordinateEpoch();
1577
0
        if (!bTargetIsDynamicCRS && dfTargetCoordinateEpoch > 0)
1578
0
        {
1579
0
            bTargetIsDynamicCRS = poSRSTarget->HasPointMotionOperation();
1580
0
        }
1581
0
        poSRSTarget->GetAxis(nullptr, 0, &m_eTargetFirstAxisOrient);
1582
0
    }
1583
1584
#if PROJ_VERSION_MAJOR < 9 ||                                                  \
1585
    (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR < 4)
1586
    if (bSourceIsDynamicCRS && bTargetIsDynamicCRS &&
1587
        dfSourceCoordinateEpoch > 0 && dfTargetCoordinateEpoch > 0 &&
1588
        dfSourceCoordinateEpoch != dfTargetCoordinateEpoch)
1589
    {
1590
        CPLError(CE_Warning, CPLE_AppDefined,
1591
                 "Coordinate transformation between different epochs only"
1592
                 "supported since PROJ 9.4");
1593
    }
1594
#endif
1595
1596
    /* -------------------------------------------------------------------- */
1597
    /*      Setup source and target translations to radians for lat/long    */
1598
    /*      systems.                                                        */
1599
    /* -------------------------------------------------------------------- */
1600
0
    bSourceWrap = false;
1601
0
    dfSourceWrapLong = 0.0;
1602
1603
0
    bTargetWrap = false;
1604
0
    dfTargetWrapLong = 0.0;
1605
1606
    /* -------------------------------------------------------------------- */
1607
    /*      Preliminary logic to setup wrapping.                            */
1608
    /* -------------------------------------------------------------------- */
1609
0
    if (CPLGetConfigOption("CENTER_LONG", nullptr) != nullptr)
1610
0
    {
1611
0
        bSourceWrap = true;
1612
0
        bTargetWrap = true;
1613
        // coverity[tainted_data]
1614
0
        dfSourceWrapLong = dfTargetWrapLong =
1615
0
            CPLAtof(CPLGetConfigOption("CENTER_LONG", ""));
1616
0
        CPLDebug("OGRCT", "Wrap at %g.", dfSourceWrapLong);
1617
0
    }
1618
1619
0
    const char *pszCENTER_LONG;
1620
0
    {
1621
0
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1622
0
        pszCENTER_LONG =
1623
0
            poSRSSource ? poSRSSource->GetExtension("GEOGCS", "CENTER_LONG")
1624
0
                        : nullptr;
1625
0
    }
1626
0
    if (pszCENTER_LONG != nullptr)
1627
0
    {
1628
0
        dfSourceWrapLong = CPLAtof(pszCENTER_LONG);
1629
0
        bSourceWrap = true;
1630
0
        CPLDebug("OGRCT", "Wrap source at %g.", dfSourceWrapLong);
1631
0
    }
1632
0
    else if (bSourceLatLong && options.d->bHasSourceCenterLong)
1633
0
    {
1634
0
        dfSourceWrapLong = options.d->dfSourceCenterLong;
1635
0
        bSourceWrap = true;
1636
0
        CPLDebug("OGRCT", "Wrap source at %g.", dfSourceWrapLong);
1637
0
    }
1638
1639
0
    {
1640
0
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1641
0
        pszCENTER_LONG =
1642
0
            poSRSTarget ? poSRSTarget->GetExtension("GEOGCS", "CENTER_LONG")
1643
0
                        : nullptr;
1644
0
    }
1645
0
    if (pszCENTER_LONG != nullptr)
1646
0
    {
1647
0
        dfTargetWrapLong = CPLAtof(pszCENTER_LONG);
1648
0
        bTargetWrap = true;
1649
0
        CPLDebug("OGRCT", "Wrap target at %g.", dfTargetWrapLong);
1650
0
    }
1651
0
    else if (bTargetLatLong && options.d->bHasTargetCenterLong)
1652
0
    {
1653
0
        dfTargetWrapLong = options.d->dfTargetCenterLong;
1654
0
        bTargetWrap = true;
1655
0
        CPLDebug("OGRCT", "Wrap target at %g.", dfTargetWrapLong);
1656
0
    }
1657
1658
0
    ComputeThreshold();
1659
1660
0
    DetectWebMercatorToWGS84();
1661
1662
0
    const char *pszCTOpSelection =
1663
0
        CPLGetConfigOption("OGR_CT_OP_SELECTION", nullptr);
1664
0
    if (pszCTOpSelection)
1665
0
    {
1666
0
        if (EQUAL(pszCTOpSelection, "PROJ"))
1667
0
            m_eStrategy = Strategy::PROJ;
1668
0
        else if (EQUAL(pszCTOpSelection, "BEST_ACCURACY"))
1669
0
            m_eStrategy = Strategy::BEST_ACCURACY;
1670
0
        else if (EQUAL(pszCTOpSelection, "FIRST_MATCHING"))
1671
0
            m_eStrategy = Strategy::FIRST_MATCHING;
1672
0
        else
1673
0
            CPLError(CE_Warning, CPLE_NotSupported,
1674
0
                     "OGR_CT_OP_SELECTION=%s not supported", pszCTOpSelection);
1675
0
    }
1676
#if PROJ_VERSION_MAJOR < 8
1677
    else
1678
    {
1679
        if (options.d->dfAccuracy >= 0 || !options.d->bAllowBallpark)
1680
        {
1681
            m_eStrategy = Strategy::BEST_ACCURACY;
1682
        }
1683
    }
1684
#endif
1685
0
    if (m_eStrategy == Strategy::PROJ)
1686
0
    {
1687
0
        const char *pszUseApproxTMERC =
1688
0
            CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr);
1689
0
        if (pszUseApproxTMERC && CPLTestBool(pszUseApproxTMERC))
1690
0
        {
1691
0
            CPLDebug("OSRCT", "Using OGR_CT_OP_SELECTION=BEST_ACCURACY as "
1692
0
                              "OSR_USE_APPROX_TMERC is set");
1693
0
            m_eStrategy = Strategy::BEST_ACCURACY;
1694
0
        }
1695
0
    }
1696
1697
0
    if (!options.d->osCoordOperation.empty())
1698
0
    {
1699
0
        auto ctx = OSRGetProjTLSContext();
1700
0
        m_pj = proj_create(ctx, options.d->osCoordOperation);
1701
0
        if (!m_pj)
1702
0
        {
1703
0
            CPLError(CE_Failure, CPLE_NotSupported,
1704
0
                     "Cannot instantiate pipeline %s",
1705
0
                     options.d->osCoordOperation.c_str());
1706
0
            return FALSE;
1707
0
        }
1708
0
        m_bReversePj = options.d->bReverseCO;
1709
0
#ifdef DEBUG
1710
0
        auto info = proj_pj_info(m_pj);
1711
0
        CPLDebug("OGRCT", "%s %s(user set)", info.definition,
1712
0
                 m_bReversePj ? "(reversed) " : "");
1713
0
#endif
1714
0
    }
1715
0
    else if (!bWebMercatorToWGS84LongLat && poSRSSource && poSRSTarget)
1716
0
    {
1717
#ifdef DEBUG_PERF
1718
        struct CPLTimeVal tvStart;
1719
        CPLGettimeofday(&tvStart, nullptr);
1720
        CPLDebug("OGR_CT", "Before proj_create_crs_to_crs()");
1721
#endif
1722
0
#ifdef DEBUG
1723
0
        CPLDebug("OGR_CT", "Source CRS: '%s'", pszSrcSRS);
1724
0
        if (dfSourceCoordinateEpoch > 0)
1725
0
            CPLDebug("OGR_CT", "Source coordinate epoch: %.3f",
1726
0
                     dfSourceCoordinateEpoch);
1727
0
        CPLDebug("OGR_CT", "Target CRS: '%s'", pszTargetSRS);
1728
0
        if (dfTargetCoordinateEpoch > 0)
1729
0
            CPLDebug("OGR_CT", "Target coordinate epoch: %.3f",
1730
0
                     dfTargetCoordinateEpoch);
1731
0
#endif
1732
1733
0
        if (m_eStrategy == Strategy::PROJ)
1734
0
        {
1735
0
            PJ_AREA *area = nullptr;
1736
0
            if (options.d->bHasAreaOfInterest)
1737
0
            {
1738
0
                area = proj_area_create();
1739
0
                proj_area_set_bbox(area, options.d->dfWestLongitudeDeg,
1740
0
                                   options.d->dfSouthLatitudeDeg,
1741
0
                                   options.d->dfEastLongitudeDeg,
1742
0
                                   options.d->dfNorthLatitudeDeg);
1743
0
            }
1744
0
            auto ctx = OSRGetProjTLSContext();
1745
0
#if PROJ_VERSION_MAJOR >= 8
1746
0
            auto srcCRS = proj_create(ctx, pszSrcSRS);
1747
0
            auto targetCRS = proj_create(ctx, pszTargetSRS);
1748
0
            if (srcCRS == nullptr || targetCRS == nullptr)
1749
0
            {
1750
0
                proj_destroy(srcCRS);
1751
0
                proj_destroy(targetCRS);
1752
0
                if (area)
1753
0
                    proj_area_destroy(area);
1754
0
                return FALSE;
1755
0
            }
1756
0
            CPLStringList aosOptions;
1757
0
            if (options.d->dfAccuracy >= 0)
1758
0
                aosOptions.SetNameValue(
1759
0
                    "ACCURACY", CPLSPrintf("%.17g", options.d->dfAccuracy));
1760
0
            if (!options.d->bAllowBallpark)
1761
0
                aosOptions.SetNameValue("ALLOW_BALLPARK", "NO");
1762
0
#if PROJ_VERSION_MAJOR > 9 ||                                                  \
1763
0
    (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 2)
1764
0
            if (options.d->bOnlyBestOptionSet)
1765
0
            {
1766
0
                aosOptions.SetNameValue("ONLY_BEST",
1767
0
                                        options.d->bOnlyBest ? "YES" : "NO");
1768
0
            }
1769
0
#endif
1770
1771
0
#if PROJ_VERSION_MAJOR > 9 ||                                                  \
1772
0
    (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 4)
1773
0
            if (bSourceIsDynamicCRS && dfSourceCoordinateEpoch > 0 &&
1774
0
                bTargetIsDynamicCRS && dfTargetCoordinateEpoch > 0)
1775
0
            {
1776
0
                auto srcCM = proj_coordinate_metadata_create(
1777
0
                    ctx, srcCRS, dfSourceCoordinateEpoch);
1778
0
                proj_destroy(srcCRS);
1779
0
                srcCRS = srcCM;
1780
1781
0
                auto targetCM = proj_coordinate_metadata_create(
1782
0
                    ctx, targetCRS, dfTargetCoordinateEpoch);
1783
0
                proj_destroy(targetCRS);
1784
0
                targetCRS = targetCM;
1785
0
            }
1786
0
#endif
1787
1788
0
            m_pj = proj_create_crs_to_crs_from_pj(ctx, srcCRS, targetCRS, area,
1789
0
                                                  aosOptions.List());
1790
0
            proj_destroy(srcCRS);
1791
0
            proj_destroy(targetCRS);
1792
#else
1793
            m_pj = proj_create_crs_to_crs(ctx, pszSrcSRS, pszTargetSRS, area);
1794
#endif
1795
0
            if (area)
1796
0
                proj_area_destroy(area);
1797
0
            if (m_pj == nullptr)
1798
0
            {
1799
0
                CPLError(CE_Failure, CPLE_NotSupported,
1800
0
                         "Cannot find coordinate operations from `%s' to `%s'",
1801
0
                         pszSrcSRS, pszTargetSRS);
1802
0
                return FALSE;
1803
0
            }
1804
0
        }
1805
0
        else if (!ListCoordinateOperations(pszSrcSRS, pszTargetSRS, options))
1806
0
        {
1807
0
            CPLError(CE_Failure, CPLE_NotSupported,
1808
0
                     "Cannot find coordinate operations from `%s' to `%s'",
1809
0
                     pszSrcSRS, pszTargetSRS);
1810
0
            return FALSE;
1811
0
        }
1812
#ifdef DEBUG_PERF
1813
        struct CPLTimeVal tvEnd;
1814
        CPLGettimeofday(&tvEnd, nullptr);
1815
        const double delay = (tvEnd.tv_sec + tvEnd.tv_usec * 1e-6) -
1816
                             (tvStart.tv_sec + tvStart.tv_usec * 1e-6);
1817
        g_dfTotalTimeCRStoCRS += delay;
1818
        CPLDebug("OGR_CT", "After proj_create_crs_to_crs(): %d ms",
1819
                 static_cast<int>(delay * 1000));
1820
#endif
1821
0
    }
1822
1823
0
    if (options.d->osCoordOperation.empty() && poSRSSource && poSRSTarget &&
1824
0
        (dfSourceCoordinateEpoch == 0 || dfTargetCoordinateEpoch == 0 ||
1825
0
         dfSourceCoordinateEpoch == dfTargetCoordinateEpoch))
1826
0
    {
1827
        // Determine if we can skip the transformation completely.
1828
0
        const char *const apszOptionsIsSame[] = {"CRITERION=EQUIVALENT",
1829
0
                                                 nullptr};
1830
0
        bNoTransform =
1831
0
            !bSourceWrap && !bTargetWrap &&
1832
0
            CPL_TO_BOOL(poSRSSource->IsSame(poSRSTarget, apszOptionsIsSame));
1833
0
    }
1834
1835
0
    return TRUE;
1836
0
}
1837
1838
/************************************************************************/
1839
/*                              op_to_pj()                              */
1840
/************************************************************************/
1841
1842
static PJ *op_to_pj(PJ_CONTEXT *ctx, PJ *op,
1843
                    CPLString *osOutProjString = nullptr)
1844
0
{
1845
    // OSR_USE_ETMERC is here just for legacy
1846
0
    bool bForceApproxTMerc = false;
1847
0
    const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr);
1848
0
    if (pszUseETMERC && pszUseETMERC[0])
1849
0
    {
1850
0
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
1851
0
                     "OSR_USE_ETMERC is a legacy configuration option, which "
1852
0
                     "now has only effect when set to NO (YES is the default). "
1853
0
                     "Use OSR_USE_APPROX_TMERC=YES instead");
1854
0
        bForceApproxTMerc = !CPLTestBool(pszUseETMERC);
1855
0
    }
1856
0
    else
1857
0
    {
1858
0
        const char *pszUseApproxTMERC =
1859
0
            CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr);
1860
0
        if (pszUseApproxTMERC && pszUseApproxTMERC[0])
1861
0
        {
1862
0
            bForceApproxTMerc = CPLTestBool(pszUseApproxTMERC);
1863
0
        }
1864
0
    }
1865
0
    const char *options[] = {
1866
0
        bForceApproxTMerc ? "USE_APPROX_TMERC=YES" : nullptr, nullptr};
1867
0
    auto proj_string = proj_as_proj_string(ctx, op, PJ_PROJ_5, options);
1868
0
    if (!proj_string)
1869
0
    {
1870
0
        return nullptr;
1871
0
    }
1872
0
    if (osOutProjString)
1873
0
        *osOutProjString = proj_string;
1874
1875
0
    if (proj_string[0] == '\0')
1876
0
    {
1877
        /* Null transform ? */
1878
0
        return proj_create(ctx, "proj=affine");
1879
0
    }
1880
0
    else
1881
0
    {
1882
0
        return proj_create(ctx, proj_string);
1883
0
    }
1884
0
}
1885
1886
/************************************************************************/
1887
/*                      ListCoordinateOperations()                      */
1888
/************************************************************************/
1889
1890
bool OGRProjCT::ListCoordinateOperations(
1891
    const char *pszSrcSRS, const char *pszTargetSRS,
1892
    const OGRCoordinateTransformationOptions &options)
1893
0
{
1894
0
    auto ctx = OSRGetProjTLSContext();
1895
1896
0
    auto src = proj_create(ctx, pszSrcSRS);
1897
0
    if (!src)
1898
0
    {
1899
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot instantiate source_crs");
1900
0
        return false;
1901
0
    }
1902
1903
0
    auto dst = proj_create(ctx, pszTargetSRS);
1904
0
    if (!dst)
1905
0
    {
1906
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot instantiate target_crs");
1907
0
        proj_destroy(src);
1908
0
        return false;
1909
0
    }
1910
1911
0
    auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr);
1912
0
    if (!operation_ctx)
1913
0
    {
1914
0
        proj_destroy(src);
1915
0
        proj_destroy(dst);
1916
0
        return false;
1917
0
    }
1918
1919
0
    proj_operation_factory_context_set_spatial_criterion(
1920
0
        ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1921
0
    proj_operation_factory_context_set_grid_availability_use(
1922
0
        ctx, operation_ctx,
1923
0
#if PROJ_VERSION_MAJOR >= 7
1924
0
        proj_context_is_network_enabled(ctx)
1925
0
            ? PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE
1926
0
            :
1927
0
#endif
1928
0
            PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
1929
1930
0
    if (options.d->bHasAreaOfInterest)
1931
0
    {
1932
0
        proj_operation_factory_context_set_area_of_interest(
1933
0
            ctx, operation_ctx, options.d->dfWestLongitudeDeg,
1934
0
            options.d->dfSouthLatitudeDeg, options.d->dfEastLongitudeDeg,
1935
0
            options.d->dfNorthLatitudeDeg);
1936
0
    }
1937
1938
0
    if (options.d->dfAccuracy >= 0)
1939
0
        proj_operation_factory_context_set_desired_accuracy(
1940
0
            ctx, operation_ctx, options.d->dfAccuracy);
1941
0
    if (!options.d->bAllowBallpark)
1942
0
    {
1943
0
#if PROJ_VERSION_MAJOR > 7 ||                                                  \
1944
0
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 1)
1945
0
        proj_operation_factory_context_set_allow_ballpark_transformations(
1946
0
            ctx, operation_ctx, FALSE);
1947
#else
1948
        if (options.d->dfAccuracy < 0)
1949
        {
1950
            proj_operation_factory_context_set_desired_accuracy(
1951
                ctx, operation_ctx, HUGE_VAL);
1952
        }
1953
#endif
1954
0
    }
1955
1956
0
    auto op_list = proj_create_operations(ctx, src, dst, operation_ctx);
1957
1958
0
    if (!op_list)
1959
0
    {
1960
0
        proj_operation_factory_context_destroy(operation_ctx);
1961
0
        proj_destroy(src);
1962
0
        proj_destroy(dst);
1963
0
        return false;
1964
0
    }
1965
1966
0
    auto op_count = proj_list_get_count(op_list);
1967
0
    if (op_count == 0)
1968
0
    {
1969
0
        proj_list_destroy(op_list);
1970
0
        proj_operation_factory_context_destroy(operation_ctx);
1971
0
        proj_destroy(src);
1972
0
        proj_destroy(dst);
1973
0
        CPLDebug("OGRCT", "No operation found matching criteria");
1974
0
        return false;
1975
0
    }
1976
1977
0
    if (op_count == 1 || options.d->bHasAreaOfInterest ||
1978
0
        proj_get_type(src) == PJ_TYPE_GEOCENTRIC_CRS ||
1979
0
        proj_get_type(dst) == PJ_TYPE_GEOCENTRIC_CRS)
1980
0
    {
1981
0
        auto op = proj_list_get(ctx, op_list, 0);
1982
0
        CPLAssert(op);
1983
0
        m_pj = op_to_pj(ctx, op);
1984
0
        CPLString osName;
1985
0
        auto name = proj_get_name(op);
1986
0
        if (name)
1987
0
            osName = name;
1988
0
        proj_destroy(op);
1989
0
        proj_list_destroy(op_list);
1990
0
        proj_operation_factory_context_destroy(operation_ctx);
1991
0
        proj_destroy(src);
1992
0
        proj_destroy(dst);
1993
0
        if (!m_pj)
1994
0
            return false;
1995
0
#ifdef DEBUG
1996
0
        auto info = proj_pj_info(m_pj);
1997
0
        CPLDebug("OGRCT", "%s (%s)", info.definition, osName.c_str());
1998
0
#endif
1999
0
        return true;
2000
0
    }
2001
2002
    // Create a geographic 2D long-lat degrees CRS that is related to the
2003
    // source CRS
2004
0
    auto geodetic_crs = proj_crs_get_geodetic_crs(ctx, src);
2005
0
    if (!geodetic_crs)
2006
0
    {
2007
0
        proj_list_destroy(op_list);
2008
0
        proj_operation_factory_context_destroy(operation_ctx);
2009
0
        proj_destroy(src);
2010
0
        proj_destroy(dst);
2011
0
        CPLDebug("OGRCT", "Cannot find geodetic CRS matching source CRS");
2012
0
        return false;
2013
0
    }
2014
0
    auto geodetic_crs_type = proj_get_type(geodetic_crs);
2015
0
    if (geodetic_crs_type == PJ_TYPE_GEOCENTRIC_CRS ||
2016
0
        geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
2017
0
        geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_3D_CRS)
2018
0
    {
2019
0
        auto datum = proj_crs_get_datum(ctx, geodetic_crs);
2020
0
#if PROJ_VERSION_MAJOR > 7 ||                                                  \
2021
0
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
2022
0
        if (datum == nullptr)
2023
0
        {
2024
0
            datum = proj_crs_get_datum_forced(ctx, geodetic_crs);
2025
0
        }
2026
0
#endif
2027
0
        if (datum)
2028
0
        {
2029
0
            auto ellps = proj_get_ellipsoid(ctx, datum);
2030
0
            proj_destroy(datum);
2031
0
            double semi_major_metre = 0;
2032
0
            double inv_flattening = 0;
2033
0
            proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre,
2034
0
                                          nullptr, nullptr, &inv_flattening);
2035
0
            auto cs = proj_create_ellipsoidal_2D_cs(
2036
0
                ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
2037
            // It is critical to set the prime meridian to 0
2038
0
            auto temp = proj_create_geographic_crs(
2039
0
                ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps),
2040
0
                semi_major_metre, inv_flattening, "Reference prime meridian", 0,
2041
0
                nullptr, 0, cs);
2042
0
            proj_destroy(ellps);
2043
0
            proj_destroy(cs);
2044
0
            proj_destroy(geodetic_crs);
2045
0
            geodetic_crs = temp;
2046
0
            geodetic_crs_type = proj_get_type(geodetic_crs);
2047
0
        }
2048
0
    }
2049
0
    if (geodetic_crs_type != PJ_TYPE_GEOGRAPHIC_2D_CRS)
2050
0
    {
2051
        // Shouldn't happen
2052
0
        proj_list_destroy(op_list);
2053
0
        proj_operation_factory_context_destroy(operation_ctx);
2054
0
        proj_destroy(src);
2055
0
        proj_destroy(dst);
2056
0
        proj_destroy(geodetic_crs);
2057
0
        CPLDebug("OGRCT", "Cannot find geographic CRS matching source CRS");
2058
0
        return false;
2059
0
    }
2060
2061
    // Create the transformation from this geographic 2D CRS to the source CRS
2062
0
    auto op_list_to_geodetic =
2063
0
        proj_create_operations(ctx, geodetic_crs, src, operation_ctx);
2064
0
    proj_destroy(geodetic_crs);
2065
2066
0
    if (op_list_to_geodetic == nullptr ||
2067
0
        proj_list_get_count(op_list_to_geodetic) == 0)
2068
0
    {
2069
0
        CPLDebug(
2070
0
            "OGRCT",
2071
0
            "Cannot compute transformation from geographic CRS to source CRS");
2072
0
        proj_list_destroy(op_list);
2073
0
        proj_list_destroy(op_list_to_geodetic);
2074
0
        proj_operation_factory_context_destroy(operation_ctx);
2075
0
        proj_destroy(src);
2076
0
        proj_destroy(dst);
2077
0
        return false;
2078
0
    }
2079
0
    auto opGeogToSrc = proj_list_get(ctx, op_list_to_geodetic, 0);
2080
0
    CPLAssert(opGeogToSrc);
2081
0
    proj_list_destroy(op_list_to_geodetic);
2082
0
    auto pjGeogToSrc = op_to_pj(ctx, opGeogToSrc);
2083
0
    proj_destroy(opGeogToSrc);
2084
0
    if (!pjGeogToSrc)
2085
0
    {
2086
0
        proj_list_destroy(op_list);
2087
0
        proj_operation_factory_context_destroy(operation_ctx);
2088
0
        proj_destroy(src);
2089
0
        proj_destroy(dst);
2090
0
        return false;
2091
0
    }
2092
2093
0
    const auto addTransformation =
2094
0
        [this, &pjGeogToSrc, &ctx](PJ *op, double west_lon, double south_lat,
2095
0
                                   double east_lon, double north_lat)
2096
0
    {
2097
0
        double minx = -std::numeric_limits<double>::max();
2098
0
        double miny = -std::numeric_limits<double>::max();
2099
0
        double maxx = std::numeric_limits<double>::max();
2100
0
        double maxy = std::numeric_limits<double>::max();
2101
2102
0
        if (!(west_lon == -180.0 && east_lon == 180.0 && south_lat == -90.0 &&
2103
0
              north_lat == 90.0))
2104
0
        {
2105
0
            minx = -minx;
2106
0
            miny = -miny;
2107
0
            maxx = -maxx;
2108
0
            maxy = -maxy;
2109
2110
0
            double x[21 * 4], y[21 * 4];
2111
0
            for (int j = 0; j <= 20; j++)
2112
0
            {
2113
0
                x[j] = west_lon + j * (east_lon - west_lon) / 20;
2114
0
                y[j] = south_lat;
2115
0
                x[21 + j] = west_lon + j * (east_lon - west_lon) / 20;
2116
0
                y[21 + j] = north_lat;
2117
0
                x[21 * 2 + j] = west_lon;
2118
0
                y[21 * 2 + j] = south_lat + j * (north_lat - south_lat) / 20;
2119
0
                x[21 * 3 + j] = east_lon;
2120
0
                y[21 * 3 + j] = south_lat + j * (north_lat - south_lat) / 20;
2121
0
            }
2122
0
            proj_trans_generic(pjGeogToSrc, PJ_FWD, x, sizeof(double), 21 * 4,
2123
0
                               y, sizeof(double), 21 * 4, nullptr, 0, 0,
2124
0
                               nullptr, 0, 0);
2125
0
            for (int j = 0; j < 21 * 4; j++)
2126
0
            {
2127
0
                if (x[j] != HUGE_VAL && y[j] != HUGE_VAL)
2128
0
                {
2129
0
                    minx = std::min(minx, x[j]);
2130
0
                    miny = std::min(miny, y[j]);
2131
0
                    maxx = std::max(maxx, x[j]);
2132
0
                    maxy = std::max(maxy, y[j]);
2133
0
                }
2134
0
            }
2135
0
        }
2136
2137
0
        if (minx <= maxx)
2138
0
        {
2139
0
            CPLString osProjString;
2140
0
            const double accuracy = proj_coordoperation_get_accuracy(ctx, op);
2141
0
            auto pj = op_to_pj(ctx, op, &osProjString);
2142
0
            CPLString osName;
2143
0
            auto name = proj_get_name(op);
2144
0
            if (name)
2145
0
                osName = name;
2146
0
            proj_destroy(op);
2147
0
            op = nullptr;
2148
0
            if (pj)
2149
0
            {
2150
0
                m_oTransformations.emplace_back(minx, miny, maxx, maxy, pj,
2151
0
                                                osName, osProjString, accuracy);
2152
0
            }
2153
0
        }
2154
0
        return op;
2155
0
    };
2156
2157
    // Iterate over source->target candidate transformations and reproject
2158
    // their long-lat bounding box into the source CRS.
2159
0
    bool foundWorldTransformation = false;
2160
0
    for (int i = 0; i < op_count; i++)
2161
0
    {
2162
0
        auto op = proj_list_get(ctx, op_list, i);
2163
0
        CPLAssert(op);
2164
0
        double west_lon = 0.0;
2165
0
        double south_lat = 0.0;
2166
0
        double east_lon = 0.0;
2167
0
        double north_lat = 0.0;
2168
0
        if (proj_get_area_of_use(ctx, op, &west_lon, &south_lat, &east_lon,
2169
0
                                 &north_lat, nullptr))
2170
0
        {
2171
0
            if (west_lon <= east_lon)
2172
0
            {
2173
0
                if (west_lon == -180 && east_lon == 180 && south_lat == -90 &&
2174
0
                    north_lat == 90)
2175
0
                {
2176
0
                    foundWorldTransformation = true;
2177
0
                }
2178
0
                op = addTransformation(op, west_lon, south_lat, east_lon,
2179
0
                                       north_lat);
2180
0
            }
2181
0
            else
2182
0
            {
2183
0
                auto op_clone = proj_clone(ctx, op);
2184
2185
0
                op = addTransformation(op, west_lon, south_lat, 180, north_lat);
2186
0
                op_clone = addTransformation(op_clone, -180, south_lat,
2187
0
                                             east_lon, north_lat);
2188
0
                proj_destroy(op_clone);
2189
0
            }
2190
0
        }
2191
2192
0
        proj_destroy(op);
2193
0
    }
2194
2195
0
    proj_list_destroy(op_list);
2196
2197
    // Sometimes the user will operate even outside the area of use of the
2198
    // source and target CRS, so if no global transformation has been returned
2199
    // previously, trigger the computation of one.
2200
0
    if (!foundWorldTransformation)
2201
0
    {
2202
0
        proj_operation_factory_context_set_area_of_interest(ctx, operation_ctx,
2203
0
                                                            -180, -90, 180, 90);
2204
0
        proj_operation_factory_context_set_spatial_criterion(
2205
0
            ctx, operation_ctx, PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT);
2206
0
        op_list = proj_create_operations(ctx, src, dst, operation_ctx);
2207
0
        if (op_list)
2208
0
        {
2209
0
            op_count = proj_list_get_count(op_list);
2210
0
            for (int i = 0; i < op_count; i++)
2211
0
            {
2212
0
                auto op = proj_list_get(ctx, op_list, i);
2213
0
                CPLAssert(op);
2214
0
                double west_lon = 0.0;
2215
0
                double south_lat = 0.0;
2216
0
                double east_lon = 0.0;
2217
0
                double north_lat = 0.0;
2218
0
                if (proj_get_area_of_use(ctx, op, &west_lon, &south_lat,
2219
0
                                         &east_lon, &north_lat, nullptr) &&
2220
0
                    west_lon == -180 && east_lon == 180 && south_lat == -90 &&
2221
0
                    north_lat == 90)
2222
0
                {
2223
0
                    op = addTransformation(op, west_lon, south_lat, east_lon,
2224
0
                                           north_lat);
2225
0
                }
2226
0
                proj_destroy(op);
2227
0
            }
2228
0
        }
2229
0
        proj_list_destroy(op_list);
2230
0
    }
2231
2232
0
    proj_operation_factory_context_destroy(operation_ctx);
2233
0
    proj_destroy(src);
2234
0
    proj_destroy(dst);
2235
0
    proj_destroy(pjGeogToSrc);
2236
0
    return !m_oTransformations.empty();
2237
0
}
2238
2239
/************************************************************************/
2240
/*                            GetSourceCS()                             */
2241
/************************************************************************/
2242
2243
const OGRSpatialReference *OGRProjCT::GetSourceCS() const
2244
2245
0
{
2246
0
    return poSRSSource;
2247
0
}
2248
2249
/************************************************************************/
2250
/*                            GetTargetCS()                             */
2251
/************************************************************************/
2252
2253
const OGRSpatialReference *OGRProjCT::GetTargetCS() const
2254
2255
0
{
2256
0
    return poSRSTarget;
2257
0
}
2258
2259
/************************************************************************/
2260
/*                             Transform()                              */
2261
/************************************************************************/
2262
2263
int OGRCoordinateTransformation::Transform(size_t nCount, double *x, double *y,
2264
                                           double *z, int *pabSuccessIn)
2265
2266
0
{
2267
0
    int *pabSuccess =
2268
0
        pabSuccessIn
2269
0
            ? pabSuccessIn
2270
0
            : static_cast<int *>(VSI_MALLOC2_VERBOSE(sizeof(int), nCount));
2271
0
    if (!pabSuccess)
2272
0
        return FALSE;
2273
2274
0
    const int bRet = Transform(nCount, x, y, z, nullptr, pabSuccess);
2275
2276
0
    if (pabSuccess != pabSuccessIn)
2277
0
        CPLFree(pabSuccess);
2278
2279
0
    return bRet;
2280
0
}
2281
2282
/************************************************************************/
2283
/*                      TransformWithErrorCodes()                       */
2284
/************************************************************************/
2285
2286
int OGRCoordinateTransformation::TransformWithErrorCodes(size_t nCount,
2287
                                                         double *x, double *y,
2288
                                                         double *z, double *t,
2289
                                                         int *panErrorCodes)
2290
2291
0
{
2292
0
    if (nCount == 1)
2293
0
    {
2294
0
        int nSuccess = 0;
2295
0
        const int bRet = Transform(nCount, x, y, z, t, &nSuccess);
2296
0
        if (panErrorCodes)
2297
0
        {
2298
0
            panErrorCodes[0] = nSuccess ? 0 : -1;
2299
0
        }
2300
0
        return bRet;
2301
0
    }
2302
2303
0
    std::vector<int> abSuccess;
2304
0
    try
2305
0
    {
2306
0
        abSuccess.resize(nCount);
2307
0
    }
2308
0
    catch (const std::bad_alloc &)
2309
0
    {
2310
0
        CPLError(CE_Failure, CPLE_OutOfMemory,
2311
0
                 "Cannot allocate abSuccess[] temporary array");
2312
0
        return FALSE;
2313
0
    }
2314
2315
0
    const int bRet = Transform(nCount, x, y, z, t, abSuccess.data());
2316
2317
0
    if (panErrorCodes)
2318
0
    {
2319
0
        for (size_t i = 0; i < nCount; i++)
2320
0
        {
2321
0
            panErrorCodes[i] = abSuccess[i] ? 0 : -1;
2322
0
        }
2323
0
    }
2324
2325
0
    return bRet;
2326
0
}
2327
2328
/************************************************************************/
2329
/*                             Transform()                              */
2330
/************************************************************************/
2331
2332
int OGRProjCT::Transform(size_t nCount, double *x, double *y, double *z,
2333
                         double *t, int *pabSuccess)
2334
2335
0
{
2336
0
    const int bRet = TransformWithErrorCodes(nCount, x, y, z, t, pabSuccess);
2337
2338
0
    if (pabSuccess)
2339
0
    {
2340
0
        for (size_t i = 0; i < nCount; i++)
2341
0
        {
2342
0
            pabSuccess[i] = (pabSuccess[i] == 0);
2343
0
        }
2344
0
    }
2345
2346
0
    return bRet;
2347
0
}
2348
2349
/************************************************************************/
2350
/*                      TransformWithErrorCodes()                       */
2351
/************************************************************************/
2352
2353
#ifndef PROJ_ERR_COORD_TRANSFM_INVALID_COORD
2354
#define PROJ_ERR_COORD_TRANSFM_INVALID_COORD 2049
2355
#define PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN 2050
2356
#define PROJ_ERR_COORD_TRANSFM_NO_OPERATION 2051
2357
#endif
2358
2359
int OGRProjCT::TransformWithErrorCodes(size_t nCount, double *x, double *y,
2360
                                       double *z, double *t, int *panErrorCodes)
2361
2362
0
{
2363
0
    if (nCount == 0)
2364
0
        return TRUE;
2365
2366
    // Prevent any coordinate modification when possible
2367
0
    if (bNoTransform)
2368
0
    {
2369
0
        if (panErrorCodes)
2370
0
        {
2371
0
            for (size_t i = 0; i < nCount; i++)
2372
0
            {
2373
0
                panErrorCodes[i] = 0;
2374
0
            }
2375
0
        }
2376
0
        return TRUE;
2377
0
    }
2378
2379
#ifdef DEBUG_VERBOSE
2380
    bool bDebugCT = CPLTestBool(CPLGetConfigOption("OGR_CT_DEBUG", "NO"));
2381
    if (bDebugCT)
2382
    {
2383
        CPLDebug("OGRCT", "count = %u", static_cast<unsigned>(nCount));
2384
        for (size_t i = 0; i < nCount; ++i)
2385
        {
2386
            CPLDebug("OGRCT", "  x[%d] = %.16g y[%d] = %.16g", int(i), x[i],
2387
                     int(i), y[i]);
2388
        }
2389
    }
2390
#endif
2391
#ifdef DEBUG_PERF
2392
    // CPLDebug("OGR_CT", "Begin TransformWithErrorCodes()");
2393
    struct CPLTimeVal tvStart;
2394
    CPLGettimeofday(&tvStart, nullptr);
2395
#endif
2396
2397
    /* -------------------------------------------------------------------- */
2398
    /*      Apply data axis to source CRS mapping.                          */
2399
    /* -------------------------------------------------------------------- */
2400
2401
    // Since we may swap the x and y pointers, but cannot tell the caller about this swap,
2402
    // we save the original pointer. The same axis swap code is executed for poSRSTarget.
2403
    // If this nullifies, we save the swap of both axes
2404
0
    const auto xOriginal = x;
2405
2406
0
    if (poSRSSource)
2407
0
    {
2408
0
        const auto &mapping = poSRSSource->GetDataAxisToSRSAxisMapping();
2409
0
        if (mapping.size() >= 2)
2410
0
        {
2411
0
            if (std::abs(mapping[0]) == 2 && std::abs(mapping[1]) == 1)
2412
0
            {
2413
0
                std::swap(x, y);
2414
0
            }
2415
0
            const bool bNegateX = mapping[0] < 0;
2416
0
            if (bNegateX)
2417
0
            {
2418
0
                for (size_t i = 0; i < nCount; i++)
2419
0
                {
2420
0
                    x[i] = -x[i];
2421
0
                }
2422
0
            }
2423
0
            const bool bNegateY = mapping[1] < 0;
2424
0
            if (bNegateY)
2425
0
            {
2426
0
                for (size_t i = 0; i < nCount; i++)
2427
0
                {
2428
0
                    y[i] = -y[i];
2429
0
                }
2430
0
            }
2431
0
            if (z && mapping.size() >= 3 && mapping[2] == -3)
2432
0
            {
2433
0
                for (size_t i = 0; i < nCount; i++)
2434
0
                {
2435
0
                    z[i] = -z[i];
2436
0
                }
2437
0
            }
2438
0
        }
2439
0
    }
2440
2441
    /* -------------------------------------------------------------------- */
2442
    /*      Potentially do longitude wrapping.                              */
2443
    /* -------------------------------------------------------------------- */
2444
0
    if (bSourceLatLong && bSourceWrap)
2445
0
    {
2446
0
        if (m_eSourceFirstAxisOrient == OAO_East)
2447
0
        {
2448
0
            for (size_t i = 0; i < nCount; i++)
2449
0
            {
2450
0
                if (x[i] != HUGE_VAL && y[i] != HUGE_VAL)
2451
0
                {
2452
0
                    if (x[i] < dfSourceWrapLong - 180.0)
2453
0
                        x[i] += 360.0;
2454
0
                    else if (x[i] > dfSourceWrapLong + 180)
2455
0
                        x[i] -= 360.0;
2456
0
                }
2457
0
            }
2458
0
        }
2459
0
        else
2460
0
        {
2461
0
            for (size_t i = 0; i < nCount; i++)
2462
0
            {
2463
0
                if (x[i] != HUGE_VAL && y[i] != HUGE_VAL)
2464
0
                {
2465
0
                    if (y[i] < dfSourceWrapLong - 180.0)
2466
0
                        y[i] += 360.0;
2467
0
                    else if (y[i] > dfSourceWrapLong + 180)
2468
0
                        y[i] -= 360.0;
2469
0
                }
2470
0
            }
2471
0
        }
2472
0
    }
2473
2474
    /* -------------------------------------------------------------------- */
2475
    /*      Optimized transform from WebMercator to WGS84                   */
2476
    /* -------------------------------------------------------------------- */
2477
0
    bool bTransformDone = false;
2478
0
    int bRet = TRUE;
2479
0
    if (bWebMercatorToWGS84LongLat)
2480
0
    {
2481
0
        constexpr double REVERSE_SPHERE_RADIUS = 1.0 / 6378137.0;
2482
2483
0
        if (m_eSourceFirstAxisOrient != OAO_East)
2484
0
        {
2485
0
            std::swap(x, y);
2486
0
        }
2487
2488
0
        double y0 = y[0];
2489
0
        for (size_t i = 0; i < nCount; i++)
2490
0
        {
2491
0
            if (x[i] == HUGE_VAL)
2492
0
            {
2493
0
                bRet = FALSE;
2494
0
            }
2495
0
            else
2496
0
            {
2497
0
                x[i] = x[i] * REVERSE_SPHERE_RADIUS;
2498
0
                if (x[i] > M_PI)
2499
0
                {
2500
0
                    if (x[i] < M_PI + 1e-14)
2501
0
                    {
2502
0
                        x[i] = M_PI;
2503
0
                    }
2504
0
                    else if (m_options.d->bCheckWithInvertProj)
2505
0
                    {
2506
0
                        x[i] = HUGE_VAL;
2507
0
                        y[i] = HUGE_VAL;
2508
0
                        y0 = HUGE_VAL;
2509
0
                        continue;
2510
0
                    }
2511
0
                    else
2512
0
                    {
2513
0
                        do
2514
0
                        {
2515
0
                            x[i] -= 2 * M_PI;
2516
0
                        } while (x[i] > M_PI);
2517
0
                    }
2518
0
                }
2519
0
                else if (x[i] < -M_PI)
2520
0
                {
2521
0
                    if (x[i] > -M_PI - 1e-14)
2522
0
                    {
2523
0
                        x[i] = -M_PI;
2524
0
                    }
2525
0
                    else if (m_options.d->bCheckWithInvertProj)
2526
0
                    {
2527
0
                        x[i] = HUGE_VAL;
2528
0
                        y[i] = HUGE_VAL;
2529
0
                        y0 = HUGE_VAL;
2530
0
                        continue;
2531
0
                    }
2532
0
                    else
2533
0
                    {
2534
0
                        do
2535
0
                        {
2536
0
                            x[i] += 2 * M_PI;
2537
0
                        } while (x[i] < -M_PI);
2538
0
                    }
2539
0
                }
2540
0
                constexpr double RAD_TO_DEG = 180. / M_PI;
2541
0
                x[i] *= RAD_TO_DEG;
2542
2543
                // Optimization for the case where we are provided a whole line
2544
                // of same northing.
2545
0
                if (i > 0 && y[i] == y0)
2546
0
                    y[i] = y[0];
2547
0
                else
2548
0
                {
2549
0
                    y[i] = M_PI / 2.0 -
2550
0
                           2.0 * atan(exp(-y[i] * REVERSE_SPHERE_RADIUS));
2551
0
                    y[i] *= RAD_TO_DEG;
2552
0
                }
2553
0
            }
2554
0
        }
2555
2556
0
        if (panErrorCodes)
2557
0
        {
2558
0
            for (size_t i = 0; i < nCount; i++)
2559
0
            {
2560
0
                if (x[i] != HUGE_VAL)
2561
0
                    panErrorCodes[i] = 0;
2562
0
                else
2563
0
                    panErrorCodes[i] =
2564
0
                        PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN;
2565
0
            }
2566
0
        }
2567
2568
0
        if (m_eTargetFirstAxisOrient != OAO_East)
2569
0
        {
2570
0
            std::swap(x, y);
2571
0
        }
2572
2573
0
        bTransformDone = true;
2574
0
    }
2575
2576
    // Determine the default coordinate epoch, if not provided in the point to
2577
    // transform.
2578
    // For time-dependent transformations, PROJ can currently only do
2579
    // staticCRS -> dynamicCRS or dynamicCRS -> staticCRS transformations, and
2580
    // in either case, the coordinate epoch of the dynamicCRS must be provided
2581
    // as the input time.
2582
0
    double dfDefaultTime = HUGE_VAL;
2583
0
    if (bSourceIsDynamicCRS && dfSourceCoordinateEpoch > 0 &&
2584
0
        !bTargetIsDynamicCRS &&
2585
0
        CPLTestBool(
2586
0
            CPLGetConfigOption("OGR_CT_USE_SRS_COORDINATE_EPOCH", "YES")))
2587
0
    {
2588
0
        dfDefaultTime = dfSourceCoordinateEpoch;
2589
0
        CPLDebug("OGR_CT", "Using coordinate epoch %f from source CRS",
2590
0
                 dfDefaultTime);
2591
0
    }
2592
0
    else if (bTargetIsDynamicCRS && dfTargetCoordinateEpoch > 0 &&
2593
0
             !bSourceIsDynamicCRS &&
2594
0
             CPLTestBool(
2595
0
                 CPLGetConfigOption("OGR_CT_USE_SRS_COORDINATE_EPOCH", "YES")))
2596
0
    {
2597
0
        dfDefaultTime = dfTargetCoordinateEpoch;
2598
0
        CPLDebug("OGR_CT", "Using coordinate epoch %f from target CRS",
2599
0
                 dfDefaultTime);
2600
0
    }
2601
2602
    /* -------------------------------------------------------------------- */
2603
    /*      Select dynamically the best transformation for the data, if     */
2604
    /*      needed.                                                         */
2605
    /* -------------------------------------------------------------------- */
2606
0
    auto ctx = OSRGetProjTLSContext();
2607
2608
0
    PJ *pj = m_pj;
2609
0
    if (!bTransformDone && !pj)
2610
0
    {
2611
0
        double avgX = 0.0;
2612
0
        double avgY = 0.0;
2613
0
        size_t nCountValid = 0;
2614
0
        for (size_t i = 0; i < nCount; i++)
2615
0
        {
2616
0
            if (x[i] != HUGE_VAL && y[i] != HUGE_VAL)
2617
0
            {
2618
0
                avgX += x[i];
2619
0
                avgY += y[i];
2620
0
                nCountValid++;
2621
0
            }
2622
0
        }
2623
0
        if (nCountValid != 0)
2624
0
        {
2625
0
            avgX /= static_cast<double>(nCountValid);
2626
0
            avgY /= static_cast<double>(nCountValid);
2627
0
        }
2628
2629
0
        constexpr int N_MAX_RETRY = 2;
2630
0
        int iExcluded[N_MAX_RETRY] = {-1, -1};
2631
2632
0
        const int nOperations = static_cast<int>(m_oTransformations.size());
2633
0
        PJ_COORD coord;
2634
0
        coord.xyzt.x = avgX;
2635
0
        coord.xyzt.y = avgY;
2636
0
        coord.xyzt.z = z ? z[0] : 0;
2637
0
        coord.xyzt.t = t ? t[0] : dfDefaultTime;
2638
2639
        // We may need several attempts. For example the point at
2640
        // lon=-111.5 lat=45.26 falls into the bounding box of the Canadian
2641
        // ntv2_0.gsb grid, except that it is not in any of the subgrids, being
2642
        // in the US. We thus need another retry that will select the conus
2643
        // grid.
2644
0
        for (int iRetry = 0; iRetry <= N_MAX_RETRY; iRetry++)
2645
0
        {
2646
0
            int iBestTransf = -1;
2647
            // Select transform whose BBOX match our data and has the best
2648
            // accuracy if m_eStrategy == BEST_ACCURACY. Or just the first BBOX
2649
            // matching one, if
2650
            //  m_eStrategy == FIRST_MATCHING
2651
0
            double dfBestAccuracy = std::numeric_limits<double>::infinity();
2652
0
            for (int i = 0; i < nOperations; i++)
2653
0
            {
2654
0
                if (i == iExcluded[0] || i == iExcluded[1])
2655
0
                {
2656
0
                    continue;
2657
0
                }
2658
0
                const auto &transf = m_oTransformations[i];
2659
0
                if (avgX >= transf.minx && avgX <= transf.maxx &&
2660
0
                    avgY >= transf.miny && avgY <= transf.maxy &&
2661
0
                    (iBestTransf < 0 || (transf.accuracy >= 0 &&
2662
0
                                         transf.accuracy < dfBestAccuracy)))
2663
0
                {
2664
0
                    iBestTransf = i;
2665
0
                    dfBestAccuracy = transf.accuracy;
2666
0
                    if (m_eStrategy == Strategy::FIRST_MATCHING)
2667
0
                        break;
2668
0
                }
2669
0
            }
2670
0
            if (iBestTransf < 0)
2671
0
            {
2672
0
                break;
2673
0
            }
2674
0
            auto &transf = m_oTransformations[iBestTransf];
2675
0
            pj = transf.pj;
2676
0
            proj_assign_context(pj, ctx);
2677
0
            if (iBestTransf != m_iCurTransformation)
2678
0
            {
2679
0
                CPLDebug("OGRCT", "Selecting transformation %s (%s)",
2680
0
                         transf.osProjString.c_str(), transf.osName.c_str());
2681
0
                m_iCurTransformation = iBestTransf;
2682
0
            }
2683
2684
0
            auto res = proj_trans(pj, m_bReversePj ? PJ_INV : PJ_FWD, coord);
2685
0
            if (res.xyzt.x != HUGE_VAL)
2686
0
            {
2687
0
                break;
2688
0
            }
2689
0
            pj = nullptr;
2690
0
            CPLDebug("OGRCT", "Did not result in valid result. "
2691
0
                              "Attempting a retry with another operation.");
2692
0
            if (iRetry == N_MAX_RETRY)
2693
0
            {
2694
0
                break;
2695
0
            }
2696
0
            iExcluded[iRetry] = iBestTransf;
2697
0
        }
2698
2699
0
        if (!pj)
2700
0
        {
2701
            // In case we did not find an operation whose area of use is
2702
            // compatible with the input coordinate, then goes through again the
2703
            // list, and use the first operation that does not require grids.
2704
0
            for (int i = 0; i < nOperations; i++)
2705
0
            {
2706
0
                auto &transf = m_oTransformations[i];
2707
0
                if (proj_coordoperation_get_grid_used_count(ctx, transf.pj) ==
2708
0
                    0)
2709
0
                {
2710
0
                    pj = transf.pj;
2711
0
                    proj_assign_context(pj, ctx);
2712
0
                    if (i != m_iCurTransformation)
2713
0
                    {
2714
0
                        CPLDebug("OGRCT", "Selecting transformation %s (%s)",
2715
0
                                 transf.osProjString.c_str(),
2716
0
                                 transf.osName.c_str());
2717
0
                        m_iCurTransformation = i;
2718
0
                    }
2719
0
                    break;
2720
0
                }
2721
0
            }
2722
0
        }
2723
2724
0
        if (!pj)
2725
0
        {
2726
0
            if (m_bEmitErrors && ++nErrorCount < 20)
2727
0
            {
2728
0
                CPLError(CE_Failure, CPLE_AppDefined,
2729
0
                         "Cannot find transformation for provided coordinates");
2730
0
            }
2731
0
            else if (nErrorCount == 20)
2732
0
            {
2733
0
                CPLError(CE_Failure, CPLE_AppDefined,
2734
0
                         "Reprojection failed, further errors will be "
2735
0
                         "suppressed on the transform object.");
2736
0
            }
2737
2738
0
            for (size_t i = 0; i < nCount; i++)
2739
0
            {
2740
0
                x[i] = HUGE_VAL;
2741
0
                y[i] = HUGE_VAL;
2742
0
                if (panErrorCodes)
2743
0
                    panErrorCodes[i] = PROJ_ERR_COORD_TRANSFM_NO_OPERATION;
2744
0
            }
2745
0
            return FALSE;
2746
0
        }
2747
0
    }
2748
0
    if (pj)
2749
0
    {
2750
0
        proj_assign_context(pj, ctx);
2751
0
    }
2752
2753
    /* -------------------------------------------------------------------- */
2754
    /*      Do the transformation (or not...) using PROJ                    */
2755
    /* -------------------------------------------------------------------- */
2756
2757
0
    if (!bTransformDone)
2758
0
    {
2759
0
        const auto nLastErrorCounter = CPLGetErrorCounter();
2760
2761
0
        for (size_t i = 0; i < nCount; i++)
2762
0
        {
2763
0
            PJ_COORD coord;
2764
0
            const double xIn = x[i];
2765
0
            const double yIn = y[i];
2766
0
            if (!std::isfinite(xIn))
2767
0
            {
2768
0
                bRet = FALSE;
2769
0
                x[i] = HUGE_VAL;
2770
0
                y[i] = HUGE_VAL;
2771
0
                if (panErrorCodes)
2772
0
                    panErrorCodes[i] = PROJ_ERR_COORD_TRANSFM_INVALID_COORD;
2773
0
                continue;
2774
0
            }
2775
0
            coord.xyzt.x = x[i];
2776
0
            coord.xyzt.y = y[i];
2777
0
            coord.xyzt.z = z ? z[i] : 0;
2778
0
            coord.xyzt.t = t ? t[i] : dfDefaultTime;
2779
0
            proj_errno_reset(pj);
2780
0
            coord = proj_trans(pj, m_bReversePj ? PJ_INV : PJ_FWD, coord);
2781
#if 0
2782
            CPLDebug("OGRCT",
2783
                     "Transforming (x=%f,y=%f,z=%f,time=%f) to "
2784
                     "(x=%f,y=%f,z=%f,time=%f)",
2785
                     x[i], y[i], z ? z[i] : 0, t ? t[i] : dfDefaultTime,
2786
                     coord.xyzt.x, coord.xyzt.y, coord.xyzt.z, coord.xyzt.t);
2787
#endif
2788
0
            x[i] = coord.xyzt.x;
2789
0
            y[i] = coord.xyzt.y;
2790
0
            if (z)
2791
0
                z[i] = coord.xyzt.z;
2792
0
            if (t)
2793
0
                t[i] = coord.xyzt.t;
2794
0
            int err = 0;
2795
0
            if (std::isnan(coord.xyzt.x))
2796
0
            {
2797
0
                bRet = FALSE;
2798
                // This shouldn't normally happen if PROJ projections behave
2799
                // correctly, but e.g inverse laea before PROJ 8.1.1 could
2800
                // do that for points out of domain.
2801
                // See https://github.com/OSGeo/PROJ/pull/2800
2802
0
                x[i] = HUGE_VAL;
2803
0
                y[i] = HUGE_VAL;
2804
0
                err = PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN;
2805
2806
0
#ifdef DEBUG
2807
0
                CPLErrorOnce(CE_Warning, CPLE_AppDefined,
2808
0
                             "PROJ returned a NaN value. It should be fixed");
2809
#else
2810
                CPLDebugOnce("OGR_CT",
2811
                             "PROJ returned a NaN value. It should be fixed");
2812
#endif
2813
0
            }
2814
0
            else if (coord.xyzt.x == HUGE_VAL)
2815
0
            {
2816
0
                bRet = FALSE;
2817
0
                err = proj_errno(pj);
2818
                // PROJ should normally emit an error, but in case it does not
2819
                // (e.g PROJ 6.3 with the +ortho projection), synthesize one
2820
0
                if (err == 0)
2821
0
                    err = PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN;
2822
0
            }
2823
0
            else
2824
0
            {
2825
0
                if (m_recordDifferentOperationsUsed &&
2826
0
                    !m_differentOperationsUsed)
2827
0
                {
2828
0
#if PROJ_VERSION_MAJOR > 9 ||                                                  \
2829
0
    (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 1)
2830
2831
0
                    PJ *lastOp = proj_trans_get_last_used_operation(pj);
2832
0
                    if (lastOp)
2833
0
                    {
2834
0
                        const char *projString = proj_as_proj_string(
2835
0
                            ctx, lastOp, PJ_PROJ_5, nullptr);
2836
0
                        if (projString)
2837
0
                        {
2838
0
                            if (m_lastPjUsedPROJString.empty())
2839
0
                            {
2840
0
                                m_lastPjUsedPROJString = projString;
2841
0
                            }
2842
0
                            else if (m_lastPjUsedPROJString != projString)
2843
0
                            {
2844
0
                                m_differentOperationsUsed = true;
2845
0
                            }
2846
0
                        }
2847
0
                        proj_destroy(lastOp);
2848
0
                    }
2849
0
#endif
2850
0
                }
2851
2852
0
                if (m_options.d->bCheckWithInvertProj)
2853
0
                {
2854
                    // For some projections, we cannot detect if we are trying to
2855
                    // reproject coordinates outside the validity area of the
2856
                    // projection. So let's do the reverse reprojection and compare
2857
                    // with the source coordinates.
2858
0
                    coord =
2859
0
                        proj_trans(pj, m_bReversePj ? PJ_FWD : PJ_INV, coord);
2860
0
                    if (fabs(coord.xyzt.x - xIn) > dfThreshold ||
2861
0
                        fabs(coord.xyzt.y - yIn) > dfThreshold)
2862
0
                    {
2863
0
                        bRet = FALSE;
2864
0
                        err = PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN;
2865
0
                        x[i] = HUGE_VAL;
2866
0
                        y[i] = HUGE_VAL;
2867
0
                    }
2868
0
                }
2869
0
            }
2870
2871
0
            if (panErrorCodes)
2872
0
                panErrorCodes[i] = err;
2873
2874
            /* --------------------------------------------------------------------
2875
             */
2876
            /*      Try to report an error through CPL.  Get proj error string
2877
             */
2878
            /*      if possible.  Try to avoid reporting thousands of errors. */
2879
            /*      Suppress further error reporting on this OGRProjCT if we */
2880
            /*      have already reported 20 errors. */
2881
            /* --------------------------------------------------------------------
2882
             */
2883
0
            if (err != 0)
2884
0
            {
2885
0
                if (++nErrorCount < 20)
2886
0
                {
2887
0
#if PROJ_VERSION_MAJOR >= 8
2888
0
                    const char *pszError = proj_context_errno_string(ctx, err);
2889
#else
2890
                    const char *pszError = proj_errno_string(err);
2891
#endif
2892
0
                    if (m_bEmitErrors
2893
0
#ifdef PROJ_ERR_OTHER_NO_INVERSE_OP
2894
0
                        || (i == 0 && err == PROJ_ERR_OTHER_NO_INVERSE_OP)
2895
0
#endif
2896
0
                    )
2897
0
                    {
2898
0
                        if (nLastErrorCounter != CPLGetErrorCounter() &&
2899
0
                            CPLGetLastErrorType() == CE_Failure &&
2900
0
                            strstr(CPLGetLastErrorMsg(), "PROJ:"))
2901
0
                        {
2902
                            // do nothing
2903
0
                        }
2904
0
                        else if (pszError == nullptr)
2905
0
                            CPLError(CE_Failure, CPLE_AppDefined,
2906
0
                                     "Reprojection failed, err = %d", err);
2907
0
                        else
2908
0
                            CPLError(CE_Failure, CPLE_AppDefined, "%s",
2909
0
                                     pszError);
2910
0
                    }
2911
0
                    else
2912
0
                    {
2913
0
                        if (pszError == nullptr)
2914
0
                            CPLDebug("OGRCT", "Reprojection failed, err = %d",
2915
0
                                     err);
2916
0
                        else
2917
0
                            CPLDebug("OGRCT", "%s for xIn=%.10g yIn=%.10g",
2918
0
                                     pszError, xIn, yIn);
2919
0
                    }
2920
0
                }
2921
0
                else if (nErrorCount == 20)
2922
0
                {
2923
0
                    if (m_bEmitErrors)
2924
0
                    {
2925
0
                        CPLError(CE_Failure, CPLE_AppDefined,
2926
0
                                 "Reprojection failed, err = %d, further "
2927
0
                                 "errors will be "
2928
0
                                 "suppressed on the transform object.",
2929
0
                                 err);
2930
0
                    }
2931
0
                    else
2932
0
                    {
2933
0
                        CPLDebug("OGRCT",
2934
0
                                 "Reprojection failed, err = %d, further "
2935
0
                                 "errors will be "
2936
0
                                 "suppressed on the transform object.",
2937
0
                                 err);
2938
0
                    }
2939
0
                }
2940
0
            }
2941
0
        }
2942
0
    }
2943
2944
    /* -------------------------------------------------------------------- */
2945
    /*      Potentially do longitude wrapping.                              */
2946
    /* -------------------------------------------------------------------- */
2947
0
    if (bTargetLatLong && bTargetWrap)
2948
0
    {
2949
0
        if (m_eTargetFirstAxisOrient == OAO_East)
2950
0
        {
2951
0
            for (size_t i = 0; i < nCount; i++)
2952
0
            {
2953
0
                if (x[i] != HUGE_VAL && y[i] != HUGE_VAL)
2954
0
                {
2955
0
                    if (x[i] < dfTargetWrapLong - 180.0)
2956
0
                        x[i] += 360.0;
2957
0
                    else if (x[i] > dfTargetWrapLong + 180)
2958
0
                        x[i] -= 360.0;
2959
0
                }
2960
0
            }
2961
0
        }
2962
0
        else
2963
0
        {
2964
0
            for (size_t i = 0; i < nCount; i++)
2965
0
            {
2966
0
                if (x[i] != HUGE_VAL && y[i] != HUGE_VAL)
2967
0
                {
2968
0
                    if (y[i] < dfTargetWrapLong - 180.0)
2969
0
                        y[i] += 360.0;
2970
0
                    else if (y[i] > dfTargetWrapLong + 180)
2971
0
                        y[i] -= 360.0;
2972
0
                }
2973
0
            }
2974
0
        }
2975
0
    }
2976
2977
    /* -------------------------------------------------------------------- */
2978
    /*      Apply data axis to target CRS mapping.                          */
2979
    /* -------------------------------------------------------------------- */
2980
0
    if (poSRSTarget)
2981
0
    {
2982
0
        const auto &mapping = poSRSTarget->GetDataAxisToSRSAxisMapping();
2983
0
        if (mapping.size() >= 2)
2984
0
        {
2985
0
            const bool bNegateX = mapping[0] < 0;
2986
0
            if (bNegateX)
2987
0
            {
2988
0
                for (size_t i = 0; i < nCount; i++)
2989
0
                {
2990
0
                    x[i] = -x[i];
2991
0
                }
2992
0
            }
2993
0
            const bool bNegateY = mapping[1] < 0;
2994
0
            if (bNegateY)
2995
0
            {
2996
0
                for (size_t i = 0; i < nCount; i++)
2997
0
                {
2998
0
                    y[i] = -y[i];
2999
0
                }
3000
0
            }
3001
0
            if (z && mapping.size() >= 3 && mapping[2] == -3)
3002
0
            {
3003
0
                for (size_t i = 0; i < nCount; i++)
3004
0
                {
3005
0
                    z[i] = -z[i];
3006
0
                }
3007
0
            }
3008
3009
0
            if (std::abs(mapping[0]) == 2 && std::abs(mapping[1]) == 1)
3010
0
            {
3011
0
                std::swap(x, y);
3012
0
            }
3013
0
        }
3014
0
    }
3015
3016
    // Check whether final "genuine" axis swap is really necessary
3017
0
    if (x != xOriginal)
3018
0
    {
3019
0
        std::swap_ranges(x, x + nCount, y);
3020
0
    }
3021
3022
#ifdef DEBUG_VERBOSE
3023
    if (bDebugCT)
3024
    {
3025
        CPLDebug("OGRCT", "Out:");
3026
        for (size_t i = 0; i < nCount; ++i)
3027
        {
3028
            CPLDebug("OGRCT", "  x[%d] = %.16g y[%d] = %.16g", int(i), x[i],
3029
                     int(i), y[i]);
3030
        }
3031
    }
3032
#endif
3033
#ifdef DEBUG_PERF
3034
    struct CPLTimeVal tvEnd;
3035
    CPLGettimeofday(&tvEnd, nullptr);
3036
    const double delay = (tvEnd.tv_sec + tvEnd.tv_usec * 1e-6) -
3037
                         (tvStart.tv_sec + tvStart.tv_usec * 1e-6);
3038
    g_dfTotalTimeReprojection += delay;
3039
    // CPLDebug("OGR_CT", "End TransformWithErrorCodes(): %d ms",
3040
    //          static_cast<int>(delay * 1000));
3041
#endif
3042
3043
0
    return bRet;
3044
0
}
3045
3046
/************************************************************************/
3047
/*                          TransformBounds()                           */
3048
/************************************************************************/
3049
3050
// ---------------------------------------------------------------------------
3051
static double simple_min(const double *data, const int *panErrorCodes,
3052
                         const int arr_len)
3053
0
{
3054
0
    double min_value = HUGE_VAL;
3055
0
    for (int iii = 0; iii < arr_len; iii++)
3056
0
    {
3057
0
        if ((data[iii] < min_value || min_value == HUGE_VAL) &&
3058
0
            panErrorCodes[iii] == 0)
3059
0
            min_value = data[iii];
3060
0
    }
3061
0
    return min_value;
3062
0
}
3063
3064
// ---------------------------------------------------------------------------
3065
static double simple_max(const double *data, const int *panErrorCodes,
3066
                         const int arr_len)
3067
0
{
3068
0
    double max_value = HUGE_VAL;
3069
0
    for (int iii = 0; iii < arr_len; iii++)
3070
0
    {
3071
0
        if ((data[iii] > max_value || max_value == HUGE_VAL) &&
3072
0
            panErrorCodes[iii] == 0)
3073
0
            max_value = data[iii];
3074
0
    }
3075
0
    return max_value;
3076
0
}
3077
3078
// ---------------------------------------------------------------------------
3079
static int _find_previous_index(const int iii, const int *panErrorCodes,
3080
                                const int arr_len)
3081
0
{
3082
    // find index of nearest valid previous value if exists
3083
0
    int prev_iii = iii - 1;
3084
0
    if (prev_iii == -1)  // handle wraparound
3085
0
        prev_iii = arr_len - 1;
3086
0
    while (panErrorCodes[prev_iii] != 0 && prev_iii != iii)
3087
0
    {
3088
0
        prev_iii--;
3089
0
        if (prev_iii == -1)  // handle wraparound
3090
0
            prev_iii = arr_len - 1;
3091
0
    }
3092
0
    return prev_iii;
3093
0
}
3094
3095
// ---------------------------------------------------------------------------
3096
/******************************************************************************
3097
Handles the case when longitude values cross the antimeridian
3098
when calculating the minimum.
3099
Note: The data array must be in a linear ring.
3100
Note: This requires a densified ring with at least 2 additional
3101
        points per edge to correctly handle global extents.
3102
If only 1 additional point:
3103
    |        |
3104
    |RL--x0--|RL--
3105
    |        |
3106
-180    180|-180
3107
If they are evenly spaced and it crosses the antimeridian:
3108
x0 - L = 180
3109
R - x0 = -180
3110
For example:
3111
Let R = -179.9, x0 = 0.1, L = -179.89
3112
x0 - L = 0.1 - -179.9 = 180
3113
R - x0 = -179.89 - 0.1 ~= -180
3114
This is the same in the case when it didn't cross the antimeridian.
3115
If you have 2 additional points:
3116
    |            |
3117
    |RL--x0--x1--|RL--
3118
    |            |
3119
-180        180|-180
3120
If they are evenly spaced and it crosses the antimeridian:
3121
x0 - L = 120
3122
x1 - x0 = 120
3123
R - x1 = -240
3124
For example:
3125
Let R = -179.9, x0 = -59.9, x1 = 60.1 L = -179.89
3126
x0 - L = 59.9 - -179.9 = 120
3127
x1 - x0 = 60.1 - 59.9 = 120
3128
R - x1 = -179.89 - 60.1 ~= -240
3129
However, if they are evenly spaced and it didn't cross the antimeridian:
3130
x0 - L = 120
3131
x1 - x0 = 120
3132
R - x1 = 120
3133
From this, we have a delta that is guaranteed to be significantly
3134
large enough to tell the difference reguarless of the direction
3135
the antimeridian was crossed.
3136
However, even though the spacing was even in the source projection, it isn't
3137
guaranteed in the target geographic projection. So, instead of 240, 200 is used
3138
as it significantly larger than 120 to be sure that the antimeridian was crossed
3139
but smalller than 240 to account for possible irregularities in distances
3140
when re-projecting. Also, 200 ensures latitudes are ignored for axis order
3141
handling.
3142
******************************************************************************/
3143
static double antimeridian_min(const double *data, const int *panErrorCodes,
3144
                               const int arr_len)
3145
0
{
3146
0
    double positive_min = HUGE_VAL;
3147
0
    double min_value = HUGE_VAL;
3148
0
    int crossed_meridian_count = 0;
3149
0
    bool positive_meridian = false;
3150
3151
0
    for (int iii = 0; iii < arr_len; iii++)
3152
0
    {
3153
0
        if (panErrorCodes[iii])
3154
0
            continue;
3155
0
        int prev_iii = _find_previous_index(iii, panErrorCodes, arr_len);
3156
        // check if crossed meridian
3157
0
        double delta = data[prev_iii] - data[iii];
3158
        // 180 -> -180
3159
0
        if (delta >= 200 && delta != HUGE_VAL)
3160
0
        {
3161
0
            if (crossed_meridian_count == 0)
3162
0
                positive_min = min_value;
3163
0
            crossed_meridian_count++;
3164
0
            positive_meridian = false;
3165
            // -180 -> 180
3166
0
        }
3167
0
        else if (delta <= -200 && delta != HUGE_VAL)
3168
0
        {
3169
0
            if (crossed_meridian_count == 0)
3170
0
                positive_min = data[iii];
3171
0
            crossed_meridian_count++;
3172
0
            positive_meridian = true;
3173
0
        }
3174
        // positive meridian side min
3175
0
        if (positive_meridian && data[iii] < positive_min)
3176
0
            positive_min = data[iii];
3177
        // track general min value
3178
0
        if (data[iii] < min_value)
3179
0
            min_value = data[iii];
3180
0
    }
3181
3182
0
    if (crossed_meridian_count == 2)
3183
0
        return positive_min;
3184
0
    else if (crossed_meridian_count == 4)
3185
        // bounds extends beyond -180/180
3186
0
        return -180;
3187
0
    return min_value;
3188
0
}
3189
3190
// ---------------------------------------------------------------------------
3191
// Handles the case when longitude values cross the antimeridian
3192
// when calculating the minimum.
3193
// Note: The data array must be in a linear ring.
3194
// Note: This requires a densified ring with at least 2 additional
3195
//       points per edge to correctly handle global extents.
3196
// See antimeridian_min docstring for reasoning.
3197
static double antimeridian_max(const double *data, const int *panErrorCodes,
3198
                               const int arr_len)
3199
0
{
3200
0
    double negative_max = -HUGE_VAL;
3201
0
    double max_value = -HUGE_VAL;
3202
0
    bool negative_meridian = false;
3203
0
    int crossed_meridian_count = 0;
3204
3205
0
    for (int iii = 0; iii < arr_len; iii++)
3206
0
    {
3207
0
        if (panErrorCodes[iii])
3208
0
            continue;
3209
0
        int prev_iii = _find_previous_index(iii, panErrorCodes, arr_len);
3210
        // check if crossed meridian
3211
0
        double delta = data[prev_iii] - data[iii];
3212
        // 180 -> -180
3213
0
        if (delta >= 200 && delta != HUGE_VAL)
3214
0
        {
3215
0
            if (crossed_meridian_count == 0)
3216
0
                negative_max = data[iii];
3217
0
            crossed_meridian_count++;
3218
0
            negative_meridian = true;
3219
            // -180 -> 180
3220
0
        }
3221
0
        else if (delta <= -200 && delta != HUGE_VAL)
3222
0
        {
3223
0
            if (crossed_meridian_count == 0)
3224
0
                negative_max = max_value;
3225
0
            negative_meridian = false;
3226
0
            crossed_meridian_count++;
3227
0
        }
3228
        // negative meridian side max
3229
0
        if (negative_meridian &&
3230
0
            (data[iii] > negative_max || negative_max == HUGE_VAL) &&
3231
0
            panErrorCodes[iii] == 0)
3232
0
            negative_max = data[iii];
3233
        // track general max value
3234
0
        if ((data[iii] > max_value || max_value == HUGE_VAL) &&
3235
0
            panErrorCodes[iii] == 0)
3236
0
            max_value = data[iii];
3237
0
    }
3238
0
    if (crossed_meridian_count == 2)
3239
0
        return negative_max;
3240
0
    else if (crossed_meridian_count == 4)
3241
        // bounds extends beyond -180/180
3242
0
        return 180;
3243
0
    return max_value;
3244
0
}
3245
3246
// ---------------------------------------------------------------------------
3247
// Check if the original projected bounds contains
3248
// the north pole.
3249
// This assumes that the destination CRS is geographic.
3250
bool OGRProjCT::ContainsNorthPole(const double xmin, const double ymin,
3251
                                  const double xmax, const double ymax,
3252
                                  bool lon_lat_order)
3253
0
{
3254
0
    double pole_y = 90;
3255
0
    double pole_x = 0;
3256
0
    if (!lon_lat_order)
3257
0
    {
3258
0
        pole_y = 0;
3259
0
        pole_x = 90;
3260
0
    }
3261
0
    auto inverseCT = std::unique_ptr<OGRCoordinateTransformation>(GetInverse());
3262
0
    if (!inverseCT)
3263
0
        return false;
3264
0
    CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
3265
0
    const bool success = inverseCT->TransformWithErrorCodes(
3266
0
        1, &pole_x, &pole_y, nullptr, nullptr, nullptr);
3267
0
    return success && xmin < pole_x && pole_x < xmax && ymax > pole_y &&
3268
0
           pole_y > ymin;
3269
0
}
3270
3271
// ---------------------------------------------------------------------------
3272
// Check if the original projected bounds contains
3273
// the south pole.
3274
// This assumes that the destination CRS is geographic.
3275
bool OGRProjCT::ContainsSouthPole(const double xmin, const double ymin,
3276
                                  const double xmax, const double ymax,
3277
                                  bool lon_lat_order)
3278
0
{
3279
0
    double pole_y = -90;
3280
0
    double pole_x = 0;
3281
0
    if (!lon_lat_order)
3282
0
    {
3283
0
        pole_y = 0;
3284
0
        pole_x = -90;
3285
0
    }
3286
0
    auto inverseCT = std::unique_ptr<OGRCoordinateTransformation>(GetInverse());
3287
0
    if (!inverseCT)
3288
0
        return false;
3289
0
    CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
3290
0
    const bool success = inverseCT->TransformWithErrorCodes(
3291
0
        1, &pole_x, &pole_y, nullptr, nullptr, nullptr);
3292
0
    return success && xmin < pole_x && pole_x < xmax && ymax > pole_y &&
3293
0
           pole_y > ymin;
3294
0
}
3295
3296
int OGRProjCT::TransformBounds(const double xmin, const double ymin,
3297
                               const double xmax, const double ymax,
3298
                               double *out_xmin, double *out_ymin,
3299
                               double *out_xmax, double *out_ymax,
3300
                               const int densify_pts)
3301
0
{
3302
0
    if (bNoTransform)
3303
0
    {
3304
0
        *out_xmin = xmin;
3305
0
        *out_ymin = ymin;
3306
0
        *out_xmax = xmax;
3307
0
        *out_ymax = ymax;
3308
0
        return true;
3309
0
    }
3310
3311
0
    *out_xmin = HUGE_VAL;
3312
0
    *out_ymin = HUGE_VAL;
3313
0
    *out_xmax = HUGE_VAL;
3314
0
    *out_ymax = HUGE_VAL;
3315
3316
0
    if (densify_pts < 0 || densify_pts > 10000)
3317
0
    {
3318
0
        CPLError(CE_Failure, CPLE_AppDefined,
3319
0
                 "densify_pts must be between 0-10000.");
3320
0
        return false;
3321
0
    }
3322
0
    if (!poSRSSource)
3323
0
    {
3324
0
        CPLError(CE_Failure, CPLE_AppDefined, "missing source SRS.");
3325
0
        return false;
3326
0
    }
3327
0
    if (!poSRSTarget)
3328
0
    {
3329
0
        CPLError(CE_Failure, CPLE_AppDefined, "missing target SRS.");
3330
0
        return false;
3331
0
    }
3332
3333
0
    bool degree_input = false;
3334
0
    bool degree_output = false;
3335
0
    bool input_lon_lat_order = false;
3336
0
    bool output_lon_lat_order = false;
3337
3338
0
    if (bSourceLatLong)
3339
0
    {
3340
0
        degree_input = fabs(poSRSSource->GetAngularUnits(nullptr) -
3341
0
                            CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8;
3342
0
        const auto &mapping = poSRSSource->GetDataAxisToSRSAxisMapping();
3343
0
        if ((mapping[0] == 1 && m_eSourceFirstAxisOrient == OAO_East) ||
3344
0
            (mapping[0] == 2 && m_eSourceFirstAxisOrient != OAO_East))
3345
0
        {
3346
0
            input_lon_lat_order = true;
3347
0
        }
3348
0
    }
3349
0
    if (bTargetLatLong)
3350
0
    {
3351
0
        degree_output = fabs(poSRSTarget->GetAngularUnits(nullptr) -
3352
0
                             CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8;
3353
0
        const auto &mapping = poSRSTarget->GetDataAxisToSRSAxisMapping();
3354
0
        if ((mapping[0] == 1 && m_eTargetFirstAxisOrient == OAO_East) ||
3355
0
            (mapping[0] == 2 && m_eTargetFirstAxisOrient != OAO_East))
3356
0
        {
3357
0
            output_lon_lat_order = true;
3358
0
        }
3359
0
    }
3360
3361
0
    if (degree_output && densify_pts < 2)
3362
0
    {
3363
0
        CPLError(CE_Failure, CPLE_AppDefined,
3364
0
                 "densify_pts must be at least 2 if the output is geograpic.");
3365
0
        return false;
3366
0
    }
3367
3368
0
    int side_pts = densify_pts + 1;  // add one because we are densifying
3369
0
    const int boundary_len = side_pts * 4;
3370
0
    std::vector<double> x_boundary_array;
3371
0
    std::vector<double> y_boundary_array;
3372
0
    std::vector<int> anErrorCodes;
3373
0
    try
3374
0
    {
3375
0
        x_boundary_array.resize(boundary_len);
3376
0
        y_boundary_array.resize(boundary_len);
3377
0
        anErrorCodes.resize(boundary_len);
3378
0
    }
3379
0
    catch (const std::exception &e)  // memory allocation failure
3380
0
    {
3381
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
3382
0
        return false;
3383
0
    }
3384
0
    double delta_x = 0;
3385
0
    double delta_y = 0;
3386
0
    bool north_pole_in_bounds = false;
3387
0
    bool south_pole_in_bounds = false;
3388
0
    if (degree_output)
3389
0
    {
3390
0
        north_pole_in_bounds =
3391
0
            ContainsNorthPole(xmin, ymin, xmax, ymax, output_lon_lat_order);
3392
0
        south_pole_in_bounds =
3393
0
            ContainsSouthPole(xmin, ymin, xmax, ymax, output_lon_lat_order);
3394
0
    }
3395
3396
0
    if (degree_input && xmax < xmin)
3397
0
    {
3398
0
        if (!input_lon_lat_order)
3399
0
        {
3400
0
            CPLError(CE_Failure, CPLE_AppDefined,
3401
0
                     "latitude max < latitude min.");
3402
0
            return false;
3403
0
        }
3404
        // handle antimeridian
3405
0
        delta_x = (xmax - xmin + 360.0) / side_pts;
3406
0
    }
3407
0
    else
3408
0
    {
3409
0
        delta_x = (xmax - xmin) / side_pts;
3410
0
    }
3411
0
    if (degree_input && ymax < ymin)
3412
0
    {
3413
0
        if (input_lon_lat_order)
3414
0
        {
3415
0
            CPLError(CE_Failure, CPLE_AppDefined,
3416
0
                     "latitude max < latitude min.");
3417
0
            return false;
3418
0
        }
3419
        // handle antimeridian
3420
0
        delta_y = (ymax - ymin + 360.0) / side_pts;
3421
0
    }
3422
0
    else
3423
0
    {
3424
0
        delta_y = (ymax - ymin) / side_pts;
3425
0
    }
3426
3427
    // build densified bounding box
3428
    // Note: must be a linear ring for antimeridian logic
3429
0
    for (int iii = 0; iii < side_pts; iii++)
3430
0
    {
3431
        // xmin boundary
3432
0
        y_boundary_array[iii] = ymax - iii * delta_y;
3433
0
        x_boundary_array[iii] = xmin;
3434
        // ymin boundary
3435
0
        y_boundary_array[iii + side_pts] = ymin;
3436
0
        x_boundary_array[iii + side_pts] = xmin + iii * delta_x;
3437
        // xmax boundary
3438
0
        y_boundary_array[iii + side_pts * 2] = ymin + iii * delta_y;
3439
0
        x_boundary_array[iii + side_pts * 2] = xmax;
3440
        // ymax boundary
3441
0
        y_boundary_array[iii + side_pts * 3] = ymax;
3442
0
        x_boundary_array[iii + side_pts * 3] = xmax - iii * delta_x;
3443
0
    }
3444
3445
0
    {
3446
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
3447
0
        bool success = TransformWithErrorCodes(
3448
0
            boundary_len, &x_boundary_array[0], &y_boundary_array[0], nullptr,
3449
0
            nullptr, anErrorCodes.data());
3450
0
        if (!success)
3451
0
        {
3452
0
            for (int i = 0; i < boundary_len; ++i)
3453
0
            {
3454
0
                if (anErrorCodes[i] == 0)
3455
0
                {
3456
0
                    success = true;
3457
0
                    break;
3458
0
                }
3459
0
            }
3460
0
            if (!success)
3461
0
                return false;
3462
0
        }
3463
0
    }
3464
3465
0
    if (!degree_output)
3466
0
    {
3467
0
        *out_xmin =
3468
0
            simple_min(&x_boundary_array[0], anErrorCodes.data(), boundary_len);
3469
0
        *out_xmax =
3470
0
            simple_max(&x_boundary_array[0], anErrorCodes.data(), boundary_len);
3471
0
        *out_ymin =
3472
0
            simple_min(&y_boundary_array[0], anErrorCodes.data(), boundary_len);
3473
0
        *out_ymax =
3474
0
            simple_max(&y_boundary_array[0], anErrorCodes.data(), boundary_len);
3475
3476
0
        if (poSRSTarget->IsProjected())
3477
0
        {
3478
0
            CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
3479
3480
0
            auto poBaseTarget = std::unique_ptr<OGRSpatialReference>(
3481
0
                poSRSTarget->CloneGeogCS());
3482
0
            poBaseTarget->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3483
3484
0
            auto poCTBaseTargetToSrc =
3485
0
                std::unique_ptr<OGRCoordinateTransformation>(
3486
0
                    OGRCreateCoordinateTransformation(poBaseTarget.get(),
3487
0
                                                      poSRSSource));
3488
0
            if (poCTBaseTargetToSrc)
3489
0
            {
3490
0
                const double dfLon0 =
3491
0
                    poSRSTarget->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
3492
3493
0
                double dfSignedPoleLat = 0;
3494
0
                double dfAbsPoleLat = 90;
3495
0
                bool bIncludesPole = false;
3496
0
                const char *pszProjection =
3497
0
                    poSRSTarget->GetAttrValue("PROJECTION");
3498
0
                if (pszProjection &&
3499
0
                    EQUAL(pszProjection, SRS_PT_MERCATOR_1SP) && dfLon0 == 0)
3500
0
                {
3501
                    // This MAX_LAT_MERCATOR values is equivalent to the
3502
                    // semi_major_axis * PI easting/northing value only
3503
                    // for EPSG:3857, but it is also quite
3504
                    // reasonable for other Mercator projections
3505
0
                    constexpr double MAX_LAT_MERCATOR = 85.0511287798066;
3506
0
                    dfAbsPoleLat = MAX_LAT_MERCATOR;
3507
0
                }
3508
3509
                // Detect if a point at long = central_meridian and
3510
                // lat = +/- 90deg is included in the extent.
3511
                // Helps for example for EPSG:4326 to ESRI:53037
3512
0
                for (int iSign = -1; iSign <= 1; iSign += 2)
3513
0
                {
3514
0
                    double dfX = dfLon0;
3515
0
                    constexpr double EPS = 1e-8;
3516
0
                    double dfY = iSign * (dfAbsPoleLat - EPS);
3517
3518
0
                    if (poCTBaseTargetToSrc->TransformWithErrorCodes(
3519
0
                            1, &dfX, &dfY, nullptr, nullptr, nullptr) &&
3520
0
                        dfX >= xmin && dfY >= ymin && dfX <= xmax &&
3521
0
                        dfY <= ymax &&
3522
0
                        TransformWithErrorCodes(1, &dfX, &dfY, nullptr, nullptr,
3523
0
                                                nullptr))
3524
0
                    {
3525
0
                        dfSignedPoleLat = iSign * dfAbsPoleLat;
3526
0
                        bIncludesPole = true;
3527
0
                        *out_xmin = std::min(*out_xmin, dfX);
3528
0
                        *out_ymin = std::min(*out_ymin, dfY);
3529
0
                        *out_xmax = std::max(*out_xmax, dfX);
3530
0
                        *out_ymax = std::max(*out_ymax, dfY);
3531
0
                    }
3532
0
                }
3533
3534
0
                const auto TryAtPlusMinus180 =
3535
0
                    [this, dfLon0, xmin, ymin, xmax, ymax, out_xmin, out_ymin,
3536
0
                     out_xmax, out_ymax, &poCTBaseTargetToSrc](double dfLat)
3537
0
                {
3538
0
                    for (int iSign = -1; iSign <= 1; iSign += 2)
3539
0
                    {
3540
0
                        constexpr double EPS = 1e-8;
3541
0
                        double dfX =
3542
0
                            fmod(dfLon0 + iSign * (180 - EPS) + 180, 360) - 180;
3543
0
                        double dfY = dfLat;
3544
3545
0
                        if (poCTBaseTargetToSrc->TransformWithErrorCodes(
3546
0
                                1, &dfX, &dfY, nullptr, nullptr, nullptr) &&
3547
0
                            dfX >= xmin && dfY >= ymin && dfX <= xmax &&
3548
0
                            dfY <= ymax &&
3549
0
                            TransformWithErrorCodes(1, &dfX, &dfY, nullptr,
3550
0
                                                    nullptr, nullptr))
3551
0
                        {
3552
0
                            *out_xmin = std::min(*out_xmin, dfX);
3553
0
                            *out_ymin = std::min(*out_ymin, dfY);
3554
0
                            *out_xmax = std::max(*out_xmax, dfX);
3555
0
                            *out_ymax = std::max(*out_ymax, dfY);
3556
0
                        }
3557
0
                    }
3558
0
                };
3559
3560
                // For a projected CRS with a central meridian != 0, try to
3561
                // reproject the points with long = +/- 180deg of the central
3562
                // meridian and at lat = latitude_of_origin.
3563
0
                const double dfLat0 = poSRSTarget->GetNormProjParm(
3564
0
                    SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
3565
0
                if (dfLon0 != 0)
3566
0
                {
3567
0
                    TryAtPlusMinus180(dfLat0);
3568
0
                }
3569
3570
0
                if (bIncludesPole && dfLat0 != dfSignedPoleLat)
3571
0
                {
3572
0
                    TryAtPlusMinus180(dfSignedPoleLat);
3573
0
                }
3574
0
            }
3575
0
        }
3576
0
    }
3577
0
    else if (north_pole_in_bounds && output_lon_lat_order)
3578
0
    {
3579
0
        *out_xmin = -180;
3580
0
        *out_ymin =
3581
0
            simple_min(&y_boundary_array[0], anErrorCodes.data(), boundary_len);
3582
0
        *out_xmax = 180;
3583
0
        *out_ymax = 90;
3584
0
    }
3585
0
    else if (north_pole_in_bounds)
3586
0
    {
3587
0
        *out_xmin =
3588
0
            simple_min(&x_boundary_array[0], anErrorCodes.data(), boundary_len);
3589
0
        *out_ymin = -180;
3590
0
        *out_xmax = 90;
3591
0
        *out_ymax = 180;
3592
0
    }
3593
0
    else if (south_pole_in_bounds && output_lon_lat_order)
3594
0
    {
3595
0
        *out_xmin = -180;
3596
0
        *out_ymin = -90;
3597
0
        *out_xmax = 180;
3598
0
        *out_ymax =
3599
0
            simple_max(&y_boundary_array[0], anErrorCodes.data(), boundary_len);
3600
0
    }
3601
0
    else if (south_pole_in_bounds)
3602
0
    {
3603
0
        *out_xmin = -90;
3604
0
        *out_ymin = -180;
3605
0
        *out_xmax =
3606
0
            simple_max(&x_boundary_array[0], anErrorCodes.data(), boundary_len);
3607
0
        *out_ymax = 180;
3608
0
    }
3609
0
    else if (output_lon_lat_order)
3610
0
    {
3611
0
        *out_xmin = antimeridian_min(&x_boundary_array[0], anErrorCodes.data(),
3612
0
                                     boundary_len);
3613
0
        *out_xmax = antimeridian_max(&x_boundary_array[0], anErrorCodes.data(),
3614
0
                                     boundary_len);
3615
0
        *out_ymin =
3616
0
            simple_min(&y_boundary_array[0], anErrorCodes.data(), boundary_len);
3617
0
        *out_ymax =
3618
0
            simple_max(&y_boundary_array[0], anErrorCodes.data(), boundary_len);
3619
0
    }
3620
0
    else
3621
0
    {
3622
0
        *out_xmin =
3623
0
            simple_min(&x_boundary_array[0], anErrorCodes.data(), boundary_len);
3624
0
        *out_xmax =
3625
0
            simple_max(&x_boundary_array[0], anErrorCodes.data(), boundary_len);
3626
0
        *out_ymin = antimeridian_min(&y_boundary_array[0], anErrorCodes.data(),
3627
0
                                     boundary_len);
3628
0
        *out_ymax = antimeridian_max(&y_boundary_array[0], anErrorCodes.data(),
3629
0
                                     boundary_len);
3630
0
    }
3631
3632
0
    return *out_xmin != HUGE_VAL && *out_ymin != HUGE_VAL &&
3633
0
           *out_xmax != HUGE_VAL && *out_ymax != HUGE_VAL;
3634
0
}
3635
3636
/************************************************************************/
3637
/*                               Clone()                                */
3638
/************************************************************************/
3639
3640
OGRCoordinateTransformation *OGRProjCT::Clone() const
3641
0
{
3642
0
    auto poNewCT = std::make_unique<OGRProjCT>(*this);
3643
#if (PROJ_VERSION_MAJOR * 10000 + PROJ_VERSION_MINOR * 100 +                   \
3644
     PROJ_VERSION_PATCH) < 80001
3645
    // See https://github.com/OSGeo/PROJ/pull/2582
3646
    // This may fail before PROJ 8.0.1 if the m_pj object is a "meta"
3647
    // operation being a set of real operations
3648
    bool bCloneDone = ((m_pj == nullptr) == (poNewCT->m_pj == nullptr));
3649
    if (!bCloneDone)
3650
    {
3651
        poNewCT.reset(new OGRProjCT());
3652
        auto ret =
3653
            poNewCT->Initialize(poSRSSource, m_osSrcSRS.c_str(), poSRSTarget,
3654
                                m_osTargetSRS.c_str(), m_options);
3655
        if (!ret)
3656
        {
3657
            return nullptr;
3658
        }
3659
    }
3660
#endif  // PROJ_VERSION
3661
0
    return poNewCT.release();
3662
0
}
3663
3664
/************************************************************************/
3665
/*                             GetInverse()                             */
3666
/************************************************************************/
3667
3668
OGRCoordinateTransformation *OGRProjCT::GetInverse() const
3669
0
{
3670
0
    PJ *new_pj = nullptr;
3671
    // m_pj can be nullptr if using m_eStrategy != PROJ
3672
0
    if (m_pj && !bWebMercatorToWGS84LongLat && !bNoTransform)
3673
0
    {
3674
        // See https://github.com/OSGeo/PROJ/pull/2582
3675
        // This may fail before PROJ 8.0.1 if the m_pj object is a "meta"
3676
        // operation being a set of real operations
3677
0
        new_pj = proj_clone(OSRGetProjTLSContext(), m_pj);
3678
0
    }
3679
3680
0
    OGRCoordinateTransformationOptions newOptions(m_options);
3681
0
    std::swap(newOptions.d->bHasSourceCenterLong,
3682
0
              newOptions.d->bHasTargetCenterLong);
3683
0
    std::swap(newOptions.d->dfSourceCenterLong,
3684
0
              newOptions.d->dfTargetCenterLong);
3685
0
    newOptions.d->bReverseCO = !newOptions.d->bReverseCO;
3686
0
    newOptions.d->RefreshCheckWithInvertProj();
3687
3688
0
    if (new_pj == nullptr && !bNoTransform)
3689
0
    {
3690
0
        return OGRCreateCoordinateTransformation(poSRSTarget, poSRSSource,
3691
0
                                                 newOptions);
3692
0
    }
3693
3694
0
    auto poNewCT = new OGRProjCT();
3695
3696
0
    if (poSRSTarget)
3697
0
        poNewCT->poSRSSource = poSRSTarget->Clone();
3698
0
    poNewCT->m_eSourceFirstAxisOrient = m_eTargetFirstAxisOrient;
3699
0
    poNewCT->bSourceLatLong = bTargetLatLong;
3700
0
    poNewCT->bSourceWrap = bTargetWrap;
3701
0
    poNewCT->dfSourceWrapLong = dfTargetWrapLong;
3702
0
    poNewCT->bSourceIsDynamicCRS = bTargetIsDynamicCRS;
3703
0
    poNewCT->dfSourceCoordinateEpoch = dfTargetCoordinateEpoch;
3704
0
    poNewCT->m_osSrcSRS = m_osTargetSRS;
3705
3706
0
    if (poSRSSource)
3707
0
        poNewCT->poSRSTarget = poSRSSource->Clone();
3708
0
    poNewCT->m_eTargetFirstAxisOrient = m_eSourceFirstAxisOrient;
3709
0
    poNewCT->bTargetLatLong = bSourceLatLong;
3710
0
    poNewCT->bTargetWrap = bSourceWrap;
3711
0
    poNewCT->dfTargetWrapLong = dfSourceWrapLong;
3712
0
    poNewCT->bTargetIsDynamicCRS = bSourceIsDynamicCRS;
3713
0
    poNewCT->dfTargetCoordinateEpoch = dfSourceCoordinateEpoch;
3714
0
    poNewCT->m_osTargetSRS = m_osSrcSRS;
3715
3716
0
    poNewCT->ComputeThreshold();
3717
3718
0
    poNewCT->m_pj = new_pj;
3719
0
    poNewCT->m_bReversePj = !m_bReversePj;
3720
0
    poNewCT->bNoTransform = bNoTransform;
3721
0
    poNewCT->m_eStrategy = m_eStrategy;
3722
0
    poNewCT->m_options = newOptions;
3723
3724
0
    poNewCT->DetectWebMercatorToWGS84();
3725
3726
0
    return poNewCT;
3727
0
}
3728
3729
/************************************************************************/
3730
/*                          OSRCTCleanCache()                           */
3731
/************************************************************************/
3732
3733
void OSRCTCleanCache()
3734
0
{
3735
0
    std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex);
3736
0
    delete g_poCTCache;
3737
0
    g_poCTCache = nullptr;
3738
0
}
3739
3740
/************************************************************************/
3741
/*                            MakeCacheKey()                            */
3742
/************************************************************************/
3743
3744
CTCacheKey OGRProjCT::MakeCacheKey(
3745
    const OGRSpatialReference *poSRS1, const char *pszSrcSRS,
3746
    const OGRSpatialReference *poSRS2, const char *pszTargetSRS,
3747
    const OGRCoordinateTransformationOptions &options)
3748
0
{
3749
0
    const auto GetKeyForSRS =
3750
0
        [](const OGRSpatialReference *poSRS, const char *pszText)
3751
0
    {
3752
0
        if (poSRS)
3753
0
        {
3754
0
            std::string ret(pszText);
3755
0
            const auto &mapping = poSRS->GetDataAxisToSRSAxisMapping();
3756
0
            for (const auto &axis : mapping)
3757
0
            {
3758
0
                ret += std::to_string(axis);
3759
0
            }
3760
0
            return ret;
3761
0
        }
3762
0
        else
3763
0
        {
3764
0
            return std::string("null");
3765
0
        }
3766
0
    };
3767
3768
0
    std::string ret(GetKeyForSRS(poSRS1, pszSrcSRS));
3769
0
    ret += GetKeyForSRS(poSRS2, pszTargetSRS);
3770
0
    ret += options.d->GetKey();
3771
0
    return ret;
3772
0
}
3773
3774
/************************************************************************/
3775
/*                          InsertIntoCache()                           */
3776
/************************************************************************/
3777
3778
void OGRProjCT::InsertIntoCache(OGRProjCT *poCT)
3779
0
{
3780
0
    {
3781
0
        std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex);
3782
0
        if (g_poCTCache == nullptr)
3783
0
        {
3784
0
            g_poCTCache = new lru11::Cache<CTCacheKey, CTCacheValue>();
3785
0
        }
3786
0
    }
3787
0
    const auto key = MakeCacheKey(poCT->poSRSSource, poCT->m_osSrcSRS.c_str(),
3788
0
                                  poCT->poSRSTarget,
3789
0
                                  poCT->m_osTargetSRS.c_str(), poCT->m_options);
3790
3791
0
    std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex);
3792
0
    if (g_poCTCache->contains(key))
3793
0
    {
3794
0
        delete poCT;
3795
0
        return;
3796
0
    }
3797
0
    g_poCTCache->insert(key, std::unique_ptr<OGRProjCT>(poCT));
3798
0
}
3799
3800
/************************************************************************/
3801
/*                           FindFromCache()                            */
3802
/************************************************************************/
3803
3804
OGRProjCT *OGRProjCT::FindFromCache(
3805
    const OGRSpatialReference *poSource, const char *pszSrcSRS,
3806
    const OGRSpatialReference *poTarget, const char *pszTargetSRS,
3807
    const OGRCoordinateTransformationOptions &options)
3808
0
{
3809
0
    {
3810
0
        std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex);
3811
0
        if (g_poCTCache == nullptr || g_poCTCache->empty())
3812
0
            return nullptr;
3813
0
    }
3814
3815
0
    const auto key =
3816
0
        MakeCacheKey(poSource, pszSrcSRS, poTarget, pszTargetSRS, options);
3817
    // Get value from cache and remove it
3818
0
    std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex);
3819
0
    CTCacheValue *cachedValue = g_poCTCache->getPtr(key);
3820
0
    if (cachedValue)
3821
0
    {
3822
0
        auto poCT = cachedValue->release();
3823
0
        g_poCTCache->remove(key);
3824
0
        return poCT;
3825
0
    }
3826
0
    return nullptr;
3827
0
}
3828
3829
//! @endcond
3830
3831
/************************************************************************/
3832
/*                            OCTTransform()                            */
3833
/************************************************************************/
3834
3835
/** Transform an array of points
3836
 *
3837
 * @param hTransform Transformation object
3838
 * @param nCount Number of points
3839
 * @param x Array of nCount x values.
3840
 * @param y Array of nCount y values.
3841
 * @param z Array of nCount z values.
3842
 * @return TRUE if a transformation could be found (but not all points may
3843
 * have necessarily succeed to transform), otherwise FALSE.
3844
 */
3845
int CPL_STDCALL OCTTransform(OGRCoordinateTransformationH hTransform,
3846
                             int nCount, double *x, double *y, double *z)
3847
3848
0
{
3849
0
    VALIDATE_POINTER1(hTransform, "OCTTransform", FALSE);
3850
3851
0
    return OGRCoordinateTransformation::FromHandle(hTransform)
3852
0
        ->Transform(nCount, x, y, z);
3853
0
}
3854
3855
/************************************************************************/
3856
/*                           OCTTransformEx()                           */
3857
/************************************************************************/
3858
3859
/** Transform an array of points
3860
 *
3861
 * @param hTransform Transformation object
3862
 * @param nCount Number of points
3863
 * @param x Array of nCount x values.
3864
 * @param y Array of nCount y values.
3865
 * @param z Array of nCount z values.
3866
 * @param pabSuccess Output array of nCount value that will be set to TRUE/FALSE
3867
 * @return TRUE if a transformation could be found (but not all points may
3868
 * have necessarily succeed to transform), otherwise FALSE.
3869
 */
3870
int CPL_STDCALL OCTTransformEx(OGRCoordinateTransformationH hTransform,
3871
                               int nCount, double *x, double *y, double *z,
3872
                               int *pabSuccess)
3873
3874
0
{
3875
0
    VALIDATE_POINTER1(hTransform, "OCTTransformEx", FALSE);
3876
3877
0
    return OGRCoordinateTransformation::FromHandle(hTransform)
3878
0
        ->Transform(nCount, x, y, z, pabSuccess);
3879
0
}
3880
3881
/************************************************************************/
3882
/*                           OCTTransform4D()                           */
3883
/************************************************************************/
3884
3885
/** Transform an array of points
3886
 *
3887
 * @param hTransform Transformation object
3888
 * @param nCount Number of points
3889
 * @param x Array of nCount x values. Should not be NULL
3890
 * @param y Array of nCount y values. Should not be NULL
3891
 * @param z Array of nCount z values. Might be NULL
3892
 * @param t Array of nCount time values. Might be NULL
3893
 * @param pabSuccess Output array of nCount value that will be set to
3894
 * TRUE/FALSE. Might be NULL.
3895
 * @since GDAL 3.0
3896
 * @return TRUE if a transformation could be found (but not all points may
3897
 * have necessarily succeed to transform), otherwise FALSE.
3898
 */
3899
int OCTTransform4D(OGRCoordinateTransformationH hTransform, int nCount,
3900
                   double *x, double *y, double *z, double *t, int *pabSuccess)
3901
3902
0
{
3903
0
    VALIDATE_POINTER1(hTransform, "OCTTransform4D", FALSE);
3904
3905
0
    return OGRCoordinateTransformation::FromHandle(hTransform)
3906
0
        ->Transform(nCount, x, y, z, t, pabSuccess);
3907
0
}
3908
3909
/************************************************************************/
3910
/*                    OCTTransform4DWithErrorCodes()                    */
3911
/************************************************************************/
3912
3913
/** Transform an array of points
3914
 *
3915
 * @param hTransform Transformation object
3916
 * @param nCount Number of points
3917
 * @param x Array of nCount x values. Should not be NULL
3918
 * @param y Array of nCount y values. Should not be NULL
3919
 * @param z Array of nCount z values. Might be NULL
3920
 * @param t Array of nCount time values. Might be NULL
3921
 * @param panErrorCodes Output array of nCount value that will be set to 0 for
3922
 *                      success, or a non-zero value for failure. Refer to
3923
 *                      PROJ 8 public error codes. Might be NULL
3924
 * @since GDAL 3.3, and PROJ 8 to be able to use PROJ public error codes
3925
 * @return TRUE if a transformation could be found (but not all points may
3926
 * have necessarily succeed to transform), otherwise FALSE.
3927
 */
3928
int OCTTransform4DWithErrorCodes(OGRCoordinateTransformationH hTransform,
3929
                                 int nCount, double *x, double *y, double *z,
3930
                                 double *t, int *panErrorCodes)
3931
3932
0
{
3933
0
    VALIDATE_POINTER1(hTransform, "OCTTransform4DWithErrorCodes", FALSE);
3934
3935
0
    return OGRCoordinateTransformation::FromHandle(hTransform)
3936
0
        ->TransformWithErrorCodes(nCount, x, y, z, t, panErrorCodes);
3937
0
}
3938
3939
/************************************************************************/
3940
/*                         OCTTransformBounds()                         */
3941
/************************************************************************/
3942
/** \brief Transform boundary.
3943
 *
3944
 * Transform boundary densifying the edges to account for nonlinear
3945
 * transformations along these edges and extracting the outermost bounds.
3946
 *
3947
 * If the destination CRS is geographic, the first axis is longitude,
3948
 * and *out_xmax < *out_xmin then the bounds crossed the antimeridian.
3949
 * In this scenario there are two polygons, one on each side of the
3950
 * antimeridian. The first polygon should be constructed with
3951
 * (*out_xmin, *out_ymin, 180, ymax) and the second with
3952
 * (-180, *out_ymin, *out_xmax, *out_ymax).
3953
 *
3954
 * If the destination CRS is geographic, the first axis is latitude,
3955
 * and *out_ymax < *out_ymin then the bounds crossed the antimeridian.
3956
 * In this scenario there are two polygons, one on each side of the
3957
 * antimeridian. The first polygon should be constructed with
3958
 * (*out_ymin, *out_xmin, *out_ymax, 180) and the second with
3959
 * (*out_ymin, -180, *out_ymax, *out_xmax).
3960
 *
3961
 * @param hTransform Transformation object
3962
 * @param xmin Minimum bounding coordinate of the first axis in source CRS.
3963
 * @param ymin Minimum bounding coordinate of the second axis in source CRS.
3964
 * @param xmax Maximum bounding coordinate of the first axis in source CRS.
3965
 * @param ymax Maximum bounding coordinate of the second axis in source CRS.
3966
 * @param out_xmin Minimum bounding coordinate of the first axis in target CRS
3967
 * @param out_ymin Minimum bounding coordinate of the second axis in target CRS.
3968
 * @param out_xmax Maximum bounding coordinate of the first axis in target CRS.
3969
 * @param out_ymax Maximum bounding coordinate of the second axis in target CRS.
3970
 * @param densify_pts Recommended to use 21. This is the number of points
3971
 *     to use to densify the bounding polygon in the transformation.
3972
 * @return TRUE if successful. FALSE if failures encountered.
3973
 * @since 3.4
3974
 */
3975
int CPL_STDCALL OCTTransformBounds(OGRCoordinateTransformationH hTransform,
3976
                                   const double xmin, const double ymin,
3977
                                   const double xmax, const double ymax,
3978
                                   double *out_xmin, double *out_ymin,
3979
                                   double *out_xmax, double *out_ymax,
3980
                                   int densify_pts)
3981
3982
0
{
3983
0
    VALIDATE_POINTER1(hTransform, "TransformBounds", FALSE);
3984
3985
0
    return OGRProjCT::FromHandle(hTransform)
3986
0
        ->TransformBounds(xmin, ymin, xmax, ymax, out_xmin, out_ymin, out_xmax,
3987
0
                          out_ymax, densify_pts);
3988
0
}
3989
3990
/************************************************************************/
3991
/*                        OGRCTDumpStatistics()                         */
3992
/************************************************************************/
3993
3994
void OGRCTDumpStatistics()
3995
0
{
3996
#ifdef DEBUG_PERF
3997
    CPLDebug("OGR_CT", "Total time in proj_create_crs_to_crs(): %d ms",
3998
             static_cast<int>(g_dfTotalTimeCRStoCRS * 1000));
3999
    CPLDebug("OGR_CT", "Total time in coordinate transformation: %d ms",
4000
             static_cast<int>(g_dfTotalTimeReprojection * 1000));
4001
#endif
4002
0
}