Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrspatialreference.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  The OGRSpatialReference class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999,  Les Technologies SoftMap Inc.
9
 * Copyright (c) 2008-2018, Even Rouault <even.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 <cmath>
18
#include <cstddef>
19
#include <cstdio>
20
#include <cstdlib>
21
#include <cstring>
22
#include <limits>
23
#include <string>
24
#include <mutex>
25
#include <set>
26
#include <vector>
27
28
#include "cpl_atomic_ops.h"
29
#include "cpl_conv.h"
30
#include "cpl_csv.h"
31
#include "cpl_error.h"
32
#include "cpl_error_internal.h"
33
#include "cpl_http.h"
34
#include "cpl_json.h"
35
#include "cpl_multiproc.h"
36
#include "cpl_string.h"
37
#include "cpl_vsi.h"
38
#include "ogr_core.h"
39
#include "ogr_p.h"
40
#include "ogr_proj_p.h"
41
#include "ogr_srs_api.h"
42
#include "ogrmitabspatialref.h"
43
44
#include "proj.h"
45
#include "proj_experimental.h"
46
#include "proj_constants.h"
47
48
bool GDALThreadLocalDatasetCacheIsInDestruction();
49
50
// Exists since 8.0.1
51
#ifndef PROJ_AT_LEAST_VERSION
52
#define PROJ_COMPUTE_VERSION(maj, min, patch)                                  \
53
    ((maj) * 10000 + (min) * 100 + (patch))
54
#define PROJ_VERSION_NUMBER                                                    \
55
    PROJ_COMPUTE_VERSION(PROJ_VERSION_MAJOR, PROJ_VERSION_MINOR,               \
56
                         PROJ_VERSION_PATCH)
57
#define PROJ_AT_LEAST_VERSION(maj, min, patch)                                 \
58
    (PROJ_VERSION_NUMBER >= PROJ_COMPUTE_VERSION(maj, min, patch))
59
#endif
60
61
22.7k
#define STRINGIFY(s) #s
62
22.7k
#define XSTRINGIFY(s) STRINGIFY(s)
63
64
struct OGRSpatialReference::Private
65
{
66
    struct Listener final : public OGR_SRSNode::Listener
67
    {
68
        OGRSpatialReference::Private *m_poObj = nullptr;
69
70
2.95M
        explicit Listener(OGRSpatialReference::Private *poObj) : m_poObj(poObj)
71
2.95M
        {
72
2.95M
        }
73
74
        Listener(const Listener &) = delete;
75
        Listener &operator=(const Listener &) = delete;
76
77
        void notifyChange(OGR_SRSNode *) override;
78
    };
79
80
    OGRSpatialReference *m_poSelf = nullptr;
81
    PJ *m_pj_crs = nullptr;
82
83
    // Temporary state used for object construction
84
    PJ_TYPE m_pjType = PJ_TYPE_UNKNOWN;
85
    CPLString m_osPrimeMeridianName{};
86
    CPLString m_osAngularUnits{};
87
    CPLString m_osLinearUnits{};
88
    CPLString m_osAxisName[3]{};
89
90
    std::vector<std::string> m_wktImportWarnings{};
91
    std::vector<std::string> m_wktImportErrors{};
92
    CPLString m_osAreaName{};
93
    CPLString m_celestialBodyName{};
94
95
    bool m_bIsThreadSafe = false;
96
    bool m_bNodesChanged = false;
97
    bool m_bNodesWKT2 = false;
98
    OGR_SRSNode *m_poRoot = nullptr;
99
100
    double dfFromGreenwich = 0.0;
101
    double dfToMeter = 0.0;
102
    double dfToDegrees = 0.0;
103
    double m_dfAngularUnitToRadian = 0.0;
104
105
    int nRefCount = 1;
106
    int bNormInfoSet = FALSE;
107
108
    PJ *m_pj_geod_base_crs_temp = nullptr;
109
    PJ *m_pj_proj_crs_cs_temp = nullptr;
110
111
    bool m_pj_crs_modified_during_demote = false;
112
    PJ *m_pj_bound_crs_target = nullptr;
113
    PJ *m_pj_bound_crs_co = nullptr;
114
    PJ *m_pj_crs_backup = nullptr;
115
    OGR_SRSNode *m_poRootBackup = nullptr;
116
117
    bool m_bMorphToESRI = false;
118
    bool m_bHasCenterLong = false;
119
120
    std::shared_ptr<Listener> m_poListener{};
121
122
    std::recursive_mutex m_mutex{};
123
124
    OSRAxisMappingStrategy m_axisMappingStrategy = OAMS_AUTHORITY_COMPLIANT;
125
    std::vector<int> m_axisMapping{1, 2, 3};
126
127
    double m_coordinateEpoch = 0;  // as decimal year
128
129
    explicit Private(OGRSpatialReference *poSelf);
130
    ~Private();
131
    Private(const Private &) = delete;
132
    Private &operator=(const Private &) = delete;
133
134
    void SetThreadSafe()
135
0
    {
136
0
        m_bIsThreadSafe = true;
137
0
    }
138
139
    void clear();
140
    void setPjCRS(PJ *pj_crsIn, bool doRefreshAxisMapping = true);
141
    void setRoot(OGR_SRSNode *poRoot);
142
    void refreshProjObj();
143
    void nodesChanged();
144
    void refreshRootFromProjObj();
145
    void invalidateNodes();
146
147
    void setMorphToESRI(bool b);
148
149
    PJ *getGeodBaseCRS();
150
    PJ *getProjCRSCoordSys();
151
152
    const char *getProjCRSName();
153
    OGRErr replaceConversionAndUnref(PJ *conv);
154
155
    void demoteFromBoundCRS();
156
    void undoDemoteFromBoundCRS();
157
158
    PJ_CONTEXT *getPROJContext()
159
13.8M
    {
160
13.8M
        return OSRGetProjTLSContext();
161
13.8M
    }
162
163
    const char *nullifyTargetKeyIfPossible(const char *pszTargetKey);
164
165
    void refreshAxisMapping();
166
167
    // This structures enables locking during calls to OGRSpatialReference
168
    // public methods. Locking is only needed for instances of
169
    // OGRSpatialReference that have been asked to be thread-safe at
170
    // construction.
171
    // The lock is not just for a single call to OGRSpatialReference::Private,
172
    // but for the series of calls done by a OGRSpatialReference method.
173
    // We need a recursive mutex, because some OGRSpatialReference methods
174
    // may call other ones.
175
    struct OptionalLockGuard
176
    {
177
        Private &m_private;
178
179
8.07M
        explicit OptionalLockGuard(Private *p) : m_private(*p)
180
8.07M
        {
181
8.07M
            if (m_private.m_bIsThreadSafe)
182
0
                m_private.m_mutex.lock();
183
8.07M
        }
184
185
        ~OptionalLockGuard()
186
8.07M
        {
187
8.07M
            if (m_private.m_bIsThreadSafe)
188
0
                m_private.m_mutex.unlock();
189
8.07M
        }
190
    };
191
192
    inline OptionalLockGuard GetOptionalLockGuard()
193
8.07M
    {
194
8.07M
        return OptionalLockGuard(this);
195
8.07M
    }
196
};
197
198
void OGRSpatialReference::Private::Listener::notifyChange(OGR_SRSNode *)
199
30.1M
{
200
30.1M
    m_poObj->nodesChanged();
201
30.1M
}
202
203
#define TAKE_OPTIONAL_LOCK()                                                   \
204
8.07M
    auto lock = d->GetOptionalLockGuard();                                     \
205
8.07M
    CPL_IGNORE_RET_VAL(lock)
206
207
static OSRAxisMappingStrategy GetDefaultAxisMappingStrategy()
208
2.95M
{
209
2.95M
    const char *pszDefaultAMS =
210
2.95M
        CPLGetConfigOption("OSR_DEFAULT_AXIS_MAPPING_STRATEGY", nullptr);
211
2.95M
    if (pszDefaultAMS)
212
0
    {
213
0
        if (EQUAL(pszDefaultAMS, "AUTHORITY_COMPLIANT"))
214
0
            return OAMS_AUTHORITY_COMPLIANT;
215
0
        else if (EQUAL(pszDefaultAMS, "TRADITIONAL_GIS_ORDER"))
216
0
            return OAMS_TRADITIONAL_GIS_ORDER;
217
0
        else
218
0
        {
219
0
            CPLError(CE_Failure, CPLE_AppDefined,
220
0
                     "Illegal value for OSR_DEFAULT_AXIS_MAPPING_STRATEGY = %s",
221
0
                     pszDefaultAMS);
222
0
        }
223
0
    }
224
2.95M
    return OAMS_AUTHORITY_COMPLIANT;
225
2.95M
}
226
227
OGRSpatialReference::Private::Private(OGRSpatialReference *poSelf)
228
2.95M
    : m_poSelf(poSelf), m_poListener(std::make_shared<Listener>(this))
229
2.95M
{
230
    // Get the default value for m_axisMappingStrategy from the
231
    // OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration option, if set.
232
2.95M
    m_axisMappingStrategy = GetDefaultAxisMappingStrategy();
233
2.95M
}
234
235
OGRSpatialReference::Private::~Private()
236
2.95M
{
237
    // In case we destroy the object not in the thread that created it,
238
    // we need to reassign the PROJ context. Having the context bundled inside
239
    // PJ* deeply sucks...
240
2.95M
    PJ_CONTEXT *pj_context_to_destroy = nullptr;
241
2.95M
    PJ_CONTEXT *ctxt;
242
2.95M
    if (GDALThreadLocalDatasetCacheIsInDestruction())
243
0
    {
244
0
        pj_context_to_destroy = proj_context_create();
245
0
        ctxt = pj_context_to_destroy;
246
0
    }
247
2.95M
    else
248
2.95M
    {
249
2.95M
        ctxt = getPROJContext();
250
2.95M
    }
251
252
2.95M
    proj_assign_context(m_pj_crs, ctxt);
253
2.95M
    proj_destroy(m_pj_crs);
254
255
2.95M
    proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
256
2.95M
    proj_destroy(m_pj_geod_base_crs_temp);
257
258
2.95M
    proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
259
2.95M
    proj_destroy(m_pj_proj_crs_cs_temp);
260
261
2.95M
    proj_assign_context(m_pj_bound_crs_target, ctxt);
262
2.95M
    proj_destroy(m_pj_bound_crs_target);
263
264
2.95M
    proj_assign_context(m_pj_bound_crs_co, ctxt);
265
2.95M
    proj_destroy(m_pj_bound_crs_co);
266
267
2.95M
    proj_assign_context(m_pj_crs_backup, ctxt);
268
2.95M
    proj_destroy(m_pj_crs_backup);
269
270
2.95M
    delete m_poRootBackup;
271
2.95M
    delete m_poRoot;
272
2.95M
    proj_context_destroy(pj_context_to_destroy);
273
2.95M
}
274
275
void OGRSpatialReference::Private::clear()
276
1.21M
{
277
1.21M
    proj_assign_context(m_pj_crs, getPROJContext());
278
1.21M
    proj_destroy(m_pj_crs);
279
1.21M
    m_pj_crs = nullptr;
280
281
1.21M
    delete m_poRoot;
282
1.21M
    m_poRoot = nullptr;
283
1.21M
    m_bNodesChanged = false;
284
285
1.21M
    m_wktImportWarnings.clear();
286
1.21M
    m_wktImportErrors.clear();
287
288
1.21M
    m_pj_crs_modified_during_demote = false;
289
1.21M
    m_pjType = PJ_TYPE_UNKNOWN;
290
1.21M
    m_osPrimeMeridianName.clear();
291
1.21M
    m_osAngularUnits.clear();
292
1.21M
    m_osLinearUnits.clear();
293
294
1.21M
    bNormInfoSet = FALSE;
295
1.21M
    dfFromGreenwich = 1.0;
296
1.21M
    dfToMeter = 1.0;
297
1.21M
    dfToDegrees = 1.0;
298
1.21M
    m_dfAngularUnitToRadian = 0.0;
299
300
1.21M
    m_bMorphToESRI = false;
301
1.21M
    m_bHasCenterLong = false;
302
303
1.21M
    m_coordinateEpoch = 0.0;
304
1.21M
}
305
306
void OGRSpatialReference::Private::setRoot(OGR_SRSNode *poRoot)
307
371k
{
308
371k
    m_poRoot = poRoot;
309
371k
    if (m_poRoot)
310
371k
    {
311
371k
        m_poRoot->RegisterListener(m_poListener);
312
371k
    }
313
371k
    nodesChanged();
314
371k
}
315
316
void OGRSpatialReference::Private::setPjCRS(PJ *pj_crsIn,
317
                                            bool doRefreshAxisMapping)
318
2.56M
{
319
2.56M
    auto ctxt = getPROJContext();
320
321
2.56M
#if PROJ_AT_LEAST_VERSION(9, 2, 0)
322
2.56M
    if (proj_get_type(pj_crsIn) == PJ_TYPE_COORDINATE_METADATA)
323
1.22k
    {
324
1.22k
        const double dfEpoch =
325
1.22k
            proj_coordinate_metadata_get_epoch(ctxt, pj_crsIn);
326
1.22k
        if (!std::isnan(dfEpoch))
327
1.11k
        {
328
1.11k
            m_poSelf->SetCoordinateEpoch(dfEpoch);
329
1.11k
        }
330
1.22k
        auto crs = proj_get_source_crs(ctxt, pj_crsIn);
331
1.22k
        proj_destroy(pj_crsIn);
332
1.22k
        pj_crsIn = crs;
333
1.22k
    }
334
2.56M
#endif
335
336
2.56M
    proj_assign_context(m_pj_crs, ctxt);
337
2.56M
    proj_destroy(m_pj_crs);
338
2.56M
    m_pj_crs = pj_crsIn;
339
2.56M
    if (m_pj_crs)
340
2.29M
    {
341
2.29M
        m_pjType = proj_get_type(m_pj_crs);
342
2.29M
    }
343
2.56M
    if (m_pj_crs_backup)
344
3.51k
    {
345
3.51k
        m_pj_crs_modified_during_demote = true;
346
3.51k
    }
347
2.56M
    invalidateNodes();
348
2.56M
    if (doRefreshAxisMapping)
349
2.56M
    {
350
2.56M
        refreshAxisMapping();
351
2.56M
    }
352
2.56M
}
353
354
void OGRSpatialReference::Private::refreshProjObj()
355
3.74M
{
356
3.74M
    if (m_bNodesChanged && m_poRoot)
357
106k
    {
358
106k
        char *pszWKT = nullptr;
359
106k
        m_poRoot->exportToWkt(&pszWKT);
360
106k
        auto poRootBackup = m_poRoot;
361
106k
        m_poRoot = nullptr;
362
106k
        const double dfCoordinateEpochBackup = m_coordinateEpoch;
363
106k
        clear();
364
106k
        m_coordinateEpoch = dfCoordinateEpochBackup;
365
106k
        m_bHasCenterLong = strstr(pszWKT, "CENTER_LONG") != nullptr;
366
367
106k
        const char *const options[] = {
368
106k
            "STRICT=NO",
369
106k
#if PROJ_AT_LEAST_VERSION(9, 1, 0)
370
106k
            "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=NO",
371
106k
#endif
372
106k
            nullptr};
373
106k
        PROJ_STRING_LIST warnings = nullptr;
374
106k
        PROJ_STRING_LIST errors = nullptr;
375
106k
        setPjCRS(proj_create_from_wkt(getPROJContext(), pszWKT, options,
376
106k
                                      &warnings, &errors));
377
245k
        for (auto iter = warnings; iter && *iter; ++iter)
378
139k
        {
379
139k
            m_wktImportWarnings.push_back(*iter);
380
139k
        }
381
164k
        for (auto iter = errors; iter && *iter; ++iter)
382
58.1k
        {
383
58.1k
            m_wktImportErrors.push_back(*iter);
384
58.1k
        }
385
106k
        proj_string_list_destroy(warnings);
386
106k
        proj_string_list_destroy(errors);
387
388
106k
        CPLFree(pszWKT);
389
390
106k
        m_poRoot = poRootBackup;
391
106k
        m_bNodesChanged = false;
392
106k
    }
393
3.74M
}
394
395
void OGRSpatialReference::Private::refreshRootFromProjObj()
396
407k
{
397
407k
    CPLAssert(m_poRoot == nullptr);
398
399
407k
    if (m_pj_crs)
400
343k
    {
401
343k
        CPLStringList aosOptions;
402
343k
        if (!m_bMorphToESRI)
403
343k
        {
404
343k
            aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
405
343k
            aosOptions.SetNameValue("MULTILINE", "NO");
406
343k
        }
407
343k
        aosOptions.SetNameValue("STRICT", "NO");
408
409
343k
        const char *pszWKT;
410
343k
        {
411
343k
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
412
343k
            pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs,
413
343k
                                 m_bMorphToESRI ? PJ_WKT1_ESRI : PJ_WKT1_GDAL,
414
343k
                                 aosOptions.List());
415
343k
            m_bNodesWKT2 = false;
416
343k
        }
417
343k
        if (!m_bMorphToESRI && pszWKT == nullptr)
418
39.5k
        {
419
39.5k
            pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs, PJ_WKT2_2018,
420
39.5k
                                 aosOptions.List());
421
39.5k
            m_bNodesWKT2 = true;
422
39.5k
        }
423
343k
        if (pszWKT)
424
343k
        {
425
343k
            auto root = new OGR_SRSNode();
426
343k
            setRoot(root);
427
343k
            root->importFromWkt(&pszWKT);
428
343k
            m_bNodesChanged = false;
429
343k
        }
430
343k
    }
431
407k
}
432
433
static bool isNorthEastAxisOrder(PJ_CONTEXT *ctx, PJ *cs)
434
2.34M
{
435
2.34M
    const char *pszName1 = nullptr;
436
2.34M
    const char *pszDirection1 = nullptr;
437
2.34M
    proj_cs_get_axis_info(ctx, cs, 0, &pszName1, nullptr, &pszDirection1,
438
2.34M
                          nullptr, nullptr, nullptr, nullptr);
439
2.34M
    const char *pszName2 = nullptr;
440
2.34M
    const char *pszDirection2 = nullptr;
441
2.34M
    proj_cs_get_axis_info(ctx, cs, 1, &pszName2, nullptr, &pszDirection2,
442
2.34M
                          nullptr, nullptr, nullptr, nullptr);
443
2.34M
    if (pszDirection1 && EQUAL(pszDirection1, "north") && pszDirection2 &&
444
965k
        EQUAL(pszDirection2, "east"))
445
935k
    {
446
935k
        return true;
447
935k
    }
448
1.40M
    if (pszDirection1 && pszDirection2 &&
449
1.40M
        ((EQUAL(pszDirection1, "north") && EQUAL(pszDirection2, "north")) ||
450
1.38M
         (EQUAL(pszDirection1, "south") && EQUAL(pszDirection2, "south"))) &&
451
1.40M
        pszName1 && STARTS_WITH_CI(pszName1, "northing") && pszName2 &&
452
1.65k
        STARTS_WITH_CI(pszName2, "easting"))
453
1.64k
    {
454
1.64k
        return true;
455
1.64k
    }
456
1.40M
    return false;
457
1.40M
}
458
459
void OGRSpatialReference::Private::refreshAxisMapping()
460
4.18M
{
461
4.18M
    if (!m_pj_crs || m_axisMappingStrategy == OAMS_CUSTOM)
462
1.72M
        return;
463
464
2.46M
    bool doUndoDemote = false;
465
2.46M
    if (m_pj_crs_backup == nullptr)
466
2.45M
    {
467
2.45M
        doUndoDemote = true;
468
2.45M
        demoteFromBoundCRS();
469
2.45M
    }
470
2.46M
    const auto ctxt = getPROJContext();
471
2.46M
    PJ *horizCRS = nullptr;
472
2.46M
    int axisCount = 0;
473
2.46M
    if (m_pjType == PJ_TYPE_VERTICAL_CRS)
474
11.1k
    {
475
11.1k
        axisCount = 1;
476
11.1k
    }
477
2.45M
    else if (m_pjType == PJ_TYPE_COMPOUND_CRS)
478
276k
    {
479
276k
        horizCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 0);
480
276k
        if (horizCRS && proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
481
7.84k
        {
482
7.84k
            auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
483
7.84k
            if (baseCRS)
484
7.84k
            {
485
7.84k
                proj_destroy(horizCRS);
486
7.84k
                horizCRS = baseCRS;
487
7.84k
            }
488
7.84k
        }
489
490
276k
        auto vertCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 1);
491
276k
        if (vertCRS)
492
276k
        {
493
276k
            if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
494
7.89k
            {
495
7.89k
                auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
496
7.89k
                if (baseCRS)
497
7.89k
                {
498
7.89k
                    proj_destroy(vertCRS);
499
7.89k
                    vertCRS = baseCRS;
500
7.89k
                }
501
7.89k
            }
502
503
276k
            auto cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
504
276k
            if (cs)
505
276k
            {
506
276k
                axisCount += proj_cs_get_axis_count(ctxt, cs);
507
276k
                proj_destroy(cs);
508
276k
            }
509
276k
            proj_destroy(vertCRS);
510
276k
        }
511
276k
    }
512
2.17M
    else
513
2.17M
    {
514
2.17M
        horizCRS = m_pj_crs;
515
2.17M
    }
516
517
2.46M
    bool bSwitchForGisFriendlyOrder = false;
518
2.46M
    if (horizCRS)
519
2.45M
    {
520
2.45M
        auto cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
521
2.45M
        if (cs)
522
2.34M
        {
523
2.34M
            int nHorizCSAxisCount = proj_cs_get_axis_count(ctxt, cs);
524
2.34M
            axisCount += nHorizCSAxisCount;
525
2.34M
            if (nHorizCSAxisCount >= 2)
526
2.34M
            {
527
2.34M
                bSwitchForGisFriendlyOrder = isNorthEastAxisOrder(ctxt, cs);
528
2.34M
            }
529
2.34M
            proj_destroy(cs);
530
2.34M
        }
531
2.45M
    }
532
2.46M
    if (horizCRS != m_pj_crs)
533
287k
    {
534
287k
        proj_destroy(horizCRS);
535
287k
    }
536
2.46M
    if (doUndoDemote)
537
2.45M
    {
538
2.45M
        undoDemoteFromBoundCRS();
539
2.45M
    }
540
541
2.46M
    m_axisMapping.resize(axisCount);
542
2.46M
    if (m_axisMappingStrategy == OAMS_AUTHORITY_COMPLIANT ||
543
895k
        !bSwitchForGisFriendlyOrder)
544
2.19M
    {
545
6.71M
        for (int i = 0; i < axisCount; i++)
546
4.52M
        {
547
4.52M
            m_axisMapping[i] = i + 1;
548
4.52M
        }
549
2.19M
    }
550
270k
    else
551
270k
    {
552
270k
        m_axisMapping[0] = 2;
553
270k
        m_axisMapping[1] = 1;
554
270k
        if (axisCount == 3)
555
14.4k
        {
556
14.4k
            m_axisMapping[2] = 3;
557
14.4k
        }
558
270k
    }
559
2.46M
}
560
561
void OGRSpatialReference::Private::nodesChanged()
562
30.5M
{
563
30.5M
    m_bNodesChanged = true;
564
30.5M
}
565
566
void OGRSpatialReference::Private::invalidateNodes()
567
2.57M
{
568
2.57M
    delete m_poRoot;
569
2.57M
    m_poRoot = nullptr;
570
2.57M
    m_bNodesChanged = false;
571
2.57M
}
572
573
void OGRSpatialReference::Private::setMorphToESRI(bool b)
574
2.46k
{
575
2.46k
    invalidateNodes();
576
2.46k
    m_bMorphToESRI = b;
577
2.46k
}
578
579
void OGRSpatialReference::Private::demoteFromBoundCRS()
580
4.42M
{
581
4.42M
    CPLAssert(m_pj_bound_crs_target == nullptr);
582
4.42M
    CPLAssert(m_pj_bound_crs_co == nullptr);
583
4.42M
    CPLAssert(m_poRootBackup == nullptr);
584
4.42M
    CPLAssert(m_pj_crs_backup == nullptr);
585
586
4.42M
    m_pj_crs_modified_during_demote = false;
587
588
4.42M
    if (m_pjType == PJ_TYPE_BOUND_CRS)
589
107k
    {
590
107k
        auto baseCRS = proj_get_source_crs(getPROJContext(), m_pj_crs);
591
107k
        m_pj_bound_crs_target = proj_get_target_crs(getPROJContext(), m_pj_crs);
592
107k
        m_pj_bound_crs_co =
593
107k
            proj_crs_get_coordoperation(getPROJContext(), m_pj_crs);
594
595
107k
        m_poRootBackup = m_poRoot;
596
107k
        m_poRoot = nullptr;
597
107k
        m_pj_crs_backup = m_pj_crs;
598
107k
        m_pj_crs = baseCRS;
599
107k
        m_pjType = proj_get_type(m_pj_crs);
600
107k
    }
601
4.42M
}
602
603
void OGRSpatialReference::Private::undoDemoteFromBoundCRS()
604
4.42M
{
605
4.42M
    if (m_pj_bound_crs_target)
606
107k
    {
607
107k
        CPLAssert(m_poRoot == nullptr);
608
107k
        CPLAssert(m_pj_crs);
609
107k
        if (!m_pj_crs_modified_during_demote)
610
104k
        {
611
104k
            proj_destroy(m_pj_crs);
612
104k
            m_pj_crs = m_pj_crs_backup;
613
104k
            m_pjType = proj_get_type(m_pj_crs);
614
104k
            m_poRoot = m_poRootBackup;
615
104k
        }
616
3.27k
        else
617
3.27k
        {
618
3.27k
            delete m_poRootBackup;
619
3.27k
            m_poRootBackup = nullptr;
620
3.27k
            proj_destroy(m_pj_crs_backup);
621
3.27k
            m_pj_crs_backup = nullptr;
622
3.27k
            setPjCRS(proj_crs_create_bound_crs(getPROJContext(), m_pj_crs,
623
3.27k
                                               m_pj_bound_crs_target,
624
3.27k
                                               m_pj_bound_crs_co),
625
3.27k
                     false);
626
3.27k
        }
627
107k
    }
628
629
4.42M
    m_poRootBackup = nullptr;
630
4.42M
    m_pj_crs_backup = nullptr;
631
4.42M
    proj_destroy(m_pj_bound_crs_target);
632
4.42M
    m_pj_bound_crs_target = nullptr;
633
4.42M
    proj_destroy(m_pj_bound_crs_co);
634
4.42M
    m_pj_bound_crs_co = nullptr;
635
4.42M
    m_pj_crs_modified_during_demote = false;
636
4.42M
}
637
638
const char *OGRSpatialReference::Private::nullifyTargetKeyIfPossible(
639
    const char *pszTargetKey)
640
633k
{
641
633k
    if (pszTargetKey)
642
112k
    {
643
112k
        demoteFromBoundCRS();
644
112k
        if ((m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
645
105k
             m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS) &&
646
7.01k
            EQUAL(pszTargetKey, "GEOGCS"))
647
3.21k
        {
648
3.21k
            pszTargetKey = nullptr;
649
3.21k
        }
650
109k
        else if (m_pjType == PJ_TYPE_GEOCENTRIC_CRS &&
651
4.96k
                 EQUAL(pszTargetKey, "GEOCCS"))
652
28
        {
653
28
            pszTargetKey = nullptr;
654
28
        }
655
109k
        else if (m_pjType == PJ_TYPE_PROJECTED_CRS &&
656
53.2k
                 EQUAL(pszTargetKey, "PROJCS"))
657
13.8k
        {
658
13.8k
            pszTargetKey = nullptr;
659
13.8k
        }
660
95.1k
        else if (m_pjType == PJ_TYPE_VERTICAL_CRS &&
661
642
                 EQUAL(pszTargetKey, "VERT_CS"))
662
46
        {
663
46
            pszTargetKey = nullptr;
664
46
        }
665
112k
        undoDemoteFromBoundCRS();
666
112k
    }
667
633k
    return pszTargetKey;
668
633k
}
669
670
PJ *OGRSpatialReference::Private::getGeodBaseCRS()
671
113k
{
672
113k
    if (m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
673
111k
        m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
674
1.28k
    {
675
1.28k
        return m_pj_crs;
676
1.28k
    }
677
678
111k
    auto ctxt = getPROJContext();
679
111k
    if (m_pjType == PJ_TYPE_PROJECTED_CRS)
680
21.5k
    {
681
21.5k
        proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
682
21.5k
        proj_destroy(m_pj_geod_base_crs_temp);
683
21.5k
        m_pj_geod_base_crs_temp = proj_crs_get_geodetic_crs(ctxt, m_pj_crs);
684
21.5k
        return m_pj_geod_base_crs_temp;
685
21.5k
    }
686
687
90.2k
    proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
688
90.2k
    proj_destroy(m_pj_geod_base_crs_temp);
689
90.2k
    auto cs = proj_create_ellipsoidal_2D_cs(ctxt, PJ_ELLPS2D_LATITUDE_LONGITUDE,
690
90.2k
                                            nullptr, 0);
691
90.2k
    m_pj_geod_base_crs_temp = proj_create_geographic_crs(
692
90.2k
        ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84",
693
90.2k
        SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING, SRS_PM_GREENWICH, 0.0,
694
90.2k
        SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV), cs);
695
90.2k
    proj_destroy(cs);
696
697
90.2k
    return m_pj_geod_base_crs_temp;
698
111k
}
699
700
PJ *OGRSpatialReference::Private::getProjCRSCoordSys()
701
69.5k
{
702
69.5k
    auto ctxt = getPROJContext();
703
69.5k
    if (m_pjType == PJ_TYPE_PROJECTED_CRS)
704
20.8k
    {
705
20.8k
        proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
706
20.8k
        proj_destroy(m_pj_proj_crs_cs_temp);
707
20.8k
        m_pj_proj_crs_cs_temp =
708
20.8k
            proj_crs_get_coordinate_system(getPROJContext(), m_pj_crs);
709
20.8k
        return m_pj_proj_crs_cs_temp;
710
20.8k
    }
711
712
48.6k
    proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
713
48.6k
    proj_destroy(m_pj_proj_crs_cs_temp);
714
48.6k
    m_pj_proj_crs_cs_temp = proj_create_cartesian_2D_cs(
715
48.6k
        ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0);
716
48.6k
    return m_pj_proj_crs_cs_temp;
717
69.5k
}
718
719
const char *OGRSpatialReference::Private::getProjCRSName()
720
81.9k
{
721
81.9k
    if (m_pjType == PJ_TYPE_PROJECTED_CRS)
722
21.5k
    {
723
21.5k
        return proj_get_name(m_pj_crs);
724
21.5k
    }
725
726
60.3k
    return "unnamed";
727
81.9k
}
728
729
OGRErr OGRSpatialReference::Private::replaceConversionAndUnref(PJ *conv)
730
57.7k
{
731
57.7k
    refreshProjObj();
732
733
57.7k
    demoteFromBoundCRS();
734
735
57.7k
    auto projCRS =
736
57.7k
        proj_create_projected_crs(getPROJContext(), getProjCRSName(),
737
57.7k
                                  getGeodBaseCRS(), conv, getProjCRSCoordSys());
738
57.7k
    proj_destroy(conv);
739
740
57.7k
    setPjCRS(projCRS);
741
742
57.7k
    undoDemoteFromBoundCRS();
743
57.7k
    return OGRERR_NONE;
744
57.7k
}
745
746
/************************************************************************/
747
/*                             ToPointer()                              */
748
/************************************************************************/
749
750
static inline OGRSpatialReference *ToPointer(OGRSpatialReferenceH hSRS)
751
605k
{
752
605k
    return OGRSpatialReference::FromHandle(hSRS);
753
605k
}
754
755
/************************************************************************/
756
/*                              ToHandle()                              */
757
/************************************************************************/
758
759
static inline OGRSpatialReferenceH ToHandle(OGRSpatialReference *poSRS)
760
5.34k
{
761
5.34k
    return OGRSpatialReference::ToHandle(poSRS);
762
5.34k
}
763
764
/************************************************************************/
765
/*                          OGRsnPrintDouble()                          */
766
/************************************************************************/
767
768
void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue);
769
770
void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue)
771
772
7.06k
{
773
7.06k
    CPLsnprintf(pszStrBuf, size, "%.16g", dfValue);
774
775
7.06k
    const size_t nLen = strlen(pszStrBuf);
776
777
    // The following hack is intended to truncate some "precision" in cases
778
    // that appear to be roundoff error.
779
7.06k
    if (nLen > 15 && (strcmp(pszStrBuf + nLen - 6, "999999") == 0 ||
780
2.12k
                      strcmp(pszStrBuf + nLen - 6, "000001") == 0))
781
32
    {
782
32
        CPLsnprintf(pszStrBuf, size, "%.15g", dfValue);
783
32
    }
784
785
    // Force to user periods regardless of locale.
786
7.06k
    if (strchr(pszStrBuf, ',') != nullptr)
787
0
    {
788
0
        char *const pszDelim = strchr(pszStrBuf, ',');
789
0
        *pszDelim = '.';
790
0
    }
791
7.06k
}
792
793
/************************************************************************/
794
/*                        OGRSpatialReference()                         */
795
/************************************************************************/
796
797
/**
798
 * \brief Constructor.
799
 *
800
 * This constructor takes an optional string argument which if passed
801
 * should be a WKT representation of an SRS.  Passing this is equivalent
802
 * to not passing it, and then calling importFromWkt() with the WKT string.
803
 *
804
 * Note that newly created objects are given a reference count of one.
805
 *
806
 * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
807
 * object are assumed to be in the order of the axis of the CRS definition
808
 (which
809
 * for example means latitude first, longitude second for geographic CRS
810
 belonging
811
 * to the EPSG authority). It is possible to define a data axis to CRS axis
812
 * mapping strategy with the SetAxisMappingStrategy() method.
813
 *
814
 * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
815
 * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
816
 later
817
 * being the default value when the option is not set) to control the value of
818
 the
819
 * data axis to CRS axis mapping strategy when a OSRSpatialReference object is
820
 * created. Calling SetAxisMappingStrategy() will override this default value.
821
822
 * The C function OSRNewSpatialReference() does the same thing as this
823
 * constructor.
824
 *
825
 * @param pszWKT well known text definition to which the object should
826
 * be initialized, or NULL (the default).
827
 */
828
829
OGRSpatialReference::OGRSpatialReference(const char *pszWKT)
830
2.93M
    : d(new Private(this))
831
2.93M
{
832
2.93M
    if (pszWKT != nullptr)
833
36.1k
        importFromWkt(pszWKT);
834
2.93M
}
835
836
/************************************************************************/
837
/*                       OSRNewSpatialReference()                       */
838
/************************************************************************/
839
840
/**
841
 * \brief Constructor.
842
 *
843
 * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
844
 * object are assumed to be in the order of the axis of the CRS definition
845
 * (which for example means latitude first, longitude second for geographic CRS
846
 * belonging to the EPSG authority). It is possible to define a data axis to CRS
847
 * axis mapping strategy with the SetAxisMappingStrategy() method.
848
 *
849
 * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
850
 * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
851
 * later being the default value when the option is not set) to control the
852
 * value of the data axis to CRS axis mapping strategy when a
853
 * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
854
 * override this default value.
855
 *
856
 * This function is the same as OGRSpatialReference::OGRSpatialReference()
857
 */
858
OGRSpatialReferenceH CPL_STDCALL OSRNewSpatialReference(const char *pszWKT)
859
860
2.89k
{
861
2.89k
    OGRSpatialReference *poSRS = new OGRSpatialReference();
862
863
2.89k
    if (pszWKT != nullptr && strlen(pszWKT) > 0)
864
267
    {
865
267
        if (poSRS->importFromWkt(pszWKT) != OGRERR_NONE)
866
0
        {
867
0
            delete poSRS;
868
0
            poSRS = nullptr;
869
0
        }
870
267
    }
871
872
2.89k
    return ToHandle(poSRS);
873
2.89k
}
874
875
/************************************************************************/
876
/*                        OGRSpatialReference()                         */
877
/************************************************************************/
878
879
/** Copy constructor. See also Clone().
880
 * @param oOther other spatial reference
881
 */
882
OGRSpatialReference::OGRSpatialReference(const OGRSpatialReference &oOther)
883
17.0k
    : d(new Private(this))
884
17.0k
{
885
17.0k
    *this = oOther;
886
17.0k
}
887
888
/************************************************************************/
889
/*                        OGRSpatialReference()                         */
890
/************************************************************************/
891
892
/** Move constructor.
893
 * @param oOther other spatial reference
894
 */
895
OGRSpatialReference::OGRSpatialReference(OGRSpatialReference &&oOther)
896
496
    : d(std::move(oOther.d))
897
496
{
898
496
}
899
900
/************************************************************************/
901
/*                        ~OGRSpatialReference()                        */
902
/************************************************************************/
903
904
/**
905
 * \brief OGRSpatialReference destructor.
906
 *
907
 * The C function OSRDestroySpatialReference() does the same thing as this
908
 * method. Preferred C++ method : OGRSpatialReference::DestroySpatialReference()
909
 *
910
 * @deprecated
911
 */
912
913
OGRSpatialReference::~OGRSpatialReference()
914
915
2.95M
{
916
2.95M
}
917
918
/************************************************************************/
919
/*                      DestroySpatialReference()                       */
920
/************************************************************************/
921
922
/**
923
 * \brief OGRSpatialReference destructor.
924
 *
925
 * This static method will destroy a OGRSpatialReference.  It is
926
 * equivalent to calling delete on the object, but it ensures that the
927
 * deallocation is properly executed within the OGR libraries heap on
928
 * platforms where this can matter (win32).
929
 *
930
 * This function is the same as OSRDestroySpatialReference()
931
 *
932
 * @param poSRS the object to delete
933
 *
934
 */
935
936
void OGRSpatialReference::DestroySpatialReference(OGRSpatialReference *poSRS)
937
0
{
938
0
    delete poSRS;
939
0
}
940
941
/************************************************************************/
942
/*                     OSRDestroySpatialReference()                     */
943
/************************************************************************/
944
945
/**
946
 * \brief OGRSpatialReference destructor.
947
 *
948
 * This function is the same as OGRSpatialReference::~OGRSpatialReference()
949
 * and OGRSpatialReference::DestroySpatialReference()
950
 *
951
 * @param hSRS the object to delete
952
 */
953
void CPL_STDCALL OSRDestroySpatialReference(OGRSpatialReferenceH hSRS)
954
955
43.0k
{
956
43.0k
    delete ToPointer(hSRS);
957
43.0k
}
958
959
/************************************************************************/
960
/*                               Clear()                                */
961
/************************************************************************/
962
963
/**
964
 * \brief Wipe current definition.
965
 *
966
 * Returns OGRSpatialReference to a state with no definition, as it
967
 * exists when first created.  It does not affect reference counts.
968
 */
969
970
void OGRSpatialReference::Clear()
971
972
1.10M
{
973
1.10M
    d->clear();
974
1.10M
}
975
976
/************************************************************************/
977
/*                             operator=()                              */
978
/************************************************************************/
979
980
/** Assignment operator.
981
 * @param oSource SRS to assign to *this
982
 * @return *this
983
 */
984
OGRSpatialReference &
985
OGRSpatialReference::operator=(const OGRSpatialReference &oSource)
986
987
72.9k
{
988
72.9k
    if (&oSource != this)
989
72.9k
    {
990
72.9k
        Clear();
991
#ifdef CPPCHECK
992
        // Otherwise cppcheck would protest that nRefCount isn't modified
993
        d->nRefCount = (d->nRefCount + 1) - 1;
994
#endif
995
996
72.9k
        oSource.d->refreshProjObj();
997
72.9k
        if (oSource.d->m_pj_crs)
998
69.8k
            d->setPjCRS(proj_clone(d->getPROJContext(), oSource.d->m_pj_crs));
999
72.9k
        if (oSource.d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
1000
18.0k
            SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1001
54.9k
        else if (oSource.d->m_axisMappingStrategy == OAMS_CUSTOM)
1002
17
            SetDataAxisToSRSAxisMapping(oSource.d->m_axisMapping);
1003
1004
72.9k
        d->m_coordinateEpoch = oSource.d->m_coordinateEpoch;
1005
72.9k
    }
1006
1007
72.9k
    return *this;
1008
72.9k
}
1009
1010
/************************************************************************/
1011
/*                             operator=()                              */
1012
/************************************************************************/
1013
1014
/** Move assignment operator.
1015
 * @param oSource SRS to assign to *this
1016
 * @return *this
1017
 */
1018
OGRSpatialReference &
1019
OGRSpatialReference::operator=(OGRSpatialReference &&oSource)
1020
1021
41.3k
{
1022
41.3k
    if (&oSource != this)
1023
41.3k
    {
1024
41.3k
        d = std::move(oSource.d);
1025
41.3k
    }
1026
1027
41.3k
    return *this;
1028
41.3k
}
1029
1030
/************************************************************************/
1031
/*                       AssignAndSetThreadSafe()                       */
1032
/************************************************************************/
1033
1034
/** Assignment method, with thread-safety.
1035
 *
1036
 * Same as an assignment operator, but asking also that the *this instance
1037
 * becomes thread-safe.
1038
 *
1039
 * @param oSource SRS to assign to *this
1040
 * @return *this
1041
 * @since 3.10
1042
 */
1043
1044
OGRSpatialReference &
1045
OGRSpatialReference::AssignAndSetThreadSafe(const OGRSpatialReference &oSource)
1046
0
{
1047
0
    *this = oSource;
1048
0
    d->SetThreadSafe();
1049
0
    return *this;
1050
0
}
1051
1052
/************************************************************************/
1053
/*                             Reference()                              */
1054
/************************************************************************/
1055
1056
/**
1057
 * \brief Increments the reference count by one.
1058
 *
1059
 * The reference count is used keep track of the number of OGRGeometry objects
1060
 * referencing this SRS.
1061
 *
1062
 * The method does the same thing as the C function OSRReference().
1063
 *
1064
 * @return the updated reference count.
1065
 */
1066
1067
int OGRSpatialReference::Reference()
1068
1069
1.45M
{
1070
1.45M
    return CPLAtomicInc(&d->nRefCount);
1071
1.45M
}
1072
1073
/************************************************************************/
1074
/*                            OSRReference()                            */
1075
/************************************************************************/
1076
1077
/**
1078
 * \brief Increments the reference count by one.
1079
 *
1080
 * This function is the same as OGRSpatialReference::Reference()
1081
 */
1082
int OSRReference(OGRSpatialReferenceH hSRS)
1083
1084
0
{
1085
0
    VALIDATE_POINTER1(hSRS, "OSRReference", 0);
1086
1087
0
    return ToPointer(hSRS)->Reference();
1088
0
}
1089
1090
/************************************************************************/
1091
/*                            Dereference()                             */
1092
/************************************************************************/
1093
1094
/**
1095
 * \brief Decrements the reference count by one.
1096
 *
1097
 * The method does the same thing as the C function OSRDereference().
1098
 *
1099
 * @return the updated reference count.
1100
 */
1101
1102
int OGRSpatialReference::Dereference()
1103
1104
2.86M
{
1105
2.86M
    if (d->nRefCount <= 0)
1106
0
        CPLDebug("OSR",
1107
0
                 "Dereference() called on an object with refcount %d,"
1108
0
                 "likely already destroyed!",
1109
0
                 d->nRefCount);
1110
2.86M
    return CPLAtomicDec(&d->nRefCount);
1111
2.86M
}
1112
1113
/************************************************************************/
1114
/*                           OSRDereference()                           */
1115
/************************************************************************/
1116
1117
/**
1118
 * \brief Decrements the reference count by one.
1119
 *
1120
 * This function is the same as OGRSpatialReference::Dereference()
1121
 */
1122
int OSRDereference(OGRSpatialReferenceH hSRS)
1123
1124
0
{
1125
0
    VALIDATE_POINTER1(hSRS, "OSRDereference", 0);
1126
1127
0
    return ToPointer(hSRS)->Dereference();
1128
0
}
1129
1130
/************************************************************************/
1131
/*                         GetReferenceCount()                          */
1132
/************************************************************************/
1133
1134
/**
1135
 * \brief Fetch current reference count.
1136
 *
1137
 * @return the current reference count.
1138
 */
1139
int OGRSpatialReference::GetReferenceCount() const
1140
0
{
1141
0
    return d->nRefCount;
1142
0
}
1143
1144
/************************************************************************/
1145
/*                              Release()                               */
1146
/************************************************************************/
1147
1148
/**
1149
 * \brief Decrements the reference count by one, and destroy if zero.
1150
 *
1151
 * The method does the same thing as the C function OSRRelease().
1152
 */
1153
1154
void OGRSpatialReference::Release()
1155
1156
2.85M
{
1157
2.85M
    if (Dereference() <= 0)
1158
1.41M
        delete this;
1159
2.85M
}
1160
1161
/************************************************************************/
1162
/*                             OSRRelease()                             */
1163
/************************************************************************/
1164
1165
/**
1166
 * \brief Decrements the reference count by one, and destroy if zero.
1167
 *
1168
 * This function is the same as OGRSpatialReference::Release()
1169
 */
1170
void OSRRelease(OGRSpatialReferenceH hSRS)
1171
1172
555k
{
1173
555k
    VALIDATE_POINTER0(hSRS, "OSRRelease");
1174
1175
555k
    ToPointer(hSRS)->Release();
1176
555k
}
1177
1178
OGR_SRSNode *OGRSpatialReference::GetRoot()
1179
749k
{
1180
749k
    TAKE_OPTIONAL_LOCK();
1181
1182
749k
    if (!d->m_poRoot)
1183
349k
    {
1184
349k
        d->refreshRootFromProjObj();
1185
349k
    }
1186
749k
    return d->m_poRoot;
1187
749k
}
1188
1189
const OGR_SRSNode *OGRSpatialReference::GetRoot() const
1190
63.6k
{
1191
63.6k
    TAKE_OPTIONAL_LOCK();
1192
1193
63.6k
    if (!d->m_poRoot)
1194
57.8k
    {
1195
57.8k
        d->refreshRootFromProjObj();
1196
57.8k
    }
1197
63.6k
    return d->m_poRoot;
1198
63.6k
}
1199
1200
/************************************************************************/
1201
/*                              SetRoot()                               */
1202
/************************************************************************/
1203
1204
/**
1205
 * \brief Set the root SRS node.
1206
 *
1207
 * If the object has an existing tree of OGR_SRSNodes, they are destroyed
1208
 * as part of assigning the new root.  Ownership of the passed OGR_SRSNode is
1209
 * is assumed by the OGRSpatialReference.
1210
 *
1211
 * @param poNewRoot object to assign as root.
1212
 */
1213
1214
void OGRSpatialReference::SetRoot(OGR_SRSNode *poNewRoot)
1215
1216
15.3k
{
1217
15.3k
    if (d->m_poRoot != poNewRoot)
1218
15.3k
    {
1219
15.3k
        delete d->m_poRoot;
1220
15.3k
        d->setRoot(poNewRoot);
1221
15.3k
    }
1222
15.3k
}
1223
1224
/************************************************************************/
1225
/*                            GetAttrNode()                             */
1226
/************************************************************************/
1227
1228
/**
1229
 * \brief Find named node in tree.
1230
 *
1231
 * This method does a pre-order traversal of the node tree searching for
1232
 * a node with this exact value (case insensitive), and returns it.  Leaf
1233
 * nodes are not considered, under the assumption that they are just
1234
 * attribute value nodes.
1235
 *
1236
 * If a node appears more than once in the tree (such as UNIT for instance),
1237
 * the first encountered will be returned.  Use GetNode() on a subtree to be
1238
 * more specific.
1239
 *
1240
 * @param pszNodePath the name of the node to search for.  May contain multiple
1241
 * components such as "GEOGCS|UNIT".
1242
 *
1243
 * @return a pointer to the node found, or NULL if none.
1244
 */
1245
1246
OGR_SRSNode *OGRSpatialReference::GetAttrNode(const char *pszNodePath)
1247
1248
382k
{
1249
382k
    if (strchr(pszNodePath, '|') == nullptr)
1250
309k
    {
1251
        // Fast path
1252
309k
        OGR_SRSNode *poNode = GetRoot();
1253
309k
        if (poNode)
1254
282k
            poNode = poNode->GetNode(pszNodePath);
1255
309k
        return poNode;
1256
309k
    }
1257
1258
73.1k
    char **papszPathTokens =
1259
73.1k
        CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
1260
1261
73.1k
    if (CSLCount(papszPathTokens) < 1)
1262
0
    {
1263
0
        CSLDestroy(papszPathTokens);
1264
0
        return nullptr;
1265
0
    }
1266
1267
73.1k
    OGR_SRSNode *poNode = GetRoot();
1268
270k
    for (int i = 0; poNode != nullptr && papszPathTokens[i] != nullptr; i++)
1269
197k
    {
1270
197k
        poNode = poNode->GetNode(papszPathTokens[i]);
1271
197k
    }
1272
1273
73.1k
    CSLDestroy(papszPathTokens);
1274
1275
73.1k
    return poNode;
1276
73.1k
}
1277
1278
/**
1279
 * \brief Find named node in tree.
1280
 *
1281
 * This method does a pre-order traversal of the node tree searching for
1282
 * a node with this exact value (case insensitive), and returns it.  Leaf
1283
 * nodes are not considered, under the assumption that they are just
1284
 * attribute value nodes.
1285
 *
1286
 * If a node appears more than once in the tree (such as UNIT for instance),
1287
 * the first encountered will be returned.  Use GetNode() on a subtree to be
1288
 * more specific.
1289
 *
1290
 * @param pszNodePath the name of the node to search for.  May contain multiple
1291
 * components such as "GEOGCS|UNIT".
1292
 *
1293
 * @return a pointer to the node found, or NULL if none.
1294
 */
1295
1296
const OGR_SRSNode *
1297
OGRSpatialReference::GetAttrNode(const char *pszNodePath) const
1298
1299
154k
{
1300
154k
    OGR_SRSNode *poNode =
1301
154k
        const_cast<OGRSpatialReference *>(this)->GetAttrNode(pszNodePath);
1302
1303
154k
    return poNode;
1304
154k
}
1305
1306
/************************************************************************/
1307
/*                            GetAttrValue()                            */
1308
/************************************************************************/
1309
1310
/**
1311
 * \brief Fetch indicated attribute of named node.
1312
 *
1313
 * This method uses GetAttrNode() to find the named node, and then extracts
1314
 * the value of the indicated child.  Thus a call to GetAttrValue("UNIT",1)
1315
 * would return the second child of the UNIT node, which is normally the
1316
 * length of the linear unit in meters.
1317
 *
1318
 * This method does the same thing as the C function OSRGetAttrValue().
1319
 *
1320
 * @param pszNodeName the tree node to look for (case insensitive).
1321
 * @param iAttr the child of the node to fetch (zero based).
1322
 *
1323
 * @return the requested value, or NULL if it fails for any reason.
1324
 */
1325
1326
const char *OGRSpatialReference::GetAttrValue(const char *pszNodeName,
1327
                                              int iAttr) const
1328
1329
48.6k
{
1330
48.6k
    const OGR_SRSNode *poNode = GetAttrNode(pszNodeName);
1331
48.6k
    if (poNode == nullptr)
1332
8.81k
    {
1333
8.81k
        if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJECTION"))
1334
245
        {
1335
245
            return GetAttrValue("METHOD", iAttr);
1336
245
        }
1337
8.57k
        else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS|PROJECTION"))
1338
60
        {
1339
60
            return GetAttrValue("PROJCRS|METHOD", iAttr);
1340
60
        }
1341
8.51k
        else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS"))
1342
484
        {
1343
484
            return GetAttrValue("PROJCRS", iAttr);
1344
484
        }
1345
8.02k
        return nullptr;
1346
8.81k
    }
1347
1348
39.8k
    if (iAttr < 0 || iAttr >= poNode->GetChildCount())
1349
0
        return nullptr;
1350
1351
39.8k
    return poNode->GetChild(iAttr)->GetValue();
1352
39.8k
}
1353
1354
/************************************************************************/
1355
/*                          OSRGetAttrValue()                           */
1356
/************************************************************************/
1357
1358
/**
1359
 * \brief Fetch indicated attribute of named node.
1360
 *
1361
 * This function is the same as OGRSpatialReference::GetAttrValue()
1362
 */
1363
const char *CPL_STDCALL OSRGetAttrValue(OGRSpatialReferenceH hSRS,
1364
                                        const char *pszKey, int iChild)
1365
1366
0
{
1367
0
    VALIDATE_POINTER1(hSRS, "OSRGetAttrValue", nullptr);
1368
1369
0
    return ToPointer(hSRS)->GetAttrValue(pszKey, iChild);
1370
0
}
1371
1372
/************************************************************************/
1373
/*                              GetName()                               */
1374
/************************************************************************/
1375
1376
/**
1377
 * \brief Return the CRS name.
1378
 *
1379
 * The returned value is only short lived and should not be used after other
1380
 * calls to methods on this object.
1381
 *
1382
 * @since GDAL 3.0
1383
 */
1384
1385
const char *OGRSpatialReference::GetName() const
1386
47.0k
{
1387
47.0k
    TAKE_OPTIONAL_LOCK();
1388
1389
47.0k
    d->refreshProjObj();
1390
47.0k
    if (!d->m_pj_crs)
1391
119
        return nullptr;
1392
46.9k
    const char *pszName = proj_get_name(d->m_pj_crs);
1393
#if PROJ_VERSION_NUMBER == PROJ_COMPUTE_VERSION(8, 2, 0)
1394
    if (d->m_pjType == PJ_TYPE_BOUND_CRS && EQUAL(pszName, "SOURCECRS"))
1395
    {
1396
        // Work around a bug of PROJ 8.2.0 (fixed in 8.2.1)
1397
        PJ *baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
1398
        if (baseCRS)
1399
        {
1400
            pszName = proj_get_name(baseCRS);
1401
            // pszName still remains valid after proj_destroy(), since
1402
            // d->m_pj_crs keeps a reference to the base CRS C++ object.
1403
            proj_destroy(baseCRS);
1404
        }
1405
    }
1406
#endif
1407
46.9k
    return pszName;
1408
47.0k
}
1409
1410
/************************************************************************/
1411
/*                             OSRGetName()                             */
1412
/************************************************************************/
1413
1414
/**
1415
 * \brief Return the CRS name.
1416
 *
1417
 * The returned value is only short lived and should not be used after other
1418
 * calls to methods on this object.
1419
 *
1420
 * @since GDAL 3.0
1421
 */
1422
const char *OSRGetName(OGRSpatialReferenceH hSRS)
1423
1424
0
{
1425
0
    VALIDATE_POINTER1(hSRS, "OSRGetName", nullptr);
1426
1427
0
    return ToPointer(hSRS)->GetName();
1428
0
}
1429
1430
/************************************************************************/
1431
/*                        GetCelestialBodyName()                        */
1432
/************************************************************************/
1433
1434
/**
1435
 * \brief Return the name of the celestial body of this CRS.
1436
 *
1437
 * e.g. "Earth" for an Earth CRS
1438
 *
1439
 * The returned value is only short lived and should not be used after other
1440
 * calls to methods on this object.
1441
 *
1442
 * @since GDAL 3.12 and PROJ 8.1
1443
 */
1444
1445
const char *OGRSpatialReference::GetCelestialBodyName() const
1446
0
{
1447
0
#if PROJ_AT_LEAST_VERSION(8, 1, 0)
1448
1449
0
    TAKE_OPTIONAL_LOCK();
1450
1451
0
    d->refreshProjObj();
1452
0
    if (!d->m_pj_crs)
1453
0
        return nullptr;
1454
0
    d->demoteFromBoundCRS();
1455
0
    const char *name =
1456
0
        proj_get_celestial_body_name(d->getPROJContext(), d->m_pj_crs);
1457
0
    if (name)
1458
0
    {
1459
0
        d->m_celestialBodyName = name;
1460
0
    }
1461
0
    d->undoDemoteFromBoundCRS();
1462
0
    return d->m_celestialBodyName.c_str();
1463
#else
1464
    if (std::fabs(GetSemiMajor(nullptr) - SRS_WGS84_SEMIMAJOR) <=
1465
        0.05 * SRS_WGS84_SEMIMAJOR)
1466
        return "Earth";
1467
    const char *pszAuthName = GetAuthorityName(nullptr);
1468
    if (pszAuthName && EQUAL(pszAuthName, "EPSG"))
1469
        return "Earth";
1470
    return nullptr;
1471
#endif
1472
0
}
1473
1474
/************************************************************************/
1475
/*                      OSRGetCelestialBodyName()                       */
1476
/************************************************************************/
1477
1478
/**
1479
 * \brief Return the name of the celestial body of this CRS.
1480
 *
1481
 * e.g. "Earth" for an Earth CRS
1482
 *
1483
 * The returned value is only short lived and should not be used after other
1484
 * calls to methods on this object.
1485
 *
1486
 * @since GDAL 3.12 and PROJ 8.1
1487
 */
1488
1489
const char *OSRGetCelestialBodyName(OGRSpatialReferenceH hSRS)
1490
1491
0
{
1492
0
    VALIDATE_POINTER1(hSRS, "GetCelestialBodyName", nullptr);
1493
1494
0
    return ToPointer(hSRS)->GetCelestialBodyName();
1495
0
}
1496
1497
/************************************************************************/
1498
/*                               Clone()                                */
1499
/************************************************************************/
1500
1501
/**
1502
 * \brief Make a duplicate of this OGRSpatialReference.
1503
 *
1504
 * This method is the same as the C function OSRClone().
1505
 *
1506
 * @return a new SRS, which becomes the responsibility of the caller.
1507
 */
1508
1509
OGRSpatialReference *OGRSpatialReference::Clone() const
1510
1511
440k
{
1512
440k
    OGRSpatialReference *poNewRef = new OGRSpatialReference();
1513
1514
440k
    TAKE_OPTIONAL_LOCK();
1515
1516
440k
    d->refreshProjObj();
1517
440k
    if (d->m_pj_crs != nullptr)
1518
411k
        poNewRef->d->setPjCRS(proj_clone(d->getPROJContext(), d->m_pj_crs));
1519
440k
    if (d->m_bHasCenterLong && d->m_poRoot)
1520
2.21k
    {
1521
2.21k
        poNewRef->d->setRoot(d->m_poRoot->Clone());
1522
2.21k
    }
1523
440k
    poNewRef->d->m_axisMapping = d->m_axisMapping;
1524
440k
    poNewRef->d->m_axisMappingStrategy = d->m_axisMappingStrategy;
1525
440k
    poNewRef->d->m_coordinateEpoch = d->m_coordinateEpoch;
1526
440k
    return poNewRef;
1527
440k
}
1528
1529
/************************************************************************/
1530
/*                              OSRClone()                              */
1531
/************************************************************************/
1532
1533
/**
1534
 * \brief Make a duplicate of this OGRSpatialReference.
1535
 *
1536
 * This function is the same as OGRSpatialReference::Clone()
1537
 */
1538
OGRSpatialReferenceH CPL_STDCALL OSRClone(OGRSpatialReferenceH hSRS)
1539
1540
2.18k
{
1541
2.18k
    VALIDATE_POINTER1(hSRS, "OSRClone", nullptr);
1542
1543
2.18k
    return ToHandle(ToPointer(hSRS)->Clone());
1544
2.18k
}
1545
1546
/************************************************************************/
1547
/*                            dumpReadable()                            */
1548
/************************************************************************/
1549
1550
/** Dump pretty wkt to stdout, mostly for debugging.
1551
 */
1552
void OGRSpatialReference::dumpReadable()
1553
1554
0
{
1555
0
    char *pszPrettyWkt = nullptr;
1556
1557
0
    const char *const apszOptions[] = {"FORMAT=WKT2", "MULTILINE=YES", nullptr};
1558
0
    exportToWkt(&pszPrettyWkt, apszOptions);
1559
0
    printf("%s\n", pszPrettyWkt); /*ok*/
1560
0
    CPLFree(pszPrettyWkt);
1561
0
}
1562
1563
/************************************************************************/
1564
/*                         exportToPrettyWkt()                          */
1565
/************************************************************************/
1566
1567
/**
1568
 * Convert this SRS into a nicely formatted WKT 1 string for display to a
1569
 * person.
1570
 *
1571
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
1572
 * Issues</a> page for implementation details of WKT 1 in OGR.
1573
 *
1574
 * Note that the returned WKT string should be freed with
1575
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
1576
 *
1577
 * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
1578
 * option. Valid values are the one of the FORMAT option of
1579
 * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
1580
 *
1581
 * This method is the same as the C function OSRExportToPrettyWkt().
1582
 *
1583
 * @param ppszResult the resulting string is returned in this pointer.
1584
 * @param bSimplify TRUE if the AXIS, AUTHORITY and EXTENSION nodes should be
1585
 *   stripped off.
1586
 *
1587
 * @return OGRERR_NONE if successful.
1588
 */
1589
1590
OGRErr OGRSpatialReference::exportToPrettyWkt(char **ppszResult,
1591
                                              int bSimplify) const
1592
1593
245
{
1594
245
    CPLStringList aosOptions;
1595
245
    aosOptions.SetNameValue("MULTILINE", "YES");
1596
245
    if (bSimplify)
1597
0
    {
1598
0
        aosOptions.SetNameValue("FORMAT", "WKT1_SIMPLE");
1599
0
    }
1600
245
    return exportToWkt(ppszResult, aosOptions.List());
1601
245
}
1602
1603
/************************************************************************/
1604
/*                        OSRExportToPrettyWkt()                        */
1605
/************************************************************************/
1606
1607
/**
1608
 * \brief Convert this SRS into a nicely formatted WKT 1 string for display to a
1609
 * person.
1610
 *
1611
 * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
1612
 * option. Valid values are the one of the FORMAT option of
1613
 * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
1614
 *
1615
 * This function is the same as OGRSpatialReference::exportToPrettyWkt().
1616
 */
1617
1618
OGRErr CPL_STDCALL OSRExportToPrettyWkt(OGRSpatialReferenceH hSRS,
1619
                                        char **ppszReturn, int bSimplify)
1620
1621
0
{
1622
0
    VALIDATE_POINTER1(hSRS, "OSRExportToPrettyWkt", OGRERR_FAILURE);
1623
1624
0
    *ppszReturn = nullptr;
1625
1626
0
    return ToPointer(hSRS)->exportToPrettyWkt(ppszReturn, bSimplify);
1627
0
}
1628
1629
/************************************************************************/
1630
/*                            exportToWkt()                             */
1631
/************************************************************************/
1632
1633
/**
1634
 * \brief Convert this SRS into WKT 1 format.
1635
 *
1636
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
1637
 * Issues</a> page for implementation details of WKT 1 in OGR.
1638
 *
1639
 * Note that the returned WKT string should be freed with
1640
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
1641
 *
1642
 * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
1643
 * option. Valid values are the one of the FORMAT option of
1644
 * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
1645
 *
1646
 * This method is the same as the C function OSRExportToWkt().
1647
 *
1648
 * @param ppszResult the resulting string is returned in this pointer.
1649
 *
1650
 * @return OGRERR_NONE if successful.
1651
 */
1652
1653
OGRErr OGRSpatialReference::exportToWkt(char **ppszResult) const
1654
1655
96.4k
{
1656
96.4k
    return exportToWkt(ppszResult, nullptr);
1657
96.4k
}
1658
1659
/************************************************************************/
1660
/*              GDAL_proj_crs_create_bound_crs_to_WGS84()               */
1661
/************************************************************************/
1662
1663
static PJ *GDAL_proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, PJ *pj,
1664
                                                   bool onlyIfEPSGCode,
1665
                                                   bool canModifyHorizPart)
1666
992
{
1667
992
    PJ *ret = nullptr;
1668
992
    if (proj_get_type(pj) == PJ_TYPE_COMPOUND_CRS)
1669
122
    {
1670
122
        auto horizCRS = proj_crs_get_sub_crs(ctx, pj, 0);
1671
122
        auto vertCRS = proj_crs_get_sub_crs(ctx, pj, 1);
1672
122
        if (horizCRS && proj_get_type(horizCRS) != PJ_TYPE_BOUND_CRS &&
1673
122
            vertCRS &&
1674
122
            (!onlyIfEPSGCode || proj_get_id_auth_name(horizCRS, 0) != nullptr))
1675
122
        {
1676
122
            auto boundHoriz =
1677
122
                canModifyHorizPart
1678
122
                    ? proj_crs_create_bound_crs_to_WGS84(ctx, horizCRS, nullptr)
1679
122
                    : proj_clone(ctx, horizCRS);
1680
122
            auto boundVert =
1681
122
                proj_crs_create_bound_crs_to_WGS84(ctx, vertCRS, nullptr);
1682
122
            if (boundHoriz && boundVert)
1683
122
            {
1684
122
                ret = proj_create_compound_crs(ctx, proj_get_name(pj),
1685
122
                                               boundHoriz, boundVert);
1686
122
            }
1687
122
            proj_destroy(boundHoriz);
1688
122
            proj_destroy(boundVert);
1689
122
        }
1690
122
        proj_destroy(horizCRS);
1691
122
        proj_destroy(vertCRS);
1692
122
    }
1693
870
    else if (proj_get_type(pj) != PJ_TYPE_BOUND_CRS &&
1694
860
             (!onlyIfEPSGCode || proj_get_id_auth_name(pj, 0) != nullptr))
1695
464
    {
1696
464
        ret = proj_crs_create_bound_crs_to_WGS84(ctx, pj, nullptr);
1697
464
    }
1698
992
    return ret;
1699
992
}
1700
1701
/************************************************************************/
1702
/*                            exportToWkt()                             */
1703
/************************************************************************/
1704
1705
/**
1706
 * Convert this SRS into a WKT string.
1707
 *
1708
 * Note that the returned WKT string should be freed with
1709
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
1710
 *
1711
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
1712
 * Issues</a> page for implementation details of WKT 1 in OGR.
1713
 *
1714
 * @param ppszResult the resulting string is returned in this pointer.
1715
 * @param papszOptions NULL terminated list of options, or NULL. Currently
1716
 * supported options are
1717
 * <ul>
1718
 * <li>MULTILINE=YES/NO. Defaults to NO.</li>
1719
 * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
1720
 *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
1721
 *     node is returned.
1722
 *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
1723
 *     node is returned.
1724
 *     WKT1 is an alias of WKT1_GDAL.
1725
 *     WKT2 will default to the latest revision implemented (currently
1726
 *     WKT2_2018) WKT2_2019 can be used as an alias of WKT2_2018 since GDAL 3.2
1727
 * </li>
1728
 * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
1729
 * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
1730
 * be exported as a compound CRS whose vertical part represents an ellipsoidal
1731
 * height (for example for use with LAS 1.4 WKT1).
1732
 * Requires PROJ 7.2.1 and GDAL 3.2.1.</li>
1733
 * </ul>
1734
 *
1735
 * Starting with GDAL 3.0.3, if the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
1736
 * configuration option is set to YES, when exporting to WKT1_GDAL, this method
1737
 * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
1738
 * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
1739
 * TOWGS84[] node may be added.
1740
 *
1741
 * @return OGRERR_NONE if successful.
1742
 * @since GDAL 3.0
1743
 */
1744
1745
OGRErr OGRSpatialReference::exportToWkt(char **ppszResult,
1746
                                        const char *const *papszOptions) const
1747
125k
{
1748
    // In the past calling this method was thread-safe, even if we never
1749
    // guaranteed it. Now proj_as_wkt() will cache the result internally,
1750
    // so this is no longer thread-safe.
1751
125k
    std::lock_guard oLock(d->m_mutex);
1752
1753
125k
    d->refreshProjObj();
1754
125k
    if (!d->m_pj_crs)
1755
8.59k
    {
1756
8.59k
        *ppszResult = CPLStrdup("");
1757
8.59k
        return OGRERR_FAILURE;
1758
8.59k
    }
1759
1760
117k
    if (d->m_bHasCenterLong && d->m_poRoot && !d->m_bMorphToESRI)
1761
2.67k
    {
1762
2.67k
        return d->m_poRoot->exportToWkt(ppszResult);
1763
2.67k
    }
1764
1765
114k
    auto ctxt = d->getPROJContext();
1766
114k
    auto wktFormat = PJ_WKT1_GDAL;
1767
114k
    const char *pszFormat =
1768
114k
        CSLFetchNameValueDef(papszOptions, "FORMAT",
1769
114k
                             CPLGetConfigOption("OSR_WKT_FORMAT", "DEFAULT"));
1770
114k
    if (EQUAL(pszFormat, "DEFAULT"))
1771
87.3k
        pszFormat = "";
1772
1773
114k
    if (EQUAL(pszFormat, "WKT1_ESRI") || d->m_bMorphToESRI)
1774
1.78k
    {
1775
1.78k
        wktFormat = PJ_WKT1_ESRI;
1776
1.78k
    }
1777
112k
    else if (EQUAL(pszFormat, "WKT1") || EQUAL(pszFormat, "WKT1_GDAL") ||
1778
95.1k
             EQUAL(pszFormat, "WKT1_SIMPLE") || EQUAL(pszFormat, "SFSQL"))
1779
17.4k
    {
1780
17.4k
        wktFormat = PJ_WKT1_GDAL;
1781
17.4k
    }
1782
95.1k
    else if (EQUAL(pszFormat, "WKT2_2015"))
1783
132
    {
1784
132
        wktFormat = PJ_WKT2_2015;
1785
132
    }
1786
95.0k
    else if (EQUAL(pszFormat, "WKT2") || EQUAL(pszFormat, "WKT2_2018") ||
1787
87.4k
             EQUAL(pszFormat, "WKT2_2019"))
1788
7.96k
    {
1789
7.96k
        wktFormat = PJ_WKT2_2018;
1790
7.96k
    }
1791
87.0k
    else if (pszFormat[0] == '\0')
1792
87.0k
    {
1793
        // cppcheck-suppress knownConditionTrueFalse
1794
87.0k
        if (IsDerivedGeographic())
1795
479
        {
1796
479
            wktFormat = PJ_WKT2_2018;
1797
479
        }
1798
86.5k
        else if ((IsGeographic() || IsProjected()) && !IsCompound() &&
1799
50.2k
                 GetAxesCount() == 3)
1800
1.95k
        {
1801
1.95k
            wktFormat = PJ_WKT2_2018;
1802
1.95k
        }
1803
87.0k
    }
1804
0
    else
1805
0
    {
1806
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unsupported value for FORMAT");
1807
0
        *ppszResult = CPLStrdup("");
1808
0
        return OGRERR_FAILURE;
1809
0
    }
1810
1811
114k
    CPLStringList aosOptions;
1812
114k
    if (wktFormat != PJ_WKT1_ESRI)
1813
112k
    {
1814
112k
        aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
1815
112k
    }
1816
114k
    aosOptions.SetNameValue(
1817
114k
        "MULTILINE", CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO"));
1818
1819
114k
    const char *pszAllowEllpsHeightAsVertCS = CSLFetchNameValue(
1820
114k
        papszOptions, "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS");
1821
114k
    if (pszAllowEllpsHeightAsVertCS)
1822
0
    {
1823
0
        aosOptions.SetNameValue("ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS",
1824
0
                                pszAllowEllpsHeightAsVertCS);
1825
0
    }
1826
1827
114k
    PJ *boundCRS = nullptr;
1828
114k
    if (wktFormat == PJ_WKT1_GDAL &&
1829
102k
        CPLTestBool(CSLFetchNameValueDef(
1830
102k
            papszOptions, "ADD_TOWGS84_ON_EXPORT_TO_WKT1",
1831
102k
            CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1", "NO"))))
1832
0
    {
1833
0
        boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
1834
0
            d->getPROJContext(), d->m_pj_crs, true, true);
1835
0
    }
1836
1837
114k
    CPLErrorAccumulator oErrorAccumulator;
1838
114k
    const char *pszWKT;
1839
114k
    {
1840
114k
        auto oAccumulator = oErrorAccumulator.InstallForCurrentScope();
1841
114k
        CPL_IGNORE_RET_VAL(oAccumulator);
1842
114k
        pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs, wktFormat,
1843
114k
                             aosOptions.List());
1844
114k
    }
1845
114k
    for (const auto &oError : oErrorAccumulator.GetErrors())
1846
6.29k
    {
1847
6.29k
        if (pszFormat[0] == '\0' &&
1848
6.03k
            (oError.msg.find("Unsupported conversion method") !=
1849
6.03k
                 std::string::npos ||
1850
5.63k
             oError.msg.find("can only be exported to WKT2") !=
1851
5.63k
                 std::string::npos ||
1852
1.07k
             oError.msg.find("can only be exported since WKT2:2019") !=
1853
1.07k
                 std::string::npos))
1854
5.68k
        {
1855
5.68k
            CPLErrorReset();
1856
            // If we cannot export in the default mode (WKT1), retry with WKT2
1857
5.68k
            pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs,
1858
5.68k
                                 PJ_WKT2_2018, aosOptions.List());
1859
5.68k
            break;
1860
5.68k
        }
1861
617
        CPLError(oError.type, oError.no, "%s", oError.msg.c_str());
1862
617
    }
1863
1864
114k
    if (!pszWKT)
1865
617
    {
1866
617
        *ppszResult = CPLStrdup("");
1867
617
        proj_destroy(boundCRS);
1868
617
        return OGRERR_FAILURE;
1869
617
    }
1870
1871
113k
    if (EQUAL(pszFormat, "SFSQL") || EQUAL(pszFormat, "WKT1_SIMPLE"))
1872
0
    {
1873
0
        OGR_SRSNode oRoot;
1874
0
        oRoot.importFromWkt(&pszWKT);
1875
0
        oRoot.StripNodes("AXIS");
1876
0
        if (EQUAL(pszFormat, "SFSQL"))
1877
0
        {
1878
0
            oRoot.StripNodes("TOWGS84");
1879
0
        }
1880
0
        oRoot.StripNodes("AUTHORITY");
1881
0
        oRoot.StripNodes("EXTENSION");
1882
0
        OGRErr eErr;
1883
0
        if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO")))
1884
0
            eErr = oRoot.exportToPrettyWkt(ppszResult, 1);
1885
0
        else
1886
0
            eErr = oRoot.exportToWkt(ppszResult);
1887
0
        proj_destroy(boundCRS);
1888
0
        return eErr;
1889
0
    }
1890
1891
113k
    *ppszResult = CPLStrdup(pszWKT);
1892
1893
#if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
1894
    if (wktFormat == PJ_WKT2_2018)
1895
    {
1896
        // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
1897
        // related to a wrong EPSG code assigned to UTM South conversions
1898
        char *pszPtr = strstr(*ppszResult, "CONVERSION[\"UTM zone ");
1899
        if (pszPtr)
1900
        {
1901
            pszPtr += strlen("CONVERSION[\"UTM zone ");
1902
            const int nZone = atoi(pszPtr);
1903
            while (*pszPtr >= '0' && *pszPtr <= '9')
1904
                ++pszPtr;
1905
            if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' &&
1906
                pszPtr[1] == '"' && pszPtr[2] == ',')
1907
            {
1908
                pszPtr += 3;
1909
                int nLevel = 0;
1910
                bool bInString = false;
1911
                // Find the ID node corresponding to this CONVERSION node
1912
                while (*pszPtr)
1913
                {
1914
                    if (bInString)
1915
                    {
1916
                        if (*pszPtr == '"' && pszPtr[1] == '"')
1917
                        {
1918
                            ++pszPtr;
1919
                        }
1920
                        else if (*pszPtr == '"')
1921
                        {
1922
                            bInString = false;
1923
                        }
1924
                    }
1925
                    else if (nLevel == 0 && STARTS_WITH_CI(pszPtr, "ID["))
1926
                    {
1927
                        if (STARTS_WITH_CI(pszPtr, CPLSPrintf("ID[\"EPSG\",%d]",
1928
                                                              17000 + nZone)))
1929
                        {
1930
                            CPLAssert(pszPtr[11] == '7');
1931
                            CPLAssert(pszPtr[12] == '0');
1932
                            pszPtr[11] = '6';
1933
                            pszPtr[12] = '1';
1934
                        }
1935
                        break;
1936
                    }
1937
                    else if (*pszPtr == '"')
1938
                    {
1939
                        bInString = true;
1940
                    }
1941
                    else if (*pszPtr == '[')
1942
                    {
1943
                        ++nLevel;
1944
                    }
1945
                    else if (*pszPtr == ']')
1946
                    {
1947
                        --nLevel;
1948
                    }
1949
1950
                    ++pszPtr;
1951
                }
1952
            }
1953
        }
1954
    }
1955
#endif
1956
1957
113k
    proj_destroy(boundCRS);
1958
113k
    return OGRERR_NONE;
1959
113k
}
1960
1961
/************************************************************************/
1962
/*                            exportToWkt()                             */
1963
/************************************************************************/
1964
1965
/**
1966
 * Convert this SRS into a WKT string.
1967
 *
1968
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
1969
 * Issues</a> page for implementation details of WKT 1 in OGR.
1970
 *
1971
 * @param papszOptions NULL terminated list of options, or NULL. Currently
1972
 * supported options are
1973
 * <ul>
1974
 * <li>MULTILINE=YES/NO. Defaults to NO.</li>
1975
 * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
1976
 *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
1977
 *     node is returned.
1978
 *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
1979
 *     node is returned.
1980
 *     WKT1 is an alias of WKT1_GDAL.
1981
 *     WKT2 will default to the latest revision implemented (currently
1982
 *     WKT2_2019)
1983
 * </li>
1984
 * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
1985
 * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
1986
 * be exported as a compound CRS whose vertical part represents an ellipsoidal
1987
 * height (for example for use with LAS 1.4 WKT1).
1988
 * Requires PROJ 7.2.1.</li>
1989
 * </ul>
1990
 *
1991
 * If the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
1992
 * configuration option is set to YES, when exporting to WKT1_GDAL, this method
1993
 * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
1994
 * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
1995
 * TOWGS84[] node may be added.
1996
 *
1997
 * @return a non-empty string if successful.
1998
 * @since GDAL 3.9
1999
 */
2000
2001
std::string
2002
OGRSpatialReference::exportToWkt(const char *const *papszOptions) const
2003
222
{
2004
222
    std::string osWKT;
2005
222
    char *pszWKT = nullptr;
2006
222
    if (exportToWkt(&pszWKT, papszOptions) == OGRERR_NONE)
2007
222
        osWKT = pszWKT;
2008
222
    CPLFree(pszWKT);
2009
222
    return osWKT;
2010
222
}
2011
2012
/************************************************************************/
2013
/*                           OSRExportToWkt()                           */
2014
/************************************************************************/
2015
2016
/**
2017
 * \brief Convert this SRS into WKT 1 format.
2018
 *
2019
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
2020
 * Issues</a> page for implementation details of WKT in OGR.
2021
 *
2022
 * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
2023
 * option. Valid values are the one of the FORMAT option of
2024
 * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
2025
 *
2026
 * This function is the same as OGRSpatialReference::exportToWkt().
2027
 */
2028
2029
OGRErr CPL_STDCALL OSRExportToWkt(OGRSpatialReferenceH hSRS, char **ppszReturn)
2030
2031
253
{
2032
253
    VALIDATE_POINTER1(hSRS, "OSRExportToWkt", OGRERR_FAILURE);
2033
2034
253
    *ppszReturn = nullptr;
2035
2036
253
    return ToPointer(hSRS)->exportToWkt(ppszReturn);
2037
253
}
2038
2039
/************************************************************************/
2040
/*                          OSRExportToWktEx()                          */
2041
/************************************************************************/
2042
2043
/**
2044
 * \brief Convert this SRS into WKT format.
2045
 *
2046
 * This function is the same as OGRSpatialReference::exportToWkt(char **
2047
 * ppszResult,const char* const* papszOptions ) const
2048
 *
2049
 * @since GDAL 3.0
2050
 */
2051
2052
OGRErr OSRExportToWktEx(OGRSpatialReferenceH hSRS, char **ppszReturn,
2053
                        const char *const *papszOptions)
2054
0
{
2055
0
    VALIDATE_POINTER1(hSRS, "OSRExportToWktEx", OGRERR_FAILURE);
2056
2057
0
    *ppszReturn = nullptr;
2058
2059
0
    return ToPointer(hSRS)->exportToWkt(ppszReturn, papszOptions);
2060
0
}
2061
2062
/************************************************************************/
2063
/*                          exportToPROJJSON()                          */
2064
/************************************************************************/
2065
2066
/**
2067
 * Convert this SRS into a PROJJSON string.
2068
 *
2069
 * Note that the returned JSON string should be freed with
2070
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
2071
 *
2072
 * @param ppszResult the resulting string is returned in this pointer.
2073
 * @param papszOptions NULL terminated list of options, or NULL. Currently
2074
 * supported options are
2075
 * <ul>
2076
 * <li>MULTILINE=YES/NO. Defaults to YES</li>
2077
 * <li>INDENTATION_WIDTH=number. Defaults to 2 (when multiline output is
2078
 * on).</li>
2079
 * <li>SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to
2080
 * disable it.</li>
2081
 * </ul>
2082
 *
2083
 * @return OGRERR_NONE if successful.
2084
 * @since GDAL 3.1 and PROJ 6.2
2085
 */
2086
2087
OGRErr OGRSpatialReference::exportToPROJJSON(
2088
    char **ppszResult, CPL_UNUSED const char *const *papszOptions) const
2089
55.1k
{
2090
55.1k
    TAKE_OPTIONAL_LOCK();
2091
2092
55.1k
    d->refreshProjObj();
2093
55.1k
    if (!d->m_pj_crs)
2094
0
    {
2095
0
        *ppszResult = nullptr;
2096
0
        return OGRERR_FAILURE;
2097
0
    }
2098
2099
55.1k
    const char *pszPROJJSON =
2100
55.1k
        proj_as_projjson(d->getPROJContext(), d->m_pj_crs, papszOptions);
2101
2102
55.1k
    if (!pszPROJJSON)
2103
0
    {
2104
0
        *ppszResult = CPLStrdup("");
2105
0
        return OGRERR_FAILURE;
2106
0
    }
2107
2108
55.1k
    *ppszResult = CPLStrdup(pszPROJJSON);
2109
2110
#if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
2111
    {
2112
        // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
2113
        // related to a wrong EPSG code assigned to UTM South conversions
2114
        char *pszPtr = strstr(*ppszResult, "\"name\": \"UTM zone ");
2115
        if (pszPtr)
2116
        {
2117
            pszPtr += strlen("\"name\": \"UTM zone ");
2118
            const int nZone = atoi(pszPtr);
2119
            while (*pszPtr >= '0' && *pszPtr <= '9')
2120
                ++pszPtr;
2121
            if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' && pszPtr[1] == '"')
2122
            {
2123
                pszPtr += 2;
2124
                int nLevel = 0;
2125
                bool bInString = false;
2126
                // Find the id node corresponding to this conversion node
2127
                while (*pszPtr)
2128
                {
2129
                    if (bInString)
2130
                    {
2131
                        if (*pszPtr == '\\')
2132
                        {
2133
                            ++pszPtr;
2134
                        }
2135
                        else if (*pszPtr == '"')
2136
                        {
2137
                            bInString = false;
2138
                        }
2139
                    }
2140
                    else if (nLevel == 0 && STARTS_WITH(pszPtr, "\"id\": {"))
2141
                    {
2142
                        const char *pszNextEndCurl = strchr(pszPtr, '}');
2143
                        const char *pszAuthEPSG =
2144
                            strstr(pszPtr, "\"authority\": \"EPSG\"");
2145
                        char *pszCode = strstr(
2146
                            pszPtr, CPLSPrintf("\"code\": %d", 17000 + nZone));
2147
                        if (pszAuthEPSG && pszCode && pszNextEndCurl &&
2148
                            pszNextEndCurl - pszAuthEPSG > 0 &&
2149
                            pszNextEndCurl - pszCode > 0)
2150
                        {
2151
                            CPLAssert(pszCode[9] == '7');
2152
                            CPLAssert(pszCode[10] == '0');
2153
                            pszCode[9] = '6';
2154
                            pszCode[10] = '1';
2155
                        }
2156
                        break;
2157
                    }
2158
                    else if (*pszPtr == '"')
2159
                    {
2160
                        bInString = true;
2161
                    }
2162
                    else if (*pszPtr == '{' || *pszPtr == '[')
2163
                    {
2164
                        ++nLevel;
2165
                    }
2166
                    else if (*pszPtr == '}' || *pszPtr == ']')
2167
                    {
2168
                        --nLevel;
2169
                    }
2170
2171
                    ++pszPtr;
2172
                }
2173
            }
2174
        }
2175
    }
2176
#endif
2177
2178
55.1k
    return OGRERR_NONE;
2179
55.1k
}
2180
2181
/************************************************************************/
2182
/*                        OSRExportToPROJJSON()                         */
2183
/************************************************************************/
2184
2185
/**
2186
 * \brief Convert this SRS into PROJJSON format.
2187
 *
2188
 * This function is the same as OGRSpatialReference::exportToPROJJSON() const
2189
 *
2190
 * @since GDAL 3.1 and PROJ 6.2
2191
 */
2192
2193
OGRErr OSRExportToPROJJSON(OGRSpatialReferenceH hSRS, char **ppszReturn,
2194
                           const char *const *papszOptions)
2195
0
{
2196
0
    VALIDATE_POINTER1(hSRS, "OSRExportToPROJJSON", OGRERR_FAILURE);
2197
2198
0
    *ppszReturn = nullptr;
2199
2200
0
    return ToPointer(hSRS)->exportToPROJJSON(ppszReturn, papszOptions);
2201
0
}
2202
2203
/************************************************************************/
2204
/*                           importFromWkt()                            */
2205
/************************************************************************/
2206
2207
/**
2208
 * \brief Import from WKT string.
2209
 *
2210
 * This method will wipe the existing SRS definition, and
2211
 * reassign it based on the contents of the passed WKT string.  Only as
2212
 * much of the input string as needed to construct this SRS is consumed from
2213
 * the input string, and the input string pointer
2214
 * is then updated to point to the remaining (unused) input.
2215
 *
2216
 * Starting with PROJ 9.2, if invoked on a COORDINATEMETADATA[] construct,
2217
 * the CRS contained in it will be used to fill the OGRSpatialReference object,
2218
 * and the coordinate epoch potentially present used as the coordinate epoch
2219
 * property of the OGRSpatialReference object.
2220
 *
2221
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
2222
 * Issues</a> page for implementation details of WKT in OGR.
2223
 *
2224
 * This method is the same as the C function OSRImportFromWkt().
2225
 *
2226
 * @param ppszInput Pointer to pointer to input.  The pointer is updated to
2227
 * point to remaining unused input text.
2228
 *
2229
 * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
2230
 * fails for any reason.
2231
 */
2232
2233
OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput)
2234
2235
329k
{
2236
329k
    return importFromWkt(ppszInput, nullptr);
2237
329k
}
2238
2239
/************************************************************************/
2240
/*                           importFromWkt()                            */
2241
/************************************************************************/
2242
2243
/*! @cond Doxygen_Suppress */
2244
2245
OGRErr OGRSpatialReference::importFromWkt(const char *pszInput,
2246
                                          CSLConstList papszOptions)
2247
2248
18.3k
{
2249
18.3k
    return importFromWkt(&pszInput, papszOptions);
2250
18.3k
}
2251
2252
OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput,
2253
                                          CSLConstList papszOptions)
2254
2255
347k
{
2256
347k
    TAKE_OPTIONAL_LOCK();
2257
2258
347k
    if (!ppszInput || !*ppszInput)
2259
0
        return OGRERR_FAILURE;
2260
2261
347k
    if (strlen(*ppszInput) > 100 * 1000 &&
2262
96
        CPLTestBool(CPLGetConfigOption("OSR_IMPORT_FROM_WKT_LIMIT", "YES")))
2263
96
    {
2264
96
        CPLError(CE_Failure, CPLE_NotSupported,
2265
96
                 "Suspiciously large input for importFromWkt(). Rejecting it. "
2266
96
                 "You can remove this limitation by definition the "
2267
96
                 "OSR_IMPORT_FROM_WKT_LIMIT configuration option to NO.");
2268
96
        return OGRERR_FAILURE;
2269
96
    }
2270
2271
347k
    Clear();
2272
2273
347k
    bool canCache = false;
2274
347k
    auto tlsCache = OSRGetProjTLSCache();
2275
347k
    std::string osWkt;
2276
347k
    if (**ppszInput)
2277
347k
    {
2278
347k
        osWkt = *ppszInput;
2279
347k
        auto cachedObj = tlsCache->GetPJForWKT(osWkt);
2280
347k
        if (cachedObj)
2281
228k
        {
2282
228k
            d->setPjCRS(cachedObj);
2283
228k
        }
2284
118k
        else
2285
118k
        {
2286
118k
            CPLStringList aosOptions(papszOptions);
2287
118k
            if (aosOptions.FetchNameValue("STRICT") == nullptr)
2288
118k
                aosOptions.SetNameValue("STRICT", "NO");
2289
118k
            PROJ_STRING_LIST warnings = nullptr;
2290
118k
            PROJ_STRING_LIST errors = nullptr;
2291
118k
            auto ctxt = d->getPROJContext();
2292
118k
            auto pj = proj_create_from_wkt(ctxt, *ppszInput, aosOptions.List(),
2293
118k
                                           &warnings, &errors);
2294
118k
            d->setPjCRS(pj);
2295
2296
614k
            for (auto iter = warnings; iter && *iter; ++iter)
2297
496k
            {
2298
496k
                d->m_wktImportWarnings.push_back(*iter);
2299
496k
            }
2300
230k
            for (auto iter = errors; iter && *iter; ++iter)
2301
111k
            {
2302
111k
                d->m_wktImportErrors.push_back(*iter);
2303
111k
                if (!d->m_pj_crs)
2304
68.1k
                {
2305
68.1k
                    CPLError(CE_Failure, CPLE_AppDefined, "%s", *iter);
2306
68.1k
                }
2307
111k
            }
2308
118k
            if (warnings == nullptr && errors == nullptr)
2309
4.87k
            {
2310
4.87k
                canCache = true;
2311
4.87k
            }
2312
118k
            proj_string_list_destroy(warnings);
2313
118k
            proj_string_list_destroy(errors);
2314
118k
        }
2315
347k
    }
2316
347k
    if (!d->m_pj_crs)
2317
68.1k
        return OGRERR_CORRUPT_DATA;
2318
2319
    // Only accept CRS objects
2320
279k
    if (!proj_is_crs(d->m_pj_crs))
2321
2.37k
    {
2322
2.37k
        Clear();
2323
2.37k
        return OGRERR_CORRUPT_DATA;
2324
2.37k
    }
2325
2326
277k
    if (canCache)
2327
4.84k
    {
2328
4.84k
        tlsCache->CachePJForWKT(osWkt, d->m_pj_crs);
2329
4.84k
    }
2330
2331
277k
    if (strstr(*ppszInput, "CENTER_LONG"))
2332
10.2k
    {
2333
10.2k
        auto poRoot = new OGR_SRSNode();
2334
10.2k
        d->setRoot(poRoot);
2335
10.2k
        const char *pszTmp = *ppszInput;
2336
10.2k
        poRoot->importFromWkt(&pszTmp);
2337
10.2k
        d->m_bHasCenterLong = true;
2338
10.2k
    }
2339
2340
    // TODO? we don't really update correctly since we assume that the
2341
    // passed string is only WKT.
2342
277k
    *ppszInput += strlen(*ppszInput);
2343
277k
    return OGRERR_NONE;
2344
2345
#if no_longer_implemented_for_now
2346
    /* -------------------------------------------------------------------- */
2347
    /*      The following seems to try and detect and unconsumed            */
2348
    /*      VERTCS[] coordinate system definition (ESRI style) and to       */
2349
    /*      import and attach it to the existing root.  Likely we will      */
2350
    /*      need to extend this somewhat to bring it into an acceptable     */
2351
    /*      OGRSpatialReference organization at some point.                 */
2352
    /* -------------------------------------------------------------------- */
2353
    if (strlen(*ppszInput) > 0 && strstr(*ppszInput, "VERTCS"))
2354
    {
2355
        if (((*ppszInput)[0]) == ',')
2356
            (*ppszInput)++;
2357
        OGR_SRSNode *poNewChild = new OGR_SRSNode();
2358
        poRoot->AddChild(poNewChild);
2359
        return poNewChild->importFromWkt(ppszInput);
2360
    }
2361
#endif
2362
279k
}
2363
2364
/*! @endcond */
2365
2366
/**
2367
 * \brief Import from WKT string.
2368
 *
2369
 * This method will wipe the existing SRS definition, and
2370
 * reassign it based on the contents of the passed WKT string.  Only as
2371
 * much of the input string as needed to construct this SRS is consumed from
2372
 * the input string, and the input string pointer
2373
 * is then updated to point to the remaining (unused) input.
2374
 *
2375
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
2376
 * Issues</a> page for implementation details of WKT in OGR.
2377
 *
2378
 * This method is the same as the C function OSRImportFromWkt().
2379
 *
2380
 * @param ppszInput Pointer to pointer to input.  The pointer is updated to
2381
 * point to remaining unused input text.
2382
 *
2383
 * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
2384
 * fails for any reason.
2385
 * @deprecated Use importFromWkt(const char**) or importFromWkt(const
2386
 * char*)
2387
 */
2388
2389
OGRErr OGRSpatialReference::importFromWkt(char **ppszInput)
2390
2391
0
{
2392
0
    return importFromWkt(const_cast<const char **>(ppszInput));
2393
0
}
2394
2395
/**
2396
 * \brief Import from WKT string.
2397
 *
2398
 * This method will wipe the existing SRS definition, and
2399
 * reassign it based on the contents of the passed WKT string.  Only as
2400
 * much of the input string as needed to construct this SRS is consumed from
2401
 * the input string, and the input string pointer
2402
 * is then updated to point to the remaining (unused) input.
2403
 *
2404
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
2405
 * Issues</a> page for implementation details of WKT in OGR.
2406
 *
2407
 * @param pszInput Input WKT
2408
 *
2409
 * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
2410
 * fails for any reason.
2411
 */
2412
2413
OGRErr OGRSpatialReference::importFromWkt(const char *pszInput)
2414
329k
{
2415
329k
    return importFromWkt(&pszInput);
2416
329k
}
2417
2418
/************************************************************************/
2419
/*                              Validate()                              */
2420
/************************************************************************/
2421
2422
/**
2423
 * \brief Validate CRS imported with importFromWkt() or with modified with
2424
 * direct node manipulations. Otherwise the CRS should be always valid.
2425
 *
2426
 * This method attempts to verify that the spatial reference system is
2427
 * well formed, and consists of known tokens.  The validation is not
2428
 * comprehensive.
2429
 *
2430
 * This method is the same as the C function OSRValidate().
2431
 *
2432
 * @return OGRERR_NONE if all is fine, OGRERR_CORRUPT_DATA if the SRS is
2433
 * not well formed, and OGRERR_UNSUPPORTED_SRS if the SRS is well formed,
2434
 * but contains non-standard PROJECTION[] values.
2435
 */
2436
2437
OGRErr OGRSpatialReference::Validate() const
2438
2439
0
{
2440
0
    TAKE_OPTIONAL_LOCK();
2441
2442
0
    for (const auto &str : d->m_wktImportErrors)
2443
0
    {
2444
0
        CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
2445
0
    }
2446
0
    for (const auto &str : d->m_wktImportWarnings)
2447
0
    {
2448
0
        CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
2449
0
    }
2450
0
    if (!d->m_pj_crs || !d->m_wktImportErrors.empty())
2451
0
    {
2452
0
        return OGRERR_CORRUPT_DATA;
2453
0
    }
2454
0
    if (!d->m_wktImportWarnings.empty())
2455
0
    {
2456
0
        return OGRERR_UNSUPPORTED_SRS;
2457
0
    }
2458
0
    return OGRERR_NONE;
2459
0
}
2460
2461
/************************************************************************/
2462
/*                            OSRValidate()                             */
2463
/************************************************************************/
2464
/**
2465
 * \brief Validate SRS tokens.
2466
 *
2467
 * This function is the same as the C++ method OGRSpatialReference::Validate().
2468
 */
2469
OGRErr OSRValidate(OGRSpatialReferenceH hSRS)
2470
2471
0
{
2472
0
    VALIDATE_POINTER1(hSRS, "OSRValidate", OGRERR_FAILURE);
2473
2474
0
    return OGRSpatialReference::FromHandle(hSRS)->Validate();
2475
0
}
2476
2477
/************************************************************************/
2478
/*                          OSRImportFromWkt()                          */
2479
/************************************************************************/
2480
2481
/**
2482
 * \brief Import from WKT string.
2483
 *
2484
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
2485
 * Issues</a> page for implementation details of WKT in OGR.
2486
 *
2487
 * This function is the same as OGRSpatialReference::importFromWkt().
2488
 */
2489
2490
OGRErr OSRImportFromWkt(OGRSpatialReferenceH hSRS, char **ppszInput)
2491
2492
0
{
2493
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromWkt", OGRERR_FAILURE);
2494
2495
0
    return ToPointer(hSRS)->importFromWkt(const_cast<const char **>(ppszInput));
2496
0
}
2497
2498
/************************************************************************/
2499
/*                              SetNode()                               */
2500
/************************************************************************/
2501
2502
/**
2503
 * \brief Set attribute value in spatial reference.
2504
 *
2505
 * Missing intermediate nodes in the path will be created if not already
2506
 * in existence.  If the attribute has no children one will be created and
2507
 * assigned the value otherwise the zeroth child will be assigned the value.
2508
 *
2509
 * This method does the same as the C function OSRSetAttrValue().
2510
 *
2511
 * @param pszNodePath full path to attribute to be set.  For instance
2512
 * "PROJCS|GEOGCS|UNIT".
2513
 *
2514
 * @param pszNewNodeValue value to be assigned to node, such as "meter".
2515
 * This may be NULL if you just want to force creation of the intermediate
2516
 * path.
2517
 *
2518
 * @return OGRERR_NONE on success.
2519
 */
2520
2521
OGRErr OGRSpatialReference::SetNode(const char *pszNodePath,
2522
                                    const char *pszNewNodeValue)
2523
2524
94.7k
{
2525
94.7k
    TAKE_OPTIONAL_LOCK();
2526
2527
94.7k
    char **papszPathTokens =
2528
94.7k
        CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
2529
2530
94.7k
    if (CSLCount(papszPathTokens) < 1)
2531
0
    {
2532
0
        CSLDestroy(papszPathTokens);
2533
0
        return OGRERR_FAILURE;
2534
0
    }
2535
2536
94.7k
    if (GetRoot() == nullptr ||
2537
72.6k
        !EQUAL(papszPathTokens[0], GetRoot()->GetValue()))
2538
23.1k
    {
2539
23.1k
        if (EQUAL(papszPathTokens[0], "PROJCS") &&
2540
8.29k
            CSLCount(papszPathTokens) == 1)
2541
7.92k
        {
2542
7.92k
            CSLDestroy(papszPathTokens);
2543
7.92k
            return SetProjCS(pszNewNodeValue);
2544
7.92k
        }
2545
15.2k
        else
2546
15.2k
        {
2547
15.2k
            SetRoot(new OGR_SRSNode(papszPathTokens[0]));
2548
15.2k
        }
2549
23.1k
    }
2550
2551
86.8k
    OGR_SRSNode *poNode = GetRoot();
2552
175k
    for (int i = 1; papszPathTokens[i] != nullptr; i++)
2553
89.1k
    {
2554
89.1k
        int j = 0;  // Used after for.
2555
2556
261k
        for (; j < poNode->GetChildCount(); j++)
2557
217k
        {
2558
217k
            if (EQUAL(poNode->GetChild(j)->GetValue(), papszPathTokens[i]))
2559
44.7k
            {
2560
44.7k
                poNode = poNode->GetChild(j);
2561
44.7k
                j = -1;
2562
44.7k
                break;
2563
44.7k
            }
2564
217k
        }
2565
2566
89.1k
        if (j != -1)
2567
44.4k
        {
2568
44.4k
            OGR_SRSNode *poNewNode = new OGR_SRSNode(papszPathTokens[i]);
2569
44.4k
            poNode->AddChild(poNewNode);
2570
44.4k
            poNode = poNewNode;
2571
44.4k
        }
2572
89.1k
    }
2573
2574
86.8k
    CSLDestroy(papszPathTokens);
2575
2576
86.8k
    if (pszNewNodeValue != nullptr)
2577
86.8k
    {
2578
86.8k
        if (poNode->GetChildCount() > 0)
2579
27.5k
            poNode->GetChild(0)->SetValue(pszNewNodeValue);
2580
59.3k
        else
2581
59.3k
            poNode->AddChild(new OGR_SRSNode(pszNewNodeValue));
2582
86.8k
    };
2583
86.8k
    return OGRERR_NONE;
2584
94.7k
}
2585
2586
/************************************************************************/
2587
/*                          OSRSetAttrValue()                           */
2588
/************************************************************************/
2589
2590
/**
2591
 * \brief Set attribute value in spatial reference.
2592
 *
2593
 * This function is the same as OGRSpatialReference::SetNode()
2594
 */
2595
OGRErr CPL_STDCALL OSRSetAttrValue(OGRSpatialReferenceH hSRS,
2596
                                   const char *pszPath, const char *pszValue)
2597
2598
0
{
2599
0
    VALIDATE_POINTER1(hSRS, "OSRSetAttrValue", OGRERR_FAILURE);
2600
2601
0
    return ToPointer(hSRS)->SetNode(pszPath, pszValue);
2602
0
}
2603
2604
/************************************************************************/
2605
/*                              SetNode()                               */
2606
/************************************************************************/
2607
2608
/**
2609
 * \brief Set attribute value in spatial reference.
2610
 *
2611
 * Missing intermediate nodes in the path will be created if not already
2612
 * in existence.  If the attribute has no children one will be created and
2613
 * assigned the value otherwise the zeroth child will be assigned the value.
2614
 *
2615
 * This method does the same as the C function OSRSetAttrValue().
2616
 *
2617
 * @param pszNodePath full path to attribute to be set.  For instance
2618
 * "PROJCS|GEOGCS|UNIT".
2619
 *
2620
 * @param dfValue value to be assigned to node.
2621
 *
2622
 * @return OGRERR_NONE on success.
2623
 */
2624
2625
OGRErr OGRSpatialReference::SetNode(const char *pszNodePath, double dfValue)
2626
2627
0
{
2628
0
    char szValue[64] = {'\0'};
2629
2630
0
    if (std::abs(dfValue - static_cast<int>(dfValue)) == 0.0)
2631
0
        snprintf(szValue, sizeof(szValue), "%d", static_cast<int>(dfValue));
2632
0
    else
2633
0
        OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
2634
2635
0
    return SetNode(pszNodePath, szValue);
2636
0
}
2637
2638
/************************************************************************/
2639
/*                          SetAngularUnits()                           */
2640
/************************************************************************/
2641
2642
/**
2643
 * \brief Set the angular units for the geographic coordinate system.
2644
 *
2645
 * This method creates a UNIT subnode with the specified values as a
2646
 * child of the GEOGCS node.
2647
 *
2648
 * This method does the same as the C function OSRSetAngularUnits().
2649
 *
2650
 * @param pszUnitsName the units name to be used.  Some preferred units
2651
 * names can be found in ogr_srs_api.h such as SRS_UA_DEGREE.
2652
 *
2653
 * @param dfInRadians the value to multiple by an angle in the indicated
2654
 * units to transform to radians.  Some standard conversion factors can
2655
 * be found in ogr_srs_api.h.
2656
 *
2657
 * @return OGRERR_NONE on success.
2658
 */
2659
2660
OGRErr OGRSpatialReference::SetAngularUnits(const char *pszUnitsName,
2661
                                            double dfInRadians)
2662
2663
294
{
2664
294
    TAKE_OPTIONAL_LOCK();
2665
2666
294
    d->bNormInfoSet = FALSE;
2667
2668
294
    d->refreshProjObj();
2669
294
    if (!d->m_pj_crs)
2670
0
        return OGRERR_FAILURE;
2671
294
    auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
2672
294
    if (!geodCRS)
2673
0
        return OGRERR_FAILURE;
2674
294
    proj_destroy(geodCRS);
2675
294
    d->demoteFromBoundCRS();
2676
294
    d->setPjCRS(proj_crs_alter_cs_angular_unit(d->getPROJContext(), d->m_pj_crs,
2677
294
                                               pszUnitsName, dfInRadians,
2678
294
                                               nullptr, nullptr));
2679
294
    d->undoDemoteFromBoundCRS();
2680
2681
294
    d->m_osAngularUnits = pszUnitsName;
2682
294
    d->m_dfAngularUnitToRadian = dfInRadians;
2683
2684
294
    return OGRERR_NONE;
2685
294
}
2686
2687
/************************************************************************/
2688
/*                         OSRSetAngularUnits()                         */
2689
/************************************************************************/
2690
2691
/**
2692
 * \brief Set the angular units for the geographic coordinate system.
2693
 *
2694
 * This function is the same as OGRSpatialReference::SetAngularUnits()
2695
 */
2696
OGRErr OSRSetAngularUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
2697
                          double dfInRadians)
2698
2699
0
{
2700
0
    VALIDATE_POINTER1(hSRS, "OSRSetAngularUnits", OGRERR_FAILURE);
2701
2702
0
    return ToPointer(hSRS)->SetAngularUnits(pszUnits, dfInRadians);
2703
0
}
2704
2705
/************************************************************************/
2706
/*                          GetAngularUnits()                           */
2707
/************************************************************************/
2708
2709
/**
2710
 * \brief Fetch angular geographic coordinate system units.
2711
 *
2712
 * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
2713
 * will be assumed.  This method only checks directly under the GEOGCS node
2714
 * for units.
2715
 *
2716
 * This method does the same thing as the C function OSRGetAngularUnits().
2717
 *
2718
 * @param ppszName a pointer to be updated with the pointer to the units name.
2719
 * The returned value remains internal to the OGRSpatialReference and should
2720
 * not be freed, or modified.  It may be invalidated on the next
2721
 * OGRSpatialReference call.
2722
 *
2723
 * @return the value to multiply by angular distances to transform them to
2724
 * radians.
2725
 */
2726
2727
double OGRSpatialReference::GetAngularUnits(const char **ppszName) const
2728
2729
12.2k
{
2730
12.2k
    TAKE_OPTIONAL_LOCK();
2731
2732
12.2k
    d->refreshProjObj();
2733
2734
12.2k
    if (!d->m_osAngularUnits.empty())
2735
27
    {
2736
27
        if (ppszName != nullptr)
2737
0
            *ppszName = d->m_osAngularUnits.c_str();
2738
27
        return d->m_dfAngularUnitToRadian;
2739
27
    }
2740
2741
12.2k
    do
2742
12.2k
    {
2743
12.2k
        if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
2744
886
        {
2745
886
            break;
2746
886
        }
2747
2748
11.3k
        auto geodCRS =
2749
11.3k
            proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
2750
11.3k
        if (!geodCRS)
2751
0
        {
2752
0
            break;
2753
0
        }
2754
11.3k
        auto coordSys =
2755
11.3k
            proj_crs_get_coordinate_system(d->getPROJContext(), geodCRS);
2756
11.3k
        proj_destroy(geodCRS);
2757
11.3k
        if (!coordSys)
2758
0
        {
2759
0
            break;
2760
0
        }
2761
11.3k
        if (proj_cs_get_type(d->getPROJContext(), coordSys) !=
2762
11.3k
            PJ_CS_TYPE_ELLIPSOIDAL)
2763
1
        {
2764
1
            proj_destroy(coordSys);
2765
1
            break;
2766
1
        }
2767
2768
11.3k
        double dfConvFactor = 0.0;
2769
11.3k
        const char *pszUnitName = nullptr;
2770
11.3k
        if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
2771
11.3k
                                   nullptr, nullptr, &dfConvFactor,
2772
11.3k
                                   &pszUnitName, nullptr, nullptr))
2773
0
        {
2774
0
            proj_destroy(coordSys);
2775
0
            break;
2776
0
        }
2777
2778
11.3k
        d->m_osAngularUnits = pszUnitName;
2779
2780
11.3k
        proj_destroy(coordSys);
2781
11.3k
        d->m_dfAngularUnitToRadian = dfConvFactor;
2782
11.3k
    } while (false);
2783
2784
12.2k
    if (d->m_osAngularUnits.empty())
2785
887
    {
2786
887
        d->m_osAngularUnits = "degree";
2787
887
        d->m_dfAngularUnitToRadian = CPLAtof(SRS_UA_DEGREE_CONV);
2788
887
    }
2789
2790
12.2k
    if (ppszName != nullptr)
2791
127
        *ppszName = d->m_osAngularUnits.c_str();
2792
12.2k
    return d->m_dfAngularUnitToRadian;
2793
12.2k
}
2794
2795
/**
2796
 * \brief Fetch angular geographic coordinate system units.
2797
 *
2798
 * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
2799
 * will be assumed.  This method only checks directly under the GEOGCS node
2800
 * for units.
2801
 *
2802
 * This method does the same thing as the C function OSRGetAngularUnits().
2803
 *
2804
 * @param ppszName a pointer to be updated with the pointer to the units name.
2805
 * The returned value remains internal to the OGRSpatialReference and should
2806
 * not be freed, or modified.  It may be invalidated on the next
2807
 * OGRSpatialReference call.
2808
 *
2809
 * @return the value to multiply by angular distances to transform them to
2810
 * radians.
2811
 * @deprecated Use GetAngularUnits(const char**) const.
2812
 */
2813
2814
double OGRSpatialReference::GetAngularUnits(char **ppszName) const
2815
2816
0
{
2817
0
    return GetAngularUnits(const_cast<const char **>(ppszName));
2818
0
}
2819
2820
/************************************************************************/
2821
/*                         OSRGetAngularUnits()                         */
2822
/************************************************************************/
2823
2824
/**
2825
 * \brief Fetch angular geographic coordinate system units.
2826
 *
2827
 * This function is the same as OGRSpatialReference::GetAngularUnits()
2828
 */
2829
double OSRGetAngularUnits(OGRSpatialReferenceH hSRS, char **ppszName)
2830
2831
0
{
2832
0
    VALIDATE_POINTER1(hSRS, "OSRGetAngularUnits", 0);
2833
2834
0
    return ToPointer(hSRS)->GetAngularUnits(
2835
0
        const_cast<const char **>(ppszName));
2836
0
}
2837
2838
/************************************************************************/
2839
/*                 SetLinearUnitsAndUpdateParameters()                  */
2840
/************************************************************************/
2841
2842
/**
2843
 * \brief Set the linear units for the projection.
2844
 *
2845
 * This method creates a UNIT subnode with the specified values as a
2846
 * child of the PROJCS or LOCAL_CS node.   It works the same as the
2847
 * SetLinearUnits() method, but it also updates all existing linear
2848
 * projection parameter values from the old units to the new units.
2849
 *
2850
 * @param pszName the units name to be used.  Some preferred units
2851
 * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
2852
 * and SRS_UL_US_FOOT.
2853
 *
2854
 * @param dfInMeters the value to multiple by a length in the indicated
2855
 * units to transform to meters.  Some standard conversion factors can
2856
 * be found in ogr_srs_api.h.
2857
 *
2858
 * @param pszUnitAuthority Unit authority name. Or nullptr
2859
 *
2860
 * @param pszUnitCode Unit code. Or nullptr
2861
 *
2862
 * @return OGRERR_NONE on success.
2863
 */
2864
2865
OGRErr OGRSpatialReference::SetLinearUnitsAndUpdateParameters(
2866
    const char *pszName, double dfInMeters, const char *pszUnitAuthority,
2867
    const char *pszUnitCode)
2868
2869
25.7k
{
2870
25.7k
    TAKE_OPTIONAL_LOCK();
2871
2872
25.7k
    if (dfInMeters <= 0.0)
2873
256
        return OGRERR_FAILURE;
2874
2875
25.5k
    d->refreshProjObj();
2876
25.5k
    if (!d->m_pj_crs)
2877
66
        return OGRERR_FAILURE;
2878
2879
25.4k
    d->demoteFromBoundCRS();
2880
25.4k
    if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
2881
16.5k
    {
2882
16.5k
        d->setPjCRS(proj_crs_alter_parameters_linear_unit(
2883
16.5k
            d->getPROJContext(), d->m_pj_crs, pszName, dfInMeters,
2884
16.5k
            pszUnitAuthority, pszUnitCode, true));
2885
16.5k
    }
2886
25.4k
    d->setPjCRS(proj_crs_alter_cs_linear_unit(d->getPROJContext(), d->m_pj_crs,
2887
25.4k
                                              pszName, dfInMeters,
2888
25.4k
                                              pszUnitAuthority, pszUnitCode));
2889
25.4k
    d->undoDemoteFromBoundCRS();
2890
2891
25.4k
    d->m_osLinearUnits = pszName;
2892
25.4k
    d->dfToMeter = dfInMeters;
2893
2894
25.4k
    return OGRERR_NONE;
2895
25.5k
}
2896
2897
/************************************************************************/
2898
/*                OSRSetLinearUnitsAndUpdateParameters()                */
2899
/************************************************************************/
2900
2901
/**
2902
 * \brief Set the linear units for the projection.
2903
 *
2904
 * This function is the same as
2905
 *   OGRSpatialReference::SetLinearUnitsAndUpdateParameters()
2906
 */
2907
OGRErr OSRSetLinearUnitsAndUpdateParameters(OGRSpatialReferenceH hSRS,
2908
                                            const char *pszUnits,
2909
                                            double dfInMeters)
2910
2911
0
{
2912
0
    VALIDATE_POINTER1(hSRS, "OSRSetLinearUnitsAndUpdateParameters",
2913
0
                      OGRERR_FAILURE);
2914
2915
0
    return ToPointer(hSRS)->SetLinearUnitsAndUpdateParameters(pszUnits,
2916
0
                                                              dfInMeters);
2917
0
}
2918
2919
/************************************************************************/
2920
/*                           SetLinearUnits()                           */
2921
/************************************************************************/
2922
2923
/**
2924
 * \brief Set the linear units for the projection.
2925
 *
2926
 * This method creates a UNIT subnode with the specified values as a
2927
 * child of the PROJCS, GEOCCS, GEOGCS or LOCAL_CS node. When called on a
2928
 * Geographic 3D CRS the vertical axis units will be set.
2929
 *
2930
 * This method does the same as the C function OSRSetLinearUnits().
2931
 *
2932
 * @param pszUnitsName the units name to be used.  Some preferred units
2933
 * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
2934
 * and SRS_UL_US_FOOT.
2935
 *
2936
 * @param dfInMeters the value to multiple by a length in the indicated
2937
 * units to transform to meters.  Some standard conversion factors can
2938
 * be found in ogr_srs_api.h.
2939
 *
2940
 * @return OGRERR_NONE on success.
2941
 */
2942
2943
OGRErr OGRSpatialReference::SetLinearUnits(const char *pszUnitsName,
2944
                                           double dfInMeters)
2945
2946
92.0k
{
2947
92.0k
    return SetTargetLinearUnits(nullptr, pszUnitsName, dfInMeters);
2948
92.0k
}
2949
2950
/************************************************************************/
2951
/*                         OSRSetLinearUnits()                          */
2952
/************************************************************************/
2953
2954
/**
2955
 * \brief Set the linear units for the projection.
2956
 *
2957
 * This function is the same as OGRSpatialReference::SetLinearUnits()
2958
 */
2959
OGRErr OSRSetLinearUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
2960
                         double dfInMeters)
2961
2962
0
{
2963
0
    VALIDATE_POINTER1(hSRS, "OSRSetLinearUnits", OGRERR_FAILURE);
2964
2965
0
    return ToPointer(hSRS)->SetLinearUnits(pszUnits, dfInMeters);
2966
0
}
2967
2968
/************************************************************************/
2969
/*                        SetTargetLinearUnits()                        */
2970
/************************************************************************/
2971
2972
/**
2973
 * \brief Set the linear units for the projection.
2974
 *
2975
 * This method creates a UNIT subnode with the specified values as a
2976
 * child of the target node.
2977
 *
2978
 * This method does the same as the C function OSRSetTargetLinearUnits().
2979
 *
2980
 * @param pszTargetKey the keyword to set the linear units for.
2981
 * i.e. "PROJCS" or "VERT_CS"
2982
 *
2983
 * @param pszUnitsName the units name to be used.  Some preferred units
2984
 * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
2985
 * and SRS_UL_US_FOOT.
2986
 *
2987
 * @param dfInMeters the value to multiple by a length in the indicated
2988
 * units to transform to meters.  Some standard conversion factors can
2989
 * be found in ogr_srs_api.h.
2990
 *
2991
 * @param pszUnitAuthority Unit authority name. Or nullptr
2992
 *
2993
 * @param pszUnitCode Unit code. Or nullptr
2994
 *
2995
 * @return OGRERR_NONE on success.
2996
 *
2997
 */
2998
2999
OGRErr OGRSpatialReference::SetTargetLinearUnits(const char *pszTargetKey,
3000
                                                 const char *pszUnitsName,
3001
                                                 double dfInMeters,
3002
                                                 const char *pszUnitAuthority,
3003
                                                 const char *pszUnitCode)
3004
3005
108k
{
3006
108k
    TAKE_OPTIONAL_LOCK();
3007
3008
108k
    if (dfInMeters <= 0.0)
3009
1.79k
        return OGRERR_FAILURE;
3010
3011
106k
    d->refreshProjObj();
3012
106k
    pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
3013
106k
    if (pszTargetKey == nullptr)
3014
106k
    {
3015
106k
        if (!d->m_pj_crs)
3016
498
            return OGRERR_FAILURE;
3017
3018
105k
        d->demoteFromBoundCRS();
3019
105k
        if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
3020
85.9k
        {
3021
85.9k
            d->setPjCRS(proj_crs_alter_parameters_linear_unit(
3022
85.9k
                d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
3023
85.9k
                pszUnitAuthority, pszUnitCode, false));
3024
85.9k
        }
3025
105k
        d->setPjCRS(proj_crs_alter_cs_linear_unit(
3026
105k
            d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
3027
105k
            pszUnitAuthority, pszUnitCode));
3028
105k
        d->undoDemoteFromBoundCRS();
3029
3030
105k
        d->m_osLinearUnits = pszUnitsName;
3031
105k
        d->dfToMeter = dfInMeters;
3032
3033
105k
        return OGRERR_NONE;
3034
106k
    }
3035
3036
0
    OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
3037
3038
0
    if (poCS == nullptr)
3039
0
        return OGRERR_FAILURE;
3040
3041
0
    char szValue[128] = {'\0'};
3042
0
    if (dfInMeters < std::numeric_limits<int>::max() &&
3043
0
        dfInMeters > std::numeric_limits<int>::min() &&
3044
0
        dfInMeters == static_cast<int>(dfInMeters))
3045
0
        snprintf(szValue, sizeof(szValue), "%d", static_cast<int>(dfInMeters));
3046
0
    else
3047
0
        OGRsnPrintDouble(szValue, sizeof(szValue), dfInMeters);
3048
3049
0
    OGR_SRSNode *poUnits = nullptr;
3050
0
    if (poCS->FindChild("UNIT") >= 0)
3051
0
    {
3052
0
        poUnits = poCS->GetChild(poCS->FindChild("UNIT"));
3053
0
        if (poUnits->GetChildCount() < 2)
3054
0
            return OGRERR_FAILURE;
3055
0
        poUnits->GetChild(0)->SetValue(pszUnitsName);
3056
0
        poUnits->GetChild(1)->SetValue(szValue);
3057
0
        if (poUnits->FindChild("AUTHORITY") != -1)
3058
0
            poUnits->DestroyChild(poUnits->FindChild("AUTHORITY"));
3059
0
    }
3060
0
    else
3061
0
    {
3062
0
        poUnits = new OGR_SRSNode("UNIT");
3063
0
        poUnits->AddChild(new OGR_SRSNode(pszUnitsName));
3064
0
        poUnits->AddChild(new OGR_SRSNode(szValue));
3065
3066
0
        poCS->AddChild(poUnits);
3067
0
    }
3068
3069
0
    return OGRERR_NONE;
3070
0
}
3071
3072
/************************************************************************/
3073
/*                         OSRSetLinearUnits()                          */
3074
/************************************************************************/
3075
3076
/**
3077
 * \brief Set the linear units for the target node.
3078
 *
3079
 * This function is the same as OGRSpatialReference::SetTargetLinearUnits()
3080
 *
3081
 */
3082
OGRErr OSRSetTargetLinearUnits(OGRSpatialReferenceH hSRS,
3083
                               const char *pszTargetKey, const char *pszUnits,
3084
                               double dfInMeters)
3085
3086
0
{
3087
0
    VALIDATE_POINTER1(hSRS, "OSRSetTargetLinearUnits", OGRERR_FAILURE);
3088
3089
0
    return ToPointer(hSRS)->SetTargetLinearUnits(pszTargetKey, pszUnits,
3090
0
                                                 dfInMeters);
3091
0
}
3092
3093
/************************************************************************/
3094
/*                           GetLinearUnits()                           */
3095
/************************************************************************/
3096
3097
/**
3098
 * \brief Fetch linear projection units.
3099
 *
3100
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
3101
 * This method only checks directly under the PROJCS, GEOCCS, GEOGCS or
3102
 * LOCAL_CS node for units. When called on a Geographic 3D CRS the vertical
3103
 * axis units will be returned.
3104
 *
3105
 * This method does the same thing as the C function OSRGetLinearUnits()
3106
 *
3107
 * @param ppszName a pointer to be updated with the pointer to the units name.
3108
 * The returned value remains internal to the OGRSpatialReference and should
3109
 * not be freed, or modified.  It may be invalidated on the next
3110
 * OGRSpatialReference call.
3111
 *
3112
 * @return the value to multiply by linear distances to transform them to
3113
 * meters.
3114
 * @deprecated Use GetLinearUnits(const char**) const.
3115
 */
3116
3117
double OGRSpatialReference::GetLinearUnits(char **ppszName) const
3118
3119
0
{
3120
0
    return GetTargetLinearUnits(nullptr, const_cast<const char **>(ppszName));
3121
0
}
3122
3123
/**
3124
 * \brief Fetch linear projection units.
3125
 *
3126
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
3127
 * This method only checks directly under the PROJCS, GEOCCS or LOCAL_CS node
3128
 * for units.
3129
 *
3130
 * This method does the same thing as the C function OSRGetLinearUnits()
3131
 *
3132
 * @param ppszName a pointer to be updated with the pointer to the units name.
3133
 * The returned value remains internal to the OGRSpatialReference and should
3134
 * not be freed, or modified.  It may be invalidated on the next
3135
 * OGRSpatialReference call.
3136
 *
3137
 * @return the value to multiply by linear distances to transform them to
3138
 * meters.
3139
 */
3140
3141
double OGRSpatialReference::GetLinearUnits(const char **ppszName) const
3142
3143
106k
{
3144
106k
    return GetTargetLinearUnits(nullptr, ppszName);
3145
106k
}
3146
3147
/************************************************************************/
3148
/*                         OSRGetLinearUnits()                          */
3149
/************************************************************************/
3150
3151
/**
3152
 * \brief Fetch linear projection units.
3153
 *
3154
 * This function is the same as OGRSpatialReference::GetLinearUnits()
3155
 */
3156
double OSRGetLinearUnits(OGRSpatialReferenceH hSRS, char **ppszName)
3157
3158
0
{
3159
0
    VALIDATE_POINTER1(hSRS, "OSRGetLinearUnits", 0);
3160
3161
0
    return ToPointer(hSRS)->GetLinearUnits(const_cast<const char **>(ppszName));
3162
0
}
3163
3164
/************************************************************************/
3165
/*                        GetTargetLinearUnits()                        */
3166
/************************************************************************/
3167
3168
/**
3169
 * \brief Fetch linear units for target.
3170
 *
3171
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
3172
 *
3173
 * This method does the same thing as the C function OSRGetTargetLinearUnits()
3174
 *
3175
 * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
3176
 * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
3177
 * GEOCCS, GEOGCS and VERT_CS are looked up)
3178
 * @param ppszName a pointer to be updated with the pointer to the units name.
3179
 * The returned value remains internal to the OGRSpatialReference and should not
3180
 * be freed, or modified.  It may be invalidated on the next
3181
 * OGRSpatialReference call. ppszName can be set to NULL.
3182
 *
3183
 * @return the value to multiply by linear distances to transform them to
3184
 * meters.
3185
 *
3186
 * @deprecated Use GetTargetLinearUnits(const char*, const char**)
3187
 * const.
3188
 */
3189
3190
double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
3191
                                                 const char **ppszName) const
3192
3193
134k
{
3194
134k
    TAKE_OPTIONAL_LOCK();
3195
3196
134k
    d->refreshProjObj();
3197
3198
134k
    pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
3199
134k
    if (pszTargetKey == nullptr)
3200
119k
    {
3201
        // Use cached result if available
3202
119k
        if (!d->m_osLinearUnits.empty())
3203
55.8k
        {
3204
55.8k
            if (ppszName)
3205
24.3k
                *ppszName = d->m_osLinearUnits.c_str();
3206
55.8k
            return d->dfToMeter;
3207
55.8k
        }
3208
3209
63.7k
        while (true)
3210
63.7k
        {
3211
63.7k
            if (d->m_pj_crs == nullptr)
3212
20.1k
            {
3213
20.1k
                break;
3214
20.1k
            }
3215
3216
43.5k
            d->demoteFromBoundCRS();
3217
43.5k
            PJ *coordSys = nullptr;
3218
43.5k
            if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
3219
844
            {
3220
897
                for (int iComponent = 0; iComponent < 2; iComponent++)
3221
897
                {
3222
897
                    auto subCRS = proj_crs_get_sub_crs(d->getPROJContext(),
3223
897
                                                       d->m_pj_crs, iComponent);
3224
897
                    if (subCRS && proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
3225
62
                    {
3226
62
                        auto temp =
3227
62
                            proj_get_source_crs(d->getPROJContext(), subCRS);
3228
62
                        proj_destroy(subCRS);
3229
62
                        subCRS = temp;
3230
62
                    }
3231
897
                    if (subCRS &&
3232
897
                        (proj_get_type(subCRS) == PJ_TYPE_PROJECTED_CRS ||
3233
106
                         proj_get_type(subCRS) == PJ_TYPE_ENGINEERING_CRS ||
3234
106
                         proj_get_type(subCRS) == PJ_TYPE_VERTICAL_CRS))
3235
844
                    {
3236
844
                        coordSys = proj_crs_get_coordinate_system(
3237
844
                            d->getPROJContext(), subCRS);
3238
844
                        proj_destroy(subCRS);
3239
844
                        break;
3240
844
                    }
3241
53
                    else if (subCRS)
3242
53
                    {
3243
53
                        proj_destroy(subCRS);
3244
53
                    }
3245
897
                }
3246
844
                if (coordSys == nullptr)
3247
0
                {
3248
0
                    d->undoDemoteFromBoundCRS();
3249
0
                    break;
3250
0
                }
3251
844
            }
3252
42.7k
            else
3253
42.7k
            {
3254
42.7k
                coordSys = proj_crs_get_coordinate_system(d->getPROJContext(),
3255
42.7k
                                                          d->m_pj_crs);
3256
42.7k
            }
3257
3258
43.5k
            d->undoDemoteFromBoundCRS();
3259
43.5k
            if (!coordSys)
3260
190
            {
3261
190
                break;
3262
190
            }
3263
43.3k
            auto csType = proj_cs_get_type(d->getPROJContext(), coordSys);
3264
3265
43.3k
            if (csType != PJ_CS_TYPE_CARTESIAN &&
3266
1.03k
                csType != PJ_CS_TYPE_VERTICAL &&
3267
756
                csType != PJ_CS_TYPE_ELLIPSOIDAL &&
3268
0
                csType != PJ_CS_TYPE_SPHERICAL)
3269
0
            {
3270
0
                proj_destroy(coordSys);
3271
0
                break;
3272
0
            }
3273
3274
43.3k
            int axis = 0;
3275
3276
43.3k
            if (csType == PJ_CS_TYPE_ELLIPSOIDAL ||
3277
42.6k
                csType == PJ_CS_TYPE_SPHERICAL)
3278
756
            {
3279
756
                const int axisCount =
3280
756
                    proj_cs_get_axis_count(d->getPROJContext(), coordSys);
3281
3282
756
                if (axisCount == 3)
3283
229
                {
3284
229
                    axis = 2;
3285
229
                }
3286
527
                else
3287
527
                {
3288
527
                    proj_destroy(coordSys);
3289
527
                    break;
3290
527
                }
3291
756
            }
3292
3293
42.8k
            double dfConvFactor = 0.0;
3294
42.8k
            const char *pszUnitName = nullptr;
3295
42.8k
            if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, axis,
3296
42.8k
                                       nullptr, nullptr, nullptr, &dfConvFactor,
3297
42.8k
                                       &pszUnitName, nullptr, nullptr))
3298
0
            {
3299
0
                proj_destroy(coordSys);
3300
0
                break;
3301
0
            }
3302
3303
42.8k
            d->m_osLinearUnits = pszUnitName;
3304
42.8k
            d->dfToMeter = dfConvFactor;
3305
42.8k
            if (ppszName)
3306
1.50k
                *ppszName = d->m_osLinearUnits.c_str();
3307
3308
42.8k
            proj_destroy(coordSys);
3309
42.8k
            return dfConvFactor;
3310
42.8k
        }
3311
3312
20.9k
        d->m_osLinearUnits = "unknown";
3313
20.9k
        d->dfToMeter = 1.0;
3314
3315
20.9k
        if (ppszName != nullptr)
3316
18.9k
            *ppszName = d->m_osLinearUnits.c_str();
3317
20.9k
        return 1.0;
3318
63.7k
    }
3319
3320
14.4k
    const OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
3321
3322
14.4k
    if (ppszName != nullptr)
3323
14.1k
        *ppszName = "unknown";
3324
3325
14.4k
    if (poCS == nullptr)
3326
460
        return 1.0;
3327
3328
41.9k
    for (int iChild = 0; iChild < poCS->GetChildCount(); iChild++)
3329
41.9k
    {
3330
41.9k
        const OGR_SRSNode *poChild = poCS->GetChild(iChild);
3331
3332
41.9k
        if (EQUAL(poChild->GetValue(), "UNIT") && poChild->GetChildCount() >= 2)
3333
13.9k
        {
3334
13.9k
            if (ppszName != nullptr)
3335
13.9k
                *ppszName = poChild->GetChild(0)->GetValue();
3336
3337
13.9k
            return CPLAtof(poChild->GetChild(1)->GetValue());
3338
13.9k
        }
3339
41.9k
    }
3340
3341
0
    return 1.0;
3342
13.9k
}
3343
3344
/**
3345
 * \brief Fetch linear units for target.
3346
 *
3347
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
3348
 *
3349
 * This method does the same thing as the C function OSRGetTargetLinearUnits()
3350
 *
3351
 * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
3352
 * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
3353
 * GEOCCS and VERT_CS are looked up)
3354
 * @param ppszName a pointer to be updated with the pointer to the units name.
3355
 * The returned value remains internal to the OGRSpatialReference and should not
3356
 * be freed, or modified.  It may be invalidated on the next
3357
 * OGRSpatialReference call. ppszName can be set to NULL.
3358
 *
3359
 * @return the value to multiply by linear distances to transform them to
3360
 * meters.
3361
 *
3362
 */
3363
3364
double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
3365
                                                 char **ppszName) const
3366
3367
0
{
3368
0
    return GetTargetLinearUnits(pszTargetKey,
3369
0
                                const_cast<const char **>(ppszName));
3370
0
}
3371
3372
/************************************************************************/
3373
/*                      OSRGetTargetLinearUnits()                       */
3374
/************************************************************************/
3375
3376
/**
3377
 * \brief Fetch linear projection units.
3378
 *
3379
 * This function is the same as OGRSpatialReference::GetTargetLinearUnits()
3380
 *
3381
 */
3382
double OSRGetTargetLinearUnits(OGRSpatialReferenceH hSRS,
3383
                               const char *pszTargetKey, char **ppszName)
3384
3385
0
{
3386
0
    VALIDATE_POINTER1(hSRS, "OSRGetTargetLinearUnits", 0);
3387
3388
0
    return ToPointer(hSRS)->GetTargetLinearUnits(
3389
0
        pszTargetKey, const_cast<const char **>(ppszName));
3390
0
}
3391
3392
/************************************************************************/
3393
/*                          GetPrimeMeridian()                          */
3394
/************************************************************************/
3395
3396
/**
3397
 * \brief Fetch prime meridian info.
3398
 *
3399
 * Returns the offset of the prime meridian from greenwich in degrees,
3400
 * and the prime meridian name (if requested).   If no PRIMEM value exists
3401
 * in the coordinate system definition a value of "Greenwich" and an
3402
 * offset of 0.0 is assumed.
3403
 *
3404
 * If the prime meridian name is returned, the pointer is to an internal
3405
 * copy of the name. It should not be freed, altered or depended on after
3406
 * the next OGR call.
3407
 *
3408
 * This method is the same as the C function OSRGetPrimeMeridian().
3409
 *
3410
 * @param ppszName return location for prime meridian name.  If NULL, name
3411
 * is not returned.
3412
 *
3413
 * @return the offset to the GEOGCS prime meridian from greenwich in decimal
3414
 * degrees.
3415
 * @deprecated Use GetPrimeMeridian(const char**) const.
3416
 */
3417
3418
double OGRSpatialReference::GetPrimeMeridian(const char **ppszName) const
3419
3420
11.1k
{
3421
11.1k
    TAKE_OPTIONAL_LOCK();
3422
3423
11.1k
    d->refreshProjObj();
3424
3425
11.1k
    if (!d->m_osPrimeMeridianName.empty())
3426
5
    {
3427
5
        if (ppszName != nullptr)
3428
0
            *ppszName = d->m_osPrimeMeridianName.c_str();
3429
5
        return d->dfFromGreenwich;
3430
5
    }
3431
3432
11.0k
    while (true)
3433
11.0k
    {
3434
11.0k
        if (!d->m_pj_crs)
3435
886
            break;
3436
3437
10.2k
        auto pm = proj_get_prime_meridian(d->getPROJContext(), d->m_pj_crs);
3438
10.2k
        if (!pm)
3439
0
            break;
3440
3441
10.2k
        d->m_osPrimeMeridianName = proj_get_name(pm);
3442
10.2k
        if (ppszName)
3443
0
            *ppszName = d->m_osPrimeMeridianName.c_str();
3444
10.2k
        double dfLongitude = 0.0;
3445
10.2k
        double dfConvFactor = 0.0;
3446
10.2k
        proj_prime_meridian_get_parameters(
3447
10.2k
            d->getPROJContext(), pm, &dfLongitude, &dfConvFactor, nullptr);
3448
10.2k
        proj_destroy(pm);
3449
10.2k
        d->dfFromGreenwich =
3450
10.2k
            dfLongitude * dfConvFactor / CPLAtof(SRS_UA_DEGREE_CONV);
3451
10.2k
        return d->dfFromGreenwich;
3452
10.2k
    }
3453
3454
886
    d->m_osPrimeMeridianName = SRS_PM_GREENWICH;
3455
886
    d->dfFromGreenwich = 0.0;
3456
886
    if (ppszName != nullptr)
3457
0
        *ppszName = d->m_osPrimeMeridianName.c_str();
3458
886
    return d->dfFromGreenwich;
3459
11.0k
}
3460
3461
/**
3462
 * \brief Fetch prime meridian info.
3463
 *
3464
 * Returns the offset of the prime meridian from greenwich in degrees,
3465
 * and the prime meridian name (if requested).   If no PRIMEM value exists
3466
 * in the coordinate system definition a value of "Greenwich" and an
3467
 * offset of 0.0 is assumed.
3468
 *
3469
 * If the prime meridian name is returned, the pointer is to an internal
3470
 * copy of the name. It should not be freed, altered or depended on after
3471
 * the next OGR call.
3472
 *
3473
 * This method is the same as the C function OSRGetPrimeMeridian().
3474
 *
3475
 * @param ppszName return location for prime meridian name.  If NULL, name
3476
 * is not returned.
3477
 *
3478
 * @return the offset to the GEOGCS prime meridian from greenwich in decimal
3479
 * degrees.
3480
 */
3481
3482
double OGRSpatialReference::GetPrimeMeridian(char **ppszName) const
3483
3484
0
{
3485
0
    return GetPrimeMeridian(const_cast<const char **>(ppszName));
3486
0
}
3487
3488
/************************************************************************/
3489
/*                        OSRGetPrimeMeridian()                         */
3490
/************************************************************************/
3491
3492
/**
3493
 * \brief Fetch prime meridian info.
3494
 *
3495
 * This function is the same as OGRSpatialReference::GetPrimeMeridian()
3496
 */
3497
double OSRGetPrimeMeridian(OGRSpatialReferenceH hSRS, char **ppszName)
3498
3499
0
{
3500
0
    VALIDATE_POINTER1(hSRS, "OSRGetPrimeMeridian", 0);
3501
3502
0
    return ToPointer(hSRS)->GetPrimeMeridian(
3503
0
        const_cast<const char **>(ppszName));
3504
0
}
3505
3506
/************************************************************************/
3507
/*                             SetGeogCS()                              */
3508
/************************************************************************/
3509
3510
/**
3511
 * \brief Set geographic coordinate system.
3512
 *
3513
 * This method is used to set the datum, ellipsoid, prime meridian and
3514
 * angular units for a geographic coordinate system.  It can be used on its
3515
 * own to establish a geographic spatial reference, or applied to a
3516
 * projected coordinate system to establish the underlying geographic
3517
 * coordinate system.
3518
 *
3519
 * This method does the same as the C function OSRSetGeogCS().
3520
 *
3521
 * @param pszGeogName user visible name for the geographic coordinate system
3522
 * (not to serve as a key).
3523
 *
3524
 * @param pszDatumName key name for this datum.  The OpenGIS specification
3525
 * lists some known values, and otherwise EPSG datum names with a standard
3526
 * transformation are considered legal keys.
3527
 *
3528
 * @param pszSpheroidName user visible spheroid name (not to serve as a key)
3529
 *
3530
 * @param dfSemiMajor the semi major axis of the spheroid.
3531
 *
3532
 * @param dfInvFlattening the inverse flattening for the spheroid.
3533
 * This can be computed from the semi minor axis as
3534
 * 1/f = 1.0 / (1.0 - semiminor/semimajor).
3535
 *
3536
 * @param pszPMName the name of the prime meridian (not to serve as a key)
3537
 * If this is NULL a default value of "Greenwich" will be used.
3538
 *
3539
 * @param dfPMOffset the longitude of Greenwich relative to this prime
3540
 * meridian. Always in Degrees
3541
 *
3542
 * @param pszAngularUnits the angular units name (see ogr_srs_api.h for some
3543
 * standard names).  If NULL a value of "degrees" will be assumed.
3544
 *
3545
 * @param dfConvertToRadians value to multiply angular units by to transform
3546
 * them to radians.  A value of SRS_UA_DEGREE_CONV will be used if
3547
 * pszAngularUnits is NULL.
3548
 *
3549
 * @return OGRERR_NONE on success.
3550
 */
3551
3552
OGRErr OGRSpatialReference::SetGeogCS(
3553
    const char *pszGeogName, const char *pszDatumName,
3554
    const char *pszSpheroidName, double dfSemiMajor, double dfInvFlattening,
3555
    const char *pszPMName, double dfPMOffset, const char *pszAngularUnits,
3556
    double dfConvertToRadians)
3557
3558
111k
{
3559
111k
    TAKE_OPTIONAL_LOCK();
3560
3561
111k
    d->bNormInfoSet = FALSE;
3562
111k
    d->m_osAngularUnits.clear();
3563
111k
    d->m_dfAngularUnitToRadian = 0.0;
3564
111k
    d->m_osPrimeMeridianName.clear();
3565
111k
    d->dfFromGreenwich = 0.0;
3566
3567
    /* -------------------------------------------------------------------- */
3568
    /*      For a geocentric coordinate system we want to set the datum     */
3569
    /*      and ellipsoid based on the GEOGCS.  Create the GEOGCS in a      */
3570
    /*      temporary srs and use the copy method which has special         */
3571
    /*      handling for GEOCCS.                                            */
3572
    /* -------------------------------------------------------------------- */
3573
111k
    if (IsGeocentric())
3574
2.94k
    {
3575
2.94k
        OGRSpatialReference oGCS;
3576
3577
2.94k
        oGCS.SetGeogCS(pszGeogName, pszDatumName, pszSpheroidName, dfSemiMajor,
3578
2.94k
                       dfInvFlattening, pszPMName, dfPMOffset, pszAngularUnits,
3579
2.94k
                       dfConvertToRadians);
3580
2.94k
        return CopyGeogCSFrom(&oGCS);
3581
2.94k
    }
3582
3583
108k
    auto cs = proj_create_ellipsoidal_2D_cs(
3584
108k
        d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE, pszAngularUnits,
3585
108k
        dfConvertToRadians);
3586
    // Prime meridian expressed in Degree
3587
108k
    auto obj = proj_create_geographic_crs(
3588
108k
        d->getPROJContext(), pszGeogName, pszDatumName, pszSpheroidName,
3589
108k
        dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset, nullptr, 0.0, cs);
3590
108k
    proj_destroy(cs);
3591
3592
108k
    if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
3593
65.0k
        d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
3594
43.3k
    {
3595
43.3k
        d->setPjCRS(obj);
3596
43.3k
    }
3597
65.0k
    else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
3598
63.1k
    {
3599
63.1k
        d->setPjCRS(
3600
63.1k
            proj_crs_alter_geodetic_crs(d->getPROJContext(), d->m_pj_crs, obj));
3601
63.1k
        proj_destroy(obj);
3602
63.1k
    }
3603
1.90k
    else
3604
1.90k
    {
3605
1.90k
        proj_destroy(obj);
3606
1.90k
    }
3607
3608
108k
    return OGRERR_NONE;
3609
111k
}
3610
3611
/************************************************************************/
3612
/*                            OSRSetGeogCS()                            */
3613
/************************************************************************/
3614
3615
/**
3616
 * \brief Set geographic coordinate system.
3617
 *
3618
 * This function is the same as OGRSpatialReference::SetGeogCS()
3619
 */
3620
OGRErr OSRSetGeogCS(OGRSpatialReferenceH hSRS, const char *pszGeogName,
3621
                    const char *pszDatumName, const char *pszSpheroidName,
3622
                    double dfSemiMajor, double dfInvFlattening,
3623
                    const char *pszPMName, double dfPMOffset,
3624
                    const char *pszAngularUnits, double dfConvertToRadians)
3625
3626
0
{
3627
0
    VALIDATE_POINTER1(hSRS, "OSRSetGeogCS", OGRERR_FAILURE);
3628
3629
0
    return ToPointer(hSRS)->SetGeogCS(pszGeogName, pszDatumName,
3630
0
                                      pszSpheroidName, dfSemiMajor,
3631
0
                                      dfInvFlattening, pszPMName, dfPMOffset,
3632
0
                                      pszAngularUnits, dfConvertToRadians);
3633
0
}
3634
3635
/************************************************************************/
3636
/*                         SetWellKnownGeogCS()                         */
3637
/************************************************************************/
3638
3639
/**
3640
 * \brief Set a GeogCS based on well known name.
3641
 *
3642
 * This may be called on an empty OGRSpatialReference to make a geographic
3643
 * coordinate system, or on something with an existing PROJCS node to
3644
 * set the underlying geographic coordinate system of a projected coordinate
3645
 * system.
3646
 *
3647
 * The following well known text values are currently supported,
3648
 * Except for "EPSG:n", the others are without dependency on EPSG data files:
3649
 * <ul>
3650
 * <li> "EPSG:n": where n is the code a Geographic coordinate reference system.
3651
 * <li> "WGS84": same as "EPSG:4326" (axis order lat/long).
3652
 * <li> "WGS72": same as "EPSG:4322" (axis order lat/long).
3653
 * <li> "NAD83": same as "EPSG:4269" (axis order lat/long).
3654
 * <li> "NAD27": same as "EPSG:4267" (axis order lat/long).
3655
 * <li> "CRS84", "CRS:84": same as "WGS84" but with axis order long/lat.
3656
 * <li> "CRS72", "CRS:72": same as "WGS72" but with axis order long/lat.
3657
 * <li> "CRS27", "CRS:27": same as "NAD27" but with axis order long/lat.
3658
 * </ul>
3659
 *
3660
 * @param pszName name of well known geographic coordinate system.
3661
 * @return OGRERR_NONE on success, or OGRERR_FAILURE if the name isn't
3662
 * recognised, the target object is already initialized, or an EPSG value
3663
 * can't be successfully looked up.
3664
 */
3665
3666
OGRErr OGRSpatialReference::SetWellKnownGeogCS(const char *pszName)
3667
3668
49.3k
{
3669
49.3k
    TAKE_OPTIONAL_LOCK();
3670
3671
    /* -------------------------------------------------------------------- */
3672
    /*      Check for EPSG authority numbers.                               */
3673
    /* -------------------------------------------------------------------- */
3674
49.3k
    if (STARTS_WITH_CI(pszName, "EPSG:") || STARTS_WITH_CI(pszName, "EPSGA:"))
3675
5.29k
    {
3676
5.29k
        OGRSpatialReference oSRS2;
3677
5.29k
        const OGRErr eErr = oSRS2.importFromEPSG(atoi(pszName + 5));
3678
5.29k
        if (eErr != OGRERR_NONE)
3679
2.26k
            return eErr;
3680
3681
3.02k
        if (!oSRS2.IsGeographic())
3682
1.99k
            return OGRERR_FAILURE;
3683
3684
1.02k
        return CopyGeogCSFrom(&oSRS2);
3685
3.02k
    }
3686
3687
    /* -------------------------------------------------------------------- */
3688
    /*      Check for simple names.                                         */
3689
    /* -------------------------------------------------------------------- */
3690
44.0k
    const char *pszWKT = nullptr;
3691
3692
44.0k
    if (EQUAL(pszName, "WGS84"))
3693
36.6k
    {
3694
36.6k
        pszWKT = SRS_WKT_WGS84_LAT_LONG;
3695
36.6k
    }
3696
7.37k
    else if (EQUAL(pszName, "CRS84") || EQUAL(pszName, "CRS:84"))
3697
694
    {
3698
694
        pszWKT =
3699
694
            "GEOGCRS[\"WGS 84 (CRS84)\",DATUM[\"World Geodetic System 1984\","
3700
694
            "ELLIPSOID[\"WGS "
3701
694
            "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],"
3702
694
            "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
3703
694
            "CS[ellipsoidal,2],AXIS[\"geodetic longitude (Lon)\",east,ORDER[1],"
3704
694
            "ANGLEUNIT[\"degree\",0.0174532925199433]],"
3705
694
            "AXIS[\"geodetic latitude (Lat)\",north,ORDER[2],"
3706
694
            "ANGLEUNIT[\"degree\",0.0174532925199433]],"
3707
694
            "USAGE[SCOPE[\"unknown\"],AREA[\"World\"],BBOX[-90,-180,90,180]],"
3708
694
            "ID[\"OGC\",\"CRS84\"]]";
3709
694
    }
3710
6.68k
    else if (EQUAL(pszName, "WGS72"))
3711
784
        pszWKT =
3712
784
            "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
3713
784
            "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",\"7043\"]],"
3714
784
            "AUTHORITY[\"EPSG\",\"6322\"]],"
3715
784
            "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
3716
784
            "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
3717
784
            "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
3718
784
            "AUTHORITY[\"EPSG\",\"4322\"]]";
3719
3720
5.89k
    else if (EQUAL(pszName, "NAD27"))
3721
2.17k
        pszWKT =
3722
2.17k
            "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
3723
2.17k
            "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
3724
2.17k
            "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
3725
2.17k
            "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
3726
2.17k
            "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
3727
2.17k
            "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
3728
2.17k
            "AUTHORITY[\"EPSG\",\"4267\"]]";
3729
3730
3.72k
    else if (EQUAL(pszName, "CRS27") || EQUAL(pszName, "CRS:27"))
3731
248
        pszWKT =
3732
248
            "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
3733
248
            "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
3734
248
            "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
3735
248
            "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
3736
248
            "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
3737
248
            "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
3738
3739
3.47k
    else if (EQUAL(pszName, "NAD83"))
3740
480
        pszWKT =
3741
480
            "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
3742
480
            "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
3743
480
            "AUTHORITY[\"EPSG\",\"7019\"]],"
3744
480
            "AUTHORITY[\"EPSG\",\"6269\"]],"
3745
480
            "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
3746
480
            "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
3747
480
            "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],AUTHORITY["
3748
480
            "\"EPSG\",\"4269\"]]";
3749
3750
2.99k
    else if (EQUAL(pszName, "CRS83") || EQUAL(pszName, "CRS:83"))
3751
293
        pszWKT =
3752
293
            "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
3753
293
            "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
3754
293
            "AUTHORITY[\"EPSG\",\"7019\"]],"
3755
293
            "AUTHORITY[\"EPSG\",\"6269\"]],"
3756
293
            "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
3757
293
            "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
3758
293
            "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
3759
3760
2.70k
    else
3761
2.70k
        return OGRERR_FAILURE;
3762
3763
    /* -------------------------------------------------------------------- */
3764
    /*      Import the WKT                                                  */
3765
    /* -------------------------------------------------------------------- */
3766
41.3k
    OGRSpatialReference oSRS2;
3767
41.3k
    const OGRErr eErr = oSRS2.importFromWkt(pszWKT);
3768
41.3k
    if (eErr != OGRERR_NONE)
3769
0
        return eErr;
3770
3771
    /* -------------------------------------------------------------------- */
3772
    /*      Copy over.                                                      */
3773
    /* -------------------------------------------------------------------- */
3774
41.3k
    return CopyGeogCSFrom(&oSRS2);
3775
41.3k
}
3776
3777
/************************************************************************/
3778
/*                       OSRSetWellKnownGeogCS()                        */
3779
/************************************************************************/
3780
3781
/**
3782
 * \brief Set a GeogCS based on well known name.
3783
 *
3784
 * This function is the same as OGRSpatialReference::SetWellKnownGeogCS()
3785
 */
3786
OGRErr OSRSetWellKnownGeogCS(OGRSpatialReferenceH hSRS, const char *pszName)
3787
3788
0
{
3789
0
    VALIDATE_POINTER1(hSRS, "OSRSetWellKnownGeogCS", OGRERR_FAILURE);
3790
3791
0
    return ToPointer(hSRS)->SetWellKnownGeogCS(pszName);
3792
0
}
3793
3794
/************************************************************************/
3795
/*                           CopyGeogCSFrom()                           */
3796
/************************************************************************/
3797
3798
/**
3799
 * \brief Copy GEOGCS from another OGRSpatialReference.
3800
 *
3801
 * The GEOGCS information is copied into this OGRSpatialReference from another.
3802
 * If this object has a PROJCS root already, the GEOGCS is installed within
3803
 * it, otherwise it is installed as the root.
3804
 *
3805
 * @param poSrcSRS the spatial reference to copy the GEOGCS information from.
3806
 *
3807
 * @return OGRERR_NONE on success or an error code.
3808
 */
3809
3810
OGRErr OGRSpatialReference::CopyGeogCSFrom(const OGRSpatialReference *poSrcSRS)
3811
3812
97.4k
{
3813
97.4k
    TAKE_OPTIONAL_LOCK();
3814
3815
97.4k
    d->bNormInfoSet = FALSE;
3816
97.4k
    d->m_osAngularUnits.clear();
3817
97.4k
    d->m_dfAngularUnitToRadian = 0.0;
3818
97.4k
    d->m_osPrimeMeridianName.clear();
3819
97.4k
    d->dfFromGreenwich = 0.0;
3820
3821
97.4k
    d->refreshProjObj();
3822
97.4k
    poSrcSRS->d->refreshProjObj();
3823
97.4k
    if (!poSrcSRS->d->m_pj_crs)
3824
649
    {
3825
649
        return OGRERR_FAILURE;
3826
649
    }
3827
96.7k
    auto geodCRS =
3828
96.7k
        proj_crs_get_geodetic_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
3829
96.7k
    if (!geodCRS)
3830
0
    {
3831
0
        return OGRERR_FAILURE;
3832
0
    }
3833
3834
    /* -------------------------------------------------------------------- */
3835
    /*      Handle geocentric coordinate systems specially.  We just        */
3836
    /*      want to copy the DATUM.                                         */
3837
    /* -------------------------------------------------------------------- */
3838
96.7k
    if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
3839
2.92k
    {
3840
2.92k
        auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
3841
2.92k
#if PROJ_VERSION_MAJOR > 7 ||                                                  \
3842
2.92k
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
3843
2.92k
        if (datum == nullptr)
3844
0
        {
3845
0
            datum = proj_crs_get_datum_ensemble(d->getPROJContext(), geodCRS);
3846
0
        }
3847
2.92k
#endif
3848
2.92k
        if (datum == nullptr)
3849
0
        {
3850
0
            proj_destroy(geodCRS);
3851
0
            return OGRERR_FAILURE;
3852
0
        }
3853
3854
2.92k
        const char *pszUnitName = nullptr;
3855
2.92k
        double unitConvFactor = GetLinearUnits(&pszUnitName);
3856
3857
2.92k
        auto pj_crs = proj_create_geocentric_crs_from_datum(
3858
2.92k
            d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, pszUnitName,
3859
2.92k
            unitConvFactor);
3860
2.92k
        proj_destroy(datum);
3861
3862
2.92k
        d->setPjCRS(pj_crs);
3863
2.92k
    }
3864
3865
93.8k
    else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
3866
21.6k
    {
3867
21.6k
        auto pj_crs = proj_crs_alter_geodetic_crs(d->getPROJContext(),
3868
21.6k
                                                  d->m_pj_crs, geodCRS);
3869
21.6k
        d->setPjCRS(pj_crs);
3870
21.6k
    }
3871
3872
72.2k
    else
3873
72.2k
    {
3874
72.2k
        d->setPjCRS(proj_clone(d->getPROJContext(), geodCRS));
3875
72.2k
    }
3876
3877
    // Apply TOWGS84 of source CRS
3878
96.7k
    if (poSrcSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
3879
20
    {
3880
20
        auto target =
3881
20
            proj_get_target_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
3882
20
        auto co = proj_crs_get_coordoperation(d->getPROJContext(),
3883
20
                                              poSrcSRS->d->m_pj_crs);
3884
20
        d->setPjCRS(proj_crs_create_bound_crs(d->getPROJContext(), d->m_pj_crs,
3885
20
                                              target, co));
3886
20
        proj_destroy(target);
3887
20
        proj_destroy(co);
3888
20
    }
3889
3890
96.7k
    proj_destroy(geodCRS);
3891
3892
96.7k
    return OGRERR_NONE;
3893
96.7k
}
3894
3895
/************************************************************************/
3896
/*                         OSRCopyGeogCSFrom()                          */
3897
/************************************************************************/
3898
3899
/**
3900
 * \brief Copy GEOGCS from another OGRSpatialReference.
3901
 *
3902
 * This function is the same as OGRSpatialReference::CopyGeogCSFrom()
3903
 */
3904
OGRErr OSRCopyGeogCSFrom(OGRSpatialReferenceH hSRS,
3905
                         const OGRSpatialReferenceH hSrcSRS)
3906
3907
0
{
3908
0
    VALIDATE_POINTER1(hSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
3909
0
    VALIDATE_POINTER1(hSrcSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
3910
3911
0
    return ToPointer(hSRS)->CopyGeogCSFrom(ToPointer(hSrcSRS));
3912
0
}
3913
3914
/************************************************************************/
3915
/*                SET_FROM_USER_INPUT_LIMITATIONS_get()                 */
3916
/************************************************************************/
3917
3918
/** Limitations for OGRSpatialReference::SetFromUserInput().
3919
 *
3920
 * Currently ALLOW_NETWORK_ACCESS=NO and ALLOW_FILE_ACCESS=NO.
3921
 */
3922
const char *const OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS[] = {
3923
    "ALLOW_NETWORK_ACCESS=NO", "ALLOW_FILE_ACCESS=NO", nullptr};
3924
3925
/**
3926
 * \brief Return OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS
3927
 */
3928
CSLConstList OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()
3929
323k
{
3930
323k
    return SET_FROM_USER_INPUT_LIMITATIONS;
3931
323k
}
3932
3933
/************************************************************************/
3934
/*                   RemoveIDFromMemberOfEnsembles()                    */
3935
/************************************************************************/
3936
3937
// cppcheck-suppress constParameterReference
3938
static void RemoveIDFromMemberOfEnsembles(CPLJSONObject &obj)
3939
122k
{
3940
    // Remove "id" from members of datum ensembles for compatibility with
3941
    // older PROJ versions
3942
    // Cf https://github.com/opengeospatial/geoparquet/discussions/110
3943
    // and https://github.com/OSGeo/PROJ/pull/3221
3944
122k
    if (obj.GetType() == CPLJSONObject::Type::Object)
3945
30.5k
    {
3946
30.5k
        for (auto &subObj : obj.GetChildren())
3947
109k
        {
3948
109k
            RemoveIDFromMemberOfEnsembles(subObj);
3949
109k
        }
3950
30.5k
    }
3951
92.0k
    else if (obj.GetType() == CPLJSONObject::Type::Array &&
3952
23.7k
             obj.GetName() == "members")
3953
2.04k
    {
3954
2.04k
        for (auto &subObj : obj.ToArray())
3955
14.0k
        {
3956
14.0k
            if (subObj.GetType() == CPLJSONObject::Type::Object)
3957
0
            {
3958
0
                subObj.Delete("id");
3959
0
            }
3960
14.0k
        }
3961
2.04k
    }
3962
122k
}
3963
3964
/************************************************************************/
3965
/*                          SetFromUserInput()                          */
3966
/************************************************************************/
3967
3968
/**
3969
 * \brief Set spatial reference from various text formats.
3970
 *
3971
 * This method will examine the provided input, and try to deduce the
3972
 * format, and then use it to initialize the spatial reference system.  It
3973
 * may take the following forms:
3974
 *
3975
 * <ol>
3976
 * <li> Well Known Text definition - passed on to importFromWkt().
3977
 * <li> "EPSG:n" - number passed on to importFromEPSG().
3978
 * <li> "EPSGA:n" - number passed on to importFromEPSGA().
3979
 * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
3980
 * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
3981
 * <li> PROJ.4 definitions - passed on to importFromProj4().
3982
 * <li> filename - file read for WKT, XML or PROJ.4 definition.
3983
 * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
3984
 * WGS84 or WGS72.
3985
 * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
3986
 * <li> PROJJSON (PROJ &gt;= 6.2)
3987
 * </ol>
3988
 *
3989
 * It is expected that this method will be extended in the future to support
3990
 * XML and perhaps a simplified "minilanguage" for indicating common UTM and
3991
 * State Plane definitions.
3992
 *
3993
 * This method is intended to be flexible, but by its nature it is
3994
 * imprecise as it must guess information about the format intended.  When
3995
 * possible applications should call the specific method appropriate if the
3996
 * input is known to be in a particular format.
3997
 *
3998
 * This method does the same thing as the OSRSetFromUserInput() function.
3999
 *
4000
 * @param pszDefinition text definition to try to deduce SRS from.
4001
 *
4002
 * @return OGRERR_NONE on success, or an error code if the name isn't
4003
 * recognised, the definition is corrupt, or an EPSG value can't be
4004
 * successfully looked up.
4005
 */
4006
4007
OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition)
4008
134k
{
4009
134k
    return SetFromUserInput(pszDefinition, nullptr);
4010
134k
}
4011
4012
/**
4013
 * \brief Set spatial reference from various text formats.
4014
 *
4015
 * This method will examine the provided input, and try to deduce the
4016
 * format, and then use it to initialize the spatial reference system.  It
4017
 * may take the following forms:
4018
 *
4019
 * <ol>
4020
 * <li> Well Known Text definition - passed on to importFromWkt().
4021
 * <li> "EPSG:n" - number passed on to importFromEPSG().
4022
 * <li> "EPSGA:n" - number passed on to importFromEPSGA().
4023
 * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
4024
 * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
4025
 * <li> PROJ.4 definitions - passed on to importFromProj4().
4026
 * <li> filename - file read for WKT, XML or PROJ.4 definition.
4027
 * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
4028
 * WGS84 or WGS72.
4029
 * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
4030
 * <li> PROJJSON (PROJ &gt;= 6.2)
4031
 * </ol>
4032
 *
4033
 * It is expected that this method will be extended in the future to support
4034
 * XML and perhaps a simplified "minilanguage" for indicating common UTM and
4035
 * State Plane definitions.
4036
 *
4037
 * This method is intended to be flexible, but by its nature it is
4038
 * imprecise as it must guess information about the format intended.  When
4039
 * possible applications should call the specific method appropriate if the
4040
 * input is known to be in a particular format.
4041
 *
4042
 * This method does the same thing as the OSRSetFromUserInput() and
4043
 * OSRSetFromUserInputEx() functions.
4044
 *
4045
 * @param pszDefinition text definition to try to deduce SRS from.
4046
 *
4047
 * @param papszOptions NULL terminated list of options, or NULL.
4048
 * <ol>
4049
 * <li> ALLOW_NETWORK_ACCESS=YES/NO.
4050
 *      Whether http:// or https:// access is allowed. Defaults to YES.
4051
 * <li> ALLOW_FILE_ACCESS=YES/NO.
4052
 *      Whether reading a file using the Virtual File System layer is allowed
4053
 *      (can also involve network access). Defaults to YES.
4054
 * </ol>
4055
 *
4056
 * @return OGRERR_NONE on success, or an error code if the name isn't
4057
 * recognised, the definition is corrupt, or an EPSG value can't be
4058
 * successfully looked up.
4059
 */
4060
4061
OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition,
4062
                                             CSLConstList papszOptions)
4063
570k
{
4064
570k
    TAKE_OPTIONAL_LOCK();
4065
4066
    // Skip leading white space
4067
1.16M
    while (isspace(static_cast<unsigned char>(*pszDefinition)))
4068
594k
        pszDefinition++;
4069
4070
570k
    if (STARTS_WITH_CI(pszDefinition, "ESRI::"))
4071
1.61k
    {
4072
1.61k
        pszDefinition += 6;
4073
1.61k
    }
4074
4075
    /* -------------------------------------------------------------------- */
4076
    /*      Is it a recognised syntax?                                      */
4077
    /* -------------------------------------------------------------------- */
4078
570k
    const char *const wktKeywords[] = {
4079
        // WKT1
4080
570k
        "GEOGCS", "GEOCCS", "PROJCS", "VERT_CS", "COMPD_CS", "LOCAL_CS",
4081
        // WKT2"
4082
570k
        "GEODCRS", "GEOGCRS", "GEODETICCRS", "GEOGRAPHICCRS", "PROJCRS",
4083
570k
        "PROJECTEDCRS", "VERTCRS", "VERTICALCRS", "COMPOUNDCRS", "ENGCRS",
4084
570k
        "ENGINEERINGCRS", "BOUNDCRS", "DERIVEDPROJCRS", "COORDINATEMETADATA"};
4085
570k
    for (const char *keyword : wktKeywords)
4086
8.79M
    {
4087
8.79M
        if (STARTS_WITH_CI(pszDefinition, keyword))
4088
163k
        {
4089
163k
            return importFromWkt(pszDefinition);
4090
163k
        }
4091
8.79M
    }
4092
4093
407k
    const bool bStartsWithEPSG = STARTS_WITH_CI(pszDefinition, "EPSG:");
4094
407k
    if (bStartsWithEPSG || STARTS_WITH_CI(pszDefinition, "EPSGA:"))
4095
61.2k
    {
4096
61.2k
        OGRErr eStatus = OGRERR_NONE;
4097
4098
61.2k
        if (strchr(pszDefinition, '+') || strchr(pszDefinition, '@'))
4099
33.4k
        {
4100
            // Use proj_create() as it allows things like EPSG:3157+4617
4101
            // that are not normally supported by the below code that
4102
            // builds manually a compound CRS
4103
33.4k
            PJ *pj = proj_create(d->getPROJContext(), pszDefinition);
4104
33.4k
            if (!pj)
4105
14.2k
            {
4106
14.2k
                return OGRERR_FAILURE;
4107
14.2k
            }
4108
19.1k
            Clear();
4109
19.1k
            d->setPjCRS(pj);
4110
19.1k
            return OGRERR_NONE;
4111
33.4k
        }
4112
27.8k
        else
4113
27.8k
        {
4114
27.8k
            eStatus =
4115
27.8k
                importFromEPSG(atoi(pszDefinition + (bStartsWithEPSG ? 5 : 6)));
4116
27.8k
        }
4117
4118
27.8k
        return eStatus;
4119
61.2k
    }
4120
4121
346k
    if (STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs:") ||
4122
346k
        STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs,crs:") ||
4123
346k
        STARTS_WITH_CI(pszDefinition, "urn:x-ogc:def:crs:") ||
4124
346k
        STARTS_WITH_CI(pszDefinition, "urn:opengis:crs:") ||
4125
346k
        STARTS_WITH_CI(pszDefinition, "urn:opengis:def:crs:") ||
4126
336k
        STARTS_WITH_CI(pszDefinition, "urn:ogc:def:coordinateMetadata:"))
4127
10.0k
        return importFromURN(pszDefinition);
4128
4129
336k
    if (STARTS_WITH_CI(pszDefinition, "http://opengis.net/def/crs") ||
4130
336k
        STARTS_WITH_CI(pszDefinition, "https://opengis.net/def/crs") ||
4131
336k
        STARTS_WITH_CI(pszDefinition, "http://www.opengis.net/def/crs") ||
4132
336k
        STARTS_WITH_CI(pszDefinition, "https://www.opengis.net/def/crs") ||
4133
331k
        STARTS_WITH_CI(pszDefinition, "www.opengis.net/def/crs"))
4134
7.36k
        return importFromCRSURL(pszDefinition);
4135
4136
328k
    if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
4137
4.92k
        return importFromWMSAUTO(pszDefinition);
4138
4139
    // WMS/WCS OGC codes like OGC:CRS84.
4140
323k
    if (EQUAL(pszDefinition, "OGC:CRS84"))
4141
429
        return SetWellKnownGeogCS(pszDefinition + 4);
4142
4143
323k
    if (STARTS_WITH_CI(pszDefinition, "CRS:"))
4144
364
        return SetWellKnownGeogCS(pszDefinition);
4145
4146
322k
    if (STARTS_WITH_CI(pszDefinition, "DICT:") && strstr(pszDefinition, ","))
4147
6.94k
    {
4148
6.94k
        char *pszFile = CPLStrdup(pszDefinition + 5);
4149
6.94k
        char *pszCode = strstr(pszFile, ",") + 1;
4150
4151
6.94k
        pszCode[-1] = '\0';
4152
4153
6.94k
        OGRErr err = importFromDict(pszFile, pszCode);
4154
6.94k
        CPLFree(pszFile);
4155
4156
6.94k
        return err;
4157
6.94k
    }
4158
4159
316k
    if (EQUAL(pszDefinition, "NAD27") || EQUAL(pszDefinition, "NAD83") ||
4160
315k
        EQUAL(pszDefinition, "WGS84") || EQUAL(pszDefinition, "WGS72"))
4161
1.11k
    {
4162
1.11k
        Clear();
4163
1.11k
        return SetWellKnownGeogCS(pszDefinition);
4164
1.11k
    }
4165
4166
    // PROJJSON
4167
314k
    if (pszDefinition[0] == '{' && strstr(pszDefinition, "\"type\"") &&
4168
28.7k
        (strstr(pszDefinition, "GeodeticCRS") ||
4169
25.9k
         strstr(pszDefinition, "GeographicCRS") ||
4170
19.9k
         strstr(pszDefinition, "ProjectedCRS") ||
4171
16.5k
         strstr(pszDefinition, "VerticalCRS") ||
4172
14.7k
         strstr(pszDefinition, "BoundCRS") ||
4173
14.0k
         strstr(pszDefinition, "CompoundCRS") ||
4174
13.4k
         strstr(pszDefinition, "DerivedGeodeticCRS") ||
4175
13.4k
         strstr(pszDefinition, "DerivedGeographicCRS") ||
4176
13.4k
         strstr(pszDefinition, "DerivedProjectedCRS") ||
4177
13.4k
         strstr(pszDefinition, "DerivedVerticalCRS") ||
4178
13.4k
         strstr(pszDefinition, "EngineeringCRS") ||
4179
11.6k
         strstr(pszDefinition, "DerivedEngineeringCRS") ||
4180
11.6k
         strstr(pszDefinition, "ParametricCRS") ||
4181
10.3k
         strstr(pszDefinition, "DerivedParametricCRS") ||
4182
10.3k
         strstr(pszDefinition, "TemporalCRS") ||
4183
1.48k
         strstr(pszDefinition, "DerivedTemporalCRS")))
4184
27.2k
    {
4185
27.2k
        PJ *pj;
4186
27.2k
        if (strstr(pszDefinition, "datum_ensemble") != nullptr)
4187
21.5k
        {
4188
            // PROJ < 9.0.1 doesn't like a datum_ensemble whose member have
4189
            // a unknown id.
4190
21.5k
            CPLJSONDocument oCRSDoc;
4191
21.5k
            if (!oCRSDoc.LoadMemory(pszDefinition))
4192
8.45k
                return OGRERR_CORRUPT_DATA;
4193
13.0k
            CPLJSONObject oCRSRoot = oCRSDoc.GetRoot();
4194
13.0k
            RemoveIDFromMemberOfEnsembles(oCRSRoot);
4195
13.0k
            pj = proj_create(d->getPROJContext(), oCRSRoot.ToString().c_str());
4196
13.0k
        }
4197
5.77k
        else
4198
5.77k
        {
4199
5.77k
            pj = proj_create(d->getPROJContext(), pszDefinition);
4200
5.77k
        }
4201
18.8k
        if (!pj)
4202
18.8k
        {
4203
18.8k
            return OGRERR_FAILURE;
4204
18.8k
        }
4205
0
        Clear();
4206
0
        d->setPjCRS(pj);
4207
0
        return OGRERR_NONE;
4208
18.8k
    }
4209
4210
287k
    if (strstr(pszDefinition, "+proj") != nullptr ||
4211
46.6k
        strstr(pszDefinition, "+init") != nullptr)
4212
258k
        return importFromProj4(pszDefinition);
4213
4214
28.7k
    if (STARTS_WITH_CI(pszDefinition, "http://") ||
4215
28.1k
        STARTS_WITH_CI(pszDefinition, "https://"))
4216
988
    {
4217
988
        if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
4218
988
                                             "ALLOW_NETWORK_ACCESS", "YES")))
4219
19
            return importFromUrl(pszDefinition);
4220
4221
969
        CPLError(CE_Failure, CPLE_AppDefined,
4222
969
                 "Cannot import %s due to ALLOW_NETWORK_ACCESS=NO",
4223
969
                 pszDefinition);
4224
969
        return OGRERR_FAILURE;
4225
988
    }
4226
4227
27.7k
    if (EQUAL(pszDefinition, "osgb:BNG"))
4228
18
    {
4229
18
        return importFromEPSG(27700);
4230
18
    }
4231
4232
    // Used by German CityGML files
4233
27.7k
    if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH"))
4234
1
    {
4235
        // "ETRS89 / UTM Zone 32N + DHHN92 height"
4236
1
        return SetFromUserInput("EPSG:25832+5783");
4237
1
    }
4238
27.7k
    else if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN2016_NH"))
4239
247
    {
4240
        // "ETRS89 / UTM Zone 32N + DHHN2016 height"
4241
247
        return SetFromUserInput("EPSG:25832+7837");
4242
247
    }
4243
4244
    // Used by  Japan's Fundamental Geospatial Data (FGD) GML
4245
27.5k
    if (EQUAL(pszDefinition, "fguuid:jgd2001.bl"))
4246
0
        return importFromEPSG(4612);  // JGD2000 (slight difference in years)
4247
27.5k
    else if (EQUAL(pszDefinition, "fguuid:jgd2011.bl"))
4248
0
        return importFromEPSG(6668);  // JGD2011
4249
27.5k
    else if (EQUAL(pszDefinition, "fguuid:jgd2024.bl"))
4250
0
    {
4251
        // FIXME when EPSG attributes a CRS code
4252
0
        return importFromWkt(
4253
0
            "GEOGCRS[\"JGD2024\",\n"
4254
0
            "    DATUM[\"Japanese Geodetic Datum 2024\",\n"
4255
0
            "        ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
4256
0
            "            LENGTHUNIT[\"metre\",1]]],\n"
4257
0
            "    PRIMEM[\"Greenwich\",0,\n"
4258
0
            "        ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
4259
0
            "    CS[ellipsoidal,2],\n"
4260
0
            "        AXIS[\"geodetic latitude (Lat)\",north,\n"
4261
0
            "            ORDER[1],\n"
4262
0
            "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
4263
0
            "        AXIS[\"geodetic longitude (Lon)\",east,\n"
4264
0
            "            ORDER[2],\n"
4265
0
            "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
4266
0
            "    USAGE[\n"
4267
0
            "        SCOPE[\"Horizontal component of 3D system.\"],\n"
4268
0
            "        AREA[\"Japan - onshore and offshore.\"],\n"
4269
0
            "        BBOX[17.09,122.38,46.05,157.65]]]");
4270
0
    }
4271
4272
    // Deal with IGNF:xxx, ESRI:xxx, etc from the PROJ database
4273
27.5k
    const char *pszDot = strrchr(pszDefinition, ':');
4274
27.5k
    if (pszDot)
4275
12.5k
    {
4276
12.5k
        CPLString osPrefix(pszDefinition, pszDot - pszDefinition);
4277
12.5k
        auto authorities =
4278
12.5k
            proj_get_authorities_from_database(d->getPROJContext());
4279
12.5k
        if (authorities)
4280
12.5k
        {
4281
12.5k
            std::set<std::string> aosCandidateAuthorities;
4282
109k
            for (auto iter = authorities; *iter; ++iter)
4283
97.8k
            {
4284
97.8k
                if (*iter == osPrefix)
4285
1.29k
                {
4286
1.29k
                    aosCandidateAuthorities.clear();
4287
1.29k
                    aosCandidateAuthorities.insert(*iter);
4288
1.29k
                    break;
4289
1.29k
                }
4290
                // Deal with "IAU_2015" as authority in the list and input
4291
                // "IAU:code"
4292
96.5k
                else if (strncmp(*iter, osPrefix.c_str(), osPrefix.size()) ==
4293
96.5k
                             0 &&
4294
1.98k
                         (*iter)[osPrefix.size()] == '_')
4295
0
                {
4296
0
                    aosCandidateAuthorities.insert(*iter);
4297
0
                }
4298
                // Deal with "IAU_2015" as authority in the list and input
4299
                // "IAU:2015:code"
4300
96.5k
                else if (osPrefix.find(':') != std::string::npos &&
4301
53.0k
                         osPrefix.size() == strlen(*iter) &&
4302
517
                         CPLString(osPrefix).replaceAll(':', '_') == *iter)
4303
0
                {
4304
0
                    aosCandidateAuthorities.clear();
4305
0
                    aosCandidateAuthorities.insert(*iter);
4306
0
                    break;
4307
0
                }
4308
97.8k
            }
4309
4310
12.5k
            proj_string_list_destroy(authorities);
4311
4312
12.5k
            if (!aosCandidateAuthorities.empty())
4313
1.29k
            {
4314
1.29k
                auto obj = proj_create_from_database(
4315
1.29k
                    d->getPROJContext(),
4316
1.29k
                    aosCandidateAuthorities.rbegin()->c_str(), pszDot + 1,
4317
1.29k
                    PJ_CATEGORY_CRS, false, nullptr);
4318
1.29k
                if (!obj)
4319
1.14k
                {
4320
1.14k
                    return OGRERR_FAILURE;
4321
1.14k
                }
4322
152
                Clear();
4323
152
                d->setPjCRS(obj);
4324
152
                return OGRERR_NONE;
4325
1.29k
            }
4326
12.5k
        }
4327
12.5k
    }
4328
4329
    /* -------------------------------------------------------------------- */
4330
    /*      Try to open it as a file.                                       */
4331
    /* -------------------------------------------------------------------- */
4332
26.2k
    if (!CPLTestBool(
4333
26.2k
            CSLFetchNameValueDef(papszOptions, "ALLOW_FILE_ACCESS", "YES")))
4334
25.8k
    {
4335
25.8k
        VSIStatBufL sStat;
4336
25.8k
        if (STARTS_WITH(pszDefinition, "/vsi") ||
4337
21.7k
            VSIStatExL(pszDefinition, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
4338
4.16k
        {
4339
4.16k
            CPLError(CE_Failure, CPLE_AppDefined,
4340
4.16k
                     "Cannot import %s due to ALLOW_FILE_ACCESS=NO",
4341
4.16k
                     pszDefinition);
4342
4.16k
            return OGRERR_FAILURE;
4343
4.16k
        }
4344
        // We used to silently return an error without a CE_Failure message
4345
        // Cf https://github.com/Toblerity/Fiona/issues/1063
4346
21.6k
        return OGRERR_CORRUPT_DATA;
4347
25.8k
    }
4348
4349
417
    CPLConfigOptionSetter oSetter("CPL_ALLOW_VSISTDIN", "NO", true);
4350
417
    VSILFILE *const fp = VSIFOpenL(pszDefinition, "rt");
4351
417
    if (fp == nullptr)
4352
383
        return OGRERR_CORRUPT_DATA;
4353
4354
34
    const size_t nBufMax = 100000;
4355
34
    char *const pszBuffer = static_cast<char *>(CPLMalloc(nBufMax));
4356
34
    const size_t nBytes = VSIFReadL(pszBuffer, 1, nBufMax - 1, fp);
4357
34
    VSIFCloseL(fp);
4358
4359
34
    if (nBytes == nBufMax - 1)
4360
0
    {
4361
0
        CPLDebug("OGR",
4362
0
                 "OGRSpatialReference::SetFromUserInput(%s), opened file "
4363
0
                 "but it is to large for our generous buffer.  Is it really "
4364
0
                 "just a WKT definition?",
4365
0
                 pszDefinition);
4366
0
        CPLFree(pszBuffer);
4367
0
        return OGRERR_FAILURE;
4368
0
    }
4369
4370
34
    pszBuffer[nBytes] = '\0';
4371
4372
34
    char *pszBufPtr = pszBuffer;
4373
34
    while (pszBufPtr[0] == ' ' || pszBufPtr[0] == '\n')
4374
0
        pszBufPtr++;
4375
4376
34
    OGRErr err = OGRERR_NONE;
4377
34
    if (pszBufPtr[0] == '<')
4378
0
        err = importFromXML(pszBufPtr);
4379
34
    else if ((strstr(pszBuffer, "+proj") != nullptr ||
4380
34
              strstr(pszBuffer, "+init") != nullptr) &&
4381
0
             (strstr(pszBuffer, "EXTENSION") == nullptr &&
4382
0
              strstr(pszBuffer, "extension") == nullptr))
4383
0
        err = importFromProj4(pszBufPtr);
4384
34
    else
4385
34
    {
4386
34
        if (STARTS_WITH_CI(pszBufPtr, "ESRI::"))
4387
0
        {
4388
0
            pszBufPtr += 6;
4389
0
        }
4390
4391
        // coverity[tainted_data]
4392
34
        err = importFromWkt(pszBufPtr);
4393
34
    }
4394
4395
34
    CPLFree(pszBuffer);
4396
4397
34
    return err;
4398
34
}
4399
4400
/************************************************************************/
4401
/*                        OSRSetFromUserInput()                         */
4402
/************************************************************************/
4403
4404
/**
4405
 * \brief Set spatial reference from various text formats.
4406
 *
4407
 * This function is the same as OGRSpatialReference::SetFromUserInput()
4408
 *
4409
 * \see OSRSetFromUserInputEx() for a variant allowing to pass options.
4410
 */
4411
OGRErr CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS,
4412
                                       const char *pszDef)
4413
4414
2.62k
{
4415
2.62k
    VALIDATE_POINTER1(hSRS, "OSRSetFromUserInput", OGRERR_FAILURE);
4416
4417
2.62k
    return ToPointer(hSRS)->SetFromUserInput(pszDef);
4418
2.62k
}
4419
4420
/************************************************************************/
4421
/*                       OSRSetFromUserInputEx()                        */
4422
/************************************************************************/
4423
4424
/**
4425
 * \brief Set spatial reference from various text formats.
4426
 *
4427
 * This function is the same as OGRSpatialReference::SetFromUserInput().
4428
 *
4429
 * @since GDAL 3.9
4430
 */
4431
OGRErr OSRSetFromUserInputEx(OGRSpatialReferenceH hSRS, const char *pszDef,
4432
                             CSLConstList papszOptions)
4433
4434
0
{
4435
0
    VALIDATE_POINTER1(hSRS, "OSRSetFromUserInputEx", OGRERR_FAILURE);
4436
4437
0
    return ToPointer(hSRS)->SetFromUserInput(pszDef, papszOptions);
4438
0
}
4439
4440
/************************************************************************/
4441
/*                           ImportFromUrl()                            */
4442
/************************************************************************/
4443
4444
/**
4445
 * \brief Set spatial reference from a URL.
4446
 *
4447
 * This method will download the spatial reference at a given URL and
4448
 * feed it into SetFromUserInput for you.
4449
 *
4450
 * This method does the same thing as the OSRImportFromUrl() function.
4451
 *
4452
 * @param pszUrl text definition to try to deduce SRS from.
4453
 *
4454
 * @return OGRERR_NONE on success, or an error code with the curl
4455
 * error message if it is unable to download data.
4456
 */
4457
4458
OGRErr OGRSpatialReference::importFromUrl(const char *pszUrl)
4459
4460
29.3k
{
4461
29.3k
    TAKE_OPTIONAL_LOCK();
4462
4463
29.3k
    if (!STARTS_WITH_CI(pszUrl, "http://") &&
4464
4.25k
        !STARTS_WITH_CI(pszUrl, "https://"))
4465
1.22k
    {
4466
1.22k
        CPLError(CE_Failure, CPLE_AppDefined,
4467
1.22k
                 "The given string is not recognized as a URL"
4468
1.22k
                 "starting with 'http://' -- %s",
4469
1.22k
                 pszUrl);
4470
1.22k
        return OGRERR_FAILURE;
4471
1.22k
    }
4472
4473
    /* -------------------------------------------------------------------- */
4474
    /*      Fetch the result.                                               */
4475
    /* -------------------------------------------------------------------- */
4476
28.1k
    CPLErrorReset();
4477
4478
28.1k
    std::string osUrl(pszUrl);
4479
    // We have historically supported "http://spatialreference.org/ref/AUTHNAME/CODE/"
4480
    // as a valid URL since we used a "Accept: application/x-ogcwkt" header
4481
    // to query WKT. To allow a static server to be used, rather append a
4482
    // "ogcwkt/" suffix.
4483
28.1k
    for (const char *pszPrefix : {"https://spatialreference.org/ref/",
4484
28.1k
                                  "http://spatialreference.org/ref/"})
4485
56.3k
    {
4486
56.3k
        if (STARTS_WITH(pszUrl, pszPrefix))
4487
0
        {
4488
0
            const CPLStringList aosTokens(
4489
0
                CSLTokenizeString2(pszUrl + strlen(pszPrefix), "/", 0));
4490
0
            if (aosTokens.size() == 2)
4491
0
            {
4492
0
                osUrl = "https://spatialreference.org/ref/";
4493
0
                osUrl += aosTokens[0];  // authority
4494
0
                osUrl += '/';
4495
0
                osUrl += aosTokens[1];  // code
4496
0
                osUrl += "/ogcwkt/";
4497
0
            }
4498
0
            break;
4499
0
        }
4500
56.3k
    }
4501
4502
28.1k
    const char *pszTimeout = "TIMEOUT=10";
4503
28.1k
    char *apszOptions[] = {const_cast<char *>(pszTimeout), nullptr};
4504
4505
28.1k
    CPLHTTPResult *psResult = CPLHTTPFetch(osUrl.c_str(), apszOptions);
4506
4507
    /* -------------------------------------------------------------------- */
4508
    /*      Try to handle errors.                                           */
4509
    /* -------------------------------------------------------------------- */
4510
4511
28.1k
    if (psResult == nullptr)
4512
0
        return OGRERR_FAILURE;
4513
28.1k
    if (psResult->nDataLen == 0 || CPLGetLastErrorNo() != 0 ||
4514
0
        psResult->pabyData == nullptr)
4515
28.1k
    {
4516
28.1k
        if (CPLGetLastErrorNo() == 0)
4517
189
        {
4518
189
            CPLError(CE_Failure, CPLE_AppDefined,
4519
189
                     "No data was returned from the given URL");
4520
189
        }
4521
28.1k
        CPLHTTPDestroyResult(psResult);
4522
28.1k
        return OGRERR_FAILURE;
4523
28.1k
    }
4524
4525
0
    if (psResult->nStatus != 0)
4526
0
    {
4527
0
        CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
4528
0
                 psResult->nStatus, psResult->pszErrBuf);
4529
0
        CPLHTTPDestroyResult(psResult);
4530
0
        return OGRERR_FAILURE;
4531
0
    }
4532
4533
0
    const char *pszData = reinterpret_cast<const char *>(psResult->pabyData);
4534
0
    if (STARTS_WITH_CI(pszData, "http://") ||
4535
0
        STARTS_WITH_CI(pszData, "https://"))
4536
0
    {
4537
0
        CPLError(CE_Failure, CPLE_AppDefined,
4538
0
                 "The data that was downloaded also starts with 'http://' "
4539
0
                 "and cannot be passed into SetFromUserInput.  Is this "
4540
0
                 "really a spatial reference definition? ");
4541
0
        CPLHTTPDestroyResult(psResult);
4542
0
        return OGRERR_FAILURE;
4543
0
    }
4544
0
    if (OGRERR_NONE != SetFromUserInput(pszData))
4545
0
    {
4546
0
        CPLHTTPDestroyResult(psResult);
4547
0
        return OGRERR_FAILURE;
4548
0
    }
4549
4550
0
    CPLHTTPDestroyResult(psResult);
4551
0
    return OGRERR_NONE;
4552
0
}
4553
4554
/************************************************************************/
4555
/*                          OSRimportFromUrl()                          */
4556
/************************************************************************/
4557
4558
/**
4559
 * \brief Set spatial reference from a URL.
4560
 *
4561
 * This function is the same as OGRSpatialReference::importFromUrl()
4562
 */
4563
OGRErr OSRImportFromUrl(OGRSpatialReferenceH hSRS, const char *pszUrl)
4564
4565
0
{
4566
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromUrl", OGRERR_FAILURE);
4567
4568
0
    return ToPointer(hSRS)->importFromUrl(pszUrl);
4569
0
}
4570
4571
/************************************************************************/
4572
/*                         importFromURNPart()                          */
4573
/************************************************************************/
4574
OGRErr OGRSpatialReference::importFromURNPart(const char *pszAuthority,
4575
                                              const char *pszCode,
4576
                                              const char *pszURN)
4577
0
{
4578
0
#if PROJ_AT_LEAST_VERSION(8, 1, 0)
4579
0
    (void)this;
4580
0
    (void)pszAuthority;
4581
0
    (void)pszCode;
4582
0
    (void)pszURN;
4583
0
    return OGRERR_FAILURE;
4584
#else
4585
    /* -------------------------------------------------------------------- */
4586
    /*      Is this an EPSG code? Note that we import it with EPSG          */
4587
    /*      preferred axis ordering for geographic coordinate systems.      */
4588
    /* -------------------------------------------------------------------- */
4589
    if (STARTS_WITH_CI(pszAuthority, "EPSG"))
4590
        return importFromEPSGA(atoi(pszCode));
4591
4592
    /* -------------------------------------------------------------------- */
4593
    /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
4594
    /* -------------------------------------------------------------------- */
4595
    if (STARTS_WITH_CI(pszAuthority, "IAU"))
4596
        return importFromDict("IAU2000.wkt", pszCode);
4597
4598
    /* -------------------------------------------------------------------- */
4599
    /*      Is this an OGC code?                                            */
4600
    /* -------------------------------------------------------------------- */
4601
    if (!STARTS_WITH_CI(pszAuthority, "OGC"))
4602
    {
4603
        CPLError(CE_Failure, CPLE_AppDefined,
4604
                 "URN %s has unrecognized authority.", pszURN);
4605
        return OGRERR_FAILURE;
4606
    }
4607
4608
    if (STARTS_WITH_CI(pszCode, "CRS84"))
4609
        return SetWellKnownGeogCS(pszCode);
4610
    else if (STARTS_WITH_CI(pszCode, "CRS83"))
4611
        return SetWellKnownGeogCS(pszCode);
4612
    else if (STARTS_WITH_CI(pszCode, "CRS27"))
4613
        return SetWellKnownGeogCS(pszCode);
4614
    else if (STARTS_WITH_CI(pszCode, "84"))  // urn:ogc:def:crs:OGC:2:84
4615
        return SetWellKnownGeogCS("CRS84");
4616
4617
    /* -------------------------------------------------------------------- */
4618
    /*      Handle auto codes.  We need to convert from format              */
4619
    /*      AUTO42001:99:8888 to format AUTO:42001,99,8888.                 */
4620
    /* -------------------------------------------------------------------- */
4621
    else if (STARTS_WITH_CI(pszCode, "AUTO"))
4622
    {
4623
        char szWMSAuto[100] = {'\0'};
4624
4625
        if (strlen(pszCode) > sizeof(szWMSAuto) - 2)
4626
            return OGRERR_FAILURE;
4627
4628
        snprintf(szWMSAuto, sizeof(szWMSAuto), "AUTO:%s", pszCode + 4);
4629
        for (int i = 5; szWMSAuto[i] != '\0'; i++)
4630
        {
4631
            if (szWMSAuto[i] == ':')
4632
                szWMSAuto[i] = ',';
4633
        }
4634
4635
        return importFromWMSAUTO(szWMSAuto);
4636
    }
4637
4638
    /* -------------------------------------------------------------------- */
4639
    /*      Not a recognise OGC item.                                       */
4640
    /* -------------------------------------------------------------------- */
4641
    CPLError(CE_Failure, CPLE_AppDefined, "URN %s value not supported.",
4642
             pszURN);
4643
4644
    return OGRERR_FAILURE;
4645
#endif
4646
0
}
4647
4648
/************************************************************************/
4649
/*                           importFromURN()                            */
4650
/*                                                                      */
4651
/*      See OGC recommendation paper 06-023r1 or later for details.     */
4652
/************************************************************************/
4653
4654
/**
4655
 * \brief Initialize from OGC URN.
4656
 *
4657
 * Initializes this spatial reference from a coordinate system defined
4658
 * by an OGC URN prefixed with "urn:ogc:def:crs:" per recommendation
4659
 * paper 06-023r1.  Currently EPSG and OGC authority values are supported,
4660
 * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
4661
 *
4662
 * This method is also support through SetFromUserInput() which can
4663
 * normally be used for URNs.
4664
 *
4665
 * @param pszURN the urn string.
4666
 *
4667
 * @return OGRERR_NONE on success or an error code.
4668
 */
4669
4670
OGRErr OGRSpatialReference::importFromURN(const char *pszURN)
4671
4672
93.4k
{
4673
93.4k
    constexpr const char *EPSG_URN_CRS_PREFIX = "urn:ogc:def:crs:EPSG::";
4674
93.4k
    if (STARTS_WITH(pszURN, EPSG_URN_CRS_PREFIX) &&
4675
8.16k
        CPLGetValueType(pszURN + strlen(EPSG_URN_CRS_PREFIX)) ==
4676
8.16k
            CPL_VALUE_INTEGER)
4677
7.04k
    {
4678
7.04k
        return importFromEPSG(atoi(pszURN + strlen(EPSG_URN_CRS_PREFIX)));
4679
7.04k
    }
4680
4681
86.4k
    TAKE_OPTIONAL_LOCK();
4682
4683
86.4k
#if PROJ_AT_LEAST_VERSION(8, 1, 0)
4684
4685
    // PROJ 8.2.0 has support for IAU codes now.
4686
#if !PROJ_AT_LEAST_VERSION(8, 2, 0)
4687
    /* -------------------------------------------------------------------- */
4688
    /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
4689
    /* -------------------------------------------------------------------- */
4690
    const char *pszIAU = strstr(pszURN, "IAU");
4691
    if (pszIAU)
4692
    {
4693
        const char *pszCode = strchr(pszIAU, ':');
4694
        if (pszCode)
4695
        {
4696
            ++pszCode;
4697
            if (*pszCode == ':')
4698
                ++pszCode;
4699
            return importFromDict("IAU2000.wkt", pszCode);
4700
        }
4701
    }
4702
#endif
4703
4704
86.4k
    if (strlen(pszURN) >= 1000)
4705
411
    {
4706
411
        CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
4707
411
        return OGRERR_CORRUPT_DATA;
4708
411
    }
4709
86.0k
    auto obj = proj_create(d->getPROJContext(), pszURN);
4710
86.0k
    if (!obj)
4711
51.5k
    {
4712
51.5k
        return OGRERR_FAILURE;
4713
51.5k
    }
4714
34.4k
    Clear();
4715
34.4k
    d->setPjCRS(obj);
4716
34.4k
    return OGRERR_NONE;
4717
#else
4718
    const char *pszCur = nullptr;
4719
4720
    if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs:"))
4721
        pszCur = pszURN + 16;
4722
    else if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs,crs:"))
4723
        pszCur = pszURN + 20;
4724
    else if (STARTS_WITH_CI(pszURN, "urn:x-ogc:def:crs:"))
4725
        pszCur = pszURN + 18;
4726
    else if (STARTS_WITH_CI(pszURN, "urn:opengis:crs:"))
4727
        pszCur = pszURN + 16;
4728
    else if (STARTS_WITH_CI(pszURN, "urn:opengis:def:crs:"))
4729
        pszCur = pszURN + 20;
4730
    else
4731
    {
4732
        CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
4733
                 pszURN);
4734
        return OGRERR_FAILURE;
4735
    }
4736
4737
    /* -------------------------------------------------------------------- */
4738
    /*      Clear any existing definition.                                  */
4739
    /* -------------------------------------------------------------------- */
4740
    Clear();
4741
4742
    /* -------------------------------------------------------------------- */
4743
    /*      Find code (ignoring version) out of string like:                */
4744
    /*                                                                      */
4745
    /*      authority:[version]:code                                        */
4746
    /* -------------------------------------------------------------------- */
4747
    const char *pszAuthority = pszCur;
4748
4749
    // skip authority
4750
    while (*pszCur != ':' && *pszCur)
4751
        pszCur++;
4752
    if (*pszCur == ':')
4753
        pszCur++;
4754
4755
    // skip version
4756
    const char *pszBeforeVersion = pszCur;
4757
    while (*pszCur != ':' && *pszCur)
4758
        pszCur++;
4759
    if (*pszCur == ':')
4760
        pszCur++;
4761
    else
4762
        // We come here in the case, the content to parse is authority:code
4763
        // (instead of authority::code) which is probably illegal according to
4764
        // http://www.opengeospatial.org/ogcUrnPolicy but such content is found
4765
        // for example in what is returned by GeoServer.
4766
        pszCur = pszBeforeVersion;
4767
4768
    const char *pszCode = pszCur;
4769
4770
    const char *pszComma = strchr(pszCur, ',');
4771
    if (pszComma == nullptr)
4772
        return importFromURNPart(pszAuthority, pszCode, pszURN);
4773
4774
    // There's a second part with the vertical SRS.
4775
    pszCur = pszComma + 1;
4776
    if (!STARTS_WITH(pszCur, "crs:"))
4777
    {
4778
        CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
4779
                 pszURN);
4780
        return OGRERR_FAILURE;
4781
    }
4782
4783
    pszCur += 4;
4784
4785
    char *pszFirstCode = CPLStrdup(pszCode);
4786
    pszFirstCode[pszComma - pszCode] = '\0';
4787
    OGRErr eStatus = importFromURNPart(pszAuthority, pszFirstCode, pszURN);
4788
    CPLFree(pszFirstCode);
4789
4790
    // Do we want to turn this into a compound definition
4791
    // with a vertical datum?
4792
    if (eStatus != OGRERR_NONE)
4793
        return eStatus;
4794
4795
    /* -------------------------------------------------------------------- */
4796
    /*      Find code (ignoring version) out of string like:                */
4797
    /*                                                                      */
4798
    /*      authority:[version]:code                                        */
4799
    /* -------------------------------------------------------------------- */
4800
    pszAuthority = pszCur;
4801
4802
    // skip authority
4803
    while (*pszCur != ':' && *pszCur)
4804
        pszCur++;
4805
    if (*pszCur == ':')
4806
        pszCur++;
4807
4808
    // skip version
4809
    pszBeforeVersion = pszCur;
4810
    while (*pszCur != ':' && *pszCur)
4811
        pszCur++;
4812
    if (*pszCur == ':')
4813
        pszCur++;
4814
    else
4815
        pszCur = pszBeforeVersion;
4816
4817
    pszCode = pszCur;
4818
4819
    OGRSpatialReference oVertSRS;
4820
    eStatus = oVertSRS.importFromURNPart(pszAuthority, pszCode, pszURN);
4821
    if (eStatus == OGRERR_NONE)
4822
    {
4823
        OGRSpatialReference oHorizSRS(*this);
4824
4825
        Clear();
4826
4827
        oHorizSRS.d->refreshProjObj();
4828
        oVertSRS.d->refreshProjObj();
4829
        if (!oHorizSRS.d->m_pj_crs || !oVertSRS.d->m_pj_crs)
4830
            return OGRERR_FAILURE;
4831
4832
        const char *pszHorizName = proj_get_name(oHorizSRS.d->m_pj_crs);
4833
        const char *pszVertName = proj_get_name(oVertSRS.d->m_pj_crs);
4834
4835
        CPLString osName = pszHorizName ? pszHorizName : "";
4836
        osName += " + ";
4837
        osName += pszVertName ? pszVertName : "";
4838
4839
        SetCompoundCS(osName, &oHorizSRS, &oVertSRS);
4840
    }
4841
4842
    return eStatus;
4843
#endif
4844
86.0k
}
4845
4846
/************************************************************************/
4847
/*                           importFromCRSURL()                         */
4848
/*                                                                      */
4849
/*      See OGC Best Practice document 11-135 for details.              */
4850
/************************************************************************/
4851
4852
/**
4853
 * \brief Initialize from OGC URL.
4854
 *
4855
 * Initializes this spatial reference from a coordinate system defined
4856
 * by an OGC URL prefixed with "http://opengis.net/def/crs" per best practice
4857
 * paper 11-135.  Currently EPSG and OGC authority values are supported,
4858
 * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
4859
 *
4860
 * This method is also supported through SetFromUserInput() which can
4861
 * normally be used for URLs.
4862
 *
4863
 * @param pszURL the URL string.
4864
 *
4865
 * @return OGRERR_NONE on success or an error code.
4866
 */
4867
4868
OGRErr OGRSpatialReference::importFromCRSURL(const char *pszURL)
4869
4870
7.37k
{
4871
7.37k
    TAKE_OPTIONAL_LOCK();
4872
4873
#if !PROJ_AT_LEAST_VERSION(9, 1, 0)
4874
    if (strcmp(pszURL, "http://www.opengis.net/def/crs/OGC/0/CRS84h") == 0)
4875
    {
4876
        PJ *obj = proj_create(
4877
            d->getPROJContext(),
4878
            "GEOGCRS[\"WGS 84 longitude-latitude-height\",\n"
4879
            "    ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n"
4880
            "        MEMBER[\"World Geodetic System 1984 (Transit)\"],\n"
4881
            "        MEMBER[\"World Geodetic System 1984 (G730)\"],\n"
4882
            "        MEMBER[\"World Geodetic System 1984 (G873)\"],\n"
4883
            "        MEMBER[\"World Geodetic System 1984 (G1150)\"],\n"
4884
            "        MEMBER[\"World Geodetic System 1984 (G1674)\"],\n"
4885
            "        MEMBER[\"World Geodetic System 1984 (G1762)\"],\n"
4886
            "        MEMBER[\"World Geodetic System 1984 (G2139)\"],\n"
4887
            "        MEMBER[\"World Geodetic System 1984 (G2296)\"],\n"
4888
            "        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
4889
            "            LENGTHUNIT[\"metre\",1]],\n"
4890
            "        ENSEMBLEACCURACY[2.0]],\n"
4891
            "    PRIMEM[\"Greenwich\",0,\n"
4892
            "        ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
4893
            "    CS[ellipsoidal,3],\n"
4894
            "        AXIS[\"geodetic longitude (Lon)\",east,\n"
4895
            "            ORDER[1],\n"
4896
            "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
4897
            "        AXIS[\"geodetic latitude (Lat)\",north,\n"
4898
            "            ORDER[2],\n"
4899
            "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
4900
            "        AXIS[\"ellipsoidal height (h)\",up,\n"
4901
            "            ORDER[3],\n"
4902
            "            LENGTHUNIT[\"metre\",1]],\n"
4903
            "    USAGE[\n"
4904
            "        SCOPE[\"3D system frequently used in GIS, Web APIs and "
4905
            "Web applications\"],\n"
4906
            "        AREA[\"World.\"],\n"
4907
            "        BBOX[-90,-180,90,180]],\n"
4908
            "    ID[\"OGC\",\"CRS84h\"]]");
4909
        if (!obj)
4910
        {
4911
            return OGRERR_FAILURE;
4912
        }
4913
        Clear();
4914
        d->setPjCRS(obj);
4915
        return OGRERR_NONE;
4916
    }
4917
#endif
4918
4919
7.37k
#if PROJ_AT_LEAST_VERSION(8, 1, 0)
4920
7.37k
    if (strlen(pszURL) >= 10000)
4921
167
    {
4922
167
        CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
4923
167
        return OGRERR_CORRUPT_DATA;
4924
167
    }
4925
4926
7.20k
    PJ *obj;
4927
#if !PROJ_AT_LEAST_VERSION(9, 2, 0)
4928
    if (STARTS_WITH(pszURL, "http://www.opengis.net/def/crs/IAU/0/"))
4929
    {
4930
        obj = proj_create(
4931
            d->getPROJContext(),
4932
            CPLSPrintf("IAU:%s",
4933
                       pszURL +
4934
                           strlen("http://www.opengis.net/def/crs/IAU/0/")));
4935
    }
4936
    else
4937
#endif
4938
7.20k
    {
4939
7.20k
        obj = proj_create(d->getPROJContext(), pszURL);
4940
7.20k
    }
4941
7.20k
    if (!obj)
4942
6.78k
    {
4943
6.78k
        return OGRERR_FAILURE;
4944
6.78k
    }
4945
424
    Clear();
4946
424
    d->setPjCRS(obj);
4947
424
    return OGRERR_NONE;
4948
#else
4949
    const char *pszCur = nullptr;
4950
4951
    if (STARTS_WITH_CI(pszURL, "http://opengis.net/def/crs"))
4952
        pszCur = pszURL + 26;
4953
    else if (STARTS_WITH_CI(pszURL, "https://opengis.net/def/crs"))
4954
        pszCur = pszURL + 27;
4955
    else if (STARTS_WITH_CI(pszURL, "http://www.opengis.net/def/crs"))
4956
        pszCur = pszURL + 30;
4957
    else if (STARTS_WITH_CI(pszURL, "https://www.opengis.net/def/crs"))
4958
        pszCur = pszURL + 31;
4959
    else if (STARTS_WITH_CI(pszURL, "www.opengis.net/def/crs"))
4960
        pszCur = pszURL + 23;
4961
    else
4962
    {
4963
        CPLError(CE_Failure, CPLE_AppDefined, "URL %s not a supported format.",
4964
                 pszURL);
4965
        return OGRERR_FAILURE;
4966
    }
4967
4968
    if (*pszCur == '\0')
4969
    {
4970
        CPLError(CE_Failure, CPLE_AppDefined, "URL %s malformed.", pszURL);
4971
        return OGRERR_FAILURE;
4972
    }
4973
4974
    /* -------------------------------------------------------------------- */
4975
    /*      Clear any existing definition.                                  */
4976
    /* -------------------------------------------------------------------- */
4977
    Clear();
4978
4979
    if (STARTS_WITH_CI(pszCur, "-compound?1="))
4980
    {
4981
        /* --------------------------------------------------------------------
4982
         */
4983
        /*      It's a compound CRS, of the form: */
4984
        /*                                                                      */
4985
        /*      http://opengis.net/def/crs-compound?1=URL1&2=URL2&3=URL3&.. */
4986
        /* --------------------------------------------------------------------
4987
         */
4988
        pszCur += 12;
4989
4990
        // Extract each component CRS URL.
4991
        int iComponentUrl = 2;
4992
4993
        CPLString osName = "";
4994
        Clear();
4995
4996
        while (iComponentUrl != -1)
4997
        {
4998
            char searchStr[15] = {};
4999
            snprintf(searchStr, sizeof(searchStr), "&%d=", iComponentUrl);
5000
5001
            const char *pszUrlEnd = strstr(pszCur, searchStr);
5002
5003
            // Figure out the next component URL.
5004
            char *pszComponentUrl = nullptr;
5005
5006
            if (pszUrlEnd)
5007
            {
5008
                size_t nLen = pszUrlEnd - pszCur;
5009
                pszComponentUrl = static_cast<char *>(CPLMalloc(nLen + 1));
5010
                strncpy(pszComponentUrl, pszCur, nLen);
5011
                pszComponentUrl[nLen] = '\0';
5012
5013
                ++iComponentUrl;
5014
                pszCur += nLen + strlen(searchStr);
5015
            }
5016
            else
5017
            {
5018
                if (iComponentUrl == 2)
5019
                {
5020
                    CPLError(CE_Failure, CPLE_AppDefined,
5021
                             "Compound CRS URLs must have at least two "
5022
                             "component CRSs.");
5023
                    return OGRERR_FAILURE;
5024
                }
5025
                else
5026
                {
5027
                    pszComponentUrl = CPLStrdup(pszCur);
5028
                    // no more components
5029
                    iComponentUrl = -1;
5030
                }
5031
            }
5032
5033
            OGRSpatialReference oComponentSRS;
5034
            OGRErr eStatus = oComponentSRS.importFromCRSURL(pszComponentUrl);
5035
5036
            CPLFree(pszComponentUrl);
5037
            pszComponentUrl = nullptr;
5038
5039
            if (eStatus == OGRERR_NONE)
5040
            {
5041
                if (osName.length() != 0)
5042
                {
5043
                    osName += " + ";
5044
                }
5045
                osName += oComponentSRS.GetRoot()->GetValue();
5046
                SetNode("COMPD_CS", osName);
5047
                GetRoot()->AddChild(oComponentSRS.GetRoot()->Clone());
5048
            }
5049
            else
5050
                return eStatus;
5051
        }
5052
5053
        return OGRERR_NONE;
5054
    }
5055
5056
    /* -------------------------------------------------------------------- */
5057
    /*      It's a normal CRS URL, of the form:                             */
5058
    /*                                                                      */
5059
    /*      http://opengis.net/def/crs/AUTHORITY/VERSION/CODE               */
5060
    /* -------------------------------------------------------------------- */
5061
    ++pszCur;
5062
    const char *pszAuthority = pszCur;
5063
5064
    // skip authority
5065
    while (*pszCur != '/' && *pszCur)
5066
        pszCur++;
5067
    if (*pszCur == '/')
5068
        pszCur++;
5069
5070
    // skip version
5071
    while (*pszCur != '/' && *pszCur)
5072
        pszCur++;
5073
    if (*pszCur == '/')
5074
        pszCur++;
5075
5076
    const char *pszCode = pszCur;
5077
5078
    return importFromURNPart(pszAuthority, pszCode, pszURL);
5079
#endif
5080
7.20k
}
5081
5082
/************************************************************************/
5083
/*                         importFromWMSAUTO()                          */
5084
/************************************************************************/
5085
5086
/**
5087
 * \brief Initialize from WMSAUTO string.
5088
 *
5089
 * Note that the WMS 1.3 specification does not include the
5090
 * units code, while apparently earlier specs do.  We try to
5091
 * guess around this.
5092
 *
5093
 * @param pszDefinition the WMSAUTO string
5094
 *
5095
 * @return OGRERR_NONE on success or an error code.
5096
 */
5097
OGRErr OGRSpatialReference::importFromWMSAUTO(const char *pszDefinition)
5098
5099
4.92k
{
5100
4.92k
    TAKE_OPTIONAL_LOCK();
5101
5102
4.92k
#if PROJ_AT_LEAST_VERSION(8, 1, 0)
5103
4.92k
    if (strlen(pszDefinition) >= 10000)
5104
153
    {
5105
153
        CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
5106
153
        return OGRERR_CORRUPT_DATA;
5107
153
    }
5108
5109
4.77k
    auto obj = proj_create(d->getPROJContext(), pszDefinition);
5110
4.77k
    if (!obj)
5111
3.91k
    {
5112
3.91k
        return OGRERR_FAILURE;
5113
3.91k
    }
5114
855
    Clear();
5115
855
    d->setPjCRS(obj);
5116
855
    return OGRERR_NONE;
5117
#else
5118
    int nProjId, nUnitsId;
5119
    double dfRefLong, dfRefLat = 0.0;
5120
5121
    /* -------------------------------------------------------------------- */
5122
    /*      Tokenize                                                        */
5123
    /* -------------------------------------------------------------------- */
5124
    if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
5125
        pszDefinition += 5;
5126
5127
    char **papszTokens =
5128
        CSLTokenizeStringComplex(pszDefinition, ",", FALSE, TRUE);
5129
5130
    if (CSLCount(papszTokens) == 4)
5131
    {
5132
        nProjId = atoi(papszTokens[0]);
5133
        nUnitsId = atoi(papszTokens[1]);
5134
        dfRefLong = CPLAtof(papszTokens[2]);
5135
        dfRefLat = CPLAtof(papszTokens[3]);
5136
    }
5137
    else if (CSLCount(papszTokens) == 3 && atoi(papszTokens[0]) == 42005)
5138
    {
5139
        nProjId = atoi(papszTokens[0]);
5140
        nUnitsId = atoi(papszTokens[1]);
5141
        dfRefLong = CPLAtof(papszTokens[2]);
5142
        dfRefLat = 0.0;
5143
    }
5144
    else if (CSLCount(papszTokens) == 3)
5145
    {
5146
        nProjId = atoi(papszTokens[0]);
5147
        nUnitsId = 9001;
5148
        dfRefLong = CPLAtof(papszTokens[1]);
5149
        dfRefLat = CPLAtof(papszTokens[2]);
5150
    }
5151
    else if (CSLCount(papszTokens) == 2 && atoi(papszTokens[0]) == 42005)
5152
    {
5153
        nProjId = atoi(papszTokens[0]);
5154
        nUnitsId = 9001;
5155
        dfRefLong = CPLAtof(papszTokens[1]);
5156
    }
5157
    else
5158
    {
5159
        CSLDestroy(papszTokens);
5160
        CPLError(CE_Failure, CPLE_AppDefined,
5161
                 "AUTO projection has wrong number of arguments, expected\n"
5162
                 "AUTO:proj_id,units_id,ref_long,ref_lat or"
5163
                 "AUTO:proj_id,ref_long,ref_lat");
5164
        return OGRERR_FAILURE;
5165
    }
5166
5167
    CSLDestroy(papszTokens);
5168
    papszTokens = nullptr;
5169
5170
    /* -------------------------------------------------------------------- */
5171
    /*      Build coordsys.                                                 */
5172
    /* -------------------------------------------------------------------- */
5173
    Clear();
5174
5175
    /* -------------------------------------------------------------------- */
5176
    /*      Set WGS84.                                                      */
5177
    /* -------------------------------------------------------------------- */
5178
    SetWellKnownGeogCS("WGS84");
5179
5180
    switch (nProjId)
5181
    {
5182
        case 42001:  // Auto UTM
5183
            SetUTM(static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
5184
                   dfRefLat >= 0.0);
5185
            break;
5186
5187
        case 42002:  // Auto TM (strangely very UTM-like).
5188
            SetTM(0, dfRefLong, 0.9996, 500000.0,
5189
                  (dfRefLat >= 0.0) ? 0.0 : 10000000.0);
5190
            break;
5191
5192
        case 42003:  // Auto Orthographic.
5193
            SetOrthographic(dfRefLat, dfRefLong, 0.0, 0.0);
5194
            break;
5195
5196
        case 42004:  // Auto Equirectangular
5197
            SetEquirectangular(dfRefLat, dfRefLong, 0.0, 0.0);
5198
            break;
5199
5200
        case 42005:
5201
            SetMollweide(dfRefLong, 0.0, 0.0);
5202
            break;
5203
5204
        default:
5205
            CPLError(CE_Failure, CPLE_AppDefined,
5206
                     "Unsupported projection id in importFromWMSAUTO(): %d",
5207
                     nProjId);
5208
            return OGRERR_FAILURE;
5209
    }
5210
5211
    /* -------------------------------------------------------------------- */
5212
    /*      Set units.                                                      */
5213
    /* -------------------------------------------------------------------- */
5214
5215
    switch (nUnitsId)
5216
    {
5217
        case 9001:
5218
            SetTargetLinearUnits(nullptr, SRS_UL_METER, 1.0, "EPSG", "9001");
5219
            break;
5220
5221
        case 9002:
5222
            SetTargetLinearUnits(nullptr, "Foot", 0.3048, "EPSG", "9002");
5223
            break;
5224
5225
        case 9003:
5226
            SetTargetLinearUnits(nullptr, "US survey foot",
5227
                                 CPLAtof(SRS_UL_US_FOOT_CONV), "EPSG", "9003");
5228
            break;
5229
5230
        default:
5231
            CPLError(CE_Failure, CPLE_AppDefined,
5232
                     "Unsupported units code (%d).", nUnitsId);
5233
            return OGRERR_FAILURE;
5234
            break;
5235
    }
5236
5237
    return OGRERR_NONE;
5238
#endif
5239
4.77k
}
5240
5241
/************************************************************************/
5242
/*                            GetSemiMajor()                            */
5243
/************************************************************************/
5244
5245
/**
5246
 * \brief Get spheroid semi major axis (in metres starting with GDAL 3.0)
5247
 *
5248
 * This method does the same thing as the C function OSRGetSemiMajor().
5249
 *
5250
 * @param pnErr if non-NULL set to OGRERR_FAILURE if semi major axis
5251
 * can be found.
5252
 *
5253
 * @return semi-major axis, or SRS_WGS84_SEMIMAJOR if it can't be found.
5254
 */
5255
5256
double OGRSpatialReference::GetSemiMajor(OGRErr *pnErr) const
5257
5258
14.2k
{
5259
14.2k
    TAKE_OPTIONAL_LOCK();
5260
5261
14.2k
    if (pnErr != nullptr)
5262
127
        *pnErr = OGRERR_FAILURE;
5263
5264
14.2k
    d->refreshProjObj();
5265
14.2k
    if (!d->m_pj_crs)
5266
103
        return SRS_WGS84_SEMIMAJOR;
5267
5268
14.1k
    auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
5269
14.1k
    if (!ellps)
5270
3.36k
        return SRS_WGS84_SEMIMAJOR;
5271
5272
10.8k
    double dfSemiMajor = 0.0;
5273
10.8k
    proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, &dfSemiMajor,
5274
10.8k
                                  nullptr, nullptr, nullptr);
5275
10.8k
    proj_destroy(ellps);
5276
5277
10.8k
    if (dfSemiMajor > 0)
5278
10.8k
    {
5279
10.8k
        if (pnErr != nullptr)
5280
127
            *pnErr = OGRERR_NONE;
5281
10.8k
        return dfSemiMajor;
5282
10.8k
    }
5283
5284
0
    return SRS_WGS84_SEMIMAJOR;
5285
10.8k
}
5286
5287
/************************************************************************/
5288
/*                          OSRGetSemiMajor()                           */
5289
/************************************************************************/
5290
5291
/**
5292
 * \brief Get spheroid semi major axis.
5293
 *
5294
 * This function is the same as OGRSpatialReference::GetSemiMajor()
5295
 */
5296
double OSRGetSemiMajor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
5297
5298
0
{
5299
0
    VALIDATE_POINTER1(hSRS, "OSRGetSemiMajor", 0);
5300
5301
0
    return ToPointer(hSRS)->GetSemiMajor(pnErr);
5302
0
}
5303
5304
/************************************************************************/
5305
/*                          GetInvFlattening()                          */
5306
/************************************************************************/
5307
5308
/**
5309
 * \brief Get spheroid inverse flattening.
5310
 *
5311
 * This method does the same thing as the C function OSRGetInvFlattening().
5312
 *
5313
 * @param pnErr if non-NULL set to OGRERR_FAILURE if no inverse flattening
5314
 * can be found.
5315
 *
5316
 * @return inverse flattening, or SRS_WGS84_INVFLATTENING if it can't be found.
5317
 */
5318
5319
double OGRSpatialReference::GetInvFlattening(OGRErr *pnErr) const
5320
5321
5.09k
{
5322
5.09k
    TAKE_OPTIONAL_LOCK();
5323
5324
5.09k
    if (pnErr != nullptr)
5325
127
        *pnErr = OGRERR_FAILURE;
5326
5327
5.09k
    d->refreshProjObj();
5328
5.09k
    if (!d->m_pj_crs)
5329
53
        return SRS_WGS84_INVFLATTENING;
5330
5331
5.03k
    auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
5332
5.03k
    if (!ellps)
5333
4
        return SRS_WGS84_INVFLATTENING;
5334
5335
5.03k
    double dfInvFlattening = -1.0;
5336
5.03k
    proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, nullptr, nullptr,
5337
5.03k
                                  nullptr, &dfInvFlattening);
5338
5.03k
    proj_destroy(ellps);
5339
5340
5.03k
    if (dfInvFlattening >= 0.0)
5341
5.03k
    {
5342
5.03k
        if (pnErr != nullptr)
5343
127
            *pnErr = OGRERR_NONE;
5344
5.03k
        return dfInvFlattening;
5345
5.03k
    }
5346
5347
0
    return SRS_WGS84_INVFLATTENING;
5348
5.03k
}
5349
5350
/************************************************************************/
5351
/*                        OSRGetInvFlattening()                         */
5352
/************************************************************************/
5353
5354
/**
5355
 * \brief Get spheroid inverse flattening.
5356
 *
5357
 * This function is the same as OGRSpatialReference::GetInvFlattening()
5358
 */
5359
double OSRGetInvFlattening(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
5360
5361
0
{
5362
0
    VALIDATE_POINTER1(hSRS, "OSRGetInvFlattening", 0);
5363
5364
0
    return ToPointer(hSRS)->GetInvFlattening(pnErr);
5365
0
}
5366
5367
/************************************************************************/
5368
/*                          GetEccentricity()                           */
5369
/************************************************************************/
5370
5371
/**
5372
 * \brief Get spheroid eccentricity
5373
 *
5374
 * @return eccentricity (or -1 in case of error)
5375
 */
5376
5377
double OGRSpatialReference::GetEccentricity() const
5378
5379
0
{
5380
0
    OGRErr eErr = OGRERR_NONE;
5381
0
    const double dfInvFlattening = GetInvFlattening(&eErr);
5382
0
    if (eErr != OGRERR_NONE)
5383
0
    {
5384
0
        return -1.0;
5385
0
    }
5386
0
    if (dfInvFlattening == 0.0)
5387
0
        return 0.0;
5388
0
    if (dfInvFlattening < 0.5)
5389
0
        return -1.0;
5390
0
    return sqrt(2.0 / dfInvFlattening -
5391
0
                1.0 / (dfInvFlattening * dfInvFlattening));
5392
0
}
5393
5394
/************************************************************************/
5395
/*                       GetSquaredEccentricity()                       */
5396
/************************************************************************/
5397
5398
/**
5399
 * \brief Get spheroid squared eccentricity
5400
 *
5401
 * @return squared eccentricity (or -1 in case of error)
5402
 */
5403
5404
double OGRSpatialReference::GetSquaredEccentricity() const
5405
5406
0
{
5407
0
    OGRErr eErr = OGRERR_NONE;
5408
0
    const double dfInvFlattening = GetInvFlattening(&eErr);
5409
0
    if (eErr != OGRERR_NONE)
5410
0
    {
5411
0
        return -1.0;
5412
0
    }
5413
0
    if (dfInvFlattening == 0.0)
5414
0
        return 0.0;
5415
0
    if (dfInvFlattening < 0.5)
5416
0
        return -1.0;
5417
0
    return 2.0 / dfInvFlattening - 1.0 / (dfInvFlattening * dfInvFlattening);
5418
0
}
5419
5420
/************************************************************************/
5421
/*                            GetSemiMinor()                            */
5422
/************************************************************************/
5423
5424
/**
5425
 * \brief Get spheroid semi minor axis.
5426
 *
5427
 * This method does the same thing as the C function OSRGetSemiMinor().
5428
 *
5429
 * @param pnErr if non-NULL set to OGRERR_FAILURE if semi minor axis
5430
 * can be found.
5431
 *
5432
 * @return semi-minor axis, or WGS84 semi minor if it can't be found.
5433
 */
5434
5435
double OGRSpatialReference::GetSemiMinor(OGRErr *pnErr) const
5436
5437
2.39k
{
5438
2.39k
    const double dfSemiMajor = GetSemiMajor(pnErr);
5439
2.39k
    const double dfInvFlattening = GetInvFlattening(pnErr);
5440
5441
2.39k
    return OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
5442
2.39k
}
5443
5444
/************************************************************************/
5445
/*                          OSRGetSemiMinor()                           */
5446
/************************************************************************/
5447
5448
/**
5449
 * \brief Get spheroid semi minor axis.
5450
 *
5451
 * This function is the same as OGRSpatialReference::GetSemiMinor()
5452
 */
5453
double OSRGetSemiMinor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
5454
5455
0
{
5456
0
    VALIDATE_POINTER1(hSRS, "OSRGetSemiMinor", 0);
5457
5458
0
    return ToPointer(hSRS)->GetSemiMinor(pnErr);
5459
0
}
5460
5461
/************************************************************************/
5462
/*                             SetLocalCS()                             */
5463
/************************************************************************/
5464
5465
/**
5466
 * \brief Set the user visible LOCAL_CS name.
5467
 *
5468
 * This method is the same as the C function OSRSetLocalCS().
5469
 *
5470
 * This method will ensure a LOCAL_CS node is created as the root,
5471
 * and set the provided name on it.  It must be used before SetLinearUnits().
5472
 *
5473
 * @param pszName the user visible name to assign.  Not used as a key.
5474
 *
5475
 * @return OGRERR_NONE on success.
5476
 */
5477
5478
OGRErr OGRSpatialReference::SetLocalCS(const char *pszName)
5479
5480
42.5k
{
5481
42.5k
    TAKE_OPTIONAL_LOCK();
5482
5483
42.5k
    if (d->m_pjType == PJ_TYPE_UNKNOWN ||
5484
3
        d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
5485
42.5k
    {
5486
42.5k
        d->setPjCRS(proj_create_engineering_crs(d->getPROJContext(), pszName));
5487
42.5k
    }
5488
0
    else
5489
0
    {
5490
0
        CPLDebug("OGR",
5491
0
                 "OGRSpatialReference::SetLocalCS(%s) failed.  "
5492
0
                 "It appears an incompatible object already exists.",
5493
0
                 pszName);
5494
0
        return OGRERR_FAILURE;
5495
0
    }
5496
5497
42.5k
    return OGRERR_NONE;
5498
42.5k
}
5499
5500
/************************************************************************/
5501
/*                           OSRSetLocalCS()                            */
5502
/************************************************************************/
5503
5504
/**
5505
 * \brief Set the user visible LOCAL_CS name.
5506
 *
5507
 * This function is the same as OGRSpatialReference::SetLocalCS()
5508
 */
5509
OGRErr OSRSetLocalCS(OGRSpatialReferenceH hSRS, const char *pszName)
5510
5511
0
{
5512
0
    VALIDATE_POINTER1(hSRS, "OSRSetLocalCS", OGRERR_FAILURE);
5513
5514
0
    return ToPointer(hSRS)->SetLocalCS(pszName);
5515
0
}
5516
5517
/************************************************************************/
5518
/*                             SetGeocCS()                              */
5519
/************************************************************************/
5520
5521
/**
5522
 * \brief Set the user visible GEOCCS name.
5523
 *
5524
 * This method is the same as the C function OSRSetGeocCS().
5525
5526
 * This method will ensure a GEOCCS node is created as the root,
5527
 * and set the provided name on it.  If used on a GEOGCS coordinate system,
5528
 * the DATUM and PRIMEM nodes from the GEOGCS will be transferred over to
5529
 * the GEOGCS.
5530
 *
5531
 * @param pszName the user visible name to assign.  Not used as a key.
5532
 *
5533
 * @return OGRERR_NONE on success.
5534
 *
5535
 */
5536
5537
OGRErr OGRSpatialReference::SetGeocCS(const char *pszName)
5538
5539
2.94k
{
5540
2.94k
    TAKE_OPTIONAL_LOCK();
5541
5542
2.94k
    OGRErr eErr = OGRERR_NONE;
5543
2.94k
    d->refreshProjObj();
5544
2.94k
    d->demoteFromBoundCRS();
5545
2.94k
    if (d->m_pjType == PJ_TYPE_UNKNOWN)
5546
2.94k
    {
5547
2.94k
        d->setPjCRS(proj_create_geocentric_crs(
5548
2.94k
            d->getPROJContext(), pszName, "World Geodetic System 1984",
5549
2.94k
            "WGS 84", SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING,
5550
2.94k
            SRS_PM_GREENWICH, 0.0, SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV),
5551
2.94k
            "Metre", 1.0));
5552
2.94k
    }
5553
0
    else if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
5554
0
    {
5555
0
        d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
5556
0
    }
5557
0
    else if (d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
5558
0
             d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
5559
0
    {
5560
0
        auto datum = proj_crs_get_datum(d->getPROJContext(), d->m_pj_crs);
5561
0
#if PROJ_VERSION_MAJOR > 7 ||                                                  \
5562
0
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
5563
0
        if (datum == nullptr)
5564
0
        {
5565
0
            datum =
5566
0
                proj_crs_get_datum_ensemble(d->getPROJContext(), d->m_pj_crs);
5567
0
        }
5568
0
#endif
5569
0
        if (datum == nullptr)
5570
0
        {
5571
0
            d->undoDemoteFromBoundCRS();
5572
0
            return OGRERR_FAILURE;
5573
0
        }
5574
5575
0
        auto pj_crs = proj_create_geocentric_crs_from_datum(
5576
0
            d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, nullptr,
5577
0
            0.0);
5578
0
        d->setPjCRS(pj_crs);
5579
5580
0
        proj_destroy(datum);
5581
0
    }
5582
0
    else
5583
0
    {
5584
0
        CPLDebug("OGR",
5585
0
                 "OGRSpatialReference::SetGeocCS(%s) failed.  "
5586
0
                 "It appears an incompatible object already exists.",
5587
0
                 pszName);
5588
0
        eErr = OGRERR_FAILURE;
5589
0
    }
5590
2.94k
    d->undoDemoteFromBoundCRS();
5591
5592
2.94k
    return eErr;
5593
2.94k
}
5594
5595
/************************************************************************/
5596
/*                            OSRSetGeocCS()                            */
5597
/************************************************************************/
5598
5599
/**
5600
 * \brief Set the user visible PROJCS name.
5601
 *
5602
 * This function is the same as OGRSpatialReference::SetGeocCS()
5603
 *
5604
 */
5605
OGRErr OSRSetGeocCS(OGRSpatialReferenceH hSRS, const char *pszName)
5606
5607
0
{
5608
0
    VALIDATE_POINTER1(hSRS, "OSRSetGeocCS", OGRERR_FAILURE);
5609
5610
0
    return ToPointer(hSRS)->SetGeocCS(pszName);
5611
0
}
5612
5613
/************************************************************************/
5614
/*                             SetVertCS()                              */
5615
/************************************************************************/
5616
5617
/**
5618
 * \brief Set the user visible VERT_CS name.
5619
 *
5620
 * This method is the same as the C function OSRSetVertCS().
5621
5622
 * This method will ensure a VERT_CS node is created if needed.  If the
5623
 * existing coordinate system is GEOGCS or PROJCS rooted, then it will be
5624
 * turned into a COMPD_CS.
5625
 *
5626
 * @param pszVertCSName the user visible name of the vertical coordinate
5627
 * system. Not used as a key.
5628
 *
5629
 * @param pszVertDatumName the user visible name of the vertical datum.  It
5630
 * is helpful if this matches the EPSG name.
5631
 *
5632
 * @param nVertDatumType the OGC vertical datum type. Ignored
5633
 *
5634
 * @return OGRERR_NONE on success.
5635
 *
5636
 */
5637
5638
OGRErr OGRSpatialReference::SetVertCS(const char *pszVertCSName,
5639
                                      const char *pszVertDatumName,
5640
                                      int nVertDatumType)
5641
5642
0
{
5643
0
    TAKE_OPTIONAL_LOCK();
5644
5645
0
    CPL_IGNORE_RET_VAL(nVertDatumType);
5646
5647
0
    d->refreshProjObj();
5648
5649
0
    auto vertCRS = proj_create_vertical_crs(d->getPROJContext(), pszVertCSName,
5650
0
                                            pszVertDatumName, nullptr, 0.0);
5651
5652
    /* -------------------------------------------------------------------- */
5653
    /*      Handle the case where we want to make a compound coordinate     */
5654
    /*      system.                                                         */
5655
    /* -------------------------------------------------------------------- */
5656
0
    if (IsProjected() || IsGeographic())
5657
0
    {
5658
0
        auto compoundCRS = proj_create_compound_crs(
5659
0
            d->getPROJContext(), nullptr, d->m_pj_crs, vertCRS);
5660
0
        proj_destroy(vertCRS);
5661
0
        d->setPjCRS(compoundCRS);
5662
0
    }
5663
0
    else
5664
0
    {
5665
0
        d->setPjCRS(vertCRS);
5666
0
    }
5667
0
    return OGRERR_NONE;
5668
0
}
5669
5670
/************************************************************************/
5671
/*                            OSRSetVertCS()                            */
5672
/************************************************************************/
5673
5674
/**
5675
 * \brief Setup the vertical coordinate system.
5676
 *
5677
 * This function is the same as OGRSpatialReference::SetVertCS()
5678
 *
5679
 */
5680
OGRErr OSRSetVertCS(OGRSpatialReferenceH hSRS, const char *pszVertCSName,
5681
                    const char *pszVertDatumName, int nVertDatumType)
5682
5683
0
{
5684
0
    VALIDATE_POINTER1(hSRS, "OSRSetVertCS", OGRERR_FAILURE);
5685
5686
0
    return ToPointer(hSRS)->SetVertCS(pszVertCSName, pszVertDatumName,
5687
0
                                      nVertDatumType);
5688
0
}
5689
5690
/************************************************************************/
5691
/*                           SetCompoundCS()                            */
5692
/************************************************************************/
5693
5694
/**
5695
 * \brief Setup a compound coordinate system.
5696
 *
5697
 * This method is the same as the C function OSRSetCompoundCS().
5698
5699
 * This method is replace the current SRS with a COMPD_CS coordinate system
5700
 * consisting of the passed in horizontal and vertical coordinate systems.
5701
 *
5702
 * @param pszName the name of the compound coordinate system.
5703
 *
5704
 * @param poHorizSRS the horizontal SRS (PROJCS or GEOGCS).
5705
 *
5706
 * @param poVertSRS the vertical SRS (VERT_CS).
5707
 *
5708
 * @return OGRERR_NONE on success.
5709
 */
5710
5711
OGRErr OGRSpatialReference::SetCompoundCS(const char *pszName,
5712
                                          const OGRSpatialReference *poHorizSRS,
5713
                                          const OGRSpatialReference *poVertSRS)
5714
5715
74
{
5716
74
    TAKE_OPTIONAL_LOCK();
5717
5718
    /* -------------------------------------------------------------------- */
5719
    /*      Verify these are legal horizontal and vertical coordinate       */
5720
    /*      systems.                                                        */
5721
    /* -------------------------------------------------------------------- */
5722
74
    if (!poVertSRS->IsVertical())
5723
0
    {
5724
0
        CPLError(CE_Failure, CPLE_AppDefined,
5725
0
                 "SetCompoundCS() fails, vertical component is not VERT_CS.");
5726
0
        return OGRERR_FAILURE;
5727
0
    }
5728
74
    if (!poHorizSRS->IsProjected() && !poHorizSRS->IsGeographic())
5729
0
    {
5730
0
        CPLError(CE_Failure, CPLE_AppDefined,
5731
0
                 "SetCompoundCS() fails, horizontal component is not PROJCS or "
5732
0
                 "GEOGCS.");
5733
0
        return OGRERR_FAILURE;
5734
0
    }
5735
5736
    /* -------------------------------------------------------------------- */
5737
    /*      Replace with compound srs.                                      */
5738
    /* -------------------------------------------------------------------- */
5739
74
    Clear();
5740
5741
74
    auto compoundCRS = proj_create_compound_crs(d->getPROJContext(), pszName,
5742
74
                                                poHorizSRS->d->m_pj_crs,
5743
74
                                                poVertSRS->d->m_pj_crs);
5744
74
    d->setPjCRS(compoundCRS);
5745
5746
74
    return OGRERR_NONE;
5747
74
}
5748
5749
/************************************************************************/
5750
/*                          OSRSetCompoundCS()                          */
5751
/************************************************************************/
5752
5753
/**
5754
 * \brief Setup a compound coordinate system.
5755
 *
5756
 * This function is the same as OGRSpatialReference::SetCompoundCS()
5757
 */
5758
OGRErr OSRSetCompoundCS(OGRSpatialReferenceH hSRS, const char *pszName,
5759
                        OGRSpatialReferenceH hHorizSRS,
5760
                        OGRSpatialReferenceH hVertSRS)
5761
5762
0
{
5763
0
    VALIDATE_POINTER1(hSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
5764
0
    VALIDATE_POINTER1(hHorizSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
5765
0
    VALIDATE_POINTER1(hVertSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
5766
5767
0
    return ToPointer(hSRS)->SetCompoundCS(pszName, ToPointer(hHorizSRS),
5768
0
                                          ToPointer(hVertSRS));
5769
0
}
5770
5771
/************************************************************************/
5772
/*                             SetProjCS()                              */
5773
/************************************************************************/
5774
5775
/**
5776
 * \brief Set the user visible PROJCS name.
5777
 *
5778
 * This method is the same as the C function OSRSetProjCS().
5779
 *
5780
 * This method will ensure a PROJCS node is created as the root,
5781
 * and set the provided name on it.  If used on a GEOGCS coordinate system,
5782
 * the GEOGCS node will be demoted to be a child of the new PROJCS root.
5783
 *
5784
 * @param pszName the user visible name to assign.  Not used as a key.
5785
 *
5786
 * @return OGRERR_NONE on success.
5787
 */
5788
5789
OGRErr OGRSpatialReference::SetProjCS(const char *pszName)
5790
5791
32.9k
{
5792
32.9k
    TAKE_OPTIONAL_LOCK();
5793
5794
32.9k
    d->refreshProjObj();
5795
32.9k
    d->demoteFromBoundCRS();
5796
32.9k
    if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
5797
1.84k
    {
5798
1.84k
        d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
5799
1.84k
    }
5800
31.1k
    else
5801
31.1k
    {
5802
31.1k
        auto dummyConv = proj_create_conversion(d->getPROJContext(), nullptr,
5803
31.1k
                                                nullptr, nullptr, nullptr,
5804
31.1k
                                                nullptr, nullptr, 0, nullptr);
5805
31.1k
        auto cs = proj_create_cartesian_2D_cs(
5806
31.1k
            d->getPROJContext(), PJ_CART2D_EASTING_NORTHING, nullptr, 0);
5807
5808
31.1k
        auto projCRS = proj_create_projected_crs(
5809
31.1k
            d->getPROJContext(), pszName, d->getGeodBaseCRS(), dummyConv, cs);
5810
31.1k
        proj_destroy(dummyConv);
5811
31.1k
        proj_destroy(cs);
5812
5813
31.1k
        d->setPjCRS(projCRS);
5814
31.1k
    }
5815
32.9k
    d->undoDemoteFromBoundCRS();
5816
32.9k
    return OGRERR_NONE;
5817
32.9k
}
5818
5819
/************************************************************************/
5820
/*                            OSRSetProjCS()                            */
5821
/************************************************************************/
5822
5823
/**
5824
 * \brief Set the user visible PROJCS name.
5825
 *
5826
 * This function is the same as OGRSpatialReference::SetProjCS()
5827
 */
5828
OGRErr OSRSetProjCS(OGRSpatialReferenceH hSRS, const char *pszName)
5829
5830
0
{
5831
0
    VALIDATE_POINTER1(hSRS, "OSRSetProjCS", OGRERR_FAILURE);
5832
5833
0
    return ToPointer(hSRS)->SetProjCS(pszName);
5834
0
}
5835
5836
/************************************************************************/
5837
/*                           SetProjection()                            */
5838
/************************************************************************/
5839
5840
/**
5841
 * \brief Set a projection name.
5842
 *
5843
 * This method is the same as the C function OSRSetProjection().
5844
 *
5845
 * @param pszProjection the projection name, which should be selected from
5846
 * the macros in ogr_srs_api.h, such as SRS_PT_TRANSVERSE_MERCATOR.
5847
 *
5848
 * @return OGRERR_NONE on success.
5849
 */
5850
5851
OGRErr OGRSpatialReference::SetProjection(const char *pszProjection)
5852
5853
10.4k
{
5854
10.4k
    TAKE_OPTIONAL_LOCK();
5855
5856
10.4k
    OGR_SRSNode *poGeogCS = nullptr;
5857
5858
10.4k
    if (GetRoot() != nullptr && EQUAL(d->m_poRoot->GetValue(), "GEOGCS"))
5859
94
    {
5860
94
        poGeogCS = d->m_poRoot;
5861
94
        d->m_poRoot = nullptr;
5862
94
    }
5863
5864
10.4k
    if (!GetAttrNode("PROJCS"))
5865
838
    {
5866
838
        SetNode("PROJCS", "unnamed");
5867
838
    }
5868
5869
10.4k
    const OGRErr eErr = SetNode("PROJCS|PROJECTION", pszProjection);
5870
10.4k
    if (eErr != OGRERR_NONE)
5871
0
        return eErr;
5872
5873
10.4k
    if (poGeogCS != nullptr)
5874
94
        d->m_poRoot->InsertChild(poGeogCS, 1);
5875
5876
10.4k
    return OGRERR_NONE;
5877
10.4k
}
5878
5879
/************************************************************************/
5880
/*                          OSRSetProjection()                          */
5881
/************************************************************************/
5882
5883
/**
5884
 * \brief Set a projection name.
5885
 *
5886
 * This function is the same as OGRSpatialReference::SetProjection()
5887
 */
5888
OGRErr OSRSetProjection(OGRSpatialReferenceH hSRS, const char *pszProjection)
5889
5890
0
{
5891
0
    VALIDATE_POINTER1(hSRS, "OSRSetProjection", OGRERR_FAILURE);
5892
5893
0
    return ToPointer(hSRS)->SetProjection(pszProjection);
5894
0
}
5895
5896
/************************************************************************/
5897
/*                      GetWKT2ProjectionMethod()                       */
5898
/************************************************************************/
5899
5900
/**
5901
 * \brief Returns info on the projection method, based on WKT2 naming
5902
 * conventions.
5903
 *
5904
 * The returned strings are short lived and should be considered to be
5905
 * invalidated by any further call to the GDAL API.
5906
 *
5907
 * @param[out] ppszMethodName Pointer to a string that will receive the
5908
 * projection method name.
5909
 * @param[out] ppszMethodAuthName null pointer, or pointer to a string that will
5910
 * receive the name of the authority that defines the projection method.
5911
 * *ppszMethodAuthName may be nullptr if the projection method is not linked to
5912
 * an authority.
5913
 * @param[out] ppszMethodCode null pointer, or pointer to a string that will
5914
 * receive the code that defines the projection method.
5915
 * *ppszMethodCode may be nullptr if the projection method is not linked to
5916
 * an authority.
5917
 *
5918
 * @return OGRERR_NONE on success.
5919
 */
5920
OGRErr
5921
OGRSpatialReference::GetWKT2ProjectionMethod(const char **ppszMethodName,
5922
                                             const char **ppszMethodAuthName,
5923
                                             const char **ppszMethodCode) const
5924
0
{
5925
0
    TAKE_OPTIONAL_LOCK();
5926
5927
0
    auto conv = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
5928
0
    if (!conv)
5929
0
        return OGRERR_FAILURE;
5930
0
    const char *pszTmpMethodName = "";
5931
0
    const char *pszTmpMethodAuthName = "";
5932
0
    const char *pszTmpMethodCode = "";
5933
0
    int ret = proj_coordoperation_get_method_info(
5934
0
        d->getPROJContext(), conv, &pszTmpMethodName, &pszTmpMethodAuthName,
5935
0
        &pszTmpMethodCode);
5936
    // "Internalize" temporary strings returned by PROJ
5937
0
    CPLAssert(pszTmpMethodName);
5938
0
    if (ppszMethodName)
5939
0
        *ppszMethodName = CPLSPrintf("%s", pszTmpMethodName);
5940
0
    if (ppszMethodAuthName)
5941
0
        *ppszMethodAuthName = pszTmpMethodAuthName
5942
0
                                  ? CPLSPrintf("%s", pszTmpMethodAuthName)
5943
0
                                  : nullptr;
5944
0
    if (ppszMethodCode)
5945
0
        *ppszMethodCode =
5946
0
            pszTmpMethodCode ? CPLSPrintf("%s", pszTmpMethodCode) : nullptr;
5947
0
    proj_destroy(conv);
5948
0
    return ret ? OGRERR_NONE : OGRERR_FAILURE;
5949
0
}
5950
5951
/************************************************************************/
5952
/*                            SetProjParm()                             */
5953
/************************************************************************/
5954
5955
/**
5956
 * \brief Set a projection parameter value.
5957
 *
5958
 * Adds a new PARAMETER under the PROJCS with the indicated name and value.
5959
 *
5960
 * This method is the same as the C function OSRSetProjParm().
5961
 *
5962
 * Please check https://gdal.org/proj_list pages for
5963
 * legal parameter names for specific projections.
5964
 *
5965
 *
5966
 * @param pszParamName the parameter name, which should be selected from
5967
 * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
5968
 *
5969
 * @param dfValue value to assign.
5970
 *
5971
 * @return OGRERR_NONE on success.
5972
 */
5973
5974
OGRErr OGRSpatialReference::SetProjParm(const char *pszParamName,
5975
                                        double dfValue)
5976
5977
7.06k
{
5978
7.06k
    TAKE_OPTIONAL_LOCK();
5979
5980
7.06k
    OGR_SRSNode *poPROJCS = GetAttrNode("PROJCS");
5981
5982
7.06k
    if (poPROJCS == nullptr)
5983
0
        return OGRERR_FAILURE;
5984
5985
7.06k
    char szValue[64] = {'\0'};
5986
7.06k
    OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
5987
5988
    /* -------------------------------------------------------------------- */
5989
    /*      Try to find existing parameter with this name.                  */
5990
    /* -------------------------------------------------------------------- */
5991
66.5k
    for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
5992
59.6k
    {
5993
59.6k
        OGR_SRSNode *poParam = poPROJCS->GetChild(iChild);
5994
5995
59.6k
        if (EQUAL(poParam->GetValue(), "PARAMETER") &&
5996
14.9k
            poParam->GetChildCount() == 2 &&
5997
14.9k
            EQUAL(poParam->GetChild(0)->GetValue(), pszParamName))
5998
189
        {
5999
189
            poParam->GetChild(1)->SetValue(szValue);
6000
189
            return OGRERR_NONE;
6001
189
        }
6002
59.6k
    }
6003
6004
    /* -------------------------------------------------------------------- */
6005
    /*      Otherwise create a new parameter and append.                    */
6006
    /* -------------------------------------------------------------------- */
6007
6.87k
    OGR_SRSNode *poParam = new OGR_SRSNode("PARAMETER");
6008
6.87k
    poParam->AddChild(new OGR_SRSNode(pszParamName));
6009
6.87k
    poParam->AddChild(new OGR_SRSNode(szValue));
6010
6011
6.87k
    poPROJCS->AddChild(poParam);
6012
6013
6.87k
    return OGRERR_NONE;
6014
7.06k
}
6015
6016
/************************************************************************/
6017
/*                           OSRSetProjParm()                           */
6018
/************************************************************************/
6019
6020
/**
6021
 * \brief Set a projection parameter value.
6022
 *
6023
 * This function is the same as OGRSpatialReference::SetProjParm()
6024
 */
6025
OGRErr OSRSetProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
6026
                      double dfValue)
6027
6028
0
{
6029
0
    VALIDATE_POINTER1(hSRS, "OSRSetProjParm", OGRERR_FAILURE);
6030
6031
0
    return ToPointer(hSRS)->SetProjParm(pszParamName, dfValue);
6032
0
}
6033
6034
/************************************************************************/
6035
/*                            FindProjParm()                            */
6036
/************************************************************************/
6037
6038
/**
6039
 * \brief Return the child index of the named projection parameter on
6040
 * its parent PROJCS node.
6041
 *
6042
 * @param pszParameter projection parameter to look for
6043
 * @param poPROJCS projection CS node to look in. If NULL is passed,
6044
 *        the PROJCS node of the SpatialReference object will be searched.
6045
 *
6046
 * @return the child index of the named projection parameter. -1 on failure
6047
 */
6048
int OGRSpatialReference::FindProjParm(const char *pszParameter,
6049
                                      const OGR_SRSNode *poPROJCS) const
6050
6051
7.87k
{
6052
7.87k
    TAKE_OPTIONAL_LOCK();
6053
6054
7.87k
    if (poPROJCS == nullptr)
6055
0
        poPROJCS = GetAttrNode("PROJCS");
6056
6057
7.87k
    if (poPROJCS == nullptr)
6058
0
        return -1;
6059
6060
    /* -------------------------------------------------------------------- */
6061
    /*      Search for requested parameter.                                 */
6062
    /* -------------------------------------------------------------------- */
6063
7.87k
    bool bIsWKT2 = false;
6064
40.0k
    for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
6065
39.9k
    {
6066
39.9k
        const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
6067
6068
39.9k
        if (poParameter->GetChildCount() >= 2)
6069
24.2k
        {
6070
24.2k
            const char *pszValue = poParameter->GetValue();
6071
24.2k
            if (EQUAL(pszValue, "PARAMETER") &&
6072
16.3k
                EQUAL(poPROJCS->GetChild(iChild)->GetChild(0)->GetValue(),
6073
24.2k
                      pszParameter))
6074
7.86k
            {
6075
7.86k
                return iChild;
6076
7.86k
            }
6077
16.3k
            else if (EQUAL(pszValue, "METHOD"))
6078
0
            {
6079
0
                bIsWKT2 = true;
6080
0
            }
6081
24.2k
        }
6082
39.9k
    }
6083
6084
    /* -------------------------------------------------------------------- */
6085
    /*      Try similar names, for selected parameters.                     */
6086
    /* -------------------------------------------------------------------- */
6087
9
    if (EQUAL(pszParameter, SRS_PP_LATITUDE_OF_ORIGIN))
6088
3
    {
6089
3
        if (bIsWKT2)
6090
0
        {
6091
0
            int iChild = FindProjParm(
6092
0
                EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, poPROJCS);
6093
0
            if (iChild == -1)
6094
0
                iChild = FindProjParm(
6095
0
                    EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, poPROJCS);
6096
0
            return iChild;
6097
0
        }
6098
3
        return FindProjParm(SRS_PP_LATITUDE_OF_CENTER, poPROJCS);
6099
3
    }
6100
6101
6
    if (EQUAL(pszParameter, SRS_PP_CENTRAL_MERIDIAN))
6102
3
    {
6103
3
        if (bIsWKT2)
6104
0
        {
6105
0
            int iChild = FindProjParm(
6106
0
                EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, poPROJCS);
6107
0
            if (iChild == -1)
6108
0
                iChild = FindProjParm(
6109
0
                    EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, poPROJCS);
6110
0
            return iChild;
6111
0
        }
6112
3
        int iChild = FindProjParm(SRS_PP_LONGITUDE_OF_CENTER, poPROJCS);
6113
3
        if (iChild == -1)
6114
0
            iChild = FindProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, poPROJCS);
6115
3
        return iChild;
6116
3
    }
6117
6118
3
    return -1;
6119
6
}
6120
6121
/************************************************************************/
6122
/*                            GetProjParm()                             */
6123
/************************************************************************/
6124
6125
/**
6126
 * \brief Fetch a projection parameter value.
6127
 *
6128
 * NOTE: This code should be modified to translate non degree angles into
6129
 * degrees based on the GEOGCS unit.  This has not yet been done.
6130
 *
6131
 * This method is the same as the C function OSRGetProjParm().
6132
 *
6133
 * @param pszName the name of the parameter to fetch, from the set of
6134
 * SRS_PP codes in ogr_srs_api.h.
6135
 *
6136
 * @param dfDefaultValue the value to return if this parameter doesn't exist.
6137
 *
6138
 * @param pnErr place to put error code on failure.  Ignored if NULL.
6139
 *
6140
 * @return value of parameter.
6141
 */
6142
6143
double OGRSpatialReference::GetProjParm(const char *pszName,
6144
                                        double dfDefaultValue,
6145
                                        OGRErr *pnErr) const
6146
6147
7.87k
{
6148
7.87k
    TAKE_OPTIONAL_LOCK();
6149
6150
7.87k
    d->refreshProjObj();
6151
7.87k
    GetRoot();  // force update of d->m_bNodesWKT2
6152
6153
7.87k
    if (pnErr != nullptr)
6154
7.33k
        *pnErr = OGRERR_NONE;
6155
6156
    /* -------------------------------------------------------------------- */
6157
    /*      Find the desired parameter.                                     */
6158
    /* -------------------------------------------------------------------- */
6159
7.87k
    const OGR_SRSNode *poPROJCS =
6160
7.87k
        GetAttrNode(d->m_bNodesWKT2 ? "CONVERSION" : "PROJCS");
6161
7.87k
    if (poPROJCS == nullptr)
6162
0
    {
6163
0
        if (pnErr != nullptr)
6164
0
            *pnErr = OGRERR_FAILURE;
6165
0
        return dfDefaultValue;
6166
0
    }
6167
6168
7.87k
    const int iChild = FindProjParm(pszName, poPROJCS);
6169
7.87k
    if (iChild == -1)
6170
3
    {
6171
3
        if (IsProjected() && GetAxesCount() == 3)
6172
0
        {
6173
0
            OGRSpatialReference *poSRSTmp = Clone();
6174
0
            poSRSTmp->DemoteTo2D(nullptr);
6175
0
            const double dfRet =
6176
0
                poSRSTmp->GetProjParm(pszName, dfDefaultValue, pnErr);
6177
0
            delete poSRSTmp;
6178
0
            return dfRet;
6179
0
        }
6180
6181
3
        if (pnErr != nullptr)
6182
3
            *pnErr = OGRERR_FAILURE;
6183
3
        return dfDefaultValue;
6184
3
    }
6185
6186
7.86k
    const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
6187
7.86k
    return CPLAtof(poParameter->GetChild(1)->GetValue());
6188
7.87k
}
6189
6190
/************************************************************************/
6191
/*                           OSRGetProjParm()                           */
6192
/************************************************************************/
6193
6194
/**
6195
 * \brief Fetch a projection parameter value.
6196
 *
6197
 * This function is the same as OGRSpatialReference::GetProjParm()
6198
 */
6199
double OSRGetProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
6200
                      double dfDefaultValue, OGRErr *pnErr)
6201
6202
0
{
6203
0
    VALIDATE_POINTER1(hSRS, "OSRGetProjParm", 0);
6204
6205
0
    return ToPointer(hSRS)->GetProjParm(pszName, dfDefaultValue, pnErr);
6206
0
}
6207
6208
/************************************************************************/
6209
/*                          GetNormProjParm()                           */
6210
/************************************************************************/
6211
6212
/**
6213
 * \brief Fetch a normalized projection parameter value.
6214
 *
6215
 * This method is the same as GetProjParm() except that the value of
6216
 * the parameter is "normalized" into degrees or meters depending on
6217
 * whether it is linear or angular.
6218
 *
6219
 * This method is the same as the C function OSRGetNormProjParm().
6220
 *
6221
 * @param pszName the name of the parameter to fetch, from the set of
6222
 * SRS_PP codes in ogr_srs_api.h.
6223
 *
6224
 * @param dfDefaultValue the value to return if this parameter doesn't exist.
6225
 *
6226
 * @param pnErr place to put error code on failure.  Ignored if NULL.
6227
 *
6228
 * @return value of parameter.
6229
 */
6230
6231
double OGRSpatialReference::GetNormProjParm(const char *pszName,
6232
                                            double dfDefaultValue,
6233
                                            OGRErr *pnErr) const
6234
6235
7.33k
{
6236
7.33k
    TAKE_OPTIONAL_LOCK();
6237
6238
7.33k
    GetNormInfo();
6239
6240
7.33k
    OGRErr nError = OGRERR_NONE;
6241
7.33k
    double dfRawResult = GetProjParm(pszName, dfDefaultValue, &nError);
6242
7.33k
    if (pnErr != nullptr)
6243
0
        *pnErr = nError;
6244
6245
    // If we got the default just return it unadjusted.
6246
7.33k
    if (nError != OGRERR_NONE)
6247
3
        return dfRawResult;
6248
6249
7.33k
    if (d->dfToDegrees != 1.0 && IsAngularParameter(pszName))
6250
0
        dfRawResult *= d->dfToDegrees;
6251
6252
7.33k
    if (d->dfToMeter != 1.0 && IsLinearParameter(pszName))
6253
80
        return dfRawResult * d->dfToMeter;
6254
6255
7.25k
    return dfRawResult;
6256
7.33k
}
6257
6258
/************************************************************************/
6259
/*                         OSRGetNormProjParm()                         */
6260
/************************************************************************/
6261
6262
/**
6263
 * \brief This function is the same as OGRSpatialReference::
6264
 *
6265
 * This function is the same as OGRSpatialReference::GetNormProjParm()
6266
 */
6267
double OSRGetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
6268
                          double dfDefaultValue, OGRErr *pnErr)
6269
6270
0
{
6271
0
    VALIDATE_POINTER1(hSRS, "OSRGetNormProjParm", 0);
6272
6273
0
    return ToPointer(hSRS)->GetNormProjParm(pszName, dfDefaultValue, pnErr);
6274
0
}
6275
6276
/************************************************************************/
6277
/*                          SetNormProjParm()                           */
6278
/************************************************************************/
6279
6280
/**
6281
 * \brief Set a projection parameter with a normalized value.
6282
 *
6283
 * This method is the same as SetProjParm() except that the value of
6284
 * the parameter passed in is assumed to be in "normalized" form (decimal
6285
 * degrees for angular values, meters for linear values.  The values are
6286
 * converted in a form suitable for the GEOGCS and linear units in effect.
6287
 *
6288
 * This method is the same as the C function OSRSetNormProjParm().
6289
 *
6290
 * @param pszName the parameter name, which should be selected from
6291
 * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
6292
 *
6293
 * @param dfValue value to assign.
6294
 *
6295
 * @return OGRERR_NONE on success.
6296
 */
6297
6298
OGRErr OGRSpatialReference::SetNormProjParm(const char *pszName, double dfValue)
6299
6300
7.06k
{
6301
7.06k
    TAKE_OPTIONAL_LOCK();
6302
6303
7.06k
    GetNormInfo();
6304
6305
7.06k
    if (d->dfToDegrees != 0.0 &&
6306
7.06k
        (d->dfToDegrees != 1.0 || d->dfFromGreenwich != 0.0) &&
6307
2.75k
        IsAngularParameter(pszName))
6308
1.67k
    {
6309
1.67k
        dfValue /= d->dfToDegrees;
6310
1.67k
    }
6311
5.38k
    else if (d->dfToMeter != 1.0 && d->dfToMeter != 0.0 &&
6312
137
             IsLinearParameter(pszName))
6313
56
        dfValue /= d->dfToMeter;
6314
6315
7.06k
    return SetProjParm(pszName, dfValue);
6316
7.06k
}
6317
6318
/************************************************************************/
6319
/*                         OSRSetNormProjParm()                         */
6320
/************************************************************************/
6321
6322
/**
6323
 * \brief Set a projection parameter with a normalized value.
6324
 *
6325
 * This function is the same as OGRSpatialReference::SetNormProjParm()
6326
 */
6327
OGRErr OSRSetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
6328
                          double dfValue)
6329
6330
0
{
6331
0
    VALIDATE_POINTER1(hSRS, "OSRSetNormProjParm", OGRERR_FAILURE);
6332
6333
0
    return ToPointer(hSRS)->SetNormProjParm(pszParamName, dfValue);
6334
0
}
6335
6336
/************************************************************************/
6337
/*                               SetTM()                                */
6338
/************************************************************************/
6339
6340
OGRErr OGRSpatialReference::SetTM(double dfCenterLat, double dfCenterLong,
6341
                                  double dfScale, double dfFalseEasting,
6342
                                  double dfFalseNorthing)
6343
6344
5.78k
{
6345
5.78k
    TAKE_OPTIONAL_LOCK();
6346
6347
5.78k
    return d->replaceConversionAndUnref(
6348
5.78k
        proj_create_conversion_transverse_mercator(
6349
5.78k
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
6350
5.78k
            dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6351
5.78k
}
6352
6353
/************************************************************************/
6354
/*                              OSRSetTM()                              */
6355
/************************************************************************/
6356
6357
OGRErr OSRSetTM(OGRSpatialReferenceH hSRS, double dfCenterLat,
6358
                double dfCenterLong, double dfScale, double dfFalseEasting,
6359
                double dfFalseNorthing)
6360
6361
0
{
6362
0
    VALIDATE_POINTER1(hSRS, "OSRSetTM", OGRERR_FAILURE);
6363
6364
0
    return ToPointer(hSRS)->SetTM(dfCenterLat, dfCenterLong, dfScale,
6365
0
                                  dfFalseEasting, dfFalseNorthing);
6366
0
}
6367
6368
/************************************************************************/
6369
/*                            SetTMVariant()                            */
6370
/************************************************************************/
6371
6372
OGRErr OGRSpatialReference::SetTMVariant(const char *pszVariantName,
6373
                                         double dfCenterLat,
6374
                                         double dfCenterLong, double dfScale,
6375
                                         double dfFalseEasting,
6376
                                         double dfFalseNorthing)
6377
6378
0
{
6379
0
    TAKE_OPTIONAL_LOCK();
6380
6381
0
    SetProjection(pszVariantName);
6382
0
    SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
6383
0
    SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
6384
0
    SetNormProjParm(SRS_PP_SCALE_FACTOR, dfScale);
6385
0
    SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
6386
0
    SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
6387
6388
0
    return OGRERR_NONE;
6389
0
}
6390
6391
/************************************************************************/
6392
/*                          OSRSetTMVariant()                           */
6393
/************************************************************************/
6394
6395
OGRErr OSRSetTMVariant(OGRSpatialReferenceH hSRS, const char *pszVariantName,
6396
                       double dfCenterLat, double dfCenterLong, double dfScale,
6397
                       double dfFalseEasting, double dfFalseNorthing)
6398
6399
0
{
6400
0
    VALIDATE_POINTER1(hSRS, "OSRSetTMVariant", OGRERR_FAILURE);
6401
6402
0
    return ToPointer(hSRS)->SetTMVariant(pszVariantName, dfCenterLat,
6403
0
                                         dfCenterLong, dfScale, dfFalseEasting,
6404
0
                                         dfFalseNorthing);
6405
0
}
6406
6407
/************************************************************************/
6408
/*                              SetTMSO()                               */
6409
/************************************************************************/
6410
6411
OGRErr OGRSpatialReference::SetTMSO(double dfCenterLat, double dfCenterLong,
6412
                                    double dfScale, double dfFalseEasting,
6413
                                    double dfFalseNorthing)
6414
6415
152
{
6416
152
    TAKE_OPTIONAL_LOCK();
6417
6418
152
    auto conv = proj_create_conversion_transverse_mercator_south_oriented(
6419
152
        d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
6420
152
        dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
6421
6422
152
    const char *pszName = nullptr;
6423
152
    double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
6424
152
    CPLString osName = pszName ? pszName : "";
6425
6426
152
    d->refreshProjObj();
6427
6428
152
    d->demoteFromBoundCRS();
6429
6430
152
    auto cs = proj_create_cartesian_2D_cs(
6431
152
        d->getPROJContext(), PJ_CART2D_WESTING_SOUTHING,
6432
152
        !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
6433
152
    auto projCRS =
6434
152
        proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
6435
152
                                  d->getGeodBaseCRS(), conv, cs);
6436
152
    proj_destroy(conv);
6437
152
    proj_destroy(cs);
6438
6439
152
    d->setPjCRS(projCRS);
6440
6441
152
    d->undoDemoteFromBoundCRS();
6442
6443
152
    return OGRERR_NONE;
6444
152
}
6445
6446
/************************************************************************/
6447
/*                             OSRSetTMSO()                             */
6448
/************************************************************************/
6449
6450
OGRErr OSRSetTMSO(OGRSpatialReferenceH hSRS, double dfCenterLat,
6451
                  double dfCenterLong, double dfScale, double dfFalseEasting,
6452
                  double dfFalseNorthing)
6453
6454
0
{
6455
0
    VALIDATE_POINTER1(hSRS, "OSRSetTMSO", OGRERR_FAILURE);
6456
6457
0
    return ToPointer(hSRS)->SetTMSO(dfCenterLat, dfCenterLong, dfScale,
6458
0
                                    dfFalseEasting, dfFalseNorthing);
6459
0
}
6460
6461
/************************************************************************/
6462
/*                              SetTPED()                               */
6463
/************************************************************************/
6464
6465
OGRErr OGRSpatialReference::SetTPED(double dfLat1, double dfLong1,
6466
                                    double dfLat2, double dfLong2,
6467
                                    double dfFalseEasting,
6468
                                    double dfFalseNorthing)
6469
6470
20
{
6471
20
    TAKE_OPTIONAL_LOCK();
6472
6473
20
    return d->replaceConversionAndUnref(
6474
20
        proj_create_conversion_two_point_equidistant(
6475
20
            d->getPROJContext(), dfLat1, dfLong1, dfLat2, dfLong2,
6476
20
            dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6477
20
}
6478
6479
/************************************************************************/
6480
/*                             OSRSetTPED()                             */
6481
/************************************************************************/
6482
6483
OGRErr OSRSetTPED(OGRSpatialReferenceH hSRS, double dfLat1, double dfLong1,
6484
                  double dfLat2, double dfLong2, double dfFalseEasting,
6485
                  double dfFalseNorthing)
6486
6487
0
{
6488
0
    VALIDATE_POINTER1(hSRS, "OSRSetTPED", OGRERR_FAILURE);
6489
6490
0
    return ToPointer(hSRS)->SetTPED(dfLat1, dfLong1, dfLat2, dfLong2,
6491
0
                                    dfFalseEasting, dfFalseNorthing);
6492
0
}
6493
6494
/************************************************************************/
6495
/*                               SetTMG()                               */
6496
/************************************************************************/
6497
6498
OGRErr OGRSpatialReference::SetTMG(double dfCenterLat, double dfCenterLong,
6499
                                   double dfFalseEasting,
6500
                                   double dfFalseNorthing)
6501
6502
0
{
6503
0
    TAKE_OPTIONAL_LOCK();
6504
6505
0
    return d->replaceConversionAndUnref(
6506
0
        proj_create_conversion_tunisia_mapping_grid(
6507
0
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
6508
0
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6509
0
}
6510
6511
/************************************************************************/
6512
/*                             OSRSetTMG()                              */
6513
/************************************************************************/
6514
6515
OGRErr OSRSetTMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
6516
                 double dfCenterLong, double dfFalseEasting,
6517
                 double dfFalseNorthing)
6518
6519
0
{
6520
0
    VALIDATE_POINTER1(hSRS, "OSRSetTMG", OGRERR_FAILURE);
6521
6522
0
    return ToPointer(hSRS)->SetTMG(dfCenterLat, dfCenterLong, dfFalseEasting,
6523
0
                                   dfFalseNorthing);
6524
0
}
6525
6526
/************************************************************************/
6527
/*                              SetACEA()                               */
6528
/************************************************************************/
6529
6530
OGRErr OGRSpatialReference::SetACEA(double dfStdP1, double dfStdP2,
6531
                                    double dfCenterLat, double dfCenterLong,
6532
                                    double dfFalseEasting,
6533
                                    double dfFalseNorthing)
6534
6535
1.14k
{
6536
1.14k
    TAKE_OPTIONAL_LOCK();
6537
6538
    // Note different order of parameters. The one in PROJ is conformant with
6539
    // EPSG
6540
1.14k
    return d->replaceConversionAndUnref(
6541
1.14k
        proj_create_conversion_albers_equal_area(
6542
1.14k
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
6543
1.14k
            dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6544
1.14k
}
6545
6546
/************************************************************************/
6547
/*                             OSRSetACEA()                             */
6548
/************************************************************************/
6549
6550
OGRErr OSRSetACEA(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
6551
                  double dfCenterLat, double dfCenterLong,
6552
                  double dfFalseEasting, double dfFalseNorthing)
6553
6554
0
{
6555
0
    VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
6556
6557
0
    return ToPointer(hSRS)->SetACEA(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
6558
0
                                    dfFalseEasting, dfFalseNorthing);
6559
0
}
6560
6561
/************************************************************************/
6562
/*                               SetAE()                                */
6563
/************************************************************************/
6564
6565
OGRErr OGRSpatialReference::SetAE(double dfCenterLat, double dfCenterLong,
6566
                                  double dfFalseEasting, double dfFalseNorthing)
6567
6568
1.55k
{
6569
1.55k
    TAKE_OPTIONAL_LOCK();
6570
6571
1.55k
    return d->replaceConversionAndUnref(
6572
1.55k
        proj_create_conversion_azimuthal_equidistant(
6573
1.55k
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
6574
1.55k
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6575
1.55k
}
6576
6577
/************************************************************************/
6578
/*                              OSRSetAE()                              */
6579
/************************************************************************/
6580
6581
OGRErr OSRSetAE(OGRSpatialReferenceH hSRS, double dfCenterLat,
6582
                double dfCenterLong, double dfFalseEasting,
6583
                double dfFalseNorthing)
6584
6585
0
{
6586
0
    VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
6587
6588
0
    return ToPointer(hSRS)->SetAE(dfCenterLat, dfCenterLong, dfFalseEasting,
6589
0
                                  dfFalseNorthing);
6590
0
}
6591
6592
/************************************************************************/
6593
/*                              SetBonne()                              */
6594
/************************************************************************/
6595
6596
OGRErr OGRSpatialReference::SetBonne(double dfStdP1, double dfCentralMeridian,
6597
                                     double dfFalseEasting,
6598
                                     double dfFalseNorthing)
6599
6600
35
{
6601
35
    TAKE_OPTIONAL_LOCK();
6602
6603
35
    return d->replaceConversionAndUnref(proj_create_conversion_bonne(
6604
35
        d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
6605
35
        dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6606
35
}
6607
6608
/************************************************************************/
6609
/*                            OSRSetBonne()                             */
6610
/************************************************************************/
6611
6612
OGRErr OSRSetBonne(OGRSpatialReferenceH hSRS, double dfStdP1,
6613
                   double dfCentralMeridian, double dfFalseEasting,
6614
                   double dfFalseNorthing)
6615
6616
0
{
6617
0
    VALIDATE_POINTER1(hSRS, "OSRSetBonne", OGRERR_FAILURE);
6618
6619
0
    return ToPointer(hSRS)->SetBonne(dfStdP1, dfCentralMeridian, dfFalseEasting,
6620
0
                                     dfFalseNorthing);
6621
0
}
6622
6623
/************************************************************************/
6624
/*                               SetCEA()                               */
6625
/************************************************************************/
6626
6627
OGRErr OGRSpatialReference::SetCEA(double dfStdP1, double dfCentralMeridian,
6628
                                   double dfFalseEasting,
6629
                                   double dfFalseNorthing)
6630
6631
119
{
6632
119
    TAKE_OPTIONAL_LOCK();
6633
6634
119
    return d->replaceConversionAndUnref(
6635
119
        proj_create_conversion_lambert_cylindrical_equal_area(
6636
119
            d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
6637
119
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6638
119
}
6639
6640
/************************************************************************/
6641
/*                             OSRSetCEA()                              */
6642
/************************************************************************/
6643
6644
OGRErr OSRSetCEA(OGRSpatialReferenceH hSRS, double dfStdP1,
6645
                 double dfCentralMeridian, double dfFalseEasting,
6646
                 double dfFalseNorthing)
6647
6648
0
{
6649
0
    VALIDATE_POINTER1(hSRS, "OSRSetCEA", OGRERR_FAILURE);
6650
6651
0
    return ToPointer(hSRS)->SetCEA(dfStdP1, dfCentralMeridian, dfFalseEasting,
6652
0
                                   dfFalseNorthing);
6653
0
}
6654
6655
/************************************************************************/
6656
/*                               SetCS()                                */
6657
/************************************************************************/
6658
6659
OGRErr OGRSpatialReference::SetCS(double dfCenterLat, double dfCenterLong,
6660
                                  double dfFalseEasting, double dfFalseNorthing)
6661
6662
103
{
6663
103
    TAKE_OPTIONAL_LOCK();
6664
6665
103
    return d->replaceConversionAndUnref(proj_create_conversion_cassini_soldner(
6666
103
        d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
6667
103
        dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6668
103
}
6669
6670
/************************************************************************/
6671
/*                              OSRSetCS()                              */
6672
/************************************************************************/
6673
6674
OGRErr OSRSetCS(OGRSpatialReferenceH hSRS, double dfCenterLat,
6675
                double dfCenterLong, double dfFalseEasting,
6676
                double dfFalseNorthing)
6677
6678
0
{
6679
0
    VALIDATE_POINTER1(hSRS, "OSRSetCS", OGRERR_FAILURE);
6680
6681
0
    return ToPointer(hSRS)->SetCS(dfCenterLat, dfCenterLong, dfFalseEasting,
6682
0
                                  dfFalseNorthing);
6683
0
}
6684
6685
/************************************************************************/
6686
/*                               SetEC()                                */
6687
/************************************************************************/
6688
6689
OGRErr OGRSpatialReference::SetEC(double dfStdP1, double dfStdP2,
6690
                                  double dfCenterLat, double dfCenterLong,
6691
                                  double dfFalseEasting, double dfFalseNorthing)
6692
6693
5.86k
{
6694
5.86k
    TAKE_OPTIONAL_LOCK();
6695
6696
    // Note: different order of arguments
6697
5.86k
    return d->replaceConversionAndUnref(
6698
5.86k
        proj_create_conversion_equidistant_conic(
6699
5.86k
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
6700
5.86k
            dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6701
5.86k
}
6702
6703
/************************************************************************/
6704
/*                              OSRSetEC()                              */
6705
/************************************************************************/
6706
6707
OGRErr OSRSetEC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
6708
                double dfCenterLat, double dfCenterLong, double dfFalseEasting,
6709
                double dfFalseNorthing)
6710
6711
0
{
6712
0
    VALIDATE_POINTER1(hSRS, "OSRSetEC", OGRERR_FAILURE);
6713
6714
0
    return ToPointer(hSRS)->SetEC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
6715
0
                                  dfFalseEasting, dfFalseNorthing);
6716
0
}
6717
6718
/************************************************************************/
6719
/*                             SetEckert()                              */
6720
/************************************************************************/
6721
6722
OGRErr OGRSpatialReference::SetEckert(int nVariation,  // 1-6.
6723
                                      double dfCentralMeridian,
6724
                                      double dfFalseEasting,
6725
                                      double dfFalseNorthing)
6726
6727
39
{
6728
39
    TAKE_OPTIONAL_LOCK();
6729
6730
39
    PJ *conv;
6731
39
    if (nVariation == 1)
6732
3
    {
6733
3
        conv = proj_create_conversion_eckert_i(
6734
3
            d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
6735
3
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
6736
3
    }
6737
36
    else if (nVariation == 2)
6738
10
    {
6739
10
        conv = proj_create_conversion_eckert_ii(
6740
10
            d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
6741
10
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
6742
10
    }
6743
26
    else if (nVariation == 3)
6744
4
    {
6745
4
        conv = proj_create_conversion_eckert_iii(
6746
4
            d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
6747
4
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
6748
4
    }
6749
22
    else if (nVariation == 4)
6750
11
    {
6751
11
        conv = proj_create_conversion_eckert_iv(
6752
11
            d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
6753
11
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
6754
11
    }
6755
11
    else if (nVariation == 5)
6756
7
    {
6757
7
        conv = proj_create_conversion_eckert_v(
6758
7
            d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
6759
7
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
6760
7
    }
6761
4
    else if (nVariation == 6)
6762
4
    {
6763
4
        conv = proj_create_conversion_eckert_vi(
6764
4
            d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
6765
4
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
6766
4
    }
6767
0
    else
6768
0
    {
6769
0
        CPLError(CE_Failure, CPLE_AppDefined,
6770
0
                 "Unsupported Eckert variation (%d).", nVariation);
6771
0
        return OGRERR_UNSUPPORTED_SRS;
6772
0
    }
6773
6774
39
    return d->replaceConversionAndUnref(conv);
6775
39
}
6776
6777
/************************************************************************/
6778
/*                            OSRSetEckert()                            */
6779
/************************************************************************/
6780
6781
OGRErr OSRSetEckert(OGRSpatialReferenceH hSRS, int nVariation,
6782
                    double dfCentralMeridian, double dfFalseEasting,
6783
                    double dfFalseNorthing)
6784
6785
0
{
6786
0
    VALIDATE_POINTER1(hSRS, "OSRSetEckert", OGRERR_FAILURE);
6787
6788
0
    return ToPointer(hSRS)->SetEckert(nVariation, dfCentralMeridian,
6789
0
                                      dfFalseEasting, dfFalseNorthing);
6790
0
}
6791
6792
/************************************************************************/
6793
/*                            SetEckertIV()                             */
6794
/*                                                                      */
6795
/*      Deprecated                                                      */
6796
/************************************************************************/
6797
6798
OGRErr OGRSpatialReference::SetEckertIV(double dfCentralMeridian,
6799
                                        double dfFalseEasting,
6800
                                        double dfFalseNorthing)
6801
6802
0
{
6803
0
    return SetEckert(4, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
6804
0
}
6805
6806
/************************************************************************/
6807
/*                           OSRSetEckertIV()                           */
6808
/************************************************************************/
6809
6810
OGRErr OSRSetEckertIV(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
6811
                      double dfFalseEasting, double dfFalseNorthing)
6812
6813
0
{
6814
0
    VALIDATE_POINTER1(hSRS, "OSRSetEckertIV", OGRERR_FAILURE);
6815
6816
0
    return ToPointer(hSRS)->SetEckertIV(dfCentralMeridian, dfFalseEasting,
6817
0
                                        dfFalseNorthing);
6818
0
}
6819
6820
/************************************************************************/
6821
/*                            SetEckertVI()                             */
6822
/*                                                                      */
6823
/*      Deprecated                                                      */
6824
/************************************************************************/
6825
6826
OGRErr OGRSpatialReference::SetEckertVI(double dfCentralMeridian,
6827
                                        double dfFalseEasting,
6828
                                        double dfFalseNorthing)
6829
6830
1
{
6831
1
    return SetEckert(6, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
6832
1
}
6833
6834
/************************************************************************/
6835
/*                           OSRSetEckertVI()                           */
6836
/************************************************************************/
6837
6838
OGRErr OSRSetEckertVI(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
6839
                      double dfFalseEasting, double dfFalseNorthing)
6840
6841
0
{
6842
0
    VALIDATE_POINTER1(hSRS, "OSRSetEckertVI", OGRERR_FAILURE);
6843
6844
0
    return ToPointer(hSRS)->SetEckertVI(dfCentralMeridian, dfFalseEasting,
6845
0
                                        dfFalseNorthing);
6846
0
}
6847
6848
/************************************************************************/
6849
/*                         SetEquirectangular()                         */
6850
/************************************************************************/
6851
6852
OGRErr OGRSpatialReference::SetEquirectangular(double dfCenterLat,
6853
                                               double dfCenterLong,
6854
                                               double dfFalseEasting,
6855
                                               double dfFalseNorthing)
6856
6857
13
{
6858
13
    TAKE_OPTIONAL_LOCK();
6859
6860
13
    if (dfCenterLat == 0.0)
6861
1
    {
6862
1
        return d->replaceConversionAndUnref(
6863
1
            proj_create_conversion_equidistant_cylindrical(
6864
1
                d->getPROJContext(), 0.0, dfCenterLong, dfFalseEasting,
6865
1
                dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6866
1
    }
6867
6868
    // Non-standard extension with non-zero latitude of origin
6869
12
    SetProjection(SRS_PT_EQUIRECTANGULAR);
6870
12
    SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
6871
12
    SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
6872
12
    SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
6873
12
    SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
6874
6875
12
    return OGRERR_NONE;
6876
13
}
6877
6878
/************************************************************************/
6879
/*                       OSRSetEquirectangular()                        */
6880
/************************************************************************/
6881
6882
OGRErr OSRSetEquirectangular(OGRSpatialReferenceH hSRS, double dfCenterLat,
6883
                             double dfCenterLong, double dfFalseEasting,
6884
                             double dfFalseNorthing)
6885
6886
0
{
6887
0
    VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular", OGRERR_FAILURE);
6888
6889
0
    return ToPointer(hSRS)->SetEquirectangular(dfCenterLat, dfCenterLong,
6890
0
                                               dfFalseEasting, dfFalseNorthing);
6891
0
}
6892
6893
/************************************************************************/
6894
/*                         SetEquirectangular2()                        */
6895
/* Generalized form                                                     */
6896
/************************************************************************/
6897
6898
OGRErr OGRSpatialReference::SetEquirectangular2(double dfCenterLat,
6899
                                                double dfCenterLong,
6900
                                                double dfStdParallel1,
6901
                                                double dfFalseEasting,
6902
                                                double dfFalseNorthing)
6903
6904
787
{
6905
787
    TAKE_OPTIONAL_LOCK();
6906
6907
787
    if (dfCenterLat == 0.0)
6908
328
    {
6909
328
        return d->replaceConversionAndUnref(
6910
328
            proj_create_conversion_equidistant_cylindrical(
6911
328
                d->getPROJContext(), dfStdParallel1, dfCenterLong,
6912
328
                dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
6913
328
    }
6914
6915
    // Non-standard extension with non-zero latitude of origin
6916
459
    SetProjection(SRS_PT_EQUIRECTANGULAR);
6917
459
    SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
6918
459
    SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
6919
459
    SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdParallel1);
6920
459
    SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
6921
459
    SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
6922
6923
459
    return OGRERR_NONE;
6924
787
}
6925
6926
/************************************************************************/
6927
/*                       OSRSetEquirectangular2()                       */
6928
/************************************************************************/
6929
6930
OGRErr OSRSetEquirectangular2(OGRSpatialReferenceH hSRS, double dfCenterLat,
6931
                              double dfCenterLong, double dfStdParallel1,
6932
                              double dfFalseEasting, double dfFalseNorthing)
6933
6934
0
{
6935
0
    VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular2", OGRERR_FAILURE);
6936
6937
0
    return ToPointer(hSRS)->SetEquirectangular2(dfCenterLat, dfCenterLong,
6938
0
                                                dfStdParallel1, dfFalseEasting,
6939
0
                                                dfFalseNorthing);
6940
0
}
6941
6942
/************************************************************************/
6943
/*                               SetGS()                                */
6944
/************************************************************************/
6945
6946
OGRErr OGRSpatialReference::SetGS(double dfCentralMeridian,
6947
                                  double dfFalseEasting, double dfFalseNorthing)
6948
6949
3
{
6950
3
    return d->replaceConversionAndUnref(proj_create_conversion_gall(
6951
3
        d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
6952
3
        nullptr, 0.0, nullptr, 0.0));
6953
3
}
6954
6955
/************************************************************************/
6956
/*                              OSRSetGS()                              */
6957
/************************************************************************/
6958
6959
OGRErr OSRSetGS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
6960
                double dfFalseEasting, double dfFalseNorthing)
6961
6962
0
{
6963
0
    VALIDATE_POINTER1(hSRS, "OSRSetGS", OGRERR_FAILURE);
6964
6965
0
    return ToPointer(hSRS)->SetGS(dfCentralMeridian, dfFalseEasting,
6966
0
                                  dfFalseNorthing);
6967
0
}
6968
6969
/************************************************************************/
6970
/*                               SetGH()                                */
6971
/************************************************************************/
6972
6973
OGRErr OGRSpatialReference::SetGH(double dfCentralMeridian,
6974
                                  double dfFalseEasting, double dfFalseNorthing)
6975
6976
0
{
6977
0
    TAKE_OPTIONAL_LOCK();
6978
6979
0
    return d->replaceConversionAndUnref(proj_create_conversion_goode_homolosine(
6980
0
        d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
6981
0
        nullptr, 0.0, nullptr, 0.0));
6982
0
}
6983
6984
/************************************************************************/
6985
/*                              OSRSetGH()                              */
6986
/************************************************************************/
6987
6988
OGRErr OSRSetGH(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
6989
                double dfFalseEasting, double dfFalseNorthing)
6990
6991
0
{
6992
0
    VALIDATE_POINTER1(hSRS, "OSRSetGH", OGRERR_FAILURE);
6993
6994
0
    return ToPointer(hSRS)->SetGH(dfCentralMeridian, dfFalseEasting,
6995
0
                                  dfFalseNorthing);
6996
0
}
6997
6998
/************************************************************************/
6999
/*                               SetIGH()                               */
7000
/************************************************************************/
7001
7002
OGRErr OGRSpatialReference::SetIGH()
7003
7004
0
{
7005
0
    TAKE_OPTIONAL_LOCK();
7006
7007
0
    return d->replaceConversionAndUnref(
7008
0
        proj_create_conversion_interrupted_goode_homolosine(
7009
0
            d->getPROJContext(), 0.0, 0.0, 0.0, nullptr, 0.0, nullptr, 0.0));
7010
0
}
7011
7012
/************************************************************************/
7013
/*                             OSRSetIGH()                              */
7014
/************************************************************************/
7015
7016
OGRErr OSRSetIGH(OGRSpatialReferenceH hSRS)
7017
7018
0
{
7019
0
    VALIDATE_POINTER1(hSRS, "OSRSetIGH", OGRERR_FAILURE);
7020
7021
0
    return ToPointer(hSRS)->SetIGH();
7022
0
}
7023
7024
/************************************************************************/
7025
/*                              SetGEOS()                               */
7026
/************************************************************************/
7027
7028
OGRErr OGRSpatialReference::SetGEOS(double dfCentralMeridian,
7029
                                    double dfSatelliteHeight,
7030
                                    double dfFalseEasting,
7031
                                    double dfFalseNorthing)
7032
7033
0
{
7034
0
    TAKE_OPTIONAL_LOCK();
7035
7036
0
    return d->replaceConversionAndUnref(
7037
0
        proj_create_conversion_geostationary_satellite_sweep_y(
7038
0
            d->getPROJContext(), dfCentralMeridian, dfSatelliteHeight,
7039
0
            dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
7040
0
}
7041
7042
/************************************************************************/
7043
/*                             OSRSetGEOS()                             */
7044
/************************************************************************/
7045
7046
OGRErr OSRSetGEOS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
7047
                  double dfSatelliteHeight, double dfFalseEasting,
7048
                  double dfFalseNorthing)
7049
7050
0
{
7051
0
    VALIDATE_POINTER1(hSRS, "OSRSetGEOS", OGRERR_FAILURE);
7052
7053
0
    return ToPointer(hSRS)->SetGEOS(dfCentralMeridian, dfSatelliteHeight,
7054
0
                                    dfFalseEasting, dfFalseNorthing);
7055
0
}
7056
7057
/************************************************************************/
7058
/*                     SetGaussSchreiberTMercator()                     */
7059
/************************************************************************/
7060
7061
OGRErr OGRSpatialReference::SetGaussSchreiberTMercator(double dfCenterLat,
7062
                                                       double dfCenterLong,
7063
                                                       double dfScale,
7064
                                                       double dfFalseEasting,
7065
                                                       double dfFalseNorthing)
7066
7067
0
{
7068
0
    TAKE_OPTIONAL_LOCK();
7069
7070
0
    return d->replaceConversionAndUnref(
7071
0
        proj_create_conversion_gauss_schreiber_transverse_mercator(
7072
0
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
7073
0
            dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
7074
0
}
7075
7076
/************************************************************************/
7077
/*                   OSRSetGaussSchreiberTMercator()                    */
7078
/************************************************************************/
7079
7080
OGRErr OSRSetGaussSchreiberTMercator(OGRSpatialReferenceH hSRS,
7081
                                     double dfCenterLat, double dfCenterLong,
7082
                                     double dfScale, double dfFalseEasting,
7083
                                     double dfFalseNorthing)
7084
7085
0
{
7086
0
    VALIDATE_POINTER1(hSRS, "OSRSetGaussSchreiberTMercator", OGRERR_FAILURE);
7087
7088
0
    return ToPointer(hSRS)->SetGaussSchreiberTMercator(
7089
0
        dfCenterLat, dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing);
7090
0
}
7091
7092
/************************************************************************/
7093
/*                            SetGnomonic()                             */
7094
/************************************************************************/
7095
7096
OGRErr OGRSpatialReference::SetGnomonic(double dfCenterLat, double dfCenterLong,
7097
                                        double dfFalseEasting,
7098
                                        double dfFalseNorthing)
7099
7100
322
{
7101
322
    TAKE_OPTIONAL_LOCK();
7102
7103
322
    return d->replaceConversionAndUnref(proj_create_conversion_gnomonic(
7104
322
        d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
7105
322
        dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
7106
322
}
7107
7108
/************************************************************************/
7109
/*                           OSRSetGnomonic()                           */
7110
/************************************************************************/
7111
7112
OGRErr OSRSetGnomonic(OGRSpatialReferenceH hSRS, double dfCenterLat,
7113
                      double dfCenterLong, double dfFalseEasting,
7114
                      double dfFalseNorthing)
7115
7116
0
{
7117
0
    VALIDATE_POINTER1(hSRS, "OSRSetGnomonic", OGRERR_FAILURE);
7118
7119
0
    return ToPointer(hSRS)->SetGnomonic(dfCenterLat, dfCenterLong,
7120
0
                                        dfFalseEasting, dfFalseNorthing);
7121
0
}
7122
7123
/************************************************************************/
7124
/*                              SetHOMAC()                              */
7125
/************************************************************************/
7126
7127
/**
7128
 * \brief Set an Hotine Oblique Mercator Azimuth Center projection using
7129
 * azimuth angle.
7130
 *
7131
 * This projection corresponds to EPSG projection method 9815, also
7132
 * sometimes known as hotine oblique mercator (variant B).
7133
 *
7134
 * This method does the same thing as the C function OSRSetHOMAC().
7135
 *
7136
 * @param dfCenterLat Latitude of the projection origin.
7137
 * @param dfCenterLong Longitude of the projection origin.
7138
 * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
7139
 * centerline.
7140
 * @param dfRectToSkew Angle from Rectified to Skew Grid
7141
 * @param dfScale Scale factor applies to the projection origin.
7142
 * @param dfFalseEasting False easting.
7143
 * @param dfFalseNorthing False northing.
7144
 *
7145
 * @return OGRERR_NONE on success.
7146
 */
7147
7148
OGRErr OGRSpatialReference::SetHOMAC(double dfCenterLat, double dfCenterLong,
7149
                                     double dfAzimuth, double dfRectToSkew,
7150
                                     double dfScale, double dfFalseEasting,
7151
                                     double dfFalseNorthing)
7152
7153
13
{
7154
13
    TAKE_OPTIONAL_LOCK();
7155
7156
13
    return d->replaceConversionAndUnref(
7157
13
        proj_create_conversion_hotine_oblique_mercator_variant_b(
7158
13
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
7159
13
            dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
7160
13
            0.0, nullptr, 0.0));
7161
13
}
7162
7163
/************************************************************************/
7164
/*                            OSRSetHOMAC()                             */
7165
/************************************************************************/
7166
7167
/**
7168
 * \brief Set an Oblique Mercator projection using azimuth angle.
7169
 *
7170
 * This is the same as the C++ method OGRSpatialReference::SetHOMAC()
7171
 */
7172
OGRErr OSRSetHOMAC(OGRSpatialReferenceH hSRS, double dfCenterLat,
7173
                   double dfCenterLong, double dfAzimuth, double dfRectToSkew,
7174
                   double dfScale, double dfFalseEasting,
7175
                   double dfFalseNorthing)
7176
7177
0
{
7178
0
    VALIDATE_POINTER1(hSRS, "OSRSetHOMAC", OGRERR_FAILURE);
7179
7180
0
    return ToPointer(hSRS)->SetHOMAC(dfCenterLat, dfCenterLong, dfAzimuth,
7181
0
                                     dfRectToSkew, dfScale, dfFalseEasting,
7182
0
                                     dfFalseNorthing);
7183
0
}
7184
7185
/************************************************************************/
7186
/*                               SetHOM()                               */
7187
/************************************************************************/
7188
7189
/**
7190
 * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
7191
 *
7192
 * This projection corresponds to EPSG projection method 9812, also
7193
 * sometimes known as hotine oblique mercator (variant A)..
7194
 *
7195
 * This method does the same thing as the C function OSRSetHOM().
7196
 *
7197
 * @param dfCenterLat Latitude of the projection origin.
7198
 * @param dfCenterLong Longitude of the projection origin.
7199
 * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
7200
 * centerline.
7201
 * @param dfRectToSkew Angle from Rectified to Skew Grid
7202
 * @param dfScale Scale factor applies to the projection origin.
7203
 * @param dfFalseEasting False easting.
7204
 * @param dfFalseNorthing False northing.
7205
 *
7206
 * @return OGRERR_NONE on success.
7207
 */
7208
7209
OGRErr OGRSpatialReference::SetHOM(double dfCenterLat, double dfCenterLong,
7210
                                   double dfAzimuth, double dfRectToSkew,
7211
                                   double dfScale, double dfFalseEasting,
7212
                                   double dfFalseNorthing)
7213
7214
1.30k
{
7215
1.30k
    TAKE_OPTIONAL_LOCK();
7216
7217
1.30k
    return d->replaceConversionAndUnref(
7218
1.30k
        proj_create_conversion_hotine_oblique_mercator_variant_a(
7219
1.30k
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
7220
1.30k
            dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
7221
1.30k
            0.0, nullptr, 0.0));
7222
1.30k
}
7223
7224
/************************************************************************/
7225
/*                             OSRSetHOM()                              */
7226
/************************************************************************/
7227
/**
7228
 * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
7229
 *
7230
 * This is the same as the C++ method OGRSpatialReference::SetHOM()
7231
 */
7232
OGRErr OSRSetHOM(OGRSpatialReferenceH hSRS, double dfCenterLat,
7233
                 double dfCenterLong, double dfAzimuth, double dfRectToSkew,
7234
                 double dfScale, double dfFalseEasting, double dfFalseNorthing)
7235
7236
0
{
7237
0
    VALIDATE_POINTER1(hSRS, "OSRSetHOM", OGRERR_FAILURE);
7238
7239
0
    return ToPointer(hSRS)->SetHOM(dfCenterLat, dfCenterLong, dfAzimuth,
7240
0
                                   dfRectToSkew, dfScale, dfFalseEasting,
7241
0
                                   dfFalseNorthing);
7242
0
}
7243
7244
/************************************************************************/
7245
/*                             SetHOM2PNO()                             */
7246
/************************************************************************/
7247
7248
/**
7249
 * \brief Set a Hotine Oblique Mercator projection using two points on
7250
 * projection centerline.
7251
 *
7252
 * This method does the same thing as the C function OSRSetHOM2PNO().
7253
 *
7254
 * @param dfCenterLat Latitude of the projection origin.
7255
 * @param dfLat1 Latitude of the first point on center line.
7256
 * @param dfLong1 Longitude of the first point on center line.
7257
 * @param dfLat2 Latitude of the second point on center line.
7258
 * @param dfLong2 Longitude of the second point on center line.
7259
 * @param dfScale Scale factor applies to the projection origin.
7260
 * @param dfFalseEasting False easting.
7261
 * @param dfFalseNorthing False northing.
7262
 *
7263
 * @return OGRERR_NONE on success.
7264
 */
7265
7266
OGRErr OGRSpatialReference::SetHOM2PNO(double dfCenterLat, double dfLat1,
7267
                                       double dfLong1, double dfLat2,
7268
                                       double dfLong2, double dfScale,
7269
                                       double dfFalseEasting,
7270
                                       double dfFalseNorthing)
7271
7272
973
{
7273
973
    TAKE_OPTIONAL_LOCK();
7274
7275
973
    return d->replaceConversionAndUnref(
7276
973
        proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin(
7277
973
            d->getPROJContext(), dfCenterLat, dfLat1, dfLong1, dfLat2, dfLong2,
7278
973
            dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
7279
973
            0.0));
7280
973
}
7281
7282
/************************************************************************/
7283
/*                           OSRSetHOM2PNO()                            */
7284
/************************************************************************/
7285
/**
7286
 * \brief Set a Hotine Oblique Mercator projection using two points on
7287
 *  projection centerline.
7288
 *
7289
 * This is the same as the C++ method OGRSpatialReference::SetHOM2PNO()
7290
 */
7291
OGRErr OSRSetHOM2PNO(OGRSpatialReferenceH hSRS, double dfCenterLat,
7292
                     double dfLat1, double dfLong1, double dfLat2,
7293
                     double dfLong2, double dfScale, double dfFalseEasting,
7294
                     double dfFalseNorthing)
7295
7296
0
{
7297
0
    VALIDATE_POINTER1(hSRS, "OSRSetHOM2PNO", OGRERR_FAILURE);
7298
7299
0
    return ToPointer(hSRS)->SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
7300
0
                                       dfLong2, dfScale, dfFalseEasting,
7301
0
                                       dfFalseNorthing);
7302
0
}
7303
7304
/************************************************************************/
7305
/*                               SetLOM()                               */
7306
/************************************************************************/
7307
7308
/**
7309
 * \brief Set a Laborde Oblique Mercator projection.
7310
 *
7311
 * @param dfCenterLat Latitude of the projection origin.
7312
 * @param dfCenterLong Longitude of the projection origin.
7313
 * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
7314
 * centerline.
7315
 * @param dfScale Scale factor on the initial line
7316
 * @param dfFalseEasting False easting.
7317
 * @param dfFalseNorthing False northing.
7318
 *
7319
 * @return OGRERR_NONE on success.
7320
 */
7321
7322
OGRErr OGRSpatialReference::SetLOM(double dfCenterLat, double dfCenterLong,
7323
                                   double dfAzimuth, double dfScale,
7324
                                   double dfFalseEasting,
7325
                                   double dfFalseNorthing)
7326
7327
413
{
7328
413
    TAKE_OPTIONAL_LOCK();
7329
7330
413
    return d->replaceConversionAndUnref(
7331
413
        proj_create_conversion_laborde_oblique_mercator(
7332
413
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth, dfScale,
7333
413
            dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
7334
413
}
7335
7336
/************************************************************************/
7337
/*                          SetIWMPolyconic()                           */
7338
/************************************************************************/
7339
7340
OGRErr OGRSpatialReference::SetIWMPolyconic(double dfLat1, double dfLat2,
7341
                                            double dfCenterLong,
7342
                                            double dfFalseEasting,
7343
                                            double dfFalseNorthing)
7344
7345
0
{
7346
0
    TAKE_OPTIONAL_LOCK();
7347
7348
0
    return d->replaceConversionAndUnref(
7349
0
        proj_create_conversion_international_map_world_polyconic(
7350
0
            d->getPROJContext(), dfCenterLong, dfLat1, dfLat2, dfFalseEasting,
7351
0
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
7352
0
}
7353
7354
/************************************************************************/
7355
/*                         OSRSetIWMPolyconic()                         */
7356
/************************************************************************/
7357
7358
OGRErr OSRSetIWMPolyconic(OGRSpatialReferenceH hSRS, double dfLat1,
7359
                          double dfLat2, double dfCenterLong,
7360
                          double dfFalseEasting, double dfFalseNorthing)
7361
7362
0
{
7363
0
    VALIDATE_POINTER1(hSRS, "OSRSetIWMPolyconic", OGRERR_FAILURE);
7364
7365
0
    return ToPointer(hSRS)->SetIWMPolyconic(dfLat1, dfLat2, dfCenterLong,
7366
0
                                            dfFalseEasting, dfFalseNorthing);
7367
0
}
7368
7369
/************************************************************************/
7370
/*                             SetKrovak()                              */
7371
/************************************************************************/
7372
7373
/** Krovak east-north projection.
7374
 *
7375
 * Note that dfAzimuth and dfPseudoStdParallel1 are ignored when exporting
7376
 * to PROJ and should be respectively set to 30.28813972222222 and 78.5
7377
 */
7378
OGRErr OGRSpatialReference::SetKrovak(double dfCenterLat, double dfCenterLong,
7379
                                      double dfAzimuth,
7380
                                      double dfPseudoStdParallel1,
7381
                                      double dfScale, double dfFalseEasting,
7382
                                      double dfFalseNorthing)
7383
7384
75
{
7385
75
    TAKE_OPTIONAL_LOCK();
7386
7387
75
    return d->replaceConversionAndUnref(
7388
75
        proj_create_conversion_krovak_north_oriented(
7389
75
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
7390
75
            dfPseudoStdParallel1, dfScale, dfFalseEasting, dfFalseNorthing,
7391
75
            nullptr, 0.0, nullptr, 0.0));
7392
75
}
7393
7394
/************************************************************************/
7395
/*                            OSRSetKrovak()                            */
7396
/************************************************************************/
7397
7398
OGRErr OSRSetKrovak(OGRSpatialReferenceH hSRS, double dfCenterLat,
7399
                    double dfCenterLong, double dfAzimuth,
7400
                    double dfPseudoStdParallel1, double dfScale,
7401
                    double dfFalseEasting, double dfFalseNorthing)
7402
7403
0
{
7404
0
    VALIDATE_POINTER1(hSRS, "OSRSetKrovak", OGRERR_FAILURE);
7405
7406
0
    return ToPointer(hSRS)->SetKrovak(dfCenterLat, dfCenterLong, dfAzimuth,
7407
0
                                      dfPseudoStdParallel1, dfScale,
7408
0
                                      dfFalseEasting, dfFalseNorthing);
7409
0
}
7410
7411
/************************************************************************/
7412
/*                              SetLAEA()                               */
7413
/************************************************************************/
7414
7415
OGRErr OGRSpatialReference::SetLAEA(double dfCenterLat, double dfCenterLong,
7416
                                    double dfFalseEasting,
7417
                                    double dfFalseNorthing)
7418
7419
2.10k
{
7420
2.10k
    TAKE_OPTIONAL_LOCK();
7421
7422
2.10k
    auto conv = proj_create_conversion_lambert_azimuthal_equal_area(
7423
2.10k
        d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
7424
2.10k
        dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
7425
7426
2.10k
    const char *pszName = nullptr;
7427
2.10k
    double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
7428
2.10k
    CPLString osName = pszName ? pszName : "";
7429
7430
2.10k
    d->refreshProjObj();
7431
7432
2.10k
    d->demoteFromBoundCRS();
7433
7434
2.10k
    auto cs = proj_create_cartesian_2D_cs(
7435
2.10k
        d->getPROJContext(),
7436
2.10k
        std::fabs(dfCenterLat - 90) < 1e-10 && dfCenterLong == 0
7437
2.10k
            ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
7438
2.10k
        : std::fabs(dfCenterLat - -90) < 1e-10 && dfCenterLong == 0
7439
2.05k
            ? PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH
7440
2.05k
            : PJ_CART2D_EASTING_NORTHING,
7441
2.10k
        !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
7442
2.10k
    auto projCRS =
7443
2.10k
        proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
7444
2.10k
                                  d->getGeodBaseCRS(), conv, cs);
7445
2.10k
    proj_destroy(conv);
7446
2.10k
    proj_destroy(cs);
7447
7448
2.10k
    d->setPjCRS(projCRS);
7449
7450
2.10k
    d->undoDemoteFromBoundCRS();
7451
7452
2.10k
    return OGRERR_NONE;
7453
2.10k
}
7454
7455
/************************************************************************/
7456
/*                             OSRSetLAEA()                             */
7457
/************************************************************************/
7458
7459
OGRErr OSRSetLAEA(OGRSpatialReferenceH hSRS, double dfCenterLat,
7460
                  double dfCenterLong, double dfFalseEasting,
7461
                  double dfFalseNorthing)
7462
7463
0
{
7464
0
    VALIDATE_POINTER1(hSRS, "OSRSetLAEA", OGRERR_FAILURE);
7465
7466
0
    return ToPointer(hSRS)->SetLAEA(dfCenterLat, dfCenterLong, dfFalseEasting,
7467
0
                                    dfFalseNorthing);
7468
0
}
7469
7470
/************************************************************************/
7471
/*                               SetLCC()                               */
7472
/************************************************************************/
7473
7474
OGRErr OGRSpatialReference::SetLCC(double dfStdP1, double dfStdP2,
7475
                                   double dfCenterLat, double dfCenterLong,
7476
                                   double dfFalseEasting,
7477
                                   double dfFalseNorthing)
7478
7479
15.3k
{
7480
15.3k
    TAKE_OPTIONAL_LOCK();
7481
7482
15.3k
    return d->replaceConversionAndUnref(
7483
15.3k
        proj_create_conversion_lambert_conic_conformal_2sp(
7484
15.3k
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
7485
15.3k
            dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
7486
15.3k
}
7487
7488
/************************************************************************/
7489
/*                             OSRSetLCC()                              */
7490
/************************************************************************/
7491
7492
OGRErr OSRSetLCC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
7493
                 double dfCenterLat, double dfCenterLong, double dfFalseEasting,
7494
                 double dfFalseNorthing)
7495
7496
0
{
7497
0
    VALIDATE_POINTER1(hSRS, "OSRSetLCC", OGRERR_FAILURE);
7498
7499
0
    return ToPointer(hSRS)->SetLCC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
7500
0
                                   dfFalseEasting, dfFalseNorthing);
7501
0
}
7502
7503
/************************************************************************/
7504
/*                             SetLCC1SP()                              */
7505
/************************************************************************/
7506
7507
OGRErr OGRSpatialReference::SetLCC1SP(double dfCenterLat, double dfCenterLong,
7508
                                      double dfScale, double dfFalseEasting,
7509
                                      double dfFalseNorthing)
7510
7511
443
{
7512
443
    TAKE_OPTIONAL_LOCK();
7513
7514
443
    return d->replaceConversionAndUnref(
7515
443
        proj_create_conversion_lambert_conic_conformal_1sp(
7516
443
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
7517
443
            dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
7518
443
}
7519
7520
/************************************************************************/
7521
/*                            OSRSetLCC1SP()                            */
7522
/************************************************************************/
7523
7524
OGRErr OSRSetLCC1SP(OGRSpatialReferenceH hSRS, double dfCenterLat,
7525
                    double dfCenterLong, double dfScale, double dfFalseEasting,
7526
                    double dfFalseNorthing)
7527
7528
0
{
7529
0
    VALIDATE_POINTER1(hSRS, "OSRSetLCC1SP", OGRERR_FAILURE);
7530
7531
0
    return ToPointer(hSRS)->SetLCC1SP(dfCenterLat, dfCenterLong, dfScale,
7532
0
                                      dfFalseEasting, dfFalseNorthing);
7533
0
}
7534
7535
/************************************************************************/
7536
/*                              SetLCCB()                               */
7537
/************************************************************************/
7538
7539
OGRErr OGRSpatialReference::SetLCCB(double dfStdP1, double dfStdP2,
7540
                                    double dfCenterLat, double dfCenterLong,
7541
                                    double dfFalseEasting,
7542
                                    double dfFalseNorthing)
7543
7544
0
{
7545
0
    TAKE_OPTIONAL_LOCK();
7546
7547
0
    return d->replaceConversionAndUnref(
7548
0
        proj_create_conversion_lambert_conic_conformal_2sp_belgium(
7549
0
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
7550
0
            dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
7551
0
}
7552
7553
/************************************************************************/
7554
/*                             OSRSetLCCB()                             */
7555
/************************************************************************/
7556
7557
OGRErr OSRSetLCCB(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
7558
                  double dfCenterLat, double dfCenterLong,
7559
                  double dfFalseEasting, double dfFalseNorthing)
7560
7561
0
{
7562
0
    VALIDATE_POINTER1(hSRS, "OSRSetLCCB", OGRERR_FAILURE);
7563
7564
0
    return ToPointer(hSRS)->SetLCCB(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
7565
0
                                    dfFalseEasting, dfFalseNorthing);
7566
0
}
7567
7568
/************************************************************************/
7569
/*                               SetMC()                                */
7570
/************************************************************************/
7571
7572
OGRErr OGRSpatialReference::SetMC(double dfCenterLat, double dfCenterLong,
7573
                                  double dfFalseEasting, double dfFalseNorthing)
7574
7575
30
{
7576
30
    TAKE_OPTIONAL_LOCK();
7577
7578
30
    (void)dfCenterLat;  // ignored
7579
7580
30
    return d->replaceConversionAndUnref(
7581
30
        proj_create_conversion_miller_cylindrical(
7582
30
            d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
7583
30
            nullptr, 0, nullptr, 0));
7584
30
}
7585
7586
/************************************************************************/
7587
/*                              OSRSetMC()                              */
7588
/************************************************************************/
7589
7590
OGRErr OSRSetMC(OGRSpatialReferenceH hSRS, double dfCenterLat,
7591
                double dfCenterLong, double dfFalseEasting,
7592
                double dfFalseNorthing)
7593
7594
0
{
7595
0
    VALIDATE_POINTER1(hSRS, "OSRSetMC", OGRERR_FAILURE);
7596
7597
0
    return ToPointer(hSRS)->SetMC(dfCenterLat, dfCenterLong, dfFalseEasting,
7598
0
                                  dfFalseNorthing);
7599
0
}
7600
7601
/************************************************************************/
7602
/*                            SetMercator()                             */
7603
/************************************************************************/
7604
7605
OGRErr OGRSpatialReference::SetMercator(double dfCenterLat, double dfCenterLong,
7606
                                        double dfScale, double dfFalseEasting,
7607
                                        double dfFalseNorthing)
7608
7609
4.27k
{
7610
4.27k
    TAKE_OPTIONAL_LOCK();
7611
7612
4.27k
    if (dfCenterLat != 0.0 && dfScale == 1.0)
7613
116
    {
7614
        // Not sure this is correct, but this is how it has been used
7615
        // historically
7616
116
        return SetMercator2SP(dfCenterLat, 0.0, dfCenterLong, dfFalseEasting,
7617
116
                              dfFalseNorthing);
7618
116
    }
7619
4.16k
    return d->replaceConversionAndUnref(
7620
4.16k
        proj_create_conversion_mercator_variant_a(
7621
4.16k
            d->getPROJContext(),
7622
4.16k
            dfCenterLat,  // should be zero
7623
4.16k
            dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0,
7624
4.16k
            nullptr, 0));
7625
4.27k
}
7626
7627
/************************************************************************/
7628
/*                           OSRSetMercator()                           */
7629
/************************************************************************/
7630
7631
OGRErr OSRSetMercator(OGRSpatialReferenceH hSRS, double dfCenterLat,
7632
                      double dfCenterLong, double dfScale,
7633
                      double dfFalseEasting, double dfFalseNorthing)
7634
7635
0
{
7636
0
    VALIDATE_POINTER1(hSRS, "OSRSetMercator", OGRERR_FAILURE);
7637
7638
0
    return ToPointer(hSRS)->SetMercator(dfCenterLat, dfCenterLong, dfScale,
7639
0
                                        dfFalseEasting, dfFalseNorthing);
7640
0
}
7641
7642
/************************************************************************/
7643
/*                           SetMercator2SP()                           */
7644
/************************************************************************/
7645
7646
OGRErr OGRSpatialReference::SetMercator2SP(double dfStdP1, double dfCenterLat,
7647
                                           double dfCenterLong,
7648
                                           double dfFalseEasting,
7649
                                           double dfFalseNorthing)
7650
7651
8.86k
{
7652
8.86k
    if (dfCenterLat == 0.0)
7653
8.17k
    {
7654
8.17k
        return d->replaceConversionAndUnref(
7655
8.17k
            proj_create_conversion_mercator_variant_b(
7656
8.17k
                d->getPROJContext(), dfStdP1, dfCenterLong, dfFalseEasting,
7657
8.17k
                dfFalseNorthing, nullptr, 0, nullptr, 0));
7658
8.17k
    }
7659
7660
695
    TAKE_OPTIONAL_LOCK();
7661
7662
695
    SetProjection(SRS_PT_MERCATOR_2SP);
7663
7664
695
    SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdP1);
7665
695
    SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
7666
695
    SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
7667
695
    SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
7668
695
    SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
7669
7670
695
    return OGRERR_NONE;
7671
8.86k
}
7672
7673
/************************************************************************/
7674
/*                         OSRSetMercator2SP()                          */
7675
/************************************************************************/
7676
7677
OGRErr OSRSetMercator2SP(OGRSpatialReferenceH hSRS, double dfStdP1,
7678
                         double dfCenterLat, double dfCenterLong,
7679
                         double dfFalseEasting, double dfFalseNorthing)
7680
7681
0
{
7682
0
    VALIDATE_POINTER1(hSRS, "OSRSetMercator2SP", OGRERR_FAILURE);
7683
7684
0
    return ToPointer(hSRS)->SetMercator2SP(dfStdP1, dfCenterLat, dfCenterLong,
7685
0
                                           dfFalseEasting, dfFalseNorthing);
7686
0
}
7687
7688
/************************************************************************/
7689
/*                            SetMollweide()                            */
7690
/************************************************************************/
7691
7692
OGRErr OGRSpatialReference::SetMollweide(double dfCentralMeridian,
7693
                                         double dfFalseEasting,
7694
                                         double dfFalseNorthing)
7695
7696
104
{
7697
104
    TAKE_OPTIONAL_LOCK();
7698
7699
104
    return d->replaceConversionAndUnref(proj_create_conversion_mollweide(
7700
104
        d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
7701
104
        nullptr, 0, nullptr, 0));
7702
104
}
7703
7704
/************************************************************************/
7705
/*                          OSRSetMollweide()                           */
7706
/************************************************************************/
7707
7708
OGRErr OSRSetMollweide(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
7709
                       double dfFalseEasting, double dfFalseNorthing)
7710
7711
0
{
7712
0
    VALIDATE_POINTER1(hSRS, "OSRSetMollweide", OGRERR_FAILURE);
7713
7714
0
    return ToPointer(hSRS)->SetMollweide(dfCentralMeridian, dfFalseEasting,
7715
0
                                         dfFalseNorthing);
7716
0
}
7717
7718
/************************************************************************/
7719
/*                              SetNZMG()                               */
7720
/************************************************************************/
7721
7722
OGRErr OGRSpatialReference::SetNZMG(double dfCenterLat, double dfCenterLong,
7723
                                    double dfFalseEasting,
7724
                                    double dfFalseNorthing)
7725
7726
89
{
7727
89
    TAKE_OPTIONAL_LOCK();
7728
7729
89
    return d->replaceConversionAndUnref(
7730
89
        proj_create_conversion_new_zealand_mapping_grid(
7731
89
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
7732
89
            dfFalseNorthing, nullptr, 0, nullptr, 0));
7733
89
}
7734
7735
/************************************************************************/
7736
/*                             OSRSetNZMG()                             */
7737
/************************************************************************/
7738
7739
OGRErr OSRSetNZMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
7740
                  double dfCenterLong, double dfFalseEasting,
7741
                  double dfFalseNorthing)
7742
7743
0
{
7744
0
    VALIDATE_POINTER1(hSRS, "OSRSetNZMG", OGRERR_FAILURE);
7745
7746
0
    return ToPointer(hSRS)->SetNZMG(dfCenterLat, dfCenterLong, dfFalseEasting,
7747
0
                                    dfFalseNorthing);
7748
0
}
7749
7750
/************************************************************************/
7751
/*                               SetOS()                                */
7752
/************************************************************************/
7753
7754
OGRErr OGRSpatialReference::SetOS(double dfOriginLat, double dfCMeridian,
7755
                                  double dfScale, double dfFalseEasting,
7756
                                  double dfFalseNorthing)
7757
7758
15
{
7759
15
    TAKE_OPTIONAL_LOCK();
7760
7761
15
    return d->replaceConversionAndUnref(
7762
15
        proj_create_conversion_oblique_stereographic(
7763
15
            d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale,
7764
15
            dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
7765
15
}
7766
7767
/************************************************************************/
7768
/*                              OSRSetOS()                              */
7769
/************************************************************************/
7770
7771
OGRErr OSRSetOS(OGRSpatialReferenceH hSRS, double dfOriginLat,
7772
                double dfCMeridian, double dfScale, double dfFalseEasting,
7773
                double dfFalseNorthing)
7774
7775
0
{
7776
0
    VALIDATE_POINTER1(hSRS, "OSRSetOS", OGRERR_FAILURE);
7777
7778
0
    return ToPointer(hSRS)->SetOS(dfOriginLat, dfCMeridian, dfScale,
7779
0
                                  dfFalseEasting, dfFalseNorthing);
7780
0
}
7781
7782
/************************************************************************/
7783
/*                          SetOrthographic()                           */
7784
/************************************************************************/
7785
7786
OGRErr OGRSpatialReference::SetOrthographic(double dfCenterLat,
7787
                                            double dfCenterLong,
7788
                                            double dfFalseEasting,
7789
                                            double dfFalseNorthing)
7790
7791
54
{
7792
54
    TAKE_OPTIONAL_LOCK();
7793
7794
54
    return d->replaceConversionAndUnref(proj_create_conversion_orthographic(
7795
54
        d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
7796
54
        dfFalseNorthing, nullptr, 0, nullptr, 0));
7797
54
}
7798
7799
/************************************************************************/
7800
/*                         OSRSetOrthographic()                         */
7801
/************************************************************************/
7802
7803
OGRErr OSRSetOrthographic(OGRSpatialReferenceH hSRS, double dfCenterLat,
7804
                          double dfCenterLong, double dfFalseEasting,
7805
                          double dfFalseNorthing)
7806
7807
0
{
7808
0
    VALIDATE_POINTER1(hSRS, "OSRSetOrthographic", OGRERR_FAILURE);
7809
7810
0
    return ToPointer(hSRS)->SetOrthographic(dfCenterLat, dfCenterLong,
7811
0
                                            dfFalseEasting, dfFalseNorthing);
7812
0
}
7813
7814
/************************************************************************/
7815
/*                            SetPolyconic()                            */
7816
/************************************************************************/
7817
7818
OGRErr OGRSpatialReference::SetPolyconic(double dfCenterLat,
7819
                                         double dfCenterLong,
7820
                                         double dfFalseEasting,
7821
                                         double dfFalseNorthing)
7822
7823
170
{
7824
170
    TAKE_OPTIONAL_LOCK();
7825
7826
    // note: it seems that by some definitions this should include a
7827
    //       scale_factor parameter.
7828
170
    return d->replaceConversionAndUnref(
7829
170
        proj_create_conversion_american_polyconic(
7830
170
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
7831
170
            dfFalseNorthing, nullptr, 0, nullptr, 0));
7832
170
}
7833
7834
/************************************************************************/
7835
/*                          OSRSetPolyconic()                           */
7836
/************************************************************************/
7837
7838
OGRErr OSRSetPolyconic(OGRSpatialReferenceH hSRS, double dfCenterLat,
7839
                       double dfCenterLong, double dfFalseEasting,
7840
                       double dfFalseNorthing)
7841
7842
0
{
7843
0
    VALIDATE_POINTER1(hSRS, "OSRSetPolyconic", OGRERR_FAILURE);
7844
7845
0
    return ToPointer(hSRS)->SetPolyconic(dfCenterLat, dfCenterLong,
7846
0
                                         dfFalseEasting, dfFalseNorthing);
7847
0
}
7848
7849
/************************************************************************/
7850
/*                               SetPS()                                */
7851
/************************************************************************/
7852
7853
/** Sets a Polar Stereographic projection.
7854
 *
7855
 * Two variants are possible:
7856
 * - Polar Stereographic Variant A: dfCenterLat must be +/- 90° and is
7857
 *   interpreted as the latitude of origin, combined with the scale factor
7858
 * - Polar Stereographic Variant B: dfCenterLat is different from +/- 90° and
7859
 *   is interpreted as the latitude of true scale. In that situation, dfScale
7860
 *   must be set to 1 (it is ignored in the projection parameters)
7861
 */
7862
OGRErr OGRSpatialReference::SetPS(double dfCenterLat, double dfCenterLong,
7863
                                  double dfScale, double dfFalseEasting,
7864
                                  double dfFalseNorthing)
7865
7866
10.1k
{
7867
10.1k
    TAKE_OPTIONAL_LOCK();
7868
7869
10.1k
    PJ *conv;
7870
10.1k
    if (dfScale == 1.0 && std::abs(std::abs(dfCenterLat) - 90) > 1e-8)
7871
9.72k
    {
7872
9.72k
        conv = proj_create_conversion_polar_stereographic_variant_b(
7873
9.72k
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
7874
9.72k
            dfFalseNorthing, nullptr, 0, nullptr, 0);
7875
9.72k
    }
7876
426
    else
7877
426
    {
7878
426
        conv = proj_create_conversion_polar_stereographic_variant_a(
7879
426
            d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
7880
426
            dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0);
7881
426
    }
7882
7883
10.1k
    const char *pszName = nullptr;
7884
10.1k
    double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
7885
10.1k
    CPLString osName = pszName ? pszName : "";
7886
7887
10.1k
    d->refreshProjObj();
7888
7889
10.1k
    d->demoteFromBoundCRS();
7890
7891
10.1k
    auto cs = proj_create_cartesian_2D_cs(
7892
10.1k
        d->getPROJContext(),
7893
10.1k
        dfCenterLat > 0 ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
7894
10.1k
                        : PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH,
7895
10.1k
        !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
7896
10.1k
    auto projCRS =
7897
10.1k
        proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
7898
10.1k
                                  d->getGeodBaseCRS(), conv, cs);
7899
10.1k
    proj_destroy(conv);
7900
10.1k
    proj_destroy(cs);
7901
7902
10.1k
    d->setPjCRS(projCRS);
7903
7904
10.1k
    d->undoDemoteFromBoundCRS();
7905
7906
10.1k
    return OGRERR_NONE;
7907
10.1k
}
7908
7909
/************************************************************************/
7910
/*                              OSRSetPS()                              */
7911
/************************************************************************/
7912
7913
OGRErr OSRSetPS(OGRSpatialReferenceH hSRS, double dfCenterLat,
7914
                double dfCenterLong, double dfScale, double dfFalseEasting,
7915
                double dfFalseNorthing)
7916
7917
0
{
7918
0
    VALIDATE_POINTER1(hSRS, "OSRSetPS", OGRERR_FAILURE);
7919
7920
0
    return ToPointer(hSRS)->SetPS(dfCenterLat, dfCenterLong, dfScale,
7921
0
                                  dfFalseEasting, dfFalseNorthing);
7922
0
}
7923
7924
/************************************************************************/
7925
/*                            SetRobinson()                             */
7926
/************************************************************************/
7927
7928
OGRErr OGRSpatialReference::SetRobinson(double dfCenterLong,
7929
                                        double dfFalseEasting,
7930
                                        double dfFalseNorthing)
7931
7932
30
{
7933
30
    TAKE_OPTIONAL_LOCK();
7934
7935
30
    return d->replaceConversionAndUnref(proj_create_conversion_robinson(
7936
30
        d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
7937
30
        nullptr, 0, nullptr, 0));
7938
30
}
7939
7940
/************************************************************************/
7941
/*                           OSRSetRobinson()                           */
7942
/************************************************************************/
7943
7944
OGRErr OSRSetRobinson(OGRSpatialReferenceH hSRS, double dfCenterLong,
7945
                      double dfFalseEasting, double dfFalseNorthing)
7946
7947
0
{
7948
0
    VALIDATE_POINTER1(hSRS, "OSRSetRobinson", OGRERR_FAILURE);
7949
7950
0
    return ToPointer(hSRS)->SetRobinson(dfCenterLong, dfFalseEasting,
7951
0
                                        dfFalseNorthing);
7952
0
}
7953
7954
/************************************************************************/
7955
/*                           SetSinusoidal()                            */
7956
/************************************************************************/
7957
7958
OGRErr OGRSpatialReference::SetSinusoidal(double dfCenterLong,
7959
                                          double dfFalseEasting,
7960
                                          double dfFalseNorthing)
7961
7962
60
{
7963
60
    TAKE_OPTIONAL_LOCK();
7964
7965
60
    return d->replaceConversionAndUnref(proj_create_conversion_sinusoidal(
7966
60
        d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
7967
60
        nullptr, 0, nullptr, 0));
7968
60
}
7969
7970
/************************************************************************/
7971
/*                          OSRSetSinusoidal()                          */
7972
/************************************************************************/
7973
7974
OGRErr OSRSetSinusoidal(OGRSpatialReferenceH hSRS, double dfCenterLong,
7975
                        double dfFalseEasting, double dfFalseNorthing)
7976
7977
0
{
7978
0
    VALIDATE_POINTER1(hSRS, "OSRSetSinusoidal", OGRERR_FAILURE);
7979
7980
0
    return ToPointer(hSRS)->SetSinusoidal(dfCenterLong, dfFalseEasting,
7981
0
                                          dfFalseNorthing);
7982
0
}
7983
7984
/************************************************************************/
7985
/*                          SetStereographic()                          */
7986
/************************************************************************/
7987
7988
OGRErr OGRSpatialReference::SetStereographic(double dfOriginLat,
7989
                                             double dfCMeridian, double dfScale,
7990
                                             double dfFalseEasting,
7991
                                             double dfFalseNorthing)
7992
7993
512
{
7994
512
    TAKE_OPTIONAL_LOCK();
7995
7996
512
    return d->replaceConversionAndUnref(proj_create_conversion_stereographic(
7997
512
        d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale, dfFalseEasting,
7998
512
        dfFalseNorthing, nullptr, 0, nullptr, 0));
7999
512
}
8000
8001
/************************************************************************/
8002
/*                        OSRSetStereographic()                         */
8003
/************************************************************************/
8004
8005
OGRErr OSRSetStereographic(OGRSpatialReferenceH hSRS, double dfOriginLat,
8006
                           double dfCMeridian, double dfScale,
8007
                           double dfFalseEasting, double dfFalseNorthing)
8008
8009
0
{
8010
0
    VALIDATE_POINTER1(hSRS, "OSRSetStereographic", OGRERR_FAILURE);
8011
8012
0
    return ToPointer(hSRS)->SetStereographic(dfOriginLat, dfCMeridian, dfScale,
8013
0
                                             dfFalseEasting, dfFalseNorthing);
8014
0
}
8015
8016
/************************************************************************/
8017
/*                               SetSOC()                               */
8018
/*                                                                      */
8019
/*      NOTE: This definition isn't really used in practice any more    */
8020
/*      and should be considered deprecated.  It seems that swiss       */
8021
/*      oblique mercator is now define as Hotine_Oblique_Mercator       */
8022
/*      with an azimuth of 90 and a rectified_grid_angle of 90.  See    */
8023
/*      EPSG:2056 and Bug 423.                                          */
8024
/************************************************************************/
8025
8026
OGRErr OGRSpatialReference::SetSOC(double dfLatitudeOfOrigin,
8027
                                   double dfCentralMeridian,
8028
                                   double dfFalseEasting,
8029
                                   double dfFalseNorthing)
8030
8031
0
{
8032
0
    TAKE_OPTIONAL_LOCK();
8033
8034
0
    return d->replaceConversionAndUnref(
8035
0
        proj_create_conversion_hotine_oblique_mercator_variant_b(
8036
0
            d->getPROJContext(), dfLatitudeOfOrigin, dfCentralMeridian, 90.0,
8037
0
            90.0, 1.0, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
8038
0
            0.0));
8039
#if 0
8040
    SetProjection( SRS_PT_SWISS_OBLIQUE_CYLINDRICAL );
8041
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfLatitudeOfOrigin );
8042
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
8043
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
8044
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );
8045
8046
    return OGRERR_NONE;
8047
#endif
8048
0
}
8049
8050
/************************************************************************/
8051
/*                             OSRSetSOC()                              */
8052
/************************************************************************/
8053
8054
OGRErr OSRSetSOC(OGRSpatialReferenceH hSRS, double dfLatitudeOfOrigin,
8055
                 double dfCentralMeridian, double dfFalseEasting,
8056
                 double dfFalseNorthing)
8057
8058
0
{
8059
0
    VALIDATE_POINTER1(hSRS, "OSRSetSOC", OGRERR_FAILURE);
8060
8061
0
    return ToPointer(hSRS)->SetSOC(dfLatitudeOfOrigin, dfCentralMeridian,
8062
0
                                   dfFalseEasting, dfFalseNorthing);
8063
0
}
8064
8065
/************************************************************************/
8066
/*                               SetVDG()                               */
8067
/************************************************************************/
8068
8069
OGRErr OGRSpatialReference::SetVDG(double dfCMeridian, double dfFalseEasting,
8070
                                   double dfFalseNorthing)
8071
8072
17
{
8073
17
    TAKE_OPTIONAL_LOCK();
8074
8075
17
    return d->replaceConversionAndUnref(proj_create_conversion_van_der_grinten(
8076
17
        d->getPROJContext(), dfCMeridian, dfFalseEasting, dfFalseNorthing,
8077
17
        nullptr, 0, nullptr, 0));
8078
17
}
8079
8080
/************************************************************************/
8081
/*                             OSRSetVDG()                              */
8082
/************************************************************************/
8083
8084
OGRErr OSRSetVDG(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
8085
                 double dfFalseEasting, double dfFalseNorthing)
8086
8087
0
{
8088
0
    VALIDATE_POINTER1(hSRS, "OSRSetVDG", OGRERR_FAILURE);
8089
8090
0
    return ToPointer(hSRS)->SetVDG(dfCentralMeridian, dfFalseEasting,
8091
0
                                   dfFalseNorthing);
8092
0
}
8093
8094
/************************************************************************/
8095
/*                               SetUTM()                               */
8096
/************************************************************************/
8097
8098
/**
8099
 * \brief Set UTM projection definition.
8100
 *
8101
 * This will generate a projection definition with the full set of
8102
 * transverse mercator projection parameters for the given UTM zone.
8103
 * If no PROJCS[] description is set yet, one will be set to look
8104
 * like "UTM Zone %d, {Northern, Southern} Hemisphere".
8105
 *
8106
 * This method is the same as the C function OSRSetUTM().
8107
 *
8108
 * @param nZone UTM zone.
8109
 *
8110
 * @param bNorth TRUE for northern hemisphere, or FALSE for southern
8111
 * hemisphere.
8112
 *
8113
 * @return OGRERR_NONE on success.
8114
 */
8115
8116
OGRErr OGRSpatialReference::SetUTM(int nZone, int bNorth)
8117
8118
11.1k
{
8119
11.1k
    TAKE_OPTIONAL_LOCK();
8120
8121
11.1k
    if (nZone < 0 || nZone > 60)
8122
769
    {
8123
769
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid zone: %d", nZone);
8124
769
        return OGRERR_FAILURE;
8125
769
    }
8126
8127
10.4k
    return d->replaceConversionAndUnref(
8128
10.4k
        proj_create_conversion_utm(d->getPROJContext(), nZone, bNorth));
8129
11.1k
}
8130
8131
/************************************************************************/
8132
/*                             OSRSetUTM()                              */
8133
/************************************************************************/
8134
8135
/**
8136
 * \brief Set UTM projection definition.
8137
 *
8138
 * This is the same as the C++ method OGRSpatialReference::SetUTM()
8139
 */
8140
OGRErr OSRSetUTM(OGRSpatialReferenceH hSRS, int nZone, int bNorth)
8141
8142
0
{
8143
0
    VALIDATE_POINTER1(hSRS, "OSRSetUTM", OGRERR_FAILURE);
8144
8145
0
    return ToPointer(hSRS)->SetUTM(nZone, bNorth);
8146
0
}
8147
8148
/************************************************************************/
8149
/*                             GetUTMZone()                             */
8150
/*                                                                      */
8151
/*      Returns zero if it isn't UTM.                                   */
8152
/************************************************************************/
8153
8154
/**
8155
 * \brief Get utm zone information.
8156
 *
8157
 * This is the same as the C function OSRGetUTMZone().
8158
 *
8159
 * In SWIG bindings (Python, Java, etc) the GetUTMZone() method returns a
8160
 * zone which is negative in the southern hemisphere instead of having the
8161
 * pbNorth flag used in the C and C++ interface.
8162
 *
8163
 * @param pbNorth pointer to in to set to TRUE if northern hemisphere, or
8164
 * FALSE if southern.
8165
 *
8166
 * @return UTM zone number or zero if this isn't a UTM definition.
8167
 */
8168
8169
int OGRSpatialReference::GetUTMZone(int *pbNorth) const
8170
8171
1.57k
{
8172
1.57k
    TAKE_OPTIONAL_LOCK();
8173
8174
1.57k
    if (IsProjected() && GetAxesCount() == 3)
8175
7
    {
8176
7
        OGRSpatialReference *poSRSTmp = Clone();
8177
7
        poSRSTmp->DemoteTo2D(nullptr);
8178
7
        const int nZone = poSRSTmp->GetUTMZone(pbNorth);
8179
7
        delete poSRSTmp;
8180
7
        return nZone;
8181
7
    }
8182
8183
1.56k
    const char *pszProjection = GetAttrValue("PROJECTION");
8184
8185
1.56k
    if (pszProjection == nullptr ||
8186
1.41k
        !EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
8187
1.02k
        return 0;
8188
8189
542
    if (GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) != 0.0)
8190
137
        return 0;
8191
8192
405
    if (GetProjParm(SRS_PP_SCALE_FACTOR, 1.0) != 0.9996)
8193
137
        return 0;
8194
8195
268
    if (fabs(GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0) - 500000.0) > 0.001)
8196
7
        return 0;
8197
8198
261
    const double dfFalseNorthing = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
8199
8200
261
    if (dfFalseNorthing != 0.0 && fabs(dfFalseNorthing - 10000000.0) > 0.001)
8201
1
        return 0;
8202
8203
260
    if (pbNorth != nullptr)
8204
253
        *pbNorth = (dfFalseNorthing == 0);
8205
8206
260
    const double dfCentralMeridian =
8207
260
        GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
8208
260
    const double dfZone = (dfCentralMeridian + 186.0) / 6.0;
8209
8210
260
    if (dfCentralMeridian < -177.00001 || dfCentralMeridian > 177.000001 ||
8211
259
        std::isnan(dfZone) ||
8212
259
        std::abs(dfZone - static_cast<int>(dfZone) - 0.5) > 0.00001)
8213
2
        return 0;
8214
8215
258
    return static_cast<int>(dfZone);
8216
260
}
8217
8218
/************************************************************************/
8219
/*                           OSRGetUTMZone()                            */
8220
/************************************************************************/
8221
8222
/**
8223
 * \brief Get utm zone information.
8224
 *
8225
 * This is the same as the C++ method OGRSpatialReference::GetUTMZone()
8226
 */
8227
int OSRGetUTMZone(OGRSpatialReferenceH hSRS, int *pbNorth)
8228
8229
0
{
8230
0
    VALIDATE_POINTER1(hSRS, "OSRGetUTMZone", 0);
8231
8232
0
    return ToPointer(hSRS)->GetUTMZone(pbNorth);
8233
0
}
8234
8235
/************************************************************************/
8236
/*                             SetWagner()                              */
8237
/************************************************************************/
8238
8239
OGRErr OGRSpatialReference::SetWagner(int nVariation,  // 1--7.
8240
                                      double dfCenterLat, double dfFalseEasting,
8241
                                      double dfFalseNorthing)
8242
8243
0
{
8244
0
    TAKE_OPTIONAL_LOCK();
8245
8246
0
    PJ *conv;
8247
0
    if (nVariation == 1)
8248
0
    {
8249
0
        conv = proj_create_conversion_wagner_i(d->getPROJContext(), 0.0,
8250
0
                                               dfFalseEasting, dfFalseNorthing,
8251
0
                                               nullptr, 0.0, nullptr, 0.0);
8252
0
    }
8253
0
    else if (nVariation == 2)
8254
0
    {
8255
0
        conv = proj_create_conversion_wagner_ii(d->getPROJContext(), 0.0,
8256
0
                                                dfFalseEasting, dfFalseNorthing,
8257
0
                                                nullptr, 0.0, nullptr, 0.0);
8258
0
    }
8259
0
    else if (nVariation == 3)
8260
0
    {
8261
0
        conv = proj_create_conversion_wagner_iii(
8262
0
            d->getPROJContext(), dfCenterLat, 0.0, dfFalseEasting,
8263
0
            dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
8264
0
    }
8265
0
    else if (nVariation == 4)
8266
0
    {
8267
0
        conv = proj_create_conversion_wagner_iv(d->getPROJContext(), 0.0,
8268
0
                                                dfFalseEasting, dfFalseNorthing,
8269
0
                                                nullptr, 0.0, nullptr, 0.0);
8270
0
    }
8271
0
    else if (nVariation == 5)
8272
0
    {
8273
0
        conv = proj_create_conversion_wagner_v(d->getPROJContext(), 0.0,
8274
0
                                               dfFalseEasting, dfFalseNorthing,
8275
0
                                               nullptr, 0.0, nullptr, 0.0);
8276
0
    }
8277
0
    else if (nVariation == 6)
8278
0
    {
8279
0
        conv = proj_create_conversion_wagner_vi(d->getPROJContext(), 0.0,
8280
0
                                                dfFalseEasting, dfFalseNorthing,
8281
0
                                                nullptr, 0.0, nullptr, 0.0);
8282
0
    }
8283
0
    else if (nVariation == 7)
8284
0
    {
8285
0
        conv = proj_create_conversion_wagner_vii(
8286
0
            d->getPROJContext(), 0.0, dfFalseEasting, dfFalseNorthing, nullptr,
8287
0
            0.0, nullptr, 0.0);
8288
0
    }
8289
0
    else
8290
0
    {
8291
0
        CPLError(CE_Failure, CPLE_AppDefined,
8292
0
                 "Unsupported Wagner variation (%d).", nVariation);
8293
0
        return OGRERR_UNSUPPORTED_SRS;
8294
0
    }
8295
8296
0
    return d->replaceConversionAndUnref(conv);
8297
0
}
8298
8299
/************************************************************************/
8300
/*                            OSRSetWagner()                            */
8301
/************************************************************************/
8302
8303
OGRErr OSRSetWagner(OGRSpatialReferenceH hSRS, int nVariation,
8304
                    double dfCenterLat, double dfFalseEasting,
8305
                    double dfFalseNorthing)
8306
8307
0
{
8308
0
    VALIDATE_POINTER1(hSRS, "OSRSetWagner", OGRERR_FAILURE);
8309
8310
0
    return ToPointer(hSRS)->SetWagner(nVariation, dfCenterLat, dfFalseEasting,
8311
0
                                      dfFalseNorthing);
8312
0
}
8313
8314
/************************************************************************/
8315
/*                               SetQSC()                               */
8316
/************************************************************************/
8317
8318
OGRErr OGRSpatialReference::SetQSC(double dfCenterLat, double dfCenterLong)
8319
0
{
8320
0
    TAKE_OPTIONAL_LOCK();
8321
8322
0
    return d->replaceConversionAndUnref(
8323
0
        proj_create_conversion_quadrilateralized_spherical_cube(
8324
0
            d->getPROJContext(), dfCenterLat, dfCenterLong, 0.0, 0.0, nullptr,
8325
0
            0, nullptr, 0));
8326
0
}
8327
8328
/************************************************************************/
8329
/*                             OSRSetQSC()                              */
8330
/************************************************************************/
8331
8332
OGRErr OSRSetQSC(OGRSpatialReferenceH hSRS, double dfCenterLat,
8333
                 double dfCenterLong)
8334
8335
0
{
8336
0
    VALIDATE_POINTER1(hSRS, "OSRSetQSC", OGRERR_FAILURE);
8337
8338
0
    return ToPointer(hSRS)->SetQSC(dfCenterLat, dfCenterLong);
8339
0
}
8340
8341
/************************************************************************/
8342
/*                               SetSCH()                               */
8343
/************************************************************************/
8344
8345
OGRErr OGRSpatialReference::SetSCH(double dfPegLat, double dfPegLong,
8346
                                   double dfPegHeading, double dfPegHgt)
8347
8348
0
{
8349
0
    TAKE_OPTIONAL_LOCK();
8350
8351
0
    return d->replaceConversionAndUnref(
8352
0
        proj_create_conversion_spherical_cross_track_height(
8353
0
            d->getPROJContext(), dfPegLat, dfPegLong, dfPegHeading, dfPegHgt,
8354
0
            nullptr, 0, nullptr, 0));
8355
0
}
8356
8357
/************************************************************************/
8358
/*                             OSRSetSCH()                              */
8359
/************************************************************************/
8360
8361
OGRErr OSRSetSCH(OGRSpatialReferenceH hSRS, double dfPegLat, double dfPegLong,
8362
                 double dfPegHeading, double dfPegHgt)
8363
8364
0
{
8365
0
    VALIDATE_POINTER1(hSRS, "OSRSetSCH", OGRERR_FAILURE);
8366
8367
0
    return ToPointer(hSRS)->SetSCH(dfPegLat, dfPegLong, dfPegHeading, dfPegHgt);
8368
0
}
8369
8370
/************************************************************************/
8371
/*                       SetVerticalPerspective()                       */
8372
/************************************************************************/
8373
8374
OGRErr OGRSpatialReference::SetVerticalPerspective(
8375
    double dfTopoOriginLat, double dfTopoOriginLon, double dfTopoOriginHeight,
8376
    double dfViewPointHeight, double dfFalseEasting, double dfFalseNorthing)
8377
49
{
8378
49
    TAKE_OPTIONAL_LOCK();
8379
8380
49
    return d->replaceConversionAndUnref(
8381
49
        proj_create_conversion_vertical_perspective(
8382
49
            d->getPROJContext(), dfTopoOriginLat, dfTopoOriginLon,
8383
49
            dfTopoOriginHeight, dfViewPointHeight, dfFalseEasting,
8384
49
            dfFalseNorthing, nullptr, 0, nullptr, 0));
8385
49
}
8386
8387
/************************************************************************/
8388
/*                     OSRSetVerticalPerspective()                      */
8389
/************************************************************************/
8390
8391
OGRErr OSRSetVerticalPerspective(OGRSpatialReferenceH hSRS,
8392
                                 double dfTopoOriginLat, double dfTopoOriginLon,
8393
                                 double dfTopoOriginHeight,
8394
                                 double dfViewPointHeight,
8395
                                 double dfFalseEasting, double dfFalseNorthing)
8396
8397
0
{
8398
0
    VALIDATE_POINTER1(hSRS, "OSRSetVerticalPerspective", OGRERR_FAILURE);
8399
8400
0
    return ToPointer(hSRS)->SetVerticalPerspective(
8401
0
        dfTopoOriginLat, dfTopoOriginLon, dfTopoOriginHeight, dfViewPointHeight,
8402
0
        dfFalseEasting, dfFalseNorthing);
8403
0
}
8404
8405
/************************************************************************/
8406
/*          SetDerivedGeogCRSWithPoleRotationGRIBConvention()           */
8407
/************************************************************************/
8408
8409
OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationGRIBConvention(
8410
    const char *pszCRSName, double dfSouthPoleLat, double dfSouthPoleLon,
8411
    double dfAxisRotation)
8412
8.20k
{
8413
8.20k
    TAKE_OPTIONAL_LOCK();
8414
8415
8.20k
    d->refreshProjObj();
8416
8.20k
    if (!d->m_pj_crs)
8417
0
        return OGRERR_FAILURE;
8418
8.20k
    if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
8419
0
        return OGRERR_FAILURE;
8420
8.20k
    auto ctxt = d->getPROJContext();
8421
8.20k
    auto conv = proj_create_conversion_pole_rotation_grib_convention(
8422
8.20k
        ctxt, dfSouthPoleLat, dfSouthPoleLon, dfAxisRotation, nullptr, 0);
8423
8.20k
    auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
8424
8.20k
    d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
8425
8.20k
                                                   d->m_pj_crs, conv, cs));
8426
8.20k
    proj_destroy(conv);
8427
8.20k
    proj_destroy(cs);
8428
8.20k
    return OGRERR_NONE;
8429
8.20k
}
8430
8431
/************************************************************************/
8432
/*        SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention()         */
8433
/************************************************************************/
8434
8435
OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention(
8436
    const char *pszCRSName, double dfGridNorthPoleLat,
8437
    double dfGridNorthPoleLon, double dfNorthPoleGridLon)
8438
0
{
8439
0
    TAKE_OPTIONAL_LOCK();
8440
8441
0
#if PROJ_VERSION_MAJOR > 8 ||                                                  \
8442
0
    (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR >= 2)
8443
0
    d->refreshProjObj();
8444
0
    if (!d->m_pj_crs)
8445
0
        return OGRERR_FAILURE;
8446
0
    if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
8447
0
        return OGRERR_FAILURE;
8448
0
    auto ctxt = d->getPROJContext();
8449
0
    auto conv = proj_create_conversion_pole_rotation_netcdf_cf_convention(
8450
0
        ctxt, dfGridNorthPoleLat, dfGridNorthPoleLon, dfNorthPoleGridLon,
8451
0
        nullptr, 0);
8452
0
    auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
8453
0
    d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
8454
0
                                                   d->m_pj_crs, conv, cs));
8455
0
    proj_destroy(conv);
8456
0
    proj_destroy(cs);
8457
0
    return OGRERR_NONE;
8458
#else
8459
    (void)pszCRSName;
8460
    SetProjection("Rotated_pole");
8461
    SetExtension(
8462
        "PROJCS", "PROJ4",
8463
        CPLSPrintf("+proj=ob_tran +o_proj=longlat +lon_0=%.17g +o_lon_p=%.17g "
8464
                   "+o_lat_p=%.17g +a=%.17g +b=%.17g "
8465
                   "+to_meter=0.0174532925199433 "
8466
                   "+wktext",
8467
                   180.0 + dfGridNorthPoleLon, dfNorthPoleGridLon,
8468
                   dfGridNorthPoleLat, GetSemiMajor(nullptr),
8469
                   GetSemiMinor(nullptr)));
8470
    return OGRERR_NONE;
8471
#endif
8472
0
}
8473
8474
/************************************************************************/
8475
/*                            SetAuthority()                            */
8476
/************************************************************************/
8477
8478
/**
8479
 * \brief Set the authority for a node.
8480
 *
8481
 * This method is the same as the C function OSRSetAuthority().
8482
 *
8483
 * @param pszTargetKey the partial or complete path to the node to
8484
 * set an authority on.  i.e. "PROJCS", "GEOGCS" or "GEOGCS|UNIT".
8485
 *
8486
 * @param pszAuthority authority name, such as "EPSG".
8487
 *
8488
 * @param nCode code for value with this authority.
8489
 *
8490
 * @return OGRERR_NONE on success.
8491
 */
8492
8493
OGRErr OGRSpatialReference::SetAuthority(const char *pszTargetKey,
8494
                                         const char *pszAuthority, int nCode)
8495
8496
69.8k
{
8497
69.8k
    TAKE_OPTIONAL_LOCK();
8498
8499
69.8k
    d->refreshProjObj();
8500
69.8k
    pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
8501
8502
69.8k
    if (pszTargetKey == nullptr)
8503
8.41k
    {
8504
8.41k
        if (!d->m_pj_crs)
8505
62
            return OGRERR_FAILURE;
8506
8.35k
        CPLString osCode;
8507
8.35k
        osCode.Printf("%d", nCode);
8508
8.35k
        d->demoteFromBoundCRS();
8509
8.35k
        d->setPjCRS(proj_alter_id(d->getPROJContext(), d->m_pj_crs,
8510
8.35k
                                  pszAuthority, osCode.c_str()));
8511
8.35k
        d->undoDemoteFromBoundCRS();
8512
8.35k
        return OGRERR_NONE;
8513
8.41k
    }
8514
8515
61.4k
    d->demoteFromBoundCRS();
8516
61.4k
    if (d->m_pjType == PJ_TYPE_PROJECTED_CRS && EQUAL(pszTargetKey, "GEOGCS"))
8517
11.7k
    {
8518
11.7k
        CPLString osCode;
8519
11.7k
        osCode.Printf("%d", nCode);
8520
11.7k
        auto newGeogCRS =
8521
11.7k
            proj_alter_id(d->getPROJContext(), d->getGeodBaseCRS(),
8522
11.7k
                          pszAuthority, osCode.c_str());
8523
8524
11.7k
        auto conv =
8525
11.7k
            proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
8526
8527
11.7k
        auto projCRS = proj_create_projected_crs(
8528
11.7k
            d->getPROJContext(), d->getProjCRSName(), newGeogCRS, conv,
8529
11.7k
            d->getProjCRSCoordSys());
8530
8531
        // Preserve existing id on the PROJCRS
8532
11.7k
        const char *pszProjCRSAuthName = proj_get_id_auth_name(d->m_pj_crs, 0);
8533
11.7k
        const char *pszProjCRSCode = proj_get_id_code(d->m_pj_crs, 0);
8534
11.7k
        if (pszProjCRSAuthName && pszProjCRSCode)
8535
6
        {
8536
6
            auto projCRSWithId =
8537
6
                proj_alter_id(d->getPROJContext(), projCRS, pszProjCRSAuthName,
8538
6
                              pszProjCRSCode);
8539
6
            proj_destroy(projCRS);
8540
6
            projCRS = projCRSWithId;
8541
6
        }
8542
8543
11.7k
        proj_destroy(newGeogCRS);
8544
11.7k
        proj_destroy(conv);
8545
8546
11.7k
        d->setPjCRS(projCRS);
8547
11.7k
        d->undoDemoteFromBoundCRS();
8548
11.7k
        return OGRERR_NONE;
8549
11.7k
    }
8550
49.6k
    d->undoDemoteFromBoundCRS();
8551
8552
    /* -------------------------------------------------------------------- */
8553
    /*      Find the node below which the authority should be put.          */
8554
    /* -------------------------------------------------------------------- */
8555
49.6k
    OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
8556
8557
49.6k
    if (poNode == nullptr)
8558
3.06k
        return OGRERR_FAILURE;
8559
8560
    /* -------------------------------------------------------------------- */
8561
    /*      If there is an existing AUTHORITY child blow it away before     */
8562
    /*      trying to set a new one.                                        */
8563
    /* -------------------------------------------------------------------- */
8564
46.5k
    int iOldChild = poNode->FindChild("AUTHORITY");
8565
46.5k
    if (iOldChild != -1)
8566
38
        poNode->DestroyChild(iOldChild);
8567
8568
    /* -------------------------------------------------------------------- */
8569
    /*      Create a new authority node.                                    */
8570
    /* -------------------------------------------------------------------- */
8571
46.5k
    char szCode[32] = {};
8572
8573
46.5k
    snprintf(szCode, sizeof(szCode), "%d", nCode);
8574
8575
46.5k
    OGR_SRSNode *poAuthNode = new OGR_SRSNode("AUTHORITY");
8576
46.5k
    poAuthNode->AddChild(new OGR_SRSNode(pszAuthority));
8577
46.5k
    poAuthNode->AddChild(new OGR_SRSNode(szCode));
8578
8579
46.5k
    poNode->AddChild(poAuthNode);
8580
8581
46.5k
    return OGRERR_NONE;
8582
49.6k
}
8583
8584
/************************************************************************/
8585
/*                          OSRSetAuthority()                           */
8586
/************************************************************************/
8587
8588
/**
8589
 * \brief Set the authority for a node.
8590
 *
8591
 * This function is the same as OGRSpatialReference::SetAuthority().
8592
 */
8593
OGRErr OSRSetAuthority(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
8594
                       const char *pszAuthority, int nCode)
8595
8596
0
{
8597
0
    VALIDATE_POINTER1(hSRS, "OSRSetAuthority", OGRERR_FAILURE);
8598
8599
0
    return ToPointer(hSRS)->SetAuthority(pszTargetKey, pszAuthority, nCode);
8600
0
}
8601
8602
/************************************************************************/
8603
/*                          GetAuthorityCode()                          */
8604
/************************************************************************/
8605
8606
/**
8607
 * \brief Get the authority code for a node.
8608
 *
8609
 * This method is used to query an AUTHORITY[] node from within the
8610
 * WKT tree, and fetch the code value.
8611
 *
8612
 * While in theory values may be non-numeric, for the EPSG authority all
8613
 * code values should be integral.
8614
 *
8615
 * This method is the same as the C function OSRGetAuthorityCode().
8616
 *
8617
 * @param pszTargetKey the partial or complete path to the node to
8618
 * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
8619
 * search for an authority node on the root element.
8620
 *
8621
 * @return value code from authority node, or NULL on failure.  The value
8622
 * returned is internal and should not be freed or modified.
8623
 */
8624
8625
const char *
8626
OGRSpatialReference::GetAuthorityCode(const char *pszTargetKey) const
8627
8628
159k
{
8629
159k
    TAKE_OPTIONAL_LOCK();
8630
8631
159k
    d->refreshProjObj();
8632
159k
    const char *pszInputTargetKey = pszTargetKey;
8633
159k
    pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
8634
159k
    if (pszTargetKey == nullptr)
8635
150k
    {
8636
150k
        if (!d->m_pj_crs)
8637
1.20k
        {
8638
1.20k
            return nullptr;
8639
1.20k
        }
8640
149k
        d->demoteFromBoundCRS();
8641
149k
        auto ret = proj_get_id_code(d->m_pj_crs, 0);
8642
149k
        if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
8643
52.2k
        {
8644
52.2k
            auto ctxt = d->getPROJContext();
8645
52.2k
            auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
8646
52.2k
            if (cs)
8647
52.2k
            {
8648
52.2k
                const int axisCount = proj_cs_get_axis_count(ctxt, cs);
8649
52.2k
                proj_destroy(cs);
8650
52.2k
                if (axisCount == 3)
8651
12
                {
8652
                    // This might come from a COMPD_CS with a VERT_DATUM type =
8653
                    // 2002 in which case, using the WKT1 representation will
8654
                    // enable us to recover the EPSG code.
8655
12
                    pszTargetKey = pszInputTargetKey;
8656
12
                }
8657
52.2k
            }
8658
52.2k
        }
8659
149k
        d->undoDemoteFromBoundCRS();
8660
149k
        if (ret != nullptr || pszTargetKey == nullptr)
8661
149k
        {
8662
149k
            return ret;
8663
149k
        }
8664
149k
    }
8665
8666
    // Special key for that context
8667
8.71k
    else if (EQUAL(pszTargetKey, "HORIZCRS") &&
8668
0
             d->m_pjType == PJ_TYPE_COMPOUND_CRS)
8669
0
    {
8670
0
        auto ctxt = d->getPROJContext();
8671
0
        auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
8672
0
        if (crs)
8673
0
        {
8674
0
            const char *ret = proj_get_id_code(crs, 0);
8675
0
            if (ret)
8676
0
                ret = CPLSPrintf("%s", ret);
8677
0
            proj_destroy(crs);
8678
0
            return ret;
8679
0
        }
8680
0
    }
8681
8.71k
    else if (EQUAL(pszTargetKey, "VERTCRS") &&
8682
0
             d->m_pjType == PJ_TYPE_COMPOUND_CRS)
8683
0
    {
8684
0
        auto ctxt = d->getPROJContext();
8685
0
        auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
8686
0
        if (crs)
8687
0
        {
8688
0
            const char *ret = proj_get_id_code(crs, 0);
8689
0
            if (ret)
8690
0
                ret = CPLSPrintf("%s", ret);
8691
0
            proj_destroy(crs);
8692
0
            return ret;
8693
0
        }
8694
0
    }
8695
8696
    /* -------------------------------------------------------------------- */
8697
    /*      Find the node below which the authority should be put.          */
8698
    /* -------------------------------------------------------------------- */
8699
8.71k
    const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
8700
8701
8.71k
    if (poNode == nullptr)
8702
3.79k
        return nullptr;
8703
8704
    /* -------------------------------------------------------------------- */
8705
    /*      Fetch AUTHORITY child if there is one.                          */
8706
    /* -------------------------------------------------------------------- */
8707
4.91k
    if (poNode->FindChild("AUTHORITY") == -1)
8708
1.31k
        return nullptr;
8709
8710
3.60k
    poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
8711
8712
    /* -------------------------------------------------------------------- */
8713
    /*      Create a new authority node.                                    */
8714
    /* -------------------------------------------------------------------- */
8715
3.60k
    if (poNode->GetChildCount() < 2)
8716
0
        return nullptr;
8717
8718
3.60k
    return poNode->GetChild(1)->GetValue();
8719
3.60k
}
8720
8721
/************************************************************************/
8722
/*                        OSRGetAuthorityCode()                         */
8723
/************************************************************************/
8724
8725
/**
8726
 * \brief Get the authority code for a node.
8727
 *
8728
 * This function is the same as OGRSpatialReference::GetAuthorityCode().
8729
 */
8730
const char *OSRGetAuthorityCode(OGRSpatialReferenceH hSRS,
8731
                                const char *pszTargetKey)
8732
8733
253
{
8734
253
    VALIDATE_POINTER1(hSRS, "OSRGetAuthorityCode", nullptr);
8735
8736
253
    return ToPointer(hSRS)->GetAuthorityCode(pszTargetKey);
8737
253
}
8738
8739
/************************************************************************/
8740
/*                          GetAuthorityName()                          */
8741
/************************************************************************/
8742
8743
/**
8744
 * \brief Get the authority name for a node.
8745
 *
8746
 * This method is used to query an AUTHORITY[] node from within the
8747
 * WKT tree, and fetch the authority name value.
8748
 *
8749
 * The most common authority is "EPSG".
8750
 *
8751
 * This method is the same as the C function OSRGetAuthorityName().
8752
 *
8753
 * @param pszTargetKey the partial or complete path to the node to
8754
 * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
8755
 * search for an authority node on the root element.
8756
 *
8757
 * @return value code from authority node, or NULL on failure. The value
8758
 * returned is internal and should not be freed or modified.
8759
 */
8760
8761
const char *
8762
OGRSpatialReference::GetAuthorityName(const char *pszTargetKey) const
8763
8764
98.0k
{
8765
98.0k
    TAKE_OPTIONAL_LOCK();
8766
8767
98.0k
    d->refreshProjObj();
8768
98.0k
    const char *pszInputTargetKey = pszTargetKey;
8769
98.0k
    pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
8770
98.0k
    if (pszTargetKey == nullptr)
8771
87.4k
    {
8772
87.4k
        if (!d->m_pj_crs)
8773
3.32k
        {
8774
3.32k
            return nullptr;
8775
3.32k
        }
8776
84.1k
        d->demoteFromBoundCRS();
8777
84.1k
        auto ret = proj_get_id_auth_name(d->m_pj_crs, 0);
8778
84.1k
        if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
8779
27.7k
        {
8780
27.7k
            auto ctxt = d->getPROJContext();
8781
27.7k
            auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
8782
27.7k
            if (cs)
8783
27.7k
            {
8784
27.7k
                const int axisCount = proj_cs_get_axis_count(ctxt, cs);
8785
27.7k
                proj_destroy(cs);
8786
27.7k
                if (axisCount == 3)
8787
0
                {
8788
                    // This might come from a COMPD_CS with a VERT_DATUM type =
8789
                    // 2002 in which case, using the WKT1 representation will
8790
                    // enable us to recover the EPSG code.
8791
0
                    pszTargetKey = pszInputTargetKey;
8792
0
                }
8793
27.7k
            }
8794
27.7k
        }
8795
84.1k
        d->undoDemoteFromBoundCRS();
8796
84.1k
        if (ret != nullptr || pszTargetKey == nullptr)
8797
84.1k
        {
8798
84.1k
            return ret;
8799
84.1k
        }
8800
84.1k
    }
8801
8802
    // Special key for that context
8803
10.5k
    else if (EQUAL(pszTargetKey, "HORIZCRS") &&
8804
0
             d->m_pjType == PJ_TYPE_COMPOUND_CRS)
8805
0
    {
8806
0
        auto ctxt = d->getPROJContext();
8807
0
        auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
8808
0
        if (crs)
8809
0
        {
8810
0
            const char *ret = proj_get_id_auth_name(crs, 0);
8811
0
            if (ret)
8812
0
                ret = CPLSPrintf("%s", ret);
8813
0
            proj_destroy(crs);
8814
0
            return ret;
8815
0
        }
8816
0
    }
8817
10.5k
    else if (EQUAL(pszTargetKey, "VERTCRS") &&
8818
0
             d->m_pjType == PJ_TYPE_COMPOUND_CRS)
8819
0
    {
8820
0
        auto ctxt = d->getPROJContext();
8821
0
        auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
8822
0
        if (crs)
8823
0
        {
8824
0
            const char *ret = proj_get_id_auth_name(crs, 0);
8825
0
            if (ret)
8826
0
                ret = CPLSPrintf("%s", ret);
8827
0
            proj_destroy(crs);
8828
0
            return ret;
8829
0
        }
8830
0
    }
8831
8832
    /* -------------------------------------------------------------------- */
8833
    /*      Find the node below which the authority should be put.          */
8834
    /* -------------------------------------------------------------------- */
8835
10.5k
    const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
8836
8837
10.5k
    if (poNode == nullptr)
8838
4.07k
        return nullptr;
8839
8840
    /* -------------------------------------------------------------------- */
8841
    /*      Fetch AUTHORITY child if there is one.                          */
8842
    /* -------------------------------------------------------------------- */
8843
6.47k
    if (poNode->FindChild("AUTHORITY") == -1)
8844
2.84k
        return nullptr;
8845
8846
3.62k
    poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
8847
8848
    /* -------------------------------------------------------------------- */
8849
    /*      Create a new authority node.                                    */
8850
    /* -------------------------------------------------------------------- */
8851
3.62k
    if (poNode->GetChildCount() < 2)
8852
0
        return nullptr;
8853
8854
3.62k
    return poNode->GetChild(0)->GetValue();
8855
3.62k
}
8856
8857
/************************************************************************/
8858
/*                        OSRGetAuthorityName()                         */
8859
/************************************************************************/
8860
8861
/**
8862
 * \brief Get the authority name for a node.
8863
 *
8864
 * This function is the same as OGRSpatialReference::GetAuthorityName().
8865
 */
8866
const char *OSRGetAuthorityName(OGRSpatialReferenceH hSRS,
8867
                                const char *pszTargetKey)
8868
8869
253
{
8870
253
    VALIDATE_POINTER1(hSRS, "OSRGetAuthorityName", nullptr);
8871
8872
253
    return ToPointer(hSRS)->GetAuthorityName(pszTargetKey);
8873
253
}
8874
8875
/************************************************************************/
8876
/*                             GetOGCURN()                              */
8877
/************************************************************************/
8878
8879
/**
8880
 * \brief Get a OGC URN string describing the CRS, when possible
8881
 *
8882
 * This method assumes that the CRS has a top-level identifier, or is
8883
 * a compound CRS whose horizontal and vertical parts have a top-level
8884
 * identifier.
8885
 *
8886
 * @return a string to free with CPLFree(), or nullptr when no result can be
8887
 * generated
8888
 *
8889
 * @since GDAL 3.5
8890
 */
8891
8892
char *OGRSpatialReference::GetOGCURN() const
8893
8894
58
{
8895
58
    TAKE_OPTIONAL_LOCK();
8896
8897
58
    const char *pszAuthName = GetAuthorityName(nullptr);
8898
58
    const char *pszAuthCode = GetAuthorityCode(nullptr);
8899
58
    if (pszAuthName && pszAuthCode)
8900
3
        return CPLStrdup(
8901
3
            CPLSPrintf("urn:ogc:def:crs:%s::%s", pszAuthName, pszAuthCode));
8902
55
    if (d->m_pjType != PJ_TYPE_COMPOUND_CRS)
8903
55
        return nullptr;
8904
0
    auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
8905
0
    auto vertCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
8906
0
    char *pszRet = nullptr;
8907
0
    if (horizCRS && vertCRS)
8908
0
    {
8909
0
        auto horizAuthName = proj_get_id_auth_name(horizCRS, 0);
8910
0
        auto horizAuthCode = proj_get_id_code(horizCRS, 0);
8911
0
        auto vertAuthName = proj_get_id_auth_name(vertCRS, 0);
8912
0
        auto vertAuthCode = proj_get_id_code(vertCRS, 0);
8913
0
        if (horizAuthName && horizAuthCode && vertAuthName && vertAuthCode)
8914
0
        {
8915
0
            pszRet = CPLStrdup(CPLSPrintf(
8916
0
                "urn:ogc:def:crs,crs:%s::%s,crs:%s::%s", horizAuthName,
8917
0
                horizAuthCode, vertAuthName, vertAuthCode));
8918
0
        }
8919
0
    }
8920
0
    proj_destroy(horizCRS);
8921
0
    proj_destroy(vertCRS);
8922
0
    return pszRet;
8923
55
}
8924
8925
/************************************************************************/
8926
/*                           StripVertical()                            */
8927
/************************************************************************/
8928
8929
/**
8930
 * \brief Convert a compound cs into a horizontal CS.
8931
 *
8932
 * If this SRS is of type COMPD_CS[] then the vertical CS and the root COMPD_CS
8933
 * nodes are stripped resulting and only the horizontal coordinate system
8934
 * portion remains (normally PROJCS, GEOGCS or LOCAL_CS).
8935
 *
8936
 * If this is not a compound coordinate system then nothing is changed.
8937
 *
8938
 * This method is the same as the C function OSRStripVertical().
8939
 *
8940
 */
8941
8942
OGRErr OGRSpatialReference::StripVertical()
8943
8944
3.68k
{
8945
3.68k
    TAKE_OPTIONAL_LOCK();
8946
8947
3.68k
    d->refreshProjObj();
8948
3.68k
    d->demoteFromBoundCRS();
8949
3.68k
    if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_COMPOUND_CRS)
8950
0
    {
8951
0
        d->undoDemoteFromBoundCRS();
8952
0
        return OGRERR_NONE;
8953
0
    }
8954
3.68k
    auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
8955
3.68k
    if (!horizCRS)
8956
0
    {
8957
0
        d->undoDemoteFromBoundCRS();
8958
0
        return OGRERR_FAILURE;
8959
0
    }
8960
8961
3.68k
    bool reuseExistingBoundCRS = false;
8962
3.68k
    if (d->m_pj_bound_crs_target)
8963
6
    {
8964
6
        auto type = proj_get_type(d->m_pj_bound_crs_target);
8965
6
        reuseExistingBoundCRS = type == PJ_TYPE_GEOCENTRIC_CRS ||
8966
6
                                type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
8967
0
                                type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
8968
6
    }
8969
8970
3.68k
    if (reuseExistingBoundCRS)
8971
6
    {
8972
6
        auto newBoundCRS = proj_crs_create_bound_crs(
8973
6
            d->getPROJContext(), horizCRS, d->m_pj_bound_crs_target,
8974
6
            d->m_pj_bound_crs_co);
8975
6
        proj_destroy(horizCRS);
8976
6
        d->undoDemoteFromBoundCRS();
8977
6
        d->setPjCRS(newBoundCRS);
8978
6
    }
8979
3.67k
    else
8980
3.67k
    {
8981
3.67k
        d->undoDemoteFromBoundCRS();
8982
3.67k
        d->setPjCRS(horizCRS);
8983
3.67k
    }
8984
8985
3.68k
    return OGRERR_NONE;
8986
3.68k
}
8987
8988
/************************************************************************/
8989
/*                          OSRStripVertical()                          */
8990
/************************************************************************/
8991
/**
8992
 * \brief Convert a compound cs into a horizontal CS.
8993
 *
8994
 * This function is the same as the C++ method
8995
 * OGRSpatialReference::StripVertical().
8996
 */
8997
OGRErr OSRStripVertical(OGRSpatialReferenceH hSRS)
8998
8999
0
{
9000
0
    VALIDATE_POINTER1(hSRS, "OSRStripVertical", OGRERR_FAILURE);
9001
9002
0
    return OGRSpatialReference::FromHandle(hSRS)->StripVertical();
9003
0
}
9004
9005
/************************************************************************/
9006
/*                 StripTOWGS84IfKnownDatumAndAllowed()                 */
9007
/************************************************************************/
9008
9009
/**
9010
 * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
9011
 *        and this is allowed by the user.
9012
 *
9013
 * The default behavior is to remove TOWGS84 information if the CRS has a
9014
 * known horizontal datum. This can be disabled by setting the
9015
 * OSR_STRIP_TOWGS84 configuration option to NO.
9016
 *
9017
 * @return true if TOWGS84 has been removed.
9018
 * @since OGR 3.1.0
9019
 */
9020
9021
bool OGRSpatialReference::StripTOWGS84IfKnownDatumAndAllowed()
9022
37.2k
{
9023
37.2k
    if (CPLTestBool(CPLGetConfigOption("OSR_STRIP_TOWGS84", "YES")))
9024
37.2k
    {
9025
37.2k
        if (StripTOWGS84IfKnownDatum())
9026
645
        {
9027
645
            CPLDebug("OSR", "TOWGS84 information has been removed. "
9028
645
                            "It can be kept by setting the OSR_STRIP_TOWGS84 "
9029
645
                            "configuration option to NO");
9030
645
            return true;
9031
645
        }
9032
37.2k
    }
9033
36.5k
    return false;
9034
37.2k
}
9035
9036
/************************************************************************/
9037
/*                      StripTOWGS84IfKnownDatum()                      */
9038
/************************************************************************/
9039
9040
/**
9041
 * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
9042
 *
9043
 * @return true if TOWGS84 has been removed.
9044
 * @since OGR 3.1.0
9045
 */
9046
9047
bool OGRSpatialReference::StripTOWGS84IfKnownDatum()
9048
9049
37.2k
{
9050
37.2k
    TAKE_OPTIONAL_LOCK();
9051
9052
37.2k
    d->refreshProjObj();
9053
37.2k
    if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_BOUND_CRS)
9054
35.7k
    {
9055
35.7k
        return false;
9056
35.7k
    }
9057
1.49k
    auto ctxt = d->getPROJContext();
9058
1.49k
    auto baseCRS = proj_get_source_crs(ctxt, d->m_pj_crs);
9059
1.49k
    if (proj_get_type(baseCRS) == PJ_TYPE_COMPOUND_CRS)
9060
9
    {
9061
9
        proj_destroy(baseCRS);
9062
9
        return false;
9063
9
    }
9064
9065
    // Known base CRS code ? Return base CRS
9066
1.48k
    const char *pszCode = proj_get_id_code(baseCRS, 0);
9067
1.48k
    if (pszCode)
9068
410
    {
9069
410
        d->setPjCRS(baseCRS);
9070
410
        return true;
9071
410
    }
9072
9073
1.07k
    auto datum = proj_crs_get_datum(ctxt, baseCRS);
9074
1.07k
#if PROJ_VERSION_MAJOR > 7 ||                                                  \
9075
1.07k
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
9076
1.07k
    if (datum == nullptr)
9077
0
    {
9078
0
        datum = proj_crs_get_datum_ensemble(ctxt, baseCRS);
9079
0
    }
9080
1.07k
#endif
9081
1.07k
    if (!datum)
9082
0
    {
9083
0
        proj_destroy(baseCRS);
9084
0
        return false;
9085
0
    }
9086
9087
    // Known datum code ? Return base CRS
9088
1.07k
    pszCode = proj_get_id_code(datum, 0);
9089
1.07k
    if (pszCode)
9090
130
    {
9091
130
        proj_destroy(datum);
9092
130
        d->setPjCRS(baseCRS);
9093
130
        return true;
9094
130
    }
9095
9096
943
    const char *name = proj_get_name(datum);
9097
943
    if (EQUAL(name, "unknown"))
9098
0
    {
9099
0
        proj_destroy(datum);
9100
0
        proj_destroy(baseCRS);
9101
0
        return false;
9102
0
    }
9103
943
    const PJ_TYPE type = PJ_TYPE_GEODETIC_REFERENCE_FRAME;
9104
943
    PJ_OBJ_LIST *list =
9105
943
        proj_create_from_name(ctxt, nullptr, name, &type, 1, false, 1, nullptr);
9106
9107
943
    bool knownDatumName = false;
9108
943
    if (list)
9109
943
    {
9110
943
        if (proj_list_get_count(list) == 1)
9111
105
        {
9112
105
            knownDatumName = true;
9113
105
        }
9114
943
        proj_list_destroy(list);
9115
943
    }
9116
9117
943
    proj_destroy(datum);
9118
943
    if (knownDatumName)
9119
105
    {
9120
105
        d->setPjCRS(baseCRS);
9121
105
        return true;
9122
105
    }
9123
838
    proj_destroy(baseCRS);
9124
838
    return false;
9125
943
}
9126
9127
/************************************************************************/
9128
/*                             IsCompound()                             */
9129
/************************************************************************/
9130
9131
/**
9132
 * \brief Check if coordinate system is compound.
9133
 *
9134
 * This method is the same as the C function OSRIsCompound().
9135
 *
9136
 * @return TRUE if this is rooted with a COMPD_CS node.
9137
 */
9138
9139
int OGRSpatialReference::IsCompound() const
9140
9141
108k
{
9142
108k
    TAKE_OPTIONAL_LOCK();
9143
9144
108k
    d->refreshProjObj();
9145
108k
    d->demoteFromBoundCRS();
9146
108k
    bool isCompound = d->m_pjType == PJ_TYPE_COMPOUND_CRS;
9147
108k
    d->undoDemoteFromBoundCRS();
9148
108k
    return isCompound;
9149
108k
}
9150
9151
/************************************************************************/
9152
/*                           OSRIsCompound()                            */
9153
/************************************************************************/
9154
9155
/**
9156
 * \brief Check if the coordinate system is compound.
9157
 *
9158
 * This function is the same as OGRSpatialReference::IsCompound().
9159
 */
9160
int OSRIsCompound(OGRSpatialReferenceH hSRS)
9161
9162
0
{
9163
0
    VALIDATE_POINTER1(hSRS, "OSRIsCompound", 0);
9164
9165
0
    return ToPointer(hSRS)->IsCompound();
9166
0
}
9167
9168
/************************************************************************/
9169
/*                            IsProjected()                             */
9170
/************************************************************************/
9171
9172
/**
9173
 * \brief Check if projected coordinate system.
9174
 *
9175
 * This method is the same as the C function OSRIsProjected().
9176
 *
9177
 * @return TRUE if this contains a PROJCS node indicating a it is a
9178
 * projected coordinate system. Also if it is a CompoundCRS made of a
9179
 * ProjectedCRS
9180
 */
9181
9182
int OGRSpatialReference::IsProjected() const
9183
9184
375k
{
9185
375k
    TAKE_OPTIONAL_LOCK();
9186
9187
375k
    d->refreshProjObj();
9188
375k
    d->demoteFromBoundCRS();
9189
375k
    bool isProjected = d->m_pjType == PJ_TYPE_PROJECTED_CRS;
9190
375k
    if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
9191
13.9k
    {
9192
13.9k
        auto horizCRS =
9193
13.9k
            proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
9194
13.9k
        if (horizCRS)
9195
13.9k
        {
9196
13.9k
            auto horizCRSType = proj_get_type(horizCRS);
9197
13.9k
            isProjected = horizCRSType == PJ_TYPE_PROJECTED_CRS;
9198
13.9k
            if (horizCRSType == PJ_TYPE_BOUND_CRS)
9199
583
            {
9200
583
                auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
9201
583
                if (base)
9202
583
                {
9203
583
                    isProjected = proj_get_type(base) == PJ_TYPE_PROJECTED_CRS;
9204
583
                    proj_destroy(base);
9205
583
                }
9206
583
            }
9207
13.9k
            proj_destroy(horizCRS);
9208
13.9k
        }
9209
13.9k
    }
9210
375k
    d->undoDemoteFromBoundCRS();
9211
375k
    return isProjected;
9212
375k
}
9213
9214
/************************************************************************/
9215
/*                           OSRIsProjected()                           */
9216
/************************************************************************/
9217
/**
9218
 * \brief Check if projected coordinate system.
9219
 *
9220
 * This function is the same as OGRSpatialReference::IsProjected().
9221
 */
9222
int OSRIsProjected(OGRSpatialReferenceH hSRS)
9223
9224
0
{
9225
0
    VALIDATE_POINTER1(hSRS, "OSRIsProjected", 0);
9226
9227
0
    return ToPointer(hSRS)->IsProjected();
9228
0
}
9229
9230
/************************************************************************/
9231
/*                            IsGeocentric()                            */
9232
/************************************************************************/
9233
9234
/**
9235
 * \brief Check if geocentric coordinate system.
9236
 *
9237
 * This method is the same as the C function OSRIsGeocentric().
9238
 *
9239
 * @return TRUE if this contains a GEOCCS node indicating a it is a
9240
 * geocentric coordinate system.
9241
 *
9242
 */
9243
9244
int OGRSpatialReference::IsGeocentric() const
9245
9246
115k
{
9247
115k
    TAKE_OPTIONAL_LOCK();
9248
9249
115k
    d->refreshProjObj();
9250
115k
    d->demoteFromBoundCRS();
9251
115k
    bool isGeocentric = d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS;
9252
115k
    d->undoDemoteFromBoundCRS();
9253
115k
    return isGeocentric;
9254
115k
}
9255
9256
/************************************************************************/
9257
/*                          OSRIsGeocentric()                           */
9258
/************************************************************************/
9259
/**
9260
 * \brief Check if geocentric coordinate system.
9261
 *
9262
 * This function is the same as OGRSpatialReference::IsGeocentric().
9263
 *
9264
 */
9265
int OSRIsGeocentric(OGRSpatialReferenceH hSRS)
9266
9267
0
{
9268
0
    VALIDATE_POINTER1(hSRS, "OSRIsGeocentric", 0);
9269
9270
0
    return ToPointer(hSRS)->IsGeocentric();
9271
0
}
9272
9273
/************************************************************************/
9274
/*                              IsEmpty()                               */
9275
/************************************************************************/
9276
9277
/**
9278
 * \brief Return if the SRS is not set.
9279
 */
9280
9281
bool OGRSpatialReference::IsEmpty() const
9282
455k
{
9283
455k
    TAKE_OPTIONAL_LOCK();
9284
9285
455k
    d->refreshProjObj();
9286
455k
    return d->m_pj_crs == nullptr;
9287
455k
}
9288
9289
/************************************************************************/
9290
/*                            IsGeographic()                            */
9291
/************************************************************************/
9292
9293
/**
9294
 * \brief Check if geographic coordinate system.
9295
 *
9296
 * This method is the same as the C function OSRIsGeographic().
9297
 *
9298
 * @return TRUE if this spatial reference is geographic ... that is the
9299
 * root is a GEOGCS node. Also if it is a CompoundCRS made of a
9300
 * GeographicCRS
9301
 */
9302
9303
int OGRSpatialReference::IsGeographic() const
9304
9305
313k
{
9306
313k
    TAKE_OPTIONAL_LOCK();
9307
9308
313k
    d->refreshProjObj();
9309
313k
    d->demoteFromBoundCRS();
9310
313k
    bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
9311
216k
                  d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
9312
313k
    if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
9313
20.6k
    {
9314
20.6k
        auto horizCRS =
9315
20.6k
            proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
9316
20.6k
        if (horizCRS)
9317
20.6k
        {
9318
20.6k
            auto horizCRSType = proj_get_type(horizCRS);
9319
20.6k
            isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
9320
14.8k
                     horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
9321
20.6k
            if (horizCRSType == PJ_TYPE_BOUND_CRS)
9322
804
            {
9323
804
                auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
9324
804
                if (base)
9325
804
                {
9326
804
                    horizCRSType = proj_get_type(base);
9327
804
                    isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
9328
545
                             horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
9329
804
                    proj_destroy(base);
9330
804
                }
9331
804
            }
9332
20.6k
            proj_destroy(horizCRS);
9333
20.6k
        }
9334
20.6k
    }
9335
313k
    d->undoDemoteFromBoundCRS();
9336
313k
    return isGeog;
9337
313k
}
9338
9339
/************************************************************************/
9340
/*                          OSRIsGeographic()                           */
9341
/************************************************************************/
9342
/**
9343
 * \brief Check if geographic coordinate system.
9344
 *
9345
 * This function is the same as OGRSpatialReference::IsGeographic().
9346
 */
9347
int OSRIsGeographic(OGRSpatialReferenceH hSRS)
9348
9349
253
{
9350
253
    VALIDATE_POINTER1(hSRS, "OSRIsGeographic", 0);
9351
9352
253
    return ToPointer(hSRS)->IsGeographic();
9353
253
}
9354
9355
/************************************************************************/
9356
/*                        IsDerivedGeographic()                         */
9357
/************************************************************************/
9358
9359
/**
9360
 * \brief Check if the CRS is a derived geographic coordinate system.
9361
 * (for example a rotated long/lat grid)
9362
 *
9363
 * This method is the same as the C function OSRIsDerivedGeographic().
9364
 *
9365
 * @since GDAL 3.1.0 and PROJ 6.3.0
9366
 */
9367
9368
int OGRSpatialReference::IsDerivedGeographic() const
9369
9370
89.3k
{
9371
89.3k
    TAKE_OPTIONAL_LOCK();
9372
9373
89.3k
    d->refreshProjObj();
9374
89.3k
    d->demoteFromBoundCRS();
9375
89.3k
    const bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
9376
66.3k
                        d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
9377
89.3k
    const bool isDerivedGeographic =
9378
89.3k
        isGeog && proj_is_derived_crs(d->getPROJContext(), d->m_pj_crs);
9379
89.3k
    d->undoDemoteFromBoundCRS();
9380
89.3k
    return isDerivedGeographic ? TRUE : FALSE;
9381
89.3k
}
9382
9383
/************************************************************************/
9384
/*                       OSRIsDerivedGeographic()                       */
9385
/************************************************************************/
9386
/**
9387
 * \brief Check if the CRS is a derived geographic coordinate system.
9388
 * (for example a rotated long/lat grid)
9389
 *
9390
 * This function is the same as OGRSpatialReference::IsDerivedGeographic().
9391
 */
9392
int OSRIsDerivedGeographic(OGRSpatialReferenceH hSRS)
9393
9394
0
{
9395
0
    VALIDATE_POINTER1(hSRS, "OSRIsDerivedGeographic", 0);
9396
9397
0
    return ToPointer(hSRS)->IsDerivedGeographic();
9398
0
}
9399
9400
/************************************************************************/
9401
/*                         IsDerivedProjected()                         */
9402
/************************************************************************/
9403
9404
/**
9405
 * \brief Check if the CRS is a derived projected coordinate system.
9406
 *
9407
 * This method is the same as the C function OSRIsDerivedGeographic().
9408
 *
9409
 * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
9410
 */
9411
9412
int OGRSpatialReference::IsDerivedProjected() const
9413
9414
0
{
9415
0
#if PROJ_AT_LEAST_VERSION(9, 2, 0)
9416
0
    TAKE_OPTIONAL_LOCK();
9417
0
    d->refreshProjObj();
9418
0
    d->demoteFromBoundCRS();
9419
0
    const bool isDerivedProjected =
9420
0
        d->m_pjType == PJ_TYPE_DERIVED_PROJECTED_CRS;
9421
0
    d->undoDemoteFromBoundCRS();
9422
0
    return isDerivedProjected ? TRUE : FALSE;
9423
#else
9424
    return FALSE;
9425
#endif
9426
0
}
9427
9428
/************************************************************************/
9429
/*                       OSRIsDerivedProjected()                        */
9430
/************************************************************************/
9431
/**
9432
 * \brief Check if the CRS is a derived projected coordinate system.
9433
 *
9434
 * This function is the same as OGRSpatialReference::IsDerivedProjected().
9435
 *
9436
 * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
9437
 */
9438
int OSRIsDerivedProjected(OGRSpatialReferenceH hSRS)
9439
9440
0
{
9441
0
    VALIDATE_POINTER1(hSRS, "OSRIsDerivedProjected", 0);
9442
9443
0
    return ToPointer(hSRS)->IsDerivedProjected();
9444
0
}
9445
9446
/************************************************************************/
9447
/*                              IsLocal()                               */
9448
/************************************************************************/
9449
9450
/**
9451
 * \brief Check if local coordinate system.
9452
 *
9453
 * This method is the same as the C function OSRIsLocal().
9454
 *
9455
 * @return TRUE if this spatial reference is local ... that is the
9456
 * root is a LOCAL_CS node.
9457
 */
9458
9459
int OGRSpatialReference::IsLocal() const
9460
9461
67.4k
{
9462
67.4k
    TAKE_OPTIONAL_LOCK();
9463
67.4k
    d->refreshProjObj();
9464
67.4k
    return d->m_pjType == PJ_TYPE_ENGINEERING_CRS;
9465
67.4k
}
9466
9467
/************************************************************************/
9468
/*                             OSRIsLocal()                             */
9469
/************************************************************************/
9470
/**
9471
 * \brief Check if local coordinate system.
9472
 *
9473
 * This function is the same as OGRSpatialReference::IsLocal().
9474
 */
9475
int OSRIsLocal(OGRSpatialReferenceH hSRS)
9476
9477
0
{
9478
0
    VALIDATE_POINTER1(hSRS, "OSRIsLocal", 0);
9479
9480
0
    return ToPointer(hSRS)->IsLocal();
9481
0
}
9482
9483
/************************************************************************/
9484
/*                             IsVertical()                             */
9485
/************************************************************************/
9486
9487
/**
9488
 * \brief Check if vertical coordinate system.
9489
 *
9490
 * This method is the same as the C function OSRIsVertical().
9491
 *
9492
 * @return TRUE if this contains a VERT_CS node indicating a it is a
9493
 * vertical coordinate system. Also if it is a CompoundCRS made of a
9494
 * VerticalCRS
9495
 *
9496
 */
9497
9498
int OGRSpatialReference::IsVertical() const
9499
9500
16.5k
{
9501
16.5k
    TAKE_OPTIONAL_LOCK();
9502
16.5k
    d->refreshProjObj();
9503
16.5k
    d->demoteFromBoundCRS();
9504
16.5k
    bool isVertical = d->m_pjType == PJ_TYPE_VERTICAL_CRS;
9505
16.5k
    if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
9506
252
    {
9507
252
        auto vertCRS =
9508
252
            proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
9509
252
        if (vertCRS)
9510
252
        {
9511
252
            const auto vertCRSType = proj_get_type(vertCRS);
9512
252
            isVertical = vertCRSType == PJ_TYPE_VERTICAL_CRS;
9513
252
            if (vertCRSType == PJ_TYPE_BOUND_CRS)
9514
0
            {
9515
0
                auto base = proj_get_source_crs(d->getPROJContext(), vertCRS);
9516
0
                if (base)
9517
0
                {
9518
0
                    isVertical = proj_get_type(base) == PJ_TYPE_VERTICAL_CRS;
9519
0
                    proj_destroy(base);
9520
0
                }
9521
0
            }
9522
252
            proj_destroy(vertCRS);
9523
252
        }
9524
252
    }
9525
16.5k
    d->undoDemoteFromBoundCRS();
9526
16.5k
    return isVertical;
9527
16.5k
}
9528
9529
/************************************************************************/
9530
/*                           OSRIsVertical()                            */
9531
/************************************************************************/
9532
/**
9533
 * \brief Check if vertical coordinate system.
9534
 *
9535
 * This function is the same as OGRSpatialReference::IsVertical().
9536
 *
9537
 */
9538
int OSRIsVertical(OGRSpatialReferenceH hSRS)
9539
9540
0
{
9541
0
    VALIDATE_POINTER1(hSRS, "OSRIsVertical", 0);
9542
9543
0
    return ToPointer(hSRS)->IsVertical();
9544
0
}
9545
9546
/************************************************************************/
9547
/*                             IsDynamic()                              */
9548
/************************************************************************/
9549
9550
/**
9551
 * \brief Check if a CRS is a dynamic CRS.
9552
 *
9553
 * A dynamic CRS relies on a dynamic datum, that is a datum that is not
9554
 * plate-fixed.
9555
 *
9556
 * This method is the same as the C function OSRIsDynamic().
9557
 *
9558
 * @return true if the CRS is dynamic
9559
 *
9560
 * @since OGR 3.4.0
9561
 *
9562
 * @see HasPointMotionOperation()
9563
 */
9564
9565
bool OGRSpatialReference::IsDynamic() const
9566
9567
78.2k
{
9568
78.2k
    TAKE_OPTIONAL_LOCK();
9569
78.2k
    bool isDynamic = false;
9570
78.2k
    d->refreshProjObj();
9571
78.2k
    d->demoteFromBoundCRS();
9572
78.2k
    auto ctxt = d->getPROJContext();
9573
78.2k
    PJ *horiz = nullptr;
9574
78.2k
    if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
9575
712
    {
9576
712
        horiz = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
9577
712
    }
9578
77.5k
    else if (d->m_pj_crs)
9579
76.4k
    {
9580
76.4k
        horiz = proj_clone(ctxt, d->m_pj_crs);
9581
76.4k
    }
9582
78.2k
    if (horiz && proj_get_type(horiz) == PJ_TYPE_BOUND_CRS)
9583
0
    {
9584
0
        auto baseCRS = proj_get_source_crs(ctxt, horiz);
9585
0
        if (baseCRS)
9586
0
        {
9587
0
            proj_destroy(horiz);
9588
0
            horiz = baseCRS;
9589
0
        }
9590
0
    }
9591
78.2k
    auto datum = horiz ? proj_crs_get_datum(ctxt, horiz) : nullptr;
9592
78.2k
    if (datum)
9593
72.1k
    {
9594
72.1k
        const auto type = proj_get_type(datum);
9595
72.1k
        isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
9596
67.3k
                    type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
9597
72.1k
        if (!isDynamic)
9598
67.3k
        {
9599
67.3k
            const char *auth_name = proj_get_id_auth_name(datum, 0);
9600
67.3k
            const char *code = proj_get_id_code(datum, 0);
9601
67.3k
            if (auth_name && code && EQUAL(auth_name, "EPSG") &&
9602
14.3k
                EQUAL(code, "6326"))
9603
1.94k
            {
9604
1.94k
                isDynamic = true;
9605
1.94k
            }
9606
67.3k
        }
9607
72.1k
        proj_destroy(datum);
9608
72.1k
    }
9609
6.14k
#if PROJ_VERSION_MAJOR > 7 ||                                                  \
9610
6.14k
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
9611
6.14k
    else
9612
6.14k
    {
9613
6.14k
        auto ensemble =
9614
6.14k
            horiz ? proj_crs_get_datum_ensemble(ctxt, horiz) : nullptr;
9615
6.14k
        if (ensemble)
9616
4.75k
        {
9617
4.75k
            auto member = proj_datum_ensemble_get_member(ctxt, ensemble, 0);
9618
4.75k
            if (member)
9619
4.75k
            {
9620
4.75k
                const auto type = proj_get_type(member);
9621
4.75k
                isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
9622
10
                            type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
9623
4.75k
                proj_destroy(member);
9624
4.75k
            }
9625
4.75k
            proj_destroy(ensemble);
9626
4.75k
        }
9627
6.14k
    }
9628
78.2k
#endif
9629
78.2k
    proj_destroy(horiz);
9630
78.2k
    d->undoDemoteFromBoundCRS();
9631
78.2k
    return isDynamic;
9632
78.2k
}
9633
9634
/************************************************************************/
9635
/*                            OSRIsDynamic()                            */
9636
/************************************************************************/
9637
/**
9638
 * \brief Check if a CRS is a dynamic CRS.
9639
 *
9640
 * A dynamic CRS relies on a dynamic datum, that is a datum that is not
9641
 * plate-fixed.
9642
 *
9643
 * This function is the same as OGRSpatialReference::IsDynamic().
9644
 *
9645
 * @since OGR 3.4.0
9646
 */
9647
int OSRIsDynamic(OGRSpatialReferenceH hSRS)
9648
9649
0
{
9650
0
    VALIDATE_POINTER1(hSRS, "OSRIsDynamic", 0);
9651
9652
0
    return ToPointer(hSRS)->IsDynamic();
9653
0
}
9654
9655
/************************************************************************/
9656
/*                      HasPointMotionOperation()                       */
9657
/************************************************************************/
9658
9659
/**
9660
 * \brief Check if a CRS has at least an associated point motion operation.
9661
 *
9662
 * Some CRS are not formally declared as dynamic, but may behave as such
9663
 * in practice due to the presence of point motion operation, to perform
9664
 * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
9665
 *
9666
 * @return true if the CRS has at least an associated point motion operation.
9667
 *
9668
 * @since OGR 3.8.0 and PROJ 9.4.0
9669
 *
9670
 * @see IsDynamic()
9671
 */
9672
9673
bool OGRSpatialReference::HasPointMotionOperation() const
9674
9675
0
{
9676
0
#if PROJ_VERSION_MAJOR > 9 ||                                                  \
9677
0
    (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 4)
9678
0
    TAKE_OPTIONAL_LOCK();
9679
0
    d->refreshProjObj();
9680
0
    d->demoteFromBoundCRS();
9681
0
    auto ctxt = d->getPROJContext();
9682
0
    auto res =
9683
0
        CPL_TO_BOOL(proj_crs_has_point_motion_operation(ctxt, d->m_pj_crs));
9684
0
    d->undoDemoteFromBoundCRS();
9685
0
    return res;
9686
#else
9687
    return false;
9688
#endif
9689
0
}
9690
9691
/************************************************************************/
9692
/*                     OSRHasPointMotionOperation()                     */
9693
/************************************************************************/
9694
9695
/**
9696
 * \brief Check if a CRS has at least an associated point motion operation.
9697
 *
9698
 * Some CRS are not formally declared as dynamic, but may behave as such
9699
 * in practice due to the presence of point motion operation, to perform
9700
 * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
9701
 *
9702
 * This function is the same as OGRSpatialReference::HasPointMotionOperation().
9703
 *
9704
 * @since OGR 3.8.0 and PROJ 9.4.0
9705
 */
9706
int OSRHasPointMotionOperation(OGRSpatialReferenceH hSRS)
9707
9708
0
{
9709
0
    VALIDATE_POINTER1(hSRS, "OSRHasPointMotionOperation", 0);
9710
9711
0
    return ToPointer(hSRS)->HasPointMotionOperation();
9712
0
}
9713
9714
/************************************************************************/
9715
/*                            CloneGeogCS()                             */
9716
/************************************************************************/
9717
9718
/**
9719
 * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
9720
 * object.
9721
 *
9722
 * @return a new SRS, which becomes the responsibility of the caller.
9723
 */
9724
OGRSpatialReference *OGRSpatialReference::CloneGeogCS() const
9725
9726
5.62k
{
9727
5.62k
    TAKE_OPTIONAL_LOCK();
9728
5.62k
    d->refreshProjObj();
9729
5.62k
    if (d->m_pj_crs)
9730
5.56k
    {
9731
5.56k
        if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
9732
2
            return nullptr;
9733
9734
5.56k
        auto geodCRS =
9735
5.56k
            proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
9736
5.56k
        if (geodCRS)
9737
5.52k
        {
9738
5.52k
            OGRSpatialReference *poNewSRS = new OGRSpatialReference();
9739
5.52k
            if (d->m_pjType == PJ_TYPE_BOUND_CRS)
9740
521
            {
9741
521
                PJ *hub_crs =
9742
521
                    proj_get_target_crs(d->getPROJContext(), d->m_pj_crs);
9743
521
                PJ *co = proj_crs_get_coordoperation(d->getPROJContext(),
9744
521
                                                     d->m_pj_crs);
9745
521
                auto temp = proj_crs_create_bound_crs(d->getPROJContext(),
9746
521
                                                      geodCRS, hub_crs, co);
9747
521
                proj_destroy(geodCRS);
9748
521
                geodCRS = temp;
9749
521
                proj_destroy(hub_crs);
9750
521
                proj_destroy(co);
9751
521
            }
9752
9753
            /* --------------------------------------------------------------------
9754
             */
9755
            /*      We have to reconstruct the GEOGCS node for geocentric */
9756
            /*      coordinate systems. */
9757
            /* --------------------------------------------------------------------
9758
             */
9759
5.52k
            if (proj_get_type(geodCRS) == PJ_TYPE_GEOCENTRIC_CRS)
9760
1
            {
9761
1
                auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
9762
1
#if PROJ_VERSION_MAJOR > 7 ||                                                  \
9763
1
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
9764
1
                if (datum == nullptr)
9765
0
                {
9766
0
                    datum = proj_crs_get_datum_ensemble(d->getPROJContext(),
9767
0
                                                        geodCRS);
9768
0
                }
9769
1
#endif
9770
1
                if (datum)
9771
1
                {
9772
1
                    auto cs = proj_create_ellipsoidal_2D_cs(
9773
1
                        d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE,
9774
1
                        nullptr, 0);
9775
1
                    auto temp = proj_create_geographic_crs_from_datum(
9776
1
                        d->getPROJContext(), "unnamed", datum, cs);
9777
1
                    proj_destroy(datum);
9778
1
                    proj_destroy(cs);
9779
1
                    proj_destroy(geodCRS);
9780
1
                    geodCRS = temp;
9781
1
                }
9782
1
            }
9783
9784
5.52k
            poNewSRS->d->setPjCRS(geodCRS);
9785
5.52k
            if (d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
9786
4.35k
                poNewSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
9787
5.52k
            return poNewSRS;
9788
5.52k
        }
9789
5.56k
    }
9790
95
    return nullptr;
9791
5.62k
}
9792
9793
/************************************************************************/
9794
/*                           OSRCloneGeogCS()                           */
9795
/************************************************************************/
9796
/**
9797
 * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
9798
 * object.
9799
 *
9800
 * This function is the same as OGRSpatialReference::CloneGeogCS().
9801
 */
9802
OGRSpatialReferenceH CPL_STDCALL OSRCloneGeogCS(OGRSpatialReferenceH hSource)
9803
9804
267
{
9805
267
    VALIDATE_POINTER1(hSource, "OSRCloneGeogCS", nullptr);
9806
9807
267
    return ToHandle(ToPointer(hSource)->CloneGeogCS());
9808
267
}
9809
9810
/************************************************************************/
9811
/*                            IsSameGeogCS()                            */
9812
/************************************************************************/
9813
9814
/**
9815
 * \brief Do the GeogCS'es match?
9816
 *
9817
 * This method is the same as the C function OSRIsSameGeogCS().
9818
 *
9819
 * @param poOther the SRS being compared against.
9820
 *
9821
 * @return TRUE if they are the same or FALSE otherwise.
9822
 */
9823
9824
int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther) const
9825
9826
8.48k
{
9827
8.48k
    return IsSameGeogCS(poOther, nullptr);
9828
8.48k
}
9829
9830
/**
9831
 * \brief Do the GeogCS'es match?
9832
 *
9833
 * This method is the same as the C function OSRIsSameGeogCS().
9834
 *
9835
 * @param poOther the SRS being compared against.
9836
 * @param papszOptions options. ignored
9837
 *
9838
 * @return TRUE if they are the same or FALSE otherwise.
9839
 */
9840
9841
int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther,
9842
                                      const char *const *papszOptions) const
9843
9844
8.48k
{
9845
8.48k
    TAKE_OPTIONAL_LOCK();
9846
9847
8.48k
    CPL_IGNORE_RET_VAL(papszOptions);
9848
9849
8.48k
    d->refreshProjObj();
9850
8.48k
    poOther->d->refreshProjObj();
9851
8.48k
    if (!d->m_pj_crs || !poOther->d->m_pj_crs)
9852
85
        return FALSE;
9853
8.39k
    if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
9854
8.39k
        d->m_pjType == PJ_TYPE_VERTICAL_CRS ||
9855
8.39k
        poOther->d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
9856
8.39k
        poOther->d->m_pjType == PJ_TYPE_VERTICAL_CRS)
9857
0
    {
9858
0
        return FALSE;
9859
0
    }
9860
9861
8.39k
    auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
9862
8.39k
    auto otherGeodCRS =
9863
8.39k
        proj_crs_get_geodetic_crs(d->getPROJContext(), poOther->d->m_pj_crs);
9864
8.39k
    if (!geodCRS || !otherGeodCRS)
9865
0
    {
9866
0
        proj_destroy(geodCRS);
9867
0
        proj_destroy(otherGeodCRS);
9868
0
        return FALSE;
9869
0
    }
9870
9871
8.39k
    int ret = proj_is_equivalent_to(
9872
8.39k
        geodCRS, otherGeodCRS, PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS);
9873
9874
8.39k
    proj_destroy(geodCRS);
9875
8.39k
    proj_destroy(otherGeodCRS);
9876
8.39k
    return ret;
9877
8.39k
}
9878
9879
/************************************************************************/
9880
/*                          OSRIsSameGeogCS()                           */
9881
/************************************************************************/
9882
9883
/**
9884
 * \brief Do the GeogCS'es match?
9885
 *
9886
 * This function is the same as OGRSpatialReference::IsSameGeogCS().
9887
 */
9888
int OSRIsSameGeogCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
9889
9890
0
{
9891
0
    VALIDATE_POINTER1(hSRS1, "OSRIsSameGeogCS", 0);
9892
0
    VALIDATE_POINTER1(hSRS2, "OSRIsSameGeogCS", 0);
9893
9894
0
    return ToPointer(hSRS1)->IsSameGeogCS(ToPointer(hSRS2));
9895
0
}
9896
9897
/************************************************************************/
9898
/*                            IsSameVertCS()                            */
9899
/************************************************************************/
9900
9901
/**
9902
 * \brief Do the VertCS'es match?
9903
 *
9904
 * This method is the same as the C function OSRIsSameVertCS().
9905
 *
9906
 * @param poOther the SRS being compared against.
9907
 *
9908
 * @return TRUE if they are the same or FALSE otherwise.
9909
 */
9910
9911
int OGRSpatialReference::IsSameVertCS(const OGRSpatialReference *poOther) const
9912
9913
0
{
9914
0
    TAKE_OPTIONAL_LOCK();
9915
9916
    /* -------------------------------------------------------------------- */
9917
    /*      Does the datum name match?                                      */
9918
    /* -------------------------------------------------------------------- */
9919
0
    const char *pszThisValue = this->GetAttrValue("VERT_DATUM");
9920
0
    const char *pszOtherValue = poOther->GetAttrValue("VERT_DATUM");
9921
9922
0
    if (pszThisValue == nullptr || pszOtherValue == nullptr ||
9923
0
        !EQUAL(pszThisValue, pszOtherValue))
9924
0
        return FALSE;
9925
9926
    /* -------------------------------------------------------------------- */
9927
    /*      Do the units match?                                             */
9928
    /* -------------------------------------------------------------------- */
9929
0
    pszThisValue = this->GetAttrValue("VERT_CS|UNIT", 1);
9930
0
    if (pszThisValue == nullptr)
9931
0
        pszThisValue = "1.0";
9932
9933
0
    pszOtherValue = poOther->GetAttrValue("VERT_CS|UNIT", 1);
9934
0
    if (pszOtherValue == nullptr)
9935
0
        pszOtherValue = "1.0";
9936
9937
0
    if (std::abs(CPLAtof(pszOtherValue) - CPLAtof(pszThisValue)) > 0.00000001)
9938
0
        return FALSE;
9939
9940
0
    return TRUE;
9941
0
}
9942
9943
/************************************************************************/
9944
/*                          OSRIsSameVertCS()                           */
9945
/************************************************************************/
9946
9947
/**
9948
 * \brief Do the VertCS'es match?
9949
 *
9950
 * This function is the same as OGRSpatialReference::IsSameVertCS().
9951
 */
9952
int OSRIsSameVertCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
9953
9954
0
{
9955
0
    VALIDATE_POINTER1(hSRS1, "OSRIsSameVertCS", 0);
9956
0
    VALIDATE_POINTER1(hSRS2, "OSRIsSameVertCS", 0);
9957
9958
0
    return ToPointer(hSRS1)->IsSameVertCS(ToPointer(hSRS2));
9959
0
}
9960
9961
/************************************************************************/
9962
/*                               IsSame()                               */
9963
/************************************************************************/
9964
9965
/**
9966
 * \brief Do these two spatial references describe the same system ?
9967
 *
9968
 * @param poOtherSRS the SRS being compared to.
9969
 *
9970
 * @return TRUE if equivalent or FALSE otherwise.
9971
 */
9972
9973
int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS) const
9974
9975
27.7k
{
9976
27.7k
    return IsSame(poOtherSRS, nullptr);
9977
27.7k
}
9978
9979
/**
9980
 * \brief Do these two spatial references describe the same system ?
9981
 *
9982
 * This also takes into account the data axis to CRS axis mapping by default
9983
 *
9984
 * @param poOtherSRS the SRS being compared to.
9985
 * @param papszOptions options. NULL or NULL terminated list of options.
9986
 * Currently supported options are:
9987
 * <ul>
9988
 * <li>IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES/NO. Defaults to NO</li>
9989
 * <li>CRITERION=STRICT/EQUIVALENT/EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.
9990
 *     Defaults to EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.</li>
9991
 * <li>IGNORE_COORDINATE_EPOCH=YES/NO. Defaults to NO</li>
9992
 * </ul>
9993
 *
9994
 * @return TRUE if equivalent or FALSE otherwise.
9995
 */
9996
9997
int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS,
9998
                                const char *const *papszOptions) const
9999
10000
83.4k
{
10001
83.4k
    TAKE_OPTIONAL_LOCK();
10002
10003
83.4k
    d->refreshProjObj();
10004
83.4k
    poOtherSRS->d->refreshProjObj();
10005
83.4k
    if (!d->m_pj_crs || !poOtherSRS->d->m_pj_crs)
10006
10.8k
        return d->m_pj_crs == poOtherSRS->d->m_pj_crs;
10007
72.5k
    if (!CPLTestBool(CSLFetchNameValueDef(
10008
72.5k
            papszOptions, "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING", "NO")))
10009
66.5k
    {
10010
66.5k
        if (d->m_axisMapping != poOtherSRS->d->m_axisMapping)
10011
32.0k
            return false;
10012
66.5k
    }
10013
10014
40.5k
    if (!CPLTestBool(CSLFetchNameValueDef(papszOptions,
10015
40.5k
                                          "IGNORE_COORDINATE_EPOCH", "NO")))
10016
40.2k
    {
10017
40.2k
        if (d->m_coordinateEpoch != poOtherSRS->d->m_coordinateEpoch)
10018
0
            return false;
10019
40.2k
    }
10020
10021
40.5k
    bool reboundSelf = false;
10022
40.5k
    bool reboundOther = false;
10023
40.5k
    if (d->m_pjType == PJ_TYPE_BOUND_CRS &&
10024
12
        poOtherSRS->d->m_pjType != PJ_TYPE_BOUND_CRS)
10025
0
    {
10026
0
        d->demoteFromBoundCRS();
10027
0
        reboundSelf = true;
10028
0
    }
10029
40.5k
    else if (d->m_pjType != PJ_TYPE_BOUND_CRS &&
10030
40.5k
             poOtherSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
10031
393
    {
10032
393
        poOtherSRS->d->demoteFromBoundCRS();
10033
393
        reboundOther = true;
10034
393
    }
10035
10036
40.5k
    PJ_COMPARISON_CRITERION criterion =
10037
40.5k
        PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS;
10038
40.5k
    const char *pszCriterion = CSLFetchNameValueDef(
10039
40.5k
        papszOptions, "CRITERION", "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS");
10040
40.5k
    if (EQUAL(pszCriterion, "STRICT"))
10041
0
        criterion = PJ_COMP_STRICT;
10042
40.5k
    else if (EQUAL(pszCriterion, "EQUIVALENT"))
10043
14.8k
        criterion = PJ_COMP_EQUIVALENT;
10044
25.6k
    else if (!EQUAL(pszCriterion, "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS"))
10045
0
    {
10046
0
        CPLError(CE_Warning, CPLE_NotSupported,
10047
0
                 "Unsupported value for CRITERION: %s", pszCriterion);
10048
0
    }
10049
40.5k
    int ret =
10050
40.5k
        proj_is_equivalent_to(d->m_pj_crs, poOtherSRS->d->m_pj_crs, criterion);
10051
40.5k
    if (reboundSelf)
10052
0
        d->undoDemoteFromBoundCRS();
10053
40.5k
    if (reboundOther)
10054
393
        poOtherSRS->d->undoDemoteFromBoundCRS();
10055
10056
40.5k
    return ret;
10057
40.5k
}
10058
10059
/************************************************************************/
10060
/*                             OSRIsSame()                              */
10061
/************************************************************************/
10062
10063
/**
10064
 * \brief Do these two spatial references describe the same system ?
10065
 *
10066
 * This function is the same as OGRSpatialReference::IsSame().
10067
 */
10068
int OSRIsSame(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
10069
10070
272
{
10071
272
    VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
10072
272
    VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
10073
10074
272
    return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2));
10075
272
}
10076
10077
/************************************************************************/
10078
/*                            OSRIsSameEx()                             */
10079
/************************************************************************/
10080
10081
/**
10082
 * \brief Do these two spatial references describe the same system ?
10083
 *
10084
 * This function is the same as OGRSpatialReference::IsSame().
10085
 */
10086
int OSRIsSameEx(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2,
10087
                const char *const *papszOptions)
10088
0
{
10089
0
    VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
10090
0
    VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
10091
10092
0
    return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2), papszOptions);
10093
0
}
10094
10095
/************************************************************************/
10096
/*                      convertToOtherProjection()                      */
10097
/************************************************************************/
10098
10099
/**
10100
 * \brief Convert to another equivalent projection
10101
 *
10102
 * Currently implemented:
10103
 * <ul>
10104
 * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
10105
 * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
10106
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
10107
 * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
10108
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
10109
 * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
10110
 * </ul>
10111
 *
10112
 * @param pszTargetProjection target projection.
10113
 * @param papszOptions lists of options. None supported currently.
10114
 * @return a new SRS, or NULL in case of error.
10115
 *
10116
 */
10117
OGRSpatialReference *OGRSpatialReference::convertToOtherProjection(
10118
    const char *pszTargetProjection,
10119
    CPL_UNUSED const char *const *papszOptions) const
10120
1.42k
{
10121
1.42k
    TAKE_OPTIONAL_LOCK();
10122
10123
1.42k
    if (pszTargetProjection == nullptr)
10124
0
        return nullptr;
10125
1.42k
    int new_code;
10126
1.42k
    if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_1SP))
10127
0
    {
10128
0
        new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A;
10129
0
    }
10130
1.42k
    else if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_2SP))
10131
0
    {
10132
0
        new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B;
10133
0
    }
10134
1.42k
    else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
10135
1.42k
    {
10136
1.42k
        new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP;
10137
1.42k
    }
10138
0
    else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
10139
0
    {
10140
0
        new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP;
10141
0
    }
10142
0
    else
10143
0
    {
10144
0
        return nullptr;
10145
0
    }
10146
10147
1.42k
    d->refreshProjObj();
10148
1.42k
    d->demoteFromBoundCRS();
10149
1.42k
    OGRSpatialReference *poNewSRS = nullptr;
10150
1.42k
    if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
10151
1.41k
    {
10152
1.41k
        auto conv =
10153
1.41k
            proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
10154
1.41k
        auto new_conv = proj_convert_conversion_to_other_method(
10155
1.41k
            d->getPROJContext(), conv, new_code, nullptr);
10156
1.41k
        proj_destroy(conv);
10157
1.41k
        if (new_conv)
10158
1.05k
        {
10159
1.05k
            auto geodCRS =
10160
1.05k
                proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
10161
1.05k
            auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
10162
1.05k
                                                     d->m_pj_crs);
10163
1.05k
            if (geodCRS && cs)
10164
1.05k
            {
10165
1.05k
                auto new_proj_crs = proj_create_projected_crs(
10166
1.05k
                    d->getPROJContext(), proj_get_name(d->m_pj_crs), geodCRS,
10167
1.05k
                    new_conv, cs);
10168
1.05k
                proj_destroy(new_conv);
10169
1.05k
                if (new_proj_crs)
10170
1.05k
                {
10171
1.05k
                    poNewSRS = new OGRSpatialReference();
10172
10173
1.05k
                    if (d->m_pj_bound_crs_target && d->m_pj_bound_crs_co)
10174
1
                    {
10175
1
                        auto boundCRS = proj_crs_create_bound_crs(
10176
1
                            d->getPROJContext(), new_proj_crs,
10177
1
                            d->m_pj_bound_crs_target, d->m_pj_bound_crs_co);
10178
1
                        if (boundCRS)
10179
1
                        {
10180
1
                            proj_destroy(new_proj_crs);
10181
1
                            new_proj_crs = boundCRS;
10182
1
                        }
10183
1
                    }
10184
10185
1.05k
                    poNewSRS->d->setPjCRS(new_proj_crs);
10186
1.05k
                }
10187
1.05k
            }
10188
1.05k
            proj_destroy(geodCRS);
10189
1.05k
            proj_destroy(cs);
10190
1.05k
        }
10191
1.41k
    }
10192
1.42k
    d->undoDemoteFromBoundCRS();
10193
1.42k
    return poNewSRS;
10194
1.42k
}
10195
10196
/************************************************************************/
10197
/*                    OSRConvertToOtherProjection()                     */
10198
/************************************************************************/
10199
10200
/**
10201
 * \brief Convert to another equivalent projection
10202
 *
10203
 * Currently implemented:
10204
 * <ul>
10205
 * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
10206
 * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
10207
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
10208
 * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
10209
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
10210
 * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
10211
 * </ul>
10212
 *
10213
 * @param hSRS source SRS
10214
 * @param pszTargetProjection target projection.
10215
 * @param papszOptions lists of options. None supported currently.
10216
 * @return a new SRS, or NULL in case of error.
10217
 *
10218
 */
10219
OGRSpatialReferenceH
10220
OSRConvertToOtherProjection(OGRSpatialReferenceH hSRS,
10221
                            const char *pszTargetProjection,
10222
                            const char *const *papszOptions)
10223
0
{
10224
0
    VALIDATE_POINTER1(hSRS, "OSRConvertToOtherProjection", nullptr);
10225
0
    return ToHandle(ToPointer(hSRS)->convertToOtherProjection(
10226
0
        pszTargetProjection, papszOptions));
10227
0
}
10228
10229
/************************************************************************/
10230
/*                           OSRFindMatches()                           */
10231
/************************************************************************/
10232
10233
/**
10234
 * \brief Try to identify a match between the passed SRS and a related SRS
10235
 * in a catalog.
10236
 *
10237
 * Matching may be partial, or may fail.
10238
 * Returned entries will be sorted by decreasing match confidence (first
10239
 * entry has the highest match confidence).
10240
 *
10241
 * The exact way matching is done may change in future versions. Starting with
10242
 * GDAL 3.0, it relies on PROJ' proj_identify() function.
10243
 *
10244
 * This function is the same as OGRSpatialReference::FindMatches().
10245
 *
10246
 * @param hSRS SRS to match
10247
 * @param papszOptions NULL terminated list of options or NULL
10248
 * @param pnEntries Output parameter. Number of values in the returned array.
10249
 * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
10250
 * will be allocated to an array of *pnEntries whose values between 0 and 100
10251
 * indicate the confidence in the match. 100 is the highest confidence level.
10252
 * The array must be freed with CPLFree().
10253
 *
10254
 * @return an array of SRS that match the passed SRS, or NULL. Must be freed
10255
 * with OSRFreeSRSArray()
10256
 *
10257
 */
10258
OGRSpatialReferenceH *OSRFindMatches(OGRSpatialReferenceH hSRS,
10259
                                     CSLConstList papszOptions, int *pnEntries,
10260
                                     int **ppanMatchConfidence)
10261
0
{
10262
0
    if (pnEntries)
10263
0
        *pnEntries = 0;
10264
0
    if (ppanMatchConfidence)
10265
0
        *ppanMatchConfidence = nullptr;
10266
0
    VALIDATE_POINTER1(hSRS, "OSRFindMatches", nullptr);
10267
10268
0
    OGRSpatialReference *poSRS = ToPointer(hSRS);
10269
0
    return poSRS->FindMatches(papszOptions, pnEntries, ppanMatchConfidence);
10270
0
}
10271
10272
/************************************************************************/
10273
/*                          OSRFreeSRSArray()                           */
10274
/************************************************************************/
10275
10276
/**
10277
 * \brief Free return of OSRIdentifyMatches()
10278
 *
10279
 * @param pahSRS array of SRS (must be NULL terminated)
10280
 */
10281
void OSRFreeSRSArray(OGRSpatialReferenceH *pahSRS)
10282
24.7k
{
10283
24.7k
    if (pahSRS != nullptr)
10284
23.1k
    {
10285
578k
        for (int i = 0; pahSRS[i] != nullptr; ++i)
10286
555k
        {
10287
555k
            OSRRelease(pahSRS[i]);
10288
555k
        }
10289
23.1k
        CPLFree(pahSRS);
10290
23.1k
    }
10291
24.7k
}
10292
10293
/************************************************************************/
10294
/*                           FindBestMatch()                            */
10295
/************************************************************************/
10296
10297
/**
10298
 * \brief Try to identify the best match between the passed SRS and a related
10299
 * SRS in a catalog.
10300
 *
10301
 * This is a wrapper over OGRSpatialReference::FindMatches() that takes care
10302
 * of filtering its output.
10303
 * Only matches whose confidence is greater or equal to nMinimumMatchConfidence
10304
 * will be considered. If there is a single match, it is returned.
10305
 * If there are several matches, only return the one under the
10306
 * pszPreferredAuthority, if there is a single one under that authority.
10307
 *
10308
 * @param nMinimumMatchConfidence Minimum match confidence (value between 0 and
10309
 * 100). If set to 0, 90 is used.
10310
 * @param pszPreferredAuthority Preferred CRS authority. If set to nullptr,
10311
 * "EPSG" is used.
10312
 * @param papszOptions NULL terminated list of options or NULL. No option is
10313
 * defined at time of writing.
10314
 *
10315
 * @return a new OGRSpatialReference* object to free with Release(), or nullptr
10316
 *
10317
 * @since GDAL 3.6
10318
 * @see OGRSpatialReference::FindMatches()
10319
 */
10320
OGRSpatialReference *
10321
OGRSpatialReference::FindBestMatch(int nMinimumMatchConfidence,
10322
                                   const char *pszPreferredAuthority,
10323
                                   CSLConstList papszOptions) const
10324
25.9k
{
10325
25.9k
    TAKE_OPTIONAL_LOCK();
10326
10327
25.9k
    CPL_IGNORE_RET_VAL(papszOptions);  // ignored for now.
10328
10329
25.9k
    if (nMinimumMatchConfidence == 0)
10330
0
        nMinimumMatchConfidence = 90;
10331
25.9k
    if (pszPreferredAuthority == nullptr)
10332
2.05k
        pszPreferredAuthority = "EPSG";
10333
10334
    // Try to identify the CRS with the database
10335
25.9k
    int nEntries = 0;
10336
25.9k
    int *panConfidence = nullptr;
10337
25.9k
    OGRSpatialReferenceH *pahSRS =
10338
25.9k
        FindMatches(nullptr, &nEntries, &panConfidence);
10339
25.9k
    if (nEntries == 1 && panConfidence[0] >= nMinimumMatchConfidence)
10340
1.21k
    {
10341
1.21k
        std::vector<double> adfTOWGS84(7);
10342
1.21k
        if (GetTOWGS84(&adfTOWGS84[0], 7) != OGRERR_NONE)
10343
1.20k
        {
10344
1.20k
            adfTOWGS84.clear();
10345
1.20k
        }
10346
10347
1.21k
        auto poSRS = OGRSpatialReference::FromHandle(pahSRS[0]);
10348
10349
1.21k
        auto poBaseGeogCRS =
10350
1.21k
            std::unique_ptr<OGRSpatialReference>(poSRS->CloneGeogCS());
10351
1.21k
        if (poBaseGeogCRS)
10352
1.17k
        {
10353
            // If the base geographic SRS of the SRS is EPSG:4326
10354
            // with TOWGS84[0,0,0,0,0,0], then just use the official
10355
            // SRS code
10356
            // Same with EPSG:4258 (ETRS89), since it's the only known
10357
            // TOWGS84[] style transformation to WGS 84, and given the
10358
            // "fuzzy" nature of both ETRS89 and WGS 84, there's little
10359
            // chance that a non-NULL TOWGS84[] will emerge.
10360
1.17k
            const char *pszAuthorityName = nullptr;
10361
1.17k
            const char *pszAuthorityCode = nullptr;
10362
1.17k
            const char *pszBaseAuthorityName = nullptr;
10363
1.17k
            const char *pszBaseAuthorityCode = nullptr;
10364
1.17k
            const char *pszBaseName = poBaseGeogCRS->GetName();
10365
1.17k
            if (adfTOWGS84 == std::vector<double>(7) &&
10366
5
                (pszAuthorityName = poSRS->GetAuthorityName(nullptr)) !=
10367
5
                    nullptr &&
10368
5
                EQUAL(pszAuthorityName, "EPSG") &&
10369
5
                (pszAuthorityCode = poSRS->GetAuthorityCode(nullptr)) !=
10370
5
                    nullptr &&
10371
5
                (pszBaseAuthorityName =
10372
5
                     poBaseGeogCRS->GetAuthorityName(nullptr)) != nullptr &&
10373
5
                EQUAL(pszBaseAuthorityName, "EPSG") &&
10374
5
                (pszBaseAuthorityCode =
10375
5
                     poBaseGeogCRS->GetAuthorityCode(nullptr)) != nullptr &&
10376
5
                (EQUAL(pszBaseAuthorityCode, "4326") ||
10377
5
                 EQUAL(pszBaseAuthorityCode, "4258") ||
10378
                 // For ETRS89-XXX [...] new CRS added in EPSG 12.033+
10379
5
                 (pszBaseName && STARTS_WITH(pszBaseName, "ETRS89"))))
10380
0
            {
10381
0
                poSRS->importFromEPSG(atoi(pszAuthorityCode));
10382
0
            }
10383
1.17k
        }
10384
10385
1.21k
        CPLFree(pahSRS);
10386
1.21k
        CPLFree(panConfidence);
10387
10388
1.21k
        return poSRS;
10389
1.21k
    }
10390
24.7k
    else
10391
24.7k
    {
10392
        // If there are several matches >= nMinimumMatchConfidence, take the
10393
        // only one that is under pszPreferredAuthority
10394
24.7k
        int iBestEntry = -1;
10395
580k
        for (int i = 0; i < nEntries; i++)
10396
555k
        {
10397
555k
            if (panConfidence[i] >= nMinimumMatchConfidence)
10398
163
            {
10399
163
                const char *pszAuthName =
10400
163
                    OGRSpatialReference::FromHandle(pahSRS[i])
10401
163
                        ->GetAuthorityName(nullptr);
10402
163
                if (pszAuthName != nullptr &&
10403
163
                    EQUAL(pszAuthName, pszPreferredAuthority))
10404
80
                {
10405
80
                    if (iBestEntry < 0)
10406
80
                        iBestEntry = i;
10407
0
                    else
10408
0
                    {
10409
0
                        iBestEntry = -1;
10410
0
                        break;
10411
0
                    }
10412
80
                }
10413
163
            }
10414
555k
        }
10415
24.7k
        if (iBestEntry >= 0)
10416
80
        {
10417
80
            auto poRet = OGRSpatialReference::FromHandle(pahSRS[0])->Clone();
10418
80
            OSRFreeSRSArray(pahSRS);
10419
80
            CPLFree(panConfidence);
10420
80
            return poRet;
10421
80
        }
10422
24.7k
    }
10423
24.6k
    OSRFreeSRSArray(pahSRS);
10424
24.6k
    CPLFree(panConfidence);
10425
24.6k
    return nullptr;
10426
25.9k
}
10427
10428
/************************************************************************/
10429
/*                             SetTOWGS84()                             */
10430
/************************************************************************/
10431
10432
/**
10433
 * \brief Set the Bursa-Wolf conversion to WGS84.
10434
 *
10435
 * This will create the TOWGS84 node as a child of the DATUM.  It will fail
10436
 * if there is no existing DATUM node. It will replace
10437
 * an existing TOWGS84 node if there is one.
10438
 *
10439
 * The parameters have the same meaning as EPSG transformation 9606
10440
 * (Position Vector 7-param. transformation).
10441
 *
10442
 * This method is the same as the C function OSRSetTOWGS84().
10443
 *
10444
 * @param dfDX X child in meters.
10445
 * @param dfDY Y child in meters.
10446
 * @param dfDZ Z child in meters.
10447
 * @param dfEX X rotation in arc seconds (optional, defaults to zero).
10448
 * @param dfEY Y rotation in arc seconds (optional, defaults to zero).
10449
 * @param dfEZ Z rotation in arc seconds (optional, defaults to zero).
10450
 * @param dfPPM scaling factor (parts per million).
10451
 *
10452
 * @return OGRERR_NONE on success.
10453
 */
10454
10455
OGRErr OGRSpatialReference::SetTOWGS84(double dfDX, double dfDY, double dfDZ,
10456
                                       double dfEX, double dfEY, double dfEZ,
10457
                                       double dfPPM)
10458
10459
3.37k
{
10460
3.37k
    TAKE_OPTIONAL_LOCK();
10461
10462
3.37k
    d->refreshProjObj();
10463
3.37k
    if (d->m_pj_crs == nullptr)
10464
119
    {
10465
119
        return OGRERR_FAILURE;
10466
119
    }
10467
10468
    // Remove existing BoundCRS
10469
3.25k
    if (d->m_pjType == PJ_TYPE_BOUND_CRS)
10470
99
    {
10471
99
        auto baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
10472
99
        if (!baseCRS)
10473
0
            return OGRERR_FAILURE;
10474
99
        d->setPjCRS(baseCRS);
10475
99
    }
10476
10477
3.25k
    PJ_PARAM_DESCRIPTION params[7];
10478
10479
3.25k
    params[0].name = EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION;
10480
3.25k
    params[0].auth_name = "EPSG";
10481
3.25k
    params[0].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
10482
3.25k
    params[0].value = dfDX;
10483
3.25k
    params[0].unit_name = "metre";
10484
3.25k
    params[0].unit_conv_factor = 1.0;
10485
3.25k
    params[0].unit_type = PJ_UT_LINEAR;
10486
10487
3.25k
    params[1].name = EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION;
10488
3.25k
    params[1].auth_name = "EPSG";
10489
3.25k
    params[1].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
10490
3.25k
    params[1].value = dfDY;
10491
3.25k
    params[1].unit_name = "metre";
10492
3.25k
    params[1].unit_conv_factor = 1.0;
10493
3.25k
    params[1].unit_type = PJ_UT_LINEAR;
10494
10495
3.25k
    params[2].name = EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION;
10496
3.25k
    params[2].auth_name = "EPSG";
10497
3.25k
    params[2].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
10498
3.25k
    params[2].value = dfDZ;
10499
3.25k
    params[2].unit_name = "metre";
10500
3.25k
    params[2].unit_conv_factor = 1.0;
10501
3.25k
    params[2].unit_type = PJ_UT_LINEAR;
10502
10503
3.25k
    params[3].name = EPSG_NAME_PARAMETER_X_AXIS_ROTATION;
10504
3.25k
    params[3].auth_name = "EPSG";
10505
3.25k
    params[3].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_ROTATION);
10506
3.25k
    params[3].value = dfEX;
10507
3.25k
    params[3].unit_name = "arc-second";
10508
3.25k
    params[3].unit_conv_factor = 1. / 3600 * M_PI / 180;
10509
3.25k
    params[3].unit_type = PJ_UT_ANGULAR;
10510
10511
3.25k
    params[4].name = EPSG_NAME_PARAMETER_Y_AXIS_ROTATION;
10512
3.25k
    params[4].auth_name = "EPSG";
10513
3.25k
    params[4].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION);
10514
3.25k
    params[4].value = dfEY;
10515
3.25k
    params[4].unit_name = "arc-second";
10516
3.25k
    params[4].unit_conv_factor = 1. / 3600 * M_PI / 180;
10517
3.25k
    params[4].unit_type = PJ_UT_ANGULAR;
10518
10519
3.25k
    params[5].name = EPSG_NAME_PARAMETER_Z_AXIS_ROTATION;
10520
3.25k
    params[5].auth_name = "EPSG";
10521
3.25k
    params[5].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION);
10522
3.25k
    params[5].value = dfEZ;
10523
3.25k
    params[5].unit_name = "arc-second";
10524
3.25k
    params[5].unit_conv_factor = 1. / 3600 * M_PI / 180;
10525
3.25k
    params[5].unit_type = PJ_UT_ANGULAR;
10526
10527
3.25k
    params[6].name = EPSG_NAME_PARAMETER_SCALE_DIFFERENCE;
10528
3.25k
    params[6].auth_name = "EPSG";
10529
3.25k
    params[6].code = XSTRINGIFY(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE);
10530
3.25k
    params[6].value = dfPPM;
10531
3.25k
    params[6].unit_name = "parts per million";
10532
3.25k
    params[6].unit_conv_factor = 1e-6;
10533
3.25k
    params[6].unit_type = PJ_UT_SCALE;
10534
10535
3.25k
    auto sourceCRS =
10536
3.25k
        proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
10537
3.25k
    if (!sourceCRS)
10538
21
    {
10539
21
        return OGRERR_FAILURE;
10540
21
    }
10541
10542
3.23k
    const auto sourceType = proj_get_type(sourceCRS);
10543
10544
3.23k
    auto targetCRS = proj_create_from_database(
10545
3.23k
        d->getPROJContext(), "EPSG",
10546
3.23k
        sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS   ? "4326"
10547
3.23k
        : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS ? "4979"
10548
164
                                                  : "4978",
10549
3.23k
        PJ_CATEGORY_CRS, false, nullptr);
10550
3.23k
    if (!targetCRS)
10551
0
    {
10552
0
        proj_destroy(sourceCRS);
10553
0
        return OGRERR_FAILURE;
10554
0
    }
10555
10556
3.23k
    CPLString osMethodCode;
10557
3.23k
    osMethodCode.Printf("%d",
10558
3.23k
                        sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
10559
3.23k
                            ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
10560
3.23k
                        : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
10561
164
                            ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
10562
164
                            : EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC);
10563
10564
3.23k
    auto transf = proj_create_transformation(
10565
3.23k
        d->getPROJContext(), "Transformation to WGS84", nullptr, nullptr,
10566
3.23k
        sourceCRS, targetCRS, nullptr,
10567
3.23k
        sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
10568
3.23k
            ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
10569
3.23k
        : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
10570
164
            ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
10571
164
            : EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC,
10572
3.23k
        "EPSG", osMethodCode.c_str(), 7, params, -1);
10573
3.23k
    proj_destroy(sourceCRS);
10574
3.23k
    if (!transf)
10575
0
    {
10576
0
        proj_destroy(targetCRS);
10577
0
        return OGRERR_FAILURE;
10578
0
    }
10579
10580
3.23k
    auto newBoundCRS = proj_crs_create_bound_crs(
10581
3.23k
        d->getPROJContext(), d->m_pj_crs, targetCRS, transf);
10582
3.23k
    proj_destroy(transf);
10583
3.23k
    proj_destroy(targetCRS);
10584
3.23k
    if (!newBoundCRS)
10585
0
    {
10586
0
        return OGRERR_FAILURE;
10587
0
    }
10588
10589
3.23k
    d->setPjCRS(newBoundCRS);
10590
3.23k
    return OGRERR_NONE;
10591
3.23k
}
10592
10593
/************************************************************************/
10594
/*                           OSRSetTOWGS84()                            */
10595
/************************************************************************/
10596
10597
/**
10598
 * \brief Set the Bursa-Wolf conversion to WGS84.
10599
 *
10600
 * This function is the same as OGRSpatialReference::SetTOWGS84().
10601
 */
10602
OGRErr OSRSetTOWGS84(OGRSpatialReferenceH hSRS, double dfDX, double dfDY,
10603
                     double dfDZ, double dfEX, double dfEY, double dfEZ,
10604
                     double dfPPM)
10605
10606
0
{
10607
0
    VALIDATE_POINTER1(hSRS, "OSRSetTOWGS84", OGRERR_FAILURE);
10608
10609
0
    return ToPointer(hSRS)->SetTOWGS84(dfDX, dfDY, dfDZ, dfEX, dfEY, dfEZ,
10610
0
                                       dfPPM);
10611
0
}
10612
10613
/************************************************************************/
10614
/*                             GetTOWGS84()                             */
10615
/************************************************************************/
10616
10617
/**
10618
 * \brief Fetch TOWGS84 parameters, if available.
10619
 *
10620
 * The parameters have the same meaning as EPSG transformation 9606
10621
 * (Position Vector 7-param. transformation).
10622
 *
10623
 * @param padfCoeff array into which up to 7 coefficients are placed.
10624
 * @param nCoeffCount size of padfCoeff - defaults to 7.
10625
 *
10626
 * @return OGRERR_NONE on success, or OGRERR_FAILURE if there is no
10627
 * TOWGS84 node available.
10628
 */
10629
10630
OGRErr OGRSpatialReference::GetTOWGS84(double *padfCoeff, int nCoeffCount) const
10631
10632
1.68k
{
10633
1.68k
    TAKE_OPTIONAL_LOCK();
10634
10635
1.68k
    d->refreshProjObj();
10636
1.68k
    if (d->m_pjType != PJ_TYPE_BOUND_CRS)
10637
1.60k
        return OGRERR_FAILURE;
10638
10639
83
    memset(padfCoeff, 0, sizeof(double) * nCoeffCount);
10640
10641
83
    auto transf = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
10642
83
    int success = proj_coordoperation_get_towgs84_values(
10643
83
        d->getPROJContext(), transf, padfCoeff, nCoeffCount, false);
10644
83
    proj_destroy(transf);
10645
10646
83
    return success ? OGRERR_NONE : OGRERR_FAILURE;
10647
1.68k
}
10648
10649
/************************************************************************/
10650
/*                           OSRGetTOWGS84()                            */
10651
/************************************************************************/
10652
10653
/**
10654
 * \brief Fetch TOWGS84 parameters, if available.
10655
 *
10656
 * This function is the same as OGRSpatialReference::GetTOWGS84().
10657
 */
10658
OGRErr OSRGetTOWGS84(OGRSpatialReferenceH hSRS, double *padfCoeff,
10659
                     int nCoeffCount)
10660
10661
0
{
10662
0
    VALIDATE_POINTER1(hSRS, "OSRGetTOWGS84", OGRERR_FAILURE);
10663
10664
0
    return ToPointer(hSRS)->GetTOWGS84(padfCoeff, nCoeffCount);
10665
0
}
10666
10667
/************************************************************************/
10668
/*                         IsAngularParameter()                         */
10669
/************************************************************************/
10670
10671
/** Is the passed projection parameter an angular one?
10672
 *
10673
 * @return TRUE or FALSE
10674
 */
10675
10676
/* static */
10677
int OGRSpatialReference::IsAngularParameter(const char *pszParameterName)
10678
10679
2.75k
{
10680
2.75k
    if (STARTS_WITH_CI(pszParameterName, "long") ||
10681
2.75k
        STARTS_WITH_CI(pszParameterName, "lati") ||
10682
2.18k
        EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN) ||
10683
2.75k
        STARTS_WITH_CI(pszParameterName, "standard_parallel") ||
10684
1.08k
        EQUAL(pszParameterName, SRS_PP_AZIMUTH) ||
10685
1.08k
        EQUAL(pszParameterName, SRS_PP_RECTIFIED_GRID_ANGLE))
10686
1.67k
        return TRUE;
10687
10688
1.08k
    return FALSE;
10689
2.75k
}
10690
10691
/************************************************************************/
10692
/*                        IsLongitudeParameter()                        */
10693
/************************************************************************/
10694
10695
/** Is the passed projection parameter an angular longitude
10696
 * (relative to a prime meridian)?
10697
 *
10698
 * @return TRUE or FALSE
10699
 */
10700
10701
/* static */
10702
int OGRSpatialReference::IsLongitudeParameter(const char *pszParameterName)
10703
10704
0
{
10705
0
    if (STARTS_WITH_CI(pszParameterName, "long") ||
10706
0
        EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN))
10707
0
        return TRUE;
10708
10709
0
    return FALSE;
10710
0
}
10711
10712
/************************************************************************/
10713
/*                         IsLinearParameter()                          */
10714
/************************************************************************/
10715
10716
/** Is the passed projection parameter an linear one measured in meters or
10717
 * some similar linear measure.
10718
 *
10719
 * @return TRUE or FALSE
10720
 */
10721
10722
/* static */
10723
int OGRSpatialReference::IsLinearParameter(const char *pszParameterName)
10724
10725
401
{
10726
401
    if (STARTS_WITH_CI(pszParameterName, "false_") ||
10727
265
        EQUAL(pszParameterName, SRS_PP_SATELLITE_HEIGHT))
10728
136
        return TRUE;
10729
10730
265
    return FALSE;
10731
401
}
10732
10733
/************************************************************************/
10734
/*                            GetNormInfo()                             */
10735
/************************************************************************/
10736
10737
/**
10738
 * \brief Set the internal information for normalizing linear, and angular
10739
 * values.
10740
 */
10741
void OGRSpatialReference::GetNormInfo() const
10742
10743
14.3k
{
10744
14.3k
    TAKE_OPTIONAL_LOCK();
10745
10746
14.3k
    if (d->bNormInfoSet)
10747
3.31k
        return;
10748
10749
    /* -------------------------------------------------------------------- */
10750
    /*      Initialize values.                                              */
10751
    /* -------------------------------------------------------------------- */
10752
11.0k
    d->bNormInfoSet = TRUE;
10753
10754
11.0k
    d->dfFromGreenwich = GetPrimeMeridian(nullptr);
10755
11.0k
    d->dfToMeter = GetLinearUnits(nullptr);
10756
11.0k
    d->dfToDegrees = GetAngularUnits(nullptr) / CPLAtof(SRS_UA_DEGREE_CONV);
10757
11.0k
    if (fabs(d->dfToDegrees - 1.0) < 0.000000001)
10758
8.86k
        d->dfToDegrees = 1.0;
10759
11.0k
}
10760
10761
/************************************************************************/
10762
/*                            GetExtension()                            */
10763
/************************************************************************/
10764
10765
/**
10766
 * \brief Fetch extension value.
10767
 *
10768
 * Fetch the value of the named EXTENSION item for the identified
10769
 * target node.
10770
 *
10771
 * @param pszTargetKey the name or path to the parent node of the EXTENSION.
10772
 * @param pszName the name of the extension being fetched.
10773
 * @param pszDefault the value to return if the extension is not found.
10774
 *
10775
 * @return node value if successful or pszDefault on failure.
10776
 */
10777
10778
const char *OGRSpatialReference::GetExtension(const char *pszTargetKey,
10779
                                              const char *pszName,
10780
                                              const char *pszDefault) const
10781
10782
119k
{
10783
119k
    TAKE_OPTIONAL_LOCK();
10784
10785
    /* -------------------------------------------------------------------- */
10786
    /*      Find the target node.                                           */
10787
    /* -------------------------------------------------------------------- */
10788
119k
    const OGR_SRSNode *poNode =
10789
119k
        pszTargetKey == nullptr ? GetRoot() : GetAttrNode(pszTargetKey);
10790
10791
119k
    if (poNode == nullptr)
10792
1.14k
        return nullptr;
10793
10794
    /* -------------------------------------------------------------------- */
10795
    /*      Fetch matching EXTENSION if there is one.                       */
10796
    /* -------------------------------------------------------------------- */
10797
903k
    for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
10798
784k
    {
10799
784k
        const OGR_SRSNode *poChild = poNode->GetChild(i);
10800
10801
784k
        if (EQUAL(poChild->GetValue(), "EXTENSION") &&
10802
219
            poChild->GetChildCount() >= 2)
10803
219
        {
10804
219
            if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
10805
219
                return poChild->GetChild(1)->GetValue();
10806
219
        }
10807
784k
    }
10808
10809
118k
    return pszDefault;
10810
118k
}
10811
10812
/************************************************************************/
10813
/*                            SetExtension()                            */
10814
/************************************************************************/
10815
/**
10816
 * \brief Set extension value.
10817
 *
10818
 * Set the value of the named EXTENSION item for the identified
10819
 * target node.
10820
 *
10821
 * @param pszTargetKey the name or path to the parent node of the EXTENSION.
10822
 * @param pszName the name of the extension being fetched.
10823
 * @param pszValue the value to set
10824
 *
10825
 * @return OGRERR_NONE on success
10826
 */
10827
10828
OGRErr OGRSpatialReference::SetExtension(const char *pszTargetKey,
10829
                                         const char *pszName,
10830
                                         const char *pszValue)
10831
10832
55
{
10833
55
    TAKE_OPTIONAL_LOCK();
10834
10835
    /* -------------------------------------------------------------------- */
10836
    /*      Find the target node.                                           */
10837
    /* -------------------------------------------------------------------- */
10838
55
    OGR_SRSNode *poNode = nullptr;
10839
10840
55
    if (pszTargetKey == nullptr)
10841
0
        poNode = GetRoot();
10842
55
    else
10843
55
        poNode = GetAttrNode(pszTargetKey);
10844
10845
55
    if (poNode == nullptr)
10846
6
        return OGRERR_FAILURE;
10847
10848
    /* -------------------------------------------------------------------- */
10849
    /*      Fetch matching EXTENSION if there is one.                       */
10850
    /* -------------------------------------------------------------------- */
10851
438
    for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
10852
392
    {
10853
392
        OGR_SRSNode *poChild = poNode->GetChild(i);
10854
10855
392
        if (EQUAL(poChild->GetValue(), "EXTENSION") &&
10856
3
            poChild->GetChildCount() >= 2)
10857
3
        {
10858
3
            if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
10859
3
            {
10860
3
                poChild->GetChild(1)->SetValue(pszValue);
10861
3
                return OGRERR_NONE;
10862
3
            }
10863
3
        }
10864
392
    }
10865
10866
    /* -------------------------------------------------------------------- */
10867
    /*      Create a new EXTENSION node.                                    */
10868
    /* -------------------------------------------------------------------- */
10869
46
    OGR_SRSNode *poAuthNode = new OGR_SRSNode("EXTENSION");
10870
46
    poAuthNode->AddChild(new OGR_SRSNode(pszName));
10871
46
    poAuthNode->AddChild(new OGR_SRSNode(pszValue));
10872
10873
46
    poNode->AddChild(poAuthNode);
10874
10875
46
    return OGRERR_NONE;
10876
49
}
10877
10878
/************************************************************************/
10879
/*                             OSRCleanup()                             */
10880
/************************************************************************/
10881
10882
static void CleanupSRSWGS84Mutex();
10883
10884
/**
10885
 * \brief Cleanup cached SRS related memory.
10886
 *
10887
 * This function will attempt to cleanup any cache spatial reference
10888
 * related information, such as cached tables of coordinate systems.
10889
 *
10890
 * This function should not be called concurrently with any other GDAL/OGR
10891
 * function. It is meant at being called once before process termination
10892
 * (typically from the main thread). CPLCleanupTLS() might be used to clean
10893
 * thread-specific resources before thread termination.
10894
 */
10895
void OSRCleanup(void)
10896
10897
0
{
10898
0
    OGRCTDumpStatistics();
10899
0
    CSVDeaccess(nullptr);
10900
0
    CleanupSRSWGS84Mutex();
10901
0
    OSRCTCleanCache();
10902
0
    OSRCleanupTLSContext();
10903
0
}
10904
10905
/************************************************************************/
10906
/*                            GetAxesCount()                            */
10907
/************************************************************************/
10908
10909
/**
10910
 * \brief Return the number of axis of the coordinate system of the CRS.
10911
 *
10912
 * @since GDAL 3.0
10913
 */
10914
int OGRSpatialReference::GetAxesCount() const
10915
100k
{
10916
100k
    TAKE_OPTIONAL_LOCK();
10917
10918
100k
    int axisCount = 0;
10919
100k
    d->refreshProjObj();
10920
100k
    if (d->m_pj_crs == nullptr)
10921
2.45k
    {
10922
2.45k
        return 0;
10923
2.45k
    }
10924
98.5k
    d->demoteFromBoundCRS();
10925
98.5k
    auto ctxt = d->getPROJContext();
10926
98.5k
    if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
10927
2.13k
    {
10928
4.27k
        for (int i = 0;; i++)
10929
6.41k
        {
10930
6.41k
            auto subCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, i);
10931
6.41k
            if (!subCRS)
10932
2.13k
                break;
10933
4.27k
            if (proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
10934
337
            {
10935
337
                auto baseCRS = proj_get_source_crs(ctxt, subCRS);
10936
337
                if (baseCRS)
10937
337
                {
10938
337
                    proj_destroy(subCRS);
10939
337
                    subCRS = baseCRS;
10940
337
                }
10941
337
            }
10942
4.27k
            auto cs = proj_crs_get_coordinate_system(ctxt, subCRS);
10943
4.27k
            if (cs)
10944
4.27k
            {
10945
4.27k
                axisCount += proj_cs_get_axis_count(ctxt, cs);
10946
4.27k
                proj_destroy(cs);
10947
4.27k
            }
10948
4.27k
            proj_destroy(subCRS);
10949
4.27k
        }
10950
2.13k
    }
10951
96.3k
    else
10952
96.3k
    {
10953
96.3k
        auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
10954
96.3k
        if (cs)
10955
93.8k
        {
10956
93.8k
            axisCount = proj_cs_get_axis_count(ctxt, cs);
10957
93.8k
            proj_destroy(cs);
10958
93.8k
        }
10959
96.3k
    }
10960
98.5k
    d->undoDemoteFromBoundCRS();
10961
98.5k
    return axisCount;
10962
100k
}
10963
10964
/************************************************************************/
10965
/*                          OSRGetAxesCount()                           */
10966
/************************************************************************/
10967
10968
/**
10969
 * \brief Return the number of axis of the coordinate system of the CRS.
10970
 *
10971
 * This method is the equivalent of the C++ method
10972
 * OGRSpatialReference::GetAxesCount()
10973
 *
10974
 * @since GDAL 3.1
10975
 */
10976
int OSRGetAxesCount(OGRSpatialReferenceH hSRS)
10977
10978
0
{
10979
0
    VALIDATE_POINTER1(hSRS, "OSRGetAxesCount", 0);
10980
10981
0
    return ToPointer(hSRS)->GetAxesCount();
10982
0
}
10983
10984
/************************************************************************/
10985
/*                              GetAxis()                               */
10986
/************************************************************************/
10987
10988
/**
10989
 * \brief Fetch the orientation of one axis.
10990
 *
10991
 * Fetches the request axis (iAxis - zero based) from the
10992
 * indicated portion of the coordinate system (pszTargetKey) which
10993
 * should be either "GEOGCS" or "PROJCS".
10994
 *
10995
 * No CPLError is issued on routine failures (such as not finding the AXIS).
10996
 *
10997
 * This method is equivalent to the C function OSRGetAxis().
10998
 *
10999
 * @param pszTargetKey the coordinate system part to query ("PROJCS" or
11000
 * "GEOGCS").
11001
 * @param iAxis the axis to query (0 for first, 1 for second, 2 for third).
11002
 * @param peOrientation location into which to place the fetch orientation, may
11003
 * be NULL.
11004
 * @param pdfConvUnit (GDAL >= 3.4) Location into which to place axis conversion
11005
 * factor. May be NULL. Only set if pszTargetKey == NULL
11006
 *
11007
 * @return the name of the axis or NULL on failure.
11008
 */
11009
11010
const char *OGRSpatialReference::GetAxis(const char *pszTargetKey, int iAxis,
11011
                                         OGRAxisOrientation *peOrientation,
11012
                                         double *pdfConvUnit) const
11013
11014
66.5k
{
11015
66.5k
    TAKE_OPTIONAL_LOCK();
11016
11017
66.5k
    if (peOrientation != nullptr)
11018
65.3k
        *peOrientation = OAO_Other;
11019
66.5k
    if (pdfConvUnit != nullptr)
11020
1.12k
        *pdfConvUnit = 0;
11021
11022
66.5k
    d->refreshProjObj();
11023
66.5k
    if (d->m_pj_crs == nullptr)
11024
54
    {
11025
54
        return nullptr;
11026
54
    }
11027
11028
66.4k
    pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
11029
66.4k
    if (pszTargetKey == nullptr && iAxis <= 2)
11030
66.4k
    {
11031
66.4k
        auto ctxt = d->getPROJContext();
11032
11033
66.4k
        int iAxisModified = iAxis;
11034
11035
66.4k
        d->demoteFromBoundCRS();
11036
11037
66.4k
        PJ *cs = nullptr;
11038
66.4k
        if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
11039
1.50k
        {
11040
1.50k
            auto horizCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
11041
1.50k
            if (horizCRS)
11042
1.50k
            {
11043
1.50k
                if (proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
11044
0
                {
11045
0
                    auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
11046
0
                    if (baseCRS)
11047
0
                    {
11048
0
                        proj_destroy(horizCRS);
11049
0
                        horizCRS = baseCRS;
11050
0
                    }
11051
0
                }
11052
1.50k
                cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
11053
1.50k
                proj_destroy(horizCRS);
11054
1.50k
                if (cs)
11055
1.50k
                {
11056
1.50k
                    if (iAxisModified >= proj_cs_get_axis_count(ctxt, cs))
11057
885
                    {
11058
885
                        iAxisModified -= proj_cs_get_axis_count(ctxt, cs);
11059
885
                        proj_destroy(cs);
11060
885
                        cs = nullptr;
11061
885
                    }
11062
1.50k
                }
11063
1.50k
            }
11064
11065
1.50k
            if (cs == nullptr)
11066
885
            {
11067
885
                auto vertCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
11068
885
                if (vertCRS)
11069
885
                {
11070
885
                    if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
11071
0
                    {
11072
0
                        auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
11073
0
                        if (baseCRS)
11074
0
                        {
11075
0
                            proj_destroy(vertCRS);
11076
0
                            vertCRS = baseCRS;
11077
0
                        }
11078
0
                    }
11079
11080
885
                    cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
11081
885
                    proj_destroy(vertCRS);
11082
885
                }
11083
885
            }
11084
1.50k
        }
11085
64.9k
        else
11086
64.9k
        {
11087
64.9k
            cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
11088
64.9k
        }
11089
11090
66.4k
        if (cs)
11091
66.1k
        {
11092
66.1k
            const char *pszName = nullptr;
11093
66.1k
            const char *pszOrientation = nullptr;
11094
66.1k
            double dfConvFactor = 0.0;
11095
66.1k
            proj_cs_get_axis_info(ctxt, cs, iAxisModified, &pszName, nullptr,
11096
66.1k
                                  &pszOrientation, &dfConvFactor, nullptr,
11097
66.1k
                                  nullptr, nullptr);
11098
11099
66.1k
            if (pdfConvUnit != nullptr)
11100
1.12k
            {
11101
1.12k
                *pdfConvUnit = dfConvFactor;
11102
1.12k
            }
11103
11104
66.1k
            if (pszName && pszOrientation)
11105
66.1k
            {
11106
66.1k
                d->m_osAxisName[iAxis] = pszName;
11107
66.1k
                if (peOrientation)
11108
65.0k
                {
11109
65.0k
                    if (EQUAL(pszOrientation, "NORTH"))
11110
32.5k
                        *peOrientation = OAO_North;
11111
32.4k
                    else if (EQUAL(pszOrientation, "EAST"))
11112
27.0k
                        *peOrientation = OAO_East;
11113
5.42k
                    else if (EQUAL(pszOrientation, "SOUTH"))
11114
5.05k
                        *peOrientation = OAO_South;
11115
368
                    else if (EQUAL(pszOrientation, "WEST"))
11116
4
                        *peOrientation = OAO_West;
11117
364
                    else if (EQUAL(pszOrientation, "UP"))
11118
57
                        *peOrientation = OAO_Up;
11119
307
                    else if (EQUAL(pszOrientation, "DOWN"))
11120
34
                        *peOrientation = OAO_Down;
11121
65.0k
                }
11122
66.1k
                proj_destroy(cs);
11123
66.1k
                d->undoDemoteFromBoundCRS();
11124
66.1k
                return d->m_osAxisName[iAxis].c_str();
11125
66.1k
            }
11126
0
            proj_destroy(cs);
11127
0
        }
11128
282
        d->undoDemoteFromBoundCRS();
11129
282
    }
11130
11131
    /* -------------------------------------------------------------------- */
11132
    /*      Find the target node.                                           */
11133
    /* -------------------------------------------------------------------- */
11134
282
    const OGR_SRSNode *poNode = nullptr;
11135
11136
282
    if (pszTargetKey == nullptr)
11137
282
        poNode = GetRoot();
11138
0
    else
11139
0
        poNode = GetAttrNode(pszTargetKey);
11140
11141
282
    if (poNode == nullptr)
11142
0
        return nullptr;
11143
11144
    /* -------------------------------------------------------------------- */
11145
    /*      Find desired child AXIS.                                        */
11146
    /* -------------------------------------------------------------------- */
11147
282
    const OGR_SRSNode *poAxis = nullptr;
11148
282
    const int nChildCount = poNode->GetChildCount();
11149
11150
682
    for (int iChild = 0; iChild < nChildCount; iChild++)
11151
400
    {
11152
400
        const OGR_SRSNode *poChild = poNode->GetChild(iChild);
11153
11154
400
        if (!EQUAL(poChild->GetValue(), "AXIS"))
11155
400
            continue;
11156
11157
0
        if (iAxis == 0)
11158
0
        {
11159
0
            poAxis = poChild;
11160
0
            break;
11161
0
        }
11162
0
        iAxis--;
11163
0
    }
11164
11165
282
    if (poAxis == nullptr)
11166
282
        return nullptr;
11167
11168
0
    if (poAxis->GetChildCount() < 2)
11169
0
        return nullptr;
11170
11171
    /* -------------------------------------------------------------------- */
11172
    /*      Extract name and orientation if possible.                       */
11173
    /* -------------------------------------------------------------------- */
11174
0
    if (peOrientation != nullptr)
11175
0
    {
11176
0
        const char *pszOrientation = poAxis->GetChild(1)->GetValue();
11177
11178
0
        if (EQUAL(pszOrientation, "NORTH"))
11179
0
            *peOrientation = OAO_North;
11180
0
        else if (EQUAL(pszOrientation, "EAST"))
11181
0
            *peOrientation = OAO_East;
11182
0
        else if (EQUAL(pszOrientation, "SOUTH"))
11183
0
            *peOrientation = OAO_South;
11184
0
        else if (EQUAL(pszOrientation, "WEST"))
11185
0
            *peOrientation = OAO_West;
11186
0
        else if (EQUAL(pszOrientation, "UP"))
11187
0
            *peOrientation = OAO_Up;
11188
0
        else if (EQUAL(pszOrientation, "DOWN"))
11189
0
            *peOrientation = OAO_Down;
11190
0
        else if (EQUAL(pszOrientation, "OTHER"))
11191
0
            *peOrientation = OAO_Other;
11192
0
        else
11193
0
        {
11194
0
            CPLDebug("OSR", "Unrecognized orientation value '%s'.",
11195
0
                     pszOrientation);
11196
0
        }
11197
0
    }
11198
11199
0
    return poAxis->GetChild(0)->GetValue();
11200
0
}
11201
11202
/************************************************************************/
11203
/*                             OSRGetAxis()                             */
11204
/************************************************************************/
11205
11206
/**
11207
 * \brief Fetch the orientation of one axis.
11208
 *
11209
 * This method is the equivalent of the C++ method OGRSpatialReference::GetAxis
11210
 */
11211
const char *OSRGetAxis(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
11212
                       int iAxis, OGRAxisOrientation *peOrientation)
11213
11214
0
{
11215
0
    VALIDATE_POINTER1(hSRS, "OSRGetAxis", nullptr);
11216
11217
0
    return ToPointer(hSRS)->GetAxis(pszTargetKey, iAxis, peOrientation);
11218
0
}
11219
11220
/************************************************************************/
11221
/*                         OSRAxisEnumToName()                          */
11222
/************************************************************************/
11223
11224
/**
11225
 * \brief Return the string representation for the OGRAxisOrientation
11226
 * enumeration.
11227
 *
11228
 * For example "NORTH" for OAO_North.
11229
 *
11230
 * @return an internal string
11231
 */
11232
const char *OSRAxisEnumToName(OGRAxisOrientation eOrientation)
11233
11234
43.9k
{
11235
43.9k
    if (eOrientation == OAO_North)
11236
21.9k
        return "NORTH";
11237
21.9k
    if (eOrientation == OAO_East)
11238
21.9k
        return "EAST";
11239
0
    if (eOrientation == OAO_South)
11240
0
        return "SOUTH";
11241
0
    if (eOrientation == OAO_West)
11242
0
        return "WEST";
11243
0
    if (eOrientation == OAO_Up)
11244
0
        return "UP";
11245
0
    if (eOrientation == OAO_Down)
11246
0
        return "DOWN";
11247
0
    if (eOrientation == OAO_Other)
11248
0
        return "OTHER";
11249
11250
0
    return "UNKNOWN";
11251
0
}
11252
11253
/************************************************************************/
11254
/*                              SetAxes()                               */
11255
/************************************************************************/
11256
11257
/**
11258
 * \brief Set the axes for a coordinate system.
11259
 *
11260
 * Set the names, and orientations of the axes for either a projected
11261
 * (PROJCS) or geographic (GEOGCS) coordinate system.
11262
 *
11263
 * This method is equivalent to the C function OSRSetAxes().
11264
 *
11265
 * @param pszTargetKey either "PROJCS" or "GEOGCS", must already exist in SRS.
11266
 * @param pszXAxisName name of first axis, normally "Long" or "Easting".
11267
 * @param eXAxisOrientation normally OAO_East.
11268
 * @param pszYAxisName name of second axis, normally "Lat" or "Northing".
11269
 * @param eYAxisOrientation normally OAO_North.
11270
 *
11271
 * @return OGRERR_NONE on success or an error code.
11272
 */
11273
11274
OGRErr OGRSpatialReference::SetAxes(const char *pszTargetKey,
11275
                                    const char *pszXAxisName,
11276
                                    OGRAxisOrientation eXAxisOrientation,
11277
                                    const char *pszYAxisName,
11278
                                    OGRAxisOrientation eYAxisOrientation)
11279
11280
31.3k
{
11281
31.3k
    TAKE_OPTIONAL_LOCK();
11282
11283
    /* -------------------------------------------------------------------- */
11284
    /*      Find the target node.                                           */
11285
    /* -------------------------------------------------------------------- */
11286
31.3k
    OGR_SRSNode *poNode = nullptr;
11287
11288
31.3k
    if (pszTargetKey == nullptr)
11289
31.3k
        poNode = GetRoot();
11290
0
    else
11291
0
        poNode = GetAttrNode(pszTargetKey);
11292
11293
31.3k
    if (poNode == nullptr)
11294
9.41k
        return OGRERR_FAILURE;
11295
11296
    /* -------------------------------------------------------------------- */
11297
    /*      Strip any existing AXIS children.                               */
11298
    /* -------------------------------------------------------------------- */
11299
65.6k
    while (poNode->FindChild("AXIS") >= 0)
11300
43.6k
        poNode->DestroyChild(poNode->FindChild("AXIS"));
11301
11302
    /* -------------------------------------------------------------------- */
11303
    /*      Insert desired axes                                             */
11304
    /* -------------------------------------------------------------------- */
11305
21.9k
    OGR_SRSNode *poAxis = new OGR_SRSNode("AXIS");
11306
11307
21.9k
    poAxis->AddChild(new OGR_SRSNode(pszXAxisName));
11308
21.9k
    poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eXAxisOrientation)));
11309
11310
21.9k
    poNode->AddChild(poAxis);
11311
11312
21.9k
    poAxis = new OGR_SRSNode("AXIS");
11313
11314
21.9k
    poAxis->AddChild(new OGR_SRSNode(pszYAxisName));
11315
21.9k
    poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eYAxisOrientation)));
11316
11317
21.9k
    poNode->AddChild(poAxis);
11318
11319
21.9k
    return OGRERR_NONE;
11320
31.3k
}
11321
11322
/************************************************************************/
11323
/*                             OSRSetAxes()                             */
11324
/************************************************************************/
11325
/**
11326
 * \brief Set the axes for a coordinate system.
11327
 *
11328
 * This method is the equivalent of the C++ method OGRSpatialReference::SetAxes
11329
 */
11330
OGRErr OSRSetAxes(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
11331
                  const char *pszXAxisName,
11332
                  OGRAxisOrientation eXAxisOrientation,
11333
                  const char *pszYAxisName,
11334
                  OGRAxisOrientation eYAxisOrientation)
11335
0
{
11336
0
    VALIDATE_POINTER1(hSRS, "OSRSetAxes", OGRERR_FAILURE);
11337
11338
0
    return ToPointer(hSRS)->SetAxes(pszTargetKey, pszXAxisName,
11339
0
                                    eXAxisOrientation, pszYAxisName,
11340
0
                                    eYAxisOrientation);
11341
0
}
11342
11343
/************************************************************************/
11344
/*                       OSRExportToMICoordSys()                        */
11345
/************************************************************************/
11346
/**
11347
 * \brief Export coordinate system in Mapinfo style CoordSys format.
11348
 *
11349
 * This method is the equivalent of the C++ method
11350
 * OGRSpatialReference::exportToMICoordSys
11351
 */
11352
OGRErr OSRExportToMICoordSys(OGRSpatialReferenceH hSRS, char **ppszReturn)
11353
11354
0
{
11355
0
    VALIDATE_POINTER1(hSRS, "OSRExportToMICoordSys", OGRERR_FAILURE);
11356
11357
0
    *ppszReturn = nullptr;
11358
11359
0
    return ToPointer(hSRS)->exportToMICoordSys(ppszReturn);
11360
0
}
11361
11362
/************************************************************************/
11363
/*                         exportToMICoordSys()                         */
11364
/************************************************************************/
11365
11366
/**
11367
 * \brief Export coordinate system in Mapinfo style CoordSys format.
11368
 *
11369
 * Note that the returned WKT string should be freed with
11370
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
11371
 *
11372
 * This method is the same as the C function OSRExportToMICoordSys().
11373
 *
11374
 * @param ppszResult pointer to which dynamically allocated Mapinfo CoordSys
11375
 * definition will be assigned.
11376
 *
11377
 * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
11378
 * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
11379
 */
11380
11381
OGRErr OGRSpatialReference::exportToMICoordSys(char **ppszResult) const
11382
11383
0
{
11384
0
    *ppszResult = MITABSpatialRef2CoordSys(this);
11385
0
    if (*ppszResult != nullptr && strlen(*ppszResult) > 0)
11386
0
        return OGRERR_NONE;
11387
11388
0
    return OGRERR_FAILURE;
11389
0
}
11390
11391
/************************************************************************/
11392
/*                      OSRImportFromMICoordSys()                       */
11393
/************************************************************************/
11394
/**
11395
 * \brief Import Mapinfo style CoordSys definition.
11396
 *
11397
 * This method is the equivalent of the C++ method
11398
 * OGRSpatialReference::importFromMICoordSys
11399
 */
11400
11401
OGRErr OSRImportFromMICoordSys(OGRSpatialReferenceH hSRS,
11402
                               const char *pszCoordSys)
11403
11404
0
{
11405
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromMICoordSys", OGRERR_FAILURE);
11406
11407
0
    return ToPointer(hSRS)->importFromMICoordSys(pszCoordSys);
11408
0
}
11409
11410
/************************************************************************/
11411
/*                        importFromMICoordSys()                        */
11412
/************************************************************************/
11413
11414
/**
11415
 * \brief Import Mapinfo style CoordSys definition.
11416
 *
11417
 * The OGRSpatialReference is initialized from the passed Mapinfo style CoordSys
11418
 * definition string.
11419
 *
11420
 * This method is the equivalent of the C function OSRImportFromMICoordSys().
11421
 *
11422
 * @param pszCoordSys Mapinfo style CoordSys definition string.
11423
 *
11424
 * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
11425
 * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
11426
 */
11427
11428
OGRErr OGRSpatialReference::importFromMICoordSys(const char *pszCoordSys)
11429
11430
0
{
11431
0
    OGRSpatialReference *poResult = MITABCoordSys2SpatialRef(pszCoordSys);
11432
11433
0
    if (poResult == nullptr)
11434
0
        return OGRERR_FAILURE;
11435
11436
0
    *this = *poResult;
11437
0
    delete poResult;
11438
11439
0
    return OGRERR_NONE;
11440
0
}
11441
11442
/************************************************************************/
11443
/*                        OSRCalcInvFlattening()                        */
11444
/************************************************************************/
11445
11446
/**
11447
 * \brief Compute inverse flattening from semi-major and semi-minor axis
11448
 *
11449
 * @param dfSemiMajor Semi-major axis length.
11450
 * @param dfSemiMinor Semi-minor axis length.
11451
 *
11452
 * @return inverse flattening, or 0 if both axis are equal.
11453
 */
11454
11455
double OSRCalcInvFlattening(double dfSemiMajor, double dfSemiMinor)
11456
17.0k
{
11457
17.0k
    if (fabs(dfSemiMajor - dfSemiMinor) < 1e-1)
11458
783
        return 0;
11459
16.2k
    if (dfSemiMajor <= 0 || dfSemiMinor <= 0 || dfSemiMinor > dfSemiMajor)
11460
1.79k
    {
11461
1.79k
        CPLError(CE_Failure, CPLE_IllegalArg,
11462
1.79k
                 "OSRCalcInvFlattening(): Wrong input values");
11463
1.79k
        return 0;
11464
1.79k
    }
11465
11466
14.4k
    return dfSemiMajor / (dfSemiMajor - dfSemiMinor);
11467
16.2k
}
11468
11469
/************************************************************************/
11470
/*                        OSRCalcInvFlattening()                        */
11471
/************************************************************************/
11472
11473
/**
11474
 * \brief Compute semi-minor axis from semi-major axis and inverse flattening.
11475
 *
11476
 * @param dfSemiMajor Semi-major axis length.
11477
 * @param dfInvFlattening Inverse flattening or 0 for sphere.
11478
 *
11479
 * @return semi-minor axis
11480
 */
11481
11482
double OSRCalcSemiMinorFromInvFlattening(double dfSemiMajor,
11483
                                         double dfInvFlattening)
11484
2.49k
{
11485
2.49k
    if (fabs(dfInvFlattening) < 0.000000000001)
11486
142
        return dfSemiMajor;
11487
2.35k
    if (dfSemiMajor <= 0.0 || dfInvFlattening <= 1.0)
11488
0
    {
11489
0
        CPLError(CE_Failure, CPLE_IllegalArg,
11490
0
                 "OSRCalcSemiMinorFromInvFlattening(): Wrong input values");
11491
0
        return dfSemiMajor;
11492
0
    }
11493
11494
2.35k
    return dfSemiMajor * (1.0 - 1.0 / dfInvFlattening);
11495
2.35k
}
11496
11497
/************************************************************************/
11498
/*                            GetWGS84SRS()                             */
11499
/************************************************************************/
11500
11501
static OGRSpatialReference *poSRSWGS84 = nullptr;
11502
static CPLMutex *hMutex = nullptr;
11503
11504
/**
11505
 * \brief Returns an instance of a SRS object with WGS84 WKT.
11506
 *
11507
 * Note: the instance will have
11508
 * SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
11509
 *
11510
 * The reference counter of the returned object is not increased by this
11511
 * operation.
11512
 *
11513
 * @return instance.
11514
 */
11515
11516
OGRSpatialReference *OGRSpatialReference::GetWGS84SRS()
11517
42.1k
{
11518
42.1k
    CPLMutexHolderD(&hMutex);
11519
42.1k
    if (poSRSWGS84 == nullptr)
11520
8
    {
11521
8
        poSRSWGS84 = new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
11522
8
        poSRSWGS84->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
11523
8
    }
11524
42.1k
    return poSRSWGS84;
11525
42.1k
}
11526
11527
/************************************************************************/
11528
/*                        CleanupSRSWGS84Mutex()                        */
11529
/************************************************************************/
11530
11531
static void CleanupSRSWGS84Mutex()
11532
0
{
11533
0
    if (hMutex != nullptr)
11534
0
    {
11535
0
        poSRSWGS84->Release();
11536
0
        poSRSWGS84 = nullptr;
11537
0
        CPLDestroyMutex(hMutex);
11538
0
        hMutex = nullptr;
11539
0
    }
11540
0
}
11541
11542
/************************************************************************/
11543
/*                         OSRImportFromProj4()                         */
11544
/************************************************************************/
11545
/**
11546
 * \brief Import PROJ coordinate string.
11547
 *
11548
 * This function is the same as OGRSpatialReference::importFromProj4().
11549
 */
11550
OGRErr OSRImportFromProj4(OGRSpatialReferenceH hSRS, const char *pszProj4)
11551
11552
0
{
11553
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromProj4", OGRERR_FAILURE);
11554
11555
0
    return OGRSpatialReference::FromHandle(hSRS)->importFromProj4(pszProj4);
11556
0
}
11557
11558
/************************************************************************/
11559
/*                          importFromProj4()                           */
11560
/************************************************************************/
11561
11562
/**
11563
 * \brief Import PROJ coordinate string.
11564
 *
11565
 * The OGRSpatialReference is initialized from the passed PROJs style
11566
 * coordinate system string.
11567
 *
11568
 * Example:
11569
 *   pszProj4 = "+proj=utm +zone=11 +datum=WGS84"
11570
 *
11571
 * It is also possible to import "+init=epsg:n" style definitions. Those are
11572
 * a legacy syntax that should be avoided in the future. In particular they will
11573
 * result in CRS objects whose axis order might not correspond to the official
11574
 * EPSG axis order.
11575
 *
11576
 * This method is the equivalent of the C function OSRImportFromProj4().
11577
 *
11578
 * @param pszProj4 the PROJ style string.
11579
 *
11580
 * @return OGRERR_NONE on success or OGRERR_CORRUPT_DATA on failure.
11581
 */
11582
11583
OGRErr OGRSpatialReference::importFromProj4(const char *pszProj4)
11584
11585
292k
{
11586
292k
    TAKE_OPTIONAL_LOCK();
11587
11588
292k
    if (strlen(pszProj4) >= 10000)
11589
431
    {
11590
431
        CPLError(CE_Failure, CPLE_AppDefined, "Too long PROJ string");
11591
431
        return OGRERR_CORRUPT_DATA;
11592
431
    }
11593
11594
    /* -------------------------------------------------------------------- */
11595
    /*      Clear any existing definition.                                  */
11596
    /* -------------------------------------------------------------------- */
11597
291k
    Clear();
11598
11599
291k
    CPLString osProj4(pszProj4);
11600
291k
    if (osProj4.find("type=crs") == std::string::npos)
11601
252k
    {
11602
252k
        osProj4 += " +type=crs";
11603
252k
    }
11604
11605
291k
    if (osProj4.find("+init=epsg:") != std::string::npos &&
11606
17.5k
        getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr)
11607
17.5k
    {
11608
17.5k
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
11609
17.5k
                     "+init=epsg:XXXX syntax is deprecated. It might return "
11610
17.5k
                     "a CRS with a non-EPSG compliant axis order.");
11611
17.5k
    }
11612
291k
    proj_context_use_proj4_init_rules(d->getPROJContext(), true);
11613
291k
    d->setPjCRS(proj_create(d->getPROJContext(), osProj4.c_str()));
11614
291k
    proj_context_use_proj4_init_rules(d->getPROJContext(), false);
11615
291k
    return d->m_pj_crs ? OGRERR_NONE : OGRERR_CORRUPT_DATA;
11616
292k
}
11617
11618
/************************************************************************/
11619
/*                          OSRExportToProj4()                          */
11620
/************************************************************************/
11621
/**
11622
 * \brief Export coordinate system in PROJ.4 legacy format.
11623
 *
11624
 * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
11625
 * PROJ &gt;= 6 is significantly different from earlier versions. In particular
11626
 * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
11627
 * will be missing most of the time. PROJ strings to encode CRS should be
11628
 * considered as a legacy solution. Using a AUTHORITY:CODE or WKT representation
11629
 * is the recommended way.
11630
 *
11631
 * This function is the same as OGRSpatialReference::exportToProj4().
11632
 */
11633
OGRErr CPL_STDCALL OSRExportToProj4(OGRSpatialReferenceH hSRS,
11634
                                    char **ppszReturn)
11635
11636
0
{
11637
0
    VALIDATE_POINTER1(hSRS, "OSRExportToProj4", OGRERR_FAILURE);
11638
11639
0
    *ppszReturn = nullptr;
11640
11641
0
    return OGRSpatialReference::FromHandle(hSRS)->exportToProj4(ppszReturn);
11642
0
}
11643
11644
/************************************************************************/
11645
/*                           exportToProj4()                            */
11646
/************************************************************************/
11647
11648
/**
11649
 * \brief Export coordinate system in PROJ.4 legacy format.
11650
 *
11651
 * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
11652
 * PROJ &gt;= 6 is significantly different from earlier versions. In particular
11653
 * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
11654
 * will be missing most of the time. PROJ strings to encode CRS should be
11655
 * considered as a a legacy solution. Using a AUTHORITY:CODE or WKT
11656
 * representation is the recommended way.
11657
 *
11658
 * Converts the loaded coordinate reference system into PROJ format
11659
 * to the extent possible.  The string returned in ppszProj4 should be
11660
 * deallocated by the caller with CPLFree() when no longer needed.
11661
 *
11662
 * LOCAL_CS coordinate systems are not translatable.  An empty string
11663
 * will be returned along with OGRERR_NONE.
11664
 *
11665
 * Special processing for Transverse Mercator:
11666
 * Starting with GDAL 3.0, if the OSR_USE_APPROX_TMERC configuration option is
11667
 * set to YES, the PROJ definition built from the SRS will use the +approx flag
11668
 * for the tmerc and utm projection methods, rather than the more accurate
11669
 * method.
11670
 *
11671
 * Starting with GDAL 3.0.3, this method will try to add a +towgs84 parameter,
11672
 * if there's none attached yet to the SRS and if the SRS has a EPSG code.
11673
 * See the AddGuessedTOWGS84() method for how this +towgs84 parameter may be
11674
 * added. This automatic addition may be disabled by setting the
11675
 * OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4 configuration option to NO.
11676
 *
11677
 * This method is the equivalent of the C function OSRExportToProj4().
11678
 *
11679
 * @param ppszProj4 pointer to which dynamically allocated PROJ definition
11680
 * will be assigned.
11681
 *
11682
 * @return OGRERR_NONE on success or an error code on failure.
11683
 */
11684
11685
OGRErr OGRSpatialReference::exportToProj4(char **ppszProj4) const
11686
11687
1.57k
{
11688
    // In the past calling this method was thread-safe, even if we never
11689
    // guaranteed it. Now proj_as_proj_string() will cache the result
11690
    // internally, so this is no longer thread-safe.
11691
1.57k
    std::lock_guard oLock(d->m_mutex);
11692
11693
1.57k
    d->refreshProjObj();
11694
1.57k
    if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
11695
7
    {
11696
7
        *ppszProj4 = CPLStrdup("");
11697
7
        return OGRERR_FAILURE;
11698
7
    }
11699
11700
    // OSR_USE_ETMERC is here just for legacy
11701
1.56k
    bool bForceApproxTMerc = false;
11702
1.56k
    const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr);
11703
1.56k
    if (pszUseETMERC && pszUseETMERC[0])
11704
0
    {
11705
0
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
11706
0
                     "OSR_USE_ETMERC is a legacy configuration option, which "
11707
0
                     "now has only effect when set to NO (YES is the default). "
11708
0
                     "Use OSR_USE_APPROX_TMERC=YES instead");
11709
0
        bForceApproxTMerc = !CPLTestBool(pszUseETMERC);
11710
0
    }
11711
1.56k
    else
11712
1.56k
    {
11713
1.56k
        const char *pszUseApproxTMERC =
11714
1.56k
            CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr);
11715
1.56k
        if (pszUseApproxTMERC && pszUseApproxTMERC[0])
11716
0
        {
11717
0
            bForceApproxTMerc = CPLTestBool(pszUseApproxTMERC);
11718
0
        }
11719
1.56k
    }
11720
1.56k
    const char *options[] = {
11721
1.56k
        bForceApproxTMerc ? "USE_APPROX_TMERC=YES" : nullptr, nullptr};
11722
11723
1.56k
    const char *projString = proj_as_proj_string(
11724
1.56k
        d->getPROJContext(), d->m_pj_crs, PJ_PROJ_4, options);
11725
11726
1.56k
    PJ *boundCRS = nullptr;
11727
1.56k
    if (projString &&
11728
1.56k
        (strstr(projString, "+datum=") == nullptr ||
11729
613
         d->m_pjType == PJ_TYPE_COMPOUND_CRS) &&
11730
954
        CPLTestBool(
11731
954
            CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4", "YES")))
11732
954
    {
11733
954
        boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
11734
954
            d->getPROJContext(), d->m_pj_crs, true,
11735
954
            strstr(projString, "+datum=") == nullptr);
11736
954
        if (boundCRS)
11737
548
        {
11738
548
            projString = proj_as_proj_string(d->getPROJContext(), boundCRS,
11739
548
                                             PJ_PROJ_4, options);
11740
548
        }
11741
954
    }
11742
11743
1.56k
    if (projString == nullptr)
11744
4
    {
11745
4
        *ppszProj4 = CPLStrdup("");
11746
4
        proj_destroy(boundCRS);
11747
4
        return OGRERR_FAILURE;
11748
4
    }
11749
1.56k
    *ppszProj4 = CPLStrdup(projString);
11750
1.56k
    proj_destroy(boundCRS);
11751
1.56k
    char *pszTypeCrs = strstr(*ppszProj4, " +type=crs");
11752
1.56k
    if (pszTypeCrs)
11753
1.56k
        *pszTypeCrs = '\0';
11754
1.56k
    return OGRERR_NONE;
11755
1.56k
}
11756
11757
/************************************************************************/
11758
/*                            morphToESRI()                             */
11759
/************************************************************************/
11760
/**
11761
 * \brief Convert in place to ESRI WKT format.
11762
 *
11763
 * The value nodes of this coordinate system are modified in various manners
11764
 * more closely map onto the ESRI concept of WKT format.  This includes
11765
 * renaming a variety of projections and arguments, and stripping out
11766
 * nodes note recognised by ESRI (like AUTHORITY and AXIS).
11767
 *
11768
 * \note Since GDAL 3.0, this function has only user-visible effects at
11769
 * exportToWkt() time. It is recommended to use instead exportToWkt(char**,
11770
 * const char* const char*) const with options having FORMAT=WKT1_ESRI.
11771
 *
11772
 * This does the same as the C function OSRMorphToESRI().
11773
 *
11774
 * @return OGRERR_NONE unless something goes badly wrong.
11775
 * @deprecated
11776
 */
11777
11778
OGRErr OGRSpatialReference::morphToESRI()
11779
11780
1.37k
{
11781
1.37k
    TAKE_OPTIONAL_LOCK();
11782
11783
1.37k
    d->refreshProjObj();
11784
1.37k
    d->setMorphToESRI(true);
11785
11786
1.37k
    return OGRERR_NONE;
11787
1.37k
}
11788
11789
/************************************************************************/
11790
/*                           OSRMorphToESRI()                           */
11791
/************************************************************************/
11792
11793
/**
11794
 * \brief Convert in place to ESRI WKT format.
11795
 *
11796
 * This function is the same as the C++ method
11797
 * OGRSpatialReference::morphToESRI().
11798
 */
11799
OGRErr OSRMorphToESRI(OGRSpatialReferenceH hSRS)
11800
11801
1.34k
{
11802
1.34k
    VALIDATE_POINTER1(hSRS, "OSRMorphToESRI", OGRERR_FAILURE);
11803
11804
1.34k
    return OGRSpatialReference::FromHandle(hSRS)->morphToESRI();
11805
1.34k
}
11806
11807
/************************************************************************/
11808
/*                           morphFromESRI()                            */
11809
/************************************************************************/
11810
11811
/**
11812
 * \brief Convert in place from ESRI WKT format.
11813
 *
11814
 * The value notes of this coordinate system are modified in various manners
11815
 * to adhere more closely to the WKT standard.  This mostly involves
11816
 * translating a variety of ESRI names for projections, arguments and
11817
 * datums to "standard" names, as defined by Adam Gawne-Cain's reference
11818
 * translation of EPSG to WKT for the CT specification.
11819
 *
11820
 * \note Since GDAL 3.0, this function is essentially a no-operation, since
11821
 * morphing from ESRI is automatically done by importFromWkt(). Its only
11822
 * effect is to undo the effect of a potential prior call to morphToESRI().
11823
 *
11824
 * This does the same as the C function OSRMorphFromESRI().
11825
 *
11826
 * @return OGRERR_NONE unless something goes badly wrong.
11827
 * @deprecated
11828
 */
11829
11830
OGRErr OGRSpatialReference::morphFromESRI()
11831
11832
1.09k
{
11833
1.09k
    TAKE_OPTIONAL_LOCK();
11834
11835
1.09k
    d->refreshProjObj();
11836
1.09k
    d->setMorphToESRI(false);
11837
11838
1.09k
    return OGRERR_NONE;
11839
1.09k
}
11840
11841
/************************************************************************/
11842
/*                          OSRMorphFromESRI()                          */
11843
/************************************************************************/
11844
11845
/**
11846
 * \brief Convert in place from ESRI WKT format.
11847
 *
11848
 * This function is the same as the C++ method
11849
 * OGRSpatialReference::morphFromESRI().
11850
 */
11851
OGRErr OSRMorphFromESRI(OGRSpatialReferenceH hSRS)
11852
11853
1.09k
{
11854
1.09k
    VALIDATE_POINTER1(hSRS, "OSRMorphFromESRI", OGRERR_FAILURE);
11855
11856
1.09k
    return OGRSpatialReference::FromHandle(hSRS)->morphFromESRI();
11857
1.09k
}
11858
11859
/************************************************************************/
11860
/*                            FindMatches()                             */
11861
/************************************************************************/
11862
11863
/**
11864
 * \brief Try to identify a match between the passed SRS and a related SRS
11865
 * in a catalog.
11866
 *
11867
 * Matching may be partial, or may fail.
11868
 * Returned entries will be sorted by decreasing match confidence (first
11869
 * entry has the highest match confidence).
11870
 *
11871
 * The exact way matching is done may change in future versions. Starting with
11872
 * GDAL 3.0, it relies on PROJ' proj_identify() function.
11873
 *
11874
 * This method is the same as OSRFindMatches().
11875
 *
11876
 * @param papszOptions NULL terminated list of options or NULL
11877
 * @param pnEntries Output parameter. Number of values in the returned array.
11878
 * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
11879
 * will be allocated to an array of *pnEntries whose values between 0 and 100
11880
 * indicate the confidence in the match. 100 is the highest confidence level.
11881
 * The array must be freed with CPLFree().
11882
 *
11883
 * @return an array of SRS that match the passed SRS, or NULL. Must be freed
11884
 * with OSRFreeSRSArray()
11885
 *
11886
 *
11887
 * @see OGRSpatialReference::FindBestMatch()
11888
 */
11889
OGRSpatialReferenceH *
11890
OGRSpatialReference::FindMatches(CSLConstList papszOptions, int *pnEntries,
11891
                                 int **ppanMatchConfidence) const
11892
25.9k
{
11893
25.9k
    TAKE_OPTIONAL_LOCK();
11894
11895
25.9k
    CPL_IGNORE_RET_VAL(papszOptions);
11896
11897
25.9k
    if (pnEntries)
11898
25.9k
        *pnEntries = 0;
11899
25.9k
    if (ppanMatchConfidence)
11900
25.9k
        *ppanMatchConfidence = nullptr;
11901
11902
25.9k
    d->refreshProjObj();
11903
25.9k
    if (!d->m_pj_crs)
11904
1.57k
        return nullptr;
11905
11906
24.3k
    int *panConfidence = nullptr;
11907
24.3k
    auto ctxt = d->getPROJContext();
11908
24.3k
    auto list =
11909
24.3k
        proj_identify(ctxt, d->m_pj_crs, nullptr, nullptr, &panConfidence);
11910
24.3k
    if (!list)
11911
34
        return nullptr;
11912
11913
24.3k
    const int nMatches = proj_list_get_count(list);
11914
11915
24.3k
    if (pnEntries)
11916
24.3k
        *pnEntries = static_cast<int>(nMatches);
11917
24.3k
    OGRSpatialReferenceH *pahRet = static_cast<OGRSpatialReferenceH *>(
11918
24.3k
        CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
11919
24.3k
    if (ppanMatchConfidence)
11920
24.3k
    {
11921
24.3k
        *ppanMatchConfidence =
11922
24.3k
            static_cast<int *>(CPLMalloc(sizeof(int) * (nMatches + 1)));
11923
24.3k
    }
11924
11925
24.3k
    bool bSortAgain = false;
11926
11927
580k
    for (int i = 0; i < nMatches; i++)
11928
556k
    {
11929
556k
        PJ *obj = proj_list_get(ctxt, list, i);
11930
556k
        CPLAssert(obj);
11931
556k
        OGRSpatialReference *poSRS = new OGRSpatialReference();
11932
556k
        poSRS->d->setPjCRS(obj);
11933
556k
        pahRet[i] = ToHandle(poSRS);
11934
11935
        // Identify matches that only differ by axis order
11936
556k
        if (panConfidence[i] == 50 && GetAxesCount() == 2 &&
11937
279
            poSRS->GetAxesCount() == 2 &&
11938
279
            GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2})
11939
279
        {
11940
279
            OGRAxisOrientation eThisAxis0 = OAO_Other;
11941
279
            OGRAxisOrientation eThisAxis1 = OAO_Other;
11942
279
            OGRAxisOrientation eSRSAxis0 = OAO_Other;
11943
279
            OGRAxisOrientation eSRSAxis1 = OAO_Other;
11944
279
            GetAxis(nullptr, 0, &eThisAxis0);
11945
279
            GetAxis(nullptr, 1, &eThisAxis1);
11946
279
            poSRS->GetAxis(nullptr, 0, &eSRSAxis0);
11947
279
            poSRS->GetAxis(nullptr, 1, &eSRSAxis1);
11948
279
            if (eThisAxis0 == OAO_East && eThisAxis1 == OAO_North &&
11949
215
                eSRSAxis0 == OAO_North && eSRSAxis1 == OAO_East)
11950
60
            {
11951
60
                auto pj_crs_normalized =
11952
60
                    proj_normalize_for_visualization(ctxt, poSRS->d->m_pj_crs);
11953
60
                if (pj_crs_normalized)
11954
60
                {
11955
60
                    if (proj_is_equivalent_to(d->m_pj_crs, pj_crs_normalized,
11956
60
                                              PJ_COMP_EQUIVALENT))
11957
17
                    {
11958
17
                        bSortAgain = true;
11959
17
                        panConfidence[i] = 90;
11960
17
                        poSRS->SetDataAxisToSRSAxisMapping({2, 1});
11961
17
                    }
11962
60
                    proj_destroy(pj_crs_normalized);
11963
60
                }
11964
60
            }
11965
279
        }
11966
11967
556k
        if (ppanMatchConfidence)
11968
556k
            (*ppanMatchConfidence)[i] = panConfidence[i];
11969
556k
    }
11970
11971
24.3k
    if (bSortAgain)
11972
17
    {
11973
17
        std::vector<int> anIndices;
11974
64
        for (int i = 0; i < nMatches; ++i)
11975
47
            anIndices.push_back(i);
11976
11977
17
        std::stable_sort(anIndices.begin(), anIndices.end(),
11978
17
                         [&panConfidence](int i, int j)
11979
30
                         { return panConfidence[i] > panConfidence[j]; });
11980
11981
17
        OGRSpatialReferenceH *pahRetSorted =
11982
17
            static_cast<OGRSpatialReferenceH *>(
11983
17
                CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
11984
64
        for (int i = 0; i < nMatches; ++i)
11985
47
        {
11986
47
            pahRetSorted[i] = pahRet[anIndices[i]];
11987
47
            if (ppanMatchConfidence)
11988
47
                (*ppanMatchConfidence)[i] = panConfidence[anIndices[i]];
11989
47
        }
11990
17
        CPLFree(pahRet);
11991
17
        pahRet = pahRetSorted;
11992
17
    }
11993
11994
24.3k
    pahRet[nMatches] = nullptr;
11995
24.3k
    proj_list_destroy(list);
11996
24.3k
    proj_int_list_destroy(panConfidence);
11997
11998
24.3k
    return pahRet;
11999
24.3k
}
12000
12001
/************************************************************************/
12002
/*                          importFromEPSGA()                           */
12003
/************************************************************************/
12004
12005
/**
12006
 * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
12007
 * code.
12008
 *
12009
 * This method will initialize the spatial reference based on the
12010
 * passed in EPSG CRS code found in the PROJ database.
12011
 *
12012
 * Since GDAL 3.0, this method is identical to importFromEPSG().
12013
 *
12014
 * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
12015
 * 7-parameter Helmert transformation to WGS84 when there is one and only one
12016
 * such method available for the CRS. This behavior might not always be
12017
 * desirable, so starting with GDAL 3.0.3, this is no longer done unless
12018
 * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
12019
 * The AddGuessedTOWGS84() method can also be used for that purpose.
12020
 *
12021
 * The method will also by default substitute a deprecated EPSG code by its
12022
 * non-deprecated replacement. If this behavior is not desired, the
12023
 * OSR_USE_NON_DEPRECATED configuration option can be set to NO.
12024
 *
12025
 * This method is the same as the C function OSRImportFromEPSGA().
12026
 *
12027
 * @param nCode a CRS code.
12028
 *
12029
 * @return OGRERR_NONE on success, or an error code on failure.
12030
 */
12031
12032
OGRErr OGRSpatialReference::importFromEPSGA(int nCode)
12033
12034
138k
{
12035
138k
    TAKE_OPTIONAL_LOCK();
12036
12037
138k
    Clear();
12038
12039
138k
    const char *pszUseNonDeprecated =
12040
138k
        CPLGetConfigOption("OSR_USE_NON_DEPRECATED", nullptr);
12041
138k
    const bool bUseNonDeprecated =
12042
138k
        CPLTestBool(pszUseNonDeprecated ? pszUseNonDeprecated : "YES");
12043
138k
    const bool bAddTOWGS84 = CPLTestBool(
12044
138k
        CPLGetConfigOption("OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG", "NO"));
12045
138k
    auto tlsCache = OSRGetProjTLSCache();
12046
138k
    if (tlsCache)
12047
138k
    {
12048
138k
        auto cachedObj =
12049
138k
            tlsCache->GetPJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84);
12050
138k
        if (cachedObj)
12051
92.5k
        {
12052
92.5k
            d->setPjCRS(cachedObj);
12053
92.5k
            return OGRERR_NONE;
12054
92.5k
        }
12055
138k
    }
12056
12057
45.4k
    CPLString osCode;
12058
45.4k
    osCode.Printf("%d", nCode);
12059
45.4k
    PJ *obj;
12060
45.4k
    constexpr int FIRST_NON_DEPRECATED_ESRI_CODE = 53001;
12061
45.4k
    if (nCode < FIRST_NON_DEPRECATED_ESRI_CODE)
12062
41.0k
    {
12063
41.0k
        obj = proj_create_from_database(d->getPROJContext(), "EPSG",
12064
41.0k
                                        osCode.c_str(), PJ_CATEGORY_CRS, true,
12065
41.0k
                                        nullptr);
12066
41.0k
        if (!obj)
12067
36.7k
        {
12068
36.7k
            return OGRERR_FAILURE;
12069
36.7k
        }
12070
41.0k
    }
12071
4.43k
    else
12072
4.43k
    {
12073
        // Likely to be an ESRI CRS...
12074
4.43k
        CPLErr eLastErrorType = CE_None;
12075
4.43k
        CPLErrorNum eLastErrorNum = CPLE_None;
12076
4.43k
        std::string osLastErrorMsg;
12077
4.43k
        bool bIsESRI = false;
12078
4.43k
        {
12079
4.43k
            CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
12080
4.43k
            CPLErrorReset();
12081
4.43k
            obj = proj_create_from_database(d->getPROJContext(), "EPSG",
12082
4.43k
                                            osCode.c_str(), PJ_CATEGORY_CRS,
12083
4.43k
                                            true, nullptr);
12084
4.43k
            if (!obj)
12085
4.43k
            {
12086
4.43k
                eLastErrorType = CPLGetLastErrorType();
12087
4.43k
                eLastErrorNum = CPLGetLastErrorNo();
12088
4.43k
                osLastErrorMsg = CPLGetLastErrorMsg();
12089
4.43k
                obj = proj_create_from_database(d->getPROJContext(), "ESRI",
12090
4.43k
                                                osCode.c_str(), PJ_CATEGORY_CRS,
12091
4.43k
                                                true, nullptr);
12092
4.43k
                if (obj)
12093
261
                    bIsESRI = true;
12094
4.43k
            }
12095
4.43k
        }
12096
4.43k
        if (!obj)
12097
4.17k
        {
12098
4.17k
            if (eLastErrorType != CE_None)
12099
4.17k
                CPLError(eLastErrorType, eLastErrorNum, "%s",
12100
4.17k
                         osLastErrorMsg.c_str());
12101
4.17k
            return OGRERR_FAILURE;
12102
4.17k
        }
12103
261
        if (bIsESRI)
12104
261
        {
12105
261
            CPLError(CE_Warning, CPLE_AppDefined,
12106
261
                     "EPSG:%d is not a valid CRS code, but ESRI:%d is. "
12107
261
                     "Assuming ESRI:%d was meant",
12108
261
                     nCode, nCode, nCode);
12109
261
        }
12110
261
    }
12111
12112
4.57k
    if (bUseNonDeprecated && proj_is_deprecated(obj))
12113
708
    {
12114
708
        auto list = proj_get_non_deprecated(d->getPROJContext(), obj);
12115
708
        if (list)
12116
708
        {
12117
708
            const auto count = proj_list_get_count(list);
12118
708
            if (count == 1)
12119
559
            {
12120
559
                auto nonDeprecated =
12121
559
                    proj_list_get(d->getPROJContext(), list, 0);
12122
559
                if (nonDeprecated)
12123
559
                {
12124
559
                    if (pszUseNonDeprecated == nullptr)
12125
559
                    {
12126
559
                        const char *pszNewAuth =
12127
559
                            proj_get_id_auth_name(nonDeprecated, 0);
12128
559
                        const char *pszNewCode =
12129
559
                            proj_get_id_code(nonDeprecated, 0);
12130
559
                        CPLError(CE_Warning, CPLE_AppDefined,
12131
559
                                 "CRS EPSG:%d is deprecated. "
12132
559
                                 "Its non-deprecated replacement %s:%s "
12133
559
                                 "will be used instead. "
12134
559
                                 "To use the original CRS, set the "
12135
559
                                 "OSR_USE_NON_DEPRECATED "
12136
559
                                 "configuration option to NO.",
12137
559
                                 nCode, pszNewAuth ? pszNewAuth : "(null)",
12138
559
                                 pszNewCode ? pszNewCode : "(null)");
12139
559
                    }
12140
559
                    proj_destroy(obj);
12141
559
                    obj = nonDeprecated;
12142
559
                }
12143
559
            }
12144
708
        }
12145
708
        proj_list_destroy(list);
12146
708
    }
12147
12148
4.57k
    if (bAddTOWGS84)
12149
0
    {
12150
0
        auto boundCRS = proj_crs_create_bound_crs_to_WGS84(d->getPROJContext(),
12151
0
                                                           obj, nullptr);
12152
0
        if (boundCRS)
12153
0
        {
12154
0
            proj_destroy(obj);
12155
0
            obj = boundCRS;
12156
0
        }
12157
0
    }
12158
12159
4.57k
    d->setPjCRS(obj);
12160
12161
4.57k
    if (tlsCache)
12162
4.57k
    {
12163
4.57k
        tlsCache->CachePJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84,
12164
4.57k
                                     obj);
12165
4.57k
    }
12166
12167
4.57k
    return OGRERR_NONE;
12168
45.4k
}
12169
12170
/************************************************************************/
12171
/*                         AddGuessedTOWGS84()                          */
12172
/************************************************************************/
12173
12174
/**
12175
 * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
12176
 * to WGS84.
12177
 *
12178
 * This method try to attach a 3-parameter or 7-parameter Helmert transformation
12179
 * to WGS84 when there is one and only one such method available for the CRS.
12180
 * Note: this is more restrictive to how GDAL < 3 worked.
12181
 *
12182
 * This method is the same as the C function OSRAddGuessedTOWGS84().
12183
 *
12184
 * @return OGRERR_NONE on success, or an error code on failure (the CRS has
12185
 * already a transformation to WGS84 or none matching could be found).
12186
 *
12187
 * @since GDAL 3.0.3
12188
 */
12189
OGRErr OGRSpatialReference::AddGuessedTOWGS84()
12190
38
{
12191
38
    TAKE_OPTIONAL_LOCK();
12192
12193
38
    d->refreshProjObj();
12194
38
    if (!d->m_pj_crs)
12195
0
        return OGRERR_FAILURE;
12196
38
    auto boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
12197
38
        d->getPROJContext(), d->m_pj_crs, false, true);
12198
38
    if (!boundCRS)
12199
0
    {
12200
0
        return OGRERR_FAILURE;
12201
0
    }
12202
38
    d->setPjCRS(boundCRS);
12203
38
    return OGRERR_NONE;
12204
38
}
12205
12206
/************************************************************************/
12207
/*                         OSRImportFromEPSGA()                         */
12208
/************************************************************************/
12209
12210
/**
12211
 * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
12212
 * to WGS84.
12213
 *
12214
 * This function is the same as OGRSpatialReference::AddGuessedTOWGS84().
12215
 *
12216
 * @since GDAL 3.0.3
12217
 */
12218
12219
OGRErr OSRAddGuessedTOWGS84(OGRSpatialReferenceH hSRS)
12220
12221
0
{
12222
0
    VALIDATE_POINTER1(hSRS, "OSRAddGuessedTOWGS84", OGRERR_FAILURE);
12223
12224
0
    return OGRSpatialReference::FromHandle(hSRS)->AddGuessedTOWGS84();
12225
0
}
12226
12227
/************************************************************************/
12228
/*                         OSRImportFromEPSGA()                         */
12229
/************************************************************************/
12230
12231
/**
12232
 * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
12233
 * code.
12234
 *
12235
 * This function is the same as OGRSpatialReference::importFromEPSGA().
12236
 */
12237
12238
OGRErr CPL_STDCALL OSRImportFromEPSGA(OGRSpatialReferenceH hSRS, int nCode)
12239
12240
0
{
12241
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromEPSGA", OGRERR_FAILURE);
12242
12243
0
    return OGRSpatialReference::FromHandle(hSRS)->importFromEPSGA(nCode);
12244
0
}
12245
12246
/************************************************************************/
12247
/*                           importFromEPSG()                           */
12248
/************************************************************************/
12249
12250
/**
12251
 * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
12252
 * code.
12253
 *
12254
 * This method will initialize the spatial reference based on the
12255
 * passed in EPSG CRS code found in the PROJ database.
12256
 *
12257
 * This method is the same as the C function OSRImportFromEPSG().
12258
 *
12259
 * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
12260
 * 7-parameter Helmert transformation to WGS84 when there is one and only one
12261
 * such method available for the CRS. This behavior might not always be
12262
 * desirable, so starting with GDAL 3.0.3, this is no longer done unless
12263
 * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
12264
 *
12265
 * @param nCode a GCS or PCS code from the horizontal coordinate system table.
12266
 *
12267
 * @return OGRERR_NONE on success, or an error code on failure.
12268
 */
12269
12270
OGRErr OGRSpatialReference::importFromEPSG(int nCode)
12271
12272
138k
{
12273
138k
    return importFromEPSGA(nCode);
12274
138k
}
12275
12276
/************************************************************************/
12277
/*                         OSRImportFromEPSG()                          */
12278
/************************************************************************/
12279
12280
/**
12281
 * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
12282
 * code.
12283
 *
12284
 * This function is the same as OGRSpatialReference::importFromEPSG().
12285
 */
12286
12287
OGRErr CPL_STDCALL OSRImportFromEPSG(OGRSpatialReferenceH hSRS, int nCode)
12288
12289
0
{
12290
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromEPSG", OGRERR_FAILURE);
12291
12292
0
    return OGRSpatialReference::FromHandle(hSRS)->importFromEPSG(nCode);
12293
0
}
12294
12295
/************************************************************************/
12296
/*                        EPSGTreatsAsLatLong()                         */
12297
/************************************************************************/
12298
12299
/**
12300
 * \brief This method returns TRUE if this geographic coordinate
12301
 * system should be treated as having lat/long coordinate ordering.
12302
 *
12303
 * Currently this returns TRUE for all geographic coordinate systems
12304
 * with axes set defining it as lat, long (prior to GDAL 3.10, it
12305
 * also checked that the CRS had belonged to EPSG authority, but this check
12306
 * has now been removed).
12307
 *
12308
 * \note Important change of behavior since GDAL 3.0. In previous versions,
12309
 * geographic CRS imported with importFromEPSG() would cause this method to
12310
 * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
12311
 * is now equivalent to importFromEPSGA().
12312
 *
12313
 * FALSE will be returned for all coordinate systems that are not geographic,
12314
 * or whose axes ordering is not latitude, longitude.
12315
 *
12316
 * This method is the same as the C function OSREPSGTreatsAsLatLong().
12317
 *
12318
 * @return TRUE or FALSE.
12319
 */
12320
12321
int OGRSpatialReference::EPSGTreatsAsLatLong() const
12322
12323
7.94k
{
12324
7.94k
    TAKE_OPTIONAL_LOCK();
12325
12326
7.94k
    if (!IsGeographic())
12327
6.86k
        return FALSE;
12328
12329
1.08k
    d->demoteFromBoundCRS();
12330
12331
1.08k
    bool ret = false;
12332
1.08k
    if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
12333
349
    {
12334
349
        auto horizCRS =
12335
349
            proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
12336
349
        if (horizCRS)
12337
349
        {
12338
349
            auto cs =
12339
349
                proj_crs_get_coordinate_system(d->getPROJContext(), horizCRS);
12340
349
            if (cs)
12341
335
            {
12342
335
                const char *pszDirection = nullptr;
12343
335
                if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
12344
335
                                          nullptr, &pszDirection, nullptr,
12345
335
                                          nullptr, nullptr, nullptr))
12346
335
                {
12347
335
                    if (EQUAL(pszDirection, "north"))
12348
292
                    {
12349
292
                        ret = true;
12350
292
                    }
12351
335
                }
12352
12353
335
                proj_destroy(cs);
12354
335
            }
12355
12356
349
            proj_destroy(horizCRS);
12357
349
        }
12358
349
    }
12359
736
    else
12360
736
    {
12361
736
        auto cs =
12362
736
            proj_crs_get_coordinate_system(d->getPROJContext(), d->m_pj_crs);
12363
736
        if (cs)
12364
736
        {
12365
736
            const char *pszDirection = nullptr;
12366
736
            if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
12367
736
                                      nullptr, &pszDirection, nullptr, nullptr,
12368
736
                                      nullptr, nullptr))
12369
736
            {
12370
736
                if (EQUAL(pszDirection, "north"))
12371
438
                {
12372
438
                    ret = true;
12373
438
                }
12374
736
            }
12375
12376
736
            proj_destroy(cs);
12377
736
        }
12378
736
    }
12379
1.08k
    d->undoDemoteFromBoundCRS();
12380
12381
1.08k
    return ret;
12382
7.94k
}
12383
12384
/************************************************************************/
12385
/*                       OSREPSGTreatsAsLatLong()                       */
12386
/************************************************************************/
12387
12388
/**
12389
 * \brief This function returns TRUE if this geographic coordinate
12390
 * system should be treated as having lat/long coordinate ordering.
12391
 *
12392
 * This function is the same as OGRSpatialReference::OSREPSGTreatsAsLatLong().
12393
 */
12394
12395
int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH hSRS)
12396
12397
0
{
12398
0
    VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsLatLong", OGRERR_FAILURE);
12399
12400
0
    return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsLatLong();
12401
0
}
12402
12403
/************************************************************************/
12404
/*                    EPSGTreatsAsNorthingEasting()                     */
12405
/************************************************************************/
12406
12407
/**
12408
 * \brief This method returns TRUE if this projected coordinate
12409
 * system should be treated as having northing/easting coordinate ordering.
12410
 *
12411
 * Currently this returns TRUE for all projected coordinate systems
12412
 * with axes set defining it as northing, easting (prior to GDAL 3.10, it
12413
 * also checked that the CRS had belonged to EPSG authority, but this check
12414
 * has now been removed).
12415
 *
12416
 * \note Important change of behavior since GDAL 3.0. In previous versions,
12417
 * projected CRS with northing, easting axis order imported with
12418
 * importFromEPSG() would cause this method to
12419
 * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
12420
 * is now equivalent to importFromEPSGA().
12421
 *
12422
 * FALSE will be returned for all coordinate systems that are not projected,
12423
 * or whose axes ordering is not northing, easting.
12424
 *
12425
 * This method is the same as the C function EPSGTreatsAsNorthingEasting().
12426
 *
12427
 * @return TRUE or FALSE.
12428
 *
12429
 */
12430
12431
int OGRSpatialReference::EPSGTreatsAsNorthingEasting() const
12432
12433
7.21k
{
12434
7.21k
    TAKE_OPTIONAL_LOCK();
12435
12436
7.21k
    if (!IsProjected())
12437
3.82k
        return FALSE;
12438
12439
3.39k
    d->demoteFromBoundCRS();
12440
3.39k
    PJ *projCRS;
12441
3.39k
    const auto ctxt = d->getPROJContext();
12442
3.39k
    if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
12443
795
    {
12444
795
        projCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
12445
795
        if (!projCRS || proj_get_type(projCRS) != PJ_TYPE_PROJECTED_CRS)
12446
39
        {
12447
39
            d->undoDemoteFromBoundCRS();
12448
39
            proj_destroy(projCRS);
12449
39
            return FALSE;
12450
39
        }
12451
795
    }
12452
2.59k
    else
12453
2.59k
    {
12454
2.59k
        projCRS = proj_clone(ctxt, d->m_pj_crs);
12455
2.59k
    }
12456
12457
3.35k
    bool ret = false;
12458
3.35k
    auto cs = proj_crs_get_coordinate_system(ctxt, projCRS);
12459
3.35k
    proj_destroy(projCRS);
12460
3.35k
    d->undoDemoteFromBoundCRS();
12461
12462
3.35k
    if (cs)
12463
3.35k
    {
12464
3.35k
        ret = isNorthEastAxisOrder(ctxt, cs);
12465
3.35k
        proj_destroy(cs);
12466
3.35k
    }
12467
12468
3.35k
    return ret;
12469
3.39k
}
12470
12471
/************************************************************************/
12472
/*                   OSREPSGTreatsAsNorthingEasting()                   */
12473
/************************************************************************/
12474
12475
/**
12476
 * \brief This function returns TRUE if this projected coordinate
12477
 * system should be treated as having northing/easting coordinate ordering.
12478
 *
12479
 * This function is the same as
12480
 * OGRSpatialReference::EPSGTreatsAsNorthingEasting().
12481
 *
12482
 */
12483
12484
int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH hSRS)
12485
12486
0
{
12487
0
    VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsNorthingEasting", OGRERR_FAILURE);
12488
12489
0
    return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsNorthingEasting();
12490
0
}
12491
12492
/************************************************************************/
12493
/*                     ImportFromESRIWisconsinWKT()                     */
12494
/*                                                                      */
12495
/*      Search a ESRI State Plane WKT and import it.                    */
12496
/************************************************************************/
12497
12498
// This is only used by the HFA driver and somewhat dubious we really need that
12499
// Coming from an old ESRI merge
12500
12501
OGRErr OGRSpatialReference::ImportFromESRIWisconsinWKT(const char *prjName,
12502
                                                       double centralMeridian,
12503
                                                       double latOfOrigin,
12504
                                                       const char *unitsName,
12505
                                                       const char *crsName)
12506
15
{
12507
15
    TAKE_OPTIONAL_LOCK();
12508
12509
15
    if (centralMeridian < -93 || centralMeridian > -87)
12510
3
        return OGRERR_FAILURE;
12511
12
    if (latOfOrigin < 40 || latOfOrigin > 47)
12512
2
        return OGRERR_FAILURE;
12513
12514
    // If the CS name is known.
12515
10
    if (!prjName && !unitsName && crsName)
12516
0
    {
12517
0
        const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
12518
0
        PJ_OBJ_LIST *list = proj_create_from_name(
12519
0
            d->getPROJContext(), "ESRI", crsName, &type, 1, false, 1, nullptr);
12520
0
        if (list)
12521
0
        {
12522
0
            if (proj_list_get_count(list) == 1)
12523
0
            {
12524
0
                auto crs = proj_list_get(d->getPROJContext(), list, 0);
12525
0
                if (crs)
12526
0
                {
12527
0
                    Clear();
12528
0
                    d->setPjCRS(crs);
12529
0
                    proj_list_destroy(list);
12530
0
                    return OGRERR_NONE;
12531
0
                }
12532
0
            }
12533
0
            proj_list_destroy(list);
12534
0
        }
12535
0
        return OGRERR_FAILURE;
12536
0
    }
12537
12538
10
    if (prjName == nullptr || unitsName == nullptr)
12539
0
    {
12540
0
        return OGRERR_FAILURE;
12541
0
    }
12542
12543
10
    const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
12544
10
    PJ_OBJ_LIST *list = proj_create_from_name(d->getPROJContext(), "ESRI",
12545
10
                                              "NAD_1983_HARN_WISCRS_", &type, 1,
12546
10
                                              true, 0, nullptr);
12547
10
    if (list)
12548
10
    {
12549
10
        const auto listSize = proj_list_get_count(list);
12550
318
        for (int i = 0; i < listSize; i++)
12551
311
        {
12552
311
            auto crs = proj_list_get(d->getPROJContext(), list, i);
12553
311
            if (!crs)
12554
0
            {
12555
0
                continue;
12556
0
            }
12557
12558
311
            auto conv = proj_crs_get_coordoperation(d->getPROJContext(), crs);
12559
311
            if (!conv)
12560
0
            {
12561
0
                proj_destroy(crs);
12562
0
                continue;
12563
0
            }
12564
311
            const char *pszMethodCode = nullptr;
12565
311
            proj_coordoperation_get_method_info(
12566
311
                d->getPROJContext(), conv, nullptr, nullptr, &pszMethodCode);
12567
311
            const int nMethodCode = atoi(pszMethodCode ? pszMethodCode : "0");
12568
311
            if (!((EQUAL(prjName, SRS_PT_TRANSVERSE_MERCATOR) &&
12569
227
                   nMethodCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) ||
12570
150
                  (EQUAL(prjName, "Lambert_Conformal_Conic") &&
12571
84
                   nMethodCode ==
12572
84
                       EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)))
12573
126
            {
12574
126
                proj_destroy(crs);
12575
126
                proj_destroy(conv);
12576
126
                continue;
12577
126
            }
12578
12579
185
            auto coordSys =
12580
185
                proj_crs_get_coordinate_system(d->getPROJContext(), crs);
12581
185
            if (!coordSys)
12582
0
            {
12583
0
                proj_destroy(crs);
12584
0
                proj_destroy(conv);
12585
0
                continue;
12586
0
            }
12587
12588
185
            double dfConvFactor = 0.0;
12589
185
            proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
12590
185
                                  nullptr, nullptr, &dfConvFactor, nullptr,
12591
185
                                  nullptr, nullptr);
12592
185
            proj_destroy(coordSys);
12593
12594
185
            if ((EQUAL(unitsName, "meters") && dfConvFactor != 1.0) ||
12595
141
                (!EQUAL(unitsName, "meters") &&
12596
103
                 std::fabs(dfConvFactor - CPLAtof(SRS_UL_US_FOOT_CONV)) >
12597
103
                     1e-10))
12598
95
            {
12599
95
                proj_destroy(crs);
12600
95
                proj_destroy(conv);
12601
95
                continue;
12602
95
            }
12603
12604
90
            int idx_lat = proj_coordoperation_get_param_index(
12605
90
                d->getPROJContext(), conv,
12606
90
                EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
12607
90
            double valueLat = -1000;
12608
90
            proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lat,
12609
90
                                          nullptr, nullptr, nullptr, &valueLat,
12610
90
                                          nullptr, nullptr, nullptr, nullptr,
12611
90
                                          nullptr, nullptr);
12612
90
            int idx_lon = proj_coordoperation_get_param_index(
12613
90
                d->getPROJContext(), conv,
12614
90
                EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN);
12615
90
            double valueLong = -1000;
12616
90
            proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lon,
12617
90
                                          nullptr, nullptr, nullptr, &valueLong,
12618
90
                                          nullptr, nullptr, nullptr, nullptr,
12619
90
                                          nullptr, nullptr);
12620
90
            if (std::fabs(centralMeridian - valueLong) <= 1e-10 &&
12621
11
                std::fabs(latOfOrigin - valueLat) <= 1e-10)
12622
3
            {
12623
3
                Clear();
12624
3
                d->setPjCRS(crs);
12625
3
                proj_list_destroy(list);
12626
3
                proj_destroy(conv);
12627
3
                return OGRERR_NONE;
12628
3
            }
12629
12630
87
            proj_destroy(crs);
12631
87
            proj_destroy(conv);
12632
87
        }
12633
7
        proj_list_destroy(list);
12634
7
    }
12635
12636
7
    return OGRERR_FAILURE;
12637
10
}
12638
12639
/************************************************************************/
12640
/*                       GetAxisMappingStrategy()                       */
12641
/************************************************************************/
12642
12643
/** \brief Return the data axis to CRS axis mapping strategy.
12644
 *
12645
 * <ul>
12646
 * <li>OAMS_TRADITIONAL_GIS_ORDER means that for geographic CRS with
12647
 *     lat/long order, the data will still be long/lat ordered. Similarly for
12648
 *     a projected CRS with northing/easting order, the data will still be
12649
 *     easting/northing ordered.
12650
 * <li>OAMS_AUTHORITY_COMPLIANT means that the data axis will be identical to
12651
 *     the CRS axis.
12652
 * <li>OAMS_CUSTOM means that the data axis are customly defined with
12653
 *     SetDataAxisToSRSAxisMapping()
12654
 * </ul>
12655
 * @return the data axis to CRS axis mapping strategy.
12656
 * @since GDAL 3.0
12657
 */
12658
OSRAxisMappingStrategy OGRSpatialReference::GetAxisMappingStrategy() const
12659
0
{
12660
0
    TAKE_OPTIONAL_LOCK();
12661
12662
0
    return d->m_axisMappingStrategy;
12663
0
}
12664
12665
/************************************************************************/
12666
/*                     OSRGetAxisMappingStrategy()                      */
12667
/************************************************************************/
12668
12669
/** \brief Return the data axis to CRS axis mapping strategy.
12670
 *
12671
 * See OGRSpatialReference::GetAxisMappingStrategy()
12672
 * @since GDAL 3.0
12673
 */
12674
OSRAxisMappingStrategy OSRGetAxisMappingStrategy(OGRSpatialReferenceH hSRS)
12675
0
{
12676
0
    VALIDATE_POINTER1(hSRS, "OSRGetAxisMappingStrategy", OAMS_CUSTOM);
12677
12678
0
    return OGRSpatialReference::FromHandle(hSRS)->GetAxisMappingStrategy();
12679
0
}
12680
12681
/************************************************************************/
12682
/*                       SetAxisMappingStrategy()                       */
12683
/************************************************************************/
12684
12685
/** \brief Set the data axis to CRS axis mapping strategy.
12686
 *
12687
 * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
12688
 * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
12689
 * later being the default value when the option is not set) to control the
12690
 * value of the data axis to CRS axis mapping strategy when a
12691
 * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
12692
 * override this default value.
12693
 *
12694
 * See OGRSpatialReference::GetAxisMappingStrategy()
12695
 * @since GDAL 3.0
12696
 */
12697
void OGRSpatialReference::SetAxisMappingStrategy(
12698
    OSRAxisMappingStrategy strategy)
12699
1.62M
{
12700
1.62M
    TAKE_OPTIONAL_LOCK();
12701
12702
1.62M
    d->m_axisMappingStrategy = strategy;
12703
1.62M
    d->refreshAxisMapping();
12704
1.62M
}
12705
12706
/************************************************************************/
12707
/*                     OSRSetAxisMappingStrategy()                      */
12708
/************************************************************************/
12709
12710
/** \brief Set the data axis to CRS axis mapping strategy.
12711
 *
12712
 * See OGRSpatialReference::SetAxisMappingStrategy()
12713
 * @since GDAL 3.0
12714
 */
12715
void OSRSetAxisMappingStrategy(OGRSpatialReferenceH hSRS,
12716
                               OSRAxisMappingStrategy strategy)
12717
534
{
12718
534
    VALIDATE_POINTER0(hSRS, "OSRSetAxisMappingStrategy");
12719
12720
534
    OGRSpatialReference::FromHandle(hSRS)->SetAxisMappingStrategy(strategy);
12721
534
}
12722
12723
/************************************************************************/
12724
/*                    GetDataAxisToSRSAxisMapping()                     */
12725
/************************************************************************/
12726
12727
/** \brief Return the data axis to SRS axis mapping.
12728
 *
12729
 * The number of elements of the vector will be the number of axis of the CRS.
12730
 * Values start at 1.
12731
 *
12732
 * If m = GetDataAxisToSRSAxisMapping(), then m[0] is the data axis number
12733
 * for the first axis of the CRS.
12734
 *
12735
 * @since GDAL 3.0
12736
 */
12737
const std::vector<int> &OGRSpatialReference::GetDataAxisToSRSAxisMapping() const
12738
208k
{
12739
208k
    TAKE_OPTIONAL_LOCK();
12740
12741
208k
    return d->m_axisMapping;
12742
208k
}
12743
12744
/************************************************************************/
12745
/*                   OSRGetDataAxisToSRSAxisMapping()                   */
12746
/************************************************************************/
12747
12748
/** \brief Return the data axis to SRS axis mapping.
12749
 *
12750
 * See OGRSpatialReference::GetDataAxisToSRSAxisMapping()
12751
 *
12752
 * @since GDAL 3.0
12753
 */
12754
const int *OSRGetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
12755
                                          int *pnCount)
12756
0
{
12757
0
    VALIDATE_POINTER1(hSRS, "OSRGetDataAxisToSRSAxisMapping", nullptr);
12758
0
    VALIDATE_POINTER1(pnCount, "OSRGetDataAxisToSRSAxisMapping", nullptr);
12759
12760
0
    const auto &v =
12761
0
        OGRSpatialReference::FromHandle(hSRS)->GetDataAxisToSRSAxisMapping();
12762
0
    *pnCount = static_cast<int>(v.size());
12763
0
    return v.data();
12764
0
}
12765
12766
/************************************************************************/
12767
/*                    SetDataAxisToSRSAxisMapping()                     */
12768
/************************************************************************/
12769
12770
/** \brief Set a custom data axis to CRS axis mapping.
12771
 *
12772
 * The number of elements of the mapping vector should be the number of axis
12773
 * of the CRS (as returned by GetAxesCount()) (although this method does not
12774
 * check that, beyond checking there are at least 2 elements, so that this
12775
 * method and setting the CRS can be done in any order).
12776
 * This is taken into account by OGRCoordinateTransformation to transform the
12777
 * order of coordinates to the order expected by the CRS before
12778
 * transformation, and back to the data order after transformation.
12779
 *
12780
 * The mapping[i] value (one based) represents the data axis number for the i(th)
12781
 * axis of the CRS. A negative value can also be used to ask for a sign
12782
 * reversal during coordinate transformation (to deal with northing vs southing,
12783
 * easting vs westing, heights vs depths).
12784
 *
12785
 * When used with OGRCoordinateTransformation,
12786
 * - the only valid values for mapping[0] (data axis number for the first axis
12787
 *   of the CRS) are 1, 2, -1, -2.
12788
 * - the only valid values for mapping[1] (data axis number for the second axis
12789
 *   of the CRS) are 1, 2, -1, -2.
12790
 *  - the only valid values mapping[2] are 3 or -3.
12791
 * Note: this method does not validate the values of mapping[].
12792
 *
12793
 * mapping=[2,1] typically expresses the inversion of axis between the data
12794
 * axis and the CRS axis for a 2D CRS.
12795
 *
12796
 * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
12797
 *
12798
 * This is the same as the C function OSRSetDataAxisToSRSAxisMapping().
12799
 *
12800
 * @param mapping The new data axis to CRS axis mapping.
12801
 *
12802
 * @since GDAL 3.0
12803
 * @see OGRSpatialReference::GetDataAxisToSRSAxisMapping()
12804
 */
12805
OGRErr OGRSpatialReference::SetDataAxisToSRSAxisMapping(
12806
    const std::vector<int> &mapping)
12807
48.7k
{
12808
48.7k
    TAKE_OPTIONAL_LOCK();
12809
12810
48.7k
    if (mapping.size() < 2)
12811
102
        return OGRERR_FAILURE;
12812
48.6k
    d->m_axisMappingStrategy = OAMS_CUSTOM;
12813
48.6k
    d->m_axisMapping = mapping;
12814
48.6k
    return OGRERR_NONE;
12815
48.7k
}
12816
12817
/************************************************************************/
12818
/*                   OSRSetDataAxisToSRSAxisMapping()                   */
12819
/************************************************************************/
12820
12821
/** \brief Set a custom data axis to CRS axis mapping.
12822
 *
12823
 * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
12824
 *
12825
 * This is the same as the C++ method
12826
 * OGRSpatialReference::SetDataAxisToSRSAxisMapping()
12827
 *
12828
 * @since GDAL 3.1
12829
 */
12830
OGRErr OSRSetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
12831
                                      int nMappingSize, const int *panMapping)
12832
0
{
12833
0
    VALIDATE_POINTER1(hSRS, "OSRSetDataAxisToSRSAxisMapping", OGRERR_FAILURE);
12834
0
    VALIDATE_POINTER1(panMapping, "OSRSetDataAxisToSRSAxisMapping",
12835
0
                      OGRERR_FAILURE);
12836
12837
0
    if (nMappingSize < 0)
12838
0
        return OGRERR_FAILURE;
12839
12840
0
    std::vector<int> mapping(nMappingSize);
12841
0
    if (nMappingSize)
12842
0
        memcpy(&mapping[0], panMapping, nMappingSize * sizeof(int));
12843
0
    return OGRSpatialReference::FromHandle(hSRS)->SetDataAxisToSRSAxisMapping(
12844
0
        mapping);
12845
0
}
12846
12847
/************************************************************************/
12848
/*                            GetAreaOfUse()                            */
12849
/************************************************************************/
12850
12851
/** \brief Return the area of use of the CRS.
12852
 *
12853
 * This method is the same as the OSRGetAreaOfUse() function.
12854
 *
12855
 * @param pdfWestLongitudeDeg Pointer to a double to receive the western-most
12856
 * longitude, expressed in degree. Might be NULL. If the returned value is
12857
 * -1000, the bounding box is unknown.
12858
 * @param pdfSouthLatitudeDeg Pointer to a double to receive the southern-most
12859
 * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
12860
 * the bounding box is unknown.
12861
 * @param pdfEastLongitudeDeg Pointer to a double to receive the eastern-most
12862
 * longitude, expressed in degree. Might be NULL. If the returned value is
12863
 * -1000, the bounding box is unknown.
12864
 * @param pdfNorthLatitudeDeg Pointer to a double to receive the northern-most
12865
 * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
12866
 * the bounding box is unknown.
12867
 * @param ppszAreaName Pointer to a string to receive the name of the area of
12868
 * use. Might be NULL. Note that *ppszAreaName is short-lived and might be
12869
 * invalidated by further calls.
12870
 * @return true in case of success
12871
 * @since GDAL 3.0
12872
 */
12873
bool OGRSpatialReference::GetAreaOfUse(double *pdfWestLongitudeDeg,
12874
                                       double *pdfSouthLatitudeDeg,
12875
                                       double *pdfEastLongitudeDeg,
12876
                                       double *pdfNorthLatitudeDeg,
12877
                                       const char **ppszAreaName) const
12878
24
{
12879
24
    TAKE_OPTIONAL_LOCK();
12880
12881
24
    d->refreshProjObj();
12882
24
    if (!d->m_pj_crs)
12883
21
    {
12884
21
        return false;
12885
21
    }
12886
3
    d->demoteFromBoundCRS();
12887
3
    const char *pszAreaName = nullptr;
12888
3
    int bSuccess = proj_get_area_of_use(
12889
3
        d->getPROJContext(), d->m_pj_crs, pdfWestLongitudeDeg,
12890
3
        pdfSouthLatitudeDeg, pdfEastLongitudeDeg, pdfNorthLatitudeDeg,
12891
3
        &pszAreaName);
12892
3
    d->undoDemoteFromBoundCRS();
12893
3
    d->m_osAreaName = pszAreaName ? pszAreaName : "";
12894
3
    if (ppszAreaName)
12895
0
        *ppszAreaName = d->m_osAreaName.c_str();
12896
3
    return CPL_TO_BOOL(bSuccess);
12897
24
}
12898
12899
/************************************************************************/
12900
/*                            GetAreaOfUse()                            */
12901
/************************************************************************/
12902
12903
/** \brief Return the area of use of the CRS.
12904
 *
12905
 * This function is the same as the OGRSpatialReference::GetAreaOfUse() method.
12906
 *
12907
 * @since GDAL 3.0
12908
 */
12909
int OSRGetAreaOfUse(OGRSpatialReferenceH hSRS, double *pdfWestLongitudeDeg,
12910
                    double *pdfSouthLatitudeDeg, double *pdfEastLongitudeDeg,
12911
                    double *pdfNorthLatitudeDeg, const char **ppszAreaName)
12912
0
{
12913
0
    VALIDATE_POINTER1(hSRS, "OSRGetAreaOfUse", FALSE);
12914
12915
0
    return OGRSpatialReference::FromHandle(hSRS)->GetAreaOfUse(
12916
0
        pdfWestLongitudeDeg, pdfSouthLatitudeDeg, pdfEastLongitudeDeg,
12917
0
        pdfNorthLatitudeDeg, ppszAreaName);
12918
0
}
12919
12920
/************************************************************************/
12921
/*                   OSRGetCRSInfoListFromDatabase()                    */
12922
/************************************************************************/
12923
12924
/** \brief Enumerate CRS objects from the database.
12925
 *
12926
 * The returned object is an array of OSRCRSInfo* pointers, whose last
12927
 * entry is NULL. This array should be freed with OSRDestroyCRSInfoList()
12928
 *
12929
 * @param pszAuthName Authority name, used to restrict the search.
12930
 * Or NULL for all authorities.
12931
 * @param params Additional criteria. Must be set to NULL for now.
12932
 * @param pnOutResultCount Output parameter pointing to an integer to receive
12933
 * the size of the result list. Might be NULL
12934
 * @return an array of OSRCRSInfo* pointers to be freed with
12935
 * OSRDestroyCRSInfoList(), or NULL in case of error.
12936
 *
12937
 * @since GDAL 3.0
12938
 */
12939
OSRCRSInfo **
12940
OSRGetCRSInfoListFromDatabase(const char *pszAuthName,
12941
                              CPL_UNUSED const OSRCRSListParameters *params,
12942
                              int *pnOutResultCount)
12943
0
{
12944
0
    int nResultCount = 0;
12945
0
    auto projList = proj_get_crs_info_list_from_database(
12946
0
        OSRGetProjTLSContext(), pszAuthName, nullptr, &nResultCount);
12947
0
    if (pnOutResultCount)
12948
0
        *pnOutResultCount = nResultCount;
12949
0
    if (!projList)
12950
0
    {
12951
0
        return nullptr;
12952
0
    }
12953
0
    auto res = new OSRCRSInfo *[nResultCount + 1];
12954
0
    for (int i = 0; i < nResultCount; i++)
12955
0
    {
12956
0
        res[i] = new OSRCRSInfo;
12957
0
        res[i]->pszAuthName = projList[i]->auth_name
12958
0
                                  ? CPLStrdup(projList[i]->auth_name)
12959
0
                                  : nullptr;
12960
0
        res[i]->pszCode =
12961
0
            projList[i]->code ? CPLStrdup(projList[i]->code) : nullptr;
12962
0
        res[i]->pszName =
12963
0
            projList[i]->name ? CPLStrdup(projList[i]->name) : nullptr;
12964
0
        res[i]->eType = OSR_CRS_TYPE_OTHER;
12965
0
        switch (projList[i]->type)
12966
0
        {
12967
0
            case PJ_TYPE_GEOGRAPHIC_2D_CRS:
12968
0
                res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_2D;
12969
0
                break;
12970
0
            case PJ_TYPE_GEOGRAPHIC_3D_CRS:
12971
0
                res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_3D;
12972
0
                break;
12973
0
            case PJ_TYPE_GEOCENTRIC_CRS:
12974
0
                res[i]->eType = OSR_CRS_TYPE_GEOCENTRIC;
12975
0
                break;
12976
0
            case PJ_TYPE_PROJECTED_CRS:
12977
0
                res[i]->eType = OSR_CRS_TYPE_PROJECTED;
12978
0
                break;
12979
0
            case PJ_TYPE_VERTICAL_CRS:
12980
0
                res[i]->eType = OSR_CRS_TYPE_VERTICAL;
12981
0
                break;
12982
0
            case PJ_TYPE_COMPOUND_CRS:
12983
0
                res[i]->eType = OSR_CRS_TYPE_COMPOUND;
12984
0
                break;
12985
0
            default:
12986
0
                break;
12987
0
        }
12988
0
        res[i]->bDeprecated = projList[i]->deprecated;
12989
0
        res[i]->bBboxValid = projList[i]->bbox_valid;
12990
0
        res[i]->dfWestLongitudeDeg = projList[i]->west_lon_degree;
12991
0
        res[i]->dfSouthLatitudeDeg = projList[i]->south_lat_degree;
12992
0
        res[i]->dfEastLongitudeDeg = projList[i]->east_lon_degree;
12993
0
        res[i]->dfNorthLatitudeDeg = projList[i]->north_lat_degree;
12994
0
        res[i]->pszAreaName = projList[i]->area_name
12995
0
                                  ? CPLStrdup(projList[i]->area_name)
12996
0
                                  : nullptr;
12997
0
        res[i]->pszProjectionMethod =
12998
0
            projList[i]->projection_method_name
12999
0
                ? CPLStrdup(projList[i]->projection_method_name)
13000
0
                : nullptr;
13001
0
#if PROJ_AT_LEAST_VERSION(8, 1, 0)
13002
0
        res[i]->pszCelestialBodyName =
13003
0
            projList[i]->celestial_body_name
13004
0
                ? CPLStrdup(projList[i]->celestial_body_name)
13005
0
                : nullptr;
13006
#else
13007
        res[i]->pszCelestialBodyName =
13008
            res[i]->pszAuthName && EQUAL(res[i]->pszAuthName, "EPSG")
13009
                ? CPLStrdup("Earth")
13010
                : nullptr;
13011
#endif
13012
0
    }
13013
0
    res[nResultCount] = nullptr;
13014
0
    proj_crs_info_list_destroy(projList);
13015
0
    return res;
13016
0
}
13017
13018
/************************************************************************/
13019
/*                       OSRDestroyCRSInfoList()                        */
13020
/************************************************************************/
13021
13022
/** \brief Destroy the result returned by
13023
 * OSRGetCRSInfoListFromDatabase().
13024
 *
13025
 * @since GDAL 3.0
13026
 */
13027
void OSRDestroyCRSInfoList(OSRCRSInfo **list)
13028
0
{
13029
0
    if (list)
13030
0
    {
13031
0
        for (int i = 0; list[i] != nullptr; i++)
13032
0
        {
13033
0
            CPLFree(list[i]->pszAuthName);
13034
0
            CPLFree(list[i]->pszCode);
13035
0
            CPLFree(list[i]->pszName);
13036
0
            CPLFree(list[i]->pszAreaName);
13037
0
            CPLFree(list[i]->pszProjectionMethod);
13038
0
            CPLFree(list[i]->pszCelestialBodyName);
13039
0
            delete list[i];
13040
0
        }
13041
0
        delete[] list;
13042
0
    }
13043
0
}
13044
13045
/************************************************************************/
13046
/*                  OSRGetAuthorityListFromDatabase()                   */
13047
/************************************************************************/
13048
13049
/** \brief Return the list of CRS authorities used in the PROJ database.
13050
 *
13051
 * Such as "EPSG", "ESRI", "PROJ", "IGNF", "IAU_2015", etc.
13052
 *
13053
 * This is a direct mapping of https://proj.org/en/latest/development/reference/functions.html#c.proj_get_authorities_from_database
13054
 *
13055
 * @return nullptr in case of error, or a NULL terminated list of strings to
13056
 * free with CSLDestroy()
13057
 * @since GDAL 3.10
13058
 */
13059
char **OSRGetAuthorityListFromDatabase()
13060
0
{
13061
0
    PROJ_STRING_LIST list =
13062
0
        proj_get_authorities_from_database(OSRGetProjTLSContext());
13063
0
    if (!list)
13064
0
    {
13065
0
        return nullptr;
13066
0
    }
13067
0
    int count = 0;
13068
0
    while (list[count])
13069
0
        ++count;
13070
0
    char **res = static_cast<char **>(CPLCalloc(count + 1, sizeof(char *)));
13071
0
    for (int i = 0; i < count; ++i)
13072
0
        res[i] = CPLStrdup(list[i]);
13073
0
    proj_string_list_destroy(list);
13074
0
    return res;
13075
0
}
13076
13077
/************************************************************************/
13078
/*                 UpdateCoordinateSystemFromGeogCRS()                  */
13079
/************************************************************************/
13080
13081
/*! @cond Doxygen_Suppress */
13082
/** \brief Used by gt_wkt_srs.cpp to create projected 3D CRS. Internal use only
13083
 *
13084
 * @since GDAL 3.1
13085
 */
13086
void OGRSpatialReference::UpdateCoordinateSystemFromGeogCRS()
13087
2
{
13088
2
    TAKE_OPTIONAL_LOCK();
13089
13090
2
    d->refreshProjObj();
13091
2
    if (!d->m_pj_crs)
13092
0
        return;
13093
2
    if (d->m_pjType != PJ_TYPE_PROJECTED_CRS)
13094
0
        return;
13095
2
    if (GetAxesCount() == 3)
13096
0
        return;
13097
2
    auto ctxt = d->getPROJContext();
13098
2
    auto baseCRS = proj_crs_get_geodetic_crs(ctxt, d->m_pj_crs);
13099
2
    if (!baseCRS)
13100
0
        return;
13101
2
    auto baseCRSCS = proj_crs_get_coordinate_system(ctxt, baseCRS);
13102
2
    if (!baseCRSCS)
13103
0
    {
13104
0
        proj_destroy(baseCRS);
13105
0
        return;
13106
0
    }
13107
2
    if (proj_cs_get_axis_count(ctxt, baseCRSCS) != 3)
13108
1
    {
13109
1
        proj_destroy(baseCRSCS);
13110
1
        proj_destroy(baseCRS);
13111
1
        return;
13112
1
    }
13113
1
    auto projCS = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
13114
1
    if (!projCS || proj_cs_get_axis_count(ctxt, projCS) != 2)
13115
0
    {
13116
0
        proj_destroy(baseCRSCS);
13117
0
        proj_destroy(baseCRS);
13118
0
        proj_destroy(projCS);
13119
0
        return;
13120
0
    }
13121
13122
1
    PJ_AXIS_DESCRIPTION axis[3];
13123
4
    for (int i = 0; i < 3; i++)
13124
3
    {
13125
3
        const char *name = nullptr;
13126
3
        const char *abbreviation = nullptr;
13127
3
        const char *direction = nullptr;
13128
3
        double unit_conv_factor = 0;
13129
3
        const char *unit_name = nullptr;
13130
3
        proj_cs_get_axis_info(ctxt, i < 2 ? projCS : baseCRSCS, i, &name,
13131
3
                              &abbreviation, &direction, &unit_conv_factor,
13132
3
                              &unit_name, nullptr, nullptr);
13133
3
        axis[i].name = CPLStrdup(name);
13134
3
        axis[i].abbreviation = CPLStrdup(abbreviation);
13135
3
        axis[i].direction = CPLStrdup(direction);
13136
3
        axis[i].unit_name = CPLStrdup(unit_name);
13137
3
        axis[i].unit_conv_factor = unit_conv_factor;
13138
3
        axis[i].unit_type = PJ_UT_LINEAR;
13139
3
    }
13140
1
    proj_destroy(baseCRSCS);
13141
1
    proj_destroy(projCS);
13142
1
    auto cs = proj_create_cs(ctxt, PJ_CS_TYPE_CARTESIAN, 3, axis);
13143
4
    for (int i = 0; i < 3; i++)
13144
3
    {
13145
3
        CPLFree(axis[i].name);
13146
3
        CPLFree(axis[i].abbreviation);
13147
3
        CPLFree(axis[i].direction);
13148
3
        CPLFree(axis[i].unit_name);
13149
3
    }
13150
1
    if (!cs)
13151
0
    {
13152
0
        proj_destroy(baseCRS);
13153
0
        return;
13154
0
    }
13155
1
    auto conversion = proj_crs_get_coordoperation(ctxt, d->m_pj_crs);
13156
1
    auto crs = proj_create_projected_crs(ctxt, d->getProjCRSName(), baseCRS,
13157
1
                                         conversion, cs);
13158
1
    proj_destroy(baseCRS);
13159
1
    proj_destroy(conversion);
13160
1
    proj_destroy(cs);
13161
1
    d->setPjCRS(crs);
13162
1
}
13163
13164
/*! @endcond */
13165
13166
/************************************************************************/
13167
/*                            PromoteTo3D()                             */
13168
/************************************************************************/
13169
13170
/** \brief "Promotes" a 2D CRS to a 3D CRS one.
13171
 *
13172
 * The new axis will be ellipsoidal height, oriented upwards, and with metre
13173
 * units.
13174
 *
13175
 * @param pszName New name for the CRS. If set to NULL, the previous name will
13176
 * be used.
13177
 * @return OGRERR_NONE if no error occurred.
13178
 * @since GDAL 3.1 and PROJ 6.3
13179
 */
13180
OGRErr OGRSpatialReference::PromoteTo3D(const char *pszName)
13181
0
{
13182
0
    TAKE_OPTIONAL_LOCK();
13183
13184
0
    d->refreshProjObj();
13185
0
    if (!d->m_pj_crs)
13186
0
        return OGRERR_FAILURE;
13187
0
    auto newPj =
13188
0
        proj_crs_promote_to_3D(d->getPROJContext(), pszName, d->m_pj_crs);
13189
0
    if (!newPj)
13190
0
        return OGRERR_FAILURE;
13191
0
    d->setPjCRS(newPj);
13192
0
    return OGRERR_NONE;
13193
0
}
13194
13195
/************************************************************************/
13196
/*                           OSRPromoteTo3D()                           */
13197
/************************************************************************/
13198
13199
/** \brief "Promotes" a 2D CRS to a 3D CRS one.
13200
 *
13201
 * See OGRSpatialReference::PromoteTo3D()
13202
 *
13203
 * @since GDAL 3.1 and PROJ 6.3
13204
 */
13205
OGRErr OSRPromoteTo3D(OGRSpatialReferenceH hSRS, const char *pszName)
13206
0
{
13207
0
    VALIDATE_POINTER1(hSRS, "OSRPromoteTo3D", OGRERR_FAILURE);
13208
13209
0
    return OGRSpatialReference::FromHandle(hSRS)->PromoteTo3D(pszName);
13210
0
}
13211
13212
/************************************************************************/
13213
/*                             DemoteTo2D()                             */
13214
/************************************************************************/
13215
13216
/** \brief "Demote" a 3D CRS to a 2D CRS one.
13217
 *
13218
 * @param pszName New name for the CRS. If set to NULL, the previous name will
13219
 * be used.
13220
 * @return OGRERR_NONE if no error occurred.
13221
 * @since GDAL 3.2 and PROJ 6.3
13222
 */
13223
OGRErr OGRSpatialReference::DemoteTo2D(const char *pszName)
13224
7
{
13225
7
    TAKE_OPTIONAL_LOCK();
13226
13227
7
    d->refreshProjObj();
13228
7
    if (!d->m_pj_crs)
13229
0
        return OGRERR_FAILURE;
13230
7
    auto newPj =
13231
7
        proj_crs_demote_to_2D(d->getPROJContext(), pszName, d->m_pj_crs);
13232
7
    if (!newPj)
13233
0
        return OGRERR_FAILURE;
13234
7
    d->setPjCRS(newPj);
13235
7
    return OGRERR_NONE;
13236
7
}
13237
13238
/************************************************************************/
13239
/*                           OSRDemoteTo2D()                            */
13240
/************************************************************************/
13241
13242
/** \brief "Demote" a 3D CRS to a 2D CRS one.
13243
 *
13244
 * See OGRSpatialReference::DemoteTo2D()
13245
 *
13246
 * @since GDAL 3.2 and PROJ 6.3
13247
 */
13248
OGRErr OSRDemoteTo2D(OGRSpatialReferenceH hSRS, const char *pszName)
13249
0
{
13250
0
    VALIDATE_POINTER1(hSRS, "OSRDemoteTo2D", OGRERR_FAILURE);
13251
13252
0
    return OGRSpatialReference::FromHandle(hSRS)->DemoteTo2D(pszName);
13253
0
}
13254
13255
/************************************************************************/
13256
/*                           GetEPSGGeogCS()                            */
13257
/************************************************************************/
13258
13259
/** Try to establish what the EPSG code for this coordinate systems
13260
 * GEOGCS might be.  Returns -1 if no reasonable guess can be made.
13261
 *
13262
 * @return EPSG code
13263
 */
13264
13265
int OGRSpatialReference::GetEPSGGeogCS() const
13266
13267
1.82k
{
13268
1.82k
    TAKE_OPTIONAL_LOCK();
13269
13270
    /* -------------------------------------------------------------------- */
13271
    /*      Check axis order.                                               */
13272
    /* -------------------------------------------------------------------- */
13273
1.82k
    auto poGeogCRS = std::unique_ptr<OGRSpatialReference>(CloneGeogCS());
13274
1.82k
    if (!poGeogCRS)
13275
52
        return -1;
13276
13277
1.77k
    bool ret = false;
13278
1.77k
    poGeogCRS->d->demoteFromBoundCRS();
13279
1.77k
    auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
13280
1.77k
                                             poGeogCRS->d->m_pj_crs);
13281
1.77k
    poGeogCRS->d->undoDemoteFromBoundCRS();
13282
1.77k
    if (cs)
13283
1.77k
    {
13284
1.77k
        const char *pszDirection = nullptr;
13285
1.77k
        if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr, nullptr,
13286
1.77k
                                  &pszDirection, nullptr, nullptr, nullptr,
13287
1.77k
                                  nullptr))
13288
1.77k
        {
13289
1.77k
            if (EQUAL(pszDirection, "north"))
13290
1.76k
            {
13291
1.76k
                ret = true;
13292
1.76k
            }
13293
1.77k
        }
13294
13295
1.77k
        proj_destroy(cs);
13296
1.77k
    }
13297
1.77k
    if (!ret)
13298
11
        return -1;
13299
13300
    /* -------------------------------------------------------------------- */
13301
    /*      Do we already have it?                                          */
13302
    /* -------------------------------------------------------------------- */
13303
1.76k
    const char *pszAuthName = GetAuthorityName("GEOGCS");
13304
1.76k
    if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg"))
13305
21
        return atoi(GetAuthorityCode("GEOGCS"));
13306
13307
    /* -------------------------------------------------------------------- */
13308
    /*      Get the datum and geogcs names.                                 */
13309
    /* -------------------------------------------------------------------- */
13310
13311
1.74k
    const char *pszGEOGCS = GetAttrValue("GEOGCS");
13312
1.74k
    const char *pszDatum = GetAttrValue("DATUM");
13313
13314
    // We can only operate on coordinate systems with a geogcs.
13315
1.74k
    OGRSpatialReference oSRSTmp;
13316
1.74k
    if (pszGEOGCS == nullptr || pszDatum == nullptr)
13317
116
    {
13318
        // Calling GetAttrValue("GEOGCS") will fail on a CRS that can't be
13319
        // export to WKT1, so try to extract the geographic CRS through PROJ
13320
        // API with CopyGeogCSFrom() and get the nodes' values from it.
13321
116
        oSRSTmp.CopyGeogCSFrom(this);
13322
116
        pszGEOGCS = oSRSTmp.GetAttrValue("GEOGCS");
13323
116
        pszDatum = oSRSTmp.GetAttrValue("DATUM");
13324
116
        if (pszGEOGCS == nullptr || pszDatum == nullptr)
13325
57
        {
13326
57
            return -1;
13327
57
        }
13328
116
    }
13329
13330
    // Lookup geographic CRS name
13331
1.68k
    const PJ_TYPE type = PJ_TYPE_GEOGRAPHIC_2D_CRS;
13332
1.68k
    PJ_OBJ_LIST *list = proj_create_from_name(
13333
1.68k
        d->getPROJContext(), nullptr, pszGEOGCS, &type, 1, false, 1, nullptr);
13334
1.68k
    if (list)
13335
1.68k
    {
13336
1.68k
        const auto listSize = proj_list_get_count(list);
13337
1.68k
        if (listSize == 1)
13338
103
        {
13339
103
            auto crs = proj_list_get(d->getPROJContext(), list, 0);
13340
103
            if (crs)
13341
103
            {
13342
103
                pszAuthName = proj_get_id_auth_name(crs, 0);
13343
103
                const char *pszCode = proj_get_id_code(crs, 0);
13344
103
                if (pszAuthName && pszCode && EQUAL(pszAuthName, "EPSG"))
13345
103
                {
13346
103
                    const int nCode = atoi(pszCode);
13347
103
                    proj_destroy(crs);
13348
103
                    proj_list_destroy(list);
13349
103
                    return nCode;
13350
103
                }
13351
0
                proj_destroy(crs);
13352
0
            }
13353
103
        }
13354
1.58k
        proj_list_destroy(list);
13355
1.58k
    }
13356
13357
    /* -------------------------------------------------------------------- */
13358
    /*      Is this a "well known" geographic coordinate system?            */
13359
    /* -------------------------------------------------------------------- */
13360
1.58k
    const bool bWGS = strstr(pszGEOGCS, "WGS") != nullptr ||
13361
1.51k
                      strstr(pszDatum, "WGS") ||
13362
1.50k
                      strstr(pszGEOGCS, "World Geodetic System") ||
13363
1.50k
                      strstr(pszGEOGCS, "World_Geodetic_System") ||
13364
1.49k
                      strstr(pszDatum, "World Geodetic System") ||
13365
1.49k
                      strstr(pszDatum, "World_Geodetic_System");
13366
13367
1.58k
    const bool bNAD = strstr(pszGEOGCS, "NAD") != nullptr ||
13368
1.48k
                      strstr(pszDatum, "NAD") ||
13369
1.48k
                      strstr(pszGEOGCS, "North American") ||
13370
1.46k
                      strstr(pszGEOGCS, "North_American") ||
13371
1.45k
                      strstr(pszDatum, "North American") ||
13372
1.45k
                      strstr(pszDatum, "North_American");
13373
13374
1.58k
    if (bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")))
13375
53
        return 4326;
13376
13377
1.53k
    if (bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")))
13378
14
        return 4322;
13379
13380
    // This is questionable as there are several 'flavors' of NAD83 that
13381
    // are not the same as 4269
13382
1.51k
    if (bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")))
13383
18
        return 4269;
13384
13385
1.50k
    if (bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")))
13386
50
        return 4267;
13387
13388
    /* -------------------------------------------------------------------- */
13389
    /*      If we know the datum, associate the most likely GCS with        */
13390
    /*      it.                                                             */
13391
    /* -------------------------------------------------------------------- */
13392
1.45k
    const OGRSpatialReference &oActiveObj = oSRSTmp.IsEmpty() ? *this : oSRSTmp;
13393
1.45k
    pszAuthName = oActiveObj.GetAuthorityName("GEOGCS|DATUM");
13394
1.45k
    if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") &&
13395
0
        GetPrimeMeridian() == 0.0)
13396
0
    {
13397
0
        const int nDatum = atoi(oActiveObj.GetAuthorityCode("GEOGCS|DATUM"));
13398
13399
0
        if (nDatum >= 6000 && nDatum <= 6999)
13400
0
            return nDatum - 2000;
13401
0
    }
13402
13403
1.45k
    return -1;
13404
1.45k
}
13405
13406
/************************************************************************/
13407
/*                         SetCoordinateEpoch()                         */
13408
/************************************************************************/
13409
13410
/** Set the coordinate epoch, as decimal year.
13411
 *
13412
 * In a dynamic CRS, coordinates of a point on the surface of the Earth may
13413
 * change with time. To be unambiguous the coordinates must always be qualified
13414
 * with the epoch at which they are valid. The coordinate epoch is not
13415
 * necessarily the epoch at which the observation was collected.
13416
 *
13417
 * Pedantically the coordinate epoch of an observation belongs to the
13418
 * observation, and not to the CRS, however it is often more practical to
13419
 * bind it to the CRS. The coordinate epoch should be specified for dynamic
13420
 * CRS (see IsDynamic())
13421
 *
13422
 * This method is the same as the OSRSetCoordinateEpoch() function.
13423
 *
13424
 * @param dfCoordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3)
13425
 * @since OGR 3.4
13426
 */
13427
13428
void OGRSpatialReference::SetCoordinateEpoch(double dfCoordinateEpoch)
13429
4.45k
{
13430
4.45k
    d->m_coordinateEpoch = dfCoordinateEpoch;
13431
4.45k
}
13432
13433
/************************************************************************/
13434
/*                       OSRSetCoordinateEpoch()                        */
13435
/************************************************************************/
13436
13437
/** \brief Set the coordinate epoch, as decimal year.
13438
 *
13439
 * See OGRSpatialReference::SetCoordinateEpoch()
13440
 *
13441
 * @since OGR 3.4
13442
 */
13443
void OSRSetCoordinateEpoch(OGRSpatialReferenceH hSRS, double dfCoordinateEpoch)
13444
0
{
13445
0
    VALIDATE_POINTER0(hSRS, "OSRSetCoordinateEpoch");
13446
13447
0
    return OGRSpatialReference::FromHandle(hSRS)->SetCoordinateEpoch(
13448
0
        dfCoordinateEpoch);
13449
0
}
13450
13451
/************************************************************************/
13452
/*                         GetCoordinateEpoch()                         */
13453
/************************************************************************/
13454
13455
/** Return the coordinate epoch, as decimal year.
13456
 *
13457
 * In a dynamic CRS, coordinates of a point on the surface of the Earth may
13458
 * change with time. To be unambiguous the coordinates must always be qualified
13459
 * with the epoch at which they are valid. The coordinate epoch is not
13460
 * necessarily the epoch at which the observation was collected.
13461
 *
13462
 * Pedantically the coordinate epoch of an observation belongs to the
13463
 * observation, and not to the CRS, however it is often more practical to
13464
 * bind it to the CRS. The coordinate epoch should be specified for dynamic
13465
 * CRS (see IsDynamic())
13466
 *
13467
 * This method is the same as the OSRGetCoordinateEpoch() function.
13468
 *
13469
 * @return coordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3), or 0
13470
 *                         if not set, or relevant.
13471
 * @since OGR 3.4
13472
 */
13473
13474
double OGRSpatialReference::GetCoordinateEpoch() const
13475
93.3k
{
13476
93.3k
    return d->m_coordinateEpoch;
13477
93.3k
}
13478
13479
/************************************************************************/
13480
/*                       OSRGetCoordinateEpoch()                        */
13481
/************************************************************************/
13482
13483
/** \brief Get the coordinate epoch, as decimal year.
13484
 *
13485
 * See OGRSpatialReference::GetCoordinateEpoch()
13486
 *
13487
 * @since OGR 3.4
13488
 */
13489
double OSRGetCoordinateEpoch(OGRSpatialReferenceH hSRS)
13490
0
{
13491
0
    VALIDATE_POINTER1(hSRS, "OSRGetCoordinateEpoch", 0);
13492
13493
0
    return OGRSpatialReference::FromHandle(hSRS)->GetCoordinateEpoch();
13494
0
}