LCOV - code coverage report
Current view: top level - ogr - ogrspatialreference.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 3469 4244 81.7 %
Date: 2026-02-23 15:56:29 Functions: 299 375 79.7 %

          Line data    Source code
       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             : #define STRINGIFY(s) #s
      62             : #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      245160 :         explicit Listener(OGRSpatialReference::Private *poObj) : m_poObj(poObj)
      71             :         {
      72      245160 :         }
      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           2 :     void SetThreadSafe()
     135             :     {
     136           2 :         m_bIsThreadSafe = true;
     137           2 :     }
     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     1222100 :     PJ_CONTEXT *getPROJContext()
     159             :     {
     160     1222100 :         return OSRGetProjTLSContext();
     161             :     }
     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    18335400 :         explicit OptionalLockGuard(Private *p) : m_private(*p)
     180             :         {
     181    18335400 :             if (m_private.m_bIsThreadSafe)
     182        3798 :                 m_private.m_mutex.lock();
     183    18335400 :         }
     184             : 
     185    18335400 :         ~OptionalLockGuard()
     186    18335400 :         {
     187    18335400 :             if (m_private.m_bIsThreadSafe)
     188        3798 :                 m_private.m_mutex.unlock();
     189    18335400 :         }
     190             :     };
     191             : 
     192    18335400 :     inline OptionalLockGuard GetOptionalLockGuard()
     193             :     {
     194    18335400 :         return OptionalLockGuard(this);
     195             :     }
     196             : };
     197             : 
     198     2320120 : void OGRSpatialReference::Private::Listener::notifyChange(OGR_SRSNode *)
     199             : {
     200     2320120 :     m_poObj->nodesChanged();
     201     2320120 : }
     202             : 
     203             : #define TAKE_OPTIONAL_LOCK()                                                   \
     204             :     auto lock = d->GetOptionalLockGuard();                                     \
     205             :     CPL_IGNORE_RET_VAL(lock)
     206             : 
     207      245160 : static OSRAxisMappingStrategy GetDefaultAxisMappingStrategy()
     208             : {
     209             :     const char *pszDefaultAMS =
     210      245160 :         CPLGetConfigOption("OSR_DEFAULT_AXIS_MAPPING_STRATEGY", nullptr);
     211      245160 :     if (pszDefaultAMS)
     212             :     {
     213           1 :         if (EQUAL(pszDefaultAMS, "AUTHORITY_COMPLIANT"))
     214           0 :             return OAMS_AUTHORITY_COMPLIANT;
     215           1 :         else if (EQUAL(pszDefaultAMS, "TRADITIONAL_GIS_ORDER"))
     216           1 :             return OAMS_TRADITIONAL_GIS_ORDER;
     217             :         else
     218             :         {
     219           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     220             :                      "Illegal value for OSR_DEFAULT_AXIS_MAPPING_STRATEGY = %s",
     221             :                      pszDefaultAMS);
     222             :         }
     223             :     }
     224      245159 :     return OAMS_AUTHORITY_COMPLIANT;
     225             : }
     226             : 
     227      245160 : OGRSpatialReference::Private::Private(OGRSpatialReference *poSelf)
     228      245160 :     : m_poSelf(poSelf), m_poListener(std::make_shared<Listener>(this))
     229             : {
     230             :     // Get the default value for m_axisMappingStrategy from the
     231             :     // OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration option, if set.
     232      245160 :     m_axisMappingStrategy = GetDefaultAxisMappingStrategy();
     233      245160 : }
     234             : 
     235      975072 : OGRSpatialReference::Private::~Private()
     236             : {
     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      243768 :     PJ_CONTEXT *pj_context_to_destroy = nullptr;
     241             :     PJ_CONTEXT *ctxt;
     242      243768 :     if (GDALThreadLocalDatasetCacheIsInDestruction())
     243             :     {
     244         185 :         pj_context_to_destroy = proj_context_create();
     245         185 :         ctxt = pj_context_to_destroy;
     246             :     }
     247             :     else
     248             :     {
     249      243583 :         ctxt = getPROJContext();
     250             :     }
     251             : 
     252      243768 :     proj_assign_context(m_pj_crs, ctxt);
     253      243768 :     proj_destroy(m_pj_crs);
     254             : 
     255      243768 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     256      243768 :     proj_destroy(m_pj_geod_base_crs_temp);
     257             : 
     258      243768 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     259      243768 :     proj_destroy(m_pj_proj_crs_cs_temp);
     260             : 
     261      243768 :     proj_assign_context(m_pj_bound_crs_target, ctxt);
     262      243768 :     proj_destroy(m_pj_bound_crs_target);
     263             : 
     264      243768 :     proj_assign_context(m_pj_bound_crs_co, ctxt);
     265      243768 :     proj_destroy(m_pj_bound_crs_co);
     266             : 
     267      243768 :     proj_assign_context(m_pj_crs_backup, ctxt);
     268      243768 :     proj_destroy(m_pj_crs_backup);
     269             : 
     270      243768 :     delete m_poRootBackup;
     271      243768 :     delete m_poRoot;
     272      243768 :     proj_context_destroy(pj_context_to_destroy);
     273      243768 : }
     274             : 
     275      127024 : void OGRSpatialReference::Private::clear()
     276             : {
     277      127024 :     proj_assign_context(m_pj_crs, getPROJContext());
     278      127024 :     proj_destroy(m_pj_crs);
     279      127024 :     m_pj_crs = nullptr;
     280             : 
     281      127024 :     delete m_poRoot;
     282      127024 :     m_poRoot = nullptr;
     283      127024 :     m_bNodesChanged = false;
     284             : 
     285      127024 :     m_wktImportWarnings.clear();
     286      127024 :     m_wktImportErrors.clear();
     287             : 
     288      127024 :     m_pj_crs_modified_during_demote = false;
     289      127024 :     m_pjType = PJ_TYPE_UNKNOWN;
     290      127024 :     m_osPrimeMeridianName.clear();
     291      127024 :     m_osAngularUnits.clear();
     292      127024 :     m_osLinearUnits.clear();
     293             : 
     294      127024 :     bNormInfoSet = FALSE;
     295      127024 :     dfFromGreenwich = 1.0;
     296      127024 :     dfToMeter = 1.0;
     297      127024 :     dfToDegrees = 1.0;
     298      127024 :     m_dfAngularUnitToRadian = 0.0;
     299             : 
     300      127024 :     m_bMorphToESRI = false;
     301      127024 :     m_bHasCenterLong = false;
     302             : 
     303      127024 :     m_coordinateEpoch = 0.0;
     304      127024 : }
     305             : 
     306       29004 : void OGRSpatialReference::Private::setRoot(OGR_SRSNode *poRoot)
     307             : {
     308       29004 :     m_poRoot = poRoot;
     309       29004 :     if (m_poRoot)
     310             :     {
     311       29004 :         m_poRoot->RegisterListener(m_poListener);
     312             :     }
     313       29004 :     nodesChanged();
     314       29004 : }
     315             : 
     316      192801 : void OGRSpatialReference::Private::setPjCRS(PJ *pj_crsIn,
     317             :                                             bool doRefreshAxisMapping)
     318             : {
     319      192801 :     auto ctxt = getPROJContext();
     320             : 
     321             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
     322             :     if (proj_get_type(pj_crsIn) == PJ_TYPE_COORDINATE_METADATA)
     323             :     {
     324             :         const double dfEpoch =
     325             :             proj_coordinate_metadata_get_epoch(ctxt, pj_crsIn);
     326             :         if (!std::isnan(dfEpoch))
     327             :         {
     328             :             m_poSelf->SetCoordinateEpoch(dfEpoch);
     329             :         }
     330             :         auto crs = proj_get_source_crs(ctxt, pj_crsIn);
     331             :         proj_destroy(pj_crsIn);
     332             :         pj_crsIn = crs;
     333             :     }
     334             : #endif
     335             : 
     336      192801 :     proj_assign_context(m_pj_crs, ctxt);
     337      192801 :     proj_destroy(m_pj_crs);
     338      192801 :     m_pj_crs = pj_crsIn;
     339      192801 :     if (m_pj_crs)
     340             :     {
     341      192750 :         m_pjType = proj_get_type(m_pj_crs);
     342             :     }
     343      192801 :     if (m_pj_crs_backup)
     344             :     {
     345          21 :         m_pj_crs_modified_during_demote = true;
     346             :     }
     347      192801 :     invalidateNodes();
     348      192801 :     if (doRefreshAxisMapping)
     349             :     {
     350      192781 :         refreshAxisMapping();
     351             :     }
     352      192801 : }
     353             : 
     354      782281 : void OGRSpatialReference::Private::refreshProjObj()
     355             : {
     356      782281 :     if (m_bNodesChanged && m_poRoot)
     357             :     {
     358        8685 :         char *pszWKT = nullptr;
     359        8685 :         m_poRoot->exportToWkt(&pszWKT);
     360        8685 :         auto poRootBackup = m_poRoot;
     361        8685 :         m_poRoot = nullptr;
     362        8685 :         const double dfCoordinateEpochBackup = m_coordinateEpoch;
     363        8685 :         clear();
     364        8685 :         m_coordinateEpoch = dfCoordinateEpochBackup;
     365        8685 :         m_bHasCenterLong = strstr(pszWKT, "CENTER_LONG") != nullptr;
     366             : 
     367        8685 :         const char *const options[] = {
     368             :             "STRICT=NO",
     369             : #if PROJ_AT_LEAST_VERSION(9, 1, 0)
     370             :             "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=NO",
     371             : #endif
     372             :             nullptr};
     373        8685 :         PROJ_STRING_LIST warnings = nullptr;
     374        8685 :         PROJ_STRING_LIST errors = nullptr;
     375        8685 :         setPjCRS(proj_create_from_wkt(getPROJContext(), pszWKT, options,
     376             :                                       &warnings, &errors));
     377       16973 :         for (auto iter = warnings; iter && *iter; ++iter)
     378             :         {
     379        8288 :             m_wktImportWarnings.push_back(*iter);
     380             :         }
     381        8900 :         for (auto iter = errors; iter && *iter; ++iter)
     382             :         {
     383         215 :             m_wktImportErrors.push_back(*iter);
     384             :         }
     385        8685 :         proj_string_list_destroy(warnings);
     386        8685 :         proj_string_list_destroy(errors);
     387             : 
     388        8685 :         CPLFree(pszWKT);
     389             : 
     390        8685 :         m_poRoot = poRootBackup;
     391        8685 :         m_bNodesChanged = false;
     392             :     }
     393      782281 : }
     394             : 
     395       31131 : void OGRSpatialReference::Private::refreshRootFromProjObj()
     396             : {
     397       31131 :     CPLAssert(m_poRoot == nullptr);
     398             : 
     399       31131 :     if (m_pj_crs)
     400             :     {
     401       57920 :         CPLStringList aosOptions;
     402       28960 :         if (!m_bMorphToESRI)
     403             :         {
     404       28956 :             aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
     405       28956 :             aosOptions.SetNameValue("MULTILINE", "NO");
     406             :         }
     407       28960 :         aosOptions.SetNameValue("STRICT", "NO");
     408             : 
     409             :         const char *pszWKT;
     410             :         {
     411       28960 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     412       28960 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs,
     413       28960 :                                  m_bMorphToESRI ? PJ_WKT1_ESRI : PJ_WKT1_GDAL,
     414       28960 :                                  aosOptions.List());
     415       28960 :             m_bNodesWKT2 = false;
     416             :         }
     417       28960 :         if (!m_bMorphToESRI && pszWKT == nullptr)
     418             :         {
     419          96 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs, PJ_WKT2_2018,
     420          96 :                                  aosOptions.List());
     421          96 :             m_bNodesWKT2 = true;
     422             :         }
     423       28960 :         if (pszWKT)
     424             :         {
     425       28960 :             auto root = new OGR_SRSNode();
     426       28960 :             setRoot(root);
     427       28960 :             root->importFromWkt(&pszWKT);
     428       28960 :             m_bNodesChanged = false;
     429             :         }
     430             :     }
     431       31131 : }
     432             : 
     433      223311 : static bool isNorthEastAxisOrder(PJ_CONTEXT *ctx, PJ *cs)
     434             : {
     435      223311 :     const char *pszName1 = nullptr;
     436      223311 :     const char *pszDirection1 = nullptr;
     437      223311 :     proj_cs_get_axis_info(ctx, cs, 0, &pszName1, nullptr, &pszDirection1,
     438             :                           nullptr, nullptr, nullptr, nullptr);
     439      223311 :     const char *pszName2 = nullptr;
     440      223311 :     const char *pszDirection2 = nullptr;
     441      223311 :     proj_cs_get_axis_info(ctx, cs, 1, &pszName2, nullptr, &pszDirection2,
     442             :                           nullptr, nullptr, nullptr, nullptr);
     443      223311 :     if (pszDirection1 && EQUAL(pszDirection1, "north") && pszDirection2 &&
     444       99597 :         EQUAL(pszDirection2, "east"))
     445             :     {
     446       98170 :         return true;
     447             :     }
     448      125141 :     if (pszDirection1 && pszDirection2 &&
     449      125141 :         ((EQUAL(pszDirection1, "north") && EQUAL(pszDirection2, "north")) ||
     450      123731 :          (EQUAL(pszDirection1, "south") && EQUAL(pszDirection2, "south"))) &&
     451        2945 :         pszName1 && STARTS_WITH_CI(pszName1, "northing") && pszName2 &&
     452        1168 :         STARTS_WITH_CI(pszName2, "easting"))
     453             :     {
     454        1168 :         return true;
     455             :     }
     456      123973 :     return false;
     457             : }
     458             : 
     459      283060 : void OGRSpatialReference::Private::refreshAxisMapping()
     460             : {
     461      283060 :     if (!m_pj_crs || m_axisMappingStrategy == OAMS_CUSTOM)
     462       60342 :         return;
     463             : 
     464      222718 :     bool doUndoDemote = false;
     465      222718 :     if (m_pj_crs_backup == nullptr)
     466             :     {
     467      222697 :         doUndoDemote = true;
     468      222697 :         demoteFromBoundCRS();
     469             :     }
     470      222718 :     const auto ctxt = getPROJContext();
     471      222718 :     PJ *horizCRS = nullptr;
     472      222718 :     int axisCount = 0;
     473      222718 :     if (m_pjType == PJ_TYPE_VERTICAL_CRS)
     474             :     {
     475         218 :         axisCount = 1;
     476             :     }
     477      222500 :     else if (m_pjType == PJ_TYPE_COMPOUND_CRS)
     478             :     {
     479        1107 :         horizCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 0);
     480        1107 :         if (horizCRS && proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
     481             :         {
     482         222 :             auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
     483         222 :             if (baseCRS)
     484             :             {
     485         222 :                 proj_destroy(horizCRS);
     486         222 :                 horizCRS = baseCRS;
     487             :             }
     488             :         }
     489             : 
     490        1107 :         auto vertCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 1);
     491        1107 :         if (vertCRS)
     492             :         {
     493        1104 :             if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
     494             :             {
     495         398 :                 auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
     496         398 :                 if (baseCRS)
     497             :                 {
     498         398 :                     proj_destroy(vertCRS);
     499         398 :                     vertCRS = baseCRS;
     500             :                 }
     501             :             }
     502             : 
     503        1104 :             auto cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
     504        1104 :             if (cs)
     505             :             {
     506        1104 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
     507        1104 :                 proj_destroy(cs);
     508             :             }
     509        1104 :             proj_destroy(vertCRS);
     510             :         }
     511             :     }
     512             :     else
     513             :     {
     514      221393 :         horizCRS = m_pj_crs;
     515             :     }
     516             : 
     517      222718 :     bool bSwitchForGisFriendlyOrder = false;
     518      222718 :     if (horizCRS)
     519             :     {
     520      222497 :         auto cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
     521      222497 :         if (cs)
     522             :         {
     523      222497 :             int nHorizCSAxisCount = proj_cs_get_axis_count(ctxt, cs);
     524      222497 :             axisCount += nHorizCSAxisCount;
     525      222497 :             if (nHorizCSAxisCount >= 2)
     526             :             {
     527      222487 :                 bSwitchForGisFriendlyOrder = isNorthEastAxisOrder(ctxt, cs);
     528             :             }
     529      222497 :             proj_destroy(cs);
     530             :         }
     531             :     }
     532      222718 :     if (horizCRS != m_pj_crs)
     533             :     {
     534        1325 :         proj_destroy(horizCRS);
     535             :     }
     536      222718 :     if (doUndoDemote)
     537             :     {
     538      222697 :         undoDemoteFromBoundCRS();
     539             :     }
     540             : 
     541      222718 :     m_axisMapping.resize(axisCount);
     542      222718 :     if (m_axisMappingStrategy == OAMS_AUTHORITY_COMPLIANT ||
     543       65311 :         !bSwitchForGisFriendlyOrder)
     544             :     {
     545      580206 :         for (int i = 0; i < axisCount; i++)
     546             :         {
     547      387318 :             m_axisMapping[i] = i + 1;
     548      192888 :         }
     549             :     }
     550             :     else
     551             :     {
     552       29830 :         m_axisMapping[0] = 2;
     553       29830 :         m_axisMapping[1] = 1;
     554       29830 :         if (axisCount == 3)
     555             :         {
     556         343 :             m_axisMapping[2] = 3;
     557             :         }
     558             :     }
     559             : }
     560             : 
     561     2349130 : void OGRSpatialReference::Private::nodesChanged()
     562             : {
     563     2349130 :     m_bNodesChanged = true;
     564     2349130 : }
     565             : 
     566      193057 : void OGRSpatialReference::Private::invalidateNodes()
     567             : {
     568      193057 :     delete m_poRoot;
     569      193057 :     m_poRoot = nullptr;
     570      193057 :     m_bNodesChanged = false;
     571      193057 : }
     572             : 
     573         256 : void OGRSpatialReference::Private::setMorphToESRI(bool b)
     574             : {
     575         256 :     invalidateNodes();
     576         256 :     m_bMorphToESRI = b;
     577         256 : }
     578             : 
     579      658437 : void OGRSpatialReference::Private::demoteFromBoundCRS()
     580             : {
     581      658437 :     CPLAssert(m_pj_bound_crs_target == nullptr);
     582      658437 :     CPLAssert(m_pj_bound_crs_co == nullptr);
     583      658437 :     CPLAssert(m_poRootBackup == nullptr);
     584      658437 :     CPLAssert(m_pj_crs_backup == nullptr);
     585             : 
     586      658437 :     m_pj_crs_modified_during_demote = false;
     587             : 
     588      658437 :     if (m_pjType == PJ_TYPE_BOUND_CRS)
     589             :     {
     590        2768 :         auto baseCRS = proj_get_source_crs(getPROJContext(), m_pj_crs);
     591        2768 :         m_pj_bound_crs_target = proj_get_target_crs(getPROJContext(), m_pj_crs);
     592        2768 :         m_pj_bound_crs_co =
     593        2768 :             proj_crs_get_coordoperation(getPROJContext(), m_pj_crs);
     594             : 
     595        2768 :         m_poRootBackup = m_poRoot;
     596        2768 :         m_poRoot = nullptr;
     597        2768 :         m_pj_crs_backup = m_pj_crs;
     598        2768 :         m_pj_crs = baseCRS;
     599        2768 :         m_pjType = proj_get_type(m_pj_crs);
     600             :     }
     601      658437 : }
     602             : 
     603      658437 : void OGRSpatialReference::Private::undoDemoteFromBoundCRS()
     604             : {
     605      658437 :     if (m_pj_bound_crs_target)
     606             :     {
     607        2768 :         CPLAssert(m_poRoot == nullptr);
     608        2768 :         CPLAssert(m_pj_crs);
     609        2768 :         if (!m_pj_crs_modified_during_demote)
     610             :         {
     611        2748 :             proj_destroy(m_pj_crs);
     612        2748 :             m_pj_crs = m_pj_crs_backup;
     613        2748 :             m_pjType = proj_get_type(m_pj_crs);
     614        2748 :             m_poRoot = m_poRootBackup;
     615             :         }
     616             :         else
     617             :         {
     618          20 :             delete m_poRootBackup;
     619          20 :             m_poRootBackup = nullptr;
     620          20 :             proj_destroy(m_pj_crs_backup);
     621          20 :             m_pj_crs_backup = nullptr;
     622          20 :             setPjCRS(proj_crs_create_bound_crs(getPROJContext(), m_pj_crs,
     623          20 :                                                m_pj_bound_crs_target,
     624          20 :                                                m_pj_bound_crs_co),
     625             :                      false);
     626             :         }
     627             :     }
     628             : 
     629      658437 :     m_poRootBackup = nullptr;
     630      658437 :     m_pj_crs_backup = nullptr;
     631      658437 :     proj_destroy(m_pj_bound_crs_target);
     632      658437 :     m_pj_bound_crs_target = nullptr;
     633      658437 :     proj_destroy(m_pj_bound_crs_co);
     634      658437 :     m_pj_bound_crs_co = nullptr;
     635      658437 :     m_pj_crs_modified_during_demote = false;
     636      658437 : }
     637             : 
     638      166662 : const char *OGRSpatialReference::Private::nullifyTargetKeyIfPossible(
     639             :     const char *pszTargetKey)
     640             : {
     641      166662 :     if (pszTargetKey)
     642             :     {
     643       60866 :         demoteFromBoundCRS();
     644       60866 :         if ((m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     645       31794 :              m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS) &&
     646       29150 :             EQUAL(pszTargetKey, "GEOGCS"))
     647             :         {
     648        7171 :             pszTargetKey = nullptr;
     649             :         }
     650       53695 :         else if (m_pjType == PJ_TYPE_GEOCENTRIC_CRS &&
     651          20 :                  EQUAL(pszTargetKey, "GEOCCS"))
     652             :         {
     653           0 :             pszTargetKey = nullptr;
     654             :         }
     655       53695 :         else if (m_pjType == PJ_TYPE_PROJECTED_CRS &&
     656       30405 :                  EQUAL(pszTargetKey, "PROJCS"))
     657             :         {
     658        4479 :             pszTargetKey = nullptr;
     659             :         }
     660       49216 :         else if (m_pjType == PJ_TYPE_VERTICAL_CRS &&
     661           4 :                  EQUAL(pszTargetKey, "VERT_CS"))
     662             :         {
     663           2 :             pszTargetKey = nullptr;
     664             :         }
     665       60866 :         undoDemoteFromBoundCRS();
     666             :     }
     667      166662 :     return pszTargetKey;
     668             : }
     669             : 
     670        9704 : PJ *OGRSpatialReference::Private::getGeodBaseCRS()
     671             : {
     672        9704 :     if (m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     673        9651 :         m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
     674             :     {
     675          53 :         return m_pj_crs;
     676             :     }
     677             : 
     678        9651 :     auto ctxt = getPROJContext();
     679        9651 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     680             :     {
     681        4446 :         proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     682        4446 :         proj_destroy(m_pj_geod_base_crs_temp);
     683        4446 :         m_pj_geod_base_crs_temp = proj_crs_get_geodetic_crs(ctxt, m_pj_crs);
     684        4446 :         return m_pj_geod_base_crs_temp;
     685             :     }
     686             : 
     687        5205 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     688        5205 :     proj_destroy(m_pj_geod_base_crs_temp);
     689        5205 :     auto cs = proj_create_ellipsoidal_2D_cs(ctxt, PJ_ELLPS2D_LATITUDE_LONGITUDE,
     690             :                                             nullptr, 0);
     691        5205 :     m_pj_geod_base_crs_temp = proj_create_geographic_crs(
     692             :         ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84",
     693             :         SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING, SRS_PM_GREENWICH, 0.0,
     694             :         SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV), cs);
     695        5205 :     proj_destroy(cs);
     696             : 
     697        5205 :     return m_pj_geod_base_crs_temp;
     698             : }
     699             : 
     700        5406 : PJ *OGRSpatialReference::Private::getProjCRSCoordSys()
     701             : {
     702        5406 :     auto ctxt = getPROJContext();
     703        5406 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     704             :     {
     705        4432 :         proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     706        4432 :         proj_destroy(m_pj_proj_crs_cs_temp);
     707        4432 :         m_pj_proj_crs_cs_temp =
     708        4432 :             proj_crs_get_coordinate_system(getPROJContext(), m_pj_crs);
     709        4432 :         return m_pj_proj_crs_cs_temp;
     710             :     }
     711             : 
     712         974 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     713         974 :     proj_destroy(m_pj_proj_crs_cs_temp);
     714         974 :     m_pj_proj_crs_cs_temp = proj_create_cartesian_2D_cs(
     715             :         ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0);
     716         974 :     return m_pj_proj_crs_cs_temp;
     717             : }
     718             : 
     719        5457 : const char *OGRSpatialReference::Private::getProjCRSName()
     720             : {
     721        5457 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     722             :     {
     723        4447 :         return proj_get_name(m_pj_crs);
     724             :     }
     725             : 
     726        1010 :     return "unnamed";
     727             : }
     728             : 
     729        1381 : OGRErr OGRSpatialReference::Private::replaceConversionAndUnref(PJ *conv)
     730             : {
     731        1381 :     refreshProjObj();
     732             : 
     733        1381 :     demoteFromBoundCRS();
     734             : 
     735             :     auto projCRS =
     736        1381 :         proj_create_projected_crs(getPROJContext(), getProjCRSName(),
     737        1381 :                                   getGeodBaseCRS(), conv, getProjCRSCoordSys());
     738        1381 :     proj_destroy(conv);
     739             : 
     740        1381 :     setPjCRS(projCRS);
     741             : 
     742        1381 :     undoDemoteFromBoundCRS();
     743        1381 :     return OGRERR_NONE;
     744             : }
     745             : 
     746             : /************************************************************************/
     747             : /*                             ToPointer()                              */
     748             : /************************************************************************/
     749             : 
     750       27188 : static inline OGRSpatialReference *ToPointer(OGRSpatialReferenceH hSRS)
     751             : {
     752       27188 :     return OGRSpatialReference::FromHandle(hSRS);
     753             : }
     754             : 
     755             : /************************************************************************/
     756             : /*                              ToHandle()                              */
     757             : /************************************************************************/
     758             : 
     759        4824 : static inline OGRSpatialReferenceH ToHandle(OGRSpatialReference *poSRS)
     760             : {
     761        4824 :     return OGRSpatialReference::ToHandle(poSRS);
     762             : }
     763             : 
     764             : /************************************************************************/
     765             : /*                          OGRsnPrintDouble()                          */
     766             : /************************************************************************/
     767             : 
     768             : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue);
     769             : 
     770         126 : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue)
     771             : 
     772             : {
     773         126 :     CPLsnprintf(pszStrBuf, size, "%.16g", dfValue);
     774             : 
     775         126 :     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         126 :     if (nLen > 15 && (strcmp(pszStrBuf + nLen - 6, "999999") == 0 ||
     780           8 :                       strcmp(pszStrBuf + nLen - 6, "000001") == 0))
     781             :     {
     782           0 :         CPLsnprintf(pszStrBuf, size, "%.15g", dfValue);
     783             :     }
     784             : 
     785             :     // Force to user periods regardless of locale.
     786         126 :     if (strchr(pszStrBuf, ',') != nullptr)
     787             :     {
     788           0 :         char *const pszDelim = strchr(pszStrBuf, ',');
     789           0 :         *pszDelim = '.';
     790             :     }
     791         126 : }
     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      242557 : OGRSpatialReference::OGRSpatialReference(const char *pszWKT)
     830      242557 :     : d(new Private(this))
     831             : {
     832      242557 :     if (pszWKT != nullptr)
     833         281 :         importFromWkt(pszWKT);
     834      242557 : }
     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        3369 : OGRSpatialReferenceH CPL_STDCALL OSRNewSpatialReference(const char *pszWKT)
     859             : 
     860             : {
     861        3369 :     OGRSpatialReference *poSRS = new OGRSpatialReference();
     862             : 
     863        3369 :     if (pszWKT != nullptr && strlen(pszWKT) > 0)
     864             :     {
     865          65 :         if (poSRS->importFromWkt(pszWKT) != OGRERR_NONE)
     866             :         {
     867           0 :             delete poSRS;
     868           0 :             poSRS = nullptr;
     869             :         }
     870             :     }
     871             : 
     872        3369 :     return ToHandle(poSRS);
     873             : }
     874             : 
     875             : /************************************************************************/
     876             : /*                        OGRSpatialReference()                         */
     877             : /************************************************************************/
     878             : 
     879             : /** Copy constructor. See also Clone().
     880             :  * @param oOther other spatial reference
     881             :  */
     882        2603 : OGRSpatialReference::OGRSpatialReference(const OGRSpatialReference &oOther)
     883        2603 :     : d(new Private(this))
     884             : {
     885        2603 :     *this = oOther;
     886        2603 : }
     887             : 
     888             : /************************************************************************/
     889             : /*                        OGRSpatialReference()                         */
     890             : /************************************************************************/
     891             : 
     892             : /** Move constructor.
     893             :  * @param oOther other spatial reference
     894             :  */
     895          29 : OGRSpatialReference::OGRSpatialReference(OGRSpatialReference &&oOther)
     896          29 :     : d(std::move(oOther.d))
     897             : {
     898          29 : }
     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      304204 : OGRSpatialReference::~OGRSpatialReference()
     914             : 
     915             : {
     916      304204 : }
     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           0 : void OGRSpatialReference::DestroySpatialReference(OGRSpatialReference *poSRS)
     937             : {
     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        9559 : void CPL_STDCALL OSRDestroySpatialReference(OGRSpatialReferenceH hSRS)
     954             : 
     955             : {
     956        9559 :     delete ToPointer(hSRS);
     957        9559 : }
     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      118339 : void OGRSpatialReference::Clear()
     971             : 
     972             : {
     973      118339 :     d->clear();
     974      118339 : }
     975             : 
     976             : /************************************************************************/
     977             : /*                             operator=()                              */
     978             : /************************************************************************/
     979             : 
     980             : /** Assignment operator.
     981             :  * @param oSource SRS to assign to *this
     982             :  * @return *this
     983             :  */
     984             : OGRSpatialReference &
     985       27536 : OGRSpatialReference::operator=(const OGRSpatialReference &oSource)
     986             : 
     987             : {
     988       27536 :     if (&oSource != this)
     989             :     {
     990       27535 :         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       27535 :         oSource.d->refreshProjObj();
     997       27535 :         if (oSource.d->m_pj_crs)
     998       27090 :             d->setPjCRS(proj_clone(d->getPROJContext(), oSource.d->m_pj_crs));
     999       27535 :         if (oSource.d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
    1000       12784 :             SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1001       14751 :         else if (oSource.d->m_axisMappingStrategy == OAMS_CUSTOM)
    1002         124 :             SetDataAxisToSRSAxisMapping(oSource.d->m_axisMapping);
    1003             : 
    1004       27535 :         d->m_coordinateEpoch = oSource.d->m_coordinateEpoch;
    1005             :     }
    1006             : 
    1007       27536 :     return *this;
    1008             : }
    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        4770 : OGRSpatialReference::operator=(OGRSpatialReference &&oSource)
    1020             : 
    1021             : {
    1022        4770 :     if (&oSource != this)
    1023             :     {
    1024        4769 :         d = std::move(oSource.d);
    1025             :     }
    1026             : 
    1027        4770 :     return *this;
    1028             : }
    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           2 : OGRSpatialReference::AssignAndSetThreadSafe(const OGRSpatialReference &oSource)
    1046             : {
    1047           2 :     *this = oSource;
    1048           2 :     d->SetThreadSafe();
    1049           2 :     return *this;
    1050             : }
    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     3628910 : int OGRSpatialReference::Reference()
    1068             : 
    1069             : {
    1070     3628910 :     return CPLAtomicInc(&d->nRefCount);
    1071             : }
    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        1009 : int OSRReference(OGRSpatialReferenceH hSRS)
    1083             : 
    1084             : {
    1085        1009 :     VALIDATE_POINTER1(hSRS, "OSRReference", 0);
    1086             : 
    1087        1009 :     return ToPointer(hSRS)->Reference();
    1088             : }
    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     3672730 : int OGRSpatialReference::Dereference()
    1103             : 
    1104             : {
    1105     3672730 :     if (d->nRefCount <= 0)
    1106           0 :         CPLDebug("OSR",
    1107             :                  "Dereference() called on an object with refcount %d,"
    1108             :                  "likely already destroyed!",
    1109           0 :                  d->nRefCount);
    1110     3672730 :     return CPLAtomicDec(&d->nRefCount);
    1111             : }
    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           0 : int OSRDereference(OGRSpatialReferenceH hSRS)
    1123             : 
    1124             : {
    1125           0 :     VALIDATE_POINTER1(hSRS, "OSRDereference", 0);
    1126             : 
    1127           0 :     return ToPointer(hSRS)->Dereference();
    1128             : }
    1129             : 
    1130             : /************************************************************************/
    1131             : /*                         GetReferenceCount()                          */
    1132             : /************************************************************************/
    1133             : 
    1134             : /**
    1135             :  * \brief Fetch current reference count.
    1136             :  *
    1137             :  * @return the current reference count.
    1138             :  */
    1139         180 : int OGRSpatialReference::GetReferenceCount() const
    1140             : {
    1141         180 :     return d->nRefCount;
    1142             : }
    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     3669950 : void OGRSpatialReference::Release()
    1155             : 
    1156             : {
    1157     3669950 :     if (Dereference() <= 0)
    1158       43757 :         delete this;
    1159     3669950 : }
    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        6760 : void OSRRelease(OGRSpatialReferenceH hSRS)
    1171             : 
    1172             : {
    1173        6760 :     VALIDATE_POINTER0(hSRS, "OSRRelease");
    1174             : 
    1175        6760 :     ToPointer(hSRS)->Release();
    1176             : }
    1177             : 
    1178       92911 : OGR_SRSNode *OGRSpatialReference::GetRoot()
    1179             : {
    1180       92911 :     TAKE_OPTIONAL_LOCK();
    1181             : 
    1182       92911 :     if (!d->m_poRoot)
    1183             :     {
    1184       27999 :         d->refreshRootFromProjObj();
    1185             :     }
    1186      185822 :     return d->m_poRoot;
    1187             : }
    1188             : 
    1189        8461 : const OGR_SRSNode *OGRSpatialReference::GetRoot() const
    1190             : {
    1191        8461 :     TAKE_OPTIONAL_LOCK();
    1192             : 
    1193        8461 :     if (!d->m_poRoot)
    1194             :     {
    1195        3132 :         d->refreshRootFromProjObj();
    1196             :     }
    1197       16922 :     return d->m_poRoot;
    1198             : }
    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          44 : void OGRSpatialReference::SetRoot(OGR_SRSNode *poNewRoot)
    1215             : 
    1216             : {
    1217          44 :     if (d->m_poRoot != poNewRoot)
    1218             :     {
    1219          44 :         delete d->m_poRoot;
    1220          44 :         d->setRoot(poNewRoot);
    1221             :     }
    1222          44 : }
    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       89779 : OGR_SRSNode *OGRSpatialReference::GetAttrNode(const char *pszNodePath)
    1247             : 
    1248             : {
    1249       89779 :     if (strchr(pszNodePath, '|') == nullptr)
    1250             :     {
    1251             :         // Fast path
    1252       49765 :         OGR_SRSNode *poNode = GetRoot();
    1253       49765 :         if (poNode)
    1254       48558 :             poNode = poNode->GetNode(pszNodePath);
    1255       49765 :         return poNode;
    1256             :     }
    1257             : 
    1258             :     char **papszPathTokens =
    1259       40014 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    1260             : 
    1261       40014 :     if (CSLCount(papszPathTokens) < 1)
    1262             :     {
    1263           0 :         CSLDestroy(papszPathTokens);
    1264           0 :         return nullptr;
    1265             :     }
    1266             : 
    1267       40014 :     OGR_SRSNode *poNode = GetRoot();
    1268      121582 :     for (int i = 0; poNode != nullptr && papszPathTokens[i] != nullptr; i++)
    1269             :     {
    1270       81568 :         poNode = poNode->GetNode(papszPathTokens[i]);
    1271             :     }
    1272             : 
    1273       40014 :     CSLDestroy(papszPathTokens);
    1274             : 
    1275       40014 :     return poNode;
    1276             : }
    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       81352 : OGRSpatialReference::GetAttrNode(const char *pszNodePath) const
    1298             : 
    1299             : {
    1300             :     OGR_SRSNode *poNode =
    1301       81352 :         const_cast<OGRSpatialReference *>(this)->GetAttrNode(pszNodePath);
    1302             : 
    1303       81352 :     return poNode;
    1304             : }
    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       24428 : const char *OGRSpatialReference::GetAttrValue(const char *pszNodeName,
    1327             :                                               int iAttr) const
    1328             : 
    1329             : {
    1330       24428 :     const OGR_SRSNode *poNode = GetAttrNode(pszNodeName);
    1331       24428 :     if (poNode == nullptr)
    1332             :     {
    1333       10495 :         if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJECTION"))
    1334             :         {
    1335          14 :             return GetAttrValue("METHOD", iAttr);
    1336             :         }
    1337       10481 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS|PROJECTION"))
    1338             :         {
    1339           0 :             return GetAttrValue("PROJCRS|METHOD", iAttr);
    1340             :         }
    1341       10481 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS"))
    1342             :         {
    1343           1 :             return GetAttrValue("PROJCRS", iAttr);
    1344             :         }
    1345       10480 :         return nullptr;
    1346             :     }
    1347             : 
    1348       13933 :     if (iAttr < 0 || iAttr >= poNode->GetChildCount())
    1349           0 :         return nullptr;
    1350             : 
    1351       13933 :     return poNode->GetChild(iAttr)->GetValue();
    1352             : }
    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          34 : const char *CPL_STDCALL OSRGetAttrValue(OGRSpatialReferenceH hSRS,
    1364             :                                         const char *pszKey, int iChild)
    1365             : 
    1366             : {
    1367          34 :     VALIDATE_POINTER1(hSRS, "OSRGetAttrValue", nullptr);
    1368             : 
    1369          34 :     return ToPointer(hSRS)->GetAttrValue(pszKey, iChild);
    1370             : }
    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        8268 : const char *OGRSpatialReference::GetName() const
    1386             : {
    1387       16536 :     TAKE_OPTIONAL_LOCK();
    1388             : 
    1389        8268 :     d->refreshProjObj();
    1390        8268 :     if (!d->m_pj_crs)
    1391         113 :         return nullptr;
    1392        8155 :     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        8155 :     return pszName;
    1408             : }
    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          44 : const char *OSRGetName(OGRSpatialReferenceH hSRS)
    1423             : 
    1424             : {
    1425          44 :     VALIDATE_POINTER1(hSRS, "OSRGetName", nullptr);
    1426             : 
    1427          44 :     return ToPointer(hSRS)->GetName();
    1428             : }
    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           4 : const char *OGRSpatialReference::GetCelestialBodyName() const
    1446             : {
    1447             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    1448             : 
    1449             :     TAKE_OPTIONAL_LOCK();
    1450             : 
    1451             :     d->refreshProjObj();
    1452             :     if (!d->m_pj_crs)
    1453             :         return nullptr;
    1454             :     d->demoteFromBoundCRS();
    1455             :     const char *name =
    1456             :         proj_get_celestial_body_name(d->getPROJContext(), d->m_pj_crs);
    1457             :     if (name)
    1458             :     {
    1459             :         d->m_celestialBodyName = name;
    1460             :     }
    1461             :     d->undoDemoteFromBoundCRS();
    1462             :     return d->m_celestialBodyName.c_str();
    1463             : #else
    1464           4 :     if (std::fabs(GetSemiMajor(nullptr) - SRS_WGS84_SEMIMAJOR) <=
    1465             :         0.05 * SRS_WGS84_SEMIMAJOR)
    1466           4 :         return "Earth";
    1467           0 :     const char *pszAuthName = GetAuthorityName(nullptr);
    1468           0 :     if (pszAuthName && EQUAL(pszAuthName, "EPSG"))
    1469           0 :         return "Earth";
    1470           0 :     return nullptr;
    1471             : #endif
    1472             : }
    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           1 : const char *OSRGetCelestialBodyName(OGRSpatialReferenceH hSRS)
    1490             : 
    1491             : {
    1492           1 :     VALIDATE_POINTER1(hSRS, "GetCelestialBodyName", nullptr);
    1493             : 
    1494           1 :     return ToPointer(hSRS)->GetCelestialBodyName();
    1495             : }
    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       35226 : OGRSpatialReference *OGRSpatialReference::Clone() const
    1510             : 
    1511             : {
    1512       35226 :     OGRSpatialReference *poNewRef = new OGRSpatialReference();
    1513             : 
    1514       35226 :     TAKE_OPTIONAL_LOCK();
    1515             : 
    1516       35226 :     d->refreshProjObj();
    1517       35226 :     if (d->m_pj_crs != nullptr)
    1518       35168 :         poNewRef->d->setPjCRS(proj_clone(d->getPROJContext(), d->m_pj_crs));
    1519       35226 :     if (d->m_bHasCenterLong && d->m_poRoot)
    1520             :     {
    1521           0 :         poNewRef->d->setRoot(d->m_poRoot->Clone());
    1522             :     }
    1523       35226 :     poNewRef->d->m_axisMapping = d->m_axisMapping;
    1524       35226 :     poNewRef->d->m_axisMappingStrategy = d->m_axisMappingStrategy;
    1525       35226 :     poNewRef->d->m_coordinateEpoch = d->m_coordinateEpoch;
    1526       70452 :     return poNewRef;
    1527             : }
    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        1301 : OGRSpatialReferenceH CPL_STDCALL OSRClone(OGRSpatialReferenceH hSRS)
    1539             : 
    1540             : {
    1541        1301 :     VALIDATE_POINTER1(hSRS, "OSRClone", nullptr);
    1542             : 
    1543        1301 :     return ToHandle(ToPointer(hSRS)->Clone());
    1544             : }
    1545             : 
    1546             : /************************************************************************/
    1547             : /*                            dumpReadable()                            */
    1548             : /************************************************************************/
    1549             : 
    1550             : /** Dump pretty wkt to stdout, mostly for debugging.
    1551             :  */
    1552           0 : void OGRSpatialReference::dumpReadable()
    1553             : 
    1554             : {
    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          58 : OGRErr OGRSpatialReference::exportToPrettyWkt(char **ppszResult,
    1591             :                                               int bSimplify) const
    1592             : 
    1593             : {
    1594         116 :     CPLStringList aosOptions;
    1595          58 :     aosOptions.SetNameValue("MULTILINE", "YES");
    1596          58 :     if (bSimplify)
    1597             :     {
    1598           0 :         aosOptions.SetNameValue("FORMAT", "WKT1_SIMPLE");
    1599             :     }
    1600         116 :     return exportToWkt(ppszResult, aosOptions.List());
    1601             : }
    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          56 : OGRErr CPL_STDCALL OSRExportToPrettyWkt(OGRSpatialReferenceH hSRS,
    1619             :                                         char **ppszReturn, int bSimplify)
    1620             : 
    1621             : {
    1622          56 :     VALIDATE_POINTER1(hSRS, "OSRExportToPrettyWkt", OGRERR_FAILURE);
    1623             : 
    1624          56 :     *ppszReturn = nullptr;
    1625             : 
    1626          56 :     return ToPointer(hSRS)->exportToPrettyWkt(ppszReturn, bSimplify);
    1627             : }
    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       12486 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult) const
    1654             : 
    1655             : {
    1656       12486 :     return exportToWkt(ppszResult, nullptr);
    1657             : }
    1658             : 
    1659             : /************************************************************************/
    1660             : /*              GDAL_proj_crs_create_bound_crs_to_WGS84()               */
    1661             : /************************************************************************/
    1662             : 
    1663         600 : static PJ *GDAL_proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, PJ *pj,
    1664             :                                                    bool onlyIfEPSGCode,
    1665             :                                                    bool canModifyHorizPart)
    1666             : {
    1667         600 :     PJ *ret = nullptr;
    1668         600 :     if (proj_get_type(pj) == PJ_TYPE_COMPOUND_CRS)
    1669             :     {
    1670          13 :         auto horizCRS = proj_crs_get_sub_crs(ctx, pj, 0);
    1671          13 :         auto vertCRS = proj_crs_get_sub_crs(ctx, pj, 1);
    1672          13 :         if (horizCRS && proj_get_type(horizCRS) != PJ_TYPE_BOUND_CRS &&
    1673          26 :             vertCRS &&
    1674          10 :             (!onlyIfEPSGCode || proj_get_id_auth_name(horizCRS, 0) != nullptr))
    1675             :         {
    1676             :             auto boundHoriz =
    1677             :                 canModifyHorizPart
    1678           3 :                     ? proj_crs_create_bound_crs_to_WGS84(ctx, horizCRS, nullptr)
    1679           3 :                     : proj_clone(ctx, horizCRS);
    1680             :             auto boundVert =
    1681           3 :                 proj_crs_create_bound_crs_to_WGS84(ctx, vertCRS, nullptr);
    1682           3 :             if (boundHoriz && boundVert)
    1683             :             {
    1684           3 :                 ret = proj_create_compound_crs(ctx, proj_get_name(pj),
    1685             :                                                boundHoriz, boundVert);
    1686             :             }
    1687           3 :             proj_destroy(boundHoriz);
    1688           3 :             proj_destroy(boundVert);
    1689             :         }
    1690          13 :         proj_destroy(horizCRS);
    1691          13 :         proj_destroy(vertCRS);
    1692             :     }
    1693        1134 :     else if (proj_get_type(pj) != PJ_TYPE_BOUND_CRS &&
    1694         547 :              (!onlyIfEPSGCode || proj_get_id_auth_name(pj, 0) != nullptr))
    1695             :     {
    1696         242 :         ret = proj_crs_create_bound_crs_to_WGS84(ctx, pj, nullptr);
    1697             :     }
    1698         600 :     return ret;
    1699             : }
    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       17339 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult,
    1746             :                                         const char *const *papszOptions) const
    1747             : {
    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       34678 :     std::lock_guard oLock(d->m_mutex);
    1752             : 
    1753       17339 :     d->refreshProjObj();
    1754       17339 :     if (!d->m_pj_crs)
    1755             :     {
    1756          21 :         *ppszResult = CPLStrdup("");
    1757          21 :         return OGRERR_FAILURE;
    1758             :     }
    1759             : 
    1760       17318 :     if (d->m_bHasCenterLong && d->m_poRoot && !d->m_bMorphToESRI)
    1761             :     {
    1762           0 :         return d->m_poRoot->exportToWkt(ppszResult);
    1763             :     }
    1764             : 
    1765       17318 :     auto ctxt = d->getPROJContext();
    1766       17318 :     auto wktFormat = PJ_WKT1_GDAL;
    1767             :     const char *pszFormat =
    1768       17318 :         CSLFetchNameValueDef(papszOptions, "FORMAT",
    1769             :                              CPLGetConfigOption("OSR_WKT_FORMAT", "DEFAULT"));
    1770       17318 :     if (EQUAL(pszFormat, "DEFAULT"))
    1771       14589 :         pszFormat = "";
    1772             : 
    1773       17318 :     if (EQUAL(pszFormat, "WKT1_ESRI") || d->m_bMorphToESRI)
    1774             :     {
    1775         650 :         wktFormat = PJ_WKT1_ESRI;
    1776             :     }
    1777       16668 :     else if (EQUAL(pszFormat, "WKT1") || EQUAL(pszFormat, "WKT1_GDAL") ||
    1778       15923 :              EQUAL(pszFormat, "WKT1_SIMPLE") || EQUAL(pszFormat, "SFSQL"))
    1779             :     {
    1780         750 :         wktFormat = PJ_WKT1_GDAL;
    1781             :     }
    1782       15918 :     else if (EQUAL(pszFormat, "WKT2_2015"))
    1783             :     {
    1784         317 :         wktFormat = PJ_WKT2_2015;
    1785             :     }
    1786       15601 :     else if (EQUAL(pszFormat, "WKT2") || EQUAL(pszFormat, "WKT2_2018") ||
    1787       15229 :              EQUAL(pszFormat, "WKT2_2019"))
    1788             :     {
    1789        1241 :         wktFormat = PJ_WKT2_2018;
    1790             :     }
    1791       14360 :     else if (pszFormat[0] == '\0')
    1792             :     {
    1793             :         // cppcheck-suppress knownConditionTrueFalse
    1794       14360 :         if (IsDerivedGeographic())
    1795             :         {
    1796           2 :             wktFormat = PJ_WKT2_2018;
    1797             :         }
    1798       28083 :         else if ((IsGeographic() || IsProjected()) && !IsCompound() &&
    1799       13725 :                  GetAxesCount() == 3)
    1800             :         {
    1801          56 :             wktFormat = PJ_WKT2_2018;
    1802             :         }
    1803             :     }
    1804             :     else
    1805             :     {
    1806           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unsupported value for FORMAT");
    1807           0 :         *ppszResult = CPLStrdup("");
    1808           0 :         return OGRERR_FAILURE;
    1809             :     }
    1810             : 
    1811       34636 :     CPLStringList aosOptions;
    1812       17318 :     if (wktFormat != PJ_WKT1_ESRI)
    1813             :     {
    1814       16668 :         aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
    1815             :     }
    1816             :     aosOptions.SetNameValue(
    1817       17318 :         "MULTILINE", CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO"));
    1818             : 
    1819       17318 :     const char *pszAllowEllpsHeightAsVertCS = CSLFetchNameValue(
    1820             :         papszOptions, "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS");
    1821       17318 :     if (pszAllowEllpsHeightAsVertCS)
    1822             :     {
    1823             :         aosOptions.SetNameValue("ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS",
    1824           0 :                                 pszAllowEllpsHeightAsVertCS);
    1825             :     }
    1826             : 
    1827       17318 :     PJ *boundCRS = nullptr;
    1828       32370 :     if (wktFormat == PJ_WKT1_GDAL &&
    1829       15052 :         CPLTestBool(CSLFetchNameValueDef(
    1830             :             papszOptions, "ADD_TOWGS84_ON_EXPORT_TO_WKT1",
    1831             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1", "NO"))))
    1832             :     {
    1833           0 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
    1834           0 :             d->getPROJContext(), d->m_pj_crs, true, true);
    1835             :     }
    1836             : 
    1837       34636 :     CPLErrorAccumulator oErrorAccumulator;
    1838             :     const char *pszWKT;
    1839             :     {
    1840       17318 :         auto oAccumulator = oErrorAccumulator.InstallForCurrentScope();
    1841       17318 :         CPL_IGNORE_RET_VAL(oAccumulator);
    1842       17318 :         pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs, wktFormat,
    1843       17318 :                              aosOptions.List());
    1844             :     }
    1845       17320 :     for (const auto &oError : oErrorAccumulator.GetErrors())
    1846             :     {
    1847          32 :         if (pszFormat[0] == '\0' &&
    1848          14 :             (oError.msg.find("Unsupported conversion method") !=
    1849           2 :                  std::string::npos ||
    1850           2 :              oError.msg.find("can only be exported to WKT2") !=
    1851           0 :                  std::string::npos ||
    1852           0 :              oError.msg.find("can only be exported since WKT2:2019") !=
    1853             :                  std::string::npos))
    1854             :         {
    1855          14 :             CPLErrorReset();
    1856             :             // If we cannot export in the default mode (WKT1), retry with WKT2
    1857          14 :             pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs,
    1858          14 :                                  PJ_WKT2_2018, aosOptions.List());
    1859          14 :             break;
    1860             :         }
    1861           2 :         CPLError(oError.type, oError.no, "%s", oError.msg.c_str());
    1862             :     }
    1863             : 
    1864       17318 :     if (!pszWKT)
    1865             :     {
    1866           2 :         *ppszResult = CPLStrdup("");
    1867           2 :         proj_destroy(boundCRS);
    1868           2 :         return OGRERR_FAILURE;
    1869             :     }
    1870             : 
    1871       17316 :     if (EQUAL(pszFormat, "SFSQL") || EQUAL(pszFormat, "WKT1_SIMPLE"))
    1872             :     {
    1873           5 :         OGR_SRSNode oRoot;
    1874           5 :         oRoot.importFromWkt(&pszWKT);
    1875           5 :         oRoot.StripNodes("AXIS");
    1876           5 :         if (EQUAL(pszFormat, "SFSQL"))
    1877             :         {
    1878           3 :             oRoot.StripNodes("TOWGS84");
    1879             :         }
    1880           5 :         oRoot.StripNodes("AUTHORITY");
    1881           5 :         oRoot.StripNodes("EXTENSION");
    1882             :         OGRErr eErr;
    1883           5 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO")))
    1884           2 :             eErr = oRoot.exportToPrettyWkt(ppszResult, 1);
    1885             :         else
    1886           3 :             eErr = oRoot.exportToWkt(ppszResult);
    1887           5 :         proj_destroy(boundCRS);
    1888           5 :         return eErr;
    1889             :     }
    1890             : 
    1891       17311 :     *ppszResult = CPLStrdup(pszWKT);
    1892             : 
    1893             : #if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
    1894       17311 :     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        1299 :         char *pszPtr = strstr(*ppszResult, "CONVERSION[\"UTM zone ");
    1899        1299 :         if (pszPtr)
    1900             :         {
    1901         318 :             pszPtr += strlen("CONVERSION[\"UTM zone ");
    1902         318 :             const int nZone = atoi(pszPtr);
    1903         953 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    1904         635 :                 ++pszPtr;
    1905         318 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' &&
    1906           1 :                 pszPtr[1] == '"' && pszPtr[2] == ',')
    1907             :             {
    1908           1 :                 pszPtr += 3;
    1909           1 :                 int nLevel = 0;
    1910           1 :                 bool bInString = false;
    1911             :                 // Find the ID node corresponding to this CONVERSION node
    1912         480 :                 while (*pszPtr)
    1913             :                 {
    1914         480 :                     if (bInString)
    1915             :                     {
    1916         197 :                         if (*pszPtr == '"' && pszPtr[1] == '"')
    1917             :                         {
    1918           0 :                             ++pszPtr;
    1919             :                         }
    1920         197 :                         else if (*pszPtr == '"')
    1921             :                         {
    1922          17 :                             bInString = false;
    1923             :                         }
    1924             :                     }
    1925         283 :                     else if (nLevel == 0 && STARTS_WITH_CI(pszPtr, "ID["))
    1926             :                     {
    1927           1 :                         if (STARTS_WITH_CI(pszPtr, CPLSPrintf("ID[\"EPSG\",%d]",
    1928             :                                                               17000 + nZone)))
    1929             :                         {
    1930           1 :                             CPLAssert(pszPtr[11] == '7');
    1931           1 :                             CPLAssert(pszPtr[12] == '0');
    1932           1 :                             pszPtr[11] = '6';
    1933           1 :                             pszPtr[12] = '1';
    1934             :                         }
    1935           1 :                         break;
    1936             :                     }
    1937         282 :                     else if (*pszPtr == '"')
    1938             :                     {
    1939          17 :                         bInString = true;
    1940             :                     }
    1941         265 :                     else if (*pszPtr == '[')
    1942             :                     {
    1943          17 :                         ++nLevel;
    1944             :                     }
    1945         248 :                     else if (*pszPtr == ']')
    1946             :                     {
    1947          17 :                         --nLevel;
    1948             :                     }
    1949             : 
    1950         479 :                     ++pszPtr;
    1951             :                 }
    1952             :             }
    1953             :         }
    1954             :     }
    1955             : #endif
    1956             : 
    1957       17311 :     proj_destroy(boundCRS);
    1958       17311 :     return OGRERR_NONE;
    1959             : }
    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         305 : OGRSpatialReference::exportToWkt(const char *const *papszOptions) const
    2003             : {
    2004         305 :     std::string osWKT;
    2005         305 :     char *pszWKT = nullptr;
    2006         305 :     if (exportToWkt(&pszWKT, papszOptions) == OGRERR_NONE)
    2007         305 :         osWKT = pszWKT;
    2008         305 :     CPLFree(pszWKT);
    2009         610 :     return osWKT;
    2010             : }
    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         984 : OGRErr CPL_STDCALL OSRExportToWkt(OGRSpatialReferenceH hSRS, char **ppszReturn)
    2030             : 
    2031             : {
    2032         984 :     VALIDATE_POINTER1(hSRS, "OSRExportToWkt", OGRERR_FAILURE);
    2033             : 
    2034         984 :     *ppszReturn = nullptr;
    2035             : 
    2036         984 :     return ToPointer(hSRS)->exportToWkt(ppszReturn);
    2037             : }
    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        1456 : OGRErr OSRExportToWktEx(OGRSpatialReferenceH hSRS, char **ppszReturn,
    2053             :                         const char *const *papszOptions)
    2054             : {
    2055        1456 :     VALIDATE_POINTER1(hSRS, "OSRExportToWktEx", OGRERR_FAILURE);
    2056             : 
    2057        1456 :     *ppszReturn = nullptr;
    2058             : 
    2059        1456 :     return ToPointer(hSRS)->exportToWkt(ppszReturn, papszOptions);
    2060             : }
    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        2735 : OGRErr OGRSpatialReference::exportToPROJJSON(
    2088             :     char **ppszResult, CPL_UNUSED const char *const *papszOptions) const
    2089             : {
    2090        5470 :     TAKE_OPTIONAL_LOCK();
    2091             : 
    2092        2735 :     d->refreshProjObj();
    2093        2735 :     if (!d->m_pj_crs)
    2094             :     {
    2095           1 :         *ppszResult = nullptr;
    2096           1 :         return OGRERR_FAILURE;
    2097             :     }
    2098             : 
    2099             :     const char *pszPROJJSON =
    2100        2734 :         proj_as_projjson(d->getPROJContext(), d->m_pj_crs, papszOptions);
    2101             : 
    2102        2734 :     if (!pszPROJJSON)
    2103             :     {
    2104           0 :         *ppszResult = CPLStrdup("");
    2105           0 :         return OGRERR_FAILURE;
    2106             :     }
    2107             : 
    2108        2734 :     *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        2734 :         char *pszPtr = strstr(*ppszResult, "\"name\": \"UTM zone ");
    2115        2734 :         if (pszPtr)
    2116             :         {
    2117         257 :             pszPtr += strlen("\"name\": \"UTM zone ");
    2118         257 :             const int nZone = atoi(pszPtr);
    2119         770 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    2120         513 :                 ++pszPtr;
    2121         257 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' && pszPtr[1] == '"')
    2122             :             {
    2123           4 :                 pszPtr += 2;
    2124           4 :                 int nLevel = 0;
    2125           4 :                 bool bInString = false;
    2126             :                 // Find the id node corresponding to this conversion node
    2127        5299 :                 while (*pszPtr)
    2128             :                 {
    2129        5299 :                     if (bInString)
    2130             :                     {
    2131        1950 :                         if (*pszPtr == '\\')
    2132             :                         {
    2133           0 :                             ++pszPtr;
    2134             :                         }
    2135        1950 :                         else if (*pszPtr == '"')
    2136             :                         {
    2137         244 :                             bInString = false;
    2138             :                         }
    2139             :                     }
    2140        3349 :                     else if (nLevel == 0 && STARTS_WITH(pszPtr, "\"id\": {"))
    2141             :                     {
    2142           4 :                         const char *pszNextEndCurl = strchr(pszPtr, '}');
    2143             :                         const char *pszAuthEPSG =
    2144           4 :                             strstr(pszPtr, "\"authority\": \"EPSG\"");
    2145           4 :                         char *pszCode = strstr(
    2146             :                             pszPtr, CPLSPrintf("\"code\": %d", 17000 + nZone));
    2147           4 :                         if (pszAuthEPSG && pszCode && pszNextEndCurl &&
    2148           4 :                             pszNextEndCurl - pszAuthEPSG > 0 &&
    2149           4 :                             pszNextEndCurl - pszCode > 0)
    2150             :                         {
    2151           4 :                             CPLAssert(pszCode[9] == '7');
    2152           4 :                             CPLAssert(pszCode[10] == '0');
    2153           4 :                             pszCode[9] = '6';
    2154           4 :                             pszCode[10] = '1';
    2155             :                         }
    2156           4 :                         break;
    2157             :                     }
    2158        3345 :                     else if (*pszPtr == '"')
    2159             :                     {
    2160         244 :                         bInString = true;
    2161             :                     }
    2162        3101 :                     else if (*pszPtr == '{' || *pszPtr == '[')
    2163             :                     {
    2164          60 :                         ++nLevel;
    2165             :                     }
    2166        3041 :                     else if (*pszPtr == '}' || *pszPtr == ']')
    2167             :                     {
    2168          60 :                         --nLevel;
    2169             :                     }
    2170             : 
    2171        5295 :                     ++pszPtr;
    2172             :                 }
    2173             :             }
    2174             :         }
    2175             :     }
    2176             : #endif
    2177             : 
    2178        2734 :     return OGRERR_NONE;
    2179             : }
    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          82 : OGRErr OSRExportToPROJJSON(OGRSpatialReferenceH hSRS, char **ppszReturn,
    2194             :                            const char *const *papszOptions)
    2195             : {
    2196          82 :     VALIDATE_POINTER1(hSRS, "OSRExportToPROJJSON", OGRERR_FAILURE);
    2197             : 
    2198          82 :     *ppszReturn = nullptr;
    2199             : 
    2200          82 :     return ToPointer(hSRS)->exportToPROJJSON(ppszReturn, papszOptions);
    2201             : }
    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       18211 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput)
    2234             : 
    2235             : {
    2236       18211 :     return importFromWkt(ppszInput, nullptr);
    2237             : }
    2238             : 
    2239             : /************************************************************************/
    2240             : /*                           importFromWkt()                            */
    2241             : /************************************************************************/
    2242             : 
    2243             : /*! @cond Doxygen_Suppress */
    2244             : 
    2245          21 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput,
    2246             :                                           CSLConstList papszOptions)
    2247             : 
    2248             : {
    2249          21 :     return importFromWkt(&pszInput, papszOptions);
    2250             : }
    2251             : 
    2252       18232 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput,
    2253             :                                           CSLConstList papszOptions)
    2254             : 
    2255             : {
    2256       36464 :     TAKE_OPTIONAL_LOCK();
    2257             : 
    2258       18232 :     if (!ppszInput || !*ppszInput)
    2259           0 :         return OGRERR_FAILURE;
    2260             : 
    2261       18232 :     if (strlen(*ppszInput) > 100 * 1000 &&
    2262           0 :         CPLTestBool(CPLGetConfigOption("OSR_IMPORT_FROM_WKT_LIMIT", "YES")))
    2263             :     {
    2264           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2265             :                  "Suspiciously large input for importFromWkt(). Rejecting it. "
    2266             :                  "You can remove this limitation by definition the "
    2267             :                  "OSR_IMPORT_FROM_WKT_LIMIT configuration option to NO.");
    2268           0 :         return OGRERR_FAILURE;
    2269             :     }
    2270             : 
    2271       18232 :     Clear();
    2272             : 
    2273       18232 :     bool canCache = false;
    2274       18232 :     auto tlsCache = OSRGetProjTLSCache();
    2275       36464 :     std::string osWkt;
    2276       18232 :     if (**ppszInput)
    2277             :     {
    2278       17676 :         osWkt = *ppszInput;
    2279       17676 :         auto cachedObj = tlsCache->GetPJForWKT(osWkt);
    2280       17676 :         if (cachedObj)
    2281             :         {
    2282       15790 :             d->setPjCRS(cachedObj);
    2283             :         }
    2284             :         else
    2285             :         {
    2286        3772 :             CPLStringList aosOptions(papszOptions);
    2287        1886 :             if (aosOptions.FetchNameValue("STRICT") == nullptr)
    2288        1886 :                 aosOptions.SetNameValue("STRICT", "NO");
    2289        1886 :             PROJ_STRING_LIST warnings = nullptr;
    2290        1886 :             PROJ_STRING_LIST errors = nullptr;
    2291        1886 :             auto ctxt = d->getPROJContext();
    2292        1886 :             auto pj = proj_create_from_wkt(ctxt, *ppszInput, aosOptions.List(),
    2293             :                                            &warnings, &errors);
    2294        1886 :             d->setPjCRS(pj);
    2295             : 
    2296        1939 :             for (auto iter = warnings; iter && *iter; ++iter)
    2297             :             {
    2298          53 :                 d->m_wktImportWarnings.push_back(*iter);
    2299             :             }
    2300        2123 :             for (auto iter = errors; iter && *iter; ++iter)
    2301             :             {
    2302         237 :                 d->m_wktImportErrors.push_back(*iter);
    2303         237 :                 if (!d->m_pj_crs)
    2304             :                 {
    2305          34 :                     CPLError(CE_Failure, CPLE_AppDefined, "%s", *iter);
    2306             :                 }
    2307             :             }
    2308        1886 :             if (warnings == nullptr && errors == nullptr)
    2309             :             {
    2310        1605 :                 canCache = true;
    2311             :             }
    2312        1886 :             proj_string_list_destroy(warnings);
    2313        1886 :             proj_string_list_destroy(errors);
    2314             :         }
    2315             :     }
    2316       18232 :     if (!d->m_pj_crs)
    2317         590 :         return OGRERR_CORRUPT_DATA;
    2318             : 
    2319             :     // Only accept CRS objects
    2320       17642 :     if (!proj_is_crs(d->m_pj_crs))
    2321             :     {
    2322           0 :         Clear();
    2323           0 :         return OGRERR_CORRUPT_DATA;
    2324             :     }
    2325             : 
    2326       17642 :     if (canCache)
    2327             :     {
    2328        1605 :         tlsCache->CachePJForWKT(osWkt, d->m_pj_crs);
    2329             :     }
    2330             : 
    2331       17642 :     if (strstr(*ppszInput, "CENTER_LONG"))
    2332             :     {
    2333           0 :         auto poRoot = new OGR_SRSNode();
    2334           0 :         d->setRoot(poRoot);
    2335           0 :         const char *pszTmp = *ppszInput;
    2336           0 :         poRoot->importFromWkt(&pszTmp);
    2337           0 :         d->m_bHasCenterLong = true;
    2338             :     }
    2339             : 
    2340             :     // TODO? we don't really update correctly since we assume that the
    2341             :     // passed string is only WKT.
    2342       17642 :     *ppszInput += strlen(*ppszInput);
    2343       17642 :     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             : }
    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           0 : OGRErr OGRSpatialReference::importFromWkt(char **ppszInput)
    2390             : 
    2391             : {
    2392           0 :     return importFromWkt(const_cast<const char **>(ppszInput));
    2393             : }
    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       17921 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput)
    2414             : {
    2415       17921 :     return importFromWkt(&pszInput);
    2416             : }
    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         116 : OGRErr OGRSpatialReference::Validate() const
    2438             : 
    2439             : {
    2440         232 :     TAKE_OPTIONAL_LOCK();
    2441             : 
    2442         154 :     for (const auto &str : d->m_wktImportErrors)
    2443             :     {
    2444          38 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2445             :     }
    2446         116 :     for (const auto &str : d->m_wktImportWarnings)
    2447             :     {
    2448           0 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2449             :     }
    2450         116 :     if (!d->m_pj_crs || !d->m_wktImportErrors.empty())
    2451             :     {
    2452          37 :         return OGRERR_CORRUPT_DATA;
    2453             :     }
    2454          79 :     if (!d->m_wktImportWarnings.empty())
    2455             :     {
    2456           0 :         return OGRERR_UNSUPPORTED_SRS;
    2457             :     }
    2458          79 :     return OGRERR_NONE;
    2459             : }
    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         114 : OGRErr OSRValidate(OGRSpatialReferenceH hSRS)
    2470             : 
    2471             : {
    2472         114 :     VALIDATE_POINTER1(hSRS, "OSRValidate", OGRERR_FAILURE);
    2473             : 
    2474         114 :     return OGRSpatialReference::FromHandle(hSRS)->Validate();
    2475             : }
    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         290 : OGRErr OSRImportFromWkt(OGRSpatialReferenceH hSRS, char **ppszInput)
    2491             : 
    2492             : {
    2493         290 :     VALIDATE_POINTER1(hSRS, "OSRImportFromWkt", OGRERR_FAILURE);
    2494             : 
    2495         290 :     return ToPointer(hSRS)->importFromWkt(const_cast<const char **>(ppszInput));
    2496             : }
    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         583 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath,
    2522             :                                     const char *pszNewNodeValue)
    2523             : 
    2524             : {
    2525        1166 :     TAKE_OPTIONAL_LOCK();
    2526             : 
    2527             :     char **papszPathTokens =
    2528         583 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    2529             : 
    2530         583 :     if (CSLCount(papszPathTokens) < 1)
    2531             :     {
    2532           0 :         CSLDestroy(papszPathTokens);
    2533           0 :         return OGRERR_FAILURE;
    2534             :     }
    2535             : 
    2536        1018 :     if (GetRoot() == nullptr ||
    2537         435 :         !EQUAL(papszPathTokens[0], GetRoot()->GetValue()))
    2538             :     {
    2539         268 :         if (EQUAL(papszPathTokens[0], "PROJCS") &&
    2540         116 :             CSLCount(papszPathTokens) == 1)
    2541             :         {
    2542         116 :             CSLDestroy(papszPathTokens);
    2543         116 :             return SetProjCS(pszNewNodeValue);
    2544             :         }
    2545             :         else
    2546             :         {
    2547          36 :             SetRoot(new OGR_SRSNode(papszPathTokens[0]));
    2548             :         }
    2549             :     }
    2550             : 
    2551         467 :     OGR_SRSNode *poNode = GetRoot();
    2552         725 :     for (int i = 1; papszPathTokens[i] != nullptr; i++)
    2553             :     {
    2554         258 :         int j = 0;  // Used after for.
    2555             : 
    2556         645 :         for (; j < poNode->GetChildCount(); j++)
    2557             :         {
    2558         585 :             if (EQUAL(poNode->GetChild(j)->GetValue(), papszPathTokens[i]))
    2559             :             {
    2560         198 :                 poNode = poNode->GetChild(j);
    2561         198 :                 j = -1;
    2562         198 :                 break;
    2563             :             }
    2564             :         }
    2565             : 
    2566         258 :         if (j != -1)
    2567             :         {
    2568          60 :             OGR_SRSNode *poNewNode = new OGR_SRSNode(papszPathTokens[i]);
    2569          60 :             poNode->AddChild(poNewNode);
    2570          60 :             poNode = poNewNode;
    2571             :         }
    2572             :     }
    2573             : 
    2574         467 :     CSLDestroy(papszPathTokens);
    2575             : 
    2576         467 :     if (pszNewNodeValue != nullptr)
    2577             :     {
    2578         467 :         if (poNode->GetChildCount() > 0)
    2579         371 :             poNode->GetChild(0)->SetValue(pszNewNodeValue);
    2580             :         else
    2581          96 :             poNode->AddChild(new OGR_SRSNode(pszNewNodeValue));
    2582             :     };
    2583         467 :     return OGRERR_NONE;
    2584             : }
    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           1 : OGRErr CPL_STDCALL OSRSetAttrValue(OGRSpatialReferenceH hSRS,
    2596             :                                    const char *pszPath, const char *pszValue)
    2597             : 
    2598             : {
    2599           1 :     VALIDATE_POINTER1(hSRS, "OSRSetAttrValue", OGRERR_FAILURE);
    2600             : 
    2601           1 :     return ToPointer(hSRS)->SetNode(pszPath, pszValue);
    2602             : }
    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           0 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath, double dfValue)
    2626             : 
    2627             : {
    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             :     else
    2633           0 :         OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    2634             : 
    2635           0 :     return SetNode(pszNodePath, szValue);
    2636             : }
    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        1435 : OGRErr OGRSpatialReference::SetAngularUnits(const char *pszUnitsName,
    2661             :                                             double dfInRadians)
    2662             : 
    2663             : {
    2664        2870 :     TAKE_OPTIONAL_LOCK();
    2665             : 
    2666        1435 :     d->bNormInfoSet = FALSE;
    2667             : 
    2668        1435 :     d->refreshProjObj();
    2669        1435 :     if (!d->m_pj_crs)
    2670           0 :         return OGRERR_FAILURE;
    2671        1435 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2672        1435 :     if (!geodCRS)
    2673           0 :         return OGRERR_FAILURE;
    2674        1435 :     proj_destroy(geodCRS);
    2675        1435 :     d->demoteFromBoundCRS();
    2676        1435 :     d->setPjCRS(proj_crs_alter_cs_angular_unit(d->getPROJContext(), d->m_pj_crs,
    2677             :                                                pszUnitsName, dfInRadians,
    2678             :                                                nullptr, nullptr));
    2679        1435 :     d->undoDemoteFromBoundCRS();
    2680             : 
    2681        1435 :     d->m_osAngularUnits = pszUnitsName;
    2682        1435 :     d->m_dfAngularUnitToRadian = dfInRadians;
    2683             : 
    2684        1435 :     return OGRERR_NONE;
    2685             : }
    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          48 : OGRErr OSRSetAngularUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2697             :                           double dfInRadians)
    2698             : 
    2699             : {
    2700          48 :     VALIDATE_POINTER1(hSRS, "OSRSetAngularUnits", OGRERR_FAILURE);
    2701             : 
    2702          48 :     return ToPointer(hSRS)->SetAngularUnits(pszUnits, dfInRadians);
    2703             : }
    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        8509 : double OGRSpatialReference::GetAngularUnits(const char **ppszName) const
    2728             : 
    2729             : {
    2730       17018 :     TAKE_OPTIONAL_LOCK();
    2731             : 
    2732        8509 :     d->refreshProjObj();
    2733             : 
    2734        8509 :     if (!d->m_osAngularUnits.empty())
    2735             :     {
    2736        2374 :         if (ppszName != nullptr)
    2737         182 :             *ppszName = d->m_osAngularUnits.c_str();
    2738        2374 :         return d->m_dfAngularUnitToRadian;
    2739             :     }
    2740             : 
    2741             :     do
    2742             :     {
    2743        6135 :         if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    2744             :         {
    2745         113 :             break;
    2746             :         }
    2747             : 
    2748             :         auto geodCRS =
    2749        6024 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2750        6024 :         if (!geodCRS)
    2751             :         {
    2752           0 :             break;
    2753             :         }
    2754             :         auto coordSys =
    2755        6024 :             proj_crs_get_coordinate_system(d->getPROJContext(), geodCRS);
    2756        6024 :         proj_destroy(geodCRS);
    2757        6024 :         if (!coordSys)
    2758             :         {
    2759           0 :             break;
    2760             :         }
    2761        6024 :         if (proj_cs_get_type(d->getPROJContext(), coordSys) !=
    2762             :             PJ_CS_TYPE_ELLIPSOIDAL)
    2763             :         {
    2764           2 :             proj_destroy(coordSys);
    2765           2 :             break;
    2766             :         }
    2767             : 
    2768        6022 :         double dfConvFactor = 0.0;
    2769        6022 :         const char *pszUnitName = nullptr;
    2770        6022 :         if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
    2771             :                                    nullptr, nullptr, &dfConvFactor,
    2772             :                                    &pszUnitName, nullptr, nullptr))
    2773             :         {
    2774           0 :             proj_destroy(coordSys);
    2775           0 :             break;
    2776             :         }
    2777             : 
    2778        6022 :         d->m_osAngularUnits = pszUnitName;
    2779             : 
    2780        6022 :         proj_destroy(coordSys);
    2781        6022 :         d->m_dfAngularUnitToRadian = dfConvFactor;
    2782             :     } while (false);
    2783             : 
    2784        6135 :     if (d->m_osAngularUnits.empty())
    2785             :     {
    2786         113 :         d->m_osAngularUnits = "degree";
    2787         113 :         d->m_dfAngularUnitToRadian = CPLAtof(SRS_UA_DEGREE_CONV);
    2788             :     }
    2789             : 
    2790        6135 :     if (ppszName != nullptr)
    2791        3195 :         *ppszName = d->m_osAngularUnits.c_str();
    2792        6135 :     return d->m_dfAngularUnitToRadian;
    2793             : }
    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           0 : double OGRSpatialReference::GetAngularUnits(char **ppszName) const
    2815             : 
    2816             : {
    2817           0 :     return GetAngularUnits(const_cast<const char **>(ppszName));
    2818             : }
    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           1 : double OSRGetAngularUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    2830             : 
    2831             : {
    2832           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAngularUnits", 0);
    2833             : 
    2834           1 :     return ToPointer(hSRS)->GetAngularUnits(
    2835           1 :         const_cast<const char **>(ppszName));
    2836             : }
    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          39 : OGRErr OGRSpatialReference::SetLinearUnitsAndUpdateParameters(
    2866             :     const char *pszName, double dfInMeters, const char *pszUnitAuthority,
    2867             :     const char *pszUnitCode)
    2868             : 
    2869             : {
    2870          78 :     TAKE_OPTIONAL_LOCK();
    2871             : 
    2872          39 :     if (dfInMeters <= 0.0)
    2873           0 :         return OGRERR_FAILURE;
    2874             : 
    2875          39 :     d->refreshProjObj();
    2876          39 :     if (!d->m_pj_crs)
    2877           0 :         return OGRERR_FAILURE;
    2878             : 
    2879          39 :     d->demoteFromBoundCRS();
    2880          39 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    2881             :     {
    2882          78 :         d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    2883          39 :             d->getPROJContext(), d->m_pj_crs, pszName, dfInMeters,
    2884             :             pszUnitAuthority, pszUnitCode, true));
    2885             :     }
    2886          39 :     d->setPjCRS(proj_crs_alter_cs_linear_unit(d->getPROJContext(), d->m_pj_crs,
    2887             :                                               pszName, dfInMeters,
    2888             :                                               pszUnitAuthority, pszUnitCode));
    2889          39 :     d->undoDemoteFromBoundCRS();
    2890             : 
    2891          39 :     d->m_osLinearUnits = pszName;
    2892          39 :     d->dfToMeter = dfInMeters;
    2893             : 
    2894          39 :     return OGRERR_NONE;
    2895             : }
    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           1 : OGRErr OSRSetLinearUnitsAndUpdateParameters(OGRSpatialReferenceH hSRS,
    2908             :                                             const char *pszUnits,
    2909             :                                             double dfInMeters)
    2910             : 
    2911             : {
    2912           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnitsAndUpdateParameters",
    2913             :                       OGRERR_FAILURE);
    2914             : 
    2915           1 :     return ToPointer(hSRS)->SetLinearUnitsAndUpdateParameters(pszUnits,
    2916           1 :                                                               dfInMeters);
    2917             : }
    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        7318 : OGRErr OGRSpatialReference::SetLinearUnits(const char *pszUnitsName,
    2944             :                                            double dfInMeters)
    2945             : 
    2946             : {
    2947        7318 :     return SetTargetLinearUnits(nullptr, pszUnitsName, dfInMeters);
    2948             : }
    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           7 : OGRErr OSRSetLinearUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2960             :                          double dfInMeters)
    2961             : 
    2962             : {
    2963           7 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnits", OGRERR_FAILURE);
    2964             : 
    2965           7 :     return ToPointer(hSRS)->SetLinearUnits(pszUnits, dfInMeters);
    2966             : }
    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       11686 : OGRErr OGRSpatialReference::SetTargetLinearUnits(const char *pszTargetKey,
    3000             :                                                  const char *pszUnitsName,
    3001             :                                                  double dfInMeters,
    3002             :                                                  const char *pszUnitAuthority,
    3003             :                                                  const char *pszUnitCode)
    3004             : 
    3005             : {
    3006       23372 :     TAKE_OPTIONAL_LOCK();
    3007             : 
    3008       11686 :     if (dfInMeters <= 0.0)
    3009           0 :         return OGRERR_FAILURE;
    3010             : 
    3011       11686 :     d->refreshProjObj();
    3012       11686 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    3013       11686 :     if (pszTargetKey == nullptr)
    3014             :     {
    3015       11686 :         if (!d->m_pj_crs)
    3016           0 :             return OGRERR_FAILURE;
    3017             : 
    3018       11686 :         d->demoteFromBoundCRS();
    3019       11686 :         if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3020             :         {
    3021       17574 :             d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    3022        8787 :                 d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    3023             :                 pszUnitAuthority, pszUnitCode, false));
    3024             :         }
    3025       23372 :         d->setPjCRS(proj_crs_alter_cs_linear_unit(
    3026       11686 :             d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    3027             :             pszUnitAuthority, pszUnitCode));
    3028       11686 :         d->undoDemoteFromBoundCRS();
    3029             : 
    3030       11686 :         d->m_osLinearUnits = pszUnitsName;
    3031       11686 :         d->dfToMeter = dfInMeters;
    3032             : 
    3033       11686 :         return OGRERR_NONE;
    3034             :     }
    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             :     else
    3047           0 :         OGRsnPrintDouble(szValue, sizeof(szValue), dfInMeters);
    3048             : 
    3049           0 :     OGR_SRSNode *poUnits = nullptr;
    3050           0 :     if (poCS->FindChild("UNIT") >= 0)
    3051             :     {
    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             :     }
    3060             :     else
    3061             :     {
    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             :     }
    3068             : 
    3069           0 :     return OGRERR_NONE;
    3070             : }
    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           1 : OGRErr OSRSetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3083             :                                const char *pszTargetKey, const char *pszUnits,
    3084             :                                double dfInMeters)
    3085             : 
    3086             : {
    3087           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTargetLinearUnits", OGRERR_FAILURE);
    3088             : 
    3089           1 :     return ToPointer(hSRS)->SetTargetLinearUnits(pszTargetKey, pszUnits,
    3090           1 :                                                  dfInMeters);
    3091             : }
    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           0 : double OGRSpatialReference::GetLinearUnits(char **ppszName) const
    3118             : 
    3119             : {
    3120           0 :     return GetTargetLinearUnits(nullptr, const_cast<const char **>(ppszName));
    3121             : }
    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       20728 : double OGRSpatialReference::GetLinearUnits(const char **ppszName) const
    3142             : 
    3143             : {
    3144       20728 :     return GetTargetLinearUnits(nullptr, ppszName);
    3145             : }
    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         227 : double OSRGetLinearUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    3157             : 
    3158             : {
    3159         227 :     VALIDATE_POINTER1(hSRS, "OSRGetLinearUnits", 0);
    3160             : 
    3161         227 :     return ToPointer(hSRS)->GetLinearUnits(const_cast<const char **>(ppszName));
    3162             : }
    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       20879 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3191             :                                                  const char **ppszName) const
    3192             : 
    3193             : {
    3194       41758 :     TAKE_OPTIONAL_LOCK();
    3195             : 
    3196       20879 :     d->refreshProjObj();
    3197             : 
    3198       20879 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    3199       20879 :     if (pszTargetKey == nullptr)
    3200             :     {
    3201             :         // Use cached result if available
    3202       20788 :         if (!d->m_osLinearUnits.empty())
    3203             :         {
    3204        8881 :             if (ppszName)
    3205        8080 :                 *ppszName = d->m_osLinearUnits.c_str();
    3206        8881 :             return d->dfToMeter;
    3207             :         }
    3208             : 
    3209             :         while (true)
    3210             :         {
    3211       11907 :             if (d->m_pj_crs == nullptr)
    3212             :             {
    3213         242 :                 break;
    3214             :             }
    3215             : 
    3216       11665 :             d->demoteFromBoundCRS();
    3217       11665 :             PJ *coordSys = nullptr;
    3218       11665 :             if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    3219             :             {
    3220          37 :                 for (int iComponent = 0; iComponent < 2; iComponent++)
    3221             :                 {
    3222          37 :                     auto subCRS = proj_crs_get_sub_crs(d->getPROJContext(),
    3223          37 :                                                        d->m_pj_crs, iComponent);
    3224          37 :                     if (subCRS && proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
    3225             :                     {
    3226             :                         auto temp =
    3227           0 :                             proj_get_source_crs(d->getPROJContext(), subCRS);
    3228           0 :                         proj_destroy(subCRS);
    3229           0 :                         subCRS = temp;
    3230             :                     }
    3231          74 :                     if (subCRS &&
    3232          37 :                         (proj_get_type(subCRS) == PJ_TYPE_PROJECTED_CRS ||
    3233          16 :                          proj_get_type(subCRS) == PJ_TYPE_ENGINEERING_CRS ||
    3234          12 :                          proj_get_type(subCRS) == PJ_TYPE_VERTICAL_CRS))
    3235             :                     {
    3236          31 :                         coordSys = proj_crs_get_coordinate_system(
    3237             :                             d->getPROJContext(), subCRS);
    3238          31 :                         proj_destroy(subCRS);
    3239          31 :                         break;
    3240             :                     }
    3241           6 :                     else if (subCRS)
    3242             :                     {
    3243           6 :                         proj_destroy(subCRS);
    3244             :                     }
    3245             :                 }
    3246          31 :                 if (coordSys == nullptr)
    3247             :                 {
    3248           0 :                     d->undoDemoteFromBoundCRS();
    3249           0 :                     break;
    3250             :                 }
    3251             :             }
    3252             :             else
    3253             :             {
    3254       11634 :                 coordSys = proj_crs_get_coordinate_system(d->getPROJContext(),
    3255       11634 :                                                           d->m_pj_crs);
    3256             :             }
    3257             : 
    3258       11665 :             d->undoDemoteFromBoundCRS();
    3259       11665 :             if (!coordSys)
    3260             :             {
    3261           0 :                 break;
    3262             :             }
    3263       11665 :             auto csType = proj_cs_get_type(d->getPROJContext(), coordSys);
    3264             : 
    3265       11665 :             if (csType != PJ_CS_TYPE_CARTESIAN &&
    3266        2247 :                 csType != PJ_CS_TYPE_VERTICAL &&
    3267           0 :                 csType != PJ_CS_TYPE_ELLIPSOIDAL &&
    3268             :                 csType != PJ_CS_TYPE_SPHERICAL)
    3269             :             {
    3270           0 :                 proj_destroy(coordSys);
    3271           0 :                 break;
    3272             :             }
    3273             : 
    3274       11665 :             int axis = 0;
    3275             : 
    3276       11665 :             if (csType == PJ_CS_TYPE_ELLIPSOIDAL ||
    3277             :                 csType == PJ_CS_TYPE_SPHERICAL)
    3278             :             {
    3279             :                 const int axisCount =
    3280        2247 :                     proj_cs_get_axis_count(d->getPROJContext(), coordSys);
    3281             : 
    3282        2247 :                 if (axisCount == 3)
    3283             :                 {
    3284           4 :                     axis = 2;
    3285             :                 }
    3286             :                 else
    3287             :                 {
    3288        2243 :                     proj_destroy(coordSys);
    3289        2243 :                     break;
    3290             :                 }
    3291             :             }
    3292             : 
    3293        9422 :             double dfConvFactor = 0.0;
    3294        9422 :             const char *pszUnitName = nullptr;
    3295        9422 :             if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, axis,
    3296             :                                        nullptr, nullptr, nullptr, &dfConvFactor,
    3297             :                                        &pszUnitName, nullptr, nullptr))
    3298             :             {
    3299           0 :                 proj_destroy(coordSys);
    3300           0 :                 break;
    3301             :             }
    3302             : 
    3303        9422 :             d->m_osLinearUnits = pszUnitName;
    3304        9422 :             d->dfToMeter = dfConvFactor;
    3305        9422 :             if (ppszName)
    3306        1234 :                 *ppszName = d->m_osLinearUnits.c_str();
    3307             : 
    3308        9422 :             proj_destroy(coordSys);
    3309        9422 :             return dfConvFactor;
    3310             :         }
    3311             : 
    3312        2485 :         d->m_osLinearUnits = "unknown";
    3313        2485 :         d->dfToMeter = 1.0;
    3314             : 
    3315        2485 :         if (ppszName != nullptr)
    3316        2299 :             *ppszName = d->m_osLinearUnits.c_str();
    3317        2485 :         return 1.0;
    3318             :     }
    3319             : 
    3320          91 :     const OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
    3321             : 
    3322          91 :     if (ppszName != nullptr)
    3323          37 :         *ppszName = "unknown";
    3324             : 
    3325          91 :     if (poCS == nullptr)
    3326          53 :         return 1.0;
    3327             : 
    3328         114 :     for (int iChild = 0; iChild < poCS->GetChildCount(); iChild++)
    3329             :     {
    3330         114 :         const OGR_SRSNode *poChild = poCS->GetChild(iChild);
    3331             : 
    3332         114 :         if (EQUAL(poChild->GetValue(), "UNIT") && poChild->GetChildCount() >= 2)
    3333             :         {
    3334          38 :             if (ppszName != nullptr)
    3335          37 :                 *ppszName = poChild->GetChild(0)->GetValue();
    3336             : 
    3337          38 :             return CPLAtof(poChild->GetChild(1)->GetValue());
    3338             :         }
    3339             :     }
    3340             : 
    3341           0 :     return 1.0;
    3342             : }
    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           0 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3365             :                                                  char **ppszName) const
    3366             : 
    3367             : {
    3368           0 :     return GetTargetLinearUnits(pszTargetKey,
    3369           0 :                                 const_cast<const char **>(ppszName));
    3370             : }
    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           4 : double OSRGetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3383             :                                const char *pszTargetKey, char **ppszName)
    3384             : 
    3385             : {
    3386           4 :     VALIDATE_POINTER1(hSRS, "OSRGetTargetLinearUnits", 0);
    3387             : 
    3388           4 :     return ToPointer(hSRS)->GetTargetLinearUnits(
    3389           4 :         pszTargetKey, const_cast<const char **>(ppszName));
    3390             : }
    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        1533 : double OGRSpatialReference::GetPrimeMeridian(const char **ppszName) const
    3419             : 
    3420             : {
    3421        3066 :     TAKE_OPTIONAL_LOCK();
    3422             : 
    3423        1533 :     d->refreshProjObj();
    3424             : 
    3425        1533 :     if (!d->m_osPrimeMeridianName.empty())
    3426             :     {
    3427          87 :         if (ppszName != nullptr)
    3428          11 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3429          87 :         return d->dfFromGreenwich;
    3430             :     }
    3431             : 
    3432             :     while (true)
    3433             :     {
    3434        1446 :         if (!d->m_pj_crs)
    3435           0 :             break;
    3436             : 
    3437        1446 :         auto pm = proj_get_prime_meridian(d->getPROJContext(), d->m_pj_crs);
    3438        1446 :         if (!pm)
    3439           0 :             break;
    3440             : 
    3441        1446 :         d->m_osPrimeMeridianName = proj_get_name(pm);
    3442        1446 :         if (ppszName)
    3443          20 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3444        1446 :         double dfLongitude = 0.0;
    3445        1446 :         double dfConvFactor = 0.0;
    3446        1446 :         proj_prime_meridian_get_parameters(
    3447             :             d->getPROJContext(), pm, &dfLongitude, &dfConvFactor, nullptr);
    3448        1446 :         proj_destroy(pm);
    3449        2892 :         d->dfFromGreenwich =
    3450        1446 :             dfLongitude * dfConvFactor / CPLAtof(SRS_UA_DEGREE_CONV);
    3451        1446 :         return d->dfFromGreenwich;
    3452             :     }
    3453             : 
    3454           0 :     d->m_osPrimeMeridianName = SRS_PM_GREENWICH;
    3455           0 :     d->dfFromGreenwich = 0.0;
    3456           0 :     if (ppszName != nullptr)
    3457           0 :         *ppszName = d->m_osPrimeMeridianName.c_str();
    3458           0 :     return d->dfFromGreenwich;
    3459             : }
    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           0 : double OGRSpatialReference::GetPrimeMeridian(char **ppszName) const
    3483             : 
    3484             : {
    3485           0 :     return GetPrimeMeridian(const_cast<const char **>(ppszName));
    3486             : }
    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           0 : double OSRGetPrimeMeridian(OGRSpatialReferenceH hSRS, char **ppszName)
    3498             : 
    3499             : {
    3500           0 :     VALIDATE_POINTER1(hSRS, "OSRGetPrimeMeridian", 0);
    3501             : 
    3502           0 :     return ToPointer(hSRS)->GetPrimeMeridian(
    3503           0 :         const_cast<const char **>(ppszName));
    3504             : }
    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        9596 : 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             : {
    3559       19192 :     TAKE_OPTIONAL_LOCK();
    3560             : 
    3561        9596 :     d->bNormInfoSet = FALSE;
    3562        9596 :     d->m_osAngularUnits.clear();
    3563        9596 :     d->m_dfAngularUnitToRadian = 0.0;
    3564        9596 :     d->m_osPrimeMeridianName.clear();
    3565        9596 :     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        9596 :     if (IsGeocentric())
    3574             :     {
    3575           4 :         OGRSpatialReference oGCS;
    3576             : 
    3577           2 :         oGCS.SetGeogCS(pszGeogName, pszDatumName, pszSpheroidName, dfSemiMajor,
    3578             :                        dfInvFlattening, pszPMName, dfPMOffset, pszAngularUnits,
    3579             :                        dfConvertToRadians);
    3580           2 :         return CopyGeogCSFrom(&oGCS);
    3581             :     }
    3582             : 
    3583        9594 :     auto cs = proj_create_ellipsoidal_2D_cs(
    3584             :         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE, pszAngularUnits,
    3585             :         dfConvertToRadians);
    3586             :     // Prime meridian expressed in Degree
    3587        9594 :     auto obj = proj_create_geographic_crs(
    3588             :         d->getPROJContext(), pszGeogName, pszDatumName, pszSpheroidName,
    3589             :         dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset, nullptr, 0.0, cs);
    3590        9594 :     proj_destroy(cs);
    3591             : 
    3592       14504 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    3593        4910 :         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    3594             :     {
    3595        4684 :         d->setPjCRS(obj);
    3596             :     }
    3597        4910 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3598             :     {
    3599        9820 :         d->setPjCRS(
    3600        4910 :             proj_crs_alter_geodetic_crs(d->getPROJContext(), d->m_pj_crs, obj));
    3601        4910 :         proj_destroy(obj);
    3602             :     }
    3603             :     else
    3604             :     {
    3605           0 :         proj_destroy(obj);
    3606             :     }
    3607             : 
    3608        9594 :     return OGRERR_NONE;
    3609             : }
    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          18 : 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             : {
    3627          18 :     VALIDATE_POINTER1(hSRS, "OSRSetGeogCS", OGRERR_FAILURE);
    3628             : 
    3629          18 :     return ToPointer(hSRS)->SetGeogCS(pszGeogName, pszDatumName,
    3630             :                                       pszSpheroidName, dfSemiMajor,
    3631             :                                       dfInvFlattening, pszPMName, dfPMOffset,
    3632          18 :                                       pszAngularUnits, dfConvertToRadians);
    3633             : }
    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        3494 : OGRErr OGRSpatialReference::SetWellKnownGeogCS(const char *pszName)
    3667             : 
    3668             : {
    3669        6988 :     TAKE_OPTIONAL_LOCK();
    3670             : 
    3671             :     /* -------------------------------------------------------------------- */
    3672             :     /*      Check for EPSG authority numbers.                               */
    3673             :     /* -------------------------------------------------------------------- */
    3674        3494 :     if (STARTS_WITH_CI(pszName, "EPSG:") || STARTS_WITH_CI(pszName, "EPSGA:"))
    3675             :     {
    3676          84 :         OGRSpatialReference oSRS2;
    3677          42 :         const OGRErr eErr = oSRS2.importFromEPSG(atoi(pszName + 5));
    3678          42 :         if (eErr != OGRERR_NONE)
    3679           0 :             return eErr;
    3680             : 
    3681          42 :         if (!oSRS2.IsGeographic())
    3682           0 :             return OGRERR_FAILURE;
    3683             : 
    3684          42 :         return CopyGeogCSFrom(&oSRS2);
    3685             :     }
    3686             : 
    3687             :     /* -------------------------------------------------------------------- */
    3688             :     /*      Check for simple names.                                         */
    3689             :     /* -------------------------------------------------------------------- */
    3690        3452 :     const char *pszWKT = nullptr;
    3691             : 
    3692        3452 :     if (EQUAL(pszName, "WGS84"))
    3693             :     {
    3694        2634 :         pszWKT = SRS_WKT_WGS84_LAT_LONG;
    3695             :     }
    3696         818 :     else if (EQUAL(pszName, "CRS84") || EQUAL(pszName, "CRS:84"))
    3697             :     {
    3698         636 :         pszWKT =
    3699             :             "GEOGCRS[\"WGS 84 (CRS84)\",DATUM[\"World Geodetic System 1984\","
    3700             :             "ELLIPSOID[\"WGS "
    3701             :             "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],"
    3702             :             "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3703             :             "CS[ellipsoidal,2],AXIS[\"geodetic longitude (Lon)\",east,ORDER[1],"
    3704             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3705             :             "AXIS[\"geodetic latitude (Lat)\",north,ORDER[2],"
    3706             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3707             :             "USAGE[SCOPE[\"unknown\"],AREA[\"World\"],BBOX[-90,-180,90,180]],"
    3708             :             "ID[\"OGC\",\"CRS84\"]]";
    3709             :     }
    3710         182 :     else if (EQUAL(pszName, "WGS72"))
    3711          19 :         pszWKT =
    3712             :             "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
    3713             :             "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",\"7043\"]],"
    3714             :             "AUTHORITY[\"EPSG\",\"6322\"]],"
    3715             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3716             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3717             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3718             :             "AUTHORITY[\"EPSG\",\"4322\"]]";
    3719             : 
    3720         163 :     else if (EQUAL(pszName, "NAD27"))
    3721         135 :         pszWKT =
    3722             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3723             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3724             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3725             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3726             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3727             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3728             :             "AUTHORITY[\"EPSG\",\"4267\"]]";
    3729             : 
    3730          28 :     else if (EQUAL(pszName, "CRS27") || EQUAL(pszName, "CRS:27"))
    3731           0 :         pszWKT =
    3732             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3733             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3734             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3735             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3736             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3737             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3738             : 
    3739          28 :     else if (EQUAL(pszName, "NAD83"))
    3740          24 :         pszWKT =
    3741             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3742             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3743             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3744             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3745             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3746             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3747             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],AUTHORITY["
    3748             :             "\"EPSG\",\"4269\"]]";
    3749             : 
    3750           4 :     else if (EQUAL(pszName, "CRS83") || EQUAL(pszName, "CRS:83"))
    3751           0 :         pszWKT =
    3752             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3753             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3754             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3755             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3756             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3757             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3758             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3759             : 
    3760             :     else
    3761           4 :         return OGRERR_FAILURE;
    3762             : 
    3763             :     /* -------------------------------------------------------------------- */
    3764             :     /*      Import the WKT                                                  */
    3765             :     /* -------------------------------------------------------------------- */
    3766        6896 :     OGRSpatialReference oSRS2;
    3767        3448 :     const OGRErr eErr = oSRS2.importFromWkt(pszWKT);
    3768        3448 :     if (eErr != OGRERR_NONE)
    3769           0 :         return eErr;
    3770             : 
    3771             :     /* -------------------------------------------------------------------- */
    3772             :     /*      Copy over.                                                      */
    3773             :     /* -------------------------------------------------------------------- */
    3774        3448 :     return CopyGeogCSFrom(&oSRS2);
    3775             : }
    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         155 : OGRErr OSRSetWellKnownGeogCS(OGRSpatialReferenceH hSRS, const char *pszName)
    3787             : 
    3788             : {
    3789         155 :     VALIDATE_POINTER1(hSRS, "OSRSetWellKnownGeogCS", OGRERR_FAILURE);
    3790             : 
    3791         155 :     return ToPointer(hSRS)->SetWellKnownGeogCS(pszName);
    3792             : }
    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        4102 : OGRErr OGRSpatialReference::CopyGeogCSFrom(const OGRSpatialReference *poSrcSRS)
    3811             : 
    3812             : {
    3813        8204 :     TAKE_OPTIONAL_LOCK();
    3814             : 
    3815        4102 :     d->bNormInfoSet = FALSE;
    3816        4102 :     d->m_osAngularUnits.clear();
    3817        4102 :     d->m_dfAngularUnitToRadian = 0.0;
    3818        4102 :     d->m_osPrimeMeridianName.clear();
    3819        4102 :     d->dfFromGreenwich = 0.0;
    3820             : 
    3821        4102 :     d->refreshProjObj();
    3822        4102 :     poSrcSRS->d->refreshProjObj();
    3823        4102 :     if (!poSrcSRS->d->m_pj_crs)
    3824             :     {
    3825           1 :         return OGRERR_FAILURE;
    3826             :     }
    3827             :     auto geodCRS =
    3828        4101 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3829        4101 :     if (!geodCRS)
    3830             :     {
    3831           0 :         return OGRERR_FAILURE;
    3832             :     }
    3833             : 
    3834             :     /* -------------------------------------------------------------------- */
    3835             :     /*      Handle geocentric coordinate systems specially.  We just        */
    3836             :     /*      want to copy the DATUM.                                         */
    3837             :     /* -------------------------------------------------------------------- */
    3838        4101 :     if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    3839             :     {
    3840           3 :         auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    3841             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    3842             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    3843             :         if (datum == nullptr)
    3844             :         {
    3845             :             datum = proj_crs_get_datum_ensemble(d->getPROJContext(), geodCRS);
    3846             :         }
    3847             : #endif
    3848           3 :         if (datum == nullptr)
    3849             :         {
    3850           0 :             proj_destroy(geodCRS);
    3851           0 :             return OGRERR_FAILURE;
    3852             :         }
    3853             : 
    3854           3 :         const char *pszUnitName = nullptr;
    3855           3 :         double unitConvFactor = GetLinearUnits(&pszUnitName);
    3856             : 
    3857           3 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    3858           3 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, pszUnitName,
    3859             :             unitConvFactor);
    3860           3 :         proj_destroy(datum);
    3861             : 
    3862           3 :         d->setPjCRS(pj_crs);
    3863             :     }
    3864             : 
    3865        4098 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3866             :     {
    3867         319 :         auto pj_crs = proj_crs_alter_geodetic_crs(d->getPROJContext(),
    3868         319 :                                                   d->m_pj_crs, geodCRS);
    3869         319 :         d->setPjCRS(pj_crs);
    3870             :     }
    3871             : 
    3872             :     else
    3873             :     {
    3874        3779 :         d->setPjCRS(proj_clone(d->getPROJContext(), geodCRS));
    3875             :     }
    3876             : 
    3877             :     // Apply TOWGS84 of source CRS
    3878        4101 :     if (poSrcSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
    3879             :     {
    3880             :         auto target =
    3881           1 :             proj_get_target_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3882           1 :         auto co = proj_crs_get_coordoperation(d->getPROJContext(),
    3883           1 :                                               poSrcSRS->d->m_pj_crs);
    3884           1 :         d->setPjCRS(proj_crs_create_bound_crs(d->getPROJContext(), d->m_pj_crs,
    3885             :                                               target, co));
    3886           1 :         proj_destroy(target);
    3887           1 :         proj_destroy(co);
    3888             :     }
    3889             : 
    3890        4101 :     proj_destroy(geodCRS);
    3891             : 
    3892        4101 :     return OGRERR_NONE;
    3893             : }
    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           1 : OGRErr OSRCopyGeogCSFrom(OGRSpatialReferenceH hSRS,
    3905             :                          const OGRSpatialReferenceH hSrcSRS)
    3906             : 
    3907             : {
    3908           1 :     VALIDATE_POINTER1(hSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3909           1 :     VALIDATE_POINTER1(hSrcSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3910             : 
    3911           1 :     return ToPointer(hSRS)->CopyGeogCSFrom(ToPointer(hSrcSRS));
    3912             : }
    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        2819 : CSLConstList OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()
    3929             : {
    3930        2819 :     return SET_FROM_USER_INPUT_LIMITATIONS;
    3931             : }
    3932             : 
    3933             : /************************************************************************/
    3934             : /*                   RemoveIDFromMemberOfEnsembles()                    */
    3935             : /************************************************************************/
    3936             : 
    3937             : // cppcheck-suppress constParameterReference
    3938         243 : static void RemoveIDFromMemberOfEnsembles(CPLJSONObject &obj)
    3939             : {
    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         243 :     if (obj.GetType() == CPLJSONObject::Type::Object)
    3945             :     {
    3946         300 :         for (auto &subObj : obj.GetChildren())
    3947             :         {
    3948         235 :             RemoveIDFromMemberOfEnsembles(subObj);
    3949             :         }
    3950             :     }
    3951         198 :     else if (obj.GetType() == CPLJSONObject::Type::Array &&
    3952         198 :              obj.GetName() == "members")
    3953             :     {
    3954          60 :         for (auto &subObj : obj.ToArray())
    3955             :         {
    3956          52 :             if (subObj.GetType() == CPLJSONObject::Type::Object)
    3957             :             {
    3958          51 :                 subObj.Delete("id");
    3959             :             }
    3960             :         }
    3961             :     }
    3962         243 : }
    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       20988 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition)
    4008             : {
    4009       20988 :     return SetFromUserInput(pszDefinition, nullptr);
    4010             : }
    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       28205 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition,
    4062             :                                              CSLConstList papszOptions)
    4063             : {
    4064       56410 :     TAKE_OPTIONAL_LOCK();
    4065             : 
    4066             :     // Skip leading white space
    4067       28207 :     while (isspace(static_cast<unsigned char>(*pszDefinition)))
    4068           2 :         pszDefinition++;
    4069             : 
    4070       28205 :     if (STARTS_WITH_CI(pszDefinition, "ESRI::"))
    4071             :     {
    4072           1 :         pszDefinition += 6;
    4073             :     }
    4074             : 
    4075             :     /* -------------------------------------------------------------------- */
    4076             :     /*      Is it a recognised syntax?                                      */
    4077             :     /* -------------------------------------------------------------------- */
    4078       28205 :     const char *const wktKeywords[] = {
    4079             :         // WKT1
    4080             :         "GEOGCS", "GEOCCS", "PROJCS", "VERT_CS", "COMPD_CS", "LOCAL_CS",
    4081             :         // WKT2"
    4082             :         "GEODCRS", "GEOGCRS", "GEODETICCRS", "GEOGRAPHICCRS", "PROJCRS",
    4083             :         "PROJECTEDCRS", "VERTCRS", "VERTICALCRS", "COMPOUNDCRS", "ENGCRS",
    4084             :         "ENGINEERINGCRS", "BOUNDCRS", "DERIVEDPROJCRS", "COORDINATEMETADATA"};
    4085      423519 :     for (const char *keyword : wktKeywords)
    4086             :     {
    4087      404351 :         if (STARTS_WITH_CI(pszDefinition, keyword))
    4088             :         {
    4089        9037 :             return importFromWkt(pszDefinition);
    4090             :         }
    4091             :     }
    4092             : 
    4093       19168 :     const bool bStartsWithEPSG = STARTS_WITH_CI(pszDefinition, "EPSG:");
    4094       19168 :     if (bStartsWithEPSG || STARTS_WITH_CI(pszDefinition, "EPSGA:"))
    4095             :     {
    4096       11412 :         OGRErr eStatus = OGRERR_NONE;
    4097             : 
    4098       11412 :         if (strchr(pszDefinition, '+') || strchr(pszDefinition, '@'))
    4099             :         {
    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          62 :             PJ *pj = proj_create(d->getPROJContext(), pszDefinition);
    4104          62 :             if (!pj)
    4105             :             {
    4106           1 :                 return OGRERR_FAILURE;
    4107             :             }
    4108          61 :             Clear();
    4109          61 :             d->setPjCRS(pj);
    4110          61 :             return OGRERR_NONE;
    4111             :         }
    4112             :         else
    4113             :         {
    4114             :             eStatus =
    4115       11350 :                 importFromEPSG(atoi(pszDefinition + (bStartsWithEPSG ? 5 : 6)));
    4116             :         }
    4117             : 
    4118       11350 :         return eStatus;
    4119             :     }
    4120             : 
    4121        7756 :     if (STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs:") ||
    4122        7036 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs,crs:") ||
    4123        7035 :         STARTS_WITH_CI(pszDefinition, "urn:x-ogc:def:crs:") ||
    4124        6977 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:crs:") ||
    4125        6977 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:def:crs:") ||
    4126        6977 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:coordinateMetadata:"))
    4127         779 :         return importFromURN(pszDefinition);
    4128             : 
    4129        6977 :     if (STARTS_WITH_CI(pszDefinition, "http://opengis.net/def/crs") ||
    4130        6975 :         STARTS_WITH_CI(pszDefinition, "https://opengis.net/def/crs") ||
    4131        6974 :         STARTS_WITH_CI(pszDefinition, "http://www.opengis.net/def/crs") ||
    4132        1637 :         STARTS_WITH_CI(pszDefinition, "https://www.opengis.net/def/crs") ||
    4133        1636 :         STARTS_WITH_CI(pszDefinition, "www.opengis.net/def/crs"))
    4134        5341 :         return importFromCRSURL(pszDefinition);
    4135             : 
    4136        1636 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    4137           1 :         return importFromWMSAUTO(pszDefinition);
    4138             : 
    4139             :     // WMS/WCS OGC codes like OGC:CRS84.
    4140        1635 :     if (EQUAL(pszDefinition, "OGC:CRS84"))
    4141          76 :         return SetWellKnownGeogCS(pszDefinition + 4);
    4142             : 
    4143        1559 :     if (STARTS_WITH_CI(pszDefinition, "CRS:"))
    4144           1 :         return SetWellKnownGeogCS(pszDefinition);
    4145             : 
    4146        1558 :     if (STARTS_WITH_CI(pszDefinition, "DICT:") && strstr(pszDefinition, ","))
    4147             :     {
    4148           0 :         char *pszFile = CPLStrdup(pszDefinition + 5);
    4149           0 :         char *pszCode = strstr(pszFile, ",") + 1;
    4150             : 
    4151           0 :         pszCode[-1] = '\0';
    4152             : 
    4153           0 :         OGRErr err = importFromDict(pszFile, pszCode);
    4154           0 :         CPLFree(pszFile);
    4155             : 
    4156           0 :         return err;
    4157             :     }
    4158             : 
    4159        1558 :     if (EQUAL(pszDefinition, "NAD27") || EQUAL(pszDefinition, "NAD83") ||
    4160        1553 :         EQUAL(pszDefinition, "WGS84") || EQUAL(pszDefinition, "WGS72"))
    4161             :     {
    4162         521 :         Clear();
    4163         521 :         return SetWellKnownGeogCS(pszDefinition);
    4164             :     }
    4165             : 
    4166             :     // PROJJSON
    4167        1037 :     if (pszDefinition[0] == '{' && strstr(pszDefinition, "\"type\"") &&
    4168         179 :         (strstr(pszDefinition, "GeodeticCRS") ||
    4169         179 :          strstr(pszDefinition, "GeographicCRS") ||
    4170         129 :          strstr(pszDefinition, "ProjectedCRS") ||
    4171           0 :          strstr(pszDefinition, "VerticalCRS") ||
    4172           0 :          strstr(pszDefinition, "BoundCRS") ||
    4173           0 :          strstr(pszDefinition, "CompoundCRS") ||
    4174           0 :          strstr(pszDefinition, "DerivedGeodeticCRS") ||
    4175           0 :          strstr(pszDefinition, "DerivedGeographicCRS") ||
    4176           0 :          strstr(pszDefinition, "DerivedProjectedCRS") ||
    4177           0 :          strstr(pszDefinition, "DerivedVerticalCRS") ||
    4178           0 :          strstr(pszDefinition, "EngineeringCRS") ||
    4179           0 :          strstr(pszDefinition, "DerivedEngineeringCRS") ||
    4180           0 :          strstr(pszDefinition, "ParametricCRS") ||
    4181           0 :          strstr(pszDefinition, "DerivedParametricCRS") ||
    4182           0 :          strstr(pszDefinition, "TemporalCRS") ||
    4183           0 :          strstr(pszDefinition, "DerivedTemporalCRS")))
    4184             :     {
    4185             :         PJ *pj;
    4186         179 :         if (strstr(pszDefinition, "datum_ensemble") != nullptr)
    4187             :         {
    4188             :             // PROJ < 9.0.1 doesn't like a datum_ensemble whose member have
    4189             :             // a unknown id.
    4190           8 :             CPLJSONDocument oCRSDoc;
    4191           8 :             if (!oCRSDoc.LoadMemory(pszDefinition))
    4192           0 :                 return OGRERR_CORRUPT_DATA;
    4193           8 :             CPLJSONObject oCRSRoot = oCRSDoc.GetRoot();
    4194           8 :             RemoveIDFromMemberOfEnsembles(oCRSRoot);
    4195           8 :             pj = proj_create(d->getPROJContext(), oCRSRoot.ToString().c_str());
    4196             :         }
    4197             :         else
    4198             :         {
    4199         171 :             pj = proj_create(d->getPROJContext(), pszDefinition);
    4200             :         }
    4201         179 :         if (!pj)
    4202             :         {
    4203           2 :             return OGRERR_FAILURE;
    4204             :         }
    4205         177 :         Clear();
    4206         177 :         d->setPjCRS(pj);
    4207         177 :         return OGRERR_NONE;
    4208             :     }
    4209             : 
    4210         858 :     if (strstr(pszDefinition, "+proj") != nullptr ||
    4211         406 :         strstr(pszDefinition, "+init") != nullptr)
    4212         452 :         return importFromProj4(pszDefinition);
    4213             : 
    4214         406 :     if (STARTS_WITH_CI(pszDefinition, "http://") ||
    4215         381 :         STARTS_WITH_CI(pszDefinition, "https://"))
    4216             :     {
    4217          26 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
    4218             :                                              "ALLOW_NETWORK_ACCESS", "YES")))
    4219           0 :             return importFromUrl(pszDefinition);
    4220             : 
    4221          26 :         CPLError(CE_Failure, CPLE_AppDefined,
    4222             :                  "Cannot import %s due to ALLOW_NETWORK_ACCESS=NO",
    4223             :                  pszDefinition);
    4224          26 :         return OGRERR_FAILURE;
    4225             :     }
    4226             : 
    4227         380 :     if (EQUAL(pszDefinition, "osgb:BNG"))
    4228             :     {
    4229           8 :         return importFromEPSG(27700);
    4230             :     }
    4231             : 
    4232             :     // Used by German CityGML files
    4233         372 :     if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH"))
    4234             :     {
    4235             :         // "ETRS89 / UTM Zone 32N + DHHN92 height"
    4236           0 :         return SetFromUserInput("EPSG:25832+5783");
    4237             :     }
    4238         372 :     else if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN2016_NH"))
    4239             :     {
    4240             :         // "ETRS89 / UTM Zone 32N + DHHN2016 height"
    4241           3 :         return SetFromUserInput("EPSG:25832+7837");
    4242             :     }
    4243             : 
    4244             :     // Used by  Japan's Fundamental Geospatial Data (FGD) GML
    4245         369 :     if (EQUAL(pszDefinition, "fguuid:jgd2001.bl"))
    4246           0 :         return importFromEPSG(4612);  // JGD2000 (slight difference in years)
    4247         369 :     else if (EQUAL(pszDefinition, "fguuid:jgd2011.bl"))
    4248           8 :         return importFromEPSG(6668);  // JGD2011
    4249         361 :     else if (EQUAL(pszDefinition, "fguuid:jgd2024.bl"))
    4250             :     {
    4251             :         // FIXME when EPSG attributes a CRS code
    4252           3 :         return importFromWkt(
    4253             :             "GEOGCRS[\"JGD2024\",\n"
    4254             :             "    DATUM[\"Japanese Geodetic Datum 2024\",\n"
    4255             :             "        ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
    4256             :             "            LENGTHUNIT[\"metre\",1]]],\n"
    4257             :             "    PRIMEM[\"Greenwich\",0,\n"
    4258             :             "        ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4259             :             "    CS[ellipsoidal,2],\n"
    4260             :             "        AXIS[\"geodetic latitude (Lat)\",north,\n"
    4261             :             "            ORDER[1],\n"
    4262             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4263             :             "        AXIS[\"geodetic longitude (Lon)\",east,\n"
    4264             :             "            ORDER[2],\n"
    4265             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4266             :             "    USAGE[\n"
    4267             :             "        SCOPE[\"Horizontal component of 3D system.\"],\n"
    4268             :             "        AREA[\"Japan - onshore and offshore.\"],\n"
    4269           3 :             "        BBOX[17.09,122.38,46.05,157.65]]]");
    4270             :     }
    4271             : 
    4272             :     // Deal with IGNF:xxx, ESRI:xxx, etc from the PROJ database
    4273         358 :     const char *pszDot = strrchr(pszDefinition, ':');
    4274         358 :     if (pszDot)
    4275             :     {
    4276         125 :         CPLString osPrefix(pszDefinition, pszDot - pszDefinition);
    4277             :         auto authorities =
    4278         125 :             proj_get_authorities_from_database(d->getPROJContext());
    4279         125 :         if (authorities)
    4280             :         {
    4281         125 :             std::set<std::string> aosCandidateAuthorities;
    4282         290 :             for (auto iter = authorities; *iter; ++iter)
    4283             :             {
    4284         288 :                 if (*iter == osPrefix)
    4285             :                 {
    4286         123 :                     aosCandidateAuthorities.clear();
    4287         123 :                     aosCandidateAuthorities.insert(*iter);
    4288         123 :                     break;
    4289             :                 }
    4290             :                 // Deal with "IAU_2015" as authority in the list and input
    4291             :                 // "IAU:code"
    4292         165 :                 else if (strncmp(*iter, osPrefix.c_str(), osPrefix.size()) ==
    4293         165 :                              0 &&
    4294           0 :                          (*iter)[osPrefix.size()] == '_')
    4295             :                 {
    4296           0 :                     aosCandidateAuthorities.insert(*iter);
    4297             :                 }
    4298             :                 // Deal with "IAU_2015" as authority in the list and input
    4299             :                 // "IAU:2015:code"
    4300         330 :                 else if (osPrefix.find(':') != std::string::npos &&
    4301         165 :                          osPrefix.size() == strlen(*iter) &&
    4302         165 :                          CPLString(osPrefix).replaceAll(':', '_') == *iter)
    4303             :                 {
    4304           0 :                     aosCandidateAuthorities.clear();
    4305           0 :                     aosCandidateAuthorities.insert(*iter);
    4306           0 :                     break;
    4307             :                 }
    4308             :             }
    4309             : 
    4310         125 :             proj_string_list_destroy(authorities);
    4311             : 
    4312         125 :             if (!aosCandidateAuthorities.empty())
    4313             :             {
    4314         123 :                 auto obj = proj_create_from_database(
    4315             :                     d->getPROJContext(),
    4316         123 :                     aosCandidateAuthorities.rbegin()->c_str(), pszDot + 1,
    4317             :                     PJ_CATEGORY_CRS, false, nullptr);
    4318         123 :                 if (!obj)
    4319             :                 {
    4320          16 :                     return OGRERR_FAILURE;
    4321             :                 }
    4322         107 :                 Clear();
    4323         107 :                 d->setPjCRS(obj);
    4324         107 :                 return OGRERR_NONE;
    4325             :             }
    4326             :         }
    4327             :     }
    4328             : 
    4329             :     /* -------------------------------------------------------------------- */
    4330             :     /*      Try to open it as a file.                                       */
    4331             :     /* -------------------------------------------------------------------- */
    4332         235 :     if (!CPLTestBool(
    4333             :             CSLFetchNameValueDef(papszOptions, "ALLOW_FILE_ACCESS", "YES")))
    4334             :     {
    4335             :         VSIStatBufL sStat;
    4336          40 :         if (STARTS_WITH(pszDefinition, "/vsi") ||
    4337          20 :             VSIStatExL(pszDefinition, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
    4338             :         {
    4339           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4340             :                      "Cannot import %s due to ALLOW_FILE_ACCESS=NO",
    4341             :                      pszDefinition);
    4342           0 :             return OGRERR_FAILURE;
    4343             :         }
    4344             :         // We used to silently return an error without a CE_Failure message
    4345             :         // Cf https://github.com/Toblerity/Fiona/issues/1063
    4346          20 :         return OGRERR_CORRUPT_DATA;
    4347             :     }
    4348             : 
    4349         430 :     CPLConfigOptionSetter oSetter("CPL_ALLOW_VSISTDIN", "NO", true);
    4350         215 :     VSILFILE *const fp = VSIFOpenL(pszDefinition, "rt");
    4351         215 :     if (fp == nullptr)
    4352         212 :         return OGRERR_CORRUPT_DATA;
    4353             : 
    4354           3 :     const size_t nBufMax = 100000;
    4355           3 :     char *const pszBuffer = static_cast<char *>(CPLMalloc(nBufMax));
    4356           3 :     const size_t nBytes = VSIFReadL(pszBuffer, 1, nBufMax - 1, fp);
    4357           3 :     VSIFCloseL(fp);
    4358             : 
    4359           3 :     if (nBytes == nBufMax - 1)
    4360             :     {
    4361           0 :         CPLDebug("OGR",
    4362             :                  "OGRSpatialReference::SetFromUserInput(%s), opened file "
    4363             :                  "but it is to large for our generous buffer.  Is it really "
    4364             :                  "just a WKT definition?",
    4365             :                  pszDefinition);
    4366           0 :         CPLFree(pszBuffer);
    4367           0 :         return OGRERR_FAILURE;
    4368             :     }
    4369             : 
    4370           3 :     pszBuffer[nBytes] = '\0';
    4371             : 
    4372           3 :     char *pszBufPtr = pszBuffer;
    4373           3 :     while (pszBufPtr[0] == ' ' || pszBufPtr[0] == '\n')
    4374           0 :         pszBufPtr++;
    4375             : 
    4376           3 :     OGRErr err = OGRERR_NONE;
    4377           3 :     if (pszBufPtr[0] == '<')
    4378           0 :         err = importFromXML(pszBufPtr);
    4379           3 :     else if ((strstr(pszBuffer, "+proj") != nullptr ||
    4380           3 :               strstr(pszBuffer, "+init") != nullptr) &&
    4381           0 :              (strstr(pszBuffer, "EXTENSION") == nullptr &&
    4382           0 :               strstr(pszBuffer, "extension") == nullptr))
    4383           0 :         err = importFromProj4(pszBufPtr);
    4384             :     else
    4385             :     {
    4386           3 :         if (STARTS_WITH_CI(pszBufPtr, "ESRI::"))
    4387             :         {
    4388           0 :             pszBufPtr += 6;
    4389             :         }
    4390             : 
    4391             :         // coverity[tainted_data]
    4392           3 :         err = importFromWkt(pszBufPtr);
    4393             :     }
    4394             : 
    4395           3 :     CPLFree(pszBuffer);
    4396             : 
    4397           3 :     return err;
    4398             : }
    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         359 : OGRErr CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS,
    4412             :                                        const char *pszDef)
    4413             : 
    4414             : {
    4415         359 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInput", OGRERR_FAILURE);
    4416             : 
    4417         359 :     return ToPointer(hSRS)->SetFromUserInput(pszDef);
    4418             : }
    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        1163 : OGRErr OSRSetFromUserInputEx(OGRSpatialReferenceH hSRS, const char *pszDef,
    4432             :                              CSLConstList papszOptions)
    4433             : 
    4434             : {
    4435        1163 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInputEx", OGRERR_FAILURE);
    4436             : 
    4437        1163 :     return ToPointer(hSRS)->SetFromUserInput(pszDef, papszOptions);
    4438             : }
    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           5 : OGRErr OGRSpatialReference::importFromUrl(const char *pszUrl)
    4459             : 
    4460             : {
    4461          10 :     TAKE_OPTIONAL_LOCK();
    4462             : 
    4463           5 :     if (!STARTS_WITH_CI(pszUrl, "http://") &&
    4464           3 :         !STARTS_WITH_CI(pszUrl, "https://"))
    4465             :     {
    4466           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    4467             :                  "The given string is not recognized as a URL"
    4468             :                  "starting with 'http://' -- %s",
    4469             :                  pszUrl);
    4470           2 :         return OGRERR_FAILURE;
    4471             :     }
    4472             : 
    4473             :     /* -------------------------------------------------------------------- */
    4474             :     /*      Fetch the result.                                               */
    4475             :     /* -------------------------------------------------------------------- */
    4476           3 :     CPLErrorReset();
    4477             : 
    4478           6 :     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           2 :     for (const char *pszPrefix : {"https://spatialreference.org/ref/",
    4484           5 :                                   "http://spatialreference.org/ref/"})
    4485             :     {
    4486           5 :         if (STARTS_WITH(pszUrl, pszPrefix))
    4487             :         {
    4488             :             const CPLStringList aosTokens(
    4489           6 :                 CSLTokenizeString2(pszUrl + strlen(pszPrefix), "/", 0));
    4490           3 :             if (aosTokens.size() == 2)
    4491             :             {
    4492           2 :                 osUrl = "https://spatialreference.org/ref/";
    4493           2 :                 osUrl += aosTokens[0];  // authority
    4494           2 :                 osUrl += '/';
    4495           2 :                 osUrl += aosTokens[1];  // code
    4496           2 :                 osUrl += "/ogcwkt/";
    4497             :             }
    4498           3 :             break;
    4499             :         }
    4500             :     }
    4501             : 
    4502           3 :     const char *pszTimeout = "TIMEOUT=10";
    4503           3 :     char *apszOptions[] = {const_cast<char *>(pszTimeout), nullptr};
    4504             : 
    4505           3 :     CPLHTTPResult *psResult = CPLHTTPFetch(osUrl.c_str(), apszOptions);
    4506             : 
    4507             :     /* -------------------------------------------------------------------- */
    4508             :     /*      Try to handle errors.                                           */
    4509             :     /* -------------------------------------------------------------------- */
    4510             : 
    4511           3 :     if (psResult == nullptr)
    4512           0 :         return OGRERR_FAILURE;
    4513           6 :     if (psResult->nDataLen == 0 || CPLGetLastErrorNo() != 0 ||
    4514           3 :         psResult->pabyData == nullptr)
    4515             :     {
    4516           0 :         if (CPLGetLastErrorNo() == 0)
    4517             :         {
    4518           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4519             :                      "No data was returned from the given URL");
    4520             :         }
    4521           0 :         CPLHTTPDestroyResult(psResult);
    4522           0 :         return OGRERR_FAILURE;
    4523             :     }
    4524             : 
    4525           3 :     if (psResult->nStatus != 0)
    4526             :     {
    4527           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
    4528             :                  psResult->nStatus, psResult->pszErrBuf);
    4529           0 :         CPLHTTPDestroyResult(psResult);
    4530           0 :         return OGRERR_FAILURE;
    4531             :     }
    4532             : 
    4533           3 :     const char *pszData = reinterpret_cast<const char *>(psResult->pabyData);
    4534           3 :     if (STARTS_WITH_CI(pszData, "http://") ||
    4535           3 :         STARTS_WITH_CI(pszData, "https://"))
    4536             :     {
    4537           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4538             :                  "The data that was downloaded also starts with 'http://' "
    4539             :                  "and cannot be passed into SetFromUserInput.  Is this "
    4540             :                  "really a spatial reference definition? ");
    4541           0 :         CPLHTTPDestroyResult(psResult);
    4542           0 :         return OGRERR_FAILURE;
    4543             :     }
    4544           3 :     if (OGRERR_NONE != SetFromUserInput(pszData))
    4545             :     {
    4546           0 :         CPLHTTPDestroyResult(psResult);
    4547           0 :         return OGRERR_FAILURE;
    4548             :     }
    4549             : 
    4550           3 :     CPLHTTPDestroyResult(psResult);
    4551           3 :     return OGRERR_NONE;
    4552             : }
    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           3 : OGRErr OSRImportFromUrl(OGRSpatialReferenceH hSRS, const char *pszUrl)
    4564             : 
    4565             : {
    4566           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromUrl", OGRERR_FAILURE);
    4567             : 
    4568           3 :     return ToPointer(hSRS)->importFromUrl(pszUrl);
    4569             : }
    4570             : 
    4571             : /************************************************************************/
    4572             : /*                         importFromURNPart()                          */
    4573             : /************************************************************************/
    4574        5552 : OGRErr OGRSpatialReference::importFromURNPart(const char *pszAuthority,
    4575             :                                               const char *pszCode,
    4576             :                                               const char *pszURN)
    4577             : {
    4578             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4579             :     (void)this;
    4580             :     (void)pszAuthority;
    4581             :     (void)pszCode;
    4582             :     (void)pszURN;
    4583             :     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        5552 :     if (STARTS_WITH_CI(pszAuthority, "EPSG"))
    4590        4990 :         return importFromEPSGA(atoi(pszCode));
    4591             : 
    4592             :     /* -------------------------------------------------------------------- */
    4593             :     /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
    4594             :     /* -------------------------------------------------------------------- */
    4595         562 :     if (STARTS_WITH_CI(pszAuthority, "IAU"))
    4596           0 :         return importFromDict("IAU2000.wkt", pszCode);
    4597             : 
    4598             :     /* -------------------------------------------------------------------- */
    4599             :     /*      Is this an OGC code?                                            */
    4600             :     /* -------------------------------------------------------------------- */
    4601         562 :     if (!STARTS_WITH_CI(pszAuthority, "OGC"))
    4602             :     {
    4603           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4604             :                  "URN %s has unrecognized authority.", pszURN);
    4605           1 :         return OGRERR_FAILURE;
    4606             :     }
    4607             : 
    4608         561 :     if (STARTS_WITH_CI(pszCode, "CRS84"))
    4609         549 :         return SetWellKnownGeogCS(pszCode);
    4610          12 :     else if (STARTS_WITH_CI(pszCode, "CRS83"))
    4611           0 :         return SetWellKnownGeogCS(pszCode);
    4612          12 :     else if (STARTS_WITH_CI(pszCode, "CRS27"))
    4613           0 :         return SetWellKnownGeogCS(pszCode);
    4614          12 :     else if (STARTS_WITH_CI(pszCode, "84"))  // urn:ogc:def:crs:OGC:2:84
    4615          10 :         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           2 :     else if (STARTS_WITH_CI(pszCode, "AUTO"))
    4622             :     {
    4623           2 :         char szWMSAuto[100] = {'\0'};
    4624             : 
    4625           2 :         if (strlen(pszCode) > sizeof(szWMSAuto) - 2)
    4626           0 :             return OGRERR_FAILURE;
    4627             : 
    4628           2 :         snprintf(szWMSAuto, sizeof(szWMSAuto), "AUTO:%s", pszCode + 4);
    4629          28 :         for (int i = 5; szWMSAuto[i] != '\0'; i++)
    4630             :         {
    4631          26 :             if (szWMSAuto[i] == ':')
    4632           4 :                 szWMSAuto[i] = ',';
    4633             :         }
    4634             : 
    4635           2 :         return importFromWMSAUTO(szWMSAuto);
    4636             :     }
    4637             : 
    4638             :     /* -------------------------------------------------------------------- */
    4639             :     /*      Not a recognise OGC item.                                       */
    4640             :     /* -------------------------------------------------------------------- */
    4641           0 :     CPLError(CE_Failure, CPLE_AppDefined, "URN %s value not supported.",
    4642             :              pszURN);
    4643             : 
    4644           0 :     return OGRERR_FAILURE;
    4645             : #endif
    4646             : }
    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         840 : OGRErr OGRSpatialReference::importFromURN(const char *pszURN)
    4671             : 
    4672             : {
    4673         840 :     constexpr const char *EPSG_URN_CRS_PREFIX = "urn:ogc:def:crs:EPSG::";
    4674        1595 :     if (STARTS_WITH(pszURN, EPSG_URN_CRS_PREFIX) &&
    4675         755 :         CPLGetValueType(pszURN + strlen(EPSG_URN_CRS_PREFIX)) ==
    4676             :             CPL_VALUE_INTEGER)
    4677             :     {
    4678         752 :         return importFromEPSG(atoi(pszURN + strlen(EPSG_URN_CRS_PREFIX)));
    4679             :     }
    4680             : 
    4681         176 :     TAKE_OPTIONAL_LOCK();
    4682             : 
    4683             : #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             :     if (strlen(pszURN) >= 1000)
    4705             :     {
    4706             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4707             :         return OGRERR_CORRUPT_DATA;
    4708             :     }
    4709             :     auto obj = proj_create(d->getPROJContext(), pszURN);
    4710             :     if (!obj)
    4711             :     {
    4712             :         return OGRERR_FAILURE;
    4713             :     }
    4714             :     Clear();
    4715             :     d->setPjCRS(obj);
    4716             :     return OGRERR_NONE;
    4717             : #else
    4718          88 :     const char *pszCur = nullptr;
    4719             : 
    4720          88 :     if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs:"))
    4721          23 :         pszCur = pszURN + 16;
    4722          65 :     else if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs,crs:"))
    4723           1 :         pszCur = pszURN + 20;
    4724          64 :     else if (STARTS_WITH_CI(pszURN, "urn:x-ogc:def:crs:"))
    4725          62 :         pszCur = pszURN + 18;
    4726           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:crs:"))
    4727           0 :         pszCur = pszURN + 16;
    4728           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:def:crs:"))
    4729           0 :         pszCur = pszURN + 20;
    4730             :     else
    4731             :     {
    4732           2 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4733             :                  pszURN);
    4734           2 :         return OGRERR_FAILURE;
    4735             :     }
    4736             : 
    4737             :     /* -------------------------------------------------------------------- */
    4738             :     /*      Clear any existing definition.                                  */
    4739             :     /* -------------------------------------------------------------------- */
    4740          86 :     Clear();
    4741             : 
    4742             :     /* -------------------------------------------------------------------- */
    4743             :     /*      Find code (ignoring version) out of string like:                */
    4744             :     /*                                                                      */
    4745             :     /*      authority:[version]:code                                        */
    4746             :     /* -------------------------------------------------------------------- */
    4747          86 :     const char *pszAuthority = pszCur;
    4748             : 
    4749             :     // skip authority
    4750         414 :     while (*pszCur != ':' && *pszCur)
    4751         328 :         pszCur++;
    4752          86 :     if (*pszCur == ':')
    4753          86 :         pszCur++;
    4754             : 
    4755             :     // skip version
    4756          86 :     const char *pszBeforeVersion = pszCur;
    4757         387 :     while (*pszCur != ':' && *pszCur)
    4758         301 :         pszCur++;
    4759          86 :     if (*pszCur == ':')
    4760          58 :         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          28 :         pszCur = pszBeforeVersion;
    4767             : 
    4768          86 :     const char *pszCode = pszCur;
    4769             : 
    4770          86 :     const char *pszComma = strchr(pszCur, ',');
    4771          86 :     if (pszComma == nullptr)
    4772          85 :         return importFromURNPart(pszAuthority, pszCode, pszURN);
    4773             : 
    4774             :     // There's a second part with the vertical SRS.
    4775           1 :     pszCur = pszComma + 1;
    4776           1 :     if (!STARTS_WITH(pszCur, "crs:"))
    4777             :     {
    4778           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4779             :                  pszURN);
    4780           0 :         return OGRERR_FAILURE;
    4781             :     }
    4782             : 
    4783           1 :     pszCur += 4;
    4784             : 
    4785           1 :     char *pszFirstCode = CPLStrdup(pszCode);
    4786           1 :     pszFirstCode[pszComma - pszCode] = '\0';
    4787           1 :     OGRErr eStatus = importFromURNPart(pszAuthority, pszFirstCode, pszURN);
    4788           1 :     CPLFree(pszFirstCode);
    4789             : 
    4790             :     // Do we want to turn this into a compound definition
    4791             :     // with a vertical datum?
    4792           1 :     if (eStatus != OGRERR_NONE)
    4793           0 :         return eStatus;
    4794             : 
    4795             :     /* -------------------------------------------------------------------- */
    4796             :     /*      Find code (ignoring version) out of string like:                */
    4797             :     /*                                                                      */
    4798             :     /*      authority:[version]:code                                        */
    4799             :     /* -------------------------------------------------------------------- */
    4800           1 :     pszAuthority = pszCur;
    4801             : 
    4802             :     // skip authority
    4803           5 :     while (*pszCur != ':' && *pszCur)
    4804           4 :         pszCur++;
    4805           1 :     if (*pszCur == ':')
    4806           1 :         pszCur++;
    4807             : 
    4808             :     // skip version
    4809           1 :     pszBeforeVersion = pszCur;
    4810           1 :     while (*pszCur != ':' && *pszCur)
    4811           0 :         pszCur++;
    4812           1 :     if (*pszCur == ':')
    4813           1 :         pszCur++;
    4814             :     else
    4815           0 :         pszCur = pszBeforeVersion;
    4816             : 
    4817           1 :     pszCode = pszCur;
    4818             : 
    4819           2 :     OGRSpatialReference oVertSRS;
    4820           1 :     eStatus = oVertSRS.importFromURNPart(pszAuthority, pszCode, pszURN);
    4821           1 :     if (eStatus == OGRERR_NONE)
    4822             :     {
    4823           1 :         OGRSpatialReference oHorizSRS(*this);
    4824             : 
    4825           1 :         Clear();
    4826             : 
    4827           1 :         oHorizSRS.d->refreshProjObj();
    4828           1 :         oVertSRS.d->refreshProjObj();
    4829           1 :         if (!oHorizSRS.d->m_pj_crs || !oVertSRS.d->m_pj_crs)
    4830           0 :             return OGRERR_FAILURE;
    4831             : 
    4832           1 :         const char *pszHorizName = proj_get_name(oHorizSRS.d->m_pj_crs);
    4833           1 :         const char *pszVertName = proj_get_name(oVertSRS.d->m_pj_crs);
    4834             : 
    4835           2 :         CPLString osName = pszHorizName ? pszHorizName : "";
    4836           1 :         osName += " + ";
    4837           1 :         osName += pszVertName ? pszVertName : "";
    4838             : 
    4839           1 :         SetCompoundCS(osName, &oHorizSRS, &oVertSRS);
    4840             :     }
    4841             : 
    4842           1 :     return eStatus;
    4843             : #endif
    4844             : }
    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        5478 : OGRErr OGRSpatialReference::importFromCRSURL(const char *pszURL)
    4869             : 
    4870             : {
    4871       10956 :     TAKE_OPTIONAL_LOCK();
    4872             : 
    4873             : #if !PROJ_AT_LEAST_VERSION(9, 1, 0)
    4874        5478 :     if (strcmp(pszURL, "http://www.opengis.net/def/crs/OGC/0/CRS84h") == 0)
    4875             :     {
    4876          12 :         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          12 :         if (!obj)
    4910             :         {
    4911           0 :             return OGRERR_FAILURE;
    4912             :         }
    4913          12 :         Clear();
    4914          12 :         d->setPjCRS(obj);
    4915          12 :         return OGRERR_NONE;
    4916             :     }
    4917             : #endif
    4918             : 
    4919             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4920             :     if (strlen(pszURL) >= 10000)
    4921             :     {
    4922             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4923             :         return OGRERR_CORRUPT_DATA;
    4924             :     }
    4925             : 
    4926             :     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             :     {
    4939             :         obj = proj_create(d->getPROJContext(), pszURL);
    4940             :     }
    4941             :     if (!obj)
    4942             :     {
    4943             :         return OGRERR_FAILURE;
    4944             :     }
    4945             :     Clear();
    4946             :     d->setPjCRS(obj);
    4947             :     return OGRERR_NONE;
    4948             : #else
    4949        5466 :     const char *pszCur = nullptr;
    4950             : 
    4951        5466 :     if (STARTS_WITH_CI(pszURL, "http://opengis.net/def/crs"))
    4952           2 :         pszCur = pszURL + 26;
    4953        5464 :     else if (STARTS_WITH_CI(pszURL, "https://opengis.net/def/crs"))
    4954           1 :         pszCur = pszURL + 27;
    4955        5463 :     else if (STARTS_WITH_CI(pszURL, "http://www.opengis.net/def/crs"))
    4956        5462 :         pszCur = pszURL + 30;
    4957           1 :     else if (STARTS_WITH_CI(pszURL, "https://www.opengis.net/def/crs"))
    4958           1 :         pszCur = pszURL + 31;
    4959           0 :     else if (STARTS_WITH_CI(pszURL, "www.opengis.net/def/crs"))
    4960           0 :         pszCur = pszURL + 23;
    4961             :     else
    4962             :     {
    4963           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s not a supported format.",
    4964             :                  pszURL);
    4965           0 :         return OGRERR_FAILURE;
    4966             :     }
    4967             : 
    4968        5466 :     if (*pszCur == '\0')
    4969             :     {
    4970           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s malformed.", pszURL);
    4971           0 :         return OGRERR_FAILURE;
    4972             :     }
    4973             : 
    4974             :     /* -------------------------------------------------------------------- */
    4975             :     /*      Clear any existing definition.                                  */
    4976             :     /* -------------------------------------------------------------------- */
    4977        5466 :     Clear();
    4978             : 
    4979        5466 :     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           1 :         pszCur += 12;
    4989             : 
    4990             :         // Extract each component CRS URL.
    4991           1 :         int iComponentUrl = 2;
    4992             : 
    4993           2 :         CPLString osName = "";
    4994           1 :         Clear();
    4995             : 
    4996           3 :         while (iComponentUrl != -1)
    4997             :         {
    4998           2 :             char searchStr[15] = {};
    4999           2 :             snprintf(searchStr, sizeof(searchStr), "&%d=", iComponentUrl);
    5000             : 
    5001           2 :             const char *pszUrlEnd = strstr(pszCur, searchStr);
    5002             : 
    5003             :             // Figure out the next component URL.
    5004           2 :             char *pszComponentUrl = nullptr;
    5005             : 
    5006           2 :             if (pszUrlEnd)
    5007             :             {
    5008           1 :                 size_t nLen = pszUrlEnd - pszCur;
    5009           1 :                 pszComponentUrl = static_cast<char *>(CPLMalloc(nLen + 1));
    5010           1 :                 strncpy(pszComponentUrl, pszCur, nLen);
    5011           1 :                 pszComponentUrl[nLen] = '\0';
    5012             : 
    5013           1 :                 ++iComponentUrl;
    5014           1 :                 pszCur += nLen + strlen(searchStr);
    5015             :             }
    5016             :             else
    5017             :             {
    5018           1 :                 if (iComponentUrl == 2)
    5019             :                 {
    5020           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5021             :                              "Compound CRS URLs must have at least two "
    5022             :                              "component CRSs.");
    5023           0 :                     return OGRERR_FAILURE;
    5024             :                 }
    5025             :                 else
    5026             :                 {
    5027           1 :                     pszComponentUrl = CPLStrdup(pszCur);
    5028             :                     // no more components
    5029           1 :                     iComponentUrl = -1;
    5030             :                 }
    5031             :             }
    5032             : 
    5033           2 :             OGRSpatialReference oComponentSRS;
    5034           2 :             OGRErr eStatus = oComponentSRS.importFromCRSURL(pszComponentUrl);
    5035             : 
    5036           2 :             CPLFree(pszComponentUrl);
    5037           2 :             pszComponentUrl = nullptr;
    5038             : 
    5039           2 :             if (eStatus == OGRERR_NONE)
    5040             :             {
    5041           2 :                 if (osName.length() != 0)
    5042             :                 {
    5043           1 :                     osName += " + ";
    5044             :                 }
    5045           2 :                 osName += oComponentSRS.GetRoot()->GetValue();
    5046           2 :                 SetNode("COMPD_CS", osName);
    5047           2 :                 GetRoot()->AddChild(oComponentSRS.GetRoot()->Clone());
    5048             :             }
    5049             :             else
    5050           0 :                 return eStatus;
    5051             :         }
    5052             : 
    5053           1 :         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        5465 :     ++pszCur;
    5062        5465 :     const char *pszAuthority = pszCur;
    5063             : 
    5064             :     // skip authority
    5065      126776 :     while (*pszCur != '/' && *pszCur)
    5066      121311 :         pszCur++;
    5067        5465 :     if (*pszCur == '/')
    5068        5464 :         pszCur++;
    5069             : 
    5070             :     // skip version
    5071       11991 :     while (*pszCur != '/' && *pszCur)
    5072        6526 :         pszCur++;
    5073        5465 :     if (*pszCur == '/')
    5074        5464 :         pszCur++;
    5075             : 
    5076        5465 :     const char *pszCode = pszCur;
    5077             : 
    5078        5465 :     return importFromURNPart(pszAuthority, pszCode, pszURL);
    5079             : #endif
    5080             : }
    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           3 : OGRErr OGRSpatialReference::importFromWMSAUTO(const char *pszDefinition)
    5098             : 
    5099             : {
    5100           6 :     TAKE_OPTIONAL_LOCK();
    5101             : 
    5102             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    5103             :     if (strlen(pszDefinition) >= 10000)
    5104             :     {
    5105             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    5106             :         return OGRERR_CORRUPT_DATA;
    5107             :     }
    5108             : 
    5109             :     auto obj = proj_create(d->getPROJContext(), pszDefinition);
    5110             :     if (!obj)
    5111             :     {
    5112             :         return OGRERR_FAILURE;
    5113             :     }
    5114             :     Clear();
    5115             :     d->setPjCRS(obj);
    5116             :     return OGRERR_NONE;
    5117             : #else
    5118             :     int nProjId, nUnitsId;
    5119           3 :     double dfRefLong, dfRefLat = 0.0;
    5120             : 
    5121             :     /* -------------------------------------------------------------------- */
    5122             :     /*      Tokenize                                                        */
    5123             :     /* -------------------------------------------------------------------- */
    5124           3 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    5125           3 :         pszDefinition += 5;
    5126             : 
    5127             :     char **papszTokens =
    5128           3 :         CSLTokenizeStringComplex(pszDefinition, ",", FALSE, TRUE);
    5129             : 
    5130           3 :     if (CSLCount(papszTokens) == 4)
    5131             :     {
    5132           0 :         nProjId = atoi(papszTokens[0]);
    5133           0 :         nUnitsId = atoi(papszTokens[1]);
    5134           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    5135           0 :         dfRefLat = CPLAtof(papszTokens[3]);
    5136             :     }
    5137           3 :     else if (CSLCount(papszTokens) == 3 && atoi(papszTokens[0]) == 42005)
    5138             :     {
    5139           0 :         nProjId = atoi(papszTokens[0]);
    5140           0 :         nUnitsId = atoi(papszTokens[1]);
    5141           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    5142           0 :         dfRefLat = 0.0;
    5143             :     }
    5144           3 :     else if (CSLCount(papszTokens) == 3)
    5145             :     {
    5146           2 :         nProjId = atoi(papszTokens[0]);
    5147           2 :         nUnitsId = 9001;
    5148           2 :         dfRefLong = CPLAtof(papszTokens[1]);
    5149           2 :         dfRefLat = CPLAtof(papszTokens[2]);
    5150             :     }
    5151           1 :     else if (CSLCount(papszTokens) == 2 && atoi(papszTokens[0]) == 42005)
    5152             :     {
    5153           0 :         nProjId = atoi(papszTokens[0]);
    5154           0 :         nUnitsId = 9001;
    5155           0 :         dfRefLong = CPLAtof(papszTokens[1]);
    5156             :     }
    5157             :     else
    5158             :     {
    5159           1 :         CSLDestroy(papszTokens);
    5160           1 :         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           1 :         return OGRERR_FAILURE;
    5165             :     }
    5166             : 
    5167           2 :     CSLDestroy(papszTokens);
    5168           2 :     papszTokens = nullptr;
    5169             : 
    5170             :     /* -------------------------------------------------------------------- */
    5171             :     /*      Build coordsys.                                                 */
    5172             :     /* -------------------------------------------------------------------- */
    5173           2 :     Clear();
    5174             : 
    5175             :     /* -------------------------------------------------------------------- */
    5176             :     /*      Set WGS84.                                                      */
    5177             :     /* -------------------------------------------------------------------- */
    5178           2 :     SetWellKnownGeogCS("WGS84");
    5179             : 
    5180           2 :     switch (nProjId)
    5181             :     {
    5182           2 :         case 42001:  // Auto UTM
    5183           2 :             SetUTM(static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
    5184             :                    dfRefLat >= 0.0);
    5185           2 :             break;
    5186             : 
    5187           0 :         case 42002:  // Auto TM (strangely very UTM-like).
    5188           0 :             SetTM(0, dfRefLong, 0.9996, 500000.0,
    5189             :                   (dfRefLat >= 0.0) ? 0.0 : 10000000.0);
    5190           0 :             break;
    5191             : 
    5192           0 :         case 42003:  // Auto Orthographic.
    5193           0 :             SetOrthographic(dfRefLat, dfRefLong, 0.0, 0.0);
    5194           0 :             break;
    5195             : 
    5196           0 :         case 42004:  // Auto Equirectangular
    5197           0 :             SetEquirectangular(dfRefLat, dfRefLong, 0.0, 0.0);
    5198           0 :             break;
    5199             : 
    5200           0 :         case 42005:
    5201           0 :             SetMollweide(dfRefLong, 0.0, 0.0);
    5202           0 :             break;
    5203             : 
    5204           0 :         default:
    5205           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5206             :                      "Unsupported projection id in importFromWMSAUTO(): %d",
    5207             :                      nProjId);
    5208           0 :             return OGRERR_FAILURE;
    5209             :     }
    5210             : 
    5211             :     /* -------------------------------------------------------------------- */
    5212             :     /*      Set units.                                                      */
    5213             :     /* -------------------------------------------------------------------- */
    5214             : 
    5215           2 :     switch (nUnitsId)
    5216             :     {
    5217           2 :         case 9001:
    5218           2 :             SetTargetLinearUnits(nullptr, SRS_UL_METER, 1.0, "EPSG", "9001");
    5219           2 :             break;
    5220             : 
    5221           0 :         case 9002:
    5222           0 :             SetTargetLinearUnits(nullptr, "Foot", 0.3048, "EPSG", "9002");
    5223           0 :             break;
    5224             : 
    5225           0 :         case 9003:
    5226           0 :             SetTargetLinearUnits(nullptr, "US survey foot",
    5227             :                                  CPLAtof(SRS_UL_US_FOOT_CONV), "EPSG", "9003");
    5228           0 :             break;
    5229             : 
    5230           0 :         default:
    5231           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5232             :                      "Unsupported units code (%d).", nUnitsId);
    5233           0 :             return OGRERR_FAILURE;
    5234             :             break;
    5235             :     }
    5236             : 
    5237           2 :     return OGRERR_NONE;
    5238             : #endif
    5239             : }
    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        6817 : double OGRSpatialReference::GetSemiMajor(OGRErr *pnErr) const
    5257             : 
    5258             : {
    5259       13634 :     TAKE_OPTIONAL_LOCK();
    5260             : 
    5261        6817 :     if (pnErr != nullptr)
    5262        3533 :         *pnErr = OGRERR_FAILURE;
    5263             : 
    5264        6817 :     d->refreshProjObj();
    5265        6817 :     if (!d->m_pj_crs)
    5266         111 :         return SRS_WGS84_SEMIMAJOR;
    5267             : 
    5268        6706 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5269        6706 :     if (!ellps)
    5270           5 :         return SRS_WGS84_SEMIMAJOR;
    5271             : 
    5272        6701 :     double dfSemiMajor = 0.0;
    5273        6701 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, &dfSemiMajor,
    5274             :                                   nullptr, nullptr, nullptr);
    5275        6701 :     proj_destroy(ellps);
    5276             : 
    5277        6701 :     if (dfSemiMajor > 0)
    5278             :     {
    5279        6701 :         if (pnErr != nullptr)
    5280        3419 :             *pnErr = OGRERR_NONE;
    5281        6701 :         return dfSemiMajor;
    5282             :     }
    5283             : 
    5284           0 :     return SRS_WGS84_SEMIMAJOR;
    5285             : }
    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          87 : double OSRGetSemiMajor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5297             : 
    5298             : {
    5299          87 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMajor", 0);
    5300             : 
    5301          87 :     return ToPointer(hSRS)->GetSemiMajor(pnErr);
    5302             : }
    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        4505 : double OGRSpatialReference::GetInvFlattening(OGRErr *pnErr) const
    5320             : 
    5321             : {
    5322        9010 :     TAKE_OPTIONAL_LOCK();
    5323             : 
    5324        4505 :     if (pnErr != nullptr)
    5325        3425 :         *pnErr = OGRERR_FAILURE;
    5326             : 
    5327        4505 :     d->refreshProjObj();
    5328        4505 :     if (!d->m_pj_crs)
    5329         111 :         return SRS_WGS84_INVFLATTENING;
    5330             : 
    5331        4394 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5332        4394 :     if (!ellps)
    5333           2 :         return SRS_WGS84_INVFLATTENING;
    5334             : 
    5335        4392 :     double dfInvFlattening = -1.0;
    5336        4392 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, nullptr, nullptr,
    5337             :                                   nullptr, &dfInvFlattening);
    5338        4392 :     proj_destroy(ellps);
    5339             : 
    5340        4392 :     if (dfInvFlattening >= 0.0)
    5341             :     {
    5342        4392 :         if (pnErr != nullptr)
    5343        3314 :             *pnErr = OGRERR_NONE;
    5344        4392 :         return dfInvFlattening;
    5345             :     }
    5346             : 
    5347           0 :     return SRS_WGS84_INVFLATTENING;
    5348             : }
    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          10 : double OSRGetInvFlattening(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5360             : 
    5361             : {
    5362          10 :     VALIDATE_POINTER1(hSRS, "OSRGetInvFlattening", 0);
    5363             : 
    5364          10 :     return ToPointer(hSRS)->GetInvFlattening(pnErr);
    5365             : }
    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           0 : double OGRSpatialReference::GetEccentricity() const
    5378             : 
    5379             : {
    5380           0 :     OGRErr eErr = OGRERR_NONE;
    5381           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5382           0 :     if (eErr != OGRERR_NONE)
    5383             :     {
    5384           0 :         return -1.0;
    5385             :     }
    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             : }
    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           0 : double OGRSpatialReference::GetSquaredEccentricity() const
    5405             : 
    5406             : {
    5407           0 :     OGRErr eErr = OGRERR_NONE;
    5408           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5409           0 :     if (eErr != OGRERR_NONE)
    5410             :     {
    5411           0 :         return -1.0;
    5412             :     }
    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             : }
    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         651 : double OGRSpatialReference::GetSemiMinor(OGRErr *pnErr) const
    5436             : 
    5437             : {
    5438         651 :     const double dfSemiMajor = GetSemiMajor(pnErr);
    5439         651 :     const double dfInvFlattening = GetInvFlattening(pnErr);
    5440             : 
    5441         651 :     return OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
    5442             : }
    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           4 : double OSRGetSemiMinor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5454             : 
    5455             : {
    5456           4 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMinor", 0);
    5457             : 
    5458           4 :     return ToPointer(hSRS)->GetSemiMinor(pnErr);
    5459             : }
    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        2898 : OGRErr OGRSpatialReference::SetLocalCS(const char *pszName)
    5479             : 
    5480             : {
    5481        5796 :     TAKE_OPTIONAL_LOCK();
    5482             : 
    5483        2898 :     if (d->m_pjType == PJ_TYPE_UNKNOWN ||
    5484           0 :         d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    5485             :     {
    5486        2898 :         d->setPjCRS(proj_create_engineering_crs(d->getPROJContext(), pszName));
    5487             :     }
    5488             :     else
    5489             :     {
    5490           0 :         CPLDebug("OGR",
    5491             :                  "OGRSpatialReference::SetLocalCS(%s) failed.  "
    5492             :                  "It appears an incompatible object already exists.",
    5493             :                  pszName);
    5494           0 :         return OGRERR_FAILURE;
    5495             :     }
    5496             : 
    5497        2898 :     return OGRERR_NONE;
    5498             : }
    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           1 : OGRErr OSRSetLocalCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5510             : 
    5511             : {
    5512           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLocalCS", OGRERR_FAILURE);
    5513             : 
    5514           1 :     return ToPointer(hSRS)->SetLocalCS(pszName);
    5515             : }
    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           6 : OGRErr OGRSpatialReference::SetGeocCS(const char *pszName)
    5538             : 
    5539             : {
    5540          12 :     TAKE_OPTIONAL_LOCK();
    5541             : 
    5542           6 :     OGRErr eErr = OGRERR_NONE;
    5543           6 :     d->refreshProjObj();
    5544           6 :     d->demoteFromBoundCRS();
    5545           6 :     if (d->m_pjType == PJ_TYPE_UNKNOWN)
    5546             :     {
    5547           3 :         d->setPjCRS(proj_create_geocentric_crs(
    5548             :             d->getPROJContext(), pszName, "World Geodetic System 1984",
    5549             :             "WGS 84", SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING,
    5550             :             SRS_PM_GREENWICH, 0.0, SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV),
    5551             :             "Metre", 1.0));
    5552             :     }
    5553           3 :     else if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    5554             :     {
    5555           1 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5556             :     }
    5557           3 :     else if (d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    5558           1 :              d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    5559             :     {
    5560           1 :         auto datum = proj_crs_get_datum(d->getPROJContext(), d->m_pj_crs);
    5561             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    5562             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    5563             :         if (datum == nullptr)
    5564             :         {
    5565             :             datum =
    5566             :                 proj_crs_get_datum_ensemble(d->getPROJContext(), d->m_pj_crs);
    5567             :         }
    5568             : #endif
    5569           1 :         if (datum == nullptr)
    5570             :         {
    5571           0 :             d->undoDemoteFromBoundCRS();
    5572           0 :             return OGRERR_FAILURE;
    5573             :         }
    5574             : 
    5575           1 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    5576           1 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, nullptr,
    5577             :             0.0);
    5578           1 :         d->setPjCRS(pj_crs);
    5579             : 
    5580           1 :         proj_destroy(datum);
    5581             :     }
    5582             :     else
    5583             :     {
    5584           1 :         CPLDebug("OGR",
    5585             :                  "OGRSpatialReference::SetGeocCS(%s) failed.  "
    5586             :                  "It appears an incompatible object already exists.",
    5587             :                  pszName);
    5588           1 :         eErr = OGRERR_FAILURE;
    5589             :     }
    5590           6 :     d->undoDemoteFromBoundCRS();
    5591             : 
    5592           6 :     return eErr;
    5593             : }
    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           4 : OGRErr OSRSetGeocCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5606             : 
    5607             : {
    5608           4 :     VALIDATE_POINTER1(hSRS, "OSRSetGeocCS", OGRERR_FAILURE);
    5609             : 
    5610           4 :     return ToPointer(hSRS)->SetGeocCS(pszName);
    5611             : }
    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           1 : OGRErr OGRSpatialReference::SetVertCS(const char *pszVertCSName,
    5639             :                                       const char *pszVertDatumName,
    5640             :                                       int nVertDatumType)
    5641             : 
    5642             : {
    5643           1 :     TAKE_OPTIONAL_LOCK();
    5644             : 
    5645           1 :     CPL_IGNORE_RET_VAL(nVertDatumType);
    5646             : 
    5647           1 :     d->refreshProjObj();
    5648             : 
    5649           1 :     auto vertCRS = proj_create_vertical_crs(d->getPROJContext(), pszVertCSName,
    5650             :                                             pszVertDatumName, nullptr, 0.0);
    5651             : 
    5652             :     /* -------------------------------------------------------------------- */
    5653             :     /*      Handle the case where we want to make a compound coordinate     */
    5654             :     /*      system.                                                         */
    5655             :     /* -------------------------------------------------------------------- */
    5656           1 :     if (IsProjected() || IsGeographic())
    5657             :     {
    5658           1 :         auto compoundCRS = proj_create_compound_crs(
    5659           1 :             d->getPROJContext(), nullptr, d->m_pj_crs, vertCRS);
    5660           1 :         proj_destroy(vertCRS);
    5661           1 :         d->setPjCRS(compoundCRS);
    5662             :     }
    5663             :     else
    5664             :     {
    5665           0 :         d->setPjCRS(vertCRS);
    5666             :     }
    5667           2 :     return OGRERR_NONE;
    5668             : }
    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           0 : OGRErr OSRSetVertCS(OGRSpatialReferenceH hSRS, const char *pszVertCSName,
    5681             :                     const char *pszVertDatumName, int nVertDatumType)
    5682             : 
    5683             : {
    5684           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVertCS", OGRERR_FAILURE);
    5685             : 
    5686           0 :     return ToPointer(hSRS)->SetVertCS(pszVertCSName, pszVertDatumName,
    5687           0 :                                       nVertDatumType);
    5688             : }
    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          92 : OGRErr OGRSpatialReference::SetCompoundCS(const char *pszName,
    5712             :                                           const OGRSpatialReference *poHorizSRS,
    5713             :                                           const OGRSpatialReference *poVertSRS)
    5714             : 
    5715             : {
    5716         184 :     TAKE_OPTIONAL_LOCK();
    5717             : 
    5718             :     /* -------------------------------------------------------------------- */
    5719             :     /*      Verify these are legal horizontal and vertical coordinate       */
    5720             :     /*      systems.                                                        */
    5721             :     /* -------------------------------------------------------------------- */
    5722          92 :     if (!poVertSRS->IsVertical())
    5723             :     {
    5724           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5725             :                  "SetCompoundCS() fails, vertical component is not VERT_CS.");
    5726           0 :         return OGRERR_FAILURE;
    5727             :     }
    5728          92 :     if (!poHorizSRS->IsProjected() && !poHorizSRS->IsGeographic())
    5729             :     {
    5730           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5731             :                  "SetCompoundCS() fails, horizontal component is not PROJCS or "
    5732             :                  "GEOGCS.");
    5733           0 :         return OGRERR_FAILURE;
    5734             :     }
    5735             : 
    5736             :     /* -------------------------------------------------------------------- */
    5737             :     /*      Replace with compound srs.                                      */
    5738             :     /* -------------------------------------------------------------------- */
    5739          92 :     Clear();
    5740             : 
    5741          92 :     auto compoundCRS = proj_create_compound_crs(d->getPROJContext(), pszName,
    5742          92 :                                                 poHorizSRS->d->m_pj_crs,
    5743          92 :                                                 poVertSRS->d->m_pj_crs);
    5744          92 :     d->setPjCRS(compoundCRS);
    5745             : 
    5746          92 :     return OGRERR_NONE;
    5747             : }
    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           8 : OGRErr OSRSetCompoundCS(OGRSpatialReferenceH hSRS, const char *pszName,
    5759             :                         OGRSpatialReferenceH hHorizSRS,
    5760             :                         OGRSpatialReferenceH hVertSRS)
    5761             : 
    5762             : {
    5763           8 :     VALIDATE_POINTER1(hSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5764           8 :     VALIDATE_POINTER1(hHorizSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5765           8 :     VALIDATE_POINTER1(hVertSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5766             : 
    5767          16 :     return ToPointer(hSRS)->SetCompoundCS(pszName, ToPointer(hHorizSRS),
    5768          16 :                                           ToPointer(hVertSRS));
    5769             : }
    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        4735 : OGRErr OGRSpatialReference::SetProjCS(const char *pszName)
    5790             : 
    5791             : {
    5792        4735 :     TAKE_OPTIONAL_LOCK();
    5793             : 
    5794        4735 :     d->refreshProjObj();
    5795        4735 :     d->demoteFromBoundCRS();
    5796        4735 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    5797             :     {
    5798         487 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5799             :     }
    5800             :     else
    5801             :     {
    5802        4248 :         auto dummyConv = proj_create_conversion(d->getPROJContext(), nullptr,
    5803             :                                                 nullptr, nullptr, nullptr,
    5804             :                                                 nullptr, nullptr, 0, nullptr);
    5805        4248 :         auto cs = proj_create_cartesian_2D_cs(
    5806             :             d->getPROJContext(), PJ_CART2D_EASTING_NORTHING, nullptr, 0);
    5807             : 
    5808        4248 :         auto projCRS = proj_create_projected_crs(
    5809        4248 :             d->getPROJContext(), pszName, d->getGeodBaseCRS(), dummyConv, cs);
    5810        4248 :         proj_destroy(dummyConv);
    5811        4248 :         proj_destroy(cs);
    5812             : 
    5813        4248 :         d->setPjCRS(projCRS);
    5814             :     }
    5815        4735 :     d->undoDemoteFromBoundCRS();
    5816        9470 :     return OGRERR_NONE;
    5817             : }
    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           1 : OGRErr OSRSetProjCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5829             : 
    5830             : {
    5831           1 :     VALIDATE_POINTER1(hSRS, "OSRSetProjCS", OGRERR_FAILURE);
    5832             : 
    5833           1 :     return ToPointer(hSRS)->SetProjCS(pszName);
    5834             : }
    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          23 : OGRErr OGRSpatialReference::SetProjection(const char *pszProjection)
    5852             : 
    5853             : {
    5854          46 :     TAKE_OPTIONAL_LOCK();
    5855             : 
    5856          23 :     OGR_SRSNode *poGeogCS = nullptr;
    5857             : 
    5858          23 :     if (GetRoot() != nullptr && EQUAL(d->m_poRoot->GetValue(), "GEOGCS"))
    5859             :     {
    5860           4 :         poGeogCS = d->m_poRoot;
    5861           4 :         d->m_poRoot = nullptr;
    5862             :     }
    5863             : 
    5864          23 :     if (!GetAttrNode("PROJCS"))
    5865             :     {
    5866          11 :         SetNode("PROJCS", "unnamed");
    5867             :     }
    5868             : 
    5869          23 :     const OGRErr eErr = SetNode("PROJCS|PROJECTION", pszProjection);
    5870          23 :     if (eErr != OGRERR_NONE)
    5871           0 :         return eErr;
    5872             : 
    5873          23 :     if (poGeogCS != nullptr)
    5874           4 :         d->m_poRoot->InsertChild(poGeogCS, 1);
    5875             : 
    5876          23 :     return OGRERR_NONE;
    5877             : }
    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           0 : OGRErr OSRSetProjection(OGRSpatialReferenceH hSRS, const char *pszProjection)
    5889             : 
    5890             : {
    5891           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjection", OGRERR_FAILURE);
    5892             : 
    5893           0 :     return ToPointer(hSRS)->SetProjection(pszProjection);
    5894             : }
    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           1 : OGRSpatialReference::GetWKT2ProjectionMethod(const char **ppszMethodName,
    5922             :                                              const char **ppszMethodAuthName,
    5923             :                                              const char **ppszMethodCode) const
    5924             : {
    5925           2 :     TAKE_OPTIONAL_LOCK();
    5926             : 
    5927           1 :     auto conv = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    5928           1 :     if (!conv)
    5929           0 :         return OGRERR_FAILURE;
    5930           1 :     const char *pszTmpMethodName = "";
    5931           1 :     const char *pszTmpMethodAuthName = "";
    5932           1 :     const char *pszTmpMethodCode = "";
    5933           1 :     int ret = proj_coordoperation_get_method_info(
    5934             :         d->getPROJContext(), conv, &pszTmpMethodName, &pszTmpMethodAuthName,
    5935             :         &pszTmpMethodCode);
    5936             :     // "Internalize" temporary strings returned by PROJ
    5937           1 :     CPLAssert(pszTmpMethodName);
    5938           1 :     if (ppszMethodName)
    5939           1 :         *ppszMethodName = CPLSPrintf("%s", pszTmpMethodName);
    5940           1 :     if (ppszMethodAuthName)
    5941           0 :         *ppszMethodAuthName = pszTmpMethodAuthName
    5942           0 :                                   ? CPLSPrintf("%s", pszTmpMethodAuthName)
    5943           0 :                                   : nullptr;
    5944           1 :     if (ppszMethodCode)
    5945           0 :         *ppszMethodCode =
    5946           0 :             pszTmpMethodCode ? CPLSPrintf("%s", pszTmpMethodCode) : nullptr;
    5947           1 :     proj_destroy(conv);
    5948           1 :     return ret ? OGRERR_NONE : OGRERR_FAILURE;
    5949             : }
    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         129 : OGRErr OGRSpatialReference::SetProjParm(const char *pszParamName,
    5975             :                                         double dfValue)
    5976             : 
    5977             : {
    5978         258 :     TAKE_OPTIONAL_LOCK();
    5979             : 
    5980         129 :     OGR_SRSNode *poPROJCS = GetAttrNode("PROJCS");
    5981             : 
    5982         129 :     if (poPROJCS == nullptr)
    5983           3 :         return OGRERR_FAILURE;
    5984             : 
    5985         126 :     char szValue[64] = {'\0'};
    5986         126 :     OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    5987             : 
    5988             :     /* -------------------------------------------------------------------- */
    5989             :     /*      Try to find existing parameter with this name.                  */
    5990             :     /* -------------------------------------------------------------------- */
    5991        1030 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    5992             :     {
    5993         943 :         OGR_SRSNode *poParam = poPROJCS->GetChild(iChild);
    5994             : 
    5995        1242 :         if (EQUAL(poParam->GetValue(), "PARAMETER") &&
    5996        1242 :             poParam->GetChildCount() == 2 &&
    5997         299 :             EQUAL(poParam->GetChild(0)->GetValue(), pszParamName))
    5998             :         {
    5999          39 :             poParam->GetChild(1)->SetValue(szValue);
    6000          39 :             return OGRERR_NONE;
    6001             :         }
    6002             :     }
    6003             : 
    6004             :     /* -------------------------------------------------------------------- */
    6005             :     /*      Otherwise create a new parameter and append.                    */
    6006             :     /* -------------------------------------------------------------------- */
    6007          87 :     OGR_SRSNode *poParam = new OGR_SRSNode("PARAMETER");
    6008          87 :     poParam->AddChild(new OGR_SRSNode(pszParamName));
    6009          87 :     poParam->AddChild(new OGR_SRSNode(szValue));
    6010             : 
    6011          87 :     poPROJCS->AddChild(poParam);
    6012             : 
    6013          87 :     return OGRERR_NONE;
    6014             : }
    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           0 : OGRErr OSRSetProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    6026             :                       double dfValue)
    6027             : 
    6028             : {
    6029           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjParm", OGRERR_FAILURE);
    6030             : 
    6031           0 :     return ToPointer(hSRS)->SetProjParm(pszParamName, dfValue);
    6032             : }
    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        5117 : int OGRSpatialReference::FindProjParm(const char *pszParameter,
    6049             :                                       const OGR_SRSNode *poPROJCS) const
    6050             : 
    6051             : {
    6052       10234 :     TAKE_OPTIONAL_LOCK();
    6053             : 
    6054        5117 :     if (poPROJCS == nullptr)
    6055           0 :         poPROJCS = GetAttrNode("PROJCS");
    6056             : 
    6057        5117 :     if (poPROJCS == nullptr)
    6058           0 :         return -1;
    6059             : 
    6060             :     /* -------------------------------------------------------------------- */
    6061             :     /*      Search for requested parameter.                                 */
    6062             :     /* -------------------------------------------------------------------- */
    6063        5117 :     bool bIsWKT2 = false;
    6064       33545 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    6065             :     {
    6066       32894 :         const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    6067             : 
    6068       32894 :         if (poParameter->GetChildCount() >= 2)
    6069             :         {
    6070       22701 :             const char *pszValue = poParameter->GetValue();
    6071       37512 :             if (EQUAL(pszValue, "PARAMETER") &&
    6072       14811 :                 EQUAL(poPROJCS->GetChild(iChild)->GetChild(0)->GetValue(),
    6073             :                       pszParameter))
    6074             :             {
    6075        4466 :                 return iChild;
    6076             :             }
    6077       18235 :             else if (EQUAL(pszValue, "METHOD"))
    6078             :             {
    6079          41 :                 bIsWKT2 = true;
    6080             :             }
    6081             :         }
    6082             :     }
    6083             : 
    6084             :     /* -------------------------------------------------------------------- */
    6085             :     /*      Try similar names, for selected parameters.                     */
    6086             :     /* -------------------------------------------------------------------- */
    6087         651 :     if (EQUAL(pszParameter, SRS_PP_LATITUDE_OF_ORIGIN))
    6088             :     {
    6089         304 :         if (bIsWKT2)
    6090             :         {
    6091           8 :             int iChild = FindProjParm(
    6092             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    6093           8 :             if (iChild == -1)
    6094           3 :                 iChild = FindProjParm(
    6095             :                     EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, poPROJCS);
    6096           8 :             return iChild;
    6097             :         }
    6098         296 :         return FindProjParm(SRS_PP_LATITUDE_OF_CENTER, poPROJCS);
    6099             :     }
    6100             : 
    6101         347 :     if (EQUAL(pszParameter, SRS_PP_CENTRAL_MERIDIAN))
    6102             :     {
    6103          38 :         if (bIsWKT2)
    6104             :         {
    6105           9 :             int iChild = FindProjParm(
    6106             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    6107           9 :             if (iChild == -1)
    6108           0 :                 iChild = FindProjParm(
    6109             :                     EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, poPROJCS);
    6110           9 :             return iChild;
    6111             :         }
    6112          29 :         int iChild = FindProjParm(SRS_PP_LONGITUDE_OF_CENTER, poPROJCS);
    6113          29 :         if (iChild == -1)
    6114           0 :             iChild = FindProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, poPROJCS);
    6115          29 :         return iChild;
    6116             :     }
    6117             : 
    6118         309 :     return -1;
    6119             : }
    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        5030 : double OGRSpatialReference::GetProjParm(const char *pszName,
    6144             :                                         double dfDefaultValue,
    6145             :                                         OGRErr *pnErr) const
    6146             : 
    6147             : {
    6148       10060 :     TAKE_OPTIONAL_LOCK();
    6149             : 
    6150        5030 :     d->refreshProjObj();
    6151        5030 :     GetRoot();  // force update of d->m_bNodesWKT2
    6152             : 
    6153        5030 :     if (pnErr != nullptr)
    6154        4031 :         *pnErr = OGRERR_NONE;
    6155             : 
    6156             :     /* -------------------------------------------------------------------- */
    6157             :     /*      Find the desired parameter.                                     */
    6158             :     /* -------------------------------------------------------------------- */
    6159             :     const OGR_SRSNode *poPROJCS =
    6160        5030 :         GetAttrNode(d->m_bNodesWKT2 ? "CONVERSION" : "PROJCS");
    6161        5030 :     if (poPROJCS == nullptr)
    6162             :     {
    6163         258 :         if (pnErr != nullptr)
    6164         258 :             *pnErr = OGRERR_FAILURE;
    6165         258 :         return dfDefaultValue;
    6166             :     }
    6167             : 
    6168        4772 :     const int iChild = FindProjParm(pszName, poPROJCS);
    6169        4772 :     if (iChild == -1)
    6170             :     {
    6171         306 :         if (IsProjected() && GetAxesCount() == 3)
    6172             :         {
    6173           3 :             OGRSpatialReference *poSRSTmp = Clone();
    6174           3 :             poSRSTmp->DemoteTo2D(nullptr);
    6175             :             const double dfRet =
    6176           3 :                 poSRSTmp->GetProjParm(pszName, dfDefaultValue, pnErr);
    6177           3 :             delete poSRSTmp;
    6178           3 :             return dfRet;
    6179             :         }
    6180             : 
    6181         303 :         if (pnErr != nullptr)
    6182         281 :             *pnErr = OGRERR_FAILURE;
    6183         303 :         return dfDefaultValue;
    6184             :     }
    6185             : 
    6186        4466 :     const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    6187        4466 :     return CPLAtof(poParameter->GetChild(1)->GetValue());
    6188             : }
    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          90 : double OSRGetProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6200             :                       double dfDefaultValue, OGRErr *pnErr)
    6201             : 
    6202             : {
    6203          90 :     VALIDATE_POINTER1(hSRS, "OSRGetProjParm", 0);
    6204             : 
    6205          90 :     return ToPointer(hSRS)->GetProjParm(pszName, dfDefaultValue, pnErr);
    6206             : }
    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        4006 : double OGRSpatialReference::GetNormProjParm(const char *pszName,
    6232             :                                             double dfDefaultValue,
    6233             :                                             OGRErr *pnErr) const
    6234             : 
    6235             : {
    6236        8012 :     TAKE_OPTIONAL_LOCK();
    6237             : 
    6238        4006 :     GetNormInfo();
    6239             : 
    6240        4006 :     OGRErr nError = OGRERR_NONE;
    6241        4006 :     double dfRawResult = GetProjParm(pszName, dfDefaultValue, &nError);
    6242        4006 :     if (pnErr != nullptr)
    6243           0 :         *pnErr = nError;
    6244             : 
    6245             :     // If we got the default just return it unadjusted.
    6246        4006 :     if (nError != OGRERR_NONE)
    6247         539 :         return dfRawResult;
    6248             : 
    6249        3467 :     if (d->dfToDegrees != 1.0 && IsAngularParameter(pszName))
    6250           8 :         dfRawResult *= d->dfToDegrees;
    6251             : 
    6252        3467 :     if (d->dfToMeter != 1.0 && IsLinearParameter(pszName))
    6253           5 :         return dfRawResult * d->dfToMeter;
    6254             : 
    6255        3462 :     return dfRawResult;
    6256             : }
    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           1 : double OSRGetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6268             :                           double dfDefaultValue, OGRErr *pnErr)
    6269             : 
    6270             : {
    6271           1 :     VALIDATE_POINTER1(hSRS, "OSRGetNormProjParm", 0);
    6272             : 
    6273           1 :     return ToPointer(hSRS)->GetNormProjParm(pszName, dfDefaultValue, pnErr);
    6274             : }
    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          91 : OGRErr OGRSpatialReference::SetNormProjParm(const char *pszName, double dfValue)
    6299             : 
    6300             : {
    6301         182 :     TAKE_OPTIONAL_LOCK();
    6302             : 
    6303          91 :     GetNormInfo();
    6304             : 
    6305          91 :     if (d->dfToDegrees != 0.0 &&
    6306          91 :         (d->dfToDegrees != 1.0 || d->dfFromGreenwich != 0.0) &&
    6307           0 :         IsAngularParameter(pszName))
    6308             :     {
    6309           0 :         dfValue /= d->dfToDegrees;
    6310             :     }
    6311          95 :     else if (d->dfToMeter != 1.0 && d->dfToMeter != 0.0 &&
    6312           4 :              IsLinearParameter(pszName))
    6313           4 :         dfValue /= d->dfToMeter;
    6314             : 
    6315         182 :     return SetProjParm(pszName, dfValue);
    6316             : }
    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           0 : OGRErr OSRSetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    6328             :                           double dfValue)
    6329             : 
    6330             : {
    6331           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNormProjParm", OGRERR_FAILURE);
    6332             : 
    6333           0 :     return ToPointer(hSRS)->SetNormProjParm(pszParamName, dfValue);
    6334             : }
    6335             : 
    6336             : /************************************************************************/
    6337             : /*                               SetTM()                                */
    6338             : /************************************************************************/
    6339             : 
    6340         442 : OGRErr OGRSpatialReference::SetTM(double dfCenterLat, double dfCenterLong,
    6341             :                                   double dfScale, double dfFalseEasting,
    6342             :                                   double dfFalseNorthing)
    6343             : 
    6344             : {
    6345         884 :     TAKE_OPTIONAL_LOCK();
    6346             : 
    6347         442 :     return d->replaceConversionAndUnref(
    6348             :         proj_create_conversion_transverse_mercator(
    6349             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    6350         884 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6351             : }
    6352             : 
    6353             : /************************************************************************/
    6354             : /*                              OSRSetTM()                              */
    6355             : /************************************************************************/
    6356             : 
    6357           1 : OGRErr OSRSetTM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6358             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    6359             :                 double dfFalseNorthing)
    6360             : 
    6361             : {
    6362           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTM", OGRERR_FAILURE);
    6363             : 
    6364           1 :     return ToPointer(hSRS)->SetTM(dfCenterLat, dfCenterLong, dfScale,
    6365           1 :                                   dfFalseEasting, dfFalseNorthing);
    6366             : }
    6367             : 
    6368             : /************************************************************************/
    6369             : /*                            SetTMVariant()                            */
    6370             : /************************************************************************/
    6371             : 
    6372           0 : OGRErr OGRSpatialReference::SetTMVariant(const char *pszVariantName,
    6373             :                                          double dfCenterLat,
    6374             :                                          double dfCenterLong, double dfScale,
    6375             :                                          double dfFalseEasting,
    6376             :                                          double dfFalseNorthing)
    6377             : 
    6378             : {
    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             : }
    6390             : 
    6391             : /************************************************************************/
    6392             : /*                          OSRSetTMVariant()                           */
    6393             : /************************************************************************/
    6394             : 
    6395           0 : OGRErr OSRSetTMVariant(OGRSpatialReferenceH hSRS, const char *pszVariantName,
    6396             :                        double dfCenterLat, double dfCenterLong, double dfScale,
    6397             :                        double dfFalseEasting, double dfFalseNorthing)
    6398             : 
    6399             : {
    6400           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMVariant", OGRERR_FAILURE);
    6401             : 
    6402           0 :     return ToPointer(hSRS)->SetTMVariant(pszVariantName, dfCenterLat,
    6403             :                                          dfCenterLong, dfScale, dfFalseEasting,
    6404           0 :                                          dfFalseNorthing);
    6405             : }
    6406             : 
    6407             : /************************************************************************/
    6408             : /*                              SetTMSO()                               */
    6409             : /************************************************************************/
    6410             : 
    6411           3 : OGRErr OGRSpatialReference::SetTMSO(double dfCenterLat, double dfCenterLong,
    6412             :                                     double dfScale, double dfFalseEasting,
    6413             :                                     double dfFalseNorthing)
    6414             : 
    6415             : {
    6416           6 :     TAKE_OPTIONAL_LOCK();
    6417             : 
    6418           3 :     auto conv = proj_create_conversion_transverse_mercator_south_oriented(
    6419             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
    6420             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6421             : 
    6422           3 :     const char *pszName = nullptr;
    6423           3 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    6424           3 :     CPLString osName = pszName ? pszName : "";
    6425             : 
    6426           3 :     d->refreshProjObj();
    6427             : 
    6428           3 :     d->demoteFromBoundCRS();
    6429             : 
    6430           3 :     auto cs = proj_create_cartesian_2D_cs(
    6431             :         d->getPROJContext(), PJ_CART2D_WESTING_SOUTHING,
    6432           3 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    6433             :     auto projCRS =
    6434           3 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    6435           3 :                                   d->getGeodBaseCRS(), conv, cs);
    6436           3 :     proj_destroy(conv);
    6437           3 :     proj_destroy(cs);
    6438             : 
    6439           3 :     d->setPjCRS(projCRS);
    6440             : 
    6441           3 :     d->undoDemoteFromBoundCRS();
    6442             : 
    6443           6 :     return OGRERR_NONE;
    6444             : }
    6445             : 
    6446             : /************************************************************************/
    6447             : /*                             OSRSetTMSO()                             */
    6448             : /************************************************************************/
    6449             : 
    6450           0 : OGRErr OSRSetTMSO(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6451             :                   double dfCenterLong, double dfScale, double dfFalseEasting,
    6452             :                   double dfFalseNorthing)
    6453             : 
    6454             : {
    6455           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMSO", OGRERR_FAILURE);
    6456             : 
    6457           0 :     return ToPointer(hSRS)->SetTMSO(dfCenterLat, dfCenterLong, dfScale,
    6458           0 :                                     dfFalseEasting, dfFalseNorthing);
    6459             : }
    6460             : 
    6461             : /************************************************************************/
    6462             : /*                              SetTPED()                               */
    6463             : /************************************************************************/
    6464             : 
    6465           1 : OGRErr OGRSpatialReference::SetTPED(double dfLat1, double dfLong1,
    6466             :                                     double dfLat2, double dfLong2,
    6467             :                                     double dfFalseEasting,
    6468             :                                     double dfFalseNorthing)
    6469             : 
    6470             : {
    6471           2 :     TAKE_OPTIONAL_LOCK();
    6472             : 
    6473           1 :     return d->replaceConversionAndUnref(
    6474             :         proj_create_conversion_two_point_equidistant(
    6475             :             d->getPROJContext(), dfLat1, dfLong1, dfLat2, dfLong2,
    6476           2 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6477             : }
    6478             : 
    6479             : /************************************************************************/
    6480             : /*                             OSRSetTPED()                             */
    6481             : /************************************************************************/
    6482             : 
    6483           0 : OGRErr OSRSetTPED(OGRSpatialReferenceH hSRS, double dfLat1, double dfLong1,
    6484             :                   double dfLat2, double dfLong2, double dfFalseEasting,
    6485             :                   double dfFalseNorthing)
    6486             : 
    6487             : {
    6488           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTPED", OGRERR_FAILURE);
    6489             : 
    6490           0 :     return ToPointer(hSRS)->SetTPED(dfLat1, dfLong1, dfLat2, dfLong2,
    6491           0 :                                     dfFalseEasting, dfFalseNorthing);
    6492             : }
    6493             : 
    6494             : /************************************************************************/
    6495             : /*                               SetTMG()                               */
    6496             : /************************************************************************/
    6497             : 
    6498           0 : OGRErr OGRSpatialReference::SetTMG(double dfCenterLat, double dfCenterLong,
    6499             :                                    double dfFalseEasting,
    6500             :                                    double dfFalseNorthing)
    6501             : 
    6502             : {
    6503           0 :     TAKE_OPTIONAL_LOCK();
    6504             : 
    6505           0 :     return d->replaceConversionAndUnref(
    6506             :         proj_create_conversion_tunisia_mapping_grid(
    6507             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6508           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6509             : }
    6510             : 
    6511             : /************************************************************************/
    6512             : /*                             OSRSetTMG()                              */
    6513             : /************************************************************************/
    6514             : 
    6515           0 : OGRErr OSRSetTMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6516             :                  double dfCenterLong, double dfFalseEasting,
    6517             :                  double dfFalseNorthing)
    6518             : 
    6519             : {
    6520           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMG", OGRERR_FAILURE);
    6521             : 
    6522           0 :     return ToPointer(hSRS)->SetTMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    6523           0 :                                    dfFalseNorthing);
    6524             : }
    6525             : 
    6526             : /************************************************************************/
    6527             : /*                              SetACEA()                               */
    6528             : /************************************************************************/
    6529             : 
    6530          39 : OGRErr OGRSpatialReference::SetACEA(double dfStdP1, double dfStdP2,
    6531             :                                     double dfCenterLat, double dfCenterLong,
    6532             :                                     double dfFalseEasting,
    6533             :                                     double dfFalseNorthing)
    6534             : 
    6535             : {
    6536          78 :     TAKE_OPTIONAL_LOCK();
    6537             : 
    6538             :     // Note different order of parameters. The one in PROJ is conformant with
    6539             :     // EPSG
    6540          39 :     return d->replaceConversionAndUnref(
    6541             :         proj_create_conversion_albers_equal_area(
    6542             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6543          78 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6544             : }
    6545             : 
    6546             : /************************************************************************/
    6547             : /*                             OSRSetACEA()                             */
    6548             : /************************************************************************/
    6549             : 
    6550           0 : OGRErr OSRSetACEA(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6551             :                   double dfCenterLat, double dfCenterLong,
    6552             :                   double dfFalseEasting, double dfFalseNorthing)
    6553             : 
    6554             : {
    6555           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6556             : 
    6557           0 :     return ToPointer(hSRS)->SetACEA(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6558           0 :                                     dfFalseEasting, dfFalseNorthing);
    6559             : }
    6560             : 
    6561             : /************************************************************************/
    6562             : /*                               SetAE()                                */
    6563             : /************************************************************************/
    6564             : 
    6565          21 : OGRErr OGRSpatialReference::SetAE(double dfCenterLat, double dfCenterLong,
    6566             :                                   double dfFalseEasting, double dfFalseNorthing)
    6567             : 
    6568             : {
    6569          42 :     TAKE_OPTIONAL_LOCK();
    6570             : 
    6571          21 :     return d->replaceConversionAndUnref(
    6572             :         proj_create_conversion_azimuthal_equidistant(
    6573             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6574          42 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6575             : }
    6576             : 
    6577             : /************************************************************************/
    6578             : /*                              OSRSetAE()                              */
    6579             : /************************************************************************/
    6580             : 
    6581           0 : OGRErr OSRSetAE(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6582             :                 double dfCenterLong, double dfFalseEasting,
    6583             :                 double dfFalseNorthing)
    6584             : 
    6585             : {
    6586           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6587             : 
    6588           0 :     return ToPointer(hSRS)->SetAE(dfCenterLat, dfCenterLong, dfFalseEasting,
    6589           0 :                                   dfFalseNorthing);
    6590             : }
    6591             : 
    6592             : /************************************************************************/
    6593             : /*                              SetBonne()                              */
    6594             : /************************************************************************/
    6595             : 
    6596           1 : OGRErr OGRSpatialReference::SetBonne(double dfStdP1, double dfCentralMeridian,
    6597             :                                      double dfFalseEasting,
    6598             :                                      double dfFalseNorthing)
    6599             : 
    6600             : {
    6601           2 :     TAKE_OPTIONAL_LOCK();
    6602             : 
    6603           1 :     return d->replaceConversionAndUnref(proj_create_conversion_bonne(
    6604             :         d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6605           2 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6606             : }
    6607             : 
    6608             : /************************************************************************/
    6609             : /*                            OSRSetBonne()                             */
    6610             : /************************************************************************/
    6611             : 
    6612           0 : OGRErr OSRSetBonne(OGRSpatialReferenceH hSRS, double dfStdP1,
    6613             :                    double dfCentralMeridian, double dfFalseEasting,
    6614             :                    double dfFalseNorthing)
    6615             : 
    6616             : {
    6617           0 :     VALIDATE_POINTER1(hSRS, "OSRSetBonne", OGRERR_FAILURE);
    6618             : 
    6619           0 :     return ToPointer(hSRS)->SetBonne(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6620           0 :                                      dfFalseNorthing);
    6621             : }
    6622             : 
    6623             : /************************************************************************/
    6624             : /*                               SetCEA()                               */
    6625             : /************************************************************************/
    6626             : 
    6627           4 : OGRErr OGRSpatialReference::SetCEA(double dfStdP1, double dfCentralMeridian,
    6628             :                                    double dfFalseEasting,
    6629             :                                    double dfFalseNorthing)
    6630             : 
    6631             : {
    6632           8 :     TAKE_OPTIONAL_LOCK();
    6633             : 
    6634           4 :     return d->replaceConversionAndUnref(
    6635             :         proj_create_conversion_lambert_cylindrical_equal_area(
    6636             :             d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6637           8 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6638             : }
    6639             : 
    6640             : /************************************************************************/
    6641             : /*                             OSRSetCEA()                              */
    6642             : /************************************************************************/
    6643             : 
    6644           0 : OGRErr OSRSetCEA(OGRSpatialReferenceH hSRS, double dfStdP1,
    6645             :                  double dfCentralMeridian, double dfFalseEasting,
    6646             :                  double dfFalseNorthing)
    6647             : 
    6648             : {
    6649           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCEA", OGRERR_FAILURE);
    6650             : 
    6651           0 :     return ToPointer(hSRS)->SetCEA(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6652           0 :                                    dfFalseNorthing);
    6653             : }
    6654             : 
    6655             : /************************************************************************/
    6656             : /*                               SetCS()                                */
    6657             : /************************************************************************/
    6658             : 
    6659           5 : OGRErr OGRSpatialReference::SetCS(double dfCenterLat, double dfCenterLong,
    6660             :                                   double dfFalseEasting, double dfFalseNorthing)
    6661             : 
    6662             : {
    6663          10 :     TAKE_OPTIONAL_LOCK();
    6664             : 
    6665           5 :     return d->replaceConversionAndUnref(proj_create_conversion_cassini_soldner(
    6666             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6667          10 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6668             : }
    6669             : 
    6670             : /************************************************************************/
    6671             : /*                              OSRSetCS()                              */
    6672             : /************************************************************************/
    6673             : 
    6674           0 : OGRErr OSRSetCS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6675             :                 double dfCenterLong, double dfFalseEasting,
    6676             :                 double dfFalseNorthing)
    6677             : 
    6678             : {
    6679           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCS", OGRERR_FAILURE);
    6680             : 
    6681           0 :     return ToPointer(hSRS)->SetCS(dfCenterLat, dfCenterLong, dfFalseEasting,
    6682           0 :                                   dfFalseNorthing);
    6683             : }
    6684             : 
    6685             : /************************************************************************/
    6686             : /*                               SetEC()                                */
    6687             : /************************************************************************/
    6688             : 
    6689           7 : OGRErr OGRSpatialReference::SetEC(double dfStdP1, double dfStdP2,
    6690             :                                   double dfCenterLat, double dfCenterLong,
    6691             :                                   double dfFalseEasting, double dfFalseNorthing)
    6692             : 
    6693             : {
    6694          14 :     TAKE_OPTIONAL_LOCK();
    6695             : 
    6696             :     // Note: different order of arguments
    6697           7 :     return d->replaceConversionAndUnref(
    6698             :         proj_create_conversion_equidistant_conic(
    6699             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6700          14 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6701             : }
    6702             : 
    6703             : /************************************************************************/
    6704             : /*                              OSRSetEC()                              */
    6705             : /************************************************************************/
    6706             : 
    6707           0 : OGRErr OSRSetEC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6708             :                 double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    6709             :                 double dfFalseNorthing)
    6710             : 
    6711             : {
    6712           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEC", OGRERR_FAILURE);
    6713             : 
    6714           0 :     return ToPointer(hSRS)->SetEC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6715           0 :                                   dfFalseEasting, dfFalseNorthing);
    6716             : }
    6717             : 
    6718             : /************************************************************************/
    6719             : /*                             SetEckert()                              */
    6720             : /************************************************************************/
    6721             : 
    6722          10 : OGRErr OGRSpatialReference::SetEckert(int nVariation,  // 1-6.
    6723             :                                       double dfCentralMeridian,
    6724             :                                       double dfFalseEasting,
    6725             :                                       double dfFalseNorthing)
    6726             : 
    6727             : {
    6728          20 :     TAKE_OPTIONAL_LOCK();
    6729             : 
    6730             :     PJ *conv;
    6731          10 :     if (nVariation == 1)
    6732             :     {
    6733           1 :         conv = proj_create_conversion_eckert_i(
    6734             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6735             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6736             :     }
    6737           9 :     else if (nVariation == 2)
    6738             :     {
    6739           1 :         conv = proj_create_conversion_eckert_ii(
    6740             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6741             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6742             :     }
    6743           8 :     else if (nVariation == 3)
    6744             :     {
    6745           1 :         conv = proj_create_conversion_eckert_iii(
    6746             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6747             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6748             :     }
    6749           7 :     else if (nVariation == 4)
    6750             :     {
    6751           3 :         conv = proj_create_conversion_eckert_iv(
    6752             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6753             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6754             :     }
    6755           4 :     else if (nVariation == 5)
    6756             :     {
    6757           1 :         conv = proj_create_conversion_eckert_v(
    6758             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6759             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6760             :     }
    6761           3 :     else if (nVariation == 6)
    6762             :     {
    6763           3 :         conv = proj_create_conversion_eckert_vi(
    6764             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6765             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6766             :     }
    6767             :     else
    6768             :     {
    6769           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6770             :                  "Unsupported Eckert variation (%d).", nVariation);
    6771           0 :         return OGRERR_UNSUPPORTED_SRS;
    6772             :     }
    6773             : 
    6774          10 :     return d->replaceConversionAndUnref(conv);
    6775             : }
    6776             : 
    6777             : /************************************************************************/
    6778             : /*                            OSRSetEckert()                            */
    6779             : /************************************************************************/
    6780             : 
    6781           0 : OGRErr OSRSetEckert(OGRSpatialReferenceH hSRS, int nVariation,
    6782             :                     double dfCentralMeridian, double dfFalseEasting,
    6783             :                     double dfFalseNorthing)
    6784             : 
    6785             : {
    6786           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckert", OGRERR_FAILURE);
    6787             : 
    6788           0 :     return ToPointer(hSRS)->SetEckert(nVariation, dfCentralMeridian,
    6789           0 :                                       dfFalseEasting, dfFalseNorthing);
    6790             : }
    6791             : 
    6792             : /************************************************************************/
    6793             : /*                            SetEckertIV()                             */
    6794             : /*                                                                      */
    6795             : /*      Deprecated                                                      */
    6796             : /************************************************************************/
    6797             : 
    6798           2 : OGRErr OGRSpatialReference::SetEckertIV(double dfCentralMeridian,
    6799             :                                         double dfFalseEasting,
    6800             :                                         double dfFalseNorthing)
    6801             : 
    6802             : {
    6803           2 :     return SetEckert(4, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6804             : }
    6805             : 
    6806             : /************************************************************************/
    6807             : /*                           OSRSetEckertIV()                           */
    6808             : /************************************************************************/
    6809             : 
    6810           0 : OGRErr OSRSetEckertIV(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6811             :                       double dfFalseEasting, double dfFalseNorthing)
    6812             : 
    6813             : {
    6814           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertIV", OGRERR_FAILURE);
    6815             : 
    6816           0 :     return ToPointer(hSRS)->SetEckertIV(dfCentralMeridian, dfFalseEasting,
    6817           0 :                                         dfFalseNorthing);
    6818             : }
    6819             : 
    6820             : /************************************************************************/
    6821             : /*                            SetEckertVI()                             */
    6822             : /*                                                                      */
    6823             : /*      Deprecated                                                      */
    6824             : /************************************************************************/
    6825             : 
    6826           2 : OGRErr OGRSpatialReference::SetEckertVI(double dfCentralMeridian,
    6827             :                                         double dfFalseEasting,
    6828             :                                         double dfFalseNorthing)
    6829             : 
    6830             : {
    6831           2 :     return SetEckert(6, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6832             : }
    6833             : 
    6834             : /************************************************************************/
    6835             : /*                           OSRSetEckertVI()                           */
    6836             : /************************************************************************/
    6837             : 
    6838           0 : OGRErr OSRSetEckertVI(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6839             :                       double dfFalseEasting, double dfFalseNorthing)
    6840             : 
    6841             : {
    6842           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertVI", OGRERR_FAILURE);
    6843             : 
    6844           0 :     return ToPointer(hSRS)->SetEckertVI(dfCentralMeridian, dfFalseEasting,
    6845           0 :                                         dfFalseNorthing);
    6846             : }
    6847             : 
    6848             : /************************************************************************/
    6849             : /*                         SetEquirectangular()                         */
    6850             : /************************************************************************/
    6851             : 
    6852           2 : OGRErr OGRSpatialReference::SetEquirectangular(double dfCenterLat,
    6853             :                                                double dfCenterLong,
    6854             :                                                double dfFalseEasting,
    6855             :                                                double dfFalseNorthing)
    6856             : 
    6857             : {
    6858           4 :     TAKE_OPTIONAL_LOCK();
    6859             : 
    6860           2 :     if (dfCenterLat == 0.0)
    6861             :     {
    6862           0 :         return d->replaceConversionAndUnref(
    6863             :             proj_create_conversion_equidistant_cylindrical(
    6864             :                 d->getPROJContext(), 0.0, dfCenterLong, dfFalseEasting,
    6865           0 :                 dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6866             :     }
    6867             : 
    6868             :     // Non-standard extension with non-zero latitude of origin
    6869           2 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6870           2 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6871           2 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6872           2 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6873           2 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6874             : 
    6875           2 :     return OGRERR_NONE;
    6876             : }
    6877             : 
    6878             : /************************************************************************/
    6879             : /*                       OSRSetEquirectangular()                        */
    6880             : /************************************************************************/
    6881             : 
    6882           0 : OGRErr OSRSetEquirectangular(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6883             :                              double dfCenterLong, double dfFalseEasting,
    6884             :                              double dfFalseNorthing)
    6885             : 
    6886             : {
    6887           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular", OGRERR_FAILURE);
    6888             : 
    6889           0 :     return ToPointer(hSRS)->SetEquirectangular(dfCenterLat, dfCenterLong,
    6890           0 :                                                dfFalseEasting, dfFalseNorthing);
    6891             : }
    6892             : 
    6893             : /************************************************************************/
    6894             : /*                         SetEquirectangular2()                        */
    6895             : /* Generalized form                                                     */
    6896             : /************************************************************************/
    6897             : 
    6898         179 : OGRErr OGRSpatialReference::SetEquirectangular2(double dfCenterLat,
    6899             :                                                 double dfCenterLong,
    6900             :                                                 double dfStdParallel1,
    6901             :                                                 double dfFalseEasting,
    6902             :                                                 double dfFalseNorthing)
    6903             : 
    6904             : {
    6905         358 :     TAKE_OPTIONAL_LOCK();
    6906             : 
    6907         179 :     if (dfCenterLat == 0.0)
    6908             :     {
    6909         174 :         return d->replaceConversionAndUnref(
    6910             :             proj_create_conversion_equidistant_cylindrical(
    6911             :                 d->getPROJContext(), dfStdParallel1, dfCenterLong,
    6912         174 :                 dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6913             :     }
    6914             : 
    6915             :     // Non-standard extension with non-zero latitude of origin
    6916           5 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6917           5 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6918           5 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6919           5 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdParallel1);
    6920           5 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6921           5 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6922             : 
    6923           5 :     return OGRERR_NONE;
    6924             : }
    6925             : 
    6926             : /************************************************************************/
    6927             : /*                       OSRSetEquirectangular2()                       */
    6928             : /************************************************************************/
    6929             : 
    6930           3 : OGRErr OSRSetEquirectangular2(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6931             :                               double dfCenterLong, double dfStdParallel1,
    6932             :                               double dfFalseEasting, double dfFalseNorthing)
    6933             : 
    6934             : {
    6935           3 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular2", OGRERR_FAILURE);
    6936             : 
    6937           3 :     return ToPointer(hSRS)->SetEquirectangular2(dfCenterLat, dfCenterLong,
    6938             :                                                 dfStdParallel1, dfFalseEasting,
    6939           3 :                                                 dfFalseNorthing);
    6940             : }
    6941             : 
    6942             : /************************************************************************/
    6943             : /*                               SetGS()                                */
    6944             : /************************************************************************/
    6945             : 
    6946           5 : OGRErr OGRSpatialReference::SetGS(double dfCentralMeridian,
    6947             :                                   double dfFalseEasting, double dfFalseNorthing)
    6948             : 
    6949             : {
    6950           5 :     return d->replaceConversionAndUnref(proj_create_conversion_gall(
    6951             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    6952           5 :         nullptr, 0.0, nullptr, 0.0));
    6953             : }
    6954             : 
    6955             : /************************************************************************/
    6956             : /*                              OSRSetGS()                              */
    6957             : /************************************************************************/
    6958             : 
    6959           2 : OGRErr OSRSetGS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6960             :                 double dfFalseEasting, double dfFalseNorthing)
    6961             : 
    6962             : {
    6963           2 :     VALIDATE_POINTER1(hSRS, "OSRSetGS", OGRERR_FAILURE);
    6964             : 
    6965           2 :     return ToPointer(hSRS)->SetGS(dfCentralMeridian, dfFalseEasting,
    6966           2 :                                   dfFalseNorthing);
    6967             : }
    6968             : 
    6969             : /************************************************************************/
    6970             : /*                               SetGH()                                */
    6971             : /************************************************************************/
    6972             : 
    6973           0 : OGRErr OGRSpatialReference::SetGH(double dfCentralMeridian,
    6974             :                                   double dfFalseEasting, double dfFalseNorthing)
    6975             : 
    6976             : {
    6977           0 :     TAKE_OPTIONAL_LOCK();
    6978             : 
    6979           0 :     return d->replaceConversionAndUnref(proj_create_conversion_goode_homolosine(
    6980             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    6981           0 :         nullptr, 0.0, nullptr, 0.0));
    6982             : }
    6983             : 
    6984             : /************************************************************************/
    6985             : /*                              OSRSetGH()                              */
    6986             : /************************************************************************/
    6987             : 
    6988           0 : OGRErr OSRSetGH(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6989             :                 double dfFalseEasting, double dfFalseNorthing)
    6990             : 
    6991             : {
    6992           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGH", OGRERR_FAILURE);
    6993             : 
    6994           0 :     return ToPointer(hSRS)->SetGH(dfCentralMeridian, dfFalseEasting,
    6995           0 :                                   dfFalseNorthing);
    6996             : }
    6997             : 
    6998             : /************************************************************************/
    6999             : /*                               SetIGH()                               */
    7000             : /************************************************************************/
    7001             : 
    7002           0 : OGRErr OGRSpatialReference::SetIGH()
    7003             : 
    7004             : {
    7005           0 :     TAKE_OPTIONAL_LOCK();
    7006             : 
    7007           0 :     return d->replaceConversionAndUnref(
    7008             :         proj_create_conversion_interrupted_goode_homolosine(
    7009           0 :             d->getPROJContext(), 0.0, 0.0, 0.0, nullptr, 0.0, nullptr, 0.0));
    7010             : }
    7011             : 
    7012             : /************************************************************************/
    7013             : /*                             OSRSetIGH()                              */
    7014             : /************************************************************************/
    7015             : 
    7016           0 : OGRErr OSRSetIGH(OGRSpatialReferenceH hSRS)
    7017             : 
    7018             : {
    7019           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIGH", OGRERR_FAILURE);
    7020             : 
    7021           0 :     return ToPointer(hSRS)->SetIGH();
    7022             : }
    7023             : 
    7024             : /************************************************************************/
    7025             : /*                              SetGEOS()                               */
    7026             : /************************************************************************/
    7027             : 
    7028           3 : OGRErr OGRSpatialReference::SetGEOS(double dfCentralMeridian,
    7029             :                                     double dfSatelliteHeight,
    7030             :                                     double dfFalseEasting,
    7031             :                                     double dfFalseNorthing)
    7032             : 
    7033             : {
    7034           6 :     TAKE_OPTIONAL_LOCK();
    7035             : 
    7036           3 :     return d->replaceConversionAndUnref(
    7037             :         proj_create_conversion_geostationary_satellite_sweep_y(
    7038             :             d->getPROJContext(), dfCentralMeridian, dfSatelliteHeight,
    7039           6 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7040             : }
    7041             : 
    7042             : /************************************************************************/
    7043             : /*                             OSRSetGEOS()                             */
    7044             : /************************************************************************/
    7045             : 
    7046           0 : OGRErr OSRSetGEOS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7047             :                   double dfSatelliteHeight, double dfFalseEasting,
    7048             :                   double dfFalseNorthing)
    7049             : 
    7050             : {
    7051           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGEOS", OGRERR_FAILURE);
    7052             : 
    7053           0 :     return ToPointer(hSRS)->SetGEOS(dfCentralMeridian, dfSatelliteHeight,
    7054           0 :                                     dfFalseEasting, dfFalseNorthing);
    7055             : }
    7056             : 
    7057             : /************************************************************************/
    7058             : /*                     SetGaussSchreiberTMercator()                     */
    7059             : /************************************************************************/
    7060             : 
    7061           0 : OGRErr OGRSpatialReference::SetGaussSchreiberTMercator(double dfCenterLat,
    7062             :                                                        double dfCenterLong,
    7063             :                                                        double dfScale,
    7064             :                                                        double dfFalseEasting,
    7065             :                                                        double dfFalseNorthing)
    7066             : 
    7067             : {
    7068           0 :     TAKE_OPTIONAL_LOCK();
    7069             : 
    7070           0 :     return d->replaceConversionAndUnref(
    7071             :         proj_create_conversion_gauss_schreiber_transverse_mercator(
    7072             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7073           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7074             : }
    7075             : 
    7076             : /************************************************************************/
    7077             : /*                   OSRSetGaussSchreiberTMercator()                    */
    7078             : /************************************************************************/
    7079             : 
    7080           0 : OGRErr OSRSetGaussSchreiberTMercator(OGRSpatialReferenceH hSRS,
    7081             :                                      double dfCenterLat, double dfCenterLong,
    7082             :                                      double dfScale, double dfFalseEasting,
    7083             :                                      double dfFalseNorthing)
    7084             : 
    7085             : {
    7086           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGaussSchreiberTMercator", OGRERR_FAILURE);
    7087             : 
    7088           0 :     return ToPointer(hSRS)->SetGaussSchreiberTMercator(
    7089           0 :         dfCenterLat, dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing);
    7090             : }
    7091             : 
    7092             : /************************************************************************/
    7093             : /*                            SetGnomonic()                             */
    7094             : /************************************************************************/
    7095             : 
    7096           2 : OGRErr OGRSpatialReference::SetGnomonic(double dfCenterLat, double dfCenterLong,
    7097             :                                         double dfFalseEasting,
    7098             :                                         double dfFalseNorthing)
    7099             : 
    7100             : {
    7101           4 :     TAKE_OPTIONAL_LOCK();
    7102             : 
    7103           2 :     return d->replaceConversionAndUnref(proj_create_conversion_gnomonic(
    7104             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7105           4 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7106             : }
    7107             : 
    7108             : /************************************************************************/
    7109             : /*                           OSRSetGnomonic()                           */
    7110             : /************************************************************************/
    7111             : 
    7112           0 : OGRErr OSRSetGnomonic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7113             :                       double dfCenterLong, double dfFalseEasting,
    7114             :                       double dfFalseNorthing)
    7115             : 
    7116             : {
    7117           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGnomonic", OGRERR_FAILURE);
    7118             : 
    7119           0 :     return ToPointer(hSRS)->SetGnomonic(dfCenterLat, dfCenterLong,
    7120           0 :                                         dfFalseEasting, dfFalseNorthing);
    7121             : }
    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           4 : OGRErr OGRSpatialReference::SetHOMAC(double dfCenterLat, double dfCenterLong,
    7149             :                                      double dfAzimuth, double dfRectToSkew,
    7150             :                                      double dfScale, double dfFalseEasting,
    7151             :                                      double dfFalseNorthing)
    7152             : 
    7153             : {
    7154           8 :     TAKE_OPTIONAL_LOCK();
    7155             : 
    7156           4 :     return d->replaceConversionAndUnref(
    7157             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    7158             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7159             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7160           8 :             0.0, nullptr, 0.0));
    7161             : }
    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           0 : OGRErr OSRSetHOMAC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7173             :                    double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7174             :                    double dfScale, double dfFalseEasting,
    7175             :                    double dfFalseNorthing)
    7176             : 
    7177             : {
    7178           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOMAC", OGRERR_FAILURE);
    7179             : 
    7180           0 :     return ToPointer(hSRS)->SetHOMAC(dfCenterLat, dfCenterLong, dfAzimuth,
    7181             :                                      dfRectToSkew, dfScale, dfFalseEasting,
    7182           0 :                                      dfFalseNorthing);
    7183             : }
    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          13 : OGRErr OGRSpatialReference::SetHOM(double dfCenterLat, double dfCenterLong,
    7210             :                                    double dfAzimuth, double dfRectToSkew,
    7211             :                                    double dfScale, double dfFalseEasting,
    7212             :                                    double dfFalseNorthing)
    7213             : 
    7214             : {
    7215          26 :     TAKE_OPTIONAL_LOCK();
    7216             : 
    7217          13 :     return d->replaceConversionAndUnref(
    7218             :         proj_create_conversion_hotine_oblique_mercator_variant_a(
    7219             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7220             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7221          26 :             0.0, nullptr, 0.0));
    7222             : }
    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           0 : OGRErr OSRSetHOM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7233             :                  double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7234             :                  double dfScale, double dfFalseEasting, double dfFalseNorthing)
    7235             : 
    7236             : {
    7237           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM", OGRERR_FAILURE);
    7238             : 
    7239           0 :     return ToPointer(hSRS)->SetHOM(dfCenterLat, dfCenterLong, dfAzimuth,
    7240             :                                    dfRectToSkew, dfScale, dfFalseEasting,
    7241           0 :                                    dfFalseNorthing);
    7242             : }
    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           3 : 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             : {
    7273           6 :     TAKE_OPTIONAL_LOCK();
    7274             : 
    7275           3 :     return d->replaceConversionAndUnref(
    7276             :         proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin(
    7277             :             d->getPROJContext(), dfCenterLat, dfLat1, dfLong1, dfLat2, dfLong2,
    7278             :             dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    7279           6 :             0.0));
    7280             : }
    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           0 : 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             : {
    7297           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM2PNO", OGRERR_FAILURE);
    7298             : 
    7299           0 :     return ToPointer(hSRS)->SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
    7300             :                                        dfLong2, dfScale, dfFalseEasting,
    7301           0 :                                        dfFalseNorthing);
    7302             : }
    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           0 : OGRErr OGRSpatialReference::SetLOM(double dfCenterLat, double dfCenterLong,
    7323             :                                    double dfAzimuth, double dfScale,
    7324             :                                    double dfFalseEasting,
    7325             :                                    double dfFalseNorthing)
    7326             : 
    7327             : {
    7328           0 :     TAKE_OPTIONAL_LOCK();
    7329             : 
    7330           0 :     return d->replaceConversionAndUnref(
    7331             :         proj_create_conversion_laborde_oblique_mercator(
    7332             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth, dfScale,
    7333           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7334             : }
    7335             : 
    7336             : /************************************************************************/
    7337             : /*                          SetIWMPolyconic()                           */
    7338             : /************************************************************************/
    7339             : 
    7340           0 : OGRErr OGRSpatialReference::SetIWMPolyconic(double dfLat1, double dfLat2,
    7341             :                                             double dfCenterLong,
    7342             :                                             double dfFalseEasting,
    7343             :                                             double dfFalseNorthing)
    7344             : 
    7345             : {
    7346           0 :     TAKE_OPTIONAL_LOCK();
    7347             : 
    7348           0 :     return d->replaceConversionAndUnref(
    7349             :         proj_create_conversion_international_map_world_polyconic(
    7350             :             d->getPROJContext(), dfCenterLong, dfLat1, dfLat2, dfFalseEasting,
    7351           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7352             : }
    7353             : 
    7354             : /************************************************************************/
    7355             : /*                         OSRSetIWMPolyconic()                         */
    7356             : /************************************************************************/
    7357             : 
    7358           0 : OGRErr OSRSetIWMPolyconic(OGRSpatialReferenceH hSRS, double dfLat1,
    7359             :                           double dfLat2, double dfCenterLong,
    7360             :                           double dfFalseEasting, double dfFalseNorthing)
    7361             : 
    7362             : {
    7363           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIWMPolyconic", OGRERR_FAILURE);
    7364             : 
    7365           0 :     return ToPointer(hSRS)->SetIWMPolyconic(dfLat1, dfLat2, dfCenterLong,
    7366           0 :                                             dfFalseEasting, dfFalseNorthing);
    7367             : }
    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           3 : OGRErr OGRSpatialReference::SetKrovak(double dfCenterLat, double dfCenterLong,
    7379             :                                       double dfAzimuth,
    7380             :                                       double dfPseudoStdParallel1,
    7381             :                                       double dfScale, double dfFalseEasting,
    7382             :                                       double dfFalseNorthing)
    7383             : 
    7384             : {
    7385           6 :     TAKE_OPTIONAL_LOCK();
    7386             : 
    7387           3 :     return d->replaceConversionAndUnref(
    7388             :         proj_create_conversion_krovak_north_oriented(
    7389             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7390             :             dfPseudoStdParallel1, dfScale, dfFalseEasting, dfFalseNorthing,
    7391           6 :             nullptr, 0.0, nullptr, 0.0));
    7392             : }
    7393             : 
    7394             : /************************************************************************/
    7395             : /*                            OSRSetKrovak()                            */
    7396             : /************************************************************************/
    7397             : 
    7398           0 : OGRErr OSRSetKrovak(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7399             :                     double dfCenterLong, double dfAzimuth,
    7400             :                     double dfPseudoStdParallel1, double dfScale,
    7401             :                     double dfFalseEasting, double dfFalseNorthing)
    7402             : 
    7403             : {
    7404           0 :     VALIDATE_POINTER1(hSRS, "OSRSetKrovak", OGRERR_FAILURE);
    7405             : 
    7406           0 :     return ToPointer(hSRS)->SetKrovak(dfCenterLat, dfCenterLong, dfAzimuth,
    7407             :                                       dfPseudoStdParallel1, dfScale,
    7408           0 :                                       dfFalseEasting, dfFalseNorthing);
    7409             : }
    7410             : 
    7411             : /************************************************************************/
    7412             : /*                              SetLAEA()                               */
    7413             : /************************************************************************/
    7414             : 
    7415          17 : OGRErr OGRSpatialReference::SetLAEA(double dfCenterLat, double dfCenterLong,
    7416             :                                     double dfFalseEasting,
    7417             :                                     double dfFalseNorthing)
    7418             : 
    7419             : {
    7420          34 :     TAKE_OPTIONAL_LOCK();
    7421             : 
    7422          17 :     auto conv = proj_create_conversion_lambert_azimuthal_equal_area(
    7423             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7424             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    7425             : 
    7426          17 :     const char *pszName = nullptr;
    7427          17 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7428          17 :     CPLString osName = pszName ? pszName : "";
    7429             : 
    7430          17 :     d->refreshProjObj();
    7431             : 
    7432          17 :     d->demoteFromBoundCRS();
    7433             : 
    7434          17 :     auto cs = proj_create_cartesian_2D_cs(
    7435             :         d->getPROJContext(),
    7436          17 :         std::fabs(dfCenterLat - 90) < 1e-10 && dfCenterLong == 0
    7437             :             ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7438           0 :         : std::fabs(dfCenterLat - -90) < 1e-10 && dfCenterLong == 0
    7439          14 :             ? PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH
    7440             :             : PJ_CART2D_EASTING_NORTHING,
    7441          17 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7442             :     auto projCRS =
    7443          17 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7444          17 :                                   d->getGeodBaseCRS(), conv, cs);
    7445          17 :     proj_destroy(conv);
    7446          17 :     proj_destroy(cs);
    7447             : 
    7448          17 :     d->setPjCRS(projCRS);
    7449             : 
    7450          17 :     d->undoDemoteFromBoundCRS();
    7451             : 
    7452          34 :     return OGRERR_NONE;
    7453             : }
    7454             : 
    7455             : /************************************************************************/
    7456             : /*                             OSRSetLAEA()                             */
    7457             : /************************************************************************/
    7458             : 
    7459           0 : OGRErr OSRSetLAEA(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7460             :                   double dfCenterLong, double dfFalseEasting,
    7461             :                   double dfFalseNorthing)
    7462             : 
    7463             : {
    7464           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLAEA", OGRERR_FAILURE);
    7465             : 
    7466           0 :     return ToPointer(hSRS)->SetLAEA(dfCenterLat, dfCenterLong, dfFalseEasting,
    7467           0 :                                     dfFalseNorthing);
    7468             : }
    7469             : 
    7470             : /************************************************************************/
    7471             : /*                               SetLCC()                               */
    7472             : /************************************************************************/
    7473             : 
    7474         148 : OGRErr OGRSpatialReference::SetLCC(double dfStdP1, double dfStdP2,
    7475             :                                    double dfCenterLat, double dfCenterLong,
    7476             :                                    double dfFalseEasting,
    7477             :                                    double dfFalseNorthing)
    7478             : 
    7479             : {
    7480         296 :     TAKE_OPTIONAL_LOCK();
    7481             : 
    7482         148 :     return d->replaceConversionAndUnref(
    7483             :         proj_create_conversion_lambert_conic_conformal_2sp(
    7484             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7485         296 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7486             : }
    7487             : 
    7488             : /************************************************************************/
    7489             : /*                             OSRSetLCC()                              */
    7490             : /************************************************************************/
    7491             : 
    7492           1 : OGRErr OSRSetLCC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7493             :                  double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    7494             :                  double dfFalseNorthing)
    7495             : 
    7496             : {
    7497           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC", OGRERR_FAILURE);
    7498             : 
    7499           1 :     return ToPointer(hSRS)->SetLCC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7500           1 :                                    dfFalseEasting, dfFalseNorthing);
    7501             : }
    7502             : 
    7503             : /************************************************************************/
    7504             : /*                             SetLCC1SP()                              */
    7505             : /************************************************************************/
    7506             : 
    7507          10 : OGRErr OGRSpatialReference::SetLCC1SP(double dfCenterLat, double dfCenterLong,
    7508             :                                       double dfScale, double dfFalseEasting,
    7509             :                                       double dfFalseNorthing)
    7510             : 
    7511             : {
    7512          20 :     TAKE_OPTIONAL_LOCK();
    7513             : 
    7514          10 :     return d->replaceConversionAndUnref(
    7515             :         proj_create_conversion_lambert_conic_conformal_1sp(
    7516             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7517          20 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7518             : }
    7519             : 
    7520             : /************************************************************************/
    7521             : /*                            OSRSetLCC1SP()                            */
    7522             : /************************************************************************/
    7523             : 
    7524           0 : OGRErr OSRSetLCC1SP(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7525             :                     double dfCenterLong, double dfScale, double dfFalseEasting,
    7526             :                     double dfFalseNorthing)
    7527             : 
    7528             : {
    7529           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC1SP", OGRERR_FAILURE);
    7530             : 
    7531           0 :     return ToPointer(hSRS)->SetLCC1SP(dfCenterLat, dfCenterLong, dfScale,
    7532           0 :                                       dfFalseEasting, dfFalseNorthing);
    7533             : }
    7534             : 
    7535             : /************************************************************************/
    7536             : /*                              SetLCCB()                               */
    7537             : /************************************************************************/
    7538             : 
    7539           2 : OGRErr OGRSpatialReference::SetLCCB(double dfStdP1, double dfStdP2,
    7540             :                                     double dfCenterLat, double dfCenterLong,
    7541             :                                     double dfFalseEasting,
    7542             :                                     double dfFalseNorthing)
    7543             : 
    7544             : {
    7545           4 :     TAKE_OPTIONAL_LOCK();
    7546             : 
    7547           2 :     return d->replaceConversionAndUnref(
    7548             :         proj_create_conversion_lambert_conic_conformal_2sp_belgium(
    7549             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7550           4 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7551             : }
    7552             : 
    7553             : /************************************************************************/
    7554             : /*                             OSRSetLCCB()                             */
    7555             : /************************************************************************/
    7556             : 
    7557           0 : OGRErr OSRSetLCCB(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7558             :                   double dfCenterLat, double dfCenterLong,
    7559             :                   double dfFalseEasting, double dfFalseNorthing)
    7560             : 
    7561             : {
    7562           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCCB", OGRERR_FAILURE);
    7563             : 
    7564           0 :     return ToPointer(hSRS)->SetLCCB(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7565           0 :                                     dfFalseEasting, dfFalseNorthing);
    7566             : }
    7567             : 
    7568             : /************************************************************************/
    7569             : /*                               SetMC()                                */
    7570             : /************************************************************************/
    7571             : 
    7572           4 : OGRErr OGRSpatialReference::SetMC(double dfCenterLat, double dfCenterLong,
    7573             :                                   double dfFalseEasting, double dfFalseNorthing)
    7574             : 
    7575             : {
    7576           8 :     TAKE_OPTIONAL_LOCK();
    7577             : 
    7578             :     (void)dfCenterLat;  // ignored
    7579             : 
    7580           4 :     return d->replaceConversionAndUnref(
    7581             :         proj_create_conversion_miller_cylindrical(
    7582             :             d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7583           8 :             nullptr, 0, nullptr, 0));
    7584             : }
    7585             : 
    7586             : /************************************************************************/
    7587             : /*                              OSRSetMC()                              */
    7588             : /************************************************************************/
    7589             : 
    7590           0 : OGRErr OSRSetMC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7591             :                 double dfCenterLong, double dfFalseEasting,
    7592             :                 double dfFalseNorthing)
    7593             : 
    7594             : {
    7595           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMC", OGRERR_FAILURE);
    7596             : 
    7597           0 :     return ToPointer(hSRS)->SetMC(dfCenterLat, dfCenterLong, dfFalseEasting,
    7598           0 :                                   dfFalseNorthing);
    7599             : }
    7600             : 
    7601             : /************************************************************************/
    7602             : /*                            SetMercator()                             */
    7603             : /************************************************************************/
    7604             : 
    7605          59 : OGRErr OGRSpatialReference::SetMercator(double dfCenterLat, double dfCenterLong,
    7606             :                                         double dfScale, double dfFalseEasting,
    7607             :                                         double dfFalseNorthing)
    7608             : 
    7609             : {
    7610         118 :     TAKE_OPTIONAL_LOCK();
    7611             : 
    7612          59 :     if (dfCenterLat != 0.0 && dfScale == 1.0)
    7613             :     {
    7614             :         // Not sure this is correct, but this is how it has been used
    7615             :         // historically
    7616           0 :         return SetMercator2SP(dfCenterLat, 0.0, dfCenterLong, dfFalseEasting,
    7617           0 :                               dfFalseNorthing);
    7618             :     }
    7619          59 :     return d->replaceConversionAndUnref(
    7620             :         proj_create_conversion_mercator_variant_a(
    7621             :             d->getPROJContext(),
    7622             :             dfCenterLat,  // should be zero
    7623             :             dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0,
    7624          59 :             nullptr, 0));
    7625             : }
    7626             : 
    7627             : /************************************************************************/
    7628             : /*                           OSRSetMercator()                           */
    7629             : /************************************************************************/
    7630             : 
    7631           2 : OGRErr OSRSetMercator(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7632             :                       double dfCenterLong, double dfScale,
    7633             :                       double dfFalseEasting, double dfFalseNorthing)
    7634             : 
    7635             : {
    7636           2 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator", OGRERR_FAILURE);
    7637             : 
    7638           2 :     return ToPointer(hSRS)->SetMercator(dfCenterLat, dfCenterLong, dfScale,
    7639           2 :                                         dfFalseEasting, dfFalseNorthing);
    7640             : }
    7641             : 
    7642             : /************************************************************************/
    7643             : /*                           SetMercator2SP()                           */
    7644             : /************************************************************************/
    7645             : 
    7646          31 : OGRErr OGRSpatialReference::SetMercator2SP(double dfStdP1, double dfCenterLat,
    7647             :                                            double dfCenterLong,
    7648             :                                            double dfFalseEasting,
    7649             :                                            double dfFalseNorthing)
    7650             : 
    7651             : {
    7652          31 :     if (dfCenterLat == 0.0)
    7653             :     {
    7654          30 :         return d->replaceConversionAndUnref(
    7655             :             proj_create_conversion_mercator_variant_b(
    7656             :                 d->getPROJContext(), dfStdP1, dfCenterLong, dfFalseEasting,
    7657          30 :                 dfFalseNorthing, nullptr, 0, nullptr, 0));
    7658             :     }
    7659             : 
    7660           1 :     TAKE_OPTIONAL_LOCK();
    7661             : 
    7662           1 :     SetProjection(SRS_PT_MERCATOR_2SP);
    7663             : 
    7664           1 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdP1);
    7665           1 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    7666           1 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    7667           1 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    7668           1 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    7669             : 
    7670           1 :     return OGRERR_NONE;
    7671             : }
    7672             : 
    7673             : /************************************************************************/
    7674             : /*                         OSRSetMercator2SP()                          */
    7675             : /************************************************************************/
    7676             : 
    7677           1 : OGRErr OSRSetMercator2SP(OGRSpatialReferenceH hSRS, double dfStdP1,
    7678             :                          double dfCenterLat, double dfCenterLong,
    7679             :                          double dfFalseEasting, double dfFalseNorthing)
    7680             : 
    7681             : {
    7682           1 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator2SP", OGRERR_FAILURE);
    7683             : 
    7684           1 :     return ToPointer(hSRS)->SetMercator2SP(dfStdP1, dfCenterLat, dfCenterLong,
    7685           1 :                                            dfFalseEasting, dfFalseNorthing);
    7686             : }
    7687             : 
    7688             : /************************************************************************/
    7689             : /*                            SetMollweide()                            */
    7690             : /************************************************************************/
    7691             : 
    7692           3 : OGRErr OGRSpatialReference::SetMollweide(double dfCentralMeridian,
    7693             :                                          double dfFalseEasting,
    7694             :                                          double dfFalseNorthing)
    7695             : 
    7696             : {
    7697           6 :     TAKE_OPTIONAL_LOCK();
    7698             : 
    7699           3 :     return d->replaceConversionAndUnref(proj_create_conversion_mollweide(
    7700             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    7701           6 :         nullptr, 0, nullptr, 0));
    7702             : }
    7703             : 
    7704             : /************************************************************************/
    7705             : /*                          OSRSetMollweide()                           */
    7706             : /************************************************************************/
    7707             : 
    7708           0 : OGRErr OSRSetMollweide(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7709             :                        double dfFalseEasting, double dfFalseNorthing)
    7710             : 
    7711             : {
    7712           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMollweide", OGRERR_FAILURE);
    7713             : 
    7714           0 :     return ToPointer(hSRS)->SetMollweide(dfCentralMeridian, dfFalseEasting,
    7715           0 :                                          dfFalseNorthing);
    7716             : }
    7717             : 
    7718             : /************************************************************************/
    7719             : /*                              SetNZMG()                               */
    7720             : /************************************************************************/
    7721             : 
    7722           7 : OGRErr OGRSpatialReference::SetNZMG(double dfCenterLat, double dfCenterLong,
    7723             :                                     double dfFalseEasting,
    7724             :                                     double dfFalseNorthing)
    7725             : 
    7726             : {
    7727          14 :     TAKE_OPTIONAL_LOCK();
    7728             : 
    7729           7 :     return d->replaceConversionAndUnref(
    7730             :         proj_create_conversion_new_zealand_mapping_grid(
    7731             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7732          14 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7733             : }
    7734             : 
    7735             : /************************************************************************/
    7736             : /*                             OSRSetNZMG()                             */
    7737             : /************************************************************************/
    7738             : 
    7739           0 : OGRErr OSRSetNZMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7740             :                   double dfCenterLong, double dfFalseEasting,
    7741             :                   double dfFalseNorthing)
    7742             : 
    7743             : {
    7744           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNZMG", OGRERR_FAILURE);
    7745             : 
    7746           0 :     return ToPointer(hSRS)->SetNZMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    7747           0 :                                     dfFalseNorthing);
    7748             : }
    7749             : 
    7750             : /************************************************************************/
    7751             : /*                               SetOS()                                */
    7752             : /************************************************************************/
    7753             : 
    7754           6 : OGRErr OGRSpatialReference::SetOS(double dfOriginLat, double dfCMeridian,
    7755             :                                   double dfScale, double dfFalseEasting,
    7756             :                                   double dfFalseNorthing)
    7757             : 
    7758             : {
    7759          12 :     TAKE_OPTIONAL_LOCK();
    7760             : 
    7761           6 :     return d->replaceConversionAndUnref(
    7762             :         proj_create_conversion_oblique_stereographic(
    7763             :             d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale,
    7764          12 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7765             : }
    7766             : 
    7767             : /************************************************************************/
    7768             : /*                              OSRSetOS()                              */
    7769             : /************************************************************************/
    7770             : 
    7771           0 : OGRErr OSRSetOS(OGRSpatialReferenceH hSRS, double dfOriginLat,
    7772             :                 double dfCMeridian, double dfScale, double dfFalseEasting,
    7773             :                 double dfFalseNorthing)
    7774             : 
    7775             : {
    7776           0 :     VALIDATE_POINTER1(hSRS, "OSRSetOS", OGRERR_FAILURE);
    7777             : 
    7778           0 :     return ToPointer(hSRS)->SetOS(dfOriginLat, dfCMeridian, dfScale,
    7779           0 :                                   dfFalseEasting, dfFalseNorthing);
    7780             : }
    7781             : 
    7782             : /************************************************************************/
    7783             : /*                          SetOrthographic()                           */
    7784             : /************************************************************************/
    7785             : 
    7786           7 : OGRErr OGRSpatialReference::SetOrthographic(double dfCenterLat,
    7787             :                                             double dfCenterLong,
    7788             :                                             double dfFalseEasting,
    7789             :                                             double dfFalseNorthing)
    7790             : 
    7791             : {
    7792          14 :     TAKE_OPTIONAL_LOCK();
    7793             : 
    7794           7 :     return d->replaceConversionAndUnref(proj_create_conversion_orthographic(
    7795             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7796          14 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    7797             : }
    7798             : 
    7799             : /************************************************************************/
    7800             : /*                         OSRSetOrthographic()                         */
    7801             : /************************************************************************/
    7802             : 
    7803           1 : OGRErr OSRSetOrthographic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7804             :                           double dfCenterLong, double dfFalseEasting,
    7805             :                           double dfFalseNorthing)
    7806             : 
    7807             : {
    7808           1 :     VALIDATE_POINTER1(hSRS, "OSRSetOrthographic", OGRERR_FAILURE);
    7809             : 
    7810           1 :     return ToPointer(hSRS)->SetOrthographic(dfCenterLat, dfCenterLong,
    7811           1 :                                             dfFalseEasting, dfFalseNorthing);
    7812             : }
    7813             : 
    7814             : /************************************************************************/
    7815             : /*                            SetPolyconic()                            */
    7816             : /************************************************************************/
    7817             : 
    7818           7 : OGRErr OGRSpatialReference::SetPolyconic(double dfCenterLat,
    7819             :                                          double dfCenterLong,
    7820             :                                          double dfFalseEasting,
    7821             :                                          double dfFalseNorthing)
    7822             : 
    7823             : {
    7824          14 :     TAKE_OPTIONAL_LOCK();
    7825             : 
    7826             :     // note: it seems that by some definitions this should include a
    7827             :     //       scale_factor parameter.
    7828           7 :     return d->replaceConversionAndUnref(
    7829             :         proj_create_conversion_american_polyconic(
    7830             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7831          14 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7832             : }
    7833             : 
    7834             : /************************************************************************/
    7835             : /*                          OSRSetPolyconic()                           */
    7836             : /************************************************************************/
    7837             : 
    7838           0 : OGRErr OSRSetPolyconic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7839             :                        double dfCenterLong, double dfFalseEasting,
    7840             :                        double dfFalseNorthing)
    7841             : 
    7842             : {
    7843           0 :     VALIDATE_POINTER1(hSRS, "OSRSetPolyconic", OGRERR_FAILURE);
    7844             : 
    7845           0 :     return ToPointer(hSRS)->SetPolyconic(dfCenterLat, dfCenterLong,
    7846           0 :                                          dfFalseEasting, dfFalseNorthing);
    7847             : }
    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          30 : OGRErr OGRSpatialReference::SetPS(double dfCenterLat, double dfCenterLong,
    7863             :                                   double dfScale, double dfFalseEasting,
    7864             :                                   double dfFalseNorthing)
    7865             : 
    7866             : {
    7867          60 :     TAKE_OPTIONAL_LOCK();
    7868             : 
    7869             :     PJ *conv;
    7870          30 :     if (dfScale == 1.0 && std::abs(std::abs(dfCenterLat) - 90) > 1e-8)
    7871             :     {
    7872          20 :         conv = proj_create_conversion_polar_stereographic_variant_b(
    7873             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7874             :             dfFalseNorthing, nullptr, 0, nullptr, 0);
    7875             :     }
    7876             :     else
    7877             :     {
    7878          10 :         conv = proj_create_conversion_polar_stereographic_variant_a(
    7879             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7880             :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0);
    7881             :     }
    7882             : 
    7883          30 :     const char *pszName = nullptr;
    7884          30 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7885          30 :     CPLString osName = pszName ? pszName : "";
    7886             : 
    7887          30 :     d->refreshProjObj();
    7888             : 
    7889          30 :     d->demoteFromBoundCRS();
    7890             : 
    7891          30 :     auto cs = proj_create_cartesian_2D_cs(
    7892             :         d->getPROJContext(),
    7893             :         dfCenterLat > 0 ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7894             :                         : PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH,
    7895          30 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7896             :     auto projCRS =
    7897          30 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7898          30 :                                   d->getGeodBaseCRS(), conv, cs);
    7899          30 :     proj_destroy(conv);
    7900          30 :     proj_destroy(cs);
    7901             : 
    7902          30 :     d->setPjCRS(projCRS);
    7903             : 
    7904          30 :     d->undoDemoteFromBoundCRS();
    7905             : 
    7906          60 :     return OGRERR_NONE;
    7907             : }
    7908             : 
    7909             : /************************************************************************/
    7910             : /*                              OSRSetPS()                              */
    7911             : /************************************************************************/
    7912             : 
    7913           1 : OGRErr OSRSetPS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7914             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    7915             :                 double dfFalseNorthing)
    7916             : 
    7917             : {
    7918           1 :     VALIDATE_POINTER1(hSRS, "OSRSetPS", OGRERR_FAILURE);
    7919             : 
    7920           1 :     return ToPointer(hSRS)->SetPS(dfCenterLat, dfCenterLong, dfScale,
    7921           1 :                                   dfFalseEasting, dfFalseNorthing);
    7922             : }
    7923             : 
    7924             : /************************************************************************/
    7925             : /*                            SetRobinson()                             */
    7926             : /************************************************************************/
    7927             : 
    7928           4 : OGRErr OGRSpatialReference::SetRobinson(double dfCenterLong,
    7929             :                                         double dfFalseEasting,
    7930             :                                         double dfFalseNorthing)
    7931             : 
    7932             : {
    7933           8 :     TAKE_OPTIONAL_LOCK();
    7934             : 
    7935           4 :     return d->replaceConversionAndUnref(proj_create_conversion_robinson(
    7936             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7937           8 :         nullptr, 0, nullptr, 0));
    7938             : }
    7939             : 
    7940             : /************************************************************************/
    7941             : /*                           OSRSetRobinson()                           */
    7942             : /************************************************************************/
    7943             : 
    7944           0 : OGRErr OSRSetRobinson(OGRSpatialReferenceH hSRS, double dfCenterLong,
    7945             :                       double dfFalseEasting, double dfFalseNorthing)
    7946             : 
    7947             : {
    7948           0 :     VALIDATE_POINTER1(hSRS, "OSRSetRobinson", OGRERR_FAILURE);
    7949             : 
    7950           0 :     return ToPointer(hSRS)->SetRobinson(dfCenterLong, dfFalseEasting,
    7951           0 :                                         dfFalseNorthing);
    7952             : }
    7953             : 
    7954             : /************************************************************************/
    7955             : /*                           SetSinusoidal()                            */
    7956             : /************************************************************************/
    7957             : 
    7958          35 : OGRErr OGRSpatialReference::SetSinusoidal(double dfCenterLong,
    7959             :                                           double dfFalseEasting,
    7960             :                                           double dfFalseNorthing)
    7961             : 
    7962             : {
    7963          70 :     TAKE_OPTIONAL_LOCK();
    7964             : 
    7965          35 :     return d->replaceConversionAndUnref(proj_create_conversion_sinusoidal(
    7966             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7967          70 :         nullptr, 0, nullptr, 0));
    7968             : }
    7969             : 
    7970             : /************************************************************************/
    7971             : /*                          OSRSetSinusoidal()                          */
    7972             : /************************************************************************/
    7973             : 
    7974           1 : OGRErr OSRSetSinusoidal(OGRSpatialReferenceH hSRS, double dfCenterLong,
    7975             :                         double dfFalseEasting, double dfFalseNorthing)
    7976             : 
    7977             : {
    7978           1 :     VALIDATE_POINTER1(hSRS, "OSRSetSinusoidal", OGRERR_FAILURE);
    7979             : 
    7980           1 :     return ToPointer(hSRS)->SetSinusoidal(dfCenterLong, dfFalseEasting,
    7981           1 :                                           dfFalseNorthing);
    7982             : }
    7983             : 
    7984             : /************************************************************************/
    7985             : /*                          SetStereographic()                          */
    7986             : /************************************************************************/
    7987             : 
    7988           2 : OGRErr OGRSpatialReference::SetStereographic(double dfOriginLat,
    7989             :                                              double dfCMeridian, double dfScale,
    7990             :                                              double dfFalseEasting,
    7991             :                                              double dfFalseNorthing)
    7992             : 
    7993             : {
    7994           4 :     TAKE_OPTIONAL_LOCK();
    7995             : 
    7996           2 :     return d->replaceConversionAndUnref(proj_create_conversion_stereographic(
    7997             :         d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale, dfFalseEasting,
    7998           4 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    7999             : }
    8000             : 
    8001             : /************************************************************************/
    8002             : /*                        OSRSetStereographic()                         */
    8003             : /************************************************************************/
    8004             : 
    8005           0 : OGRErr OSRSetStereographic(OGRSpatialReferenceH hSRS, double dfOriginLat,
    8006             :                            double dfCMeridian, double dfScale,
    8007             :                            double dfFalseEasting, double dfFalseNorthing)
    8008             : 
    8009             : {
    8010           0 :     VALIDATE_POINTER1(hSRS, "OSRSetStereographic", OGRERR_FAILURE);
    8011             : 
    8012           0 :     return ToPointer(hSRS)->SetStereographic(dfOriginLat, dfCMeridian, dfScale,
    8013           0 :                                              dfFalseEasting, dfFalseNorthing);
    8014             : }
    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           2 : OGRErr OGRSpatialReference::SetSOC(double dfLatitudeOfOrigin,
    8027             :                                    double dfCentralMeridian,
    8028             :                                    double dfFalseEasting,
    8029             :                                    double dfFalseNorthing)
    8030             : 
    8031             : {
    8032           4 :     TAKE_OPTIONAL_LOCK();
    8033             : 
    8034           2 :     return d->replaceConversionAndUnref(
    8035             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    8036             :             d->getPROJContext(), dfLatitudeOfOrigin, dfCentralMeridian, 90.0,
    8037             :             90.0, 1.0, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    8038           4 :             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             : }
    8049             : 
    8050             : /************************************************************************/
    8051             : /*                             OSRSetSOC()                              */
    8052             : /************************************************************************/
    8053             : 
    8054           0 : OGRErr OSRSetSOC(OGRSpatialReferenceH hSRS, double dfLatitudeOfOrigin,
    8055             :                  double dfCentralMeridian, double dfFalseEasting,
    8056             :                  double dfFalseNorthing)
    8057             : 
    8058             : {
    8059           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSOC", OGRERR_FAILURE);
    8060             : 
    8061           0 :     return ToPointer(hSRS)->SetSOC(dfLatitudeOfOrigin, dfCentralMeridian,
    8062           0 :                                    dfFalseEasting, dfFalseNorthing);
    8063             : }
    8064             : 
    8065             : /************************************************************************/
    8066             : /*                               SetVDG()                               */
    8067             : /************************************************************************/
    8068             : 
    8069           2 : OGRErr OGRSpatialReference::SetVDG(double dfCMeridian, double dfFalseEasting,
    8070             :                                    double dfFalseNorthing)
    8071             : 
    8072             : {
    8073           4 :     TAKE_OPTIONAL_LOCK();
    8074             : 
    8075           2 :     return d->replaceConversionAndUnref(proj_create_conversion_van_der_grinten(
    8076             :         d->getPROJContext(), dfCMeridian, dfFalseEasting, dfFalseNorthing,
    8077           4 :         nullptr, 0, nullptr, 0));
    8078             : }
    8079             : 
    8080             : /************************************************************************/
    8081             : /*                             OSRSetVDG()                              */
    8082             : /************************************************************************/
    8083             : 
    8084           0 : OGRErr OSRSetVDG(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    8085             :                  double dfFalseEasting, double dfFalseNorthing)
    8086             : 
    8087             : {
    8088           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVDG", OGRERR_FAILURE);
    8089             : 
    8090           0 :     return ToPointer(hSRS)->SetVDG(dfCentralMeridian, dfFalseEasting,
    8091           0 :                                    dfFalseNorthing);
    8092             : }
    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         313 : OGRErr OGRSpatialReference::SetUTM(int nZone, int bNorth)
    8117             : 
    8118             : {
    8119         626 :     TAKE_OPTIONAL_LOCK();
    8120             : 
    8121         313 :     if (nZone < 0 || nZone > 60)
    8122             :     {
    8123           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid zone: %d", nZone);
    8124           0 :         return OGRERR_FAILURE;
    8125             :     }
    8126             : 
    8127         313 :     return d->replaceConversionAndUnref(
    8128         313 :         proj_create_conversion_utm(d->getPROJContext(), nZone, bNorth));
    8129             : }
    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          19 : OGRErr OSRSetUTM(OGRSpatialReferenceH hSRS, int nZone, int bNorth)
    8141             : 
    8142             : {
    8143          19 :     VALIDATE_POINTER1(hSRS, "OSRSetUTM", OGRERR_FAILURE);
    8144             : 
    8145          19 :     return ToPointer(hSRS)->SetUTM(nZone, bNorth);
    8146             : }
    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         610 : int OGRSpatialReference::GetUTMZone(int *pbNorth) const
    8170             : 
    8171             : {
    8172        1220 :     TAKE_OPTIONAL_LOCK();
    8173             : 
    8174         610 :     if (IsProjected() && GetAxesCount() == 3)
    8175             :     {
    8176           1 :         OGRSpatialReference *poSRSTmp = Clone();
    8177           1 :         poSRSTmp->DemoteTo2D(nullptr);
    8178           1 :         const int nZone = poSRSTmp->GetUTMZone(pbNorth);
    8179           1 :         delete poSRSTmp;
    8180           1 :         return nZone;
    8181             :     }
    8182             : 
    8183         609 :     const char *pszProjection = GetAttrValue("PROJECTION");
    8184             : 
    8185         609 :     if (pszProjection == nullptr ||
    8186         529 :         !EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    8187         278 :         return 0;
    8188             : 
    8189         331 :     if (GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) != 0.0)
    8190           5 :         return 0;
    8191             : 
    8192         326 :     if (GetProjParm(SRS_PP_SCALE_FACTOR, 1.0) != 0.9996)
    8193          15 :         return 0;
    8194             : 
    8195         311 :     if (fabs(GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0) - 500000.0) > 0.001)
    8196           6 :         return 0;
    8197             : 
    8198         305 :     const double dfFalseNorthing = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    8199             : 
    8200         305 :     if (dfFalseNorthing != 0.0 && fabs(dfFalseNorthing - 10000000.0) > 0.001)
    8201           0 :         return 0;
    8202             : 
    8203         305 :     if (pbNorth != nullptr)
    8204         239 :         *pbNorth = (dfFalseNorthing == 0);
    8205             : 
    8206             :     const double dfCentralMeridian =
    8207         305 :         GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    8208         305 :     const double dfZone = (dfCentralMeridian + 186.0) / 6.0;
    8209             : 
    8210         610 :     if (dfCentralMeridian < -177.00001 || dfCentralMeridian > 177.000001 ||
    8211         915 :         std::isnan(dfZone) ||
    8212         305 :         std::abs(dfZone - static_cast<int>(dfZone) - 0.5) > 0.00001)
    8213           0 :         return 0;
    8214             : 
    8215         305 :     return static_cast<int>(dfZone);
    8216             : }
    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           6 : int OSRGetUTMZone(OGRSpatialReferenceH hSRS, int *pbNorth)
    8228             : 
    8229             : {
    8230           6 :     VALIDATE_POINTER1(hSRS, "OSRGetUTMZone", 0);
    8231             : 
    8232           6 :     return ToPointer(hSRS)->GetUTMZone(pbNorth);
    8233             : }
    8234             : 
    8235             : /************************************************************************/
    8236             : /*                             SetWagner()                              */
    8237             : /************************************************************************/
    8238             : 
    8239           0 : OGRErr OGRSpatialReference::SetWagner(int nVariation,  // 1--7.
    8240             :                                       double dfCenterLat, double dfFalseEasting,
    8241             :                                       double dfFalseNorthing)
    8242             : 
    8243             : {
    8244           0 :     TAKE_OPTIONAL_LOCK();
    8245             : 
    8246             :     PJ *conv;
    8247           0 :     if (nVariation == 1)
    8248             :     {
    8249           0 :         conv = proj_create_conversion_wagner_i(d->getPROJContext(), 0.0,
    8250             :                                                dfFalseEasting, dfFalseNorthing,
    8251             :                                                nullptr, 0.0, nullptr, 0.0);
    8252             :     }
    8253           0 :     else if (nVariation == 2)
    8254             :     {
    8255           0 :         conv = proj_create_conversion_wagner_ii(d->getPROJContext(), 0.0,
    8256             :                                                 dfFalseEasting, dfFalseNorthing,
    8257             :                                                 nullptr, 0.0, nullptr, 0.0);
    8258             :     }
    8259           0 :     else if (nVariation == 3)
    8260             :     {
    8261           0 :         conv = proj_create_conversion_wagner_iii(
    8262             :             d->getPROJContext(), dfCenterLat, 0.0, dfFalseEasting,
    8263             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    8264             :     }
    8265           0 :     else if (nVariation == 4)
    8266             :     {
    8267           0 :         conv = proj_create_conversion_wagner_iv(d->getPROJContext(), 0.0,
    8268             :                                                 dfFalseEasting, dfFalseNorthing,
    8269             :                                                 nullptr, 0.0, nullptr, 0.0);
    8270             :     }
    8271           0 :     else if (nVariation == 5)
    8272             :     {
    8273           0 :         conv = proj_create_conversion_wagner_v(d->getPROJContext(), 0.0,
    8274             :                                                dfFalseEasting, dfFalseNorthing,
    8275             :                                                nullptr, 0.0, nullptr, 0.0);
    8276             :     }
    8277           0 :     else if (nVariation == 6)
    8278             :     {
    8279           0 :         conv = proj_create_conversion_wagner_vi(d->getPROJContext(), 0.0,
    8280             :                                                 dfFalseEasting, dfFalseNorthing,
    8281             :                                                 nullptr, 0.0, nullptr, 0.0);
    8282             :     }
    8283           0 :     else if (nVariation == 7)
    8284             :     {
    8285           0 :         conv = proj_create_conversion_wagner_vii(
    8286             :             d->getPROJContext(), 0.0, dfFalseEasting, dfFalseNorthing, nullptr,
    8287             :             0.0, nullptr, 0.0);
    8288             :     }
    8289             :     else
    8290             :     {
    8291           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8292             :                  "Unsupported Wagner variation (%d).", nVariation);
    8293           0 :         return OGRERR_UNSUPPORTED_SRS;
    8294             :     }
    8295             : 
    8296           0 :     return d->replaceConversionAndUnref(conv);
    8297             : }
    8298             : 
    8299             : /************************************************************************/
    8300             : /*                            OSRSetWagner()                            */
    8301             : /************************************************************************/
    8302             : 
    8303           0 : OGRErr OSRSetWagner(OGRSpatialReferenceH hSRS, int nVariation,
    8304             :                     double dfCenterLat, double dfFalseEasting,
    8305             :                     double dfFalseNorthing)
    8306             : 
    8307             : {
    8308           0 :     VALIDATE_POINTER1(hSRS, "OSRSetWagner", OGRERR_FAILURE);
    8309             : 
    8310           0 :     return ToPointer(hSRS)->SetWagner(nVariation, dfCenterLat, dfFalseEasting,
    8311           0 :                                       dfFalseNorthing);
    8312             : }
    8313             : 
    8314             : /************************************************************************/
    8315             : /*                               SetQSC()                               */
    8316             : /************************************************************************/
    8317             : 
    8318           0 : OGRErr OGRSpatialReference::SetQSC(double dfCenterLat, double dfCenterLong)
    8319             : {
    8320           0 :     TAKE_OPTIONAL_LOCK();
    8321             : 
    8322           0 :     return d->replaceConversionAndUnref(
    8323             :         proj_create_conversion_quadrilateralized_spherical_cube(
    8324             :             d->getPROJContext(), dfCenterLat, dfCenterLong, 0.0, 0.0, nullptr,
    8325           0 :             0, nullptr, 0));
    8326             : }
    8327             : 
    8328             : /************************************************************************/
    8329             : /*                             OSRSetQSC()                              */
    8330             : /************************************************************************/
    8331             : 
    8332           0 : OGRErr OSRSetQSC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    8333             :                  double dfCenterLong)
    8334             : 
    8335             : {
    8336           0 :     VALIDATE_POINTER1(hSRS, "OSRSetQSC", OGRERR_FAILURE);
    8337             : 
    8338           0 :     return ToPointer(hSRS)->SetQSC(dfCenterLat, dfCenterLong);
    8339             : }
    8340             : 
    8341             : /************************************************************************/
    8342             : /*                               SetSCH()                               */
    8343             : /************************************************************************/
    8344             : 
    8345           0 : OGRErr OGRSpatialReference::SetSCH(double dfPegLat, double dfPegLong,
    8346             :                                    double dfPegHeading, double dfPegHgt)
    8347             : 
    8348             : {
    8349           0 :     TAKE_OPTIONAL_LOCK();
    8350             : 
    8351           0 :     return d->replaceConversionAndUnref(
    8352             :         proj_create_conversion_spherical_cross_track_height(
    8353             :             d->getPROJContext(), dfPegLat, dfPegLong, dfPegHeading, dfPegHgt,
    8354           0 :             nullptr, 0, nullptr, 0));
    8355             : }
    8356             : 
    8357             : /************************************************************************/
    8358             : /*                             OSRSetSCH()                              */
    8359             : /************************************************************************/
    8360             : 
    8361           0 : OGRErr OSRSetSCH(OGRSpatialReferenceH hSRS, double dfPegLat, double dfPegLong,
    8362             :                  double dfPegHeading, double dfPegHgt)
    8363             : 
    8364             : {
    8365           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSCH", OGRERR_FAILURE);
    8366             : 
    8367           0 :     return ToPointer(hSRS)->SetSCH(dfPegLat, dfPegLong, dfPegHeading, dfPegHgt);
    8368             : }
    8369             : 
    8370             : /************************************************************************/
    8371             : /*                       SetVerticalPerspective()                       */
    8372             : /************************************************************************/
    8373             : 
    8374           3 : OGRErr OGRSpatialReference::SetVerticalPerspective(
    8375             :     double dfTopoOriginLat, double dfTopoOriginLon, double dfTopoOriginHeight,
    8376             :     double dfViewPointHeight, double dfFalseEasting, double dfFalseNorthing)
    8377             : {
    8378           6 :     TAKE_OPTIONAL_LOCK();
    8379             : 
    8380           3 :     return d->replaceConversionAndUnref(
    8381             :         proj_create_conversion_vertical_perspective(
    8382             :             d->getPROJContext(), dfTopoOriginLat, dfTopoOriginLon,
    8383             :             dfTopoOriginHeight, dfViewPointHeight, dfFalseEasting,
    8384           6 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    8385             : }
    8386             : 
    8387             : /************************************************************************/
    8388             : /*                     OSRSetVerticalPerspective()                      */
    8389             : /************************************************************************/
    8390             : 
    8391           1 : OGRErr OSRSetVerticalPerspective(OGRSpatialReferenceH hSRS,
    8392             :                                  double dfTopoOriginLat, double dfTopoOriginLon,
    8393             :                                  double dfTopoOriginHeight,
    8394             :                                  double dfViewPointHeight,
    8395             :                                  double dfFalseEasting, double dfFalseNorthing)
    8396             : 
    8397             : {
    8398           1 :     VALIDATE_POINTER1(hSRS, "OSRSetVerticalPerspective", OGRERR_FAILURE);
    8399             : 
    8400           1 :     return ToPointer(hSRS)->SetVerticalPerspective(
    8401             :         dfTopoOriginLat, dfTopoOriginLon, dfTopoOriginHeight, dfViewPointHeight,
    8402           1 :         dfFalseEasting, dfFalseNorthing);
    8403             : }
    8404             : 
    8405             : /************************************************************************/
    8406             : /*          SetDerivedGeogCRSWithPoleRotationGRIBConvention()           */
    8407             : /************************************************************************/
    8408             : 
    8409           2 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationGRIBConvention(
    8410             :     const char *pszCRSName, double dfSouthPoleLat, double dfSouthPoleLon,
    8411             :     double dfAxisRotation)
    8412             : {
    8413           4 :     TAKE_OPTIONAL_LOCK();
    8414             : 
    8415           2 :     d->refreshProjObj();
    8416           2 :     if (!d->m_pj_crs)
    8417           0 :         return OGRERR_FAILURE;
    8418           2 :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8419           0 :         return OGRERR_FAILURE;
    8420           2 :     auto ctxt = d->getPROJContext();
    8421           2 :     auto conv = proj_create_conversion_pole_rotation_grib_convention(
    8422             :         ctxt, dfSouthPoleLat, dfSouthPoleLon, dfAxisRotation, nullptr, 0);
    8423           2 :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8424           4 :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8425           2 :                                                    d->m_pj_crs, conv, cs));
    8426           2 :     proj_destroy(conv);
    8427           2 :     proj_destroy(cs);
    8428           2 :     return OGRERR_NONE;
    8429             : }
    8430             : 
    8431             : /************************************************************************/
    8432             : /*        SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention()         */
    8433             : /************************************************************************/
    8434             : 
    8435           3 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention(
    8436             :     const char *pszCRSName, double dfGridNorthPoleLat,
    8437             :     double dfGridNorthPoleLon, double dfNorthPoleGridLon)
    8438             : {
    8439           3 :     TAKE_OPTIONAL_LOCK();
    8440             : 
    8441             : #if PROJ_VERSION_MAJOR > 8 ||                                                  \
    8442             :     (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR >= 2)
    8443             :     d->refreshProjObj();
    8444             :     if (!d->m_pj_crs)
    8445             :         return OGRERR_FAILURE;
    8446             :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8447             :         return OGRERR_FAILURE;
    8448             :     auto ctxt = d->getPROJContext();
    8449             :     auto conv = proj_create_conversion_pole_rotation_netcdf_cf_convention(
    8450             :         ctxt, dfGridNorthPoleLat, dfGridNorthPoleLon, dfNorthPoleGridLon,
    8451             :         nullptr, 0);
    8452             :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8453             :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8454             :                                                    d->m_pj_crs, conv, cs));
    8455             :     proj_destroy(conv);
    8456             :     proj_destroy(cs);
    8457             :     return OGRERR_NONE;
    8458             : #else
    8459             :     (void)pszCRSName;
    8460           3 :     SetProjection("Rotated_pole");
    8461           3 :     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           6 :     return OGRERR_NONE;
    8471             : #endif
    8472             : }
    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       12436 : OGRErr OGRSpatialReference::SetAuthority(const char *pszTargetKey,
    8494             :                                          const char *pszAuthority, int nCode)
    8495             : 
    8496             : {
    8497       24872 :     TAKE_OPTIONAL_LOCK();
    8498             : 
    8499       12436 :     d->refreshProjObj();
    8500       12436 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8501             : 
    8502       12436 :     if (pszTargetKey == nullptr)
    8503             :     {
    8504         263 :         if (!d->m_pj_crs)
    8505           0 :             return OGRERR_FAILURE;
    8506         263 :         CPLString osCode;
    8507         263 :         osCode.Printf("%d", nCode);
    8508         263 :         d->demoteFromBoundCRS();
    8509         263 :         d->setPjCRS(proj_alter_id(d->getPROJContext(), d->m_pj_crs,
    8510             :                                   pszAuthority, osCode.c_str()));
    8511         263 :         d->undoDemoteFromBoundCRS();
    8512         263 :         return OGRERR_NONE;
    8513             :     }
    8514             : 
    8515       12173 :     d->demoteFromBoundCRS();
    8516       12173 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS && EQUAL(pszTargetKey, "GEOGCS"))
    8517             :     {
    8518        4025 :         CPLString osCode;
    8519        4025 :         osCode.Printf("%d", nCode);
    8520             :         auto newGeogCRS =
    8521        4025 :             proj_alter_id(d->getPROJContext(), d->getGeodBaseCRS(),
    8522             :                           pszAuthority, osCode.c_str());
    8523             : 
    8524             :         auto conv =
    8525        4025 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    8526             : 
    8527        4025 :         auto projCRS = proj_create_projected_crs(
    8528             :             d->getPROJContext(), d->getProjCRSName(), newGeogCRS, conv,
    8529        4025 :             d->getProjCRSCoordSys());
    8530             : 
    8531             :         // Preserve existing id on the PROJCRS
    8532        4025 :         const char *pszProjCRSAuthName = proj_get_id_auth_name(d->m_pj_crs, 0);
    8533        4025 :         const char *pszProjCRSCode = proj_get_id_code(d->m_pj_crs, 0);
    8534        4025 :         if (pszProjCRSAuthName && pszProjCRSCode)
    8535             :         {
    8536             :             auto projCRSWithId =
    8537           0 :                 proj_alter_id(d->getPROJContext(), projCRS, pszProjCRSAuthName,
    8538             :                               pszProjCRSCode);
    8539           0 :             proj_destroy(projCRS);
    8540           0 :             projCRS = projCRSWithId;
    8541             :         }
    8542             : 
    8543        4025 :         proj_destroy(newGeogCRS);
    8544        4025 :         proj_destroy(conv);
    8545             : 
    8546        4025 :         d->setPjCRS(projCRS);
    8547        4025 :         d->undoDemoteFromBoundCRS();
    8548        4025 :         return OGRERR_NONE;
    8549             :     }
    8550        8148 :     d->undoDemoteFromBoundCRS();
    8551             : 
    8552             :     /* -------------------------------------------------------------------- */
    8553             :     /*      Find the node below which the authority should be put.          */
    8554             :     /* -------------------------------------------------------------------- */
    8555        8148 :     OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8556             : 
    8557        8148 :     if (poNode == nullptr)
    8558           0 :         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        8148 :     int iOldChild = poNode->FindChild("AUTHORITY");
    8565        8148 :     if (iOldChild != -1)
    8566           5 :         poNode->DestroyChild(iOldChild);
    8567             : 
    8568             :     /* -------------------------------------------------------------------- */
    8569             :     /*      Create a new authority node.                                    */
    8570             :     /* -------------------------------------------------------------------- */
    8571        8148 :     char szCode[32] = {};
    8572             : 
    8573        8148 :     snprintf(szCode, sizeof(szCode), "%d", nCode);
    8574             : 
    8575        8148 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("AUTHORITY");
    8576        8148 :     poAuthNode->AddChild(new OGR_SRSNode(pszAuthority));
    8577        8148 :     poAuthNode->AddChild(new OGR_SRSNode(szCode));
    8578             : 
    8579        8148 :     poNode->AddChild(poAuthNode);
    8580             : 
    8581        8148 :     return OGRERR_NONE;
    8582             : }
    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           0 : OGRErr OSRSetAuthority(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
    8594             :                        const char *pszAuthority, int nCode)
    8595             : 
    8596             : {
    8597           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAuthority", OGRERR_FAILURE);
    8598             : 
    8599           0 :     return ToPointer(hSRS)->SetAuthority(pszTargetKey, pszAuthority, nCode);
    8600             : }
    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       49738 : OGRSpatialReference::GetAuthorityCode(const char *pszTargetKey) const
    8627             : 
    8628             : {
    8629       99476 :     TAKE_OPTIONAL_LOCK();
    8630             : 
    8631       49738 :     d->refreshProjObj();
    8632       49738 :     const char *pszInputTargetKey = pszTargetKey;
    8633       49738 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8634       49738 :     if (pszTargetKey == nullptr)
    8635             :     {
    8636       41508 :         if (!d->m_pj_crs)
    8637             :         {
    8638          17 :             return nullptr;
    8639             :         }
    8640       41491 :         d->demoteFromBoundCRS();
    8641       41491 :         auto ret = proj_get_id_code(d->m_pj_crs, 0);
    8642       41491 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8643             :         {
    8644        1379 :             auto ctxt = d->getPROJContext();
    8645        1379 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8646        1379 :             if (cs)
    8647             :             {
    8648        1379 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8649        1379 :                 proj_destroy(cs);
    8650        1379 :                 if (axisCount == 3)
    8651             :                 {
    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          14 :                     pszTargetKey = pszInputTargetKey;
    8656             :                 }
    8657             :             }
    8658             :         }
    8659       41491 :         d->undoDemoteFromBoundCRS();
    8660       41491 :         if (ret != nullptr || pszTargetKey == nullptr)
    8661             :         {
    8662       41491 :             return ret;
    8663             :         }
    8664             :     }
    8665             : 
    8666             :     // Special key for that context
    8667        8234 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8668           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8669             :     {
    8670           4 :         auto ctxt = d->getPROJContext();
    8671           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8672           4 :         if (crs)
    8673             :         {
    8674           4 :             const char *ret = proj_get_id_code(crs, 0);
    8675           4 :             if (ret)
    8676           4 :                 ret = CPLSPrintf("%s", ret);
    8677           4 :             proj_destroy(crs);
    8678           4 :             return ret;
    8679             :         }
    8680             :     }
    8681        8230 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8682           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8683             :     {
    8684           4 :         auto ctxt = d->getPROJContext();
    8685           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8686           4 :         if (crs)
    8687             :         {
    8688           4 :             const char *ret = proj_get_id_code(crs, 0);
    8689           4 :             if (ret)
    8690           4 :                 ret = CPLSPrintf("%s", ret);
    8691           4 :             proj_destroy(crs);
    8692           4 :             return ret;
    8693             :         }
    8694             :     }
    8695             : 
    8696             :     /* -------------------------------------------------------------------- */
    8697             :     /*      Find the node below which the authority should be put.          */
    8698             :     /* -------------------------------------------------------------------- */
    8699        8222 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8700             : 
    8701        8222 :     if (poNode == nullptr)
    8702         105 :         return nullptr;
    8703             : 
    8704             :     /* -------------------------------------------------------------------- */
    8705             :     /*      Fetch AUTHORITY child if there is one.                          */
    8706             :     /* -------------------------------------------------------------------- */
    8707        8117 :     if (poNode->FindChild("AUTHORITY") == -1)
    8708         197 :         return nullptr;
    8709             : 
    8710        7920 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8711             : 
    8712             :     /* -------------------------------------------------------------------- */
    8713             :     /*      Create a new authority node.                                    */
    8714             :     /* -------------------------------------------------------------------- */
    8715        7920 :     if (poNode->GetChildCount() < 2)
    8716           0 :         return nullptr;
    8717             : 
    8718        7920 :     return poNode->GetChild(1)->GetValue();
    8719             : }
    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         875 : const char *OSRGetAuthorityCode(OGRSpatialReferenceH hSRS,
    8731             :                                 const char *pszTargetKey)
    8732             : 
    8733             : {
    8734         875 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityCode", nullptr);
    8735             : 
    8736         875 :     return ToPointer(hSRS)->GetAuthorityCode(pszTargetKey);
    8737             : }
    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       63478 : OGRSpatialReference::GetAuthorityName(const char *pszTargetKey) const
    8763             : 
    8764             : {
    8765      126956 :     TAKE_OPTIONAL_LOCK();
    8766             : 
    8767       63478 :     d->refreshProjObj();
    8768       63478 :     const char *pszInputTargetKey = pszTargetKey;
    8769       63478 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8770       63478 :     if (pszTargetKey == nullptr)
    8771             :     {
    8772       34758 :         if (!d->m_pj_crs)
    8773             :         {
    8774          18 :             return nullptr;
    8775             :         }
    8776       34740 :         d->demoteFromBoundCRS();
    8777       34740 :         auto ret = proj_get_id_auth_name(d->m_pj_crs, 0);
    8778       34740 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8779             :         {
    8780        1002 :             auto ctxt = d->getPROJContext();
    8781        1002 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8782        1002 :             if (cs)
    8783             :             {
    8784        1002 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8785        1002 :                 proj_destroy(cs);
    8786        1002 :                 if (axisCount == 3)
    8787             :                 {
    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          14 :                     pszTargetKey = pszInputTargetKey;
    8792             :                 }
    8793             :             }
    8794             :         }
    8795       34740 :         d->undoDemoteFromBoundCRS();
    8796       34740 :         if (ret != nullptr || pszTargetKey == nullptr)
    8797             :         {
    8798       34740 :             return ret;
    8799             :         }
    8800             :     }
    8801             : 
    8802             :     // Special key for that context
    8803       28724 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8804           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8805             :     {
    8806           4 :         auto ctxt = d->getPROJContext();
    8807           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8808           4 :         if (crs)
    8809             :         {
    8810           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8811           4 :             if (ret)
    8812           4 :                 ret = CPLSPrintf("%s", ret);
    8813           4 :             proj_destroy(crs);
    8814           4 :             return ret;
    8815             :         }
    8816             :     }
    8817       28720 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8818           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8819             :     {
    8820           4 :         auto ctxt = d->getPROJContext();
    8821           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8822           4 :         if (crs)
    8823             :         {
    8824           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8825           4 :             if (ret)
    8826           4 :                 ret = CPLSPrintf("%s", ret);
    8827           4 :             proj_destroy(crs);
    8828           4 :             return ret;
    8829             :         }
    8830             :     }
    8831             : 
    8832             :     /* -------------------------------------------------------------------- */
    8833             :     /*      Find the node below which the authority should be put.          */
    8834             :     /* -------------------------------------------------------------------- */
    8835       28712 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8836             : 
    8837       28712 :     if (poNode == nullptr)
    8838       11885 :         return nullptr;
    8839             : 
    8840             :     /* -------------------------------------------------------------------- */
    8841             :     /*      Fetch AUTHORITY child if there is one.                          */
    8842             :     /* -------------------------------------------------------------------- */
    8843       16827 :     if (poNode->FindChild("AUTHORITY") == -1)
    8844        1595 :         return nullptr;
    8845             : 
    8846       15232 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8847             : 
    8848             :     /* -------------------------------------------------------------------- */
    8849             :     /*      Create a new authority node.                                    */
    8850             :     /* -------------------------------------------------------------------- */
    8851       15232 :     if (poNode->GetChildCount() < 2)
    8852           0 :         return nullptr;
    8853             : 
    8854       15232 :     return poNode->GetChild(0)->GetValue();
    8855             : }
    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         190 : const char *OSRGetAuthorityName(OGRSpatialReferenceH hSRS,
    8867             :                                 const char *pszTargetKey)
    8868             : 
    8869             : {
    8870         190 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityName", nullptr);
    8871             : 
    8872         190 :     return ToPointer(hSRS)->GetAuthorityName(pszTargetKey);
    8873             : }
    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          61 : char *OGRSpatialReference::GetOGCURN() const
    8893             : 
    8894             : {
    8895         122 :     TAKE_OPTIONAL_LOCK();
    8896             : 
    8897          61 :     const char *pszAuthName = GetAuthorityName(nullptr);
    8898          61 :     const char *pszAuthCode = GetAuthorityCode(nullptr);
    8899          61 :     if (pszAuthName && pszAuthCode)
    8900          58 :         return CPLStrdup(
    8901          58 :             CPLSPrintf("urn:ogc:def:crs:%s::%s", pszAuthName, pszAuthCode));
    8902           3 :     if (d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    8903           2 :         return nullptr;
    8904           1 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    8905           1 :     auto vertCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    8906           1 :     char *pszRet = nullptr;
    8907           1 :     if (horizCRS && vertCRS)
    8908             :     {
    8909           1 :         auto horizAuthName = proj_get_id_auth_name(horizCRS, 0);
    8910           1 :         auto horizAuthCode = proj_get_id_code(horizCRS, 0);
    8911           1 :         auto vertAuthName = proj_get_id_auth_name(vertCRS, 0);
    8912           1 :         auto vertAuthCode = proj_get_id_code(vertCRS, 0);
    8913           1 :         if (horizAuthName && horizAuthCode && vertAuthName && vertAuthCode)
    8914             :         {
    8915           1 :             pszRet = CPLStrdup(CPLSPrintf(
    8916             :                 "urn:ogc:def:crs,crs:%s::%s,crs:%s::%s", horizAuthName,
    8917             :                 horizAuthCode, vertAuthName, vertAuthCode));
    8918             :         }
    8919             :     }
    8920           1 :     proj_destroy(horizCRS);
    8921           1 :     proj_destroy(vertCRS);
    8922           1 :     return pszRet;
    8923             : }
    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          46 : OGRErr OGRSpatialReference::StripVertical()
    8943             : 
    8944             : {
    8945          92 :     TAKE_OPTIONAL_LOCK();
    8946             : 
    8947          46 :     d->refreshProjObj();
    8948          46 :     d->demoteFromBoundCRS();
    8949          46 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    8950             :     {
    8951           0 :         d->undoDemoteFromBoundCRS();
    8952           0 :         return OGRERR_NONE;
    8953             :     }
    8954          46 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    8955          46 :     if (!horizCRS)
    8956             :     {
    8957           0 :         d->undoDemoteFromBoundCRS();
    8958           0 :         return OGRERR_FAILURE;
    8959             :     }
    8960             : 
    8961          46 :     bool reuseExistingBoundCRS = false;
    8962          46 :     if (d->m_pj_bound_crs_target)
    8963             :     {
    8964           4 :         auto type = proj_get_type(d->m_pj_bound_crs_target);
    8965           8 :         reuseExistingBoundCRS = type == PJ_TYPE_GEOCENTRIC_CRS ||
    8966           8 :                                 type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    8967             :                                 type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    8968             :     }
    8969             : 
    8970          46 :     if (reuseExistingBoundCRS)
    8971             :     {
    8972           4 :         auto newBoundCRS = proj_crs_create_bound_crs(
    8973           4 :             d->getPROJContext(), horizCRS, d->m_pj_bound_crs_target,
    8974           4 :             d->m_pj_bound_crs_co);
    8975           4 :         proj_destroy(horizCRS);
    8976           4 :         d->undoDemoteFromBoundCRS();
    8977           4 :         d->setPjCRS(newBoundCRS);
    8978             :     }
    8979             :     else
    8980             :     {
    8981          42 :         d->undoDemoteFromBoundCRS();
    8982          42 :         d->setPjCRS(horizCRS);
    8983             :     }
    8984             : 
    8985          46 :     return OGRERR_NONE;
    8986             : }
    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           1 : OGRErr OSRStripVertical(OGRSpatialReferenceH hSRS)
    8998             : 
    8999             : {
    9000           1 :     VALIDATE_POINTER1(hSRS, "OSRStripVertical", OGRERR_FAILURE);
    9001             : 
    9002           1 :     return OGRSpatialReference::FromHandle(hSRS)->StripVertical();
    9003             : }
    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        9344 : bool OGRSpatialReference::StripTOWGS84IfKnownDatumAndAllowed()
    9022             : {
    9023        9344 :     if (CPLTestBool(CPLGetConfigOption("OSR_STRIP_TOWGS84", "YES")))
    9024             :     {
    9025        9341 :         if (StripTOWGS84IfKnownDatum())
    9026             :         {
    9027          72 :             CPLDebug("OSR", "TOWGS84 information has been removed. "
    9028             :                             "It can be kept by setting the OSR_STRIP_TOWGS84 "
    9029             :                             "configuration option to NO");
    9030          72 :             return true;
    9031             :         }
    9032             :     }
    9033        9272 :     return false;
    9034             : }
    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        9347 : bool OGRSpatialReference::StripTOWGS84IfKnownDatum()
    9048             : 
    9049             : {
    9050       18694 :     TAKE_OPTIONAL_LOCK();
    9051             : 
    9052        9347 :     d->refreshProjObj();
    9053        9347 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_BOUND_CRS)
    9054             :     {
    9055        9255 :         return false;
    9056             :     }
    9057          92 :     auto ctxt = d->getPROJContext();
    9058          92 :     auto baseCRS = proj_get_source_crs(ctxt, d->m_pj_crs);
    9059          92 :     if (proj_get_type(baseCRS) == PJ_TYPE_COMPOUND_CRS)
    9060             :     {
    9061           3 :         proj_destroy(baseCRS);
    9062           3 :         return false;
    9063             :     }
    9064             : 
    9065             :     // Known base CRS code ? Return base CRS
    9066          89 :     const char *pszCode = proj_get_id_code(baseCRS, 0);
    9067          89 :     if (pszCode)
    9068             :     {
    9069           2 :         d->setPjCRS(baseCRS);
    9070           2 :         return true;
    9071             :     }
    9072             : 
    9073          87 :     auto datum = proj_crs_get_datum(ctxt, baseCRS);
    9074             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9075             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9076             :     if (datum == nullptr)
    9077             :     {
    9078             :         datum = proj_crs_get_datum_ensemble(ctxt, baseCRS);
    9079             :     }
    9080             : #endif
    9081          87 :     if (!datum)
    9082             :     {
    9083           0 :         proj_destroy(baseCRS);
    9084           0 :         return false;
    9085             :     }
    9086             : 
    9087             :     // Known datum code ? Return base CRS
    9088          87 :     pszCode = proj_get_id_code(datum, 0);
    9089          87 :     if (pszCode)
    9090             :     {
    9091           3 :         proj_destroy(datum);
    9092           3 :         d->setPjCRS(baseCRS);
    9093           3 :         return true;
    9094             :     }
    9095             : 
    9096          84 :     const char *name = proj_get_name(datum);
    9097          84 :     if (EQUAL(name, "unknown"))
    9098             :     {
    9099           1 :         proj_destroy(datum);
    9100           1 :         proj_destroy(baseCRS);
    9101           1 :         return false;
    9102             :     }
    9103          83 :     const PJ_TYPE type = PJ_TYPE_GEODETIC_REFERENCE_FRAME;
    9104             :     PJ_OBJ_LIST *list =
    9105          83 :         proj_create_from_name(ctxt, nullptr, name, &type, 1, false, 1, nullptr);
    9106             : 
    9107          83 :     bool knownDatumName = false;
    9108          83 :     if (list)
    9109             :     {
    9110          83 :         if (proj_list_get_count(list) == 1)
    9111             :         {
    9112          70 :             knownDatumName = true;
    9113             :         }
    9114          83 :         proj_list_destroy(list);
    9115             :     }
    9116             : 
    9117          83 :     proj_destroy(datum);
    9118          83 :     if (knownDatumName)
    9119             :     {
    9120          70 :         d->setPjCRS(baseCRS);
    9121          70 :         return true;
    9122             :     }
    9123          13 :     proj_destroy(baseCRS);
    9124          13 :     return false;
    9125             : }
    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       41723 : int OGRSpatialReference::IsCompound() const
    9140             : 
    9141             : {
    9142       41723 :     TAKE_OPTIONAL_LOCK();
    9143             : 
    9144       41723 :     d->refreshProjObj();
    9145       41723 :     d->demoteFromBoundCRS();
    9146       41723 :     bool isCompound = d->m_pjType == PJ_TYPE_COMPOUND_CRS;
    9147       41723 :     d->undoDemoteFromBoundCRS();
    9148       83446 :     return isCompound;
    9149             : }
    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           5 : int OSRIsCompound(OGRSpatialReferenceH hSRS)
    9161             : 
    9162             : {
    9163           5 :     VALIDATE_POINTER1(hSRS, "OSRIsCompound", 0);
    9164             : 
    9165           5 :     return ToPointer(hSRS)->IsCompound();
    9166             : }
    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       44625 : int OGRSpatialReference::IsProjected() const
    9183             : 
    9184             : {
    9185       44625 :     TAKE_OPTIONAL_LOCK();
    9186             : 
    9187       44625 :     d->refreshProjObj();
    9188       44625 :     d->demoteFromBoundCRS();
    9189       44625 :     bool isProjected = d->m_pjType == PJ_TYPE_PROJECTED_CRS;
    9190       44625 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9191             :     {
    9192             :         auto horizCRS =
    9193         150 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9194         150 :         if (horizCRS)
    9195             :         {
    9196         150 :             auto horizCRSType = proj_get_type(horizCRS);
    9197         150 :             isProjected = horizCRSType == PJ_TYPE_PROJECTED_CRS;
    9198         150 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9199             :             {
    9200           6 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9201           6 :                 if (base)
    9202             :                 {
    9203           6 :                     isProjected = proj_get_type(base) == PJ_TYPE_PROJECTED_CRS;
    9204           6 :                     proj_destroy(base);
    9205             :                 }
    9206             :             }
    9207         150 :             proj_destroy(horizCRS);
    9208             :         }
    9209             :     }
    9210       44625 :     d->undoDemoteFromBoundCRS();
    9211       89250 :     return isProjected;
    9212             : }
    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         413 : int OSRIsProjected(OGRSpatialReferenceH hSRS)
    9223             : 
    9224             : {
    9225         413 :     VALIDATE_POINTER1(hSRS, "OSRIsProjected", 0);
    9226             : 
    9227         413 :     return ToPointer(hSRS)->IsProjected();
    9228             : }
    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       17583 : int OGRSpatialReference::IsGeocentric() const
    9245             : 
    9246             : {
    9247       17583 :     TAKE_OPTIONAL_LOCK();
    9248             : 
    9249       17583 :     d->refreshProjObj();
    9250       17583 :     d->demoteFromBoundCRS();
    9251       17583 :     bool isGeocentric = d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS;
    9252       17583 :     d->undoDemoteFromBoundCRS();
    9253       35166 :     return isGeocentric;
    9254             : }
    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           2 : int OSRIsGeocentric(OGRSpatialReferenceH hSRS)
    9266             : 
    9267             : {
    9268           2 :     VALIDATE_POINTER1(hSRS, "OSRIsGeocentric", 0);
    9269             : 
    9270           2 :     return ToPointer(hSRS)->IsGeocentric();
    9271             : }
    9272             : 
    9273             : /************************************************************************/
    9274             : /*                              IsEmpty()                               */
    9275             : /************************************************************************/
    9276             : 
    9277             : /**
    9278             :  * \brief Return if the SRS is not set.
    9279             :  */
    9280             : 
    9281      117475 : bool OGRSpatialReference::IsEmpty() const
    9282             : {
    9283      117475 :     TAKE_OPTIONAL_LOCK();
    9284             : 
    9285      117475 :     d->refreshProjObj();
    9286      234950 :     return d->m_pj_crs == nullptr;
    9287             : }
    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       61779 : int OGRSpatialReference::IsGeographic() const
    9304             : 
    9305             : {
    9306       61779 :     TAKE_OPTIONAL_LOCK();
    9307             : 
    9308       61779 :     d->refreshProjObj();
    9309       61779 :     d->demoteFromBoundCRS();
    9310       86507 :     bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9311       24728 :                   d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9312       61779 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9313             :     {
    9314             :         auto horizCRS =
    9315         300 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9316         300 :         if (horizCRS)
    9317             :         {
    9318         300 :             auto horizCRSType = proj_get_type(horizCRS);
    9319         300 :             isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9320             :                      horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9321         300 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9322             :             {
    9323          13 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9324          13 :                 if (base)
    9325             :                 {
    9326          13 :                     horizCRSType = proj_get_type(base);
    9327          13 :                     isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9328             :                              horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9329          13 :                     proj_destroy(base);
    9330             :                 }
    9331             :             }
    9332         300 :             proj_destroy(horizCRS);
    9333             :         }
    9334             :     }
    9335       61779 :     d->undoDemoteFromBoundCRS();
    9336      123558 :     return isGeog;
    9337             : }
    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         304 : int OSRIsGeographic(OGRSpatialReferenceH hSRS)
    9348             : 
    9349             : {
    9350         304 :     VALIDATE_POINTER1(hSRS, "OSRIsGeographic", 0);
    9351             : 
    9352         304 :     return ToPointer(hSRS)->IsGeographic();
    9353             : }
    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       15462 : int OGRSpatialReference::IsDerivedGeographic() const
    9369             : 
    9370             : {
    9371       15462 :     TAKE_OPTIONAL_LOCK();
    9372             : 
    9373       15462 :     d->refreshProjObj();
    9374       15462 :     d->demoteFromBoundCRS();
    9375       25228 :     const bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9376        9766 :                         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9377             :     const bool isDerivedGeographic =
    9378       15462 :         isGeog && proj_is_derived_crs(d->getPROJContext(), d->m_pj_crs);
    9379       15462 :     d->undoDemoteFromBoundCRS();
    9380       30924 :     return isDerivedGeographic ? TRUE : FALSE;
    9381             : }
    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           1 : int OSRIsDerivedGeographic(OGRSpatialReferenceH hSRS)
    9393             : 
    9394             : {
    9395           1 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedGeographic", 0);
    9396             : 
    9397           1 :     return ToPointer(hSRS)->IsDerivedGeographic();
    9398             : }
    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           0 : int OGRSpatialReference::IsDerivedProjected() const
    9413             : 
    9414             : {
    9415             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
    9416             :     TAKE_OPTIONAL_LOCK();
    9417             :     d->refreshProjObj();
    9418             :     d->demoteFromBoundCRS();
    9419             :     const bool isDerivedProjected =
    9420             :         d->m_pjType == PJ_TYPE_DERIVED_PROJECTED_CRS;
    9421             :     d->undoDemoteFromBoundCRS();
    9422             :     return isDerivedProjected ? TRUE : FALSE;
    9423             : #else
    9424           0 :     return FALSE;
    9425             : #endif
    9426             : }
    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           0 : int OSRIsDerivedProjected(OGRSpatialReferenceH hSRS)
    9439             : 
    9440             : {
    9441           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedProjected", 0);
    9442             : 
    9443           0 :     return ToPointer(hSRS)->IsDerivedProjected();
    9444             : }
    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        8110 : int OGRSpatialReference::IsLocal() const
    9460             : 
    9461             : {
    9462        8110 :     TAKE_OPTIONAL_LOCK();
    9463        8110 :     d->refreshProjObj();
    9464       16220 :     return d->m_pjType == PJ_TYPE_ENGINEERING_CRS;
    9465             : }
    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           8 : int OSRIsLocal(OGRSpatialReferenceH hSRS)
    9476             : 
    9477             : {
    9478           8 :     VALIDATE_POINTER1(hSRS, "OSRIsLocal", 0);
    9479             : 
    9480           8 :     return ToPointer(hSRS)->IsLocal();
    9481             : }
    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        8989 : int OGRSpatialReference::IsVertical() const
    9499             : 
    9500             : {
    9501        8989 :     TAKE_OPTIONAL_LOCK();
    9502        8989 :     d->refreshProjObj();
    9503        8989 :     d->demoteFromBoundCRS();
    9504        8989 :     bool isVertical = d->m_pjType == PJ_TYPE_VERTICAL_CRS;
    9505        8989 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9506             :     {
    9507             :         auto vertCRS =
    9508          33 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    9509          33 :         if (vertCRS)
    9510             :         {
    9511          33 :             const auto vertCRSType = proj_get_type(vertCRS);
    9512          33 :             isVertical = vertCRSType == PJ_TYPE_VERTICAL_CRS;
    9513          33 :             if (vertCRSType == PJ_TYPE_BOUND_CRS)
    9514             :             {
    9515           0 :                 auto base = proj_get_source_crs(d->getPROJContext(), vertCRS);
    9516           0 :                 if (base)
    9517             :                 {
    9518           0 :                     isVertical = proj_get_type(base) == PJ_TYPE_VERTICAL_CRS;
    9519           0 :                     proj_destroy(base);
    9520             :                 }
    9521             :             }
    9522          33 :             proj_destroy(vertCRS);
    9523             :         }
    9524             :     }
    9525        8989 :     d->undoDemoteFromBoundCRS();
    9526       17978 :     return isVertical;
    9527             : }
    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           0 : int OSRIsVertical(OGRSpatialReferenceH hSRS)
    9539             : 
    9540             : {
    9541           0 :     VALIDATE_POINTER1(hSRS, "OSRIsVertical", 0);
    9542             : 
    9543           0 :     return ToPointer(hSRS)->IsVertical();
    9544             : }
    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       16359 : bool OGRSpatialReference::IsDynamic() const
    9566             : 
    9567             : {
    9568       16359 :     TAKE_OPTIONAL_LOCK();
    9569       16359 :     bool isDynamic = false;
    9570       16359 :     d->refreshProjObj();
    9571       16359 :     d->demoteFromBoundCRS();
    9572       16359 :     auto ctxt = d->getPROJContext();
    9573       16359 :     PJ *horiz = nullptr;
    9574       16359 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9575             :     {
    9576          96 :         horiz = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    9577             :     }
    9578       16263 :     else if (d->m_pj_crs)
    9579             :     {
    9580       16069 :         horiz = proj_clone(ctxt, d->m_pj_crs);
    9581             :     }
    9582       16359 :     if (horiz && proj_get_type(horiz) == PJ_TYPE_BOUND_CRS)
    9583             :     {
    9584           6 :         auto baseCRS = proj_get_source_crs(ctxt, horiz);
    9585           6 :         if (baseCRS)
    9586             :         {
    9587           6 :             proj_destroy(horiz);
    9588           6 :             horiz = baseCRS;
    9589             :         }
    9590             :     }
    9591       16359 :     auto datum = horiz ? proj_crs_get_datum(ctxt, horiz) : nullptr;
    9592       16359 :     if (datum)
    9593             :     {
    9594       16130 :         const auto type = proj_get_type(datum);
    9595       16130 :         isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9596             :                     type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9597       16130 :         if (!isDynamic)
    9598             :         {
    9599       16130 :             const char *auth_name = proj_get_id_auth_name(datum, 0);
    9600       16130 :             const char *code = proj_get_id_code(datum, 0);
    9601       16130 :             if (auth_name && code && EQUAL(auth_name, "EPSG") &&
    9602       15651 :                 EQUAL(code, "6326"))
    9603             :             {
    9604       10231 :                 isDynamic = true;
    9605             :             }
    9606             :         }
    9607       16130 :         proj_destroy(datum);
    9608             :     }
    9609             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9610             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9611             :     else
    9612             :     {
    9613             :         auto ensemble =
    9614             :             horiz ? proj_crs_get_datum_ensemble(ctxt, horiz) : nullptr;
    9615             :         if (ensemble)
    9616             :         {
    9617             :             auto member = proj_datum_ensemble_get_member(ctxt, ensemble, 0);
    9618             :             if (member)
    9619             :             {
    9620             :                 const auto type = proj_get_type(member);
    9621             :                 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9622             :                             type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9623             :                 proj_destroy(member);
    9624             :             }
    9625             :             proj_destroy(ensemble);
    9626             :         }
    9627             :     }
    9628             : #endif
    9629       16359 :     proj_destroy(horiz);
    9630       16359 :     d->undoDemoteFromBoundCRS();
    9631       32718 :     return isDynamic;
    9632             : }
    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           0 : int OSRIsDynamic(OGRSpatialReferenceH hSRS)
    9648             : 
    9649             : {
    9650           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDynamic", 0);
    9651             : 
    9652           0 :     return ToPointer(hSRS)->IsDynamic();
    9653             : }
    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           5 : bool OGRSpatialReference::HasPointMotionOperation() const
    9674             : 
    9675             : {
    9676             : #if PROJ_VERSION_MAJOR > 9 ||                                                  \
    9677             :     (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 4)
    9678             :     TAKE_OPTIONAL_LOCK();
    9679             :     d->refreshProjObj();
    9680             :     d->demoteFromBoundCRS();
    9681             :     auto ctxt = d->getPROJContext();
    9682             :     auto res =
    9683             :         CPL_TO_BOOL(proj_crs_has_point_motion_operation(ctxt, d->m_pj_crs));
    9684             :     d->undoDemoteFromBoundCRS();
    9685             :     return res;
    9686             : #else
    9687           5 :     return false;
    9688             : #endif
    9689             : }
    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           0 : int OSRHasPointMotionOperation(OGRSpatialReferenceH hSRS)
    9707             : 
    9708             : {
    9709           0 :     VALIDATE_POINTER1(hSRS, "OSRHasPointMotionOperation", 0);
    9710             : 
    9711           0 :     return ToPointer(hSRS)->HasPointMotionOperation();
    9712             : }
    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        4345 : OGRSpatialReference *OGRSpatialReference::CloneGeogCS() const
    9725             : 
    9726             : {
    9727        8690 :     TAKE_OPTIONAL_LOCK();
    9728        4345 :     d->refreshProjObj();
    9729        4345 :     if (d->m_pj_crs)
    9730             :     {
    9731        4345 :         if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    9732           0 :             return nullptr;
    9733             : 
    9734             :         auto geodCRS =
    9735        4345 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9736        4345 :         if (geodCRS)
    9737             :         {
    9738        4345 :             OGRSpatialReference *poNewSRS = new OGRSpatialReference();
    9739        4345 :             if (d->m_pjType == PJ_TYPE_BOUND_CRS)
    9740             :             {
    9741             :                 PJ *hub_crs =
    9742          13 :                     proj_get_target_crs(d->getPROJContext(), d->m_pj_crs);
    9743          13 :                 PJ *co = proj_crs_get_coordoperation(d->getPROJContext(),
    9744          13 :                                                      d->m_pj_crs);
    9745          13 :                 auto temp = proj_crs_create_bound_crs(d->getPROJContext(),
    9746             :                                                       geodCRS, hub_crs, co);
    9747          13 :                 proj_destroy(geodCRS);
    9748          13 :                 geodCRS = temp;
    9749          13 :                 proj_destroy(hub_crs);
    9750          13 :                 proj_destroy(co);
    9751             :             }
    9752             : 
    9753             :             /* --------------------------------------------------------------------
    9754             :              */
    9755             :             /*      We have to reconstruct the GEOGCS node for geocentric */
    9756             :             /*      coordinate systems. */
    9757             :             /* --------------------------------------------------------------------
    9758             :              */
    9759        4345 :             if (proj_get_type(geodCRS) == PJ_TYPE_GEOCENTRIC_CRS)
    9760             :             {
    9761           0 :                 auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    9762             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9763             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9764             :                 if (datum == nullptr)
    9765             :                 {
    9766             :                     datum = proj_crs_get_datum_ensemble(d->getPROJContext(),
    9767             :                                                         geodCRS);
    9768             :                 }
    9769             : #endif
    9770           0 :                 if (datum)
    9771             :                 {
    9772           0 :                     auto cs = proj_create_ellipsoidal_2D_cs(
    9773             :                         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE,
    9774             :                         nullptr, 0);
    9775           0 :                     auto temp = proj_create_geographic_crs_from_datum(
    9776             :                         d->getPROJContext(), "unnamed", datum, cs);
    9777           0 :                     proj_destroy(datum);
    9778           0 :                     proj_destroy(cs);
    9779           0 :                     proj_destroy(geodCRS);
    9780           0 :                     geodCRS = temp;
    9781             :                 }
    9782             :             }
    9783             : 
    9784        4345 :             poNewSRS->d->setPjCRS(geodCRS);
    9785        4345 :             if (d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
    9786        2871 :                 poNewSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    9787        4345 :             return poNewSRS;
    9788             :         }
    9789             :     }
    9790           0 :     return nullptr;
    9791             : }
    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         126 : OGRSpatialReferenceH CPL_STDCALL OSRCloneGeogCS(OGRSpatialReferenceH hSource)
    9803             : 
    9804             : {
    9805         126 :     VALIDATE_POINTER1(hSource, "OSRCloneGeogCS", nullptr);
    9806             : 
    9807         126 :     return ToHandle(ToPointer(hSource)->CloneGeogCS());
    9808             : }
    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        8628 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther) const
    9825             : 
    9826             : {
    9827        8628 :     return IsSameGeogCS(poOther, nullptr);
    9828             : }
    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        8628 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther,
    9842             :                                       const char *const *papszOptions) const
    9843             : 
    9844             : {
    9845       17256 :     TAKE_OPTIONAL_LOCK();
    9846             : 
    9847        8628 :     CPL_IGNORE_RET_VAL(papszOptions);
    9848             : 
    9849        8628 :     d->refreshProjObj();
    9850        8628 :     poOther->d->refreshProjObj();
    9851        8628 :     if (!d->m_pj_crs || !poOther->d->m_pj_crs)
    9852           0 :         return FALSE;
    9853        8628 :     if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9854        8628 :         d->m_pjType == PJ_TYPE_VERTICAL_CRS ||
    9855       25884 :         poOther->d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9856        8628 :         poOther->d->m_pjType == PJ_TYPE_VERTICAL_CRS)
    9857             :     {
    9858           0 :         return FALSE;
    9859             :     }
    9860             : 
    9861        8628 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9862             :     auto otherGeodCRS =
    9863        8628 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poOther->d->m_pj_crs);
    9864        8628 :     if (!geodCRS || !otherGeodCRS)
    9865             :     {
    9866           0 :         proj_destroy(geodCRS);
    9867           0 :         proj_destroy(otherGeodCRS);
    9868           0 :         return FALSE;
    9869             :     }
    9870             : 
    9871        8628 :     int ret = proj_is_equivalent_to(
    9872             :         geodCRS, otherGeodCRS, PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS);
    9873             : 
    9874        8628 :     proj_destroy(geodCRS);
    9875        8628 :     proj_destroy(otherGeodCRS);
    9876        8628 :     return ret;
    9877             : }
    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           0 : int OSRIsSameGeogCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9889             : 
    9890             : {
    9891           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameGeogCS", 0);
    9892           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameGeogCS", 0);
    9893             : 
    9894           0 :     return ToPointer(hSRS1)->IsSameGeogCS(ToPointer(hSRS2));
    9895             : }
    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           0 : int OGRSpatialReference::IsSameVertCS(const OGRSpatialReference *poOther) const
    9912             : 
    9913             : {
    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             : }
    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           0 : int OSRIsSameVertCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9953             : 
    9954             : {
    9955           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameVertCS", 0);
    9956           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameVertCS", 0);
    9957             : 
    9958           0 :     return ToPointer(hSRS1)->IsSameVertCS(ToPointer(hSRS2));
    9959             : }
    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       23248 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS) const
    9974             : 
    9975             : {
    9976       23248 :     return IsSame(poOtherSRS, nullptr);
    9977             : }
    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       36088 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS,
    9998             :                                 const char *const *papszOptions) const
    9999             : 
   10000             : {
   10001       72176 :     TAKE_OPTIONAL_LOCK();
   10002             : 
   10003       36088 :     d->refreshProjObj();
   10004       36088 :     poOtherSRS->d->refreshProjObj();
   10005       36088 :     if (!d->m_pj_crs || !poOtherSRS->d->m_pj_crs)
   10006          51 :         return d->m_pj_crs == poOtherSRS->d->m_pj_crs;
   10007       36037 :     if (!CPLTestBool(CSLFetchNameValueDef(
   10008             :             papszOptions, "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING", "NO")))
   10009             :     {
   10010       35387 :         if (d->m_axisMapping != poOtherSRS->d->m_axisMapping)
   10011        3315 :             return false;
   10012             :     }
   10013             : 
   10014       32722 :     if (!CPLTestBool(CSLFetchNameValueDef(papszOptions,
   10015             :                                           "IGNORE_COORDINATE_EPOCH", "NO")))
   10016             :     {
   10017       32330 :         if (d->m_coordinateEpoch != poOtherSRS->d->m_coordinateEpoch)
   10018          27 :             return false;
   10019             :     }
   10020             : 
   10021       32695 :     bool reboundSelf = false;
   10022       32695 :     bool reboundOther = false;
   10023       32747 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS &&
   10024          52 :         poOtherSRS->d->m_pjType != PJ_TYPE_BOUND_CRS)
   10025             :     {
   10026          14 :         d->demoteFromBoundCRS();
   10027          14 :         reboundSelf = true;
   10028             :     }
   10029       65324 :     else if (d->m_pjType != PJ_TYPE_BOUND_CRS &&
   10030       32643 :              poOtherSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
   10031             :     {
   10032          28 :         poOtherSRS->d->demoteFromBoundCRS();
   10033          28 :         reboundOther = true;
   10034             :     }
   10035             : 
   10036       32695 :     PJ_COMPARISON_CRITERION criterion =
   10037             :         PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS;
   10038       32695 :     const char *pszCriterion = CSLFetchNameValueDef(
   10039             :         papszOptions, "CRITERION", "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS");
   10040       32695 :     if (EQUAL(pszCriterion, "STRICT"))
   10041           0 :         criterion = PJ_COMP_STRICT;
   10042       32695 :     else if (EQUAL(pszCriterion, "EQUIVALENT"))
   10043        9662 :         criterion = PJ_COMP_EQUIVALENT;
   10044       23033 :     else if (!EQUAL(pszCriterion, "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS"))
   10045             :     {
   10046           0 :         CPLError(CE_Warning, CPLE_NotSupported,
   10047             :                  "Unsupported value for CRITERION: %s", pszCriterion);
   10048             :     }
   10049             :     int ret =
   10050       32695 :         proj_is_equivalent_to(d->m_pj_crs, poOtherSRS->d->m_pj_crs, criterion);
   10051       32695 :     if (reboundSelf)
   10052          14 :         d->undoDemoteFromBoundCRS();
   10053       32695 :     if (reboundOther)
   10054          28 :         poOtherSRS->d->undoDemoteFromBoundCRS();
   10055             : 
   10056       32695 :     return ret;
   10057             : }
   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          35 : int OSRIsSame(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
   10069             : 
   10070             : {
   10071          35 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
   10072          35 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
   10073             : 
   10074          35 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2));
   10075             : }
   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         644 : int OSRIsSameEx(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2,
   10087             :                 const char *const *papszOptions)
   10088             : {
   10089         644 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
   10090         644 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
   10091             : 
   10092         644 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2), papszOptions);
   10093             : }
   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          91 : OGRSpatialReference *OGRSpatialReference::convertToOtherProjection(
   10118             :     const char *pszTargetProjection,
   10119             :     CPL_UNUSED const char *const *papszOptions) const
   10120             : {
   10121         182 :     TAKE_OPTIONAL_LOCK();
   10122             : 
   10123          91 :     if (pszTargetProjection == nullptr)
   10124           1 :         return nullptr;
   10125             :     int new_code;
   10126          90 :     if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_1SP))
   10127             :     {
   10128           6 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A;
   10129             :     }
   10130          84 :     else if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_2SP))
   10131             :     {
   10132           7 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B;
   10133             :     }
   10134          77 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
   10135             :     {
   10136          65 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP;
   10137             :     }
   10138          12 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
   10139             :     {
   10140          11 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP;
   10141             :     }
   10142             :     else
   10143             :     {
   10144           1 :         return nullptr;
   10145             :     }
   10146             : 
   10147          89 :     d->refreshProjObj();
   10148          89 :     d->demoteFromBoundCRS();
   10149          89 :     OGRSpatialReference *poNewSRS = nullptr;
   10150          89 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
   10151             :     {
   10152             :         auto conv =
   10153          88 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10154          88 :         auto new_conv = proj_convert_conversion_to_other_method(
   10155             :             d->getPROJContext(), conv, new_code, nullptr);
   10156          88 :         proj_destroy(conv);
   10157          88 :         if (new_conv)
   10158             :         {
   10159             :             auto geodCRS =
   10160          74 :                 proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10161          74 :             auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   10162          74 :                                                      d->m_pj_crs);
   10163          74 :             if (geodCRS && cs)
   10164             :             {
   10165          74 :                 auto new_proj_crs = proj_create_projected_crs(
   10166          74 :                     d->getPROJContext(), proj_get_name(d->m_pj_crs), geodCRS,
   10167             :                     new_conv, cs);
   10168          74 :                 proj_destroy(new_conv);
   10169          74 :                 if (new_proj_crs)
   10170             :                 {
   10171          74 :                     poNewSRS = new OGRSpatialReference();
   10172             : 
   10173          74 :                     if (d->m_pj_bound_crs_target && d->m_pj_bound_crs_co)
   10174             :                     {
   10175           9 :                         auto boundCRS = proj_crs_create_bound_crs(
   10176             :                             d->getPROJContext(), new_proj_crs,
   10177           9 :                             d->m_pj_bound_crs_target, d->m_pj_bound_crs_co);
   10178           9 :                         if (boundCRS)
   10179             :                         {
   10180           9 :                             proj_destroy(new_proj_crs);
   10181           9 :                             new_proj_crs = boundCRS;
   10182             :                         }
   10183             :                     }
   10184             : 
   10185          74 :                     poNewSRS->d->setPjCRS(new_proj_crs);
   10186             :                 }
   10187             :             }
   10188          74 :             proj_destroy(geodCRS);
   10189          74 :             proj_destroy(cs);
   10190             :         }
   10191             :     }
   10192          89 :     d->undoDemoteFromBoundCRS();
   10193          89 :     return poNewSRS;
   10194             : }
   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          28 : OSRConvertToOtherProjection(OGRSpatialReferenceH hSRS,
   10221             :                             const char *pszTargetProjection,
   10222             :                             const char *const *papszOptions)
   10223             : {
   10224          28 :     VALIDATE_POINTER1(hSRS, "OSRConvertToOtherProjection", nullptr);
   10225          28 :     return ToHandle(ToPointer(hSRS)->convertToOtherProjection(
   10226          28 :         pszTargetProjection, papszOptions));
   10227             : }
   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          10 : OGRSpatialReferenceH *OSRFindMatches(OGRSpatialReferenceH hSRS,
   10259             :                                      CSLConstList papszOptions, int *pnEntries,
   10260             :                                      int **ppanMatchConfidence)
   10261             : {
   10262          10 :     if (pnEntries)
   10263          10 :         *pnEntries = 0;
   10264          10 :     if (ppanMatchConfidence)
   10265          10 :         *ppanMatchConfidence = nullptr;
   10266          10 :     VALIDATE_POINTER1(hSRS, "OSRFindMatches", nullptr);
   10267             : 
   10268          10 :     OGRSpatialReference *poSRS = ToPointer(hSRS);
   10269          10 :     return poSRS->FindMatches(papszOptions, pnEntries, ppanMatchConfidence);
   10270             : }
   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         197 : void OSRFreeSRSArray(OGRSpatialReferenceH *pahSRS)
   10282             : {
   10283         197 :     if (pahSRS != nullptr)
   10284             :     {
   10285        1744 :         for (int i = 0; pahSRS[i] != nullptr; ++i)
   10286             :         {
   10287        1565 :             OSRRelease(pahSRS[i]);
   10288             :         }
   10289         179 :         CPLFree(pahSRS);
   10290             :     }
   10291         197 : }
   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        1467 : OGRSpatialReference::FindBestMatch(int nMinimumMatchConfidence,
   10322             :                                    const char *pszPreferredAuthority,
   10323             :                                    CSLConstList papszOptions) const
   10324             : {
   10325        2934 :     TAKE_OPTIONAL_LOCK();
   10326             : 
   10327        1467 :     CPL_IGNORE_RET_VAL(papszOptions);  // ignored for now.
   10328             : 
   10329        1467 :     if (nMinimumMatchConfidence == 0)
   10330           0 :         nMinimumMatchConfidence = 90;
   10331        1467 :     if (pszPreferredAuthority == nullptr)
   10332         200 :         pszPreferredAuthority = "EPSG";
   10333             : 
   10334             :     // Try to identify the CRS with the database
   10335        1467 :     int nEntries = 0;
   10336        1467 :     int *panConfidence = nullptr;
   10337             :     OGRSpatialReferenceH *pahSRS =
   10338        1467 :         FindMatches(nullptr, &nEntries, &panConfidence);
   10339        1467 :     if (nEntries == 1 && panConfidence[0] >= nMinimumMatchConfidence)
   10340             :     {
   10341        2606 :         std::vector<double> adfTOWGS84(7);
   10342        1303 :         if (GetTOWGS84(&adfTOWGS84[0], 7) != OGRERR_NONE)
   10343             :         {
   10344        1302 :             adfTOWGS84.clear();
   10345             :         }
   10346             : 
   10347        1303 :         auto poSRS = OGRSpatialReference::FromHandle(pahSRS[0]);
   10348             : 
   10349             :         auto poBaseGeogCRS =
   10350        1303 :             std::unique_ptr<OGRSpatialReference>(poSRS->CloneGeogCS());
   10351        1303 :         if (poBaseGeogCRS)
   10352             :         {
   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        1303 :             const char *pszAuthorityName = nullptr;
   10361        1303 :             const char *pszAuthorityCode = nullptr;
   10362        1303 :             const char *pszBaseAuthorityName = nullptr;
   10363        1303 :             const char *pszBaseAuthorityCode = nullptr;
   10364        1303 :             const char *pszBaseName = poBaseGeogCRS->GetName();
   10365        2606 :             if (adfTOWGS84 == std::vector<double>(7) &&
   10366           1 :                 (pszAuthorityName = poSRS->GetAuthorityName(nullptr)) !=
   10367           1 :                     nullptr &&
   10368           1 :                 EQUAL(pszAuthorityName, "EPSG") &&
   10369           1 :                 (pszAuthorityCode = poSRS->GetAuthorityCode(nullptr)) !=
   10370           1 :                     nullptr &&
   10371             :                 (pszBaseAuthorityName =
   10372           1 :                      poBaseGeogCRS->GetAuthorityName(nullptr)) != nullptr &&
   10373           1 :                 EQUAL(pszBaseAuthorityName, "EPSG") &&
   10374             :                 (pszBaseAuthorityCode =
   10375        2608 :                      poBaseGeogCRS->GetAuthorityCode(nullptr)) != nullptr &&
   10376           1 :                 (EQUAL(pszBaseAuthorityCode, "4326") ||
   10377           1 :                  EQUAL(pszBaseAuthorityCode, "4258") ||
   10378             :                  // For ETRS89-XXX [...] new CRS added in EPSG 12.033+
   10379           0 :                  (pszBaseName && STARTS_WITH(pszBaseName, "ETRS89"))))
   10380             :             {
   10381           1 :                 poSRS->importFromEPSG(atoi(pszAuthorityCode));
   10382             :             }
   10383             :         }
   10384             : 
   10385        1303 :         CPLFree(pahSRS);
   10386        1303 :         CPLFree(panConfidence);
   10387             : 
   10388        1303 :         return poSRS;
   10389             :     }
   10390             :     else
   10391             :     {
   10392             :         // If there are several matches >= nMinimumMatchConfidence, take the
   10393             :         // only one that is under pszPreferredAuthority
   10394         164 :         int iBestEntry = -1;
   10395        1679 :         for (int i = 0; i < nEntries; i++)
   10396             :         {
   10397        1515 :             if (panConfidence[i] >= nMinimumMatchConfidence)
   10398             :             {
   10399             :                 const char *pszAuthName =
   10400           3 :                     OGRSpatialReference::FromHandle(pahSRS[i])
   10401           3 :                         ->GetAuthorityName(nullptr);
   10402           3 :                 if (pszAuthName != nullptr &&
   10403           3 :                     EQUAL(pszAuthName, pszPreferredAuthority))
   10404             :                 {
   10405           3 :                     if (iBestEntry < 0)
   10406           3 :                         iBestEntry = i;
   10407             :                     else
   10408             :                     {
   10409           0 :                         iBestEntry = -1;
   10410           0 :                         break;
   10411             :                     }
   10412             :                 }
   10413             :             }
   10414             :         }
   10415         164 :         if (iBestEntry >= 0)
   10416             :         {
   10417           3 :             auto poRet = OGRSpatialReference::FromHandle(pahSRS[0])->Clone();
   10418           3 :             OSRFreeSRSArray(pahSRS);
   10419           3 :             CPLFree(panConfidence);
   10420           3 :             return poRet;
   10421             :         }
   10422             :     }
   10423         161 :     OSRFreeSRSArray(pahSRS);
   10424         161 :     CPLFree(panConfidence);
   10425         161 :     return nullptr;
   10426             : }
   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         103 : OGRErr OGRSpatialReference::SetTOWGS84(double dfDX, double dfDY, double dfDZ,
   10456             :                                        double dfEX, double dfEY, double dfEZ,
   10457             :                                        double dfPPM)
   10458             : 
   10459             : {
   10460         206 :     TAKE_OPTIONAL_LOCK();
   10461             : 
   10462         103 :     d->refreshProjObj();
   10463         103 :     if (d->m_pj_crs == nullptr)
   10464             :     {
   10465           0 :         return OGRERR_FAILURE;
   10466             :     }
   10467             : 
   10468             :     // Remove existing BoundCRS
   10469         103 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS)
   10470             :     {
   10471           0 :         auto baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
   10472           0 :         if (!baseCRS)
   10473           0 :             return OGRERR_FAILURE;
   10474           0 :         d->setPjCRS(baseCRS);
   10475             :     }
   10476             : 
   10477             :     PJ_PARAM_DESCRIPTION params[7];
   10478             : 
   10479         103 :     params[0].name = EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION;
   10480         103 :     params[0].auth_name = "EPSG";
   10481         103 :     params[0].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
   10482         103 :     params[0].value = dfDX;
   10483         103 :     params[0].unit_name = "metre";
   10484         103 :     params[0].unit_conv_factor = 1.0;
   10485         103 :     params[0].unit_type = PJ_UT_LINEAR;
   10486             : 
   10487         103 :     params[1].name = EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION;
   10488         103 :     params[1].auth_name = "EPSG";
   10489         103 :     params[1].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
   10490         103 :     params[1].value = dfDY;
   10491         103 :     params[1].unit_name = "metre";
   10492         103 :     params[1].unit_conv_factor = 1.0;
   10493         103 :     params[1].unit_type = PJ_UT_LINEAR;
   10494             : 
   10495         103 :     params[2].name = EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION;
   10496         103 :     params[2].auth_name = "EPSG";
   10497         103 :     params[2].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
   10498         103 :     params[2].value = dfDZ;
   10499         103 :     params[2].unit_name = "metre";
   10500         103 :     params[2].unit_conv_factor = 1.0;
   10501         103 :     params[2].unit_type = PJ_UT_LINEAR;
   10502             : 
   10503         103 :     params[3].name = EPSG_NAME_PARAMETER_X_AXIS_ROTATION;
   10504         103 :     params[3].auth_name = "EPSG";
   10505         103 :     params[3].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_ROTATION);
   10506         103 :     params[3].value = dfEX;
   10507         103 :     params[3].unit_name = "arc-second";
   10508         103 :     params[3].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10509         103 :     params[3].unit_type = PJ_UT_ANGULAR;
   10510             : 
   10511         103 :     params[4].name = EPSG_NAME_PARAMETER_Y_AXIS_ROTATION;
   10512         103 :     params[4].auth_name = "EPSG";
   10513         103 :     params[4].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION);
   10514         103 :     params[4].value = dfEY;
   10515         103 :     params[4].unit_name = "arc-second";
   10516         103 :     params[4].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10517         103 :     params[4].unit_type = PJ_UT_ANGULAR;
   10518             : 
   10519         103 :     params[5].name = EPSG_NAME_PARAMETER_Z_AXIS_ROTATION;
   10520         103 :     params[5].auth_name = "EPSG";
   10521         103 :     params[5].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION);
   10522         103 :     params[5].value = dfEZ;
   10523         103 :     params[5].unit_name = "arc-second";
   10524         103 :     params[5].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10525         103 :     params[5].unit_type = PJ_UT_ANGULAR;
   10526             : 
   10527         103 :     params[6].name = EPSG_NAME_PARAMETER_SCALE_DIFFERENCE;
   10528         103 :     params[6].auth_name = "EPSG";
   10529         103 :     params[6].code = XSTRINGIFY(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE);
   10530         103 :     params[6].value = dfPPM;
   10531         103 :     params[6].unit_name = "parts per million";
   10532         103 :     params[6].unit_conv_factor = 1e-6;
   10533         103 :     params[6].unit_type = PJ_UT_SCALE;
   10534             : 
   10535             :     auto sourceCRS =
   10536         103 :         proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10537         103 :     if (!sourceCRS)
   10538             :     {
   10539           0 :         return OGRERR_FAILURE;
   10540             :     }
   10541             : 
   10542         103 :     const auto sourceType = proj_get_type(sourceCRS);
   10543             : 
   10544         103 :     auto targetCRS = proj_create_from_database(
   10545             :         d->getPROJContext(), "EPSG",
   10546             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS   ? "4326"
   10547             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS ? "4979"
   10548             :                                                   : "4978",
   10549             :         PJ_CATEGORY_CRS, false, nullptr);
   10550         103 :     if (!targetCRS)
   10551             :     {
   10552           0 :         proj_destroy(sourceCRS);
   10553           0 :         return OGRERR_FAILURE;
   10554             :     }
   10555             : 
   10556         206 :     CPLString osMethodCode;
   10557             :     osMethodCode.Printf("%d",
   10558             :                         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10559             :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10560             :                         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10561           0 :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10562         103 :                             : EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC);
   10563             : 
   10564         103 :     auto transf = proj_create_transformation(
   10565             :         d->getPROJContext(), "Transformation to WGS84", nullptr, nullptr,
   10566             :         sourceCRS, targetCRS, nullptr,
   10567             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10568             :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10569             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10570           0 :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10571             :             : EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC,
   10572             :         "EPSG", osMethodCode.c_str(), 7, params, -1);
   10573         103 :     proj_destroy(sourceCRS);
   10574         103 :     if (!transf)
   10575             :     {
   10576           0 :         proj_destroy(targetCRS);
   10577           0 :         return OGRERR_FAILURE;
   10578             :     }
   10579             : 
   10580         103 :     auto newBoundCRS = proj_crs_create_bound_crs(
   10581         103 :         d->getPROJContext(), d->m_pj_crs, targetCRS, transf);
   10582         103 :     proj_destroy(transf);
   10583         103 :     proj_destroy(targetCRS);
   10584         103 :     if (!newBoundCRS)
   10585             :     {
   10586           0 :         return OGRERR_FAILURE;
   10587             :     }
   10588             : 
   10589         103 :     d->setPjCRS(newBoundCRS);
   10590         103 :     return OGRERR_NONE;
   10591             : }
   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           4 : OGRErr OSRSetTOWGS84(OGRSpatialReferenceH hSRS, double dfDX, double dfDY,
   10603             :                      double dfDZ, double dfEX, double dfEY, double dfEZ,
   10604             :                      double dfPPM)
   10605             : 
   10606             : {
   10607           4 :     VALIDATE_POINTER1(hSRS, "OSRSetTOWGS84", OGRERR_FAILURE);
   10608             : 
   10609           4 :     return ToPointer(hSRS)->SetTOWGS84(dfDX, dfDY, dfDZ, dfEX, dfEY, dfEZ,
   10610           4 :                                        dfPPM);
   10611             : }
   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        4856 : OGRErr OGRSpatialReference::GetTOWGS84(double *padfCoeff, int nCoeffCount) const
   10631             : 
   10632             : {
   10633        9712 :     TAKE_OPTIONAL_LOCK();
   10634             : 
   10635        4856 :     d->refreshProjObj();
   10636        4856 :     if (d->m_pjType != PJ_TYPE_BOUND_CRS)
   10637        4808 :         return OGRERR_FAILURE;
   10638             : 
   10639          48 :     memset(padfCoeff, 0, sizeof(double) * nCoeffCount);
   10640             : 
   10641          48 :     auto transf = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10642          48 :     int success = proj_coordoperation_get_towgs84_values(
   10643             :         d->getPROJContext(), transf, padfCoeff, nCoeffCount, false);
   10644          48 :     proj_destroy(transf);
   10645             : 
   10646          48 :     return success ? OGRERR_NONE : OGRERR_FAILURE;
   10647             : }
   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          10 : OGRErr OSRGetTOWGS84(OGRSpatialReferenceH hSRS, double *padfCoeff,
   10659             :                      int nCoeffCount)
   10660             : 
   10661             : {
   10662          10 :     VALIDATE_POINTER1(hSRS, "OSRGetTOWGS84", OGRERR_FAILURE);
   10663             : 
   10664          10 :     return ToPointer(hSRS)->GetTOWGS84(padfCoeff, nCoeffCount);
   10665             : }
   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          10 : int OGRSpatialReference::IsAngularParameter(const char *pszParameterName)
   10678             : 
   10679             : {
   10680          10 :     if (STARTS_WITH_CI(pszParameterName, "long") ||
   10681          10 :         STARTS_WITH_CI(pszParameterName, "lati") ||
   10682           7 :         EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN) ||
   10683           4 :         STARTS_WITH_CI(pszParameterName, "standard_parallel") ||
   10684           2 :         EQUAL(pszParameterName, SRS_PP_AZIMUTH) ||
   10685           2 :         EQUAL(pszParameterName, SRS_PP_RECTIFIED_GRID_ANGLE))
   10686           8 :         return TRUE;
   10687             : 
   10688           2 :     return FALSE;
   10689             : }
   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           0 : int OGRSpatialReference::IsLongitudeParameter(const char *pszParameterName)
   10703             : 
   10704             : {
   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             : }
   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          43 : int OGRSpatialReference::IsLinearParameter(const char *pszParameterName)
   10724             : 
   10725             : {
   10726          43 :     if (STARTS_WITH_CI(pszParameterName, "false_") ||
   10727          34 :         EQUAL(pszParameterName, SRS_PP_SATELLITE_HEIGHT))
   10728           9 :         return TRUE;
   10729             : 
   10730          34 :     return FALSE;
   10731             : }
   10732             : 
   10733             : /************************************************************************/
   10734             : /*                            GetNormInfo()                             */
   10735             : /************************************************************************/
   10736             : 
   10737             : /**
   10738             :  * \brief Set the internal information for normalizing linear, and angular
   10739             :  * values.
   10740             :  */
   10741        4097 : void OGRSpatialReference::GetNormInfo() const
   10742             : 
   10743             : {
   10744        4097 :     TAKE_OPTIONAL_LOCK();
   10745             : 
   10746        4097 :     if (d->bNormInfoSet)
   10747        2922 :         return;
   10748             : 
   10749             :     /* -------------------------------------------------------------------- */
   10750             :     /*      Initialize values.                                              */
   10751             :     /* -------------------------------------------------------------------- */
   10752        1175 :     d->bNormInfoSet = TRUE;
   10753             : 
   10754        1175 :     d->dfFromGreenwich = GetPrimeMeridian(nullptr);
   10755        1175 :     d->dfToMeter = GetLinearUnits(nullptr);
   10756        1175 :     d->dfToDegrees = GetAngularUnits(nullptr) / CPLAtof(SRS_UA_DEGREE_CONV);
   10757        1175 :     if (fabs(d->dfToDegrees - 1.0) < 0.000000001)
   10758        1172 :         d->dfToDegrees = 1.0;
   10759             : }
   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       12649 : const char *OGRSpatialReference::GetExtension(const char *pszTargetKey,
   10779             :                                               const char *pszName,
   10780             :                                               const char *pszDefault) const
   10781             : 
   10782             : {
   10783       25298 :     TAKE_OPTIONAL_LOCK();
   10784             : 
   10785             :     /* -------------------------------------------------------------------- */
   10786             :     /*      Find the target node.                                           */
   10787             :     /* -------------------------------------------------------------------- */
   10788             :     const OGR_SRSNode *poNode =
   10789       12649 :         pszTargetKey == nullptr ? GetRoot() : GetAttrNode(pszTargetKey);
   10790             : 
   10791       12649 :     if (poNode == nullptr)
   10792        2359 :         return nullptr;
   10793             : 
   10794             :     /* -------------------------------------------------------------------- */
   10795             :     /*      Fetch matching EXTENSION if there is one.                       */
   10796             :     /* -------------------------------------------------------------------- */
   10797       76047 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10798             :     {
   10799       65781 :         const OGR_SRSNode *poChild = poNode->GetChild(i);
   10800             : 
   10801       65807 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10802          26 :             poChild->GetChildCount() >= 2)
   10803             :         {
   10804          26 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10805          24 :                 return poChild->GetChild(1)->GetValue();
   10806             :         }
   10807             :     }
   10808             : 
   10809       10266 :     return pszDefault;
   10810             : }
   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          20 : OGRErr OGRSpatialReference::SetExtension(const char *pszTargetKey,
   10829             :                                          const char *pszName,
   10830             :                                          const char *pszValue)
   10831             : 
   10832             : {
   10833          40 :     TAKE_OPTIONAL_LOCK();
   10834             : 
   10835             :     /* -------------------------------------------------------------------- */
   10836             :     /*      Find the target node.                                           */
   10837             :     /* -------------------------------------------------------------------- */
   10838          20 :     OGR_SRSNode *poNode = nullptr;
   10839             : 
   10840          20 :     if (pszTargetKey == nullptr)
   10841           0 :         poNode = GetRoot();
   10842             :     else
   10843          20 :         poNode = GetAttrNode(pszTargetKey);
   10844             : 
   10845          20 :     if (poNode == nullptr)
   10846           0 :         return OGRERR_FAILURE;
   10847             : 
   10848             :     /* -------------------------------------------------------------------- */
   10849             :     /*      Fetch matching EXTENSION if there is one.                       */
   10850             :     /* -------------------------------------------------------------------- */
   10851         151 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10852             :     {
   10853         137 :         OGR_SRSNode *poChild = poNode->GetChild(i);
   10854             : 
   10855         143 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10856           6 :             poChild->GetChildCount() >= 2)
   10857             :         {
   10858           6 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10859             :             {
   10860           6 :                 poChild->GetChild(1)->SetValue(pszValue);
   10861           6 :                 return OGRERR_NONE;
   10862             :             }
   10863             :         }
   10864             :     }
   10865             : 
   10866             :     /* -------------------------------------------------------------------- */
   10867             :     /*      Create a new EXTENSION node.                                    */
   10868             :     /* -------------------------------------------------------------------- */
   10869          14 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("EXTENSION");
   10870          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszName));
   10871          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszValue));
   10872             : 
   10873          14 :     poNode->AddChild(poAuthNode);
   10874             : 
   10875          14 :     return OGRERR_NONE;
   10876             : }
   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        1134 : void OSRCleanup(void)
   10896             : 
   10897             : {
   10898        1134 :     OGRCTDumpStatistics();
   10899        1134 :     CSVDeaccess(nullptr);
   10900        1134 :     CleanupSRSWGS84Mutex();
   10901        1134 :     OSRCTCleanCache();
   10902        1134 :     OSRCleanupTLSContext();
   10903        1134 : }
   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       38508 : int OGRSpatialReference::GetAxesCount() const
   10915             : {
   10916       77016 :     TAKE_OPTIONAL_LOCK();
   10917             : 
   10918       38508 :     int axisCount = 0;
   10919       38508 :     d->refreshProjObj();
   10920       38508 :     if (d->m_pj_crs == nullptr)
   10921             :     {
   10922           0 :         return 0;
   10923             :     }
   10924       38508 :     d->demoteFromBoundCRS();
   10925       38508 :     auto ctxt = d->getPROJContext();
   10926       38508 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   10927             :     {
   10928          38 :         for (int i = 0;; i++)
   10929             :         {
   10930         114 :             auto subCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, i);
   10931         114 :             if (!subCRS)
   10932          38 :                 break;
   10933          76 :             if (proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
   10934             :             {
   10935          18 :                 auto baseCRS = proj_get_source_crs(ctxt, subCRS);
   10936          18 :                 if (baseCRS)
   10937             :                 {
   10938          18 :                     proj_destroy(subCRS);
   10939          18 :                     subCRS = baseCRS;
   10940             :                 }
   10941             :             }
   10942          76 :             auto cs = proj_crs_get_coordinate_system(ctxt, subCRS);
   10943          76 :             if (cs)
   10944             :             {
   10945          76 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
   10946          76 :                 proj_destroy(cs);
   10947             :             }
   10948          76 :             proj_destroy(subCRS);
   10949          76 :         }
   10950             :     }
   10951             :     else
   10952             :     {
   10953       38470 :         auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   10954       38470 :         if (cs)
   10955             :         {
   10956       38470 :             axisCount = proj_cs_get_axis_count(ctxt, cs);
   10957       38470 :             proj_destroy(cs);
   10958             :         }
   10959             :     }
   10960       38508 :     d->undoDemoteFromBoundCRS();
   10961       38508 :     return axisCount;
   10962             : }
   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           6 : int OSRGetAxesCount(OGRSpatialReferenceH hSRS)
   10977             : 
   10978             : {
   10979           6 :     VALIDATE_POINTER1(hSRS, "OSRGetAxesCount", 0);
   10980             : 
   10981           6 :     return ToPointer(hSRS)->GetAxesCount();
   10982             : }
   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        8448 : const char *OGRSpatialReference::GetAxis(const char *pszTargetKey, int iAxis,
   11011             :                                          OGRAxisOrientation *peOrientation,
   11012             :                                          double *pdfConvUnit) const
   11013             : 
   11014             : {
   11015       16896 :     TAKE_OPTIONAL_LOCK();
   11016             : 
   11017        8448 :     if (peOrientation != nullptr)
   11018        8339 :         *peOrientation = OAO_Other;
   11019        8448 :     if (pdfConvUnit != nullptr)
   11020         101 :         *pdfConvUnit = 0;
   11021             : 
   11022        8448 :     d->refreshProjObj();
   11023        8448 :     if (d->m_pj_crs == nullptr)
   11024             :     {
   11025           3 :         return nullptr;
   11026             :     }
   11027             : 
   11028        8445 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
   11029        8445 :     if (pszTargetKey == nullptr && iAxis <= 2)
   11030             :     {
   11031        8445 :         auto ctxt = d->getPROJContext();
   11032             : 
   11033        8445 :         int iAxisModified = iAxis;
   11034             : 
   11035        8445 :         d->demoteFromBoundCRS();
   11036             : 
   11037        8445 :         PJ *cs = nullptr;
   11038        8445 :         if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   11039             :         {
   11040         134 :             auto horizCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   11041         134 :             if (horizCRS)
   11042             :             {
   11043         134 :                 if (proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
   11044             :                 {
   11045           6 :                     auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
   11046           6 :                     if (baseCRS)
   11047             :                     {
   11048           6 :                         proj_destroy(horizCRS);
   11049           6 :                         horizCRS = baseCRS;
   11050             :                     }
   11051             :                 }
   11052         134 :                 cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
   11053         134 :                 proj_destroy(horizCRS);
   11054         134 :                 if (cs)
   11055             :                 {
   11056         134 :                     if (iAxisModified >= proj_cs_get_axis_count(ctxt, cs))
   11057             :                     {
   11058          44 :                         iAxisModified -= proj_cs_get_axis_count(ctxt, cs);
   11059          44 :                         proj_destroy(cs);
   11060          44 :                         cs = nullptr;
   11061             :                     }
   11062             :                 }
   11063             :             }
   11064             : 
   11065         134 :             if (cs == nullptr)
   11066             :             {
   11067          44 :                 auto vertCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
   11068          44 :                 if (vertCRS)
   11069             :                 {
   11070          44 :                     if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
   11071             :                     {
   11072          30 :                         auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
   11073          30 :                         if (baseCRS)
   11074             :                         {
   11075          30 :                             proj_destroy(vertCRS);
   11076          30 :                             vertCRS = baseCRS;
   11077             :                         }
   11078             :                     }
   11079             : 
   11080          44 :                     cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
   11081          44 :                     proj_destroy(vertCRS);
   11082             :                 }
   11083             :             }
   11084             :         }
   11085             :         else
   11086             :         {
   11087        8311 :             cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   11088             :         }
   11089             : 
   11090        8445 :         if (cs)
   11091             :         {
   11092        8445 :             const char *pszName = nullptr;
   11093        8445 :             const char *pszOrientation = nullptr;
   11094        8445 :             double dfConvFactor = 0.0;
   11095        8445 :             proj_cs_get_axis_info(ctxt, cs, iAxisModified, &pszName, nullptr,
   11096             :                                   &pszOrientation, &dfConvFactor, nullptr,
   11097             :                                   nullptr, nullptr);
   11098             : 
   11099        8445 :             if (pdfConvUnit != nullptr)
   11100             :             {
   11101         101 :                 *pdfConvUnit = dfConvFactor;
   11102             :             }
   11103             : 
   11104        8445 :             if (pszName && pszOrientation)
   11105             :             {
   11106        8445 :                 d->m_osAxisName[iAxis] = pszName;
   11107        8445 :                 if (peOrientation)
   11108             :                 {
   11109        8336 :                     if (EQUAL(pszOrientation, "NORTH"))
   11110        5353 :                         *peOrientation = OAO_North;
   11111        2983 :                     else if (EQUAL(pszOrientation, "EAST"))
   11112        2907 :                         *peOrientation = OAO_East;
   11113          76 :                     else if (EQUAL(pszOrientation, "SOUTH"))
   11114          65 :                         *peOrientation = OAO_South;
   11115          11 :                     else if (EQUAL(pszOrientation, "WEST"))
   11116           0 :                         *peOrientation = OAO_West;
   11117          11 :                     else if (EQUAL(pszOrientation, "UP"))
   11118           1 :                         *peOrientation = OAO_Up;
   11119          10 :                     else if (EQUAL(pszOrientation, "DOWN"))
   11120           0 :                         *peOrientation = OAO_Down;
   11121             :                 }
   11122        8445 :                 proj_destroy(cs);
   11123        8445 :                 d->undoDemoteFromBoundCRS();
   11124        8445 :                 return d->m_osAxisName[iAxis].c_str();
   11125             :             }
   11126           0 :             proj_destroy(cs);
   11127             :         }
   11128           0 :         d->undoDemoteFromBoundCRS();
   11129             :     }
   11130             : 
   11131             :     /* -------------------------------------------------------------------- */
   11132             :     /*      Find the target node.                                           */
   11133             :     /* -------------------------------------------------------------------- */
   11134           0 :     const OGR_SRSNode *poNode = nullptr;
   11135             : 
   11136           0 :     if (pszTargetKey == nullptr)
   11137           0 :         poNode = GetRoot();
   11138             :     else
   11139           0 :         poNode = GetAttrNode(pszTargetKey);
   11140             : 
   11141           0 :     if (poNode == nullptr)
   11142           0 :         return nullptr;
   11143             : 
   11144             :     /* -------------------------------------------------------------------- */
   11145             :     /*      Find desired child AXIS.                                        */
   11146             :     /* -------------------------------------------------------------------- */
   11147           0 :     const OGR_SRSNode *poAxis = nullptr;
   11148           0 :     const int nChildCount = poNode->GetChildCount();
   11149             : 
   11150           0 :     for (int iChild = 0; iChild < nChildCount; iChild++)
   11151             :     {
   11152           0 :         const OGR_SRSNode *poChild = poNode->GetChild(iChild);
   11153             : 
   11154           0 :         if (!EQUAL(poChild->GetValue(), "AXIS"))
   11155           0 :             continue;
   11156             : 
   11157           0 :         if (iAxis == 0)
   11158             :         {
   11159           0 :             poAxis = poChild;
   11160           0 :             break;
   11161             :         }
   11162           0 :         iAxis--;
   11163             :     }
   11164             : 
   11165           0 :     if (poAxis == nullptr)
   11166           0 :         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             :     {
   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             :         else
   11193             :         {
   11194           0 :             CPLDebug("OSR", "Unrecognized orientation value '%s'.",
   11195             :                      pszOrientation);
   11196             :         }
   11197             :     }
   11198             : 
   11199           0 :     return poAxis->GetChild(0)->GetValue();
   11200             : }
   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          13 : const char *OSRGetAxis(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11212             :                        int iAxis, OGRAxisOrientation *peOrientation)
   11213             : 
   11214             : {
   11215          13 :     VALIDATE_POINTER1(hSRS, "OSRGetAxis", nullptr);
   11216             : 
   11217          13 :     return ToPointer(hSRS)->GetAxis(pszTargetKey, iAxis, peOrientation);
   11218             : }
   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         396 : const char *OSRAxisEnumToName(OGRAxisOrientation eOrientation)
   11233             : 
   11234             : {
   11235         396 :     if (eOrientation == OAO_North)
   11236         198 :         return "NORTH";
   11237         198 :     if (eOrientation == OAO_East)
   11238         198 :         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             : }
   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         198 : OGRErr OGRSpatialReference::SetAxes(const char *pszTargetKey,
   11275             :                                     const char *pszXAxisName,
   11276             :                                     OGRAxisOrientation eXAxisOrientation,
   11277             :                                     const char *pszYAxisName,
   11278             :                                     OGRAxisOrientation eYAxisOrientation)
   11279             : 
   11280             : {
   11281         396 :     TAKE_OPTIONAL_LOCK();
   11282             : 
   11283             :     /* -------------------------------------------------------------------- */
   11284             :     /*      Find the target node.                                           */
   11285             :     /* -------------------------------------------------------------------- */
   11286         198 :     OGR_SRSNode *poNode = nullptr;
   11287             : 
   11288         198 :     if (pszTargetKey == nullptr)
   11289         198 :         poNode = GetRoot();
   11290             :     else
   11291           0 :         poNode = GetAttrNode(pszTargetKey);
   11292             : 
   11293         198 :     if (poNode == nullptr)
   11294           0 :         return OGRERR_FAILURE;
   11295             : 
   11296             :     /* -------------------------------------------------------------------- */
   11297             :     /*      Strip any existing AXIS children.                               */
   11298             :     /* -------------------------------------------------------------------- */
   11299         594 :     while (poNode->FindChild("AXIS") >= 0)
   11300         396 :         poNode->DestroyChild(poNode->FindChild("AXIS"));
   11301             : 
   11302             :     /* -------------------------------------------------------------------- */
   11303             :     /*      Insert desired axes                                             */
   11304             :     /* -------------------------------------------------------------------- */
   11305         198 :     OGR_SRSNode *poAxis = new OGR_SRSNode("AXIS");
   11306             : 
   11307         198 :     poAxis->AddChild(new OGR_SRSNode(pszXAxisName));
   11308         198 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eXAxisOrientation)));
   11309             : 
   11310         198 :     poNode->AddChild(poAxis);
   11311             : 
   11312         198 :     poAxis = new OGR_SRSNode("AXIS");
   11313             : 
   11314         198 :     poAxis->AddChild(new OGR_SRSNode(pszYAxisName));
   11315         198 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eYAxisOrientation)));
   11316             : 
   11317         198 :     poNode->AddChild(poAxis);
   11318             : 
   11319         198 :     return OGRERR_NONE;
   11320             : }
   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           0 : OGRErr OSRSetAxes(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11331             :                   const char *pszXAxisName,
   11332             :                   OGRAxisOrientation eXAxisOrientation,
   11333             :                   const char *pszYAxisName,
   11334             :                   OGRAxisOrientation eYAxisOrientation)
   11335             : {
   11336           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAxes", OGRERR_FAILURE);
   11337             : 
   11338           0 :     return ToPointer(hSRS)->SetAxes(pszTargetKey, pszXAxisName,
   11339             :                                     eXAxisOrientation, pszYAxisName,
   11340           0 :                                     eYAxisOrientation);
   11341             : }
   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           5 : OGRErr OSRExportToMICoordSys(OGRSpatialReferenceH hSRS, char **ppszReturn)
   11353             : 
   11354             : {
   11355           5 :     VALIDATE_POINTER1(hSRS, "OSRExportToMICoordSys", OGRERR_FAILURE);
   11356             : 
   11357           5 :     *ppszReturn = nullptr;
   11358             : 
   11359           5 :     return ToPointer(hSRS)->exportToMICoordSys(ppszReturn);
   11360             : }
   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           7 : OGRErr OGRSpatialReference::exportToMICoordSys(char **ppszResult) const
   11382             : 
   11383             : {
   11384           7 :     *ppszResult = MITABSpatialRef2CoordSys(this);
   11385           7 :     if (*ppszResult != nullptr && strlen(*ppszResult) > 0)
   11386           7 :         return OGRERR_NONE;
   11387             : 
   11388           0 :     return OGRERR_FAILURE;
   11389             : }
   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           3 : OGRErr OSRImportFromMICoordSys(OGRSpatialReferenceH hSRS,
   11402             :                                const char *pszCoordSys)
   11403             : 
   11404             : {
   11405           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromMICoordSys", OGRERR_FAILURE);
   11406             : 
   11407           3 :     return ToPointer(hSRS)->importFromMICoordSys(pszCoordSys);
   11408             : }
   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          17 : OGRErr OGRSpatialReference::importFromMICoordSys(const char *pszCoordSys)
   11429             : 
   11430             : {
   11431          17 :     OGRSpatialReference *poResult = MITABCoordSys2SpatialRef(pszCoordSys);
   11432             : 
   11433          17 :     if (poResult == nullptr)
   11434           0 :         return OGRERR_FAILURE;
   11435             : 
   11436          17 :     *this = *poResult;
   11437          17 :     delete poResult;
   11438             : 
   11439          17 :     return OGRERR_NONE;
   11440             : }
   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        8697 : double OSRCalcInvFlattening(double dfSemiMajor, double dfSemiMinor)
   11456             : {
   11457        8697 :     if (fabs(dfSemiMajor - dfSemiMinor) < 1e-1)
   11458          27 :         return 0;
   11459        8670 :     if (dfSemiMajor <= 0 || dfSemiMinor <= 0 || dfSemiMinor > dfSemiMajor)
   11460             :     {
   11461           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11462             :                  "OSRCalcInvFlattening(): Wrong input values");
   11463           0 :         return 0;
   11464             :     }
   11465             : 
   11466        8670 :     return dfSemiMajor / (dfSemiMajor - dfSemiMinor);
   11467             : }
   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         655 : double OSRCalcSemiMinorFromInvFlattening(double dfSemiMajor,
   11483             :                                          double dfInvFlattening)
   11484             : {
   11485         655 :     if (fabs(dfInvFlattening) < 0.000000000001)
   11486         103 :         return dfSemiMajor;
   11487         552 :     if (dfSemiMajor <= 0.0 || dfInvFlattening <= 1.0)
   11488             :     {
   11489           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11490             :                  "OSRCalcSemiMinorFromInvFlattening(): Wrong input values");
   11491           0 :         return dfSemiMajor;
   11492             :     }
   11493             : 
   11494         552 :     return dfSemiMajor * (1.0 - 1.0 / dfInvFlattening);
   11495             : }
   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        1003 : OGRSpatialReference *OGRSpatialReference::GetWGS84SRS()
   11517             : {
   11518        1003 :     CPLMutexHolderD(&hMutex);
   11519        1003 :     if (poSRSWGS84 == nullptr)
   11520             :     {
   11521           5 :         poSRSWGS84 = new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
   11522           5 :         poSRSWGS84->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
   11523             :     }
   11524        2006 :     return poSRSWGS84;
   11525             : }
   11526             : 
   11527             : /************************************************************************/
   11528             : /*                        CleanupSRSWGS84Mutex()                        */
   11529             : /************************************************************************/
   11530             : 
   11531        1134 : static void CleanupSRSWGS84Mutex()
   11532             : {
   11533        1134 :     if (hMutex != nullptr)
   11534             :     {
   11535           3 :         poSRSWGS84->Release();
   11536           3 :         poSRSWGS84 = nullptr;
   11537           3 :         CPLDestroyMutex(hMutex);
   11538           3 :         hMutex = nullptr;
   11539             :     }
   11540        1134 : }
   11541             : 
   11542             : /************************************************************************/
   11543             : /*                         OSRImportFromProj4()                         */
   11544             : /************************************************************************/
   11545             : /**
   11546             :  * \brief Import PROJ coordinate string.
   11547             :  *
   11548             :  * This function is the same as OGRSpatialReference::importFromProj4().
   11549             :  */
   11550         220 : OGRErr OSRImportFromProj4(OGRSpatialReferenceH hSRS, const char *pszProj4)
   11551             : 
   11552             : {
   11553         220 :     VALIDATE_POINTER1(hSRS, "OSRImportFromProj4", OGRERR_FAILURE);
   11554             : 
   11555         220 :     return OGRSpatialReference::FromHandle(hSRS)->importFromProj4(pszProj4);
   11556             : }
   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         766 : OGRErr OGRSpatialReference::importFromProj4(const char *pszProj4)
   11584             : 
   11585             : {
   11586        1532 :     TAKE_OPTIONAL_LOCK();
   11587             : 
   11588         766 :     if (strlen(pszProj4) >= 10000)
   11589             :     {
   11590           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Too long PROJ string");
   11591           1 :         return OGRERR_CORRUPT_DATA;
   11592             :     }
   11593             : 
   11594             :     /* -------------------------------------------------------------------- */
   11595             :     /*      Clear any existing definition.                                  */
   11596             :     /* -------------------------------------------------------------------- */
   11597         765 :     Clear();
   11598             : 
   11599         765 :     CPLString osProj4(pszProj4);
   11600         765 :     if (osProj4.find("type=crs") == std::string::npos)
   11601             :     {
   11602         756 :         osProj4 += " +type=crs";
   11603             :     }
   11604             : 
   11605         767 :     if (osProj4.find("+init=epsg:") != std::string::npos &&
   11606           2 :         getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr)
   11607             :     {
   11608           2 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11609             :                      "+init=epsg:XXXX syntax is deprecated. It might return "
   11610             :                      "a CRS with a non-EPSG compliant axis order.");
   11611             :     }
   11612         765 :     proj_context_use_proj4_init_rules(d->getPROJContext(), true);
   11613         765 :     d->setPjCRS(proj_create(d->getPROJContext(), osProj4.c_str()));
   11614         765 :     proj_context_use_proj4_init_rules(d->getPROJContext(), false);
   11615         765 :     return d->m_pj_crs ? OGRERR_NONE : OGRERR_CORRUPT_DATA;
   11616             : }
   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         455 : OGRErr CPL_STDCALL OSRExportToProj4(OGRSpatialReferenceH hSRS,
   11634             :                                     char **ppszReturn)
   11635             : 
   11636             : {
   11637         455 :     VALIDATE_POINTER1(hSRS, "OSRExportToProj4", OGRERR_FAILURE);
   11638             : 
   11639         455 :     *ppszReturn = nullptr;
   11640             : 
   11641         455 :     return OGRSpatialReference::FromHandle(hSRS)->exportToProj4(ppszReturn);
   11642             : }
   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        1534 : OGRErr OGRSpatialReference::exportToProj4(char **ppszProj4) const
   11686             : 
   11687             : {
   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        3068 :     std::lock_guard oLock(d->m_mutex);
   11692             : 
   11693        1534 :     d->refreshProjObj();
   11694        1534 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
   11695             :     {
   11696           4 :         *ppszProj4 = CPLStrdup("");
   11697           4 :         return OGRERR_FAILURE;
   11698             :     }
   11699             : 
   11700             :     // OSR_USE_ETMERC is here just for legacy
   11701        1530 :     bool bForceApproxTMerc = false;
   11702        1530 :     const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr);
   11703        1530 :     if (pszUseETMERC && pszUseETMERC[0])
   11704             :     {
   11705           0 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11706             :                      "OSR_USE_ETMERC is a legacy configuration option, which "
   11707             :                      "now has only effect when set to NO (YES is the default). "
   11708             :                      "Use OSR_USE_APPROX_TMERC=YES instead");
   11709           0 :         bForceApproxTMerc = !CPLTestBool(pszUseETMERC);
   11710             :     }
   11711             :     else
   11712             :     {
   11713             :         const char *pszUseApproxTMERC =
   11714        1530 :             CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr);
   11715        1530 :         if (pszUseApproxTMERC && pszUseApproxTMERC[0])
   11716             :         {
   11717           2 :             bForceApproxTMerc = CPLTestBool(pszUseApproxTMERC);
   11718             :         }
   11719             :     }
   11720        1530 :     const char *options[] = {
   11721        1530 :         bForceApproxTMerc ? "USE_APPROX_TMERC=YES" : nullptr, nullptr};
   11722             : 
   11723        1530 :     const char *projString = proj_as_proj_string(
   11724        1530 :         d->getPROJContext(), d->m_pj_crs, PJ_PROJ_4, options);
   11725             : 
   11726        1530 :     PJ *boundCRS = nullptr;
   11727        3056 :     if (projString &&
   11728        1526 :         (strstr(projString, "+datum=") == nullptr ||
   11729        3066 :          d->m_pjType == PJ_TYPE_COMPOUND_CRS) &&
   11730         582 :         CPLTestBool(
   11731             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4", "YES")))
   11732             :     {
   11733         582 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   11734         582 :             d->getPROJContext(), d->m_pj_crs, true,
   11735         582 :             strstr(projString, "+datum=") == nullptr);
   11736         582 :         if (boundCRS)
   11737             :         {
   11738         227 :             projString = proj_as_proj_string(d->getPROJContext(), boundCRS,
   11739             :                                              PJ_PROJ_4, options);
   11740             :         }
   11741             :     }
   11742             : 
   11743        1530 :     if (projString == nullptr)
   11744             :     {
   11745           4 :         *ppszProj4 = CPLStrdup("");
   11746           4 :         proj_destroy(boundCRS);
   11747           4 :         return OGRERR_FAILURE;
   11748             :     }
   11749        1526 :     *ppszProj4 = CPLStrdup(projString);
   11750        1526 :     proj_destroy(boundCRS);
   11751        1526 :     char *pszTypeCrs = strstr(*ppszProj4, " +type=crs");
   11752        1526 :     if (pszTypeCrs)
   11753        1526 :         *pszTypeCrs = '\0';
   11754        1526 :     return OGRERR_NONE;
   11755             : }
   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         235 : OGRErr OGRSpatialReference::morphToESRI()
   11779             : 
   11780             : {
   11781         235 :     TAKE_OPTIONAL_LOCK();
   11782             : 
   11783         235 :     d->refreshProjObj();
   11784         235 :     d->setMorphToESRI(true);
   11785             : 
   11786         470 :     return OGRERR_NONE;
   11787             : }
   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          71 : OGRErr OSRMorphToESRI(OGRSpatialReferenceH hSRS)
   11800             : 
   11801             : {
   11802          71 :     VALIDATE_POINTER1(hSRS, "OSRMorphToESRI", OGRERR_FAILURE);
   11803             : 
   11804          71 :     return OGRSpatialReference::FromHandle(hSRS)->morphToESRI();
   11805             : }
   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          21 : OGRErr OGRSpatialReference::morphFromESRI()
   11831             : 
   11832             : {
   11833          21 :     TAKE_OPTIONAL_LOCK();
   11834             : 
   11835          21 :     d->refreshProjObj();
   11836          21 :     d->setMorphToESRI(false);
   11837             : 
   11838          42 :     return OGRERR_NONE;
   11839             : }
   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          20 : OGRErr OSRMorphFromESRI(OGRSpatialReferenceH hSRS)
   11852             : 
   11853             : {
   11854          20 :     VALIDATE_POINTER1(hSRS, "OSRMorphFromESRI", OGRERR_FAILURE);
   11855             : 
   11856          20 :     return OGRSpatialReference::FromHandle(hSRS)->morphFromESRI();
   11857             : }
   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        1482 : OGRSpatialReference::FindMatches(CSLConstList papszOptions, int *pnEntries,
   11891             :                                  int **ppanMatchConfidence) const
   11892             : {
   11893        2964 :     TAKE_OPTIONAL_LOCK();
   11894             : 
   11895        1482 :     CPL_IGNORE_RET_VAL(papszOptions);
   11896             : 
   11897        1482 :     if (pnEntries)
   11898        1482 :         *pnEntries = 0;
   11899        1482 :     if (ppanMatchConfidence)
   11900        1482 :         *ppanMatchConfidence = nullptr;
   11901             : 
   11902        1482 :     d->refreshProjObj();
   11903        1482 :     if (!d->m_pj_crs)
   11904           0 :         return nullptr;
   11905             : 
   11906        1482 :     int *panConfidence = nullptr;
   11907        1482 :     auto ctxt = d->getPROJContext();
   11908             :     auto list =
   11909        1482 :         proj_identify(ctxt, d->m_pj_crs, nullptr, nullptr, &panConfidence);
   11910        1482 :     if (!list)
   11911           0 :         return nullptr;
   11912             : 
   11913        1482 :     const int nMatches = proj_list_get_count(list);
   11914             : 
   11915        1482 :     if (pnEntries)
   11916        1482 :         *pnEntries = static_cast<int>(nMatches);
   11917             :     OGRSpatialReferenceH *pahRet = static_cast<OGRSpatialReferenceH *>(
   11918        1482 :         CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   11919        1482 :     if (ppanMatchConfidence)
   11920             :     {
   11921        1482 :         *ppanMatchConfidence =
   11922        1482 :             static_cast<int *>(CPLMalloc(sizeof(int) * (nMatches + 1)));
   11923             :     }
   11924             : 
   11925        1482 :     bool bSortAgain = false;
   11926             : 
   11927        4350 :     for (int i = 0; i < nMatches; i++)
   11928             :     {
   11929        2868 :         PJ *obj = proj_list_get(ctxt, list, i);
   11930        2868 :         CPLAssert(obj);
   11931        2868 :         OGRSpatialReference *poSRS = new OGRSpatialReference();
   11932        2868 :         poSRS->d->setPjCRS(obj);
   11933        2868 :         pahRet[i] = ToHandle(poSRS);
   11934             : 
   11935             :         // Identify matches that only differ by axis order
   11936           9 :         if (panConfidence[i] == 50 && GetAxesCount() == 2 &&
   11937        2886 :             poSRS->GetAxesCount() == 2 &&
   11938        2877 :             GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2})
   11939             :         {
   11940           9 :             OGRAxisOrientation eThisAxis0 = OAO_Other;
   11941           9 :             OGRAxisOrientation eThisAxis1 = OAO_Other;
   11942           9 :             OGRAxisOrientation eSRSAxis0 = OAO_Other;
   11943           9 :             OGRAxisOrientation eSRSAxis1 = OAO_Other;
   11944           9 :             GetAxis(nullptr, 0, &eThisAxis0);
   11945           9 :             GetAxis(nullptr, 1, &eThisAxis1);
   11946           9 :             poSRS->GetAxis(nullptr, 0, &eSRSAxis0);
   11947           9 :             poSRS->GetAxis(nullptr, 1, &eSRSAxis1);
   11948           9 :             if (eThisAxis0 == OAO_East && eThisAxis1 == OAO_North &&
   11949           9 :                 eSRSAxis0 == OAO_North && eSRSAxis1 == OAO_East)
   11950             :             {
   11951             :                 auto pj_crs_normalized =
   11952           9 :                     proj_normalize_for_visualization(ctxt, poSRS->d->m_pj_crs);
   11953           9 :                 if (pj_crs_normalized)
   11954             :                 {
   11955           9 :                     if (proj_is_equivalent_to(d->m_pj_crs, pj_crs_normalized,
   11956           9 :                                               PJ_COMP_EQUIVALENT))
   11957             :                     {
   11958           3 :                         bSortAgain = true;
   11959           3 :                         panConfidence[i] = 90;
   11960           3 :                         poSRS->SetDataAxisToSRSAxisMapping({2, 1});
   11961             :                     }
   11962           9 :                     proj_destroy(pj_crs_normalized);
   11963             :                 }
   11964             :             }
   11965             :         }
   11966             : 
   11967        2868 :         if (ppanMatchConfidence)
   11968        2868 :             (*ppanMatchConfidence)[i] = panConfidence[i];
   11969             :     }
   11970             : 
   11971        1482 :     if (bSortAgain)
   11972             :     {
   11973           3 :         std::vector<int> anIndices;
   11974          12 :         for (int i = 0; i < nMatches; ++i)
   11975           9 :             anIndices.push_back(i);
   11976             : 
   11977           3 :         std::stable_sort(anIndices.begin(), anIndices.end(),
   11978           9 :                          [&panConfidence](int i, int j)
   11979           9 :                          { return panConfidence[i] > panConfidence[j]; });
   11980             : 
   11981             :         OGRSpatialReferenceH *pahRetSorted =
   11982             :             static_cast<OGRSpatialReferenceH *>(
   11983           3 :                 CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   11984          12 :         for (int i = 0; i < nMatches; ++i)
   11985             :         {
   11986           9 :             pahRetSorted[i] = pahRet[anIndices[i]];
   11987           9 :             if (ppanMatchConfidence)
   11988           9 :                 (*ppanMatchConfidence)[i] = panConfidence[anIndices[i]];
   11989             :         }
   11990           3 :         CPLFree(pahRet);
   11991           3 :         pahRet = pahRetSorted;
   11992             :     }
   11993             : 
   11994        1482 :     pahRet[nMatches] = nullptr;
   11995        1482 :     proj_list_destroy(list);
   11996        1482 :     proj_int_list_destroy(panConfidence);
   11997             : 
   11998        1482 :     return pahRet;
   11999             : }
   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       46311 : OGRErr OGRSpatialReference::importFromEPSGA(int nCode)
   12033             : 
   12034             : {
   12035       92622 :     TAKE_OPTIONAL_LOCK();
   12036             : 
   12037       46311 :     Clear();
   12038             : 
   12039             :     const char *pszUseNonDeprecated =
   12040       46311 :         CPLGetConfigOption("OSR_USE_NON_DEPRECATED", nullptr);
   12041             :     const bool bUseNonDeprecated =
   12042       46311 :         CPLTestBool(pszUseNonDeprecated ? pszUseNonDeprecated : "YES");
   12043       46311 :     const bool bAddTOWGS84 = CPLTestBool(
   12044             :         CPLGetConfigOption("OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG", "NO"));
   12045       46311 :     auto tlsCache = OSRGetProjTLSCache();
   12046       46311 :     if (tlsCache)
   12047             :     {
   12048             :         auto cachedObj =
   12049       46311 :             tlsCache->GetPJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84);
   12050       46311 :         if (cachedObj)
   12051             :         {
   12052       36093 :             d->setPjCRS(cachedObj);
   12053       36093 :             return OGRERR_NONE;
   12054             :         }
   12055             :     }
   12056             : 
   12057       20436 :     CPLString osCode;
   12058       10218 :     osCode.Printf("%d", nCode);
   12059             :     PJ *obj;
   12060       10218 :     constexpr int FIRST_NON_DEPRECATED_ESRI_CODE = 53001;
   12061       10218 :     if (nCode < FIRST_NON_DEPRECATED_ESRI_CODE)
   12062             :     {
   12063       10213 :         obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   12064             :                                         osCode.c_str(), PJ_CATEGORY_CRS, true,
   12065             :                                         nullptr);
   12066       10213 :         if (!obj)
   12067             :         {
   12068          24 :             return OGRERR_FAILURE;
   12069             :         }
   12070             :     }
   12071             :     else
   12072             :     {
   12073             :         // Likely to be an ESRI CRS...
   12074           5 :         CPLErr eLastErrorType = CE_None;
   12075           5 :         CPLErrorNum eLastErrorNum = CPLE_None;
   12076           5 :         std::string osLastErrorMsg;
   12077           5 :         bool bIsESRI = false;
   12078             :         {
   12079          10 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
   12080           5 :             CPLErrorReset();
   12081           5 :             obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   12082             :                                             osCode.c_str(), PJ_CATEGORY_CRS,
   12083             :                                             true, nullptr);
   12084           5 :             if (!obj)
   12085             :             {
   12086           2 :                 eLastErrorType = CPLGetLastErrorType();
   12087           2 :                 eLastErrorNum = CPLGetLastErrorNo();
   12088           2 :                 osLastErrorMsg = CPLGetLastErrorMsg();
   12089           2 :                 obj = proj_create_from_database(d->getPROJContext(), "ESRI",
   12090             :                                                 osCode.c_str(), PJ_CATEGORY_CRS,
   12091             :                                                 true, nullptr);
   12092           2 :                 if (obj)
   12093           1 :                     bIsESRI = true;
   12094             :             }
   12095             :         }
   12096           5 :         if (!obj)
   12097             :         {
   12098           1 :             if (eLastErrorType != CE_None)
   12099           1 :                 CPLError(eLastErrorType, eLastErrorNum, "%s",
   12100             :                          osLastErrorMsg.c_str());
   12101           1 :             return OGRERR_FAILURE;
   12102             :         }
   12103           4 :         if (bIsESRI)
   12104             :         {
   12105           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12106             :                      "EPSG:%d is not a valid CRS code, but ESRI:%d is. "
   12107             :                      "Assuming ESRI:%d was meant",
   12108             :                      nCode, nCode, nCode);
   12109             :         }
   12110             :     }
   12111             : 
   12112       10193 :     if (bUseNonDeprecated && proj_is_deprecated(obj))
   12113             :     {
   12114         410 :         auto list = proj_get_non_deprecated(d->getPROJContext(), obj);
   12115         410 :         if (list)
   12116             :         {
   12117         410 :             const auto count = proj_list_get_count(list);
   12118         410 :             if (count == 1)
   12119             :             {
   12120             :                 auto nonDeprecated =
   12121         359 :                     proj_list_get(d->getPROJContext(), list, 0);
   12122         359 :                 if (nonDeprecated)
   12123             :                 {
   12124         359 :                     if (pszUseNonDeprecated == nullptr)
   12125             :                     {
   12126             :                         const char *pszNewAuth =
   12127         359 :                             proj_get_id_auth_name(nonDeprecated, 0);
   12128             :                         const char *pszNewCode =
   12129         359 :                             proj_get_id_code(nonDeprecated, 0);
   12130         359 :                         CPLError(CE_Warning, CPLE_AppDefined,
   12131             :                                  "CRS EPSG:%d is deprecated. "
   12132             :                                  "Its non-deprecated replacement %s:%s "
   12133             :                                  "will be used instead. "
   12134             :                                  "To use the original CRS, set the "
   12135             :                                  "OSR_USE_NON_DEPRECATED "
   12136             :                                  "configuration option to NO.",
   12137             :                                  nCode, pszNewAuth ? pszNewAuth : "(null)",
   12138             :                                  pszNewCode ? pszNewCode : "(null)");
   12139             :                     }
   12140         359 :                     proj_destroy(obj);
   12141         359 :                     obj = nonDeprecated;
   12142             :                 }
   12143             :             }
   12144             :         }
   12145         410 :         proj_list_destroy(list);
   12146             :     }
   12147             : 
   12148       10193 :     if (bAddTOWGS84)
   12149             :     {
   12150           1 :         auto boundCRS = proj_crs_create_bound_crs_to_WGS84(d->getPROJContext(),
   12151             :                                                            obj, nullptr);
   12152           1 :         if (boundCRS)
   12153             :         {
   12154           1 :             proj_destroy(obj);
   12155           1 :             obj = boundCRS;
   12156             :         }
   12157             :     }
   12158             : 
   12159       10193 :     d->setPjCRS(obj);
   12160             : 
   12161       10193 :     if (tlsCache)
   12162             :     {
   12163       10193 :         tlsCache->CachePJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84,
   12164             :                                      obj);
   12165             :     }
   12166             : 
   12167       10193 :     return OGRERR_NONE;
   12168             : }
   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          18 : OGRErr OGRSpatialReference::AddGuessedTOWGS84()
   12190             : {
   12191          36 :     TAKE_OPTIONAL_LOCK();
   12192             : 
   12193          18 :     d->refreshProjObj();
   12194          18 :     if (!d->m_pj_crs)
   12195           0 :         return OGRERR_FAILURE;
   12196          18 :     auto boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   12197          18 :         d->getPROJContext(), d->m_pj_crs, false, true);
   12198          18 :     if (!boundCRS)
   12199             :     {
   12200           0 :         return OGRERR_FAILURE;
   12201             :     }
   12202          18 :     d->setPjCRS(boundCRS);
   12203          18 :     return OGRERR_NONE;
   12204             : }
   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           2 : OGRErr OSRAddGuessedTOWGS84(OGRSpatialReferenceH hSRS)
   12220             : 
   12221             : {
   12222           2 :     VALIDATE_POINTER1(hSRS, "OSRAddGuessedTOWGS84", OGRERR_FAILURE);
   12223             : 
   12224           2 :     return OGRSpatialReference::FromHandle(hSRS)->AddGuessedTOWGS84();
   12225             : }
   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           3 : OGRErr CPL_STDCALL OSRImportFromEPSGA(OGRSpatialReferenceH hSRS, int nCode)
   12239             : 
   12240             : {
   12241           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSGA", OGRERR_FAILURE);
   12242             : 
   12243           3 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSGA(nCode);
   12244             : }
   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       41286 : OGRErr OGRSpatialReference::importFromEPSG(int nCode)
   12271             : 
   12272             : {
   12273       41286 :     return importFromEPSGA(nCode);
   12274             : }
   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        1565 : OGRErr CPL_STDCALL OSRImportFromEPSG(OGRSpatialReferenceH hSRS, int nCode)
   12288             : 
   12289             : {
   12290        1565 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSG", OGRERR_FAILURE);
   12291             : 
   12292        1565 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSG(nCode);
   12293             : }
   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        1127 : int OGRSpatialReference::EPSGTreatsAsLatLong() const
   12322             : 
   12323             : {
   12324        2254 :     TAKE_OPTIONAL_LOCK();
   12325             : 
   12326        1127 :     if (!IsGeographic())
   12327         786 :         return FALSE;
   12328             : 
   12329         341 :     d->demoteFromBoundCRS();
   12330             : 
   12331         341 :     bool ret = false;
   12332         341 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12333             :     {
   12334             :         auto horizCRS =
   12335           3 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
   12336           3 :         if (horizCRS)
   12337             :         {
   12338             :             auto cs =
   12339           3 :                 proj_crs_get_coordinate_system(d->getPROJContext(), horizCRS);
   12340           3 :             if (cs)
   12341             :             {
   12342           3 :                 const char *pszDirection = nullptr;
   12343           3 :                 if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12344             :                                           nullptr, &pszDirection, nullptr,
   12345           3 :                                           nullptr, nullptr, nullptr))
   12346             :                 {
   12347           3 :                     if (EQUAL(pszDirection, "north"))
   12348             :                     {
   12349           3 :                         ret = true;
   12350             :                     }
   12351             :                 }
   12352             : 
   12353           3 :                 proj_destroy(cs);
   12354             :             }
   12355             : 
   12356           3 :             proj_destroy(horizCRS);
   12357             :         }
   12358             :     }
   12359             :     else
   12360             :     {
   12361             :         auto cs =
   12362         338 :             proj_crs_get_coordinate_system(d->getPROJContext(), d->m_pj_crs);
   12363         338 :         if (cs)
   12364             :         {
   12365         338 :             const char *pszDirection = nullptr;
   12366         338 :             if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12367             :                                       nullptr, &pszDirection, nullptr, nullptr,
   12368         338 :                                       nullptr, nullptr))
   12369             :             {
   12370         338 :                 if (EQUAL(pszDirection, "north"))
   12371             :                 {
   12372         292 :                     ret = true;
   12373             :                 }
   12374             :             }
   12375             : 
   12376         338 :             proj_destroy(cs);
   12377             :         }
   12378             :     }
   12379         341 :     d->undoDemoteFromBoundCRS();
   12380             : 
   12381         341 :     return ret;
   12382             : }
   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         180 : int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH hSRS)
   12396             : 
   12397             : {
   12398         180 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsLatLong", OGRERR_FAILURE);
   12399             : 
   12400         180 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsLatLong();
   12401             : }
   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         872 : int OGRSpatialReference::EPSGTreatsAsNorthingEasting() const
   12432             : 
   12433             : {
   12434        1744 :     TAKE_OPTIONAL_LOCK();
   12435             : 
   12436         872 :     if (!IsProjected())
   12437          48 :         return FALSE;
   12438             : 
   12439         824 :     d->demoteFromBoundCRS();
   12440             :     PJ *projCRS;
   12441         824 :     const auto ctxt = d->getPROJContext();
   12442         824 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12443             :     {
   12444           4 :         projCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   12445           4 :         if (!projCRS || proj_get_type(projCRS) != PJ_TYPE_PROJECTED_CRS)
   12446             :         {
   12447           0 :             d->undoDemoteFromBoundCRS();
   12448           0 :             proj_destroy(projCRS);
   12449           0 :             return FALSE;
   12450             :         }
   12451             :     }
   12452             :     else
   12453             :     {
   12454         820 :         projCRS = proj_clone(ctxt, d->m_pj_crs);
   12455             :     }
   12456             : 
   12457         824 :     bool ret = false;
   12458         824 :     auto cs = proj_crs_get_coordinate_system(ctxt, projCRS);
   12459         824 :     proj_destroy(projCRS);
   12460         824 :     d->undoDemoteFromBoundCRS();
   12461             : 
   12462         824 :     if (cs)
   12463             :     {
   12464         824 :         ret = isNorthEastAxisOrder(ctxt, cs);
   12465         824 :         proj_destroy(cs);
   12466             :     }
   12467             : 
   12468         824 :     return ret;
   12469             : }
   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         187 : int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH hSRS)
   12485             : 
   12486             : {
   12487         187 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsNorthingEasting", OGRERR_FAILURE);
   12488             : 
   12489         187 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsNorthingEasting();
   12490             : }
   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           1 : OGRErr OGRSpatialReference::ImportFromESRIWisconsinWKT(const char *prjName,
   12502             :                                                        double centralMeridian,
   12503             :                                                        double latOfOrigin,
   12504             :                                                        const char *unitsName,
   12505             :                                                        const char *crsName)
   12506             : {
   12507           2 :     TAKE_OPTIONAL_LOCK();
   12508             : 
   12509           1 :     if (centralMeridian < -93 || centralMeridian > -87)
   12510           0 :         return OGRERR_FAILURE;
   12511           1 :     if (latOfOrigin < 40 || latOfOrigin > 47)
   12512           0 :         return OGRERR_FAILURE;
   12513             : 
   12514             :     // If the CS name is known.
   12515           1 :     if (!prjName && !unitsName && crsName)
   12516             :     {
   12517           0 :         const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12518           0 :         PJ_OBJ_LIST *list = proj_create_from_name(
   12519             :             d->getPROJContext(), "ESRI", crsName, &type, 1, false, 1, nullptr);
   12520           0 :         if (list)
   12521             :         {
   12522           0 :             if (proj_list_get_count(list) == 1)
   12523             :             {
   12524           0 :                 auto crs = proj_list_get(d->getPROJContext(), list, 0);
   12525           0 :                 if (crs)
   12526             :                 {
   12527           0 :                     Clear();
   12528           0 :                     d->setPjCRS(crs);
   12529           0 :                     proj_list_destroy(list);
   12530           0 :                     return OGRERR_NONE;
   12531             :                 }
   12532             :             }
   12533           0 :             proj_list_destroy(list);
   12534             :         }
   12535           0 :         return OGRERR_FAILURE;
   12536             :     }
   12537             : 
   12538           1 :     if (prjName == nullptr || unitsName == nullptr)
   12539             :     {
   12540           0 :         return OGRERR_FAILURE;
   12541             :     }
   12542             : 
   12543           1 :     const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12544           1 :     PJ_OBJ_LIST *list = proj_create_from_name(d->getPROJContext(), "ESRI",
   12545             :                                               "NAD_1983_HARN_WISCRS_", &type, 1,
   12546             :                                               true, 0, nullptr);
   12547           1 :     if (list)
   12548             :     {
   12549           1 :         const auto listSize = proj_list_get_count(list);
   12550           8 :         for (int i = 0; i < listSize; i++)
   12551             :         {
   12552           8 :             auto crs = proj_list_get(d->getPROJContext(), list, i);
   12553           8 :             if (!crs)
   12554             :             {
   12555           7 :                 continue;
   12556             :             }
   12557             : 
   12558           8 :             auto conv = proj_crs_get_coordoperation(d->getPROJContext(), crs);
   12559           8 :             if (!conv)
   12560             :             {
   12561           0 :                 proj_destroy(crs);
   12562           0 :                 continue;
   12563             :             }
   12564           8 :             const char *pszMethodCode = nullptr;
   12565           8 :             proj_coordoperation_get_method_info(
   12566             :                 d->getPROJContext(), conv, nullptr, nullptr, &pszMethodCode);
   12567           8 :             const int nMethodCode = atoi(pszMethodCode ? pszMethodCode : "0");
   12568           8 :             if (!((EQUAL(prjName, SRS_PT_TRANSVERSE_MERCATOR) &&
   12569             :                    nMethodCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) ||
   12570           3 :                   (EQUAL(prjName, "Lambert_Conformal_Conic") &&
   12571             :                    nMethodCode ==
   12572             :                        EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)))
   12573             :             {
   12574           3 :                 proj_destroy(crs);
   12575           3 :                 proj_destroy(conv);
   12576           3 :                 continue;
   12577             :             }
   12578             : 
   12579             :             auto coordSys =
   12580           5 :                 proj_crs_get_coordinate_system(d->getPROJContext(), crs);
   12581           5 :             if (!coordSys)
   12582             :             {
   12583           0 :                 proj_destroy(crs);
   12584           0 :                 proj_destroy(conv);
   12585           0 :                 continue;
   12586             :             }
   12587             : 
   12588           5 :             double dfConvFactor = 0.0;
   12589           5 :             proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
   12590             :                                   nullptr, nullptr, &dfConvFactor, nullptr,
   12591             :                                   nullptr, nullptr);
   12592           5 :             proj_destroy(coordSys);
   12593             : 
   12594           6 :             if ((EQUAL(unitsName, "meters") && dfConvFactor != 1.0) ||
   12595           1 :                 (!EQUAL(unitsName, "meters") &&
   12596           0 :                  std::fabs(dfConvFactor - CPLAtof(SRS_UL_US_FOOT_CONV)) >
   12597             :                      1e-10))
   12598             :             {
   12599           4 :                 proj_destroy(crs);
   12600           4 :                 proj_destroy(conv);
   12601           4 :                 continue;
   12602             :             }
   12603             : 
   12604           1 :             int idx_lat = proj_coordoperation_get_param_index(
   12605             :                 d->getPROJContext(), conv,
   12606             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
   12607           1 :             double valueLat = -1000;
   12608           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lat,
   12609             :                                           nullptr, nullptr, nullptr, &valueLat,
   12610             :                                           nullptr, nullptr, nullptr, nullptr,
   12611             :                                           nullptr, nullptr);
   12612           1 :             int idx_lon = proj_coordoperation_get_param_index(
   12613             :                 d->getPROJContext(), conv,
   12614             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN);
   12615           1 :             double valueLong = -1000;
   12616           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lon,
   12617             :                                           nullptr, nullptr, nullptr, &valueLong,
   12618             :                                           nullptr, nullptr, nullptr, nullptr,
   12619             :                                           nullptr, nullptr);
   12620           1 :             if (std::fabs(centralMeridian - valueLong) <= 1e-10 &&
   12621           1 :                 std::fabs(latOfOrigin - valueLat) <= 1e-10)
   12622             :             {
   12623           1 :                 Clear();
   12624           1 :                 d->setPjCRS(crs);
   12625           1 :                 proj_list_destroy(list);
   12626           1 :                 proj_destroy(conv);
   12627           1 :                 return OGRERR_NONE;
   12628             :             }
   12629             : 
   12630           0 :             proj_destroy(crs);
   12631           0 :             proj_destroy(conv);
   12632             :         }
   12633           0 :         proj_list_destroy(list);
   12634             :     }
   12635             : 
   12636           0 :     return OGRERR_FAILURE;
   12637             : }
   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          93 : OSRAxisMappingStrategy OGRSpatialReference::GetAxisMappingStrategy() const
   12659             : {
   12660          93 :     TAKE_OPTIONAL_LOCK();
   12661             : 
   12662         186 :     return d->m_axisMappingStrategy;
   12663             : }
   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          37 : OSRAxisMappingStrategy OSRGetAxisMappingStrategy(OGRSpatialReferenceH hSRS)
   12675             : {
   12676          37 :     VALIDATE_POINTER1(hSRS, "OSRGetAxisMappingStrategy", OAMS_CUSTOM);
   12677             : 
   12678          37 :     return OGRSpatialReference::FromHandle(hSRS)->GetAxisMappingStrategy();
   12679             : }
   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       90279 : void OGRSpatialReference::SetAxisMappingStrategy(
   12698             :     OSRAxisMappingStrategy strategy)
   12699             : {
   12700      180558 :     TAKE_OPTIONAL_LOCK();
   12701             : 
   12702       90279 :     d->m_axisMappingStrategy = strategy;
   12703       90279 :     d->refreshAxisMapping();
   12704       90279 : }
   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         888 : void OSRSetAxisMappingStrategy(OGRSpatialReferenceH hSRS,
   12716             :                                OSRAxisMappingStrategy strategy)
   12717             : {
   12718         888 :     VALIDATE_POINTER0(hSRS, "OSRSetAxisMappingStrategy");
   12719             : 
   12720         888 :     OGRSpatialReference::FromHandle(hSRS)->SetAxisMappingStrategy(strategy);
   12721             : }
   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    17300100 : const std::vector<int> &OGRSpatialReference::GetDataAxisToSRSAxisMapping() const
   12738             : {
   12739    17300100 :     TAKE_OPTIONAL_LOCK();
   12740             : 
   12741    34600200 :     return d->m_axisMapping;
   12742             : }
   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         232 : const int *OSRGetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12755             :                                           int *pnCount)
   12756             : {
   12757         232 :     VALIDATE_POINTER1(hSRS, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12758         232 :     VALIDATE_POINTER1(pnCount, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12759             : 
   12760             :     const auto &v =
   12761         232 :         OGRSpatialReference::FromHandle(hSRS)->GetDataAxisToSRSAxisMapping();
   12762         232 :     *pnCount = static_cast<int>(v.size());
   12763         232 :     return v.data();
   12764             : }
   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        9842 : OGRErr OGRSpatialReference::SetDataAxisToSRSAxisMapping(
   12806             :     const std::vector<int> &mapping)
   12807             : {
   12808       19684 :     TAKE_OPTIONAL_LOCK();
   12809             : 
   12810        9842 :     if (mapping.size() < 2)
   12811           0 :         return OGRERR_FAILURE;
   12812        9842 :     d->m_axisMappingStrategy = OAMS_CUSTOM;
   12813        9842 :     d->m_axisMapping = mapping;
   12814        9842 :     return OGRERR_NONE;
   12815             : }
   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          15 : OGRErr OSRSetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12831             :                                       int nMappingSize, const int *panMapping)
   12832             : {
   12833          15 :     VALIDATE_POINTER1(hSRS, "OSRSetDataAxisToSRSAxisMapping", OGRERR_FAILURE);
   12834          15 :     VALIDATE_POINTER1(panMapping, "OSRSetDataAxisToSRSAxisMapping",
   12835             :                       OGRERR_FAILURE);
   12836             : 
   12837          15 :     if (nMappingSize < 0)
   12838           0 :         return OGRERR_FAILURE;
   12839             : 
   12840          30 :     std::vector<int> mapping(nMappingSize);
   12841          15 :     if (nMappingSize)
   12842          15 :         memcpy(&mapping[0], panMapping, nMappingSize * sizeof(int));
   12843          15 :     return OGRSpatialReference::FromHandle(hSRS)->SetDataAxisToSRSAxisMapping(
   12844          15 :         mapping);
   12845             : }
   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          53 : bool OGRSpatialReference::GetAreaOfUse(double *pdfWestLongitudeDeg,
   12874             :                                        double *pdfSouthLatitudeDeg,
   12875             :                                        double *pdfEastLongitudeDeg,
   12876             :                                        double *pdfNorthLatitudeDeg,
   12877             :                                        const char **ppszAreaName) const
   12878             : {
   12879         106 :     TAKE_OPTIONAL_LOCK();
   12880             : 
   12881          53 :     d->refreshProjObj();
   12882          53 :     if (!d->m_pj_crs)
   12883             :     {
   12884           0 :         return false;
   12885             :     }
   12886          53 :     d->demoteFromBoundCRS();
   12887          53 :     const char *pszAreaName = nullptr;
   12888          53 :     int bSuccess = proj_get_area_of_use(
   12889          53 :         d->getPROJContext(), d->m_pj_crs, pdfWestLongitudeDeg,
   12890             :         pdfSouthLatitudeDeg, pdfEastLongitudeDeg, pdfNorthLatitudeDeg,
   12891             :         &pszAreaName);
   12892          53 :     d->undoDemoteFromBoundCRS();
   12893          53 :     d->m_osAreaName = pszAreaName ? pszAreaName : "";
   12894          53 :     if (ppszAreaName)
   12895           1 :         *ppszAreaName = d->m_osAreaName.c_str();
   12896          53 :     return CPL_TO_BOOL(bSuccess);
   12897             : }
   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           1 : int OSRGetAreaOfUse(OGRSpatialReferenceH hSRS, double *pdfWestLongitudeDeg,
   12910             :                     double *pdfSouthLatitudeDeg, double *pdfEastLongitudeDeg,
   12911             :                     double *pdfNorthLatitudeDeg, const char **ppszAreaName)
   12912             : {
   12913           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAreaOfUse", FALSE);
   12914             : 
   12915           1 :     return OGRSpatialReference::FromHandle(hSRS)->GetAreaOfUse(
   12916             :         pdfWestLongitudeDeg, pdfSouthLatitudeDeg, pdfEastLongitudeDeg,
   12917           1 :         pdfNorthLatitudeDeg, ppszAreaName);
   12918             : }
   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          24 : OSRGetCRSInfoListFromDatabase(const char *pszAuthName,
   12941             :                               CPL_UNUSED const OSRCRSListParameters *params,
   12942             :                               int *pnOutResultCount)
   12943             : {
   12944          24 :     int nResultCount = 0;
   12945          24 :     auto projList = proj_get_crs_info_list_from_database(
   12946             :         OSRGetProjTLSContext(), pszAuthName, nullptr, &nResultCount);
   12947          24 :     if (pnOutResultCount)
   12948          24 :         *pnOutResultCount = nResultCount;
   12949          24 :     if (!projList)
   12950             :     {
   12951           0 :         return nullptr;
   12952             :     }
   12953          24 :     auto res = new OSRCRSInfo *[nResultCount + 1];
   12954       89181 :     for (int i = 0; i < nResultCount; i++)
   12955             :     {
   12956       89157 :         res[i] = new OSRCRSInfo;
   12957      178314 :         res[i]->pszAuthName = projList[i]->auth_name
   12958       89157 :                                   ? CPLStrdup(projList[i]->auth_name)
   12959             :                                   : nullptr;
   12960       89157 :         res[i]->pszCode =
   12961       89157 :             projList[i]->code ? CPLStrdup(projList[i]->code) : nullptr;
   12962       89157 :         res[i]->pszName =
   12963       89157 :             projList[i]->name ? CPLStrdup(projList[i]->name) : nullptr;
   12964       89157 :         res[i]->eType = OSR_CRS_TYPE_OTHER;
   12965       89157 :         switch (projList[i]->type)
   12966             :         {
   12967        8727 :             case PJ_TYPE_GEOGRAPHIC_2D_CRS:
   12968        8727 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_2D;
   12969        8727 :                 break;
   12970        2811 :             case PJ_TYPE_GEOGRAPHIC_3D_CRS:
   12971        2811 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_3D;
   12972        2811 :                 break;
   12973        3066 :             case PJ_TYPE_GEOCENTRIC_CRS:
   12974        3066 :                 res[i]->eType = OSR_CRS_TYPE_GEOCENTRIC;
   12975        3066 :                 break;
   12976       67740 :             case PJ_TYPE_PROJECTED_CRS:
   12977       67740 :                 res[i]->eType = OSR_CRS_TYPE_PROJECTED;
   12978       67740 :                 break;
   12979        2808 :             case PJ_TYPE_VERTICAL_CRS:
   12980        2808 :                 res[i]->eType = OSR_CRS_TYPE_VERTICAL;
   12981        2808 :                 break;
   12982        4005 :             case PJ_TYPE_COMPOUND_CRS:
   12983        4005 :                 res[i]->eType = OSR_CRS_TYPE_COMPOUND;
   12984        4005 :                 break;
   12985           0 :             default:
   12986           0 :                 break;
   12987             :         }
   12988       89157 :         res[i]->bDeprecated = projList[i]->deprecated;
   12989       89157 :         res[i]->bBboxValid = projList[i]->bbox_valid;
   12990       89157 :         res[i]->dfWestLongitudeDeg = projList[i]->west_lon_degree;
   12991       89157 :         res[i]->dfSouthLatitudeDeg = projList[i]->south_lat_degree;
   12992       89157 :         res[i]->dfEastLongitudeDeg = projList[i]->east_lon_degree;
   12993       89157 :         res[i]->dfNorthLatitudeDeg = projList[i]->north_lat_degree;
   12994      178314 :         res[i]->pszAreaName = projList[i]->area_name
   12995       89157 :                                   ? CPLStrdup(projList[i]->area_name)
   12996             :                                   : nullptr;
   12997       89157 :         res[i]->pszProjectionMethod =
   12998       89157 :             projList[i]->projection_method_name
   12999       89157 :                 ? CPLStrdup(projList[i]->projection_method_name)
   13000             :                 : nullptr;
   13001             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
   13002             :         res[i]->pszCelestialBodyName =
   13003             :             projList[i]->celestial_body_name
   13004             :                 ? CPLStrdup(projList[i]->celestial_body_name)
   13005             :                 : nullptr;
   13006             : #else
   13007       89157 :         res[i]->pszCelestialBodyName =
   13008       89157 :             res[i]->pszAuthName && EQUAL(res[i]->pszAuthName, "EPSG")
   13009      178314 :                 ? CPLStrdup("Earth")
   13010             :                 : nullptr;
   13011             : #endif
   13012             :     }
   13013          24 :     res[nResultCount] = nullptr;
   13014          24 :     proj_crs_info_list_destroy(projList);
   13015          24 :     return res;
   13016             : }
   13017             : 
   13018             : /************************************************************************/
   13019             : /*                       OSRDestroyCRSInfoList()                        */
   13020             : /************************************************************************/
   13021             : 
   13022             : /** \brief Destroy the result returned by
   13023             :  * OSRGetCRSInfoListFromDatabase().
   13024             :  *
   13025             :  * @since GDAL 3.0
   13026             :  */
   13027          24 : void OSRDestroyCRSInfoList(OSRCRSInfo **list)
   13028             : {
   13029          24 :     if (list)
   13030             :     {
   13031       89181 :         for (int i = 0; list[i] != nullptr; i++)
   13032             :         {
   13033       89157 :             CPLFree(list[i]->pszAuthName);
   13034       89157 :             CPLFree(list[i]->pszCode);
   13035       89157 :             CPLFree(list[i]->pszName);
   13036       89157 :             CPLFree(list[i]->pszAreaName);
   13037       89157 :             CPLFree(list[i]->pszProjectionMethod);
   13038       89157 :             CPLFree(list[i]->pszCelestialBodyName);
   13039       89157 :             delete list[i];
   13040             :         }
   13041          24 :         delete[] list;
   13042             :     }
   13043          24 : }
   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           4 : char **OSRGetAuthorityListFromDatabase()
   13060             : {
   13061             :     PROJ_STRING_LIST list =
   13062           4 :         proj_get_authorities_from_database(OSRGetProjTLSContext());
   13063           4 :     if (!list)
   13064             :     {
   13065           0 :         return nullptr;
   13066             :     }
   13067           4 :     int count = 0;
   13068          24 :     while (list[count])
   13069          20 :         ++count;
   13070           4 :     char **res = static_cast<char **>(CPLCalloc(count + 1, sizeof(char *)));
   13071          24 :     for (int i = 0; i < count; ++i)
   13072          20 :         res[i] = CPLStrdup(list[i]);
   13073           4 :     proj_string_list_destroy(list);
   13074           4 :     return res;
   13075             : }
   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           1 : void OGRSpatialReference::UpdateCoordinateSystemFromGeogCRS()
   13087             : {
   13088           1 :     TAKE_OPTIONAL_LOCK();
   13089             : 
   13090           1 :     d->refreshProjObj();
   13091           1 :     if (!d->m_pj_crs)
   13092           0 :         return;
   13093           1 :     if (d->m_pjType != PJ_TYPE_PROJECTED_CRS)
   13094           0 :         return;
   13095           1 :     if (GetAxesCount() == 3)
   13096           0 :         return;
   13097           1 :     auto ctxt = d->getPROJContext();
   13098           1 :     auto baseCRS = proj_crs_get_geodetic_crs(ctxt, d->m_pj_crs);
   13099           1 :     if (!baseCRS)
   13100           0 :         return;
   13101           1 :     auto baseCRSCS = proj_crs_get_coordinate_system(ctxt, baseCRS);
   13102           1 :     if (!baseCRSCS)
   13103             :     {
   13104           0 :         proj_destroy(baseCRS);
   13105           0 :         return;
   13106             :     }
   13107           1 :     if (proj_cs_get_axis_count(ctxt, baseCRSCS) != 3)
   13108             :     {
   13109           0 :         proj_destroy(baseCRSCS);
   13110           0 :         proj_destroy(baseCRS);
   13111           0 :         return;
   13112             :     }
   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             :     {
   13116           0 :         proj_destroy(baseCRSCS);
   13117           0 :         proj_destroy(baseCRS);
   13118           0 :         proj_destroy(projCS);
   13119           0 :         return;
   13120             :     }
   13121             : 
   13122             :     PJ_AXIS_DESCRIPTION axis[3];
   13123           4 :     for (int i = 0; i < 3; i++)
   13124             :     {
   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             :                               &abbreviation, &direction, &unit_conv_factor,
   13132             :                               &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             :     }
   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             :     {
   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             :     }
   13150           1 :     if (!cs)
   13151             :     {
   13152           0 :         proj_destroy(baseCRS);
   13153           0 :         return;
   13154             :     }
   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             :                                          conversion, cs);
   13158           1 :     proj_destroy(baseCRS);
   13159           1 :     proj_destroy(conversion);
   13160           1 :     proj_destroy(cs);
   13161           1 :     d->setPjCRS(crs);
   13162             : }
   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          42 : OGRErr OGRSpatialReference::PromoteTo3D(const char *pszName)
   13181             : {
   13182          84 :     TAKE_OPTIONAL_LOCK();
   13183             : 
   13184          42 :     d->refreshProjObj();
   13185          42 :     if (!d->m_pj_crs)
   13186           0 :         return OGRERR_FAILURE;
   13187             :     auto newPj =
   13188          42 :         proj_crs_promote_to_3D(d->getPROJContext(), pszName, d->m_pj_crs);
   13189          42 :     if (!newPj)
   13190           0 :         return OGRERR_FAILURE;
   13191          42 :     d->setPjCRS(newPj);
   13192          42 :     return OGRERR_NONE;
   13193             : }
   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           3 : OGRErr OSRPromoteTo3D(OGRSpatialReferenceH hSRS, const char *pszName)
   13206             : {
   13207           3 :     VALIDATE_POINTER1(hSRS, "OSRPromoteTo3D", OGRERR_FAILURE);
   13208             : 
   13209           3 :     return OGRSpatialReference::FromHandle(hSRS)->PromoteTo3D(pszName);
   13210             : }
   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          47 : OGRErr OGRSpatialReference::DemoteTo2D(const char *pszName)
   13224             : {
   13225          94 :     TAKE_OPTIONAL_LOCK();
   13226             : 
   13227          47 :     d->refreshProjObj();
   13228          47 :     if (!d->m_pj_crs)
   13229           0 :         return OGRERR_FAILURE;
   13230             :     auto newPj =
   13231          47 :         proj_crs_demote_to_2D(d->getPROJContext(), pszName, d->m_pj_crs);
   13232          47 :     if (!newPj)
   13233           0 :         return OGRERR_FAILURE;
   13234          47 :     d->setPjCRS(newPj);
   13235          47 :     return OGRERR_NONE;
   13236             : }
   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           1 : OGRErr OSRDemoteTo2D(OGRSpatialReferenceH hSRS, const char *pszName)
   13249             : {
   13250           1 :     VALIDATE_POINTER1(hSRS, "OSRDemoteTo2D", OGRERR_FAILURE);
   13251             : 
   13252           1 :     return OGRSpatialReference::FromHandle(hSRS)->DemoteTo2D(pszName);
   13253             : }
   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         342 : int OGRSpatialReference::GetEPSGGeogCS() const
   13266             : 
   13267             : {
   13268         684 :     TAKE_OPTIONAL_LOCK();
   13269             : 
   13270             :     /* -------------------------------------------------------------------- */
   13271             :     /*      Check axis order.                                               */
   13272             :     /* -------------------------------------------------------------------- */
   13273         684 :     auto poGeogCRS = std::unique_ptr<OGRSpatialReference>(CloneGeogCS());
   13274         342 :     if (!poGeogCRS)
   13275           0 :         return -1;
   13276             : 
   13277         342 :     bool ret = false;
   13278         342 :     poGeogCRS->d->demoteFromBoundCRS();
   13279         342 :     auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   13280         342 :                                              poGeogCRS->d->m_pj_crs);
   13281         342 :     poGeogCRS->d->undoDemoteFromBoundCRS();
   13282         342 :     if (cs)
   13283             :     {
   13284         342 :         const char *pszDirection = nullptr;
   13285         342 :         if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr, nullptr,
   13286             :                                   &pszDirection, nullptr, nullptr, nullptr,
   13287         342 :                                   nullptr))
   13288             :         {
   13289         342 :             if (EQUAL(pszDirection, "north"))
   13290             :             {
   13291         143 :                 ret = true;
   13292             :             }
   13293             :         }
   13294             : 
   13295         342 :         proj_destroy(cs);
   13296             :     }
   13297         342 :     if (!ret)
   13298         199 :         return -1;
   13299             : 
   13300             :     /* -------------------------------------------------------------------- */
   13301             :     /*      Do we already have it?                                          */
   13302             :     /* -------------------------------------------------------------------- */
   13303         143 :     const char *pszAuthName = GetAuthorityName("GEOGCS");
   13304         143 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg"))
   13305          66 :         return atoi(GetAuthorityCode("GEOGCS"));
   13306             : 
   13307             :     /* -------------------------------------------------------------------- */
   13308             :     /*      Get the datum and geogcs names.                                 */
   13309             :     /* -------------------------------------------------------------------- */
   13310             : 
   13311          77 :     const char *pszGEOGCS = GetAttrValue("GEOGCS");
   13312          77 :     const char *pszDatum = GetAttrValue("DATUM");
   13313             : 
   13314             :     // We can only operate on coordinate systems with a geogcs.
   13315         154 :     OGRSpatialReference oSRSTmp;
   13316          77 :     if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13317             :     {
   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           1 :         oSRSTmp.CopyGeogCSFrom(this);
   13322           1 :         pszGEOGCS = oSRSTmp.GetAttrValue("GEOGCS");
   13323           1 :         pszDatum = oSRSTmp.GetAttrValue("DATUM");
   13324           1 :         if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13325             :         {
   13326           0 :             return -1;
   13327             :         }
   13328             :     }
   13329             : 
   13330             :     // Lookup geographic CRS name
   13331          77 :     const PJ_TYPE type = PJ_TYPE_GEOGRAPHIC_2D_CRS;
   13332          77 :     PJ_OBJ_LIST *list = proj_create_from_name(
   13333             :         d->getPROJContext(), nullptr, pszGEOGCS, &type, 1, false, 1, nullptr);
   13334          77 :     if (list)
   13335             :     {
   13336          77 :         const auto listSize = proj_list_get_count(list);
   13337          77 :         if (listSize == 1)
   13338             :         {
   13339          49 :             auto crs = proj_list_get(d->getPROJContext(), list, 0);
   13340          49 :             if (crs)
   13341             :             {
   13342          49 :                 pszAuthName = proj_get_id_auth_name(crs, 0);
   13343          49 :                 const char *pszCode = proj_get_id_code(crs, 0);
   13344          49 :                 if (pszAuthName && pszCode && EQUAL(pszAuthName, "EPSG"))
   13345             :                 {
   13346          47 :                     const int nCode = atoi(pszCode);
   13347          47 :                     proj_destroy(crs);
   13348          47 :                     proj_list_destroy(list);
   13349          47 :                     return nCode;
   13350             :                 }
   13351           2 :                 proj_destroy(crs);
   13352             :             }
   13353             :         }
   13354          30 :         proj_list_destroy(list);
   13355             :     }
   13356             : 
   13357             :     /* -------------------------------------------------------------------- */
   13358             :     /*      Is this a "well known" geographic coordinate system?            */
   13359             :     /* -------------------------------------------------------------------- */
   13360          90 :     const bool bWGS = strstr(pszGEOGCS, "WGS") != nullptr ||
   13361          30 :                       strstr(pszDatum, "WGS") ||
   13362          30 :                       strstr(pszGEOGCS, "World Geodetic System") ||
   13363          30 :                       strstr(pszGEOGCS, "World_Geodetic_System") ||
   13364          90 :                       strstr(pszDatum, "World Geodetic System") ||
   13365          30 :                       strstr(pszDatum, "World_Geodetic_System");
   13366             : 
   13367          90 :     const bool bNAD = strstr(pszGEOGCS, "NAD") != nullptr ||
   13368          30 :                       strstr(pszDatum, "NAD") ||
   13369          30 :                       strstr(pszGEOGCS, "North American") ||
   13370          30 :                       strstr(pszGEOGCS, "North_American") ||
   13371          90 :                       strstr(pszDatum, "North American") ||
   13372          30 :                       strstr(pszDatum, "North_American");
   13373             : 
   13374          30 :     if (bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")))
   13375           0 :         return 4326;
   13376             : 
   13377          30 :     if (bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")))
   13378           0 :         return 4322;
   13379             : 
   13380             :     // This is questionable as there are several 'flavors' of NAD83 that
   13381             :     // are not the same as 4269
   13382          30 :     if (bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")))
   13383           0 :         return 4269;
   13384             : 
   13385          30 :     if (bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")))
   13386           0 :         return 4267;
   13387             : 
   13388             :     /* -------------------------------------------------------------------- */
   13389             :     /*      If we know the datum, associate the most likely GCS with        */
   13390             :     /*      it.                                                             */
   13391             :     /* -------------------------------------------------------------------- */
   13392          30 :     const OGRSpatialReference &oActiveObj = oSRSTmp.IsEmpty() ? *this : oSRSTmp;
   13393          30 :     pszAuthName = oActiveObj.GetAuthorityName("GEOGCS|DATUM");
   13394          30 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") &&
   13395           0 :         GetPrimeMeridian() == 0.0)
   13396             :     {
   13397           0 :         const int nDatum = atoi(oActiveObj.GetAuthorityCode("GEOGCS|DATUM"));
   13398             : 
   13399           0 :         if (nDatum >= 6000 && nDatum <= 6999)
   13400           0 :             return nDatum - 2000;
   13401             :     }
   13402             : 
   13403          30 :     return -1;
   13404             : }
   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         876 : void OGRSpatialReference::SetCoordinateEpoch(double dfCoordinateEpoch)
   13429             : {
   13430         876 :     d->m_coordinateEpoch = dfCoordinateEpoch;
   13431         876 : }
   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          31 : void OSRSetCoordinateEpoch(OGRSpatialReferenceH hSRS, double dfCoordinateEpoch)
   13444             : {
   13445          31 :     VALIDATE_POINTER0(hSRS, "OSRSetCoordinateEpoch");
   13446             : 
   13447          31 :     return OGRSpatialReference::FromHandle(hSRS)->SetCoordinateEpoch(
   13448          31 :         dfCoordinateEpoch);
   13449             : }
   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       14205 : double OGRSpatialReference::GetCoordinateEpoch() const
   13475             : {
   13476       14205 :     return d->m_coordinateEpoch;
   13477             : }
   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         730 : double OSRGetCoordinateEpoch(OGRSpatialReferenceH hSRS)
   13490             : {
   13491         730 :     VALIDATE_POINTER1(hSRS, "OSRGetCoordinateEpoch", 0);
   13492             : 
   13493         730 :     return OGRSpatialReference::FromHandle(hSRS)->GetCoordinateEpoch();
   13494             : }

Generated by: LCOV version 1.14