Coverage Report

Created: 2025-06-13 06:18

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