LCOV - code coverage report
Current view: top level - ogr - ogrspatialreference.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 3438 4208 81.7 %
Date: 2025-03-28 11:40:40 Functions: 297 373 79.6 %

          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             : // Exists since 8.0.1
      49             : #ifndef PROJ_AT_LEAST_VERSION
      50             : #define PROJ_COMPUTE_VERSION(maj, min, patch)                                  \
      51             :     ((maj)*10000 + (min)*100 + (patch))
      52             : #define PROJ_VERSION_NUMBER                                                    \
      53             :     PROJ_COMPUTE_VERSION(PROJ_VERSION_MAJOR, PROJ_VERSION_MINOR,               \
      54             :                          PROJ_VERSION_PATCH)
      55             : #define PROJ_AT_LEAST_VERSION(maj, min, patch)                                 \
      56             :     (PROJ_VERSION_NUMBER >= PROJ_COMPUTE_VERSION(maj, min, patch))
      57             : #endif
      58             : 
      59             : #define STRINGIFY(s) #s
      60             : #define XSTRINGIFY(s) STRINGIFY(s)
      61             : 
      62             : struct OGRSpatialReference::Private
      63             : {
      64             :     struct Listener : public OGR_SRSNode::Listener
      65             :     {
      66             :         OGRSpatialReference::Private *m_poObj = nullptr;
      67             : 
      68      196901 :         explicit Listener(OGRSpatialReference::Private *poObj) : m_poObj(poObj)
      69             :         {
      70      196774 :         }
      71             : 
      72             :         Listener(const Listener &) = delete;
      73             :         Listener &operator=(const Listener &) = delete;
      74             : 
      75     1821600 :         void notifyChange(OGR_SRSNode *) override
      76             :         {
      77     1821600 :             m_poObj->nodesChanged();
      78     1821600 :         }
      79             :     };
      80             : 
      81             :     OGRSpatialReference *m_poSelf = nullptr;
      82             :     PJ *m_pj_crs = nullptr;
      83             : 
      84             :     // Temporary state used for object construction
      85             :     PJ_TYPE m_pjType = PJ_TYPE_UNKNOWN;
      86             :     CPLString m_osPrimeMeridianName{};
      87             :     CPLString m_osAngularUnits{};
      88             :     CPLString m_osLinearUnits{};
      89             :     CPLString m_osAxisName[3]{};
      90             : 
      91             :     std::vector<std::string> m_wktImportWarnings{};
      92             :     std::vector<std::string> m_wktImportErrors{};
      93             :     CPLString m_osAreaName{};
      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      993146 :     PJ_CONTEXT *getPROJContext()
     159             :     {
     160      993146 :         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     4500800 :         explicit OptionalLockGuard(Private *p) : m_private(*p)
     180             :         {
     181     4500800 :             if (m_private.m_bIsThreadSafe)
     182        3798 :                 m_private.m_mutex.lock();
     183     4500800 :         }
     184             : 
     185     4500850 :         ~OptionalLockGuard()
     186     4500850 :         {
     187     4500850 :             if (m_private.m_bIsThreadSafe)
     188        3798 :                 m_private.m_mutex.unlock();
     189     4500850 :         }
     190             :     };
     191             : 
     192     4500910 :     inline OptionalLockGuard GetOptionalLockGuard()
     193             :     {
     194     4500910 :         return OptionalLockGuard(this);
     195             :     }
     196             : };
     197             : 
     198             : #define TAKE_OPTIONAL_LOCK()                                                   \
     199             :     auto lock = d->GetOptionalLockGuard();                                     \
     200             :     CPL_IGNORE_RET_VAL(lock)
     201             : 
     202      196896 : static OSRAxisMappingStrategy GetDefaultAxisMappingStrategy()
     203             : {
     204             :     const char *pszDefaultAMS =
     205      196896 :         CPLGetConfigOption("OSR_DEFAULT_AXIS_MAPPING_STRATEGY", nullptr);
     206      196970 :     if (pszDefaultAMS)
     207             :     {
     208           1 :         if (EQUAL(pszDefaultAMS, "AUTHORITY_COMPLIANT"))
     209           0 :             return OAMS_AUTHORITY_COMPLIANT;
     210           1 :         else if (EQUAL(pszDefaultAMS, "TRADITIONAL_GIS_ORDER"))
     211           1 :             return OAMS_TRADITIONAL_GIS_ORDER;
     212             :         else
     213             :         {
     214           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     215             :                      "Illegal value for OSR_DEFAULT_AXIS_MAPPING_STRATEGY = %s",
     216             :                      pszDefaultAMS);
     217             :         }
     218             :     }
     219      196969 :     return OAMS_AUTHORITY_COMPLIANT;
     220             : }
     221             : 
     222      196935 : OGRSpatialReference::Private::Private(OGRSpatialReference *poSelf)
     223             :     : m_poSelf(poSelf),
     224      196935 :       m_poListener(std::shared_ptr<Listener>(new Listener(this)))
     225             : {
     226             :     // Get the default value for m_axisMappingStrategy from the
     227             :     // OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration option, if set.
     228      196753 :     m_axisMappingStrategy = GetDefaultAxisMappingStrategy();
     229      196970 : }
     230             : 
     231      784436 : OGRSpatialReference::Private::~Private()
     232             : {
     233             :     // In case we destroy the object not in the thread that created it,
     234             :     // we need to reassign the PROJ context. Having the context bundled inside
     235             :     // PJ* deeply sucks...
     236      196145 :     auto ctxt = getPROJContext();
     237             : 
     238      196154 :     proj_assign_context(m_pj_crs, ctxt);
     239      196151 :     proj_destroy(m_pj_crs);
     240             : 
     241      196148 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     242      196148 :     proj_destroy(m_pj_geod_base_crs_temp);
     243             : 
     244      196148 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     245      196147 :     proj_destroy(m_pj_proj_crs_cs_temp);
     246             : 
     247      196146 :     proj_assign_context(m_pj_bound_crs_target, ctxt);
     248      196150 :     proj_destroy(m_pj_bound_crs_target);
     249             : 
     250      196145 :     proj_assign_context(m_pj_bound_crs_co, ctxt);
     251      196148 :     proj_destroy(m_pj_bound_crs_co);
     252             : 
     253      196145 :     proj_assign_context(m_pj_crs_backup, ctxt);
     254      196144 :     proj_destroy(m_pj_crs_backup);
     255             : 
     256      196141 :     delete m_poRootBackup;
     257      196147 :     delete m_poRoot;
     258      196127 : }
     259             : 
     260       98401 : void OGRSpatialReference::Private::clear()
     261             : {
     262       98401 :     proj_assign_context(m_pj_crs, getPROJContext());
     263       98400 :     proj_destroy(m_pj_crs);
     264       98401 :     m_pj_crs = nullptr;
     265             : 
     266       98401 :     delete m_poRoot;
     267       98401 :     m_poRoot = nullptr;
     268       98401 :     m_bNodesChanged = false;
     269             : 
     270       98401 :     m_wktImportWarnings.clear();
     271       98401 :     m_wktImportErrors.clear();
     272             : 
     273       98401 :     m_pj_crs_modified_during_demote = false;
     274       98401 :     m_pjType = PJ_TYPE_UNKNOWN;
     275       98401 :     m_osPrimeMeridianName.clear();
     276       98401 :     m_osAngularUnits.clear();
     277       98400 :     m_osLinearUnits.clear();
     278             : 
     279       98400 :     bNormInfoSet = FALSE;
     280       98400 :     dfFromGreenwich = 1.0;
     281       98400 :     dfToMeter = 1.0;
     282       98400 :     dfToDegrees = 1.0;
     283       98400 :     m_dfAngularUnitToRadian = 0.0;
     284             : 
     285       98400 :     m_bMorphToESRI = false;
     286       98400 :     m_bHasCenterLong = false;
     287             : 
     288       98400 :     m_coordinateEpoch = 0.0;
     289       98400 : }
     290             : 
     291       23112 : void OGRSpatialReference::Private::setRoot(OGR_SRSNode *poRoot)
     292             : {
     293       23112 :     m_poRoot = poRoot;
     294       23112 :     if (m_poRoot)
     295             :     {
     296       23112 :         m_poRoot->RegisterListener(m_poListener);
     297             :     }
     298       23112 :     nodesChanged();
     299       23112 : }
     300             : 
     301      157375 : void OGRSpatialReference::Private::setPjCRS(PJ *pj_crsIn,
     302             :                                             bool doRefreshAxisMapping)
     303             : {
     304      157375 :     auto ctxt = getPROJContext();
     305             : 
     306             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
     307             :     if (proj_get_type(pj_crsIn) == PJ_TYPE_COORDINATE_METADATA)
     308             :     {
     309             :         const double dfEpoch =
     310             :             proj_coordinate_metadata_get_epoch(ctxt, pj_crsIn);
     311             :         if (!std::isnan(dfEpoch))
     312             :         {
     313             :             m_poSelf->SetCoordinateEpoch(dfEpoch);
     314             :         }
     315             :         auto crs = proj_get_source_crs(ctxt, pj_crsIn);
     316             :         proj_destroy(pj_crsIn);
     317             :         pj_crsIn = crs;
     318             :     }
     319             : #endif
     320             : 
     321      157375 :     proj_assign_context(m_pj_crs, ctxt);
     322      157375 :     proj_destroy(m_pj_crs);
     323      157375 :     m_pj_crs = pj_crsIn;
     324      157375 :     if (m_pj_crs)
     325             :     {
     326      157323 :         m_pjType = proj_get_type(m_pj_crs);
     327             :     }
     328      157375 :     if (m_pj_crs_backup)
     329             :     {
     330          21 :         m_pj_crs_modified_during_demote = true;
     331             :     }
     332      157375 :     invalidateNodes();
     333      157375 :     if (doRefreshAxisMapping)
     334             :     {
     335      157355 :         refreshAxisMapping();
     336             :     }
     337      157375 : }
     338             : 
     339      597983 : void OGRSpatialReference::Private::refreshProjObj()
     340             : {
     341      597983 :     if (m_bNodesChanged && m_poRoot)
     342             :     {
     343        7281 :         char *pszWKT = nullptr;
     344        7281 :         m_poRoot->exportToWkt(&pszWKT);
     345        7281 :         auto poRootBackup = m_poRoot;
     346        7281 :         m_poRoot = nullptr;
     347        7281 :         const double dfCoordinateEpochBackup = m_coordinateEpoch;
     348        7281 :         clear();
     349        7281 :         m_coordinateEpoch = dfCoordinateEpochBackup;
     350        7281 :         m_bHasCenterLong = strstr(pszWKT, "CENTER_LONG") != nullptr;
     351             : 
     352        7281 :         const char *const options[] = {
     353             :             "STRICT=NO",
     354             : #if PROJ_AT_LEAST_VERSION(9, 1, 0)
     355             :             "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=NO",
     356             : #endif
     357             :             nullptr
     358             :         };
     359        7281 :         PROJ_STRING_LIST warnings = nullptr;
     360        7281 :         PROJ_STRING_LIST errors = nullptr;
     361        7281 :         setPjCRS(proj_create_from_wkt(getPROJContext(), pszWKT, options,
     362             :                                       &warnings, &errors));
     363       14227 :         for (auto iter = warnings; iter && *iter; ++iter)
     364             :         {
     365        6946 :             m_wktImportWarnings.push_back(*iter);
     366             :         }
     367        7495 :         for (auto iter = errors; iter && *iter; ++iter)
     368             :         {
     369         214 :             m_wktImportErrors.push_back(*iter);
     370             :         }
     371        7281 :         proj_string_list_destroy(warnings);
     372        7281 :         proj_string_list_destroy(errors);
     373             : 
     374        7281 :         CPLFree(pszWKT);
     375             : 
     376        7281 :         m_poRoot = poRootBackup;
     377        7281 :         m_bNodesChanged = false;
     378             :     }
     379      597983 : }
     380             : 
     381       25217 : void OGRSpatialReference::Private::refreshRootFromProjObj()
     382             : {
     383       25217 :     CPLAssert(m_poRoot == nullptr);
     384             : 
     385       25217 :     if (m_pj_crs)
     386             :     {
     387       46136 :         CPLStringList aosOptions;
     388       23068 :         if (!m_bMorphToESRI)
     389             :         {
     390       23064 :             aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
     391       23064 :             aosOptions.SetNameValue("MULTILINE", "NO");
     392             :         }
     393       23068 :         aosOptions.SetNameValue("STRICT", "NO");
     394             : 
     395             :         const char *pszWKT;
     396             :         {
     397       23068 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     398       23068 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs,
     399       23068 :                                  m_bMorphToESRI ? PJ_WKT1_ESRI : PJ_WKT1_GDAL,
     400       23068 :                                  aosOptions.List());
     401       23068 :             m_bNodesWKT2 = false;
     402             :         }
     403       23068 :         if (!m_bMorphToESRI && pszWKT == nullptr)
     404             :         {
     405          70 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs, PJ_WKT2_2018,
     406          70 :                                  aosOptions.List());
     407          70 :             m_bNodesWKT2 = true;
     408             :         }
     409       23068 :         if (pszWKT)
     410             :         {
     411       23068 :             auto root = new OGR_SRSNode();
     412       23068 :             setRoot(root);
     413       23068 :             root->importFromWkt(&pszWKT);
     414       23068 :             m_bNodesChanged = false;
     415             :         }
     416             :     }
     417       25217 : }
     418             : 
     419      181942 : static bool isNorthEastAxisOrder(PJ_CONTEXT *ctx, PJ *cs)
     420             : {
     421      181942 :     const char *pszName1 = nullptr;
     422      181942 :     const char *pszDirection1 = nullptr;
     423      181942 :     proj_cs_get_axis_info(ctx, cs, 0, &pszName1, nullptr, &pszDirection1,
     424             :                           nullptr, nullptr, nullptr, nullptr);
     425      181942 :     const char *pszName2 = nullptr;
     426      181942 :     const char *pszDirection2 = nullptr;
     427      181942 :     proj_cs_get_axis_info(ctx, cs, 1, &pszName2, nullptr, &pszDirection2,
     428             :                           nullptr, nullptr, nullptr, nullptr);
     429      181942 :     if (pszDirection1 && EQUAL(pszDirection1, "north") && pszDirection2 &&
     430       81057 :         EQUAL(pszDirection2, "east"))
     431             :     {
     432       80475 :         return true;
     433             :     }
     434      101467 :     if (pszDirection1 && pszDirection2 &&
     435      101467 :         ((EQUAL(pszDirection1, "north") && EQUAL(pszDirection2, "north")) ||
     436      100902 :          (EQUAL(pszDirection1, "south") && EQUAL(pszDirection2, "south"))) &&
     437        1117 :         pszName1 && STARTS_WITH_CI(pszName1, "northing") && pszName2 &&
     438         258 :         STARTS_WITH_CI(pszName2, "easting"))
     439             :     {
     440         258 :         return true;
     441             :     }
     442      101209 :     return false;
     443             : }
     444             : 
     445      233091 : void OGRSpatialReference::Private::refreshAxisMapping()
     446             : {
     447      233091 :     if (!m_pj_crs || m_axisMappingStrategy == OAMS_CUSTOM)
     448       51497 :         return;
     449             : 
     450      181594 :     bool doUndoDemote = false;
     451      181594 :     if (m_pj_crs_backup == nullptr)
     452             :     {
     453      181573 :         doUndoDemote = true;
     454      181573 :         demoteFromBoundCRS();
     455             :     }
     456      181594 :     const auto ctxt = getPROJContext();
     457      181594 :     PJ *horizCRS = nullptr;
     458      181594 :     int axisCount = 0;
     459      181594 :     if (m_pjType == PJ_TYPE_VERTICAL_CRS)
     460             :     {
     461         218 :         axisCount = 1;
     462             :     }
     463      181376 :     else if (m_pjType == PJ_TYPE_COMPOUND_CRS)
     464             :     {
     465        1100 :         horizCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 0);
     466        1100 :         if (horizCRS && proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
     467             :         {
     468         222 :             auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
     469         222 :             if (baseCRS)
     470             :             {
     471         222 :                 proj_destroy(horizCRS);
     472         222 :                 horizCRS = baseCRS;
     473             :             }
     474             :         }
     475             : 
     476        1100 :         auto vertCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 1);
     477        1100 :         if (vertCRS)
     478             :         {
     479        1097 :             if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
     480             :             {
     481         391 :                 auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
     482         391 :                 if (baseCRS)
     483             :                 {
     484         391 :                     proj_destroy(vertCRS);
     485         391 :                     vertCRS = baseCRS;
     486             :                 }
     487             :             }
     488             : 
     489        1097 :             auto cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
     490        1097 :             if (cs)
     491             :             {
     492        1097 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
     493        1097 :                 proj_destroy(cs);
     494             :             }
     495        1097 :             proj_destroy(vertCRS);
     496             :         }
     497             :     }
     498             :     else
     499             :     {
     500      180276 :         horizCRS = m_pj_crs;
     501             :     }
     502             : 
     503      181594 :     bool bSwitchForGisFriendlyOrder = false;
     504      181594 :     if (horizCRS)
     505             :     {
     506      181373 :         auto cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
     507      181373 :         if (cs)
     508             :         {
     509      181373 :             int nHorizCSAxisCount = proj_cs_get_axis_count(ctxt, cs);
     510      181373 :             axisCount += nHorizCSAxisCount;
     511      181373 :             if (nHorizCSAxisCount >= 2)
     512             :             {
     513      181363 :                 bSwitchForGisFriendlyOrder = isNorthEastAxisOrder(ctxt, cs);
     514             :             }
     515      181373 :             proj_destroy(cs);
     516             :         }
     517             :     }
     518      181594 :     if (horizCRS != m_pj_crs)
     519             :     {
     520        1318 :         proj_destroy(horizCRS);
     521             :     }
     522      181594 :     if (doUndoDemote)
     523             :     {
     524      181573 :         undoDemoteFromBoundCRS();
     525             :     }
     526             : 
     527      181594 :     m_axisMapping.resize(axisCount);
     528      181594 :     if (m_axisMappingStrategy == OAMS_AUTHORITY_COMPLIANT ||
     529       56394 :         !bSwitchForGisFriendlyOrder)
     530             :     {
     531      470624 :         for (int i = 0; i < axisCount; i++)
     532             :         {
     533      314211 :             m_axisMapping[i] = i + 1;
     534      156413 :         }
     535             :     }
     536             :     else
     537             :     {
     538       25181 :         m_axisMapping[0] = 2;
     539       25181 :         m_axisMapping[1] = 1;
     540       25181 :         if (axisCount == 3)
     541             :         {
     542         328 :             m_axisMapping[2] = 3;
     543             :         }
     544             :     }
     545             : }
     546             : 
     547     1844710 : void OGRSpatialReference::Private::nodesChanged()
     548             : {
     549     1844710 :     m_bNodesChanged = true;
     550     1844710 : }
     551             : 
     552      157657 : void OGRSpatialReference::Private::invalidateNodes()
     553             : {
     554      157657 :     delete m_poRoot;
     555      157657 :     m_poRoot = nullptr;
     556      157657 :     m_bNodesChanged = false;
     557      157657 : }
     558             : 
     559         282 : void OGRSpatialReference::Private::setMorphToESRI(bool b)
     560             : {
     561         282 :     invalidateNodes();
     562         282 :     m_bMorphToESRI = b;
     563         282 : }
     564             : 
     565      523361 : void OGRSpatialReference::Private::demoteFromBoundCRS()
     566             : {
     567      523361 :     CPLAssert(m_pj_bound_crs_target == nullptr);
     568      523361 :     CPLAssert(m_pj_bound_crs_co == nullptr);
     569      523361 :     CPLAssert(m_poRootBackup == nullptr);
     570      523361 :     CPLAssert(m_pj_crs_backup == nullptr);
     571             : 
     572      523361 :     m_pj_crs_modified_during_demote = false;
     573             : 
     574      523361 :     if (m_pjType == PJ_TYPE_BOUND_CRS)
     575             :     {
     576        2722 :         auto baseCRS = proj_get_source_crs(getPROJContext(), m_pj_crs);
     577        2722 :         m_pj_bound_crs_target = proj_get_target_crs(getPROJContext(), m_pj_crs);
     578        2722 :         m_pj_bound_crs_co =
     579        2722 :             proj_crs_get_coordoperation(getPROJContext(), m_pj_crs);
     580             : 
     581        2722 :         m_poRootBackup = m_poRoot;
     582        2722 :         m_poRoot = nullptr;
     583        2722 :         m_pj_crs_backup = m_pj_crs;
     584        2722 :         m_pj_crs = baseCRS;
     585        2722 :         m_pjType = proj_get_type(m_pj_crs);
     586             :     }
     587      523361 : }
     588             : 
     589      523362 : void OGRSpatialReference::Private::undoDemoteFromBoundCRS()
     590             : {
     591      523362 :     if (m_pj_bound_crs_target)
     592             :     {
     593        2722 :         CPLAssert(m_poRoot == nullptr);
     594        2722 :         CPLAssert(m_pj_crs);
     595        2722 :         if (!m_pj_crs_modified_during_demote)
     596             :         {
     597        2702 :             proj_destroy(m_pj_crs);
     598        2702 :             m_pj_crs = m_pj_crs_backup;
     599        2702 :             m_pjType = proj_get_type(m_pj_crs);
     600        2702 :             m_poRoot = m_poRootBackup;
     601             :         }
     602             :         else
     603             :         {
     604          20 :             delete m_poRootBackup;
     605          20 :             m_poRootBackup = nullptr;
     606          20 :             proj_destroy(m_pj_crs_backup);
     607          20 :             m_pj_crs_backup = nullptr;
     608          20 :             setPjCRS(proj_crs_create_bound_crs(getPROJContext(), m_pj_crs,
     609          20 :                                                m_pj_bound_crs_target,
     610          20 :                                                m_pj_bound_crs_co),
     611             :                      false);
     612             :         }
     613             :     }
     614             : 
     615      523362 :     m_poRootBackup = nullptr;
     616      523362 :     m_pj_crs_backup = nullptr;
     617      523362 :     proj_destroy(m_pj_bound_crs_target);
     618      523362 :     m_pj_bound_crs_target = nullptr;
     619      523362 :     proj_destroy(m_pj_bound_crs_co);
     620      523362 :     m_pj_bound_crs_co = nullptr;
     621      523362 :     m_pj_crs_modified_during_demote = false;
     622      523362 : }
     623             : 
     624      112726 : const char *OGRSpatialReference::Private::nullifyTargetKeyIfPossible(
     625             :     const char *pszTargetKey)
     626             : {
     627      112726 :     if (pszTargetKey)
     628             :     {
     629       53779 :         demoteFromBoundCRS();
     630       53779 :         if ((m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     631       26561 :              m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS) &&
     632       27292 :             EQUAL(pszTargetKey, "GEOGCS"))
     633             :         {
     634        6788 :             pszTargetKey = nullptr;
     635             :         }
     636       46991 :         else if (m_pjType == PJ_TYPE_GEOCENTRIC_CRS &&
     637          20 :                  EQUAL(pszTargetKey, "GEOCCS"))
     638             :         {
     639           0 :             pszTargetKey = nullptr;
     640             :         }
     641       46991 :         else if (m_pjType == PJ_TYPE_PROJECTED_CRS &&
     642       25180 :                  EQUAL(pszTargetKey, "PROJCS"))
     643             :         {
     644        3610 :             pszTargetKey = nullptr;
     645             :         }
     646       43381 :         else if (m_pjType == PJ_TYPE_VERTICAL_CRS &&
     647           4 :                  EQUAL(pszTargetKey, "VERT_CS"))
     648             :         {
     649           2 :             pszTargetKey = nullptr;
     650             :         }
     651       53779 :         undoDemoteFromBoundCRS();
     652             :     }
     653      112726 :     return pszTargetKey;
     654             : }
     655             : 
     656        8295 : PJ *OGRSpatialReference::Private::getGeodBaseCRS()
     657             : {
     658        8295 :     if (m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     659        8242 :         m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
     660             :     {
     661          53 :         return m_pj_crs;
     662             :     }
     663             : 
     664        8242 :     auto ctxt = getPROJContext();
     665        8242 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     666             :     {
     667        3749 :         proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     668        3749 :         proj_destroy(m_pj_geod_base_crs_temp);
     669        3749 :         m_pj_geod_base_crs_temp = proj_crs_get_geodetic_crs(ctxt, m_pj_crs);
     670        3749 :         return m_pj_geod_base_crs_temp;
     671             :     }
     672             : 
     673        4493 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     674        4493 :     proj_destroy(m_pj_geod_base_crs_temp);
     675        4493 :     auto cs = proj_create_ellipsoidal_2D_cs(ctxt, PJ_ELLPS2D_LATITUDE_LONGITUDE,
     676             :                                             nullptr, 0);
     677        4493 :     m_pj_geod_base_crs_temp = proj_create_geographic_crs(
     678             :         ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84",
     679             :         SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING, SRS_PM_GREENWICH, 0.0,
     680             :         SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV), cs);
     681        4493 :     proj_destroy(cs);
     682             : 
     683        4493 :     return m_pj_geod_base_crs_temp;
     684             : }
     685             : 
     686        4695 : PJ *OGRSpatialReference::Private::getProjCRSCoordSys()
     687             : {
     688        4695 :     auto ctxt = getPROJContext();
     689        4695 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     690             :     {
     691        3734 :         proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     692        3734 :         proj_destroy(m_pj_proj_crs_cs_temp);
     693        3734 :         m_pj_proj_crs_cs_temp =
     694        3734 :             proj_crs_get_coordinate_system(getPROJContext(), m_pj_crs);
     695        3734 :         return m_pj_proj_crs_cs_temp;
     696             :     }
     697             : 
     698         961 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     699         961 :     proj_destroy(m_pj_proj_crs_cs_temp);
     700         961 :     m_pj_proj_crs_cs_temp = proj_create_cartesian_2D_cs(
     701             :         ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0);
     702         961 :     return m_pj_proj_crs_cs_temp;
     703             : }
     704             : 
     705        4747 : const char *OGRSpatialReference::Private::getProjCRSName()
     706             : {
     707        4747 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     708             :     {
     709        3750 :         return proj_get_name(m_pj_crs);
     710             :     }
     711             : 
     712         997 :     return "unnamed";
     713             : }
     714             : 
     715        1370 : OGRErr OGRSpatialReference::Private::replaceConversionAndUnref(PJ *conv)
     716             : {
     717        1370 :     refreshProjObj();
     718             : 
     719        1370 :     demoteFromBoundCRS();
     720             : 
     721             :     auto projCRS =
     722        1370 :         proj_create_projected_crs(getPROJContext(), getProjCRSName(),
     723        1370 :                                   getGeodBaseCRS(), conv, getProjCRSCoordSys());
     724        1370 :     proj_destroy(conv);
     725             : 
     726        1370 :     setPjCRS(projCRS);
     727             : 
     728        1370 :     undoDemoteFromBoundCRS();
     729        1370 :     return OGRERR_NONE;
     730             : }
     731             : 
     732             : /************************************************************************/
     733             : /*                           ToPointer()                                */
     734             : /************************************************************************/
     735             : 
     736       23550 : static inline OGRSpatialReference *ToPointer(OGRSpatialReferenceH hSRS)
     737             : {
     738       23550 :     return OGRSpatialReference::FromHandle(hSRS);
     739             : }
     740             : 
     741             : /************************************************************************/
     742             : /*                           ToHandle()                                 */
     743             : /************************************************************************/
     744             : 
     745        4019 : static inline OGRSpatialReferenceH ToHandle(OGRSpatialReference *poSRS)
     746             : {
     747        4019 :     return OGRSpatialReference::ToHandle(poSRS);
     748             : }
     749             : 
     750             : /************************************************************************/
     751             : /*                           OGRsnPrintDouble()                         */
     752             : /************************************************************************/
     753             : 
     754             : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue);
     755             : 
     756         126 : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue)
     757             : 
     758             : {
     759         126 :     CPLsnprintf(pszStrBuf, size, "%.16g", dfValue);
     760             : 
     761         126 :     const size_t nLen = strlen(pszStrBuf);
     762             : 
     763             :     // The following hack is intended to truncate some "precision" in cases
     764             :     // that appear to be roundoff error.
     765         126 :     if (nLen > 15 && (strcmp(pszStrBuf + nLen - 6, "999999") == 0 ||
     766           8 :                       strcmp(pszStrBuf + nLen - 6, "000001") == 0))
     767             :     {
     768           0 :         CPLsnprintf(pszStrBuf, size, "%.15g", dfValue);
     769             :     }
     770             : 
     771             :     // Force to user periods regardless of locale.
     772         126 :     if (strchr(pszStrBuf, ',') != nullptr)
     773             :     {
     774           0 :         char *const pszDelim = strchr(pszStrBuf, ',');
     775           0 :         *pszDelim = '.';
     776             :     }
     777         126 : }
     778             : 
     779             : /************************************************************************/
     780             : /*                        OGRSpatialReference()                         */
     781             : /************************************************************************/
     782             : 
     783             : /**
     784             :  * \brief Constructor.
     785             :  *
     786             :  * This constructor takes an optional string argument which if passed
     787             :  * should be a WKT representation of an SRS.  Passing this is equivalent
     788             :  * to not passing it, and then calling importFromWkt() with the WKT string.
     789             :  *
     790             :  * Note that newly created objects are given a reference count of one.
     791             :  *
     792             :  * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
     793             :  * object are assumed to be in the order of the axis of the CRS definition
     794             :  (which
     795             :  * for example means latitude first, longitude second for geographic CRS
     796             :  belonging
     797             :  * to the EPSG authority). It is possible to define a data axis to CRS axis
     798             :  * mapping strategy with the SetAxisMappingStrategy() method.
     799             :  *
     800             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
     801             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
     802             :  later
     803             :  * being the default value when the option is not set) to control the value of
     804             :  the
     805             :  * data axis to CRS axis mapping strategy when a OSRSpatialReference object is
     806             :  * created. Calling SetAxisMappingStrategy() will override this default value.
     807             : 
     808             :  * The C function OSRNewSpatialReference() does the same thing as this
     809             :  * constructor.
     810             :  *
     811             :  * @param pszWKT well known text definition to which the object should
     812             :  * be initialized, or NULL (the default).
     813             :  */
     814             : 
     815      194725 : OGRSpatialReference::OGRSpatialReference(const char *pszWKT)
     816      194725 :     : d(new Private(this))
     817             : {
     818      194639 :     if (pszWKT != nullptr)
     819         350 :         importFromWkt(pszWKT);
     820      194639 : }
     821             : 
     822             : /************************************************************************/
     823             : /*                       OSRNewSpatialReference()                       */
     824             : /************************************************************************/
     825             : 
     826             : /**
     827             :  * \brief Constructor.
     828             :  *
     829             :  * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
     830             :  * object are assumed to be in the order of the axis of the CRS definition
     831             :  * (which for example means latitude first, longitude second for geographic CRS
     832             :  * belonging to the EPSG authority). It is possible to define a data axis to CRS
     833             :  * axis mapping strategy with the SetAxisMappingStrategy() method.
     834             :  *
     835             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
     836             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
     837             :  * later being the default value when the option is not set) to control the
     838             :  * value of the data axis to CRS axis mapping strategy when a
     839             :  * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
     840             :  * override this default value.
     841             :  *
     842             :  * This function is the same as OGRSpatialReference::OGRSpatialReference()
     843             :  */
     844        2802 : OGRSpatialReferenceH CPL_STDCALL OSRNewSpatialReference(const char *pszWKT)
     845             : 
     846             : {
     847        2802 :     OGRSpatialReference *poSRS = new OGRSpatialReference();
     848             : 
     849        2802 :     if (pszWKT != nullptr && strlen(pszWKT) > 0)
     850             :     {
     851         257 :         if (poSRS->importFromWkt(pszWKT) != OGRERR_NONE)
     852             :         {
     853           1 :             delete poSRS;
     854           1 :             poSRS = nullptr;
     855             :         }
     856             :     }
     857             : 
     858        2802 :     return ToHandle(poSRS);
     859             : }
     860             : 
     861             : /************************************************************************/
     862             : /*                        OGRSpatialReference()                         */
     863             : /************************************************************************/
     864             : 
     865             : /** Copy constructor. See also Clone().
     866             :  * @param oOther other spatial reference
     867             :  */
     868        2218 : OGRSpatialReference::OGRSpatialReference(const OGRSpatialReference &oOther)
     869        2218 :     : d(new Private(this))
     870             : {
     871        2218 :     *this = oOther;
     872        2218 : }
     873             : 
     874             : /************************************************************************/
     875             : /*                        OGRSpatialReference()                         */
     876             : /************************************************************************/
     877             : 
     878             : /** Move constructor.
     879             :  * @param oOther other spatial reference
     880             :  */
     881          27 : OGRSpatialReference::OGRSpatialReference(OGRSpatialReference &&oOther)
     882          27 :     : d(std::move(oOther.d))
     883             : {
     884          27 : }
     885             : 
     886             : /************************************************************************/
     887             : /*                        ~OGRSpatialReference()                        */
     888             : /************************************************************************/
     889             : 
     890             : /**
     891             :  * \brief OGRSpatialReference destructor.
     892             :  *
     893             :  * The C function OSRDestroySpatialReference() does the same thing as this
     894             :  * method. Preferred C++ method : OGRSpatialReference::DestroySpatialReference()
     895             :  *
     896             :  * @deprecated
     897             :  */
     898             : 
     899      246686 : OGRSpatialReference::~OGRSpatialReference()
     900             : 
     901             : {
     902      246658 : }
     903             : 
     904             : /************************************************************************/
     905             : /*                      DestroySpatialReference()                       */
     906             : /************************************************************************/
     907             : 
     908             : /**
     909             :  * \brief OGRSpatialReference destructor.
     910             :  *
     911             :  * This static method will destroy a OGRSpatialReference.  It is
     912             :  * equivalent to calling delete on the object, but it ensures that the
     913             :  * deallocation is properly executed within the OGR libraries heap on
     914             :  * platforms where this can matter (win32).
     915             :  *
     916             :  * This function is the same as OSRDestroySpatialReference()
     917             :  *
     918             :  * @param poSRS the object to delete
     919             :  *
     920             :  * @since GDAL 1.7.0
     921             :  */
     922             : 
     923           0 : void OGRSpatialReference::DestroySpatialReference(OGRSpatialReference *poSRS)
     924             : {
     925           0 :     delete poSRS;
     926           0 : }
     927             : 
     928             : /************************************************************************/
     929             : /*                     OSRDestroySpatialReference()                     */
     930             : /************************************************************************/
     931             : 
     932             : /**
     933             :  * \brief OGRSpatialReference destructor.
     934             :  *
     935             :  * This function is the same as OGRSpatialReference::~OGRSpatialReference()
     936             :  * and OGRSpatialReference::DestroySpatialReference()
     937             :  *
     938             :  * @param hSRS the object to delete
     939             :  */
     940        8259 : void CPL_STDCALL OSRDestroySpatialReference(OGRSpatialReferenceH hSRS)
     941             : 
     942             : {
     943        8259 :     delete ToPointer(hSRS);
     944        8259 : }
     945             : 
     946             : /************************************************************************/
     947             : /*                               Clear()                                */
     948             : /************************************************************************/
     949             : 
     950             : /**
     951             :  * \brief Wipe current definition.
     952             :  *
     953             :  * Returns OGRSpatialReference to a state with no definition, as it
     954             :  * exists when first created.  It does not affect reference counts.
     955             :  */
     956             : 
     957       91118 : void OGRSpatialReference::Clear()
     958             : 
     959             : {
     960       91118 :     d->clear();
     961       91120 : }
     962             : 
     963             : /************************************************************************/
     964             : /*                             operator=()                              */
     965             : /************************************************************************/
     966             : 
     967             : /** Assignment operator.
     968             :  * @param oSource SRS to assign to *this
     969             :  * @return *this
     970             :  */
     971             : OGRSpatialReference &
     972       23088 : OGRSpatialReference::operator=(const OGRSpatialReference &oSource)
     973             : 
     974             : {
     975       23088 :     if (&oSource != this)
     976             :     {
     977       23087 :         Clear();
     978             : #ifdef CPPCHECK
     979             :         // Otherwise cppcheck would protest that nRefCount isn't modified
     980             :         d->nRefCount = (d->nRefCount + 1) - 1;
     981             : #endif
     982             : 
     983       23088 :         oSource.d->refreshProjObj();
     984       23088 :         if (oSource.d->m_pj_crs)
     985       22765 :             d->setPjCRS(proj_clone(d->getPROJContext(), oSource.d->m_pj_crs));
     986       23088 :         if (oSource.d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
     987       10483 :             SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     988       12605 :         else if (oSource.d->m_axisMappingStrategy == OAMS_CUSTOM)
     989         110 :             SetDataAxisToSRSAxisMapping(oSource.d->m_axisMapping);
     990             : 
     991       23088 :         d->m_coordinateEpoch = oSource.d->m_coordinateEpoch;
     992             :     }
     993             : 
     994       23089 :     return *this;
     995             : }
     996             : 
     997             : /************************************************************************/
     998             : /*                             operator=()                              */
     999             : /************************************************************************/
    1000             : 
    1001             : /** Move assignment operator.
    1002             :  * @param oSource SRS to assign to *this
    1003             :  * @return *this
    1004             :  */
    1005             : OGRSpatialReference &
    1006        4011 : OGRSpatialReference::operator=(OGRSpatialReference &&oSource)
    1007             : 
    1008             : {
    1009        4011 :     if (&oSource != this)
    1010             :     {
    1011        4010 :         d = std::move(oSource.d);
    1012             :     }
    1013             : 
    1014        4011 :     return *this;
    1015             : }
    1016             : 
    1017             : /************************************************************************/
    1018             : /*                      AssignAndSetThreadSafe()                        */
    1019             : /************************************************************************/
    1020             : 
    1021             : /** Assignment method, with thread-safety.
    1022             :  *
    1023             :  * Same as an assignment operator, but asking also that the *this instance
    1024             :  * becomes thread-safe.
    1025             :  *
    1026             :  * @param oSource SRS to assign to *this
    1027             :  * @return *this
    1028             :  * @since 3.10
    1029             :  */
    1030             : 
    1031             : OGRSpatialReference &
    1032           2 : OGRSpatialReference::AssignAndSetThreadSafe(const OGRSpatialReference &oSource)
    1033             : {
    1034           2 :     *this = oSource;
    1035           2 :     d->SetThreadSafe();
    1036           2 :     return *this;
    1037             : }
    1038             : 
    1039             : /************************************************************************/
    1040             : /*                             Reference()                              */
    1041             : /************************************************************************/
    1042             : 
    1043             : /**
    1044             :  * \brief Increments the reference count by one.
    1045             :  *
    1046             :  * The reference count is used keep track of the number of OGRGeometry objects
    1047             :  * referencing this SRS.
    1048             :  *
    1049             :  * The method does the same thing as the C function OSRReference().
    1050             :  *
    1051             :  * @return the updated reference count.
    1052             :  */
    1053             : 
    1054     4024210 : int OGRSpatialReference::Reference()
    1055             : 
    1056             : {
    1057     4024210 :     return CPLAtomicInc(&d->nRefCount);
    1058             : }
    1059             : 
    1060             : /************************************************************************/
    1061             : /*                            OSRReference()                            */
    1062             : /************************************************************************/
    1063             : 
    1064             : /**
    1065             :  * \brief Increments the reference count by one.
    1066             :  *
    1067             :  * This function is the same as OGRSpatialReference::Reference()
    1068             :  */
    1069         923 : int OSRReference(OGRSpatialReferenceH hSRS)
    1070             : 
    1071             : {
    1072         923 :     VALIDATE_POINTER1(hSRS, "OSRReference", 0);
    1073             : 
    1074         923 :     return ToPointer(hSRS)->Reference();
    1075             : }
    1076             : 
    1077             : /************************************************************************/
    1078             : /*                            Dereference()                             */
    1079             : /************************************************************************/
    1080             : 
    1081             : /**
    1082             :  * \brief Decrements the reference count by one.
    1083             :  *
    1084             :  * The method does the same thing as the C function OSRDereference().
    1085             :  *
    1086             :  * @return the updated reference count.
    1087             :  */
    1088             : 
    1089     4060420 : int OGRSpatialReference::Dereference()
    1090             : 
    1091             : {
    1092     4060420 :     if (d->nRefCount <= 0)
    1093           0 :         CPLDebug("OSR",
    1094             :                  "Dereference() called on an object with refcount %d,"
    1095             :                  "likely already destroyed!",
    1096           0 :                  d->nRefCount);
    1097     4060420 :     return CPLAtomicDec(&d->nRefCount);
    1098             : }
    1099             : 
    1100             : /************************************************************************/
    1101             : /*                           OSRDereference()                           */
    1102             : /************************************************************************/
    1103             : 
    1104             : /**
    1105             :  * \brief Decrements the reference count by one.
    1106             :  *
    1107             :  * This function is the same as OGRSpatialReference::Dereference()
    1108             :  */
    1109           0 : int OSRDereference(OGRSpatialReferenceH hSRS)
    1110             : 
    1111             : {
    1112           0 :     VALIDATE_POINTER1(hSRS, "OSRDereference", 0);
    1113             : 
    1114           0 :     return ToPointer(hSRS)->Dereference();
    1115             : }
    1116             : 
    1117             : /************************************************************************/
    1118             : /*                         GetReferenceCount()                          */
    1119             : /************************************************************************/
    1120             : 
    1121             : /**
    1122             :  * \brief Fetch current reference count.
    1123             :  *
    1124             :  * @return the current reference count.
    1125             :  */
    1126         180 : int OGRSpatialReference::GetReferenceCount() const
    1127             : {
    1128         180 :     return d->nRefCount;
    1129             : }
    1130             : 
    1131             : /************************************************************************/
    1132             : /*                              Release()                               */
    1133             : /************************************************************************/
    1134             : 
    1135             : /**
    1136             :  * \brief Decrements the reference count by one, and destroy if zero.
    1137             :  *
    1138             :  * The method does the same thing as the C function OSRRelease().
    1139             :  */
    1140             : 
    1141     4057650 : void OGRSpatialReference::Release()
    1142             : 
    1143             : {
    1144     4057650 :     if (Dereference() <= 0)
    1145       36196 :         delete this;
    1146     4057650 : }
    1147             : 
    1148             : /************************************************************************/
    1149             : /*                             OSRRelease()                             */
    1150             : /************************************************************************/
    1151             : 
    1152             : /**
    1153             :  * \brief Decrements the reference count by one, and destroy if zero.
    1154             :  *
    1155             :  * This function is the same as OGRSpatialReference::Release()
    1156             :  */
    1157        5923 : void OSRRelease(OGRSpatialReferenceH hSRS)
    1158             : 
    1159             : {
    1160        5923 :     VALIDATE_POINTER0(hSRS, "OSRRelease");
    1161             : 
    1162        5923 :     ToPointer(hSRS)->Release();
    1163             : }
    1164             : 
    1165       80723 : OGR_SRSNode *OGRSpatialReference::GetRoot()
    1166             : {
    1167       80723 :     TAKE_OPTIONAL_LOCK();
    1168             : 
    1169       80723 :     if (!d->m_poRoot)
    1170             :     {
    1171       22793 :         d->refreshRootFromProjObj();
    1172             :     }
    1173      161446 :     return d->m_poRoot;
    1174             : }
    1175             : 
    1176        7023 : const OGR_SRSNode *OGRSpatialReference::GetRoot() const
    1177             : {
    1178        7023 :     TAKE_OPTIONAL_LOCK();
    1179             : 
    1180        7023 :     if (!d->m_poRoot)
    1181             :     {
    1182        2424 :         d->refreshRootFromProjObj();
    1183             :     }
    1184       14046 :     return d->m_poRoot;
    1185             : }
    1186             : 
    1187             : /************************************************************************/
    1188             : /*                              SetRoot()                               */
    1189             : /************************************************************************/
    1190             : 
    1191             : /**
    1192             :  * \brief Set the root SRS node.
    1193             :  *
    1194             :  * If the object has an existing tree of OGR_SRSNodes, they are destroyed
    1195             :  * as part of assigning the new root.  Ownership of the passed OGR_SRSNode is
    1196             :  * is assumed by the OGRSpatialReference.
    1197             :  *
    1198             :  * @param poNewRoot object to assign as root.
    1199             :  */
    1200             : 
    1201          44 : void OGRSpatialReference::SetRoot(OGR_SRSNode *poNewRoot)
    1202             : 
    1203             : {
    1204          44 :     if (d->m_poRoot != poNewRoot)
    1205             :     {
    1206          44 :         delete d->m_poRoot;
    1207          44 :         d->setRoot(poNewRoot);
    1208             :     }
    1209          44 : }
    1210             : 
    1211             : /************************************************************************/
    1212             : /*                            GetAttrNode()                             */
    1213             : /************************************************************************/
    1214             : 
    1215             : /**
    1216             :  * \brief Find named node in tree.
    1217             :  *
    1218             :  * This method does a pre-order traversal of the node tree searching for
    1219             :  * a node with this exact value (case insensitive), and returns it.  Leaf
    1220             :  * nodes are not considered, under the assumption that they are just
    1221             :  * attribute value nodes.
    1222             :  *
    1223             :  * If a node appears more than once in the tree (such as UNIT for instance),
    1224             :  * the first encountered will be returned.  Use GetNode() on a subtree to be
    1225             :  * more specific.
    1226             :  *
    1227             :  * @param pszNodePath the name of the node to search for.  May contain multiple
    1228             :  * components such as "GEOGCS|UNIT".
    1229             :  *
    1230             :  * @return a pointer to the node found, or NULL if none.
    1231             :  */
    1232             : 
    1233       77626 : OGR_SRSNode *OGRSpatialReference::GetAttrNode(const char *pszNodePath)
    1234             : 
    1235             : {
    1236       77626 :     if (strchr(pszNodePath, '|') == nullptr)
    1237             :     {
    1238             :         // Fast path
    1239       41891 :         OGR_SRSNode *poNode = GetRoot();
    1240       41891 :         if (poNode)
    1241       40691 :             poNode = poNode->GetNode(pszNodePath);
    1242       41891 :         return poNode;
    1243             :     }
    1244             : 
    1245             :     char **papszPathTokens =
    1246       35735 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    1247             : 
    1248       35735 :     if (CSLCount(papszPathTokens) < 1)
    1249             :     {
    1250           0 :         CSLDestroy(papszPathTokens);
    1251           0 :         return nullptr;
    1252             :     }
    1253             : 
    1254       35735 :     OGR_SRSNode *poNode = GetRoot();
    1255      107652 :     for (int i = 0; poNode != nullptr && papszPathTokens[i] != nullptr; i++)
    1256             :     {
    1257       71917 :         poNode = poNode->GetNode(papszPathTokens[i]);
    1258             :     }
    1259             : 
    1260       35735 :     CSLDestroy(papszPathTokens);
    1261             : 
    1262       35735 :     return poNode;
    1263             : }
    1264             : 
    1265             : /**
    1266             :  * \brief Find named node in tree.
    1267             :  *
    1268             :  * This method does a pre-order traversal of the node tree searching for
    1269             :  * a node with this exact value (case insensitive), and returns it.  Leaf
    1270             :  * nodes are not considered, under the assumption that they are just
    1271             :  * attribute value nodes.
    1272             :  *
    1273             :  * If a node appears more than once in the tree (such as UNIT for instance),
    1274             :  * the first encountered will be returned.  Use GetNode() on a subtree to be
    1275             :  * more specific.
    1276             :  *
    1277             :  * @param pszNodePath the name of the node to search for.  May contain multiple
    1278             :  * components such as "GEOGCS|UNIT".
    1279             :  *
    1280             :  * @return a pointer to the node found, or NULL if none.
    1281             :  */
    1282             : 
    1283             : const OGR_SRSNode *
    1284       70611 : OGRSpatialReference::GetAttrNode(const char *pszNodePath) const
    1285             : 
    1286             : {
    1287             :     OGR_SRSNode *poNode =
    1288       70611 :         const_cast<OGRSpatialReference *>(this)->GetAttrNode(pszNodePath);
    1289             : 
    1290       70611 :     return poNode;
    1291             : }
    1292             : 
    1293             : /************************************************************************/
    1294             : /*                            GetAttrValue()                            */
    1295             : /************************************************************************/
    1296             : 
    1297             : /**
    1298             :  * \brief Fetch indicated attribute of named node.
    1299             :  *
    1300             :  * This method uses GetAttrNode() to find the named node, and then extracts
    1301             :  * the value of the indicated child.  Thus a call to GetAttrValue("UNIT",1)
    1302             :  * would return the second child of the UNIT node, which is normally the
    1303             :  * length of the linear unit in meters.
    1304             :  *
    1305             :  * This method does the same thing as the C function OSRGetAttrValue().
    1306             :  *
    1307             :  * @param pszNodeName the tree node to look for (case insensitive).
    1308             :  * @param iAttr the child of the node to fetch (zero based).
    1309             :  *
    1310             :  * @return the requested value, or NULL if it fails for any reason.
    1311             :  */
    1312             : 
    1313       21518 : const char *OGRSpatialReference::GetAttrValue(const char *pszNodeName,
    1314             :                                               int iAttr) const
    1315             : 
    1316             : {
    1317       21518 :     const OGR_SRSNode *poNode = GetAttrNode(pszNodeName);
    1318       21518 :     if (poNode == nullptr)
    1319             :     {
    1320        9623 :         if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJECTION"))
    1321             :         {
    1322          14 :             return GetAttrValue("METHOD", iAttr);
    1323             :         }
    1324        9609 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS|PROJECTION"))
    1325             :         {
    1326           0 :             return GetAttrValue("PROJCRS|METHOD", iAttr);
    1327             :         }
    1328        9609 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS"))
    1329             :         {
    1330           1 :             return GetAttrValue("PROJCRS", iAttr);
    1331             :         }
    1332        9608 :         return nullptr;
    1333             :     }
    1334             : 
    1335       11895 :     if (iAttr < 0 || iAttr >= poNode->GetChildCount())
    1336           0 :         return nullptr;
    1337             : 
    1338       11895 :     return poNode->GetChild(iAttr)->GetValue();
    1339             : }
    1340             : 
    1341             : /************************************************************************/
    1342             : /*                          OSRGetAttrValue()                           */
    1343             : /************************************************************************/
    1344             : 
    1345             : /**
    1346             :  * \brief Fetch indicated attribute of named node.
    1347             :  *
    1348             :  * This function is the same as OGRSpatialReference::GetAttrValue()
    1349             :  */
    1350          34 : const char *CPL_STDCALL OSRGetAttrValue(OGRSpatialReferenceH hSRS,
    1351             :                                         const char *pszKey, int iChild)
    1352             : 
    1353             : {
    1354          34 :     VALIDATE_POINTER1(hSRS, "OSRGetAttrValue", nullptr);
    1355             : 
    1356          34 :     return ToPointer(hSRS)->GetAttrValue(pszKey, iChild);
    1357             : }
    1358             : 
    1359             : /************************************************************************/
    1360             : /*                             GetName()                                */
    1361             : /************************************************************************/
    1362             : 
    1363             : /**
    1364             :  * \brief Return the CRS name.
    1365             :  *
    1366             :  * The returned value is only short lived and should not be used after other
    1367             :  * calls to methods on this object.
    1368             :  *
    1369             :  * @since GDAL 3.0
    1370             :  */
    1371             : 
    1372        5132 : const char *OGRSpatialReference::GetName() const
    1373             : {
    1374       10264 :     TAKE_OPTIONAL_LOCK();
    1375             : 
    1376        5132 :     d->refreshProjObj();
    1377        5132 :     if (!d->m_pj_crs)
    1378         113 :         return nullptr;
    1379        5019 :     const char *pszName = proj_get_name(d->m_pj_crs);
    1380             : #if PROJ_VERSION_NUMBER == PROJ_COMPUTE_VERSION(8, 2, 0)
    1381             :     if (d->m_pjType == PJ_TYPE_BOUND_CRS && EQUAL(pszName, "SOURCECRS"))
    1382             :     {
    1383             :         // Work around a bug of PROJ 8.2.0 (fixed in 8.2.1)
    1384             :         PJ *baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
    1385             :         if (baseCRS)
    1386             :         {
    1387             :             pszName = proj_get_name(baseCRS);
    1388             :             // pszName still remains valid after proj_destroy(), since
    1389             :             // d->m_pj_crs keeps a reference to the base CRS C++ object.
    1390             :             proj_destroy(baseCRS);
    1391             :         }
    1392             :     }
    1393             : #endif
    1394        5019 :     return pszName;
    1395             : }
    1396             : 
    1397             : /************************************************************************/
    1398             : /*                           OSRGetName()                               */
    1399             : /************************************************************************/
    1400             : 
    1401             : /**
    1402             :  * \brief Return the CRS name.
    1403             :  *
    1404             :  * The returned value is only short lived and should not be used after other
    1405             :  * calls to methods on this object.
    1406             :  *
    1407             :  * @since GDAL 3.0
    1408             :  */
    1409          41 : const char *OSRGetName(OGRSpatialReferenceH hSRS)
    1410             : 
    1411             : {
    1412          41 :     VALIDATE_POINTER1(hSRS, "OSRGetName", nullptr);
    1413             : 
    1414          41 :     return ToPointer(hSRS)->GetName();
    1415             : }
    1416             : 
    1417             : /************************************************************************/
    1418             : /*                               Clone()                                */
    1419             : /************************************************************************/
    1420             : 
    1421             : /**
    1422             :  * \brief Make a duplicate of this OGRSpatialReference.
    1423             :  *
    1424             :  * This method is the same as the C function OSRClone().
    1425             :  *
    1426             :  * @return a new SRS, which becomes the responsibility of the caller.
    1427             :  */
    1428             : 
    1429       27004 : OGRSpatialReference *OGRSpatialReference::Clone() const
    1430             : 
    1431             : {
    1432       27004 :     OGRSpatialReference *poNewRef = new OGRSpatialReference();
    1433             : 
    1434       27004 :     TAKE_OPTIONAL_LOCK();
    1435             : 
    1436       27003 :     d->refreshProjObj();
    1437       27004 :     if (d->m_pj_crs != nullptr)
    1438       26950 :         poNewRef->d->setPjCRS(proj_clone(d->getPROJContext(), d->m_pj_crs));
    1439       27004 :     if (d->m_bHasCenterLong && d->m_poRoot)
    1440             :     {
    1441           0 :         poNewRef->d->setRoot(d->m_poRoot->Clone());
    1442             :     }
    1443       27004 :     poNewRef->d->m_axisMapping = d->m_axisMapping;
    1444       27004 :     poNewRef->d->m_axisMappingStrategy = d->m_axisMappingStrategy;
    1445       27004 :     poNewRef->d->m_coordinateEpoch = d->m_coordinateEpoch;
    1446       54008 :     return poNewRef;
    1447             : }
    1448             : 
    1449             : /************************************************************************/
    1450             : /*                              OSRClone()                              */
    1451             : /************************************************************************/
    1452             : 
    1453             : /**
    1454             :  * \brief Make a duplicate of this OGRSpatialReference.
    1455             :  *
    1456             :  * This function is the same as OGRSpatialReference::Clone()
    1457             :  */
    1458        1039 : OGRSpatialReferenceH CPL_STDCALL OSRClone(OGRSpatialReferenceH hSRS)
    1459             : 
    1460             : {
    1461        1039 :     VALIDATE_POINTER1(hSRS, "OSRClone", nullptr);
    1462             : 
    1463        1039 :     return ToHandle(ToPointer(hSRS)->Clone());
    1464             : }
    1465             : 
    1466             : /************************************************************************/
    1467             : /*                            dumpReadable()                            */
    1468             : /************************************************************************/
    1469             : 
    1470             : /** Dump pretty wkt to stdout, mostly for debugging.
    1471             :  */
    1472           0 : void OGRSpatialReference::dumpReadable()
    1473             : 
    1474             : {
    1475           0 :     char *pszPrettyWkt = nullptr;
    1476             : 
    1477           0 :     const char *const apszOptions[] = {"FORMAT=WKT2", "MULTILINE=YES", nullptr};
    1478           0 :     exportToWkt(&pszPrettyWkt, apszOptions);
    1479           0 :     printf("%s\n", pszPrettyWkt); /*ok*/
    1480           0 :     CPLFree(pszPrettyWkt);
    1481           0 : }
    1482             : 
    1483             : /************************************************************************/
    1484             : /*                         exportToPrettyWkt()                          */
    1485             : /************************************************************************/
    1486             : 
    1487             : /**
    1488             :  * Convert this SRS into a nicely formatted WKT 1 string for display to a
    1489             :  * person.
    1490             :  *
    1491             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1492             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1493             :  *
    1494             :  * Note that the returned WKT string should be freed with
    1495             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1496             :  *
    1497             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1498             :  * option. Valid values are the one of the FORMAT option of
    1499             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1500             :  *
    1501             :  * This method is the same as the C function OSRExportToPrettyWkt().
    1502             :  *
    1503             :  * @param ppszResult the resulting string is returned in this pointer.
    1504             :  * @param bSimplify TRUE if the AXIS, AUTHORITY and EXTENSION nodes should be
    1505             :  *   stripped off.
    1506             :  *
    1507             :  * @return OGRERR_NONE if successful.
    1508             :  */
    1509             : 
    1510          58 : OGRErr OGRSpatialReference::exportToPrettyWkt(char **ppszResult,
    1511             :                                               int bSimplify) const
    1512             : 
    1513             : {
    1514         116 :     CPLStringList aosOptions;
    1515          58 :     aosOptions.SetNameValue("MULTILINE", "YES");
    1516          58 :     if (bSimplify)
    1517             :     {
    1518           0 :         aosOptions.SetNameValue("FORMAT", "WKT1_SIMPLE");
    1519             :     }
    1520         116 :     return exportToWkt(ppszResult, aosOptions.List());
    1521             : }
    1522             : 
    1523             : /************************************************************************/
    1524             : /*                        OSRExportToPrettyWkt()                        */
    1525             : /************************************************************************/
    1526             : 
    1527             : /**
    1528             :  * \brief Convert this SRS into a nicely formatted WKT 1 string for display to a
    1529             :  * person.
    1530             :  *
    1531             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1532             :  * option. Valid values are the one of the FORMAT option of
    1533             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1534             :  *
    1535             :  * This function is the same as OGRSpatialReference::exportToPrettyWkt().
    1536             :  */
    1537             : 
    1538          56 : OGRErr CPL_STDCALL OSRExportToPrettyWkt(OGRSpatialReferenceH hSRS,
    1539             :                                         char **ppszReturn, int bSimplify)
    1540             : 
    1541             : {
    1542          56 :     VALIDATE_POINTER1(hSRS, "OSRExportToPrettyWkt", OGRERR_FAILURE);
    1543             : 
    1544          56 :     *ppszReturn = nullptr;
    1545             : 
    1546          56 :     return ToPointer(hSRS)->exportToPrettyWkt(ppszReturn, bSimplify);
    1547             : }
    1548             : 
    1549             : /************************************************************************/
    1550             : /*                            exportToWkt()                             */
    1551             : /************************************************************************/
    1552             : 
    1553             : /**
    1554             :  * \brief Convert this SRS into WKT 1 format.
    1555             :  *
    1556             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1557             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1558             :  *
    1559             :  * Note that the returned WKT string should be freed with
    1560             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1561             :  *
    1562             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1563             :  * option. Valid values are the one of the FORMAT option of
    1564             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1565             :  *
    1566             :  * This method is the same as the C function OSRExportToWkt().
    1567             :  *
    1568             :  * @param ppszResult the resulting string is returned in this pointer.
    1569             :  *
    1570             :  * @return OGRERR_NONE if successful.
    1571             :  */
    1572             : 
    1573       12689 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult) const
    1574             : 
    1575             : {
    1576       12689 :     return exportToWkt(ppszResult, nullptr);
    1577             : }
    1578             : 
    1579             : /************************************************************************/
    1580             : /*                GDAL_proj_crs_create_bound_crs_to_WGS84()             */
    1581             : /************************************************************************/
    1582             : 
    1583         553 : static PJ *GDAL_proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, PJ *pj,
    1584             :                                                    bool onlyIfEPSGCode,
    1585             :                                                    bool canModifyHorizPart)
    1586             : {
    1587         553 :     PJ *ret = nullptr;
    1588         553 :     if (proj_get_type(pj) == PJ_TYPE_COMPOUND_CRS)
    1589             :     {
    1590          13 :         auto horizCRS = proj_crs_get_sub_crs(ctx, pj, 0);
    1591          13 :         auto vertCRS = proj_crs_get_sub_crs(ctx, pj, 1);
    1592          13 :         if (horizCRS && proj_get_type(horizCRS) != PJ_TYPE_BOUND_CRS &&
    1593          26 :             vertCRS &&
    1594          10 :             (!onlyIfEPSGCode || proj_get_id_auth_name(horizCRS, 0) != nullptr))
    1595             :         {
    1596             :             auto boundHoriz =
    1597             :                 canModifyHorizPart
    1598           3 :                     ? proj_crs_create_bound_crs_to_WGS84(ctx, horizCRS, nullptr)
    1599           3 :                     : proj_clone(ctx, horizCRS);
    1600             :             auto boundVert =
    1601           3 :                 proj_crs_create_bound_crs_to_WGS84(ctx, vertCRS, nullptr);
    1602           3 :             if (boundHoriz && boundVert)
    1603             :             {
    1604           3 :                 ret = proj_create_compound_crs(ctx, proj_get_name(pj),
    1605             :                                                boundHoriz, boundVert);
    1606             :             }
    1607           3 :             proj_destroy(boundHoriz);
    1608           3 :             proj_destroy(boundVert);
    1609             :         }
    1610          13 :         proj_destroy(horizCRS);
    1611          13 :         proj_destroy(vertCRS);
    1612             :     }
    1613        1040 :     else if (proj_get_type(pj) != PJ_TYPE_BOUND_CRS &&
    1614         500 :              (!onlyIfEPSGCode || proj_get_id_auth_name(pj, 0) != nullptr))
    1615             :     {
    1616         231 :         ret = proj_crs_create_bound_crs_to_WGS84(ctx, pj, nullptr);
    1617             :     }
    1618         553 :     return ret;
    1619             : }
    1620             : 
    1621             : /************************************************************************/
    1622             : /*                            exportToWkt()                             */
    1623             : /************************************************************************/
    1624             : 
    1625             : /**
    1626             :  * Convert this SRS into a WKT string.
    1627             :  *
    1628             :  * Note that the returned WKT string should be freed with
    1629             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1630             :  *
    1631             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1632             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1633             :  *
    1634             :  * @param ppszResult the resulting string is returned in this pointer.
    1635             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    1636             :  * supported options are
    1637             :  * <ul>
    1638             :  * <li>MULTILINE=YES/NO. Defaults to NO.</li>
    1639             :  * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
    1640             :  *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
    1641             :  *     node is returned.
    1642             :  *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
    1643             :  *     node is returned.
    1644             :  *     WKT1 is an alias of WKT1_GDAL.
    1645             :  *     WKT2 will default to the latest revision implemented (currently
    1646             :  *     WKT2_2018) WKT2_2019 can be used as an alias of WKT2_2018 since GDAL 3.2
    1647             :  * </li>
    1648             :  * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
    1649             :  * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
    1650             :  * be exported as a compound CRS whose vertical part represents an ellipsoidal
    1651             :  * height (for example for use with LAS 1.4 WKT1).
    1652             :  * Requires PROJ 7.2.1 and GDAL 3.2.1.</li>
    1653             :  * </ul>
    1654             :  *
    1655             :  * Starting with GDAL 3.0.3, if the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
    1656             :  * configuration option is set to YES, when exporting to WKT1_GDAL, this method
    1657             :  * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
    1658             :  * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
    1659             :  * TOWGS84[] node may be added.
    1660             :  *
    1661             :  * @return OGRERR_NONE if successful.
    1662             :  * @since GDAL 3.0
    1663             :  */
    1664             : 
    1665       16801 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult,
    1666             :                                         const char *const *papszOptions) const
    1667             : {
    1668             :     // In the past calling this method was thread-safe, even if we never
    1669             :     // guaranteed it. Now proj_as_wkt() will cache the result internally,
    1670             :     // so this is no longer thread-safe.
    1671       33602 :     std::lock_guard oLock(d->m_mutex);
    1672             : 
    1673       16801 :     d->refreshProjObj();
    1674       16801 :     if (!d->m_pj_crs)
    1675             :     {
    1676          21 :         *ppszResult = CPLStrdup("");
    1677          21 :         return OGRERR_FAILURE;
    1678             :     }
    1679             : 
    1680       16780 :     if (d->m_bHasCenterLong && d->m_poRoot && !d->m_bMorphToESRI)
    1681             :     {
    1682           0 :         return d->m_poRoot->exportToWkt(ppszResult);
    1683             :     }
    1684             : 
    1685       16780 :     auto ctxt = d->getPROJContext();
    1686       16780 :     auto wktFormat = PJ_WKT1_GDAL;
    1687             :     const char *pszFormat =
    1688       16780 :         CSLFetchNameValueDef(papszOptions, "FORMAT",
    1689             :                              CPLGetConfigOption("OSR_WKT_FORMAT", "DEFAULT"));
    1690       16780 :     if (EQUAL(pszFormat, "DEFAULT"))
    1691       14434 :         pszFormat = "";
    1692             : 
    1693       16780 :     if (EQUAL(pszFormat, "WKT1_ESRI") || d->m_bMorphToESRI)
    1694             :     {
    1695         618 :         wktFormat = PJ_WKT1_ESRI;
    1696             :     }
    1697       16162 :     else if (EQUAL(pszFormat, "WKT1") || EQUAL(pszFormat, "WKT1_GDAL") ||
    1698       15537 :              EQUAL(pszFormat, "WKT1_SIMPLE") || EQUAL(pszFormat, "SFSQL"))
    1699             :     {
    1700         630 :         wktFormat = PJ_WKT1_GDAL;
    1701             :     }
    1702       15532 :     else if (EQUAL(pszFormat, "WKT2_2015"))
    1703             :     {
    1704         265 :         wktFormat = PJ_WKT2_2015;
    1705             :     }
    1706       15267 :     else if (EQUAL(pszFormat, "WKT2") || EQUAL(pszFormat, "WKT2_2018") ||
    1707       14943 :              EQUAL(pszFormat, "WKT2_2019"))
    1708             :     {
    1709        1088 :         wktFormat = PJ_WKT2_2018;
    1710             :     }
    1711       14179 :     else if (pszFormat[0] == '\0')
    1712             :     {
    1713             :         // cppcheck-suppress knownConditionTrueFalse
    1714       14179 :         if (IsDerivedGeographic())
    1715             :         {
    1716           2 :             wktFormat = PJ_WKT2_2018;
    1717             :         }
    1718       27725 :         else if ((IsGeographic() || IsProjected()) && !IsCompound() &&
    1719       13548 :                  GetAxesCount() == 3)
    1720             :         {
    1721          56 :             wktFormat = PJ_WKT2_2018;
    1722             :         }
    1723             :     }
    1724             :     else
    1725             :     {
    1726           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unsupported value for FORMAT");
    1727           0 :         *ppszResult = CPLStrdup("");
    1728           0 :         return OGRERR_FAILURE;
    1729             :     }
    1730             : 
    1731       33560 :     CPLStringList aosOptions;
    1732       16780 :     if (wktFormat != PJ_WKT1_ESRI)
    1733             :     {
    1734       16162 :         aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
    1735             :     }
    1736             :     aosOptions.SetNameValue(
    1737       16780 :         "MULTILINE", CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO"));
    1738             : 
    1739       16780 :     const char *pszAllowEllpsHeightAsVertCS = CSLFetchNameValue(
    1740             :         papszOptions, "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS");
    1741       16780 :     if (pszAllowEllpsHeightAsVertCS)
    1742             :     {
    1743             :         aosOptions.SetNameValue("ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS",
    1744           0 :                                 pszAllowEllpsHeightAsVertCS);
    1745             :     }
    1746             : 
    1747       16780 :     PJ *boundCRS = nullptr;
    1748       31531 :     if (wktFormat == PJ_WKT1_GDAL &&
    1749       14751 :         CPLTestBool(CSLFetchNameValueDef(
    1750             :             papszOptions, "ADD_TOWGS84_ON_EXPORT_TO_WKT1",
    1751             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1", "NO"))))
    1752             :     {
    1753           0 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
    1754           0 :             d->getPROJContext(), d->m_pj_crs, true, true);
    1755             :     }
    1756             : 
    1757       33560 :     CPLErrorAccumulator oErrorAccumulator;
    1758             :     const char *pszWKT;
    1759             :     {
    1760       16780 :         auto oAccumulator = oErrorAccumulator.InstallForCurrentScope();
    1761       16780 :         CPL_IGNORE_RET_VAL(oAccumulator);
    1762       16780 :         pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs, wktFormat,
    1763       16780 :                              aosOptions.List());
    1764             :     }
    1765       16782 :     for (const auto &oError : oErrorAccumulator.GetErrors())
    1766             :     {
    1767          32 :         if (pszFormat[0] == '\0' &&
    1768          14 :             (oError.msg.find("Unsupported conversion method") !=
    1769           2 :                  std::string::npos ||
    1770           2 :              oError.msg.find("can only be exported to WKT2") !=
    1771           0 :                  std::string::npos ||
    1772           0 :              oError.msg.find("can only be exported since WKT2:2019") !=
    1773             :                  std::string::npos))
    1774             :         {
    1775          14 :             CPLErrorReset();
    1776             :             // If we cannot export in the default mode (WKT1), retry with WKT2
    1777          14 :             pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs,
    1778          14 :                                  PJ_WKT2_2018, aosOptions.List());
    1779          14 :             break;
    1780             :         }
    1781           2 :         CPLError(oError.type, oError.no, "%s", oError.msg.c_str());
    1782             :     }
    1783             : 
    1784       16780 :     if (!pszWKT)
    1785             :     {
    1786           2 :         *ppszResult = CPLStrdup("");
    1787           2 :         proj_destroy(boundCRS);
    1788           2 :         return OGRERR_FAILURE;
    1789             :     }
    1790             : 
    1791       16778 :     if (EQUAL(pszFormat, "SFSQL") || EQUAL(pszFormat, "WKT1_SIMPLE"))
    1792             :     {
    1793           5 :         OGR_SRSNode oRoot;
    1794           5 :         oRoot.importFromWkt(&pszWKT);
    1795           5 :         oRoot.StripNodes("AXIS");
    1796           5 :         if (EQUAL(pszFormat, "SFSQL"))
    1797             :         {
    1798           3 :             oRoot.StripNodes("TOWGS84");
    1799             :         }
    1800           5 :         oRoot.StripNodes("AUTHORITY");
    1801           5 :         oRoot.StripNodes("EXTENSION");
    1802             :         OGRErr eErr;
    1803           5 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO")))
    1804           2 :             eErr = oRoot.exportToPrettyWkt(ppszResult, 1);
    1805             :         else
    1806           3 :             eErr = oRoot.exportToWkt(ppszResult);
    1807           5 :         proj_destroy(boundCRS);
    1808           5 :         return eErr;
    1809             :     }
    1810             : 
    1811       16773 :     *ppszResult = CPLStrdup(pszWKT);
    1812             : 
    1813             : #if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
    1814       16773 :     if (wktFormat == PJ_WKT2_2018)
    1815             :     {
    1816             :         // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
    1817             :         // related to a wrong EPSG code assigned to UTM South conversions
    1818        1146 :         char *pszPtr = strstr(*ppszResult, "CONVERSION[\"UTM zone ");
    1819        1146 :         if (pszPtr)
    1820             :         {
    1821         269 :             pszPtr += strlen("CONVERSION[\"UTM zone ");
    1822         269 :             const int nZone = atoi(pszPtr);
    1823         806 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    1824         537 :                 ++pszPtr;
    1825         269 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' &&
    1826           1 :                 pszPtr[1] == '"' && pszPtr[2] == ',')
    1827             :             {
    1828           1 :                 pszPtr += 3;
    1829           1 :                 int nLevel = 0;
    1830           1 :                 bool bInString = false;
    1831             :                 // Find the ID node corresponding to this CONVERSION node
    1832         480 :                 while (*pszPtr)
    1833             :                 {
    1834         480 :                     if (bInString)
    1835             :                     {
    1836         197 :                         if (*pszPtr == '"' && pszPtr[1] == '"')
    1837             :                         {
    1838           0 :                             ++pszPtr;
    1839             :                         }
    1840         197 :                         else if (*pszPtr == '"')
    1841             :                         {
    1842          17 :                             bInString = false;
    1843             :                         }
    1844             :                     }
    1845         283 :                     else if (nLevel == 0 && STARTS_WITH_CI(pszPtr, "ID["))
    1846             :                     {
    1847           1 :                         if (STARTS_WITH_CI(pszPtr, CPLSPrintf("ID[\"EPSG\",%d]",
    1848             :                                                               17000 + nZone)))
    1849             :                         {
    1850           1 :                             CPLAssert(pszPtr[11] == '7');
    1851           1 :                             CPLAssert(pszPtr[12] == '0');
    1852           1 :                             pszPtr[11] = '6';
    1853           1 :                             pszPtr[12] = '1';
    1854             :                         }
    1855           1 :                         break;
    1856             :                     }
    1857         282 :                     else if (*pszPtr == '"')
    1858             :                     {
    1859          17 :                         bInString = true;
    1860             :                     }
    1861         265 :                     else if (*pszPtr == '[')
    1862             :                     {
    1863          17 :                         ++nLevel;
    1864             :                     }
    1865         248 :                     else if (*pszPtr == ']')
    1866             :                     {
    1867          17 :                         --nLevel;
    1868             :                     }
    1869             : 
    1870         479 :                     ++pszPtr;
    1871             :                 }
    1872             :             }
    1873             :         }
    1874             :     }
    1875             : #endif
    1876             : 
    1877       16773 :     proj_destroy(boundCRS);
    1878       16773 :     return OGRERR_NONE;
    1879             : }
    1880             : 
    1881             : /************************************************************************/
    1882             : /*                            exportToWkt()                             */
    1883             : /************************************************************************/
    1884             : 
    1885             : /**
    1886             :  * Convert this SRS into a WKT string.
    1887             :  *
    1888             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1889             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1890             :  *
    1891             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    1892             :  * supported options are
    1893             :  * <ul>
    1894             :  * <li>MULTILINE=YES/NO. Defaults to NO.</li>
    1895             :  * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
    1896             :  *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
    1897             :  *     node is returned.
    1898             :  *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
    1899             :  *     node is returned.
    1900             :  *     WKT1 is an alias of WKT1_GDAL.
    1901             :  *     WKT2 will default to the latest revision implemented (currently
    1902             :  *     WKT2_2019)
    1903             :  * </li>
    1904             :  * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
    1905             :  * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
    1906             :  * be exported as a compound CRS whose vertical part represents an ellipsoidal
    1907             :  * height (for example for use with LAS 1.4 WKT1).
    1908             :  * Requires PROJ 7.2.1.</li>
    1909             :  * </ul>
    1910             :  *
    1911             :  * If the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
    1912             :  * configuration option is set to YES, when exporting to WKT1_GDAL, this method
    1913             :  * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
    1914             :  * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
    1915             :  * TOWGS84[] node may be added.
    1916             :  *
    1917             :  * @return a non-empty string if successful.
    1918             :  * @since GDAL 3.9
    1919             :  */
    1920             : 
    1921             : std::string
    1922          97 : OGRSpatialReference::exportToWkt(const char *const *papszOptions) const
    1923             : {
    1924          97 :     std::string osWKT;
    1925          97 :     char *pszWKT = nullptr;
    1926          97 :     if (exportToWkt(&pszWKT, papszOptions) == OGRERR_NONE)
    1927          97 :         osWKT = pszWKT;
    1928          97 :     CPLFree(pszWKT);
    1929         194 :     return osWKT;
    1930             : }
    1931             : 
    1932             : /************************************************************************/
    1933             : /*                           OSRExportToWkt()                           */
    1934             : /************************************************************************/
    1935             : 
    1936             : /**
    1937             :  * \brief Convert this SRS into WKT 1 format.
    1938             :  *
    1939             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1940             :  * Issues</a> page for implementation details of WKT in OGR.
    1941             :  *
    1942             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1943             :  * option. Valid values are the one of the FORMAT option of
    1944             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1945             :  *
    1946             :  * This function is the same as OGRSpatialReference::exportToWkt().
    1947             :  */
    1948             : 
    1949         878 : OGRErr CPL_STDCALL OSRExportToWkt(OGRSpatialReferenceH hSRS, char **ppszReturn)
    1950             : 
    1951             : {
    1952         878 :     VALIDATE_POINTER1(hSRS, "OSRExportToWkt", OGRERR_FAILURE);
    1953             : 
    1954         878 :     *ppszReturn = nullptr;
    1955             : 
    1956         878 :     return ToPointer(hSRS)->exportToWkt(ppszReturn);
    1957             : }
    1958             : 
    1959             : /************************************************************************/
    1960             : /*                          OSRExportToWktEx()                          */
    1961             : /************************************************************************/
    1962             : 
    1963             : /**
    1964             :  * \brief Convert this SRS into WKT format.
    1965             :  *
    1966             :  * This function is the same as OGRSpatialReference::exportToWkt(char **
    1967             :  * ppszResult,const char* const* papszOptions ) const
    1968             :  *
    1969             :  * @since GDAL 3.0
    1970             :  */
    1971             : 
    1972        1312 : OGRErr OSRExportToWktEx(OGRSpatialReferenceH hSRS, char **ppszReturn,
    1973             :                         const char *const *papszOptions)
    1974             : {
    1975        1312 :     VALIDATE_POINTER1(hSRS, "OSRExportToWktEx", OGRERR_FAILURE);
    1976             : 
    1977        1312 :     *ppszReturn = nullptr;
    1978             : 
    1979        1312 :     return ToPointer(hSRS)->exportToWkt(ppszReturn, papszOptions);
    1980             : }
    1981             : 
    1982             : /************************************************************************/
    1983             : /*                       exportToPROJJSON()                             */
    1984             : /************************************************************************/
    1985             : 
    1986             : /**
    1987             :  * Convert this SRS into a PROJJSON string.
    1988             :  *
    1989             :  * Note that the returned JSON string should be freed with
    1990             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1991             :  *
    1992             :  * @param ppszResult the resulting string is returned in this pointer.
    1993             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    1994             :  * supported options are
    1995             :  * <ul>
    1996             :  * <li>MULTILINE=YES/NO. Defaults to YES</li>
    1997             :  * <li>INDENTATION_WIDTH=number. Defaults to 2 (when multiline output is
    1998             :  * on).</li>
    1999             :  * <li>SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to
    2000             :  * disable it.</li>
    2001             :  * </ul>
    2002             :  *
    2003             :  * @return OGRERR_NONE if successful.
    2004             :  * @since GDAL 3.1 and PROJ 6.2
    2005             :  */
    2006             : 
    2007        2119 : OGRErr OGRSpatialReference::exportToPROJJSON(
    2008             :     char **ppszResult, CPL_UNUSED const char *const *papszOptions) const
    2009             : {
    2010        4238 :     TAKE_OPTIONAL_LOCK();
    2011             : 
    2012        2119 :     d->refreshProjObj();
    2013        2119 :     if (!d->m_pj_crs)
    2014             :     {
    2015           0 :         *ppszResult = nullptr;
    2016           0 :         return OGRERR_FAILURE;
    2017             :     }
    2018             : 
    2019             :     const char *pszPROJJSON =
    2020        2119 :         proj_as_projjson(d->getPROJContext(), d->m_pj_crs, papszOptions);
    2021             : 
    2022        2119 :     if (!pszPROJJSON)
    2023             :     {
    2024           0 :         *ppszResult = CPLStrdup("");
    2025           0 :         return OGRERR_FAILURE;
    2026             :     }
    2027             : 
    2028        2119 :     *ppszResult = CPLStrdup(pszPROJJSON);
    2029             : 
    2030             : #if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
    2031             :     {
    2032             :         // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
    2033             :         // related to a wrong EPSG code assigned to UTM South conversions
    2034        2119 :         char *pszPtr = strstr(*ppszResult, "\"name\": \"UTM zone ");
    2035        2119 :         if (pszPtr)
    2036             :         {
    2037         231 :             pszPtr += strlen("\"name\": \"UTM zone ");
    2038         231 :             const int nZone = atoi(pszPtr);
    2039         692 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    2040         461 :                 ++pszPtr;
    2041         231 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' && pszPtr[1] == '"')
    2042             :             {
    2043           4 :                 pszPtr += 2;
    2044           4 :                 int nLevel = 0;
    2045           4 :                 bool bInString = false;
    2046             :                 // Find the id node corresponding to this conversion node
    2047        5299 :                 while (*pszPtr)
    2048             :                 {
    2049        5299 :                     if (bInString)
    2050             :                     {
    2051        1950 :                         if (*pszPtr == '\\')
    2052             :                         {
    2053           0 :                             ++pszPtr;
    2054             :                         }
    2055        1950 :                         else if (*pszPtr == '"')
    2056             :                         {
    2057         244 :                             bInString = false;
    2058             :                         }
    2059             :                     }
    2060        3349 :                     else if (nLevel == 0 && STARTS_WITH(pszPtr, "\"id\": {"))
    2061             :                     {
    2062           4 :                         const char *pszNextEndCurl = strchr(pszPtr, '}');
    2063             :                         const char *pszAuthEPSG =
    2064           4 :                             strstr(pszPtr, "\"authority\": \"EPSG\"");
    2065           4 :                         char *pszCode = strstr(
    2066             :                             pszPtr, CPLSPrintf("\"code\": %d", 17000 + nZone));
    2067           4 :                         if (pszAuthEPSG && pszCode && pszNextEndCurl &&
    2068           4 :                             pszNextEndCurl - pszAuthEPSG > 0 &&
    2069           4 :                             pszNextEndCurl - pszCode > 0)
    2070             :                         {
    2071           4 :                             CPLAssert(pszCode[9] == '7');
    2072           4 :                             CPLAssert(pszCode[10] == '0');
    2073           4 :                             pszCode[9] = '6';
    2074           4 :                             pszCode[10] = '1';
    2075             :                         }
    2076           4 :                         break;
    2077             :                     }
    2078        3345 :                     else if (*pszPtr == '"')
    2079             :                     {
    2080         244 :                         bInString = true;
    2081             :                     }
    2082        3101 :                     else if (*pszPtr == '{' || *pszPtr == '[')
    2083             :                     {
    2084          60 :                         ++nLevel;
    2085             :                     }
    2086        3041 :                     else if (*pszPtr == '}' || *pszPtr == ']')
    2087             :                     {
    2088          60 :                         --nLevel;
    2089             :                     }
    2090             : 
    2091        5295 :                     ++pszPtr;
    2092             :                 }
    2093             :             }
    2094             :         }
    2095             :     }
    2096             : #endif
    2097             : 
    2098        2119 :     return OGRERR_NONE;
    2099             : }
    2100             : 
    2101             : /************************************************************************/
    2102             : /*                          OSRExportToPROJJSON()                       */
    2103             : /************************************************************************/
    2104             : 
    2105             : /**
    2106             :  * \brief Convert this SRS into PROJJSON format.
    2107             :  *
    2108             :  * This function is the same as OGRSpatialReference::exportToPROJJSON() const
    2109             :  *
    2110             :  * @since GDAL 3.1 and PROJ 6.2
    2111             :  */
    2112             : 
    2113          62 : OGRErr OSRExportToPROJJSON(OGRSpatialReferenceH hSRS, char **ppszReturn,
    2114             :                            const char *const *papszOptions)
    2115             : {
    2116          62 :     VALIDATE_POINTER1(hSRS, "OSRExportToPROJJSON", OGRERR_FAILURE);
    2117             : 
    2118          62 :     *ppszReturn = nullptr;
    2119             : 
    2120          62 :     return ToPointer(hSRS)->exportToPROJJSON(ppszReturn, papszOptions);
    2121             : }
    2122             : 
    2123             : /************************************************************************/
    2124             : /*                           importFromWkt()                            */
    2125             : /************************************************************************/
    2126             : 
    2127             : /**
    2128             :  * \brief Import from WKT string.
    2129             :  *
    2130             :  * This method will wipe the existing SRS definition, and
    2131             :  * reassign it based on the contents of the passed WKT string.  Only as
    2132             :  * much of the input string as needed to construct this SRS is consumed from
    2133             :  * the input string, and the input string pointer
    2134             :  * is then updated to point to the remaining (unused) input.
    2135             :  *
    2136             :  * Starting with PROJ 9.2, if invoked on a COORDINATEMETADATA[] construct,
    2137             :  * the CRS contained in it will be used to fill the OGRSpatialReference object,
    2138             :  * and the coordinate epoch potentially present used as the coordinate epoch
    2139             :  * property of the OGRSpatialReference object.
    2140             :  *
    2141             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2142             :  * Issues</a> page for implementation details of WKT in OGR.
    2143             :  *
    2144             :  * This method is the same as the C function OSRImportFromWkt().
    2145             :  *
    2146             :  * @param ppszInput Pointer to pointer to input.  The pointer is updated to
    2147             :  * point to remaining unused input text.
    2148             :  *
    2149             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2150             :  * fails for any reason.
    2151             :  * @since GDAL 2.3
    2152             :  */
    2153             : 
    2154       16053 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput)
    2155             : 
    2156             : {
    2157       16053 :     return importFromWkt(ppszInput, nullptr);
    2158             : }
    2159             : 
    2160             : /************************************************************************/
    2161             : /*                           importFromWkt()                            */
    2162             : /************************************************************************/
    2163             : 
    2164             : /*! @cond Doxygen_Suppress */
    2165             : 
    2166          21 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput,
    2167             :                                           CSLConstList papszOptions)
    2168             : 
    2169             : {
    2170          21 :     return importFromWkt(&pszInput, papszOptions);
    2171             : }
    2172             : 
    2173       16074 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput,
    2174             :                                           CSLConstList papszOptions)
    2175             : 
    2176             : {
    2177       32148 :     TAKE_OPTIONAL_LOCK();
    2178             : 
    2179       16074 :     if (!ppszInput || !*ppszInput)
    2180           0 :         return OGRERR_FAILURE;
    2181             : 
    2182       16074 :     if (strlen(*ppszInput) > 100 * 1000 &&
    2183           0 :         CPLTestBool(CPLGetConfigOption("OSR_IMPORT_FROM_WKT_LIMIT", "YES")))
    2184             :     {
    2185           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2186             :                  "Suspiciously large input for importFromWkt(). Rejecting it. "
    2187             :                  "You can remove this limitation by definition the "
    2188             :                  "OSR_IMPORT_FROM_WKT_LIMIT configuration option to NO.");
    2189           0 :         return OGRERR_FAILURE;
    2190             :     }
    2191             : 
    2192       16074 :     Clear();
    2193             : 
    2194       16074 :     bool canCache = false;
    2195       16074 :     auto tlsCache = OSRGetProjTLSCache();
    2196       32148 :     std::string osWkt;
    2197       16074 :     if (**ppszInput)
    2198             :     {
    2199       15500 :         osWkt = *ppszInput;
    2200       15500 :         auto cachedObj = tlsCache->GetPJForWKT(osWkt);
    2201       15500 :         if (cachedObj)
    2202             :         {
    2203       13900 :             d->setPjCRS(cachedObj);
    2204             :         }
    2205             :         else
    2206             :         {
    2207        3200 :             CPLStringList aosOptions(papszOptions);
    2208        1600 :             if (aosOptions.FetchNameValue("STRICT") == nullptr)
    2209        1600 :                 aosOptions.SetNameValue("STRICT", "NO");
    2210        1600 :             PROJ_STRING_LIST warnings = nullptr;
    2211        1600 :             PROJ_STRING_LIST errors = nullptr;
    2212        1600 :             auto ctxt = d->getPROJContext();
    2213        1600 :             auto pj = proj_create_from_wkt(ctxt, *ppszInput, aosOptions.List(),
    2214             :                                            &warnings, &errors);
    2215        1600 :             d->setPjCRS(pj);
    2216             : 
    2217        1650 :             for (auto iter = warnings; iter && *iter; ++iter)
    2218             :             {
    2219          50 :                 d->m_wktImportWarnings.push_back(*iter);
    2220             :             }
    2221        1834 :             for (auto iter = errors; iter && *iter; ++iter)
    2222             :             {
    2223         234 :                 d->m_wktImportErrors.push_back(*iter);
    2224         234 :                 if (!d->m_pj_crs)
    2225             :                 {
    2226          35 :                     CPLError(CE_Failure, CPLE_AppDefined, "%s", *iter);
    2227             :                 }
    2228             :             }
    2229        1600 :             if (warnings == nullptr && errors == nullptr)
    2230             :             {
    2231        1323 :                 canCache = true;
    2232             :             }
    2233        1600 :             proj_string_list_destroy(warnings);
    2234        1600 :             proj_string_list_destroy(errors);
    2235             :         }
    2236             :     }
    2237       16074 :     if (!d->m_pj_crs)
    2238         609 :         return OGRERR_CORRUPT_DATA;
    2239             : 
    2240             :     // Only accept CRS objects
    2241       15465 :     if (!proj_is_crs(d->m_pj_crs))
    2242             :     {
    2243           0 :         Clear();
    2244           0 :         return OGRERR_CORRUPT_DATA;
    2245             :     }
    2246             : 
    2247       15465 :     if (canCache)
    2248             :     {
    2249        1323 :         tlsCache->CachePJForWKT(osWkt, d->m_pj_crs);
    2250             :     }
    2251             : 
    2252       15465 :     if (strstr(*ppszInput, "CENTER_LONG"))
    2253             :     {
    2254           0 :         auto poRoot = new OGR_SRSNode();
    2255           0 :         d->setRoot(poRoot);
    2256           0 :         const char *pszTmp = *ppszInput;
    2257           0 :         poRoot->importFromWkt(&pszTmp);
    2258           0 :         d->m_bHasCenterLong = true;
    2259             :     }
    2260             : 
    2261             :     // TODO? we don't really update correctly since we assume that the
    2262             :     // passed string is only WKT.
    2263       15465 :     *ppszInput += strlen(*ppszInput);
    2264       15465 :     return OGRERR_NONE;
    2265             : 
    2266             : #if no_longer_implemented_for_now
    2267             :     /* -------------------------------------------------------------------- */
    2268             :     /*      The following seems to try and detect and unconsumed            */
    2269             :     /*      VERTCS[] coordinate system definition (ESRI style) and to       */
    2270             :     /*      import and attach it to the existing root.  Likely we will      */
    2271             :     /*      need to extend this somewhat to bring it into an acceptable     */
    2272             :     /*      OGRSpatialReference organization at some point.                 */
    2273             :     /* -------------------------------------------------------------------- */
    2274             :     if (strlen(*ppszInput) > 0 && strstr(*ppszInput, "VERTCS"))
    2275             :     {
    2276             :         if (((*ppszInput)[0]) == ',')
    2277             :             (*ppszInput)++;
    2278             :         OGR_SRSNode *poNewChild = new OGR_SRSNode();
    2279             :         poRoot->AddChild(poNewChild);
    2280             :         return poNewChild->importFromWkt(ppszInput);
    2281             :     }
    2282             : #endif
    2283             : }
    2284             : 
    2285             : /*! @endcond */
    2286             : 
    2287             : /**
    2288             :  * \brief Import from WKT string.
    2289             :  *
    2290             :  * This method will wipe the existing SRS definition, and
    2291             :  * reassign it based on the contents of the passed WKT string.  Only as
    2292             :  * much of the input string as needed to construct this SRS is consumed from
    2293             :  * the input string, and the input string pointer
    2294             :  * is then updated to point to the remaining (unused) input.
    2295             :  *
    2296             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2297             :  * Issues</a> page for implementation details of WKT in OGR.
    2298             :  *
    2299             :  * This method is the same as the C function OSRImportFromWkt().
    2300             :  *
    2301             :  * @param ppszInput Pointer to pointer to input.  The pointer is updated to
    2302             :  * point to remaining unused input text.
    2303             :  *
    2304             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2305             :  * fails for any reason.
    2306             :  * @deprecated GDAL 2.3. Use importFromWkt(const char**) or importFromWkt(const
    2307             :  * char*)
    2308             :  */
    2309             : 
    2310           0 : OGRErr OGRSpatialReference::importFromWkt(char **ppszInput)
    2311             : 
    2312             : {
    2313           0 :     return importFromWkt(const_cast<const char **>(ppszInput));
    2314             : }
    2315             : 
    2316             : /**
    2317             :  * \brief Import from WKT string.
    2318             :  *
    2319             :  * This method will wipe the existing SRS definition, and
    2320             :  * reassign it based on the contents of the passed WKT string.  Only as
    2321             :  * much of the input string as needed to construct this SRS is consumed from
    2322             :  * the input string, and the input string pointer
    2323             :  * is then updated to point to the remaining (unused) input.
    2324             :  *
    2325             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2326             :  * Issues</a> page for implementation details of WKT in OGR.
    2327             :  *
    2328             :  * @param pszInput Input WKT
    2329             :  *
    2330             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2331             :  * fails for any reason.
    2332             :  * @since GDAL 2.3
    2333             :  */
    2334             : 
    2335       15765 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput)
    2336             : {
    2337       15765 :     return importFromWkt(&pszInput);
    2338             : }
    2339             : 
    2340             : /************************************************************************/
    2341             : /*                              Validate()                              */
    2342             : /************************************************************************/
    2343             : 
    2344             : /**
    2345             :  * \brief Validate CRS imported with importFromWkt() or with modified with
    2346             :  * direct node manipulations. Otherwise the CRS should be always valid.
    2347             :  *
    2348             :  * This method attempts to verify that the spatial reference system is
    2349             :  * well formed, and consists of known tokens.  The validation is not
    2350             :  * comprehensive.
    2351             :  *
    2352             :  * This method is the same as the C function OSRValidate().
    2353             :  *
    2354             :  * @return OGRERR_NONE if all is fine, OGRERR_CORRUPT_DATA if the SRS is
    2355             :  * not well formed, and OGRERR_UNSUPPORTED_SRS if the SRS is well formed,
    2356             :  * but contains non-standard PROJECTION[] values.
    2357             :  */
    2358             : 
    2359         116 : OGRErr OGRSpatialReference::Validate() const
    2360             : 
    2361             : {
    2362         232 :     TAKE_OPTIONAL_LOCK();
    2363             : 
    2364         154 :     for (const auto &str : d->m_wktImportErrors)
    2365             :     {
    2366          38 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2367             :     }
    2368         116 :     for (const auto &str : d->m_wktImportWarnings)
    2369             :     {
    2370           0 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2371             :     }
    2372         116 :     if (!d->m_pj_crs || !d->m_wktImportErrors.empty())
    2373             :     {
    2374          37 :         return OGRERR_CORRUPT_DATA;
    2375             :     }
    2376          79 :     if (!d->m_wktImportWarnings.empty())
    2377             :     {
    2378           0 :         return OGRERR_UNSUPPORTED_SRS;
    2379             :     }
    2380          79 :     return OGRERR_NONE;
    2381             : }
    2382             : 
    2383             : /************************************************************************/
    2384             : /*                            OSRValidate()                             */
    2385             : /************************************************************************/
    2386             : /**
    2387             :  * \brief Validate SRS tokens.
    2388             :  *
    2389             :  * This function is the same as the C++ method OGRSpatialReference::Validate().
    2390             :  */
    2391         114 : OGRErr OSRValidate(OGRSpatialReferenceH hSRS)
    2392             : 
    2393             : {
    2394         114 :     VALIDATE_POINTER1(hSRS, "OSRValidate", OGRERR_FAILURE);
    2395             : 
    2396         114 :     return OGRSpatialReference::FromHandle(hSRS)->Validate();
    2397             : }
    2398             : 
    2399             : /************************************************************************/
    2400             : /*                          OSRImportFromWkt()                          */
    2401             : /************************************************************************/
    2402             : 
    2403             : /**
    2404             :  * \brief Import from WKT string.
    2405             :  *
    2406             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2407             :  * Issues</a> page for implementation details of WKT in OGR.
    2408             :  *
    2409             :  * This function is the same as OGRSpatialReference::importFromWkt().
    2410             :  */
    2411             : 
    2412         288 : OGRErr OSRImportFromWkt(OGRSpatialReferenceH hSRS, char **ppszInput)
    2413             : 
    2414             : {
    2415         288 :     VALIDATE_POINTER1(hSRS, "OSRImportFromWkt", OGRERR_FAILURE);
    2416             : 
    2417         288 :     return ToPointer(hSRS)->importFromWkt(const_cast<const char **>(ppszInput));
    2418             : }
    2419             : 
    2420             : /************************************************************************/
    2421             : /*                              SetNode()                               */
    2422             : /************************************************************************/
    2423             : 
    2424             : /**
    2425             :  * \brief Set attribute value in spatial reference.
    2426             :  *
    2427             :  * Missing intermediate nodes in the path will be created if not already
    2428             :  * in existence.  If the attribute has no children one will be created and
    2429             :  * assigned the value otherwise the zeroth child will be assigned the value.
    2430             :  *
    2431             :  * This method does the same as the C function OSRSetAttrValue().
    2432             :  *
    2433             :  * @param pszNodePath full path to attribute to be set.  For instance
    2434             :  * "PROJCS|GEOGCS|UNIT".
    2435             :  *
    2436             :  * @param pszNewNodeValue value to be assigned to node, such as "meter".
    2437             :  * This may be NULL if you just want to force creation of the intermediate
    2438             :  * path.
    2439             :  *
    2440             :  * @return OGRERR_NONE on success.
    2441             :  */
    2442             : 
    2443         592 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath,
    2444             :                                     const char *pszNewNodeValue)
    2445             : 
    2446             : {
    2447        1184 :     TAKE_OPTIONAL_LOCK();
    2448             : 
    2449             :     char **papszPathTokens =
    2450         592 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    2451             : 
    2452         592 :     if (CSLCount(papszPathTokens) < 1)
    2453             :     {
    2454           0 :         CSLDestroy(papszPathTokens);
    2455           0 :         return OGRERR_FAILURE;
    2456             :     }
    2457             : 
    2458        1033 :     if (GetRoot() == nullptr ||
    2459         441 :         !EQUAL(papszPathTokens[0], GetRoot()->GetValue()))
    2460             :     {
    2461         274 :         if (EQUAL(papszPathTokens[0], "PROJCS") &&
    2462         119 :             CSLCount(papszPathTokens) == 1)
    2463             :         {
    2464         119 :             CSLDestroy(papszPathTokens);
    2465         119 :             return SetProjCS(pszNewNodeValue);
    2466             :         }
    2467             :         else
    2468             :         {
    2469          36 :             SetRoot(new OGR_SRSNode(papszPathTokens[0]));
    2470             :         }
    2471             :     }
    2472             : 
    2473         473 :     OGR_SRSNode *poNode = GetRoot();
    2474         731 :     for (int i = 1; papszPathTokens[i] != nullptr; i++)
    2475             :     {
    2476         258 :         int j = 0;  // Used after for.
    2477             : 
    2478         645 :         for (; j < poNode->GetChildCount(); j++)
    2479             :         {
    2480         585 :             if (EQUAL(poNode->GetChild(j)->GetValue(), papszPathTokens[i]))
    2481             :             {
    2482         198 :                 poNode = poNode->GetChild(j);
    2483         198 :                 j = -1;
    2484         198 :                 break;
    2485             :             }
    2486             :         }
    2487             : 
    2488         258 :         if (j != -1)
    2489             :         {
    2490          60 :             OGR_SRSNode *poNewNode = new OGR_SRSNode(papszPathTokens[i]);
    2491          60 :             poNode->AddChild(poNewNode);
    2492          60 :             poNode = poNewNode;
    2493             :         }
    2494             :     }
    2495             : 
    2496         473 :     CSLDestroy(papszPathTokens);
    2497             : 
    2498         473 :     if (pszNewNodeValue != nullptr)
    2499             :     {
    2500         473 :         if (poNode->GetChildCount() > 0)
    2501         377 :             poNode->GetChild(0)->SetValue(pszNewNodeValue);
    2502             :         else
    2503          96 :             poNode->AddChild(new OGR_SRSNode(pszNewNodeValue));
    2504             :     };
    2505         473 :     return OGRERR_NONE;
    2506             : }
    2507             : 
    2508             : /************************************************************************/
    2509             : /*                          OSRSetAttrValue()                           */
    2510             : /************************************************************************/
    2511             : 
    2512             : /**
    2513             :  * \brief Set attribute value in spatial reference.
    2514             :  *
    2515             :  * This function is the same as OGRSpatialReference::SetNode()
    2516             :  */
    2517           1 : OGRErr CPL_STDCALL OSRSetAttrValue(OGRSpatialReferenceH hSRS,
    2518             :                                    const char *pszPath, const char *pszValue)
    2519             : 
    2520             : {
    2521           1 :     VALIDATE_POINTER1(hSRS, "OSRSetAttrValue", OGRERR_FAILURE);
    2522             : 
    2523           1 :     return ToPointer(hSRS)->SetNode(pszPath, pszValue);
    2524             : }
    2525             : 
    2526             : /************************************************************************/
    2527             : /*                              SetNode()                               */
    2528             : /************************************************************************/
    2529             : 
    2530             : /**
    2531             :  * \brief Set attribute value in spatial reference.
    2532             :  *
    2533             :  * Missing intermediate nodes in the path will be created if not already
    2534             :  * in existence.  If the attribute has no children one will be created and
    2535             :  * assigned the value otherwise the zeroth child will be assigned the value.
    2536             :  *
    2537             :  * This method does the same as the C function OSRSetAttrValue().
    2538             :  *
    2539             :  * @param pszNodePath full path to attribute to be set.  For instance
    2540             :  * "PROJCS|GEOGCS|UNIT".
    2541             :  *
    2542             :  * @param dfValue value to be assigned to node.
    2543             :  *
    2544             :  * @return OGRERR_NONE on success.
    2545             :  */
    2546             : 
    2547           0 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath, double dfValue)
    2548             : 
    2549             : {
    2550           0 :     char szValue[64] = {'\0'};
    2551             : 
    2552           0 :     if (std::abs(dfValue - static_cast<int>(dfValue)) == 0.0)
    2553           0 :         snprintf(szValue, sizeof(szValue), "%d", static_cast<int>(dfValue));
    2554             :     else
    2555           0 :         OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    2556             : 
    2557           0 :     return SetNode(pszNodePath, szValue);
    2558             : }
    2559             : 
    2560             : /************************************************************************/
    2561             : /*                          SetAngularUnits()                           */
    2562             : /************************************************************************/
    2563             : 
    2564             : /**
    2565             :  * \brief Set the angular units for the geographic coordinate system.
    2566             :  *
    2567             :  * This method creates a UNIT subnode with the specified values as a
    2568             :  * child of the GEOGCS node.
    2569             :  *
    2570             :  * This method does the same as the C function OSRSetAngularUnits().
    2571             :  *
    2572             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2573             :  * names can be found in ogr_srs_api.h such as SRS_UA_DEGREE.
    2574             :  *
    2575             :  * @param dfInRadians the value to multiple by an angle in the indicated
    2576             :  * units to transform to radians.  Some standard conversion factors can
    2577             :  * be found in ogr_srs_api.h.
    2578             :  *
    2579             :  * @return OGRERR_NONE on success.
    2580             :  */
    2581             : 
    2582        1033 : OGRErr OGRSpatialReference::SetAngularUnits(const char *pszUnitsName,
    2583             :                                             double dfInRadians)
    2584             : 
    2585             : {
    2586        2066 :     TAKE_OPTIONAL_LOCK();
    2587             : 
    2588        1033 :     d->bNormInfoSet = FALSE;
    2589             : 
    2590        1033 :     d->refreshProjObj();
    2591        1033 :     if (!d->m_pj_crs)
    2592           0 :         return OGRERR_FAILURE;
    2593        1033 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2594        1033 :     if (!geodCRS)
    2595           0 :         return OGRERR_FAILURE;
    2596        1033 :     proj_destroy(geodCRS);
    2597        1033 :     d->demoteFromBoundCRS();
    2598        1033 :     d->setPjCRS(proj_crs_alter_cs_angular_unit(d->getPROJContext(), d->m_pj_crs,
    2599             :                                                pszUnitsName, dfInRadians,
    2600             :                                                nullptr, nullptr));
    2601        1033 :     d->undoDemoteFromBoundCRS();
    2602             : 
    2603        1033 :     d->m_osAngularUnits = pszUnitsName;
    2604        1033 :     d->m_dfAngularUnitToRadian = dfInRadians;
    2605             : 
    2606        1033 :     return OGRERR_NONE;
    2607             : }
    2608             : 
    2609             : /************************************************************************/
    2610             : /*                         OSRSetAngularUnits()                         */
    2611             : /************************************************************************/
    2612             : 
    2613             : /**
    2614             :  * \brief Set the angular units for the geographic coordinate system.
    2615             :  *
    2616             :  * This function is the same as OGRSpatialReference::SetAngularUnits()
    2617             :  */
    2618          41 : OGRErr OSRSetAngularUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2619             :                           double dfInRadians)
    2620             : 
    2621             : {
    2622          41 :     VALIDATE_POINTER1(hSRS, "OSRSetAngularUnits", OGRERR_FAILURE);
    2623             : 
    2624          41 :     return ToPointer(hSRS)->SetAngularUnits(pszUnits, dfInRadians);
    2625             : }
    2626             : 
    2627             : /************************************************************************/
    2628             : /*                          GetAngularUnits()                           */
    2629             : /************************************************************************/
    2630             : 
    2631             : /**
    2632             :  * \brief Fetch angular geographic coordinate system units.
    2633             :  *
    2634             :  * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
    2635             :  * will be assumed.  This method only checks directly under the GEOGCS node
    2636             :  * for units.
    2637             :  *
    2638             :  * This method does the same thing as the C function OSRGetAngularUnits().
    2639             :  *
    2640             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    2641             :  * The returned value remains internal to the OGRSpatialReference and should
    2642             :  * not be freed, or modified.  It may be invalidated on the next
    2643             :  * OGRSpatialReference call.
    2644             :  *
    2645             :  * @return the value to multiply by angular distances to transform them to
    2646             :  * radians.
    2647             :  * @since GDAL 2.3.0
    2648             :  */
    2649             : 
    2650        6600 : double OGRSpatialReference::GetAngularUnits(const char **ppszName) const
    2651             : 
    2652             : {
    2653       13200 :     TAKE_OPTIONAL_LOCK();
    2654             : 
    2655        6600 :     d->refreshProjObj();
    2656             : 
    2657        6600 :     if (!d->m_osAngularUnits.empty())
    2658             :     {
    2659        1830 :         if (ppszName != nullptr)
    2660         143 :             *ppszName = d->m_osAngularUnits.c_str();
    2661        1830 :         return d->m_dfAngularUnitToRadian;
    2662             :     }
    2663             : 
    2664             :     do
    2665             :     {
    2666        4770 :         if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    2667             :         {
    2668         113 :             break;
    2669             :         }
    2670             : 
    2671             :         auto geodCRS =
    2672        4659 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2673        4659 :         if (!geodCRS)
    2674             :         {
    2675           0 :             break;
    2676             :         }
    2677             :         auto coordSys =
    2678        4659 :             proj_crs_get_coordinate_system(d->getPROJContext(), geodCRS);
    2679        4659 :         proj_destroy(geodCRS);
    2680        4659 :         if (!coordSys)
    2681             :         {
    2682           0 :             break;
    2683             :         }
    2684        4659 :         if (proj_cs_get_type(d->getPROJContext(), coordSys) !=
    2685             :             PJ_CS_TYPE_ELLIPSOIDAL)
    2686             :         {
    2687           2 :             proj_destroy(coordSys);
    2688           2 :             break;
    2689             :         }
    2690             : 
    2691        4657 :         double dfConvFactor = 0.0;
    2692        4657 :         const char *pszUnitName = nullptr;
    2693        4657 :         if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
    2694             :                                    nullptr, nullptr, &dfConvFactor,
    2695             :                                    &pszUnitName, nullptr, nullptr))
    2696             :         {
    2697           0 :             proj_destroy(coordSys);
    2698           0 :             break;
    2699             :         }
    2700             : 
    2701        4657 :         d->m_osAngularUnits = pszUnitName;
    2702             : 
    2703        4657 :         proj_destroy(coordSys);
    2704        4657 :         d->m_dfAngularUnitToRadian = dfConvFactor;
    2705             :     } while (false);
    2706             : 
    2707        4770 :     if (d->m_osAngularUnits.empty())
    2708             :     {
    2709         113 :         d->m_osAngularUnits = "degree";
    2710         113 :         d->m_dfAngularUnitToRadian = CPLAtof(SRS_UA_DEGREE_CONV);
    2711             :     }
    2712             : 
    2713        4770 :     if (ppszName != nullptr)
    2714        2899 :         *ppszName = d->m_osAngularUnits.c_str();
    2715        4770 :     return d->m_dfAngularUnitToRadian;
    2716             : }
    2717             : 
    2718             : /**
    2719             :  * \brief Fetch angular geographic coordinate system units.
    2720             :  *
    2721             :  * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
    2722             :  * will be assumed.  This method only checks directly under the GEOGCS node
    2723             :  * for units.
    2724             :  *
    2725             :  * This method does the same thing as the C function OSRGetAngularUnits().
    2726             :  *
    2727             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    2728             :  * The returned value remains internal to the OGRSpatialReference and should
    2729             :  * not be freed, or modified.  It may be invalidated on the next
    2730             :  * OGRSpatialReference call.
    2731             :  *
    2732             :  * @return the value to multiply by angular distances to transform them to
    2733             :  * radians.
    2734             :  * @deprecated GDAL 2.3.0. Use GetAngularUnits(const char**) const.
    2735             :  */
    2736             : 
    2737           0 : double OGRSpatialReference::GetAngularUnits(char **ppszName) const
    2738             : 
    2739             : {
    2740           0 :     return GetAngularUnits(const_cast<const char **>(ppszName));
    2741             : }
    2742             : 
    2743             : /************************************************************************/
    2744             : /*                         OSRGetAngularUnits()                         */
    2745             : /************************************************************************/
    2746             : 
    2747             : /**
    2748             :  * \brief Fetch angular geographic coordinate system units.
    2749             :  *
    2750             :  * This function is the same as OGRSpatialReference::GetAngularUnits()
    2751             :  */
    2752           1 : double OSRGetAngularUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    2753             : 
    2754             : {
    2755           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAngularUnits", 0);
    2756             : 
    2757           1 :     return ToPointer(hSRS)->GetAngularUnits(
    2758           1 :         const_cast<const char **>(ppszName));
    2759             : }
    2760             : 
    2761             : /************************************************************************/
    2762             : /*                 SetLinearUnitsAndUpdateParameters()                  */
    2763             : /************************************************************************/
    2764             : 
    2765             : /**
    2766             :  * \brief Set the linear units for the projection.
    2767             :  *
    2768             :  * This method creates a UNIT subnode with the specified values as a
    2769             :  * child of the PROJCS or LOCAL_CS node.   It works the same as the
    2770             :  * SetLinearUnits() method, but it also updates all existing linear
    2771             :  * projection parameter values from the old units to the new units.
    2772             :  *
    2773             :  * @param pszName the units name to be used.  Some preferred units
    2774             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2775             :  * and SRS_UL_US_FOOT.
    2776             :  *
    2777             :  * @param dfInMeters the value to multiple by a length in the indicated
    2778             :  * units to transform to meters.  Some standard conversion factors can
    2779             :  * be found in ogr_srs_api.h.
    2780             :  *
    2781             :  * @param pszUnitAuthority Unit authority name. Or nullptr
    2782             :  *
    2783             :  * @param pszUnitCode Unit code. Or nullptr
    2784             :  *
    2785             :  * @return OGRERR_NONE on success.
    2786             :  */
    2787             : 
    2788          39 : OGRErr OGRSpatialReference::SetLinearUnitsAndUpdateParameters(
    2789             :     const char *pszName, double dfInMeters, const char *pszUnitAuthority,
    2790             :     const char *pszUnitCode)
    2791             : 
    2792             : {
    2793          78 :     TAKE_OPTIONAL_LOCK();
    2794             : 
    2795          39 :     if (dfInMeters <= 0.0)
    2796           0 :         return OGRERR_FAILURE;
    2797             : 
    2798          39 :     d->refreshProjObj();
    2799          39 :     if (!d->m_pj_crs)
    2800           0 :         return OGRERR_FAILURE;
    2801             : 
    2802          39 :     d->demoteFromBoundCRS();
    2803          39 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    2804             :     {
    2805          78 :         d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    2806          39 :             d->getPROJContext(), d->m_pj_crs, pszName, dfInMeters,
    2807             :             pszUnitAuthority, pszUnitCode, true));
    2808             :     }
    2809          39 :     d->setPjCRS(proj_crs_alter_cs_linear_unit(d->getPROJContext(), d->m_pj_crs,
    2810             :                                               pszName, dfInMeters,
    2811             :                                               pszUnitAuthority, pszUnitCode));
    2812          39 :     d->undoDemoteFromBoundCRS();
    2813             : 
    2814          39 :     d->m_osLinearUnits = pszName;
    2815          39 :     d->dfToMeter = dfInMeters;
    2816             : 
    2817          39 :     return OGRERR_NONE;
    2818             : }
    2819             : 
    2820             : /************************************************************************/
    2821             : /*                OSRSetLinearUnitsAndUpdateParameters()                */
    2822             : /************************************************************************/
    2823             : 
    2824             : /**
    2825             :  * \brief Set the linear units for the projection.
    2826             :  *
    2827             :  * This function is the same as
    2828             :  *   OGRSpatialReference::SetLinearUnitsAndUpdateParameters()
    2829             :  */
    2830           1 : OGRErr OSRSetLinearUnitsAndUpdateParameters(OGRSpatialReferenceH hSRS,
    2831             :                                             const char *pszUnits,
    2832             :                                             double dfInMeters)
    2833             : 
    2834             : {
    2835           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnitsAndUpdateParameters",
    2836             :                       OGRERR_FAILURE);
    2837             : 
    2838           1 :     return ToPointer(hSRS)->SetLinearUnitsAndUpdateParameters(pszUnits,
    2839           1 :                                                               dfInMeters);
    2840             : }
    2841             : 
    2842             : /************************************************************************/
    2843             : /*                           SetLinearUnits()                           */
    2844             : /************************************************************************/
    2845             : 
    2846             : /**
    2847             :  * \brief Set the linear units for the projection.
    2848             :  *
    2849             :  * This method creates a UNIT subnode with the specified values as a
    2850             :  * child of the PROJCS, GEOCCS, GEOGCS or LOCAL_CS node. When called on a
    2851             :  * Geographic 3D CRS the vertical axis units will be set.
    2852             :  *
    2853             :  * This method does the same as the C function OSRSetLinearUnits().
    2854             :  *
    2855             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2856             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2857             :  * and SRS_UL_US_FOOT.
    2858             :  *
    2859             :  * @param dfInMeters the value to multiple by a length in the indicated
    2860             :  * units to transform to meters.  Some standard conversion factors can
    2861             :  * be found in ogr_srs_api.h.
    2862             :  *
    2863             :  * @return OGRERR_NONE on success.
    2864             :  */
    2865             : 
    2866        6587 : OGRErr OGRSpatialReference::SetLinearUnits(const char *pszUnitsName,
    2867             :                                            double dfInMeters)
    2868             : 
    2869             : {
    2870        6587 :     return SetTargetLinearUnits(nullptr, pszUnitsName, dfInMeters);
    2871             : }
    2872             : 
    2873             : /************************************************************************/
    2874             : /*                         OSRSetLinearUnits()                          */
    2875             : /************************************************************************/
    2876             : 
    2877             : /**
    2878             :  * \brief Set the linear units for the projection.
    2879             :  *
    2880             :  * This function is the same as OGRSpatialReference::SetLinearUnits()
    2881             :  */
    2882           7 : OGRErr OSRSetLinearUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2883             :                          double dfInMeters)
    2884             : 
    2885             : {
    2886           7 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnits", OGRERR_FAILURE);
    2887             : 
    2888           7 :     return ToPointer(hSRS)->SetLinearUnits(pszUnits, dfInMeters);
    2889             : }
    2890             : 
    2891             : /************************************************************************/
    2892             : /*                        SetTargetLinearUnits()                        */
    2893             : /************************************************************************/
    2894             : 
    2895             : /**
    2896             :  * \brief Set the linear units for the projection.
    2897             :  *
    2898             :  * This method creates a UNIT subnode with the specified values as a
    2899             :  * child of the target node.
    2900             :  *
    2901             :  * This method does the same as the C function OSRSetTargetLinearUnits().
    2902             :  *
    2903             :  * @param pszTargetKey the keyword to set the linear units for.
    2904             :  * i.e. "PROJCS" or "VERT_CS"
    2905             :  *
    2906             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2907             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2908             :  * and SRS_UL_US_FOOT.
    2909             :  *
    2910             :  * @param dfInMeters the value to multiple by a length in the indicated
    2911             :  * units to transform to meters.  Some standard conversion factors can
    2912             :  * be found in ogr_srs_api.h.
    2913             :  *
    2914             :  * @param pszUnitAuthority Unit authority name. Or nullptr
    2915             :  *
    2916             :  * @param pszUnitCode Unit code. Or nullptr
    2917             :  *
    2918             :  * @return OGRERR_NONE on success.
    2919             :  *
    2920             :  * @since OGR 1.9.0
    2921             :  */
    2922             : 
    2923       10255 : OGRErr OGRSpatialReference::SetTargetLinearUnits(const char *pszTargetKey,
    2924             :                                                  const char *pszUnitsName,
    2925             :                                                  double dfInMeters,
    2926             :                                                  const char *pszUnitAuthority,
    2927             :                                                  const char *pszUnitCode)
    2928             : 
    2929             : {
    2930       20510 :     TAKE_OPTIONAL_LOCK();
    2931             : 
    2932       10255 :     if (dfInMeters <= 0.0)
    2933           0 :         return OGRERR_FAILURE;
    2934             : 
    2935       10255 :     d->refreshProjObj();
    2936       10255 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    2937       10255 :     if (pszTargetKey == nullptr)
    2938             :     {
    2939       10255 :         if (!d->m_pj_crs)
    2940           0 :             return OGRERR_FAILURE;
    2941             : 
    2942       10255 :         d->demoteFromBoundCRS();
    2943       10255 :         if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    2944             :         {
    2945       14754 :             d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    2946        7377 :                 d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    2947             :                 pszUnitAuthority, pszUnitCode, false));
    2948             :         }
    2949       20510 :         d->setPjCRS(proj_crs_alter_cs_linear_unit(
    2950       10255 :             d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    2951             :             pszUnitAuthority, pszUnitCode));
    2952       10255 :         d->undoDemoteFromBoundCRS();
    2953             : 
    2954       10255 :         d->m_osLinearUnits = pszUnitsName;
    2955       10255 :         d->dfToMeter = dfInMeters;
    2956             : 
    2957       10255 :         return OGRERR_NONE;
    2958             :     }
    2959             : 
    2960           0 :     OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
    2961             : 
    2962           0 :     if (poCS == nullptr)
    2963           0 :         return OGRERR_FAILURE;
    2964             : 
    2965           0 :     char szValue[128] = {'\0'};
    2966           0 :     if (dfInMeters < std::numeric_limits<int>::max() &&
    2967           0 :         dfInMeters > std::numeric_limits<int>::min() &&
    2968           0 :         dfInMeters == static_cast<int>(dfInMeters))
    2969           0 :         snprintf(szValue, sizeof(szValue), "%d", static_cast<int>(dfInMeters));
    2970             :     else
    2971           0 :         OGRsnPrintDouble(szValue, sizeof(szValue), dfInMeters);
    2972             : 
    2973           0 :     OGR_SRSNode *poUnits = nullptr;
    2974           0 :     if (poCS->FindChild("UNIT") >= 0)
    2975             :     {
    2976           0 :         poUnits = poCS->GetChild(poCS->FindChild("UNIT"));
    2977           0 :         if (poUnits->GetChildCount() < 2)
    2978           0 :             return OGRERR_FAILURE;
    2979           0 :         poUnits->GetChild(0)->SetValue(pszUnitsName);
    2980           0 :         poUnits->GetChild(1)->SetValue(szValue);
    2981           0 :         if (poUnits->FindChild("AUTHORITY") != -1)
    2982           0 :             poUnits->DestroyChild(poUnits->FindChild("AUTHORITY"));
    2983             :     }
    2984             :     else
    2985             :     {
    2986           0 :         poUnits = new OGR_SRSNode("UNIT");
    2987           0 :         poUnits->AddChild(new OGR_SRSNode(pszUnitsName));
    2988           0 :         poUnits->AddChild(new OGR_SRSNode(szValue));
    2989             : 
    2990           0 :         poCS->AddChild(poUnits);
    2991             :     }
    2992             : 
    2993           0 :     return OGRERR_NONE;
    2994             : }
    2995             : 
    2996             : /************************************************************************/
    2997             : /*                         OSRSetLinearUnits()                          */
    2998             : /************************************************************************/
    2999             : 
    3000             : /**
    3001             :  * \brief Set the linear units for the target node.
    3002             :  *
    3003             :  * This function is the same as OGRSpatialReference::SetTargetLinearUnits()
    3004             :  *
    3005             :  * @since OGR 1.9.0
    3006             :  */
    3007           1 : OGRErr OSRSetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3008             :                                const char *pszTargetKey, const char *pszUnits,
    3009             :                                double dfInMeters)
    3010             : 
    3011             : {
    3012           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTargetLinearUnits", OGRERR_FAILURE);
    3013             : 
    3014           1 :     return ToPointer(hSRS)->SetTargetLinearUnits(pszTargetKey, pszUnits,
    3015           1 :                                                  dfInMeters);
    3016             : }
    3017             : 
    3018             : /************************************************************************/
    3019             : /*                           GetLinearUnits()                           */
    3020             : /************************************************************************/
    3021             : 
    3022             : /**
    3023             :  * \brief Fetch linear projection units.
    3024             :  *
    3025             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3026             :  * This method only checks directly under the PROJCS, GEOCCS, GEOGCS or
    3027             :  * LOCAL_CS node for units. When called on a Geographic 3D CRS the vertical
    3028             :  * axis units will be returned.
    3029             :  *
    3030             :  * This method does the same thing as the C function OSRGetLinearUnits()
    3031             :  *
    3032             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3033             :  * The returned value remains internal to the OGRSpatialReference and should
    3034             :  * not be freed, or modified.  It may be invalidated on the next
    3035             :  * OGRSpatialReference call.
    3036             :  *
    3037             :  * @return the value to multiply by linear distances to transform them to
    3038             :  * meters.
    3039             :  * @deprecated GDAL 2.3.0. Use GetLinearUnits(const char**) const.
    3040             :  */
    3041             : 
    3042           0 : double OGRSpatialReference::GetLinearUnits(char **ppszName) const
    3043             : 
    3044             : {
    3045           0 :     return GetTargetLinearUnits(nullptr, const_cast<const char **>(ppszName));
    3046             : }
    3047             : 
    3048             : /**
    3049             :  * \brief Fetch linear projection units.
    3050             :  *
    3051             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3052             :  * This method only checks directly under the PROJCS, GEOCCS or LOCAL_CS node
    3053             :  * for units.
    3054             :  *
    3055             :  * This method does the same thing as the C function OSRGetLinearUnits()
    3056             :  *
    3057             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3058             :  * The returned value remains internal to the OGRSpatialReference and should
    3059             :  * not be freed, or modified.  It may be invalidated on the next
    3060             :  * OGRSpatialReference call.
    3061             :  *
    3062             :  * @return the value to multiply by linear distances to transform them to
    3063             :  * meters.
    3064             :  * @since GDAL 2.3.0
    3065             :  */
    3066             : 
    3067       15393 : double OGRSpatialReference::GetLinearUnits(const char **ppszName) const
    3068             : 
    3069             : {
    3070       15393 :     return GetTargetLinearUnits(nullptr, ppszName);
    3071             : }
    3072             : 
    3073             : /************************************************************************/
    3074             : /*                         OSRGetLinearUnits()                          */
    3075             : /************************************************************************/
    3076             : 
    3077             : /**
    3078             :  * \brief Fetch linear projection units.
    3079             :  *
    3080             :  * This function is the same as OGRSpatialReference::GetLinearUnits()
    3081             :  */
    3082         227 : double OSRGetLinearUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    3083             : 
    3084             : {
    3085         227 :     VALIDATE_POINTER1(hSRS, "OSRGetLinearUnits", 0);
    3086             : 
    3087         227 :     return ToPointer(hSRS)->GetLinearUnits(const_cast<const char **>(ppszName));
    3088             : }
    3089             : 
    3090             : /************************************************************************/
    3091             : /*                        GetTargetLinearUnits()                        */
    3092             : /************************************************************************/
    3093             : 
    3094             : /**
    3095             :  * \brief Fetch linear units for target.
    3096             :  *
    3097             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3098             :  *
    3099             :  * This method does the same thing as the C function OSRGetTargetLinearUnits()
    3100             :  *
    3101             :  * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
    3102             :  * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
    3103             :  * GEOCCS, GEOGCS and VERT_CS are looked up)
    3104             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3105             :  * The returned value remains internal to the OGRSpatialReference and should not
    3106             :  * be freed, or modified.  It may be invalidated on the next
    3107             :  * OGRSpatialReference call. ppszName can be set to NULL.
    3108             :  *
    3109             :  * @return the value to multiply by linear distances to transform them to
    3110             :  * meters.
    3111             :  *
    3112             :  * @since OGR 1.9.0
    3113             :  * @deprecated GDAL 2.3.0. Use GetTargetLinearUnits(const char*, const char**)
    3114             :  * const.
    3115             :  */
    3116             : 
    3117       15540 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3118             :                                                  const char **ppszName) const
    3119             : 
    3120             : {
    3121       31080 :     TAKE_OPTIONAL_LOCK();
    3122             : 
    3123       15540 :     d->refreshProjObj();
    3124             : 
    3125       15540 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    3126       15540 :     if (pszTargetKey == nullptr)
    3127             :     {
    3128             :         // Use cached result if available
    3129       15452 :         if (!d->m_osLinearUnits.empty())
    3130             :         {
    3131        7359 :             if (ppszName)
    3132        6645 :                 *ppszName = d->m_osLinearUnits.c_str();
    3133        7359 :             return d->dfToMeter;
    3134             :         }
    3135             : 
    3136             :         while (true)
    3137             :         {
    3138        8093 :             if (d->m_pj_crs == nullptr)
    3139             :             {
    3140         245 :                 break;
    3141             :             }
    3142             : 
    3143        7848 :             d->demoteFromBoundCRS();
    3144        7848 :             PJ *coordSys = nullptr;
    3145        7848 :             if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    3146             :             {
    3147          30 :                 for (int iComponent = 0; iComponent < 2; iComponent++)
    3148             :                 {
    3149          30 :                     auto subCRS = proj_crs_get_sub_crs(d->getPROJContext(),
    3150          30 :                                                        d->m_pj_crs, iComponent);
    3151          30 :                     if (subCRS && proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
    3152             :                     {
    3153             :                         auto temp =
    3154           0 :                             proj_get_source_crs(d->getPROJContext(), subCRS);
    3155           0 :                         proj_destroy(subCRS);
    3156           0 :                         subCRS = temp;
    3157             :                     }
    3158          60 :                     if (subCRS &&
    3159          30 :                         (proj_get_type(subCRS) == PJ_TYPE_PROJECTED_CRS ||
    3160          16 :                          proj_get_type(subCRS) == PJ_TYPE_ENGINEERING_CRS ||
    3161          12 :                          proj_get_type(subCRS) == PJ_TYPE_VERTICAL_CRS))
    3162             :                     {
    3163          24 :                         coordSys = proj_crs_get_coordinate_system(
    3164             :                             d->getPROJContext(), subCRS);
    3165          24 :                         proj_destroy(subCRS);
    3166          24 :                         break;
    3167             :                     }
    3168           6 :                     else if (subCRS)
    3169             :                     {
    3170           6 :                         proj_destroy(subCRS);
    3171             :                     }
    3172             :                 }
    3173          24 :                 if (coordSys == nullptr)
    3174             :                 {
    3175           0 :                     d->undoDemoteFromBoundCRS();
    3176           0 :                     break;
    3177             :                 }
    3178             :             }
    3179             :             else
    3180             :             {
    3181        7824 :                 coordSys = proj_crs_get_coordinate_system(d->getPROJContext(),
    3182        7824 :                                                           d->m_pj_crs);
    3183             :             }
    3184             : 
    3185        7848 :             d->undoDemoteFromBoundCRS();
    3186        7848 :             if (!coordSys)
    3187             :             {
    3188           0 :                 break;
    3189             :             }
    3190        7848 :             auto csType = proj_cs_get_type(d->getPROJContext(), coordSys);
    3191             : 
    3192        7848 :             if (csType != PJ_CS_TYPE_CARTESIAN &&
    3193        2119 :                 csType != PJ_CS_TYPE_VERTICAL &&
    3194           0 :                 csType != PJ_CS_TYPE_ELLIPSOIDAL &&
    3195             :                 csType != PJ_CS_TYPE_SPHERICAL)
    3196             :             {
    3197           0 :                 proj_destroy(coordSys);
    3198           0 :                 break;
    3199             :             }
    3200             : 
    3201        7848 :             int axis = 0;
    3202             : 
    3203        7848 :             if (csType == PJ_CS_TYPE_ELLIPSOIDAL ||
    3204             :                 csType == PJ_CS_TYPE_SPHERICAL)
    3205             :             {
    3206             :                 const int axisCount =
    3207        2119 :                     proj_cs_get_axis_count(d->getPROJContext(), coordSys);
    3208             : 
    3209        2119 :                 if (axisCount == 3)
    3210             :                 {
    3211           4 :                     axis = 2;
    3212             :                 }
    3213             :                 else
    3214             :                 {
    3215        2115 :                     proj_destroy(coordSys);
    3216        2115 :                     break;
    3217             :                 }
    3218             :             }
    3219             : 
    3220        5733 :             double dfConvFactor = 0.0;
    3221        5733 :             const char *pszUnitName = nullptr;
    3222        5733 :             if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, axis,
    3223             :                                        nullptr, nullptr, nullptr, &dfConvFactor,
    3224             :                                        &pszUnitName, nullptr, nullptr))
    3225             :             {
    3226           0 :                 proj_destroy(coordSys);
    3227           0 :                 break;
    3228             :             }
    3229             : 
    3230        5733 :             d->m_osLinearUnits = pszUnitName;
    3231        5733 :             d->dfToMeter = dfConvFactor;
    3232        5733 :             if (ppszName)
    3233        1050 :                 *ppszName = d->m_osLinearUnits.c_str();
    3234             : 
    3235        5733 :             proj_destroy(coordSys);
    3236        5733 :             return dfConvFactor;
    3237             :         }
    3238             : 
    3239        2360 :         d->m_osLinearUnits = "unknown";
    3240        2360 :         d->dfToMeter = 1.0;
    3241             : 
    3242        2360 :         if (ppszName != nullptr)
    3243        2178 :             *ppszName = d->m_osLinearUnits.c_str();
    3244        2360 :         return 1.0;
    3245             :     }
    3246             : 
    3247          88 :     const OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
    3248             : 
    3249          88 :     if (ppszName != nullptr)
    3250          37 :         *ppszName = "unknown";
    3251             : 
    3252          88 :     if (poCS == nullptr)
    3253          50 :         return 1.0;
    3254             : 
    3255         114 :     for (int iChild = 0; iChild < poCS->GetChildCount(); iChild++)
    3256             :     {
    3257         114 :         const OGR_SRSNode *poChild = poCS->GetChild(iChild);
    3258             : 
    3259         114 :         if (EQUAL(poChild->GetValue(), "UNIT") && poChild->GetChildCount() >= 2)
    3260             :         {
    3261          38 :             if (ppszName != nullptr)
    3262          37 :                 *ppszName = poChild->GetChild(0)->GetValue();
    3263             : 
    3264          38 :             return CPLAtof(poChild->GetChild(1)->GetValue());
    3265             :         }
    3266             :     }
    3267             : 
    3268           0 :     return 1.0;
    3269             : }
    3270             : 
    3271             : /**
    3272             :  * \brief Fetch linear units for target.
    3273             :  *
    3274             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3275             :  *
    3276             :  * This method does the same thing as the C function OSRGetTargetLinearUnits()
    3277             :  *
    3278             :  * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
    3279             :  * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
    3280             :  * GEOCCS and VERT_CS are looked up)
    3281             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3282             :  * The returned value remains internal to the OGRSpatialReference and should not
    3283             :  * be freed, or modified.  It may be invalidated on the next
    3284             :  * OGRSpatialReference call. ppszName can be set to NULL.
    3285             :  *
    3286             :  * @return the value to multiply by linear distances to transform them to
    3287             :  * meters.
    3288             :  *
    3289             :  * @since GDAL 2.3.0
    3290             :  */
    3291             : 
    3292           0 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3293             :                                                  char **ppszName) const
    3294             : 
    3295             : {
    3296           0 :     return GetTargetLinearUnits(pszTargetKey,
    3297           0 :                                 const_cast<const char **>(ppszName));
    3298             : }
    3299             : 
    3300             : /************************************************************************/
    3301             : /*                      OSRGetTargetLinearUnits()                       */
    3302             : /************************************************************************/
    3303             : 
    3304             : /**
    3305             :  * \brief Fetch linear projection units.
    3306             :  *
    3307             :  * This function is the same as OGRSpatialReference::GetTargetLinearUnits()
    3308             :  *
    3309             :  * @since OGR 1.9.0
    3310             :  */
    3311           4 : double OSRGetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3312             :                                const char *pszTargetKey, char **ppszName)
    3313             : 
    3314             : {
    3315           4 :     VALIDATE_POINTER1(hSRS, "OSRGetTargetLinearUnits", 0);
    3316             : 
    3317           4 :     return ToPointer(hSRS)->GetTargetLinearUnits(
    3318           4 :         pszTargetKey, const_cast<const char **>(ppszName));
    3319             : }
    3320             : 
    3321             : /************************************************************************/
    3322             : /*                          GetPrimeMeridian()                          */
    3323             : /************************************************************************/
    3324             : 
    3325             : /**
    3326             :  * \brief Fetch prime meridian info.
    3327             :  *
    3328             :  * Returns the offset of the prime meridian from greenwich in degrees,
    3329             :  * and the prime meridian name (if requested).   If no PRIMEM value exists
    3330             :  * in the coordinate system definition a value of "Greenwich" and an
    3331             :  * offset of 0.0 is assumed.
    3332             :  *
    3333             :  * If the prime meridian name is returned, the pointer is to an internal
    3334             :  * copy of the name. It should not be freed, altered or depended on after
    3335             :  * the next OGR call.
    3336             :  *
    3337             :  * This method is the same as the C function OSRGetPrimeMeridian().
    3338             :  *
    3339             :  * @param ppszName return location for prime meridian name.  If NULL, name
    3340             :  * is not returned.
    3341             :  *
    3342             :  * @return the offset to the GEOGCS prime meridian from greenwich in decimal
    3343             :  * degrees.
    3344             :  * @deprecated GDAL 2.3.0. Use GetPrimeMeridian(const char**) const.
    3345             :  */
    3346             : 
    3347        1365 : double OGRSpatialReference::GetPrimeMeridian(const char **ppszName) const
    3348             : 
    3349             : {
    3350        2730 :     TAKE_OPTIONAL_LOCK();
    3351             : 
    3352        1365 :     d->refreshProjObj();
    3353             : 
    3354        1365 :     if (!d->m_osPrimeMeridianName.empty())
    3355             :     {
    3356          61 :         if (ppszName != nullptr)
    3357           1 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3358          61 :         return d->dfFromGreenwich;
    3359             :     }
    3360             : 
    3361             :     while (true)
    3362             :     {
    3363        1304 :         if (!d->m_pj_crs)
    3364           0 :             break;
    3365             : 
    3366        1304 :         auto pm = proj_get_prime_meridian(d->getPROJContext(), d->m_pj_crs);
    3367        1304 :         if (!pm)
    3368           0 :             break;
    3369             : 
    3370        1304 :         d->m_osPrimeMeridianName = proj_get_name(pm);
    3371        1304 :         if (ppszName)
    3372          30 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3373        1304 :         double dfLongitude = 0.0;
    3374        1304 :         double dfConvFactor = 0.0;
    3375        1304 :         proj_prime_meridian_get_parameters(
    3376             :             d->getPROJContext(), pm, &dfLongitude, &dfConvFactor, nullptr);
    3377        1304 :         proj_destroy(pm);
    3378        2608 :         d->dfFromGreenwich =
    3379        1304 :             dfLongitude * dfConvFactor / CPLAtof(SRS_UA_DEGREE_CONV);
    3380        1304 :         return d->dfFromGreenwich;
    3381             :     }
    3382             : 
    3383           0 :     d->m_osPrimeMeridianName = SRS_PM_GREENWICH;
    3384           0 :     d->dfFromGreenwich = 0.0;
    3385           0 :     if (ppszName != nullptr)
    3386           0 :         *ppszName = d->m_osPrimeMeridianName.c_str();
    3387           0 :     return d->dfFromGreenwich;
    3388             : }
    3389             : 
    3390             : /**
    3391             :  * \brief Fetch prime meridian info.
    3392             :  *
    3393             :  * Returns the offset of the prime meridian from greenwich in degrees,
    3394             :  * and the prime meridian name (if requested).   If no PRIMEM value exists
    3395             :  * in the coordinate system definition a value of "Greenwich" and an
    3396             :  * offset of 0.0 is assumed.
    3397             :  *
    3398             :  * If the prime meridian name is returned, the pointer is to an internal
    3399             :  * copy of the name. It should not be freed, altered or depended on after
    3400             :  * the next OGR call.
    3401             :  *
    3402             :  * This method is the same as the C function OSRGetPrimeMeridian().
    3403             :  *
    3404             :  * @param ppszName return location for prime meridian name.  If NULL, name
    3405             :  * is not returned.
    3406             :  *
    3407             :  * @return the offset to the GEOGCS prime meridian from greenwich in decimal
    3408             :  * degrees.
    3409             :  * @since GDAL 2.3.0
    3410             :  */
    3411             : 
    3412           0 : double OGRSpatialReference::GetPrimeMeridian(char **ppszName) const
    3413             : 
    3414             : {
    3415           0 :     return GetPrimeMeridian(const_cast<const char **>(ppszName));
    3416             : }
    3417             : 
    3418             : /************************************************************************/
    3419             : /*                        OSRGetPrimeMeridian()                         */
    3420             : /************************************************************************/
    3421             : 
    3422             : /**
    3423             :  * \brief Fetch prime meridian info.
    3424             :  *
    3425             :  * This function is the same as OGRSpatialReference::GetPrimeMeridian()
    3426             :  */
    3427           0 : double OSRGetPrimeMeridian(OGRSpatialReferenceH hSRS, char **ppszName)
    3428             : 
    3429             : {
    3430           0 :     VALIDATE_POINTER1(hSRS, "OSRGetPrimeMeridian", 0);
    3431             : 
    3432           0 :     return ToPointer(hSRS)->GetPrimeMeridian(
    3433           0 :         const_cast<const char **>(ppszName));
    3434             : }
    3435             : 
    3436             : /************************************************************************/
    3437             : /*                             SetGeogCS()                              */
    3438             : /************************************************************************/
    3439             : 
    3440             : /**
    3441             :  * \brief Set geographic coordinate system.
    3442             :  *
    3443             :  * This method is used to set the datum, ellipsoid, prime meridian and
    3444             :  * angular units for a geographic coordinate system.  It can be used on its
    3445             :  * own to establish a geographic spatial reference, or applied to a
    3446             :  * projected coordinate system to establish the underlying geographic
    3447             :  * coordinate system.
    3448             :  *
    3449             :  * This method does the same as the C function OSRSetGeogCS().
    3450             :  *
    3451             :  * @param pszGeogName user visible name for the geographic coordinate system
    3452             :  * (not to serve as a key).
    3453             :  *
    3454             :  * @param pszDatumName key name for this datum.  The OpenGIS specification
    3455             :  * lists some known values, and otherwise EPSG datum names with a standard
    3456             :  * transformation are considered legal keys.
    3457             :  *
    3458             :  * @param pszSpheroidName user visible spheroid name (not to serve as a key)
    3459             :  *
    3460             :  * @param dfSemiMajor the semi major axis of the spheroid.
    3461             :  *
    3462             :  * @param dfInvFlattening the inverse flattening for the spheroid.
    3463             :  * This can be computed from the semi minor axis as
    3464             :  * 1/f = 1.0 / (1.0 - semiminor/semimajor).
    3465             :  *
    3466             :  * @param pszPMName the name of the prime meridian (not to serve as a key)
    3467             :  * If this is NULL a default value of "Greenwich" will be used.
    3468             :  *
    3469             :  * @param dfPMOffset the longitude of Greenwich relative to this prime
    3470             :  * meridian. Always in Degrees
    3471             :  *
    3472             :  * @param pszAngularUnits the angular units name (see ogr_srs_api.h for some
    3473             :  * standard names).  If NULL a value of "degrees" will be assumed.
    3474             :  *
    3475             :  * @param dfConvertToRadians value to multiply angular units by to transform
    3476             :  * them to radians.  A value of SRS_UA_DEGREE_CONV will be used if
    3477             :  * pszAngularUnits is NULL.
    3478             :  *
    3479             :  * @return OGRERR_NONE on success.
    3480             :  */
    3481             : 
    3482        8345 : OGRErr OGRSpatialReference::SetGeogCS(
    3483             :     const char *pszGeogName, const char *pszDatumName,
    3484             :     const char *pszSpheroidName, double dfSemiMajor, double dfInvFlattening,
    3485             :     const char *pszPMName, double dfPMOffset, const char *pszAngularUnits,
    3486             :     double dfConvertToRadians)
    3487             : 
    3488             : {
    3489       16690 :     TAKE_OPTIONAL_LOCK();
    3490             : 
    3491        8345 :     d->bNormInfoSet = FALSE;
    3492        8345 :     d->m_osAngularUnits.clear();
    3493        8345 :     d->m_dfAngularUnitToRadian = 0.0;
    3494        8345 :     d->m_osPrimeMeridianName.clear();
    3495        8345 :     d->dfFromGreenwich = 0.0;
    3496             : 
    3497             :     /* -------------------------------------------------------------------- */
    3498             :     /*      For a geocentric coordinate system we want to set the datum     */
    3499             :     /*      and ellipsoid based on the GEOGCS.  Create the GEOGCS in a      */
    3500             :     /*      temporary srs and use the copy method which has special         */
    3501             :     /*      handling for GEOCCS.                                            */
    3502             :     /* -------------------------------------------------------------------- */
    3503        8345 :     if (IsGeocentric())
    3504             :     {
    3505           4 :         OGRSpatialReference oGCS;
    3506             : 
    3507           2 :         oGCS.SetGeogCS(pszGeogName, pszDatumName, pszSpheroidName, dfSemiMajor,
    3508             :                        dfInvFlattening, pszPMName, dfPMOffset, pszAngularUnits,
    3509             :                        dfConvertToRadians);
    3510           2 :         return CopyGeogCSFrom(&oGCS);
    3511             :     }
    3512             : 
    3513        8343 :     auto cs = proj_create_ellipsoidal_2D_cs(
    3514             :         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE, pszAngularUnits,
    3515             :         dfConvertToRadians);
    3516             :     // Prime meridian expressed in Degree
    3517        8343 :     auto obj = proj_create_geographic_crs(
    3518             :         d->getPROJContext(), pszGeogName, pszDatumName, pszSpheroidName,
    3519             :         dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset, nullptr, 0.0, cs);
    3520        8343 :     proj_destroy(cs);
    3521             : 
    3522       12535 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    3523        4192 :         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    3524             :     {
    3525        4151 :         d->setPjCRS(obj);
    3526             :     }
    3527        4192 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3528             :     {
    3529        8384 :         d->setPjCRS(
    3530        4192 :             proj_crs_alter_geodetic_crs(d->getPROJContext(), d->m_pj_crs, obj));
    3531        4192 :         proj_destroy(obj);
    3532             :     }
    3533             :     else
    3534             :     {
    3535           0 :         proj_destroy(obj);
    3536             :     }
    3537             : 
    3538        8343 :     return OGRERR_NONE;
    3539             : }
    3540             : 
    3541             : /************************************************************************/
    3542             : /*                            OSRSetGeogCS()                            */
    3543             : /************************************************************************/
    3544             : 
    3545             : /**
    3546             :  * \brief Set geographic coordinate system.
    3547             :  *
    3548             :  * This function is the same as OGRSpatialReference::SetGeogCS()
    3549             :  */
    3550          18 : OGRErr OSRSetGeogCS(OGRSpatialReferenceH hSRS, const char *pszGeogName,
    3551             :                     const char *pszDatumName, const char *pszSpheroidName,
    3552             :                     double dfSemiMajor, double dfInvFlattening,
    3553             :                     const char *pszPMName, double dfPMOffset,
    3554             :                     const char *pszAngularUnits, double dfConvertToRadians)
    3555             : 
    3556             : {
    3557          18 :     VALIDATE_POINTER1(hSRS, "OSRSetGeogCS", OGRERR_FAILURE);
    3558             : 
    3559          18 :     return ToPointer(hSRS)->SetGeogCS(pszGeogName, pszDatumName,
    3560             :                                       pszSpheroidName, dfSemiMajor,
    3561             :                                       dfInvFlattening, pszPMName, dfPMOffset,
    3562          18 :                                       pszAngularUnits, dfConvertToRadians);
    3563             : }
    3564             : 
    3565             : /************************************************************************/
    3566             : /*                         SetWellKnownGeogCS()                         */
    3567             : /************************************************************************/
    3568             : 
    3569             : /**
    3570             :  * \brief Set a GeogCS based on well known name.
    3571             :  *
    3572             :  * This may be called on an empty OGRSpatialReference to make a geographic
    3573             :  * coordinate system, or on something with an existing PROJCS node to
    3574             :  * set the underlying geographic coordinate system of a projected coordinate
    3575             :  * system.
    3576             :  *
    3577             :  * The following well known text values are currently supported,
    3578             :  * Except for "EPSG:n", the others are without dependency on EPSG data files:
    3579             :  * <ul>
    3580             :  * <li> "EPSG:n": where n is the code a Geographic coordinate reference system.
    3581             :  * <li> "WGS84": same as "EPSG:4326" (axis order lat/long).
    3582             :  * <li> "WGS72": same as "EPSG:4322" (axis order lat/long).
    3583             :  * <li> "NAD83": same as "EPSG:4269" (axis order lat/long).
    3584             :  * <li> "NAD27": same as "EPSG:4267" (axis order lat/long).
    3585             :  * <li> "CRS84", "CRS:84": same as "WGS84" but with axis order long/lat.
    3586             :  * <li> "CRS72", "CRS:72": same as "WGS72" but with axis order long/lat.
    3587             :  * <li> "CRS27", "CRS:27": same as "NAD27" but with axis order long/lat.
    3588             :  * </ul>
    3589             :  *
    3590             :  * @param pszName name of well known geographic coordinate system.
    3591             :  * @return OGRERR_NONE on success, or OGRERR_FAILURE if the name isn't
    3592             :  * recognised, the target object is already initialized, or an EPSG value
    3593             :  * can't be successfully looked up.
    3594             :  */
    3595             : 
    3596        2371 : OGRErr OGRSpatialReference::SetWellKnownGeogCS(const char *pszName)
    3597             : 
    3598             : {
    3599        4742 :     TAKE_OPTIONAL_LOCK();
    3600             : 
    3601             :     /* -------------------------------------------------------------------- */
    3602             :     /*      Check for EPSG authority numbers.                               */
    3603             :     /* -------------------------------------------------------------------- */
    3604        2371 :     if (STARTS_WITH_CI(pszName, "EPSG:") || STARTS_WITH_CI(pszName, "EPSGA:"))
    3605             :     {
    3606          84 :         OGRSpatialReference oSRS2;
    3607          42 :         const OGRErr eErr = oSRS2.importFromEPSG(atoi(pszName + 5));
    3608          42 :         if (eErr != OGRERR_NONE)
    3609           0 :             return eErr;
    3610             : 
    3611          42 :         if (!oSRS2.IsGeographic())
    3612           0 :             return OGRERR_FAILURE;
    3613             : 
    3614          42 :         return CopyGeogCSFrom(&oSRS2);
    3615             :     }
    3616             : 
    3617             :     /* -------------------------------------------------------------------- */
    3618             :     /*      Check for simple names.                                         */
    3619             :     /* -------------------------------------------------------------------- */
    3620        2329 :     const char *pszWKT = nullptr;
    3621             : 
    3622        2329 :     if (EQUAL(pszName, "WGS84"))
    3623             :     {
    3624        2046 :         pszWKT = SRS_WKT_WGS84_LAT_LONG;
    3625             :     }
    3626         283 :     else if (EQUAL(pszName, "CRS84") || EQUAL(pszName, "CRS:84"))
    3627             :     {
    3628         104 :         pszWKT =
    3629             :             "GEOGCRS[\"WGS 84 (CRS84)\",DATUM[\"World Geodetic System 1984\","
    3630             :             "ELLIPSOID[\"WGS "
    3631             :             "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],"
    3632             :             "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3633             :             "CS[ellipsoidal,2],AXIS[\"geodetic longitude (Lon)\",east,ORDER[1],"
    3634             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3635             :             "AXIS[\"geodetic latitude (Lat)\",north,ORDER[2],"
    3636             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3637             :             "USAGE[SCOPE[\"unknown\"],AREA[\"World\"],BBOX[-90,-180,90,180]],"
    3638             :             "ID[\"OGC\",\"CRS84\"]]";
    3639             :     }
    3640         179 :     else if (EQUAL(pszName, "WGS72"))
    3641          19 :         pszWKT =
    3642             :             "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
    3643             :             "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",\"7043\"]],"
    3644             :             "AUTHORITY[\"EPSG\",\"6322\"]],"
    3645             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3646             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3647             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3648             :             "AUTHORITY[\"EPSG\",\"4322\"]]";
    3649             : 
    3650         160 :     else if (EQUAL(pszName, "NAD27"))
    3651         132 :         pszWKT =
    3652             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3653             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3654             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3655             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3656             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3657             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3658             :             "AUTHORITY[\"EPSG\",\"4267\"]]";
    3659             : 
    3660          28 :     else if (EQUAL(pszName, "CRS27") || EQUAL(pszName, "CRS:27"))
    3661           0 :         pszWKT =
    3662             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3663             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3664             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3665             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3666             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3667             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3668             : 
    3669          28 :     else if (EQUAL(pszName, "NAD83"))
    3670          24 :         pszWKT =
    3671             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3672             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3673             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3674             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3675             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3676             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3677             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],AUTHORITY["
    3678             :             "\"EPSG\",\"4269\"]]";
    3679             : 
    3680           4 :     else if (EQUAL(pszName, "CRS83") || EQUAL(pszName, "CRS:83"))
    3681           0 :         pszWKT =
    3682             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3683             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3684             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3685             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3686             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3687             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3688             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3689             : 
    3690             :     else
    3691           4 :         return OGRERR_FAILURE;
    3692             : 
    3693             :     /* -------------------------------------------------------------------- */
    3694             :     /*      Import the WKT                                                  */
    3695             :     /* -------------------------------------------------------------------- */
    3696        4650 :     OGRSpatialReference oSRS2;
    3697        2325 :     const OGRErr eErr = oSRS2.importFromWkt(pszWKT);
    3698        2325 :     if (eErr != OGRERR_NONE)
    3699           0 :         return eErr;
    3700             : 
    3701             :     /* -------------------------------------------------------------------- */
    3702             :     /*      Copy over.                                                      */
    3703             :     /* -------------------------------------------------------------------- */
    3704        2325 :     return CopyGeogCSFrom(&oSRS2);
    3705             : }
    3706             : 
    3707             : /************************************************************************/
    3708             : /*                       OSRSetWellKnownGeogCS()                        */
    3709             : /************************************************************************/
    3710             : 
    3711             : /**
    3712             :  * \brief Set a GeogCS based on well known name.
    3713             :  *
    3714             :  * This function is the same as OGRSpatialReference::SetWellKnownGeogCS()
    3715             :  */
    3716         121 : OGRErr OSRSetWellKnownGeogCS(OGRSpatialReferenceH hSRS, const char *pszName)
    3717             : 
    3718             : {
    3719         121 :     VALIDATE_POINTER1(hSRS, "OSRSetWellKnownGeogCS", OGRERR_FAILURE);
    3720             : 
    3721         121 :     return ToPointer(hSRS)->SetWellKnownGeogCS(pszName);
    3722             : }
    3723             : 
    3724             : /************************************************************************/
    3725             : /*                           CopyGeogCSFrom()                           */
    3726             : /************************************************************************/
    3727             : 
    3728             : /**
    3729             :  * \brief Copy GEOGCS from another OGRSpatialReference.
    3730             :  *
    3731             :  * The GEOGCS information is copied into this OGRSpatialReference from another.
    3732             :  * If this object has a PROJCS root already, the GEOGCS is installed within
    3733             :  * it, otherwise it is installed as the root.
    3734             :  *
    3735             :  * @param poSrcSRS the spatial reference to copy the GEOGCS information from.
    3736             :  *
    3737             :  * @return OGRERR_NONE on success or an error code.
    3738             :  */
    3739             : 
    3740        2957 : OGRErr OGRSpatialReference::CopyGeogCSFrom(const OGRSpatialReference *poSrcSRS)
    3741             : 
    3742             : {
    3743        5914 :     TAKE_OPTIONAL_LOCK();
    3744             : 
    3745        2957 :     d->bNormInfoSet = FALSE;
    3746        2957 :     d->m_osAngularUnits.clear();
    3747        2957 :     d->m_dfAngularUnitToRadian = 0.0;
    3748        2957 :     d->m_osPrimeMeridianName.clear();
    3749        2957 :     d->dfFromGreenwich = 0.0;
    3750             : 
    3751        2957 :     d->refreshProjObj();
    3752        2957 :     poSrcSRS->d->refreshProjObj();
    3753        2957 :     if (!poSrcSRS->d->m_pj_crs)
    3754             :     {
    3755           0 :         return OGRERR_FAILURE;
    3756             :     }
    3757             :     auto geodCRS =
    3758        2957 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3759        2957 :     if (!geodCRS)
    3760             :     {
    3761           0 :         return OGRERR_FAILURE;
    3762             :     }
    3763             : 
    3764             :     /* -------------------------------------------------------------------- */
    3765             :     /*      Handle geocentric coordinate systems specially.  We just        */
    3766             :     /*      want to copy the DATUM.                                         */
    3767             :     /* -------------------------------------------------------------------- */
    3768        2957 :     if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    3769             :     {
    3770           3 :         auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    3771             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    3772             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    3773             :         if (datum == nullptr)
    3774             :         {
    3775             :             datum = proj_crs_get_datum_ensemble(d->getPROJContext(), geodCRS);
    3776             :         }
    3777             : #endif
    3778           3 :         if (datum == nullptr)
    3779             :         {
    3780           0 :             proj_destroy(geodCRS);
    3781           0 :             return OGRERR_FAILURE;
    3782             :         }
    3783             : 
    3784           3 :         const char *pszUnitName = nullptr;
    3785           3 :         double unitConvFactor = GetLinearUnits(&pszUnitName);
    3786             : 
    3787           3 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    3788           3 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, pszUnitName,
    3789             :             unitConvFactor);
    3790           3 :         proj_destroy(datum);
    3791             : 
    3792           3 :         d->setPjCRS(pj_crs);
    3793             :     }
    3794             : 
    3795        2954 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3796             :     {
    3797         325 :         auto pj_crs = proj_crs_alter_geodetic_crs(d->getPROJContext(),
    3798         325 :                                                   d->m_pj_crs, geodCRS);
    3799         325 :         d->setPjCRS(pj_crs);
    3800             :     }
    3801             : 
    3802             :     else
    3803             :     {
    3804        2629 :         d->setPjCRS(proj_clone(d->getPROJContext(), geodCRS));
    3805             :     }
    3806             : 
    3807             :     // Apply TOWGS84 of source CRS
    3808        2957 :     if (poSrcSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
    3809             :     {
    3810             :         auto target =
    3811           1 :             proj_get_target_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3812           1 :         auto co = proj_crs_get_coordoperation(d->getPROJContext(),
    3813           1 :                                               poSrcSRS->d->m_pj_crs);
    3814           1 :         d->setPjCRS(proj_crs_create_bound_crs(d->getPROJContext(), d->m_pj_crs,
    3815             :                                               target, co));
    3816           1 :         proj_destroy(target);
    3817           1 :         proj_destroy(co);
    3818             :     }
    3819             : 
    3820        2957 :     proj_destroy(geodCRS);
    3821             : 
    3822        2957 :     return OGRERR_NONE;
    3823             : }
    3824             : 
    3825             : /************************************************************************/
    3826             : /*                         OSRCopyGeogCSFrom()                          */
    3827             : /************************************************************************/
    3828             : 
    3829             : /**
    3830             :  * \brief Copy GEOGCS from another OGRSpatialReference.
    3831             :  *
    3832             :  * This function is the same as OGRSpatialReference::CopyGeogCSFrom()
    3833             :  */
    3834           1 : OGRErr OSRCopyGeogCSFrom(OGRSpatialReferenceH hSRS,
    3835             :                          const OGRSpatialReferenceH hSrcSRS)
    3836             : 
    3837             : {
    3838           1 :     VALIDATE_POINTER1(hSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3839           1 :     VALIDATE_POINTER1(hSrcSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3840             : 
    3841           1 :     return ToPointer(hSRS)->CopyGeogCSFrom(ToPointer(hSrcSRS));
    3842             : }
    3843             : 
    3844             : /************************************************************************/
    3845             : /*                   SET_FROM_USER_INPUT_LIMITATIONS_get()              */
    3846             : /************************************************************************/
    3847             : 
    3848             : /** Limitations for OGRSpatialReference::SetFromUserInput().
    3849             :  *
    3850             :  * Currently ALLOW_NETWORK_ACCESS=NO and ALLOW_FILE_ACCESS=NO.
    3851             :  */
    3852             : const char *const OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS[] = {
    3853             :     "ALLOW_NETWORK_ACCESS=NO", "ALLOW_FILE_ACCESS=NO", nullptr};
    3854             : 
    3855             : /**
    3856             :  * \brief Return OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS
    3857             :  */
    3858        2433 : CSLConstList OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()
    3859             : {
    3860        2433 :     return SET_FROM_USER_INPUT_LIMITATIONS;
    3861             : }
    3862             : 
    3863             : /************************************************************************/
    3864             : /*                      RemoveIDFromMemberOfEnsembles()                 */
    3865             : /************************************************************************/
    3866             : 
    3867             : // cppcheck-suppress constParameterReference
    3868         198 : static void RemoveIDFromMemberOfEnsembles(CPLJSONObject &obj)
    3869             : {
    3870             :     // Remove "id" from members of datum ensembles for compatibility with
    3871             :     // older PROJ versions
    3872             :     // Cf https://github.com/opengeospatial/geoparquet/discussions/110
    3873             :     // and https://github.com/OSGeo/PROJ/pull/3221
    3874         198 :     if (obj.GetType() == CPLJSONObject::Type::Object)
    3875             :     {
    3876         243 :         for (auto &subObj : obj.GetChildren())
    3877             :         {
    3878         191 :             RemoveIDFromMemberOfEnsembles(subObj);
    3879             :         }
    3880             :     }
    3881         162 :     else if (obj.GetType() == CPLJSONObject::Type::Array &&
    3882         162 :              obj.GetName() == "members")
    3883             :     {
    3884          51 :         for (auto &subObj : obj.ToArray())
    3885             :         {
    3886          44 :             if (subObj.GetType() == CPLJSONObject::Type::Object)
    3887             :             {
    3888          43 :                 subObj.Delete("id");
    3889             :             }
    3890             :         }
    3891             :     }
    3892         198 : }
    3893             : 
    3894             : /************************************************************************/
    3895             : /*                          SetFromUserInput()                          */
    3896             : /************************************************************************/
    3897             : 
    3898             : /**
    3899             :  * \brief Set spatial reference from various text formats.
    3900             :  *
    3901             :  * This method will examine the provided input, and try to deduce the
    3902             :  * format, and then use it to initialize the spatial reference system.  It
    3903             :  * may take the following forms:
    3904             :  *
    3905             :  * <ol>
    3906             :  * <li> Well Known Text definition - passed on to importFromWkt().
    3907             :  * <li> "EPSG:n" - number passed on to importFromEPSG().
    3908             :  * <li> "EPSGA:n" - number passed on to importFromEPSGA().
    3909             :  * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
    3910             :  * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
    3911             :  * <li> PROJ.4 definitions - passed on to importFromProj4().
    3912             :  * <li> filename - file read for WKT, XML or PROJ.4 definition.
    3913             :  * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
    3914             :  * WGS84 or WGS72.
    3915             :  * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
    3916             :  * <li> PROJJSON (PROJ &gt;= 6.2)
    3917             :  * </ol>
    3918             :  *
    3919             :  * It is expected that this method will be extended in the future to support
    3920             :  * XML and perhaps a simplified "minilanguage" for indicating common UTM and
    3921             :  * State Plane definitions.
    3922             :  *
    3923             :  * This method is intended to be flexible, but by its nature it is
    3924             :  * imprecise as it must guess information about the format intended.  When
    3925             :  * possible applications should call the specific method appropriate if the
    3926             :  * input is known to be in a particular format.
    3927             :  *
    3928             :  * This method does the same thing as the OSRSetFromUserInput() function.
    3929             :  *
    3930             :  * @param pszDefinition text definition to try to deduce SRS from.
    3931             :  *
    3932             :  * @return OGRERR_NONE on success, or an error code if the name isn't
    3933             :  * recognised, the definition is corrupt, or an EPSG value can't be
    3934             :  * successfully looked up.
    3935             :  */
    3936             : 
    3937       13880 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition)
    3938             : {
    3939       13880 :     return SetFromUserInput(pszDefinition, nullptr);
    3940             : }
    3941             : 
    3942             : /**
    3943             :  * \brief Set spatial reference from various text formats.
    3944             :  *
    3945             :  * This method will examine the provided input, and try to deduce the
    3946             :  * format, and then use it to initialize the spatial reference system.  It
    3947             :  * may take the following forms:
    3948             :  *
    3949             :  * <ol>
    3950             :  * <li> Well Known Text definition - passed on to importFromWkt().
    3951             :  * <li> "EPSG:n" - number passed on to importFromEPSG().
    3952             :  * <li> "EPSGA:n" - number passed on to importFromEPSGA().
    3953             :  * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
    3954             :  * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
    3955             :  * <li> PROJ.4 definitions - passed on to importFromProj4().
    3956             :  * <li> filename - file read for WKT, XML or PROJ.4 definition.
    3957             :  * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
    3958             :  * WGS84 or WGS72.
    3959             :  * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
    3960             :  * <li> PROJJSON (PROJ &gt;= 6.2)
    3961             :  * </ol>
    3962             :  *
    3963             :  * It is expected that this method will be extended in the future to support
    3964             :  * XML and perhaps a simplified "minilanguage" for indicating common UTM and
    3965             :  * State Plane definitions.
    3966             :  *
    3967             :  * This method is intended to be flexible, but by its nature it is
    3968             :  * imprecise as it must guess information about the format intended.  When
    3969             :  * possible applications should call the specific method appropriate if the
    3970             :  * input is known to be in a particular format.
    3971             :  *
    3972             :  * This method does the same thing as the OSRSetFromUserInput() and
    3973             :  * OSRSetFromUserInputEx() functions.
    3974             :  *
    3975             :  * @param pszDefinition text definition to try to deduce SRS from.
    3976             :  *
    3977             :  * @param papszOptions NULL terminated list of options, or NULL.
    3978             :  * <ol>
    3979             :  * <li> ALLOW_NETWORK_ACCESS=YES/NO.
    3980             :  *      Whether http:// or https:// access is allowed. Defaults to YES.
    3981             :  * <li> ALLOW_FILE_ACCESS=YES/NO.
    3982             :  *      Whether reading a file using the Virtual File System layer is allowed
    3983             :  *      (can also involve network access). Defaults to YES.
    3984             :  * </ol>
    3985             :  *
    3986             :  * @return OGRERR_NONE on success, or an error code if the name isn't
    3987             :  * recognised, the definition is corrupt, or an EPSG value can't be
    3988             :  * successfully looked up.
    3989             :  */
    3990             : 
    3991       17752 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition,
    3992             :                                              CSLConstList papszOptions)
    3993             : {
    3994       35504 :     TAKE_OPTIONAL_LOCK();
    3995             : 
    3996             :     // Skip leading white space
    3997       17754 :     while (isspace(static_cast<unsigned char>(*pszDefinition)))
    3998           2 :         pszDefinition++;
    3999             : 
    4000       17752 :     if (STARTS_WITH_CI(pszDefinition, "ESRI::"))
    4001             :     {
    4002           1 :         pszDefinition += 6;
    4003             :     }
    4004             : 
    4005             :     /* -------------------------------------------------------------------- */
    4006             :     /*      Is it a recognised syntax?                                      */
    4007             :     /* -------------------------------------------------------------------- */
    4008       17752 :     const char *const wktKeywords[] = {
    4009             :         // WKT1
    4010             :         "GEOGCS", "GEOCCS", "PROJCS", "VERT_CS", "COMPD_CS", "LOCAL_CS",
    4011             :         // WKT2"
    4012             :         "GEODCRS", "GEOGCRS", "GEODETICCRS", "GEOGRAPHICCRS", "PROJCRS",
    4013             :         "PROJECTEDCRS", "VERTCRS", "VERTICALCRS", "COMPOUNDCRS", "ENGCRS",
    4014             :         "ENGINEERINGCRS", "BOUNDCRS", "DERIVEDPROJCRS", "COORDINATEMETADATA"};
    4015      226799 :     for (const char *keyword : wktKeywords)
    4016             :     {
    4017      216797 :         if (STARTS_WITH_CI(pszDefinition, keyword))
    4018             :         {
    4019        7750 :             return importFromWkt(pszDefinition);
    4020             :         }
    4021             :     }
    4022             : 
    4023       10002 :     const bool bStartsWithEPSG = STARTS_WITH_CI(pszDefinition, "EPSG:");
    4024       10002 :     if (bStartsWithEPSG || STARTS_WITH_CI(pszDefinition, "EPSGA:"))
    4025             :     {
    4026        7661 :         OGRErr eStatus = OGRERR_NONE;
    4027             : 
    4028        7661 :         if (strchr(pszDefinition, '+') || strchr(pszDefinition, '@'))
    4029             :         {
    4030             :             // Use proj_create() as it allows things like EPSG:3157+4617
    4031             :             // that are not normally supported by the below code that
    4032             :             // builds manually a compound CRS
    4033          62 :             PJ *pj = proj_create(d->getPROJContext(), pszDefinition);
    4034          62 :             if (!pj)
    4035             :             {
    4036           1 :                 return OGRERR_FAILURE;
    4037             :             }
    4038          61 :             Clear();
    4039          61 :             d->setPjCRS(pj);
    4040          61 :             return OGRERR_NONE;
    4041             :         }
    4042             :         else
    4043             :         {
    4044             :             eStatus =
    4045        7599 :                 importFromEPSG(atoi(pszDefinition + (bStartsWithEPSG ? 5 : 6)));
    4046             :         }
    4047             : 
    4048        7599 :         return eStatus;
    4049             :     }
    4050             : 
    4051        2341 :     if (STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs:") ||
    4052        1673 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs,crs:") ||
    4053        1672 :         STARTS_WITH_CI(pszDefinition, "urn:x-ogc:def:crs:") ||
    4054        1614 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:crs:") ||
    4055        1614 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:def:crs:") ||
    4056        1614 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:coordinateMetadata:"))
    4057         727 :         return importFromURN(pszDefinition);
    4058             : 
    4059        1614 :     if (STARTS_WITH_CI(pszDefinition, "http://opengis.net/def/crs") ||
    4060        1612 :         STARTS_WITH_CI(pszDefinition, "https://opengis.net/def/crs") ||
    4061        1611 :         STARTS_WITH_CI(pszDefinition, "http://www.opengis.net/def/crs") ||
    4062         935 :         STARTS_WITH_CI(pszDefinition, "https://www.opengis.net/def/crs") ||
    4063         934 :         STARTS_WITH_CI(pszDefinition, "www.opengis.net/def/crs"))
    4064         680 :         return importFromCRSURL(pszDefinition);
    4065             : 
    4066         934 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    4067           1 :         return importFromWMSAUTO(pszDefinition);
    4068             : 
    4069             :     // WMS/WCS OGC codes like OGC:CRS84.
    4070         933 :     if (EQUAL(pszDefinition, "OGC:CRS84"))
    4071          27 :         return SetWellKnownGeogCS(pszDefinition + 4);
    4072             : 
    4073         906 :     if (STARTS_WITH_CI(pszDefinition, "CRS:"))
    4074           1 :         return SetWellKnownGeogCS(pszDefinition);
    4075             : 
    4076         905 :     if (STARTS_WITH_CI(pszDefinition, "DICT:") && strstr(pszDefinition, ","))
    4077             :     {
    4078           0 :         char *pszFile = CPLStrdup(pszDefinition + 5);
    4079           0 :         char *pszCode = strstr(pszFile, ",") + 1;
    4080             : 
    4081           0 :         pszCode[-1] = '\0';
    4082             : 
    4083           0 :         OGRErr err = importFromDict(pszFile, pszCode);
    4084           0 :         CPLFree(pszFile);
    4085             : 
    4086           0 :         return err;
    4087             :     }
    4088             : 
    4089         905 :     if (EQUAL(pszDefinition, "NAD27") || EQUAL(pszDefinition, "NAD83") ||
    4090         901 :         EQUAL(pszDefinition, "WGS84") || EQUAL(pszDefinition, "WGS72"))
    4091             :     {
    4092         265 :         Clear();
    4093         265 :         return SetWellKnownGeogCS(pszDefinition);
    4094             :     }
    4095             : 
    4096             :     // PROJJSON
    4097         640 :     if (pszDefinition[0] == '{' && strstr(pszDefinition, "\"type\"") &&
    4098          53 :         (strstr(pszDefinition, "GeodeticCRS") ||
    4099          53 :          strstr(pszDefinition, "GeographicCRS") ||
    4100          21 :          strstr(pszDefinition, "ProjectedCRS") ||
    4101           0 :          strstr(pszDefinition, "VerticalCRS") ||
    4102           0 :          strstr(pszDefinition, "BoundCRS") ||
    4103           0 :          strstr(pszDefinition, "CompoundCRS") ||
    4104           0 :          strstr(pszDefinition, "DerivedGeodeticCRS") ||
    4105           0 :          strstr(pszDefinition, "DerivedGeographicCRS") ||
    4106           0 :          strstr(pszDefinition, "DerivedProjectedCRS") ||
    4107           0 :          strstr(pszDefinition, "DerivedVerticalCRS") ||
    4108           0 :          strstr(pszDefinition, "EngineeringCRS") ||
    4109           0 :          strstr(pszDefinition, "DerivedEngineeringCRS") ||
    4110           0 :          strstr(pszDefinition, "ParametricCRS") ||
    4111           0 :          strstr(pszDefinition, "DerivedParametricCRS") ||
    4112           0 :          strstr(pszDefinition, "TemporalCRS") ||
    4113           0 :          strstr(pszDefinition, "DerivedTemporalCRS")))
    4114             :     {
    4115             :         PJ *pj;
    4116          53 :         if (strstr(pszDefinition, "datum_ensemble") != nullptr)
    4117             :         {
    4118             :             // PROJ < 9.0.1 doesn't like a datum_ensemble whose member have
    4119             :             // a unknown id.
    4120           7 :             CPLJSONDocument oCRSDoc;
    4121           7 :             if (!oCRSDoc.LoadMemory(pszDefinition))
    4122           0 :                 return OGRERR_CORRUPT_DATA;
    4123           7 :             CPLJSONObject oCRSRoot = oCRSDoc.GetRoot();
    4124           7 :             RemoveIDFromMemberOfEnsembles(oCRSRoot);
    4125           7 :             pj = proj_create(d->getPROJContext(), oCRSRoot.ToString().c_str());
    4126             :         }
    4127             :         else
    4128             :         {
    4129          46 :             pj = proj_create(d->getPROJContext(), pszDefinition);
    4130             :         }
    4131          53 :         if (!pj)
    4132             :         {
    4133           2 :             return OGRERR_FAILURE;
    4134             :         }
    4135          51 :         Clear();
    4136          51 :         d->setPjCRS(pj);
    4137          51 :         return OGRERR_NONE;
    4138             :     }
    4139             : 
    4140         587 :     if (strstr(pszDefinition, "+proj") != nullptr ||
    4141         240 :         strstr(pszDefinition, "+init") != nullptr)
    4142         347 :         return importFromProj4(pszDefinition);
    4143             : 
    4144         240 :     if (STARTS_WITH_CI(pszDefinition, "http://") ||
    4145         240 :         STARTS_WITH_CI(pszDefinition, "https://"))
    4146             :     {
    4147           1 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
    4148             :                                              "ALLOW_NETWORK_ACCESS", "YES")))
    4149           0 :             return importFromUrl(pszDefinition);
    4150             : 
    4151           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4152             :                  "Cannot import %s due to ALLOW_NETWORK_ACCESS=NO",
    4153             :                  pszDefinition);
    4154           1 :         return OGRERR_FAILURE;
    4155             :     }
    4156             : 
    4157         239 :     if (EQUAL(pszDefinition, "osgb:BNG"))
    4158             :     {
    4159          13 :         return importFromEPSG(27700);
    4160             :     }
    4161             : 
    4162             :     // Used by German CityGML files
    4163         226 :     if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH"))
    4164             :     {
    4165             :         // "ETRS89 / UTM Zone 32N + DHHN92 height"
    4166           0 :         return SetFromUserInput("EPSG:25832+5783");
    4167             :     }
    4168         226 :     else if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN2016_NH"))
    4169             :     {
    4170             :         // "ETRS89 / UTM Zone 32N + DHHN2016 height"
    4171           4 :         return SetFromUserInput("EPSG:25832+7837");
    4172             :     }
    4173             : 
    4174             :     // Deal with IGNF:xxx, ESRI:xxx, etc from the PROJ database
    4175         222 :     const char *pszDot = strrchr(pszDefinition, ':');
    4176         222 :     if (pszDot)
    4177             :     {
    4178         109 :         CPLString osPrefix(pszDefinition, pszDot - pszDefinition);
    4179             :         auto authorities =
    4180         109 :             proj_get_authorities_from_database(d->getPROJContext());
    4181         109 :         if (authorities)
    4182             :         {
    4183         109 :             std::set<std::string> aosCandidateAuthorities;
    4184         236 :             for (auto iter = authorities; *iter; ++iter)
    4185             :             {
    4186         233 :                 if (*iter == osPrefix)
    4187             :                 {
    4188         106 :                     aosCandidateAuthorities.clear();
    4189         106 :                     aosCandidateAuthorities.insert(*iter);
    4190         106 :                     break;
    4191             :                 }
    4192             :                 // Deal with "IAU_2015" as authority in the list and input
    4193             :                 // "IAU:code"
    4194         127 :                 else if (strncmp(*iter, osPrefix.c_str(), osPrefix.size()) ==
    4195         127 :                              0 &&
    4196           0 :                          (*iter)[osPrefix.size()] == '_')
    4197             :                 {
    4198           0 :                     aosCandidateAuthorities.insert(*iter);
    4199             :                 }
    4200             :                 // Deal with "IAU_2015" as authority in the list and input
    4201             :                 // "IAU:2015:code"
    4202         254 :                 else if (osPrefix.find(':') != std::string::npos &&
    4203         127 :                          osPrefix.size() == strlen(*iter) &&
    4204         127 :                          CPLString(osPrefix).replaceAll(':', '_') == *iter)
    4205             :                 {
    4206           0 :                     aosCandidateAuthorities.clear();
    4207           0 :                     aosCandidateAuthorities.insert(*iter);
    4208           0 :                     break;
    4209             :                 }
    4210             :             }
    4211             : 
    4212         109 :             proj_string_list_destroy(authorities);
    4213             : 
    4214         109 :             if (!aosCandidateAuthorities.empty())
    4215             :             {
    4216         106 :                 auto obj = proj_create_from_database(
    4217             :                     d->getPROJContext(),
    4218         106 :                     aosCandidateAuthorities.rbegin()->c_str(), pszDot + 1,
    4219             :                     PJ_CATEGORY_CRS, false, nullptr);
    4220         106 :                 if (!obj)
    4221             :                 {
    4222           3 :                     return OGRERR_FAILURE;
    4223             :                 }
    4224         103 :                 Clear();
    4225         103 :                 d->setPjCRS(obj);
    4226         103 :                 return OGRERR_NONE;
    4227             :             }
    4228             :         }
    4229             :     }
    4230             : 
    4231             :     /* -------------------------------------------------------------------- */
    4232             :     /*      Try to open it as a file.                                       */
    4233             :     /* -------------------------------------------------------------------- */
    4234         116 :     if (!CPLTestBool(
    4235             :             CSLFetchNameValueDef(papszOptions, "ALLOW_FILE_ACCESS", "YES")))
    4236             :     {
    4237             :         VSIStatBufL sStat;
    4238          24 :         if (STARTS_WITH(pszDefinition, "/vsi") ||
    4239          12 :             VSIStatExL(pszDefinition, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
    4240             :         {
    4241           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4242             :                      "Cannot import %s due to ALLOW_FILE_ACCESS=NO",
    4243             :                      pszDefinition);
    4244           0 :             return OGRERR_FAILURE;
    4245             :         }
    4246             :         // We used to silently return an error without a CE_Failure message
    4247             :         // Cf https://github.com/Toblerity/Fiona/issues/1063
    4248          12 :         return OGRERR_CORRUPT_DATA;
    4249             :     }
    4250             : 
    4251         208 :     CPLConfigOptionSetter oSetter("CPL_ALLOW_VSISTDIN", "NO", true);
    4252         104 :     VSILFILE *const fp = VSIFOpenL(pszDefinition, "rt");
    4253         104 :     if (fp == nullptr)
    4254          99 :         return OGRERR_CORRUPT_DATA;
    4255             : 
    4256           5 :     const size_t nBufMax = 100000;
    4257           5 :     char *const pszBuffer = static_cast<char *>(CPLMalloc(nBufMax));
    4258           5 :     const size_t nBytes = VSIFReadL(pszBuffer, 1, nBufMax - 1, fp);
    4259           5 :     VSIFCloseL(fp);
    4260             : 
    4261           5 :     if (nBytes == nBufMax - 1)
    4262             :     {
    4263           0 :         CPLDebug("OGR",
    4264             :                  "OGRSpatialReference::SetFromUserInput(%s), opened file "
    4265             :                  "but it is to large for our generous buffer.  Is it really "
    4266             :                  "just a WKT definition?",
    4267             :                  pszDefinition);
    4268           0 :         CPLFree(pszBuffer);
    4269           0 :         return OGRERR_FAILURE;
    4270             :     }
    4271             : 
    4272           5 :     pszBuffer[nBytes] = '\0';
    4273             : 
    4274           5 :     char *pszBufPtr = pszBuffer;
    4275           5 :     while (pszBufPtr[0] == ' ' || pszBufPtr[0] == '\n')
    4276           0 :         pszBufPtr++;
    4277             : 
    4278           5 :     OGRErr err = OGRERR_NONE;
    4279           5 :     if (pszBufPtr[0] == '<')
    4280           0 :         err = importFromXML(pszBufPtr);
    4281           5 :     else if ((strstr(pszBuffer, "+proj") != nullptr ||
    4282           5 :               strstr(pszBuffer, "+init") != nullptr) &&
    4283           0 :              (strstr(pszBuffer, "EXTENSION") == nullptr &&
    4284           0 :               strstr(pszBuffer, "extension") == nullptr))
    4285           0 :         err = importFromProj4(pszBufPtr);
    4286             :     else
    4287             :     {
    4288           5 :         if (STARTS_WITH_CI(pszBufPtr, "ESRI::"))
    4289             :         {
    4290           3 :             pszBufPtr += 6;
    4291             :         }
    4292             : 
    4293             :         // coverity[tainted_data]
    4294           5 :         err = importFromWkt(pszBufPtr);
    4295             :     }
    4296             : 
    4297           5 :     CPLFree(pszBuffer);
    4298             : 
    4299           5 :     return err;
    4300             : }
    4301             : 
    4302             : /************************************************************************/
    4303             : /*                        OSRSetFromUserInput()                         */
    4304             : /************************************************************************/
    4305             : 
    4306             : /**
    4307             :  * \brief Set spatial reference from various text formats.
    4308             :  *
    4309             :  * This function is the same as OGRSpatialReference::SetFromUserInput()
    4310             :  *
    4311             :  * \see OSRSetFromUserInputEx() for a variant allowing to pass options.
    4312             :  */
    4313         265 : OGRErr CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS,
    4314             :                                        const char *pszDef)
    4315             : 
    4316             : {
    4317         265 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInput", OGRERR_FAILURE);
    4318             : 
    4319         265 :     return ToPointer(hSRS)->SetFromUserInput(pszDef);
    4320             : }
    4321             : 
    4322             : /************************************************************************/
    4323             : /*                       OSRSetFromUserInputEx()                        */
    4324             : /************************************************************************/
    4325             : 
    4326             : /**
    4327             :  * \brief Set spatial reference from various text formats.
    4328             :  *
    4329             :  * This function is the same as OGRSpatialReference::SetFromUserInput().
    4330             :  *
    4331             :  * @since GDAL 3.9
    4332             :  */
    4333         720 : OGRErr OSRSetFromUserInputEx(OGRSpatialReferenceH hSRS, const char *pszDef,
    4334             :                              CSLConstList papszOptions)
    4335             : 
    4336             : {
    4337         720 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInputEx", OGRERR_FAILURE);
    4338             : 
    4339         720 :     return ToPointer(hSRS)->SetFromUserInput(pszDef, papszOptions);
    4340             : }
    4341             : 
    4342             : /************************************************************************/
    4343             : /*                          ImportFromUrl()                             */
    4344             : /************************************************************************/
    4345             : 
    4346             : /**
    4347             :  * \brief Set spatial reference from a URL.
    4348             :  *
    4349             :  * This method will download the spatial reference at a given URL and
    4350             :  * feed it into SetFromUserInput for you.
    4351             :  *
    4352             :  * This method does the same thing as the OSRImportFromUrl() function.
    4353             :  *
    4354             :  * @param pszUrl text definition to try to deduce SRS from.
    4355             :  *
    4356             :  * @return OGRERR_NONE on success, or an error code with the curl
    4357             :  * error message if it is unable to download data.
    4358             :  */
    4359             : 
    4360           5 : OGRErr OGRSpatialReference::importFromUrl(const char *pszUrl)
    4361             : 
    4362             : {
    4363          10 :     TAKE_OPTIONAL_LOCK();
    4364             : 
    4365           5 :     if (!STARTS_WITH_CI(pszUrl, "http://") &&
    4366           3 :         !STARTS_WITH_CI(pszUrl, "https://"))
    4367             :     {
    4368           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    4369             :                  "The given string is not recognized as a URL"
    4370             :                  "starting with 'http://' -- %s",
    4371             :                  pszUrl);
    4372           2 :         return OGRERR_FAILURE;
    4373             :     }
    4374             : 
    4375             :     /* -------------------------------------------------------------------- */
    4376             :     /*      Fetch the result.                                               */
    4377             :     /* -------------------------------------------------------------------- */
    4378           3 :     CPLErrorReset();
    4379             : 
    4380           6 :     std::string osUrl(pszUrl);
    4381             :     // We have historically supported "http://spatialreference.org/ref/AUTHNAME/CODE/"
    4382             :     // as a valid URL since we used a "Accept: application/x-ogcwkt" header
    4383             :     // to query WKT. To allow a static server to be used, rather append a
    4384             :     // "ogcwkt/" suffix.
    4385           2 :     for (const char *pszPrefix : {"https://spatialreference.org/ref/",
    4386           5 :                                   "http://spatialreference.org/ref/"})
    4387             :     {
    4388           5 :         if (STARTS_WITH(pszUrl, pszPrefix))
    4389             :         {
    4390             :             const CPLStringList aosTokens(
    4391           6 :                 CSLTokenizeString2(pszUrl + strlen(pszPrefix), "/", 0));
    4392           3 :             if (aosTokens.size() == 2)
    4393             :             {
    4394           2 :                 osUrl = "https://spatialreference.org/ref/";
    4395           2 :                 osUrl += aosTokens[0];  // authority
    4396           2 :                 osUrl += '/';
    4397           2 :                 osUrl += aosTokens[1];  // code
    4398           2 :                 osUrl += "/ogcwkt/";
    4399             :             }
    4400           3 :             break;
    4401             :         }
    4402             :     }
    4403             : 
    4404           3 :     const char *pszTimeout = "TIMEOUT=10";
    4405           3 :     char *apszOptions[] = {const_cast<char *>(pszTimeout), nullptr};
    4406             : 
    4407           3 :     CPLHTTPResult *psResult = CPLHTTPFetch(osUrl.c_str(), apszOptions);
    4408             : 
    4409             :     /* -------------------------------------------------------------------- */
    4410             :     /*      Try to handle errors.                                           */
    4411             :     /* -------------------------------------------------------------------- */
    4412             : 
    4413           3 :     if (psResult == nullptr)
    4414           0 :         return OGRERR_FAILURE;
    4415           6 :     if (psResult->nDataLen == 0 || CPLGetLastErrorNo() != 0 ||
    4416           3 :         psResult->pabyData == nullptr)
    4417             :     {
    4418           0 :         if (CPLGetLastErrorNo() == 0)
    4419             :         {
    4420           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4421             :                      "No data was returned from the given URL");
    4422             :         }
    4423           0 :         CPLHTTPDestroyResult(psResult);
    4424           0 :         return OGRERR_FAILURE;
    4425             :     }
    4426             : 
    4427           3 :     if (psResult->nStatus != 0)
    4428             :     {
    4429           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
    4430             :                  psResult->nStatus, psResult->pszErrBuf);
    4431           0 :         CPLHTTPDestroyResult(psResult);
    4432           0 :         return OGRERR_FAILURE;
    4433             :     }
    4434             : 
    4435           3 :     const char *pszData = reinterpret_cast<const char *>(psResult->pabyData);
    4436           3 :     if (STARTS_WITH_CI(pszData, "http://") ||
    4437           3 :         STARTS_WITH_CI(pszData, "https://"))
    4438             :     {
    4439           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4440             :                  "The data that was downloaded also starts with 'http://' "
    4441             :                  "and cannot be passed into SetFromUserInput.  Is this "
    4442             :                  "really a spatial reference definition? ");
    4443           0 :         CPLHTTPDestroyResult(psResult);
    4444           0 :         return OGRERR_FAILURE;
    4445             :     }
    4446           3 :     if (OGRERR_NONE != SetFromUserInput(pszData))
    4447             :     {
    4448           0 :         CPLHTTPDestroyResult(psResult);
    4449           0 :         return OGRERR_FAILURE;
    4450             :     }
    4451             : 
    4452           3 :     CPLHTTPDestroyResult(psResult);
    4453           3 :     return OGRERR_NONE;
    4454             : }
    4455             : 
    4456             : /************************************************************************/
    4457             : /*                        OSRimportFromUrl()                            */
    4458             : /************************************************************************/
    4459             : 
    4460             : /**
    4461             :  * \brief Set spatial reference from a URL.
    4462             :  *
    4463             :  * This function is the same as OGRSpatialReference::importFromUrl()
    4464             :  */
    4465           3 : OGRErr OSRImportFromUrl(OGRSpatialReferenceH hSRS, const char *pszUrl)
    4466             : 
    4467             : {
    4468           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromUrl", OGRERR_FAILURE);
    4469             : 
    4470           3 :     return ToPointer(hSRS)->importFromUrl(pszUrl);
    4471             : }
    4472             : 
    4473             : /************************************************************************/
    4474             : /*                         importFromURNPart()                          */
    4475             : /************************************************************************/
    4476         867 : OGRErr OGRSpatialReference::importFromURNPart(const char *pszAuthority,
    4477             :                                               const char *pszCode,
    4478             :                                               const char *pszURN)
    4479             : {
    4480             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4481             :     (void)this;
    4482             :     (void)pszAuthority;
    4483             :     (void)pszCode;
    4484             :     (void)pszURN;
    4485             :     return OGRERR_FAILURE;
    4486             : #else
    4487             :     /* -------------------------------------------------------------------- */
    4488             :     /*      Is this an EPSG code? Note that we import it with EPSG          */
    4489             :     /*      preferred axis ordering for geographic coordinate systems.      */
    4490             :     /* -------------------------------------------------------------------- */
    4491         867 :     if (STARTS_WITH_CI(pszAuthority, "EPSG"))
    4492         788 :         return importFromEPSGA(atoi(pszCode));
    4493             : 
    4494             :     /* -------------------------------------------------------------------- */
    4495             :     /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
    4496             :     /* -------------------------------------------------------------------- */
    4497          79 :     if (STARTS_WITH_CI(pszAuthority, "IAU"))
    4498           0 :         return importFromDict("IAU2000.wkt", pszCode);
    4499             : 
    4500             :     /* -------------------------------------------------------------------- */
    4501             :     /*      Is this an OGC code?                                            */
    4502             :     /* -------------------------------------------------------------------- */
    4503          79 :     if (!STARTS_WITH_CI(pszAuthority, "OGC"))
    4504             :     {
    4505           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4506             :                  "URN %s has unrecognized authority.", pszURN);
    4507           1 :         return OGRERR_FAILURE;
    4508             :     }
    4509             : 
    4510          78 :     if (STARTS_WITH_CI(pszCode, "CRS84"))
    4511          68 :         return SetWellKnownGeogCS(pszCode);
    4512          10 :     else if (STARTS_WITH_CI(pszCode, "CRS83"))
    4513           0 :         return SetWellKnownGeogCS(pszCode);
    4514          10 :     else if (STARTS_WITH_CI(pszCode, "CRS27"))
    4515           0 :         return SetWellKnownGeogCS(pszCode);
    4516          10 :     else if (STARTS_WITH_CI(pszCode, "84"))  // urn:ogc:def:crs:OGC:2:84
    4517           8 :         return SetWellKnownGeogCS("CRS84");
    4518             : 
    4519             :     /* -------------------------------------------------------------------- */
    4520             :     /*      Handle auto codes.  We need to convert from format              */
    4521             :     /*      AUTO42001:99:8888 to format AUTO:42001,99,8888.                 */
    4522             :     /* -------------------------------------------------------------------- */
    4523           2 :     else if (STARTS_WITH_CI(pszCode, "AUTO"))
    4524             :     {
    4525           2 :         char szWMSAuto[100] = {'\0'};
    4526             : 
    4527           2 :         if (strlen(pszCode) > sizeof(szWMSAuto) - 2)
    4528           0 :             return OGRERR_FAILURE;
    4529             : 
    4530           2 :         snprintf(szWMSAuto, sizeof(szWMSAuto), "AUTO:%s", pszCode + 4);
    4531          28 :         for (int i = 5; szWMSAuto[i] != '\0'; i++)
    4532             :         {
    4533          26 :             if (szWMSAuto[i] == ':')
    4534           4 :                 szWMSAuto[i] = ',';
    4535             :         }
    4536             : 
    4537           2 :         return importFromWMSAUTO(szWMSAuto);
    4538             :     }
    4539             : 
    4540             :     /* -------------------------------------------------------------------- */
    4541             :     /*      Not a recognise OGC item.                                       */
    4542             :     /* -------------------------------------------------------------------- */
    4543           0 :     CPLError(CE_Failure, CPLE_AppDefined, "URN %s value not supported.",
    4544             :              pszURN);
    4545             : 
    4546           0 :     return OGRERR_FAILURE;
    4547             : #endif
    4548             : }
    4549             : 
    4550             : /************************************************************************/
    4551             : /*                           importFromURN()                            */
    4552             : /*                                                                      */
    4553             : /*      See OGC recommendation paper 06-023r1 or later for details.     */
    4554             : /************************************************************************/
    4555             : 
    4556             : /**
    4557             :  * \brief Initialize from OGC URN.
    4558             :  *
    4559             :  * Initializes this spatial reference from a coordinate system defined
    4560             :  * by an OGC URN prefixed with "urn:ogc:def:crs:" per recommendation
    4561             :  * paper 06-023r1.  Currently EPSG and OGC authority values are supported,
    4562             :  * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
    4563             :  *
    4564             :  * This method is also support through SetFromUserInput() which can
    4565             :  * normally be used for URNs.
    4566             :  *
    4567             :  * @param pszURN the urn string.
    4568             :  *
    4569             :  * @return OGRERR_NONE on success or an error code.
    4570             :  */
    4571             : 
    4572         788 : OGRErr OGRSpatialReference::importFromURN(const char *pszURN)
    4573             : 
    4574             : {
    4575         788 :     constexpr const char *EPSG_URN_CRS_PREFIX = "urn:ogc:def:crs:EPSG::";
    4576        1492 :     if (STARTS_WITH(pszURN, EPSG_URN_CRS_PREFIX) &&
    4577         704 :         CPLGetValueType(pszURN + strlen(EPSG_URN_CRS_PREFIX)) ==
    4578             :             CPL_VALUE_INTEGER)
    4579             :     {
    4580         702 :         return importFromEPSG(atoi(pszURN + strlen(EPSG_URN_CRS_PREFIX)));
    4581             :     }
    4582             : 
    4583         172 :     TAKE_OPTIONAL_LOCK();
    4584             : 
    4585             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4586             : 
    4587             :     // PROJ 8.2.0 has support for IAU codes now.
    4588             : #if !PROJ_AT_LEAST_VERSION(8, 2, 0)
    4589             :     /* -------------------------------------------------------------------- */
    4590             :     /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
    4591             :     /* -------------------------------------------------------------------- */
    4592             :     const char *pszIAU = strstr(pszURN, "IAU");
    4593             :     if (pszIAU)
    4594             :     {
    4595             :         const char *pszCode = strchr(pszIAU, ':');
    4596             :         if (pszCode)
    4597             :         {
    4598             :             ++pszCode;
    4599             :             if (*pszCode == ':')
    4600             :                 ++pszCode;
    4601             :             return importFromDict("IAU2000.wkt", pszCode);
    4602             :         }
    4603             :     }
    4604             : #endif
    4605             : 
    4606             :     if (strlen(pszURN) >= 1000)
    4607             :     {
    4608             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4609             :         return OGRERR_CORRUPT_DATA;
    4610             :     }
    4611             :     auto obj = proj_create(d->getPROJContext(), pszURN);
    4612             :     if (!obj)
    4613             :     {
    4614             :         return OGRERR_FAILURE;
    4615             :     }
    4616             :     Clear();
    4617             :     d->setPjCRS(obj);
    4618             :     return OGRERR_NONE;
    4619             : #else
    4620          86 :     const char *pszCur = nullptr;
    4621             : 
    4622          86 :     if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs:"))
    4623          21 :         pszCur = pszURN + 16;
    4624          65 :     else if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs,crs:"))
    4625           1 :         pszCur = pszURN + 20;
    4626          64 :     else if (STARTS_WITH_CI(pszURN, "urn:x-ogc:def:crs:"))
    4627          62 :         pszCur = pszURN + 18;
    4628           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:crs:"))
    4629           0 :         pszCur = pszURN + 16;
    4630           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:def:crs:"))
    4631           0 :         pszCur = pszURN + 20;
    4632             :     else
    4633             :     {
    4634           2 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4635             :                  pszURN);
    4636           2 :         return OGRERR_FAILURE;
    4637             :     }
    4638             : 
    4639             :     /* -------------------------------------------------------------------- */
    4640             :     /*      Clear any existing definition.                                  */
    4641             :     /* -------------------------------------------------------------------- */
    4642          84 :     Clear();
    4643             : 
    4644             :     /* -------------------------------------------------------------------- */
    4645             :     /*      Find code (ignoring version) out of string like:                */
    4646             :     /*                                                                      */
    4647             :     /*      authority:[version]:code                                        */
    4648             :     /* -------------------------------------------------------------------- */
    4649          84 :     const char *pszAuthority = pszCur;
    4650             : 
    4651             :     // skip authority
    4652         403 :     while (*pszCur != ':' && *pszCur)
    4653         319 :         pszCur++;
    4654          84 :     if (*pszCur == ':')
    4655          84 :         pszCur++;
    4656             : 
    4657             :     // skip version
    4658          84 :     const char *pszBeforeVersion = pszCur;
    4659         380 :     while (*pszCur != ':' && *pszCur)
    4660         296 :         pszCur++;
    4661          84 :     if (*pszCur == ':')
    4662          56 :         pszCur++;
    4663             :     else
    4664             :         // We come here in the case, the content to parse is authority:code
    4665             :         // (instead of authority::code) which is probably illegal according to
    4666             :         // http://www.opengeospatial.org/ogcUrnPolicy but such content is found
    4667             :         // for example in what is returned by GeoServer.
    4668          28 :         pszCur = pszBeforeVersion;
    4669             : 
    4670          84 :     const char *pszCode = pszCur;
    4671             : 
    4672          84 :     const char *pszComma = strchr(pszCur, ',');
    4673          84 :     if (pszComma == nullptr)
    4674          83 :         return importFromURNPart(pszAuthority, pszCode, pszURN);
    4675             : 
    4676             :     // There's a second part with the vertical SRS.
    4677           1 :     pszCur = pszComma + 1;
    4678           1 :     if (!STARTS_WITH(pszCur, "crs:"))
    4679             :     {
    4680           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4681             :                  pszURN);
    4682           0 :         return OGRERR_FAILURE;
    4683             :     }
    4684             : 
    4685           1 :     pszCur += 4;
    4686             : 
    4687           1 :     char *pszFirstCode = CPLStrdup(pszCode);
    4688           1 :     pszFirstCode[pszComma - pszCode] = '\0';
    4689           1 :     OGRErr eStatus = importFromURNPart(pszAuthority, pszFirstCode, pszURN);
    4690           1 :     CPLFree(pszFirstCode);
    4691             : 
    4692             :     // Do we want to turn this into a compound definition
    4693             :     // with a vertical datum?
    4694           1 :     if (eStatus != OGRERR_NONE)
    4695           0 :         return eStatus;
    4696             : 
    4697             :     /* -------------------------------------------------------------------- */
    4698             :     /*      Find code (ignoring version) out of string like:                */
    4699             :     /*                                                                      */
    4700             :     /*      authority:[version]:code                                        */
    4701             :     /* -------------------------------------------------------------------- */
    4702           1 :     pszAuthority = pszCur;
    4703             : 
    4704             :     // skip authority
    4705           5 :     while (*pszCur != ':' && *pszCur)
    4706           4 :         pszCur++;
    4707           1 :     if (*pszCur == ':')
    4708           1 :         pszCur++;
    4709             : 
    4710             :     // skip version
    4711           1 :     pszBeforeVersion = pszCur;
    4712           1 :     while (*pszCur != ':' && *pszCur)
    4713           0 :         pszCur++;
    4714           1 :     if (*pszCur == ':')
    4715           1 :         pszCur++;
    4716             :     else
    4717           0 :         pszCur = pszBeforeVersion;
    4718             : 
    4719           1 :     pszCode = pszCur;
    4720             : 
    4721           2 :     OGRSpatialReference oVertSRS;
    4722           1 :     eStatus = oVertSRS.importFromURNPart(pszAuthority, pszCode, pszURN);
    4723           1 :     if (eStatus == OGRERR_NONE)
    4724             :     {
    4725           1 :         OGRSpatialReference oHorizSRS(*this);
    4726             : 
    4727           1 :         Clear();
    4728             : 
    4729           1 :         oHorizSRS.d->refreshProjObj();
    4730           1 :         oVertSRS.d->refreshProjObj();
    4731           1 :         if (!oHorizSRS.d->m_pj_crs || !oVertSRS.d->m_pj_crs)
    4732           0 :             return OGRERR_FAILURE;
    4733             : 
    4734           1 :         const char *pszHorizName = proj_get_name(oHorizSRS.d->m_pj_crs);
    4735           1 :         const char *pszVertName = proj_get_name(oVertSRS.d->m_pj_crs);
    4736             : 
    4737           2 :         CPLString osName = pszHorizName ? pszHorizName : "";
    4738           1 :         osName += " + ";
    4739           1 :         osName += pszVertName ? pszVertName : "";
    4740             : 
    4741           1 :         SetCompoundCS(osName, &oHorizSRS, &oVertSRS);
    4742             :     }
    4743             : 
    4744           1 :     return eStatus;
    4745             : #endif
    4746             : }
    4747             : 
    4748             : /************************************************************************/
    4749             : /*                           importFromCRSURL()                         */
    4750             : /*                                                                      */
    4751             : /*      See OGC Best Practice document 11-135 for details.              */
    4752             : /************************************************************************/
    4753             : 
    4754             : /**
    4755             :  * \brief Initialize from OGC URL.
    4756             :  *
    4757             :  * Initializes this spatial reference from a coordinate system defined
    4758             :  * by an OGC URL prefixed with "http://opengis.net/def/crs" per best practice
    4759             :  * paper 11-135.  Currently EPSG and OGC authority values are supported,
    4760             :  * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
    4761             :  *
    4762             :  * This method is also supported through SetFromUserInput() which can
    4763             :  * normally be used for URLs.
    4764             :  *
    4765             :  * @param pszURL the URL string.
    4766             :  *
    4767             :  * @return OGRERR_NONE on success or an error code.
    4768             :  */
    4769             : 
    4770         783 : OGRErr OGRSpatialReference::importFromCRSURL(const char *pszURL)
    4771             : 
    4772             : {
    4773        1566 :     TAKE_OPTIONAL_LOCK();
    4774             : 
    4775             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4776             :     if (strlen(pszURL) >= 10000)
    4777             :     {
    4778             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4779             :         return OGRERR_CORRUPT_DATA;
    4780             :     }
    4781             : 
    4782             :     PJ *obj;
    4783             : #if !PROJ_AT_LEAST_VERSION(9, 2, 0)
    4784             :     if (STARTS_WITH(pszURL, "http://www.opengis.net/def/crs/IAU/0/"))
    4785             :     {
    4786             :         obj = proj_create(
    4787             :             d->getPROJContext(),
    4788             :             CPLSPrintf("IAU:%s",
    4789             :                        pszURL +
    4790             :                            strlen("http://www.opengis.net/def/crs/IAU/0/")));
    4791             :     }
    4792             :     else
    4793             : #endif
    4794             :     {
    4795             :         obj = proj_create(d->getPROJContext(), pszURL);
    4796             :     }
    4797             :     if (!obj)
    4798             :     {
    4799             :         return OGRERR_FAILURE;
    4800             :     }
    4801             :     Clear();
    4802             :     d->setPjCRS(obj);
    4803             :     return OGRERR_NONE;
    4804             : #else
    4805         783 :     const char *pszCur = nullptr;
    4806             : 
    4807         783 :     if (STARTS_WITH_CI(pszURL, "http://opengis.net/def/crs"))
    4808           2 :         pszCur = pszURL + 26;
    4809         781 :     else if (STARTS_WITH_CI(pszURL, "https://opengis.net/def/crs"))
    4810           1 :         pszCur = pszURL + 27;
    4811         780 :     else if (STARTS_WITH_CI(pszURL, "http://www.opengis.net/def/crs"))
    4812         779 :         pszCur = pszURL + 30;
    4813           1 :     else if (STARTS_WITH_CI(pszURL, "https://www.opengis.net/def/crs"))
    4814           1 :         pszCur = pszURL + 31;
    4815           0 :     else if (STARTS_WITH_CI(pszURL, "www.opengis.net/def/crs"))
    4816           0 :         pszCur = pszURL + 23;
    4817             :     else
    4818             :     {
    4819           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s not a supported format.",
    4820             :                  pszURL);
    4821           0 :         return OGRERR_FAILURE;
    4822             :     }
    4823             : 
    4824         783 :     if (*pszCur == '\0')
    4825             :     {
    4826           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s malformed.", pszURL);
    4827           0 :         return OGRERR_FAILURE;
    4828             :     }
    4829             : 
    4830             :     /* -------------------------------------------------------------------- */
    4831             :     /*      Clear any existing definition.                                  */
    4832             :     /* -------------------------------------------------------------------- */
    4833         783 :     Clear();
    4834             : 
    4835         783 :     if (STARTS_WITH_CI(pszCur, "-compound?1="))
    4836             :     {
    4837             :         /* --------------------------------------------------------------------
    4838             :          */
    4839             :         /*      It's a compound CRS, of the form: */
    4840             :         /*                                                                      */
    4841             :         /*      http://opengis.net/def/crs-compound?1=URL1&2=URL2&3=URL3&.. */
    4842             :         /* --------------------------------------------------------------------
    4843             :          */
    4844           1 :         pszCur += 12;
    4845             : 
    4846             :         // Extract each component CRS URL.
    4847           1 :         int iComponentUrl = 2;
    4848             : 
    4849           2 :         CPLString osName = "";
    4850           1 :         Clear();
    4851             : 
    4852           3 :         while (iComponentUrl != -1)
    4853             :         {
    4854           2 :             char searchStr[15] = {};
    4855           2 :             snprintf(searchStr, sizeof(searchStr), "&%d=", iComponentUrl);
    4856             : 
    4857           2 :             const char *pszUrlEnd = strstr(pszCur, searchStr);
    4858             : 
    4859             :             // Figure out the next component URL.
    4860           2 :             char *pszComponentUrl = nullptr;
    4861             : 
    4862           2 :             if (pszUrlEnd)
    4863             :             {
    4864           1 :                 size_t nLen = pszUrlEnd - pszCur;
    4865           1 :                 pszComponentUrl = static_cast<char *>(CPLMalloc(nLen + 1));
    4866           1 :                 strncpy(pszComponentUrl, pszCur, nLen);
    4867           1 :                 pszComponentUrl[nLen] = '\0';
    4868             : 
    4869           1 :                 ++iComponentUrl;
    4870           1 :                 pszCur += nLen + strlen(searchStr);
    4871             :             }
    4872             :             else
    4873             :             {
    4874           1 :                 if (iComponentUrl == 2)
    4875             :                 {
    4876           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    4877             :                              "Compound CRS URLs must have at least two "
    4878             :                              "component CRSs.");
    4879           0 :                     return OGRERR_FAILURE;
    4880             :                 }
    4881             :                 else
    4882             :                 {
    4883           1 :                     pszComponentUrl = CPLStrdup(pszCur);
    4884             :                     // no more components
    4885           1 :                     iComponentUrl = -1;
    4886             :                 }
    4887             :             }
    4888             : 
    4889           2 :             OGRSpatialReference oComponentSRS;
    4890           2 :             OGRErr eStatus = oComponentSRS.importFromCRSURL(pszComponentUrl);
    4891             : 
    4892           2 :             CPLFree(pszComponentUrl);
    4893           2 :             pszComponentUrl = nullptr;
    4894             : 
    4895           2 :             if (eStatus == OGRERR_NONE)
    4896             :             {
    4897           2 :                 if (osName.length() != 0)
    4898             :                 {
    4899           1 :                     osName += " + ";
    4900             :                 }
    4901           2 :                 osName += oComponentSRS.GetRoot()->GetValue();
    4902           2 :                 SetNode("COMPD_CS", osName);
    4903           2 :                 GetRoot()->AddChild(oComponentSRS.GetRoot()->Clone());
    4904             :             }
    4905             :             else
    4906           0 :                 return eStatus;
    4907             :         }
    4908             : 
    4909           1 :         return OGRERR_NONE;
    4910             :     }
    4911             : 
    4912             :     /* -------------------------------------------------------------------- */
    4913             :     /*      It's a normal CRS URL, of the form:                             */
    4914             :     /*                                                                      */
    4915             :     /*      http://opengis.net/def/crs/AUTHORITY/VERSION/CODE               */
    4916             :     /* -------------------------------------------------------------------- */
    4917         782 :     ++pszCur;
    4918         782 :     const char *pszAuthority = pszCur;
    4919             : 
    4920             :     // skip authority
    4921      103845 :     while (*pszCur != '/' && *pszCur)
    4922      103063 :         pszCur++;
    4923         782 :     if (*pszCur == '/')
    4924         781 :         pszCur++;
    4925             : 
    4926             :     // skip version
    4927        1685 :     while (*pszCur != '/' && *pszCur)
    4928         903 :         pszCur++;
    4929         782 :     if (*pszCur == '/')
    4930         781 :         pszCur++;
    4931             : 
    4932         782 :     const char *pszCode = pszCur;
    4933             : 
    4934         782 :     return importFromURNPart(pszAuthority, pszCode, pszURL);
    4935             : #endif
    4936             : }
    4937             : 
    4938             : /************************************************************************/
    4939             : /*                         importFromWMSAUTO()                          */
    4940             : /************************************************************************/
    4941             : 
    4942             : /**
    4943             :  * \brief Initialize from WMSAUTO string.
    4944             :  *
    4945             :  * Note that the WMS 1.3 specification does not include the
    4946             :  * units code, while apparently earlier specs do.  We try to
    4947             :  * guess around this.
    4948             :  *
    4949             :  * @param pszDefinition the WMSAUTO string
    4950             :  *
    4951             :  * @return OGRERR_NONE on success or an error code.
    4952             :  */
    4953           3 : OGRErr OGRSpatialReference::importFromWMSAUTO(const char *pszDefinition)
    4954             : 
    4955             : {
    4956           6 :     TAKE_OPTIONAL_LOCK();
    4957             : 
    4958             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4959             :     if (strlen(pszDefinition) >= 10000)
    4960             :     {
    4961             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4962             :         return OGRERR_CORRUPT_DATA;
    4963             :     }
    4964             : 
    4965             :     auto obj = proj_create(d->getPROJContext(), pszDefinition);
    4966             :     if (!obj)
    4967             :     {
    4968             :         return OGRERR_FAILURE;
    4969             :     }
    4970             :     Clear();
    4971             :     d->setPjCRS(obj);
    4972             :     return OGRERR_NONE;
    4973             : #else
    4974             :     int nProjId, nUnitsId;
    4975           3 :     double dfRefLong, dfRefLat = 0.0;
    4976             : 
    4977             :     /* -------------------------------------------------------------------- */
    4978             :     /*      Tokenize                                                        */
    4979             :     /* -------------------------------------------------------------------- */
    4980           3 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    4981           3 :         pszDefinition += 5;
    4982             : 
    4983             :     char **papszTokens =
    4984           3 :         CSLTokenizeStringComplex(pszDefinition, ",", FALSE, TRUE);
    4985             : 
    4986           3 :     if (CSLCount(papszTokens) == 4)
    4987             :     {
    4988           0 :         nProjId = atoi(papszTokens[0]);
    4989           0 :         nUnitsId = atoi(papszTokens[1]);
    4990           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    4991           0 :         dfRefLat = CPLAtof(papszTokens[3]);
    4992             :     }
    4993           3 :     else if (CSLCount(papszTokens) == 3 && atoi(papszTokens[0]) == 42005)
    4994             :     {
    4995           0 :         nProjId = atoi(papszTokens[0]);
    4996           0 :         nUnitsId = atoi(papszTokens[1]);
    4997           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    4998           0 :         dfRefLat = 0.0;
    4999             :     }
    5000           3 :     else if (CSLCount(papszTokens) == 3)
    5001             :     {
    5002           2 :         nProjId = atoi(papszTokens[0]);
    5003           2 :         nUnitsId = 9001;
    5004           2 :         dfRefLong = CPLAtof(papszTokens[1]);
    5005           2 :         dfRefLat = CPLAtof(papszTokens[2]);
    5006             :     }
    5007           1 :     else if (CSLCount(papszTokens) == 2 && atoi(papszTokens[0]) == 42005)
    5008             :     {
    5009           0 :         nProjId = atoi(papszTokens[0]);
    5010           0 :         nUnitsId = 9001;
    5011           0 :         dfRefLong = CPLAtof(papszTokens[1]);
    5012             :     }
    5013             :     else
    5014             :     {
    5015           1 :         CSLDestroy(papszTokens);
    5016           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    5017             :                  "AUTO projection has wrong number of arguments, expected\n"
    5018             :                  "AUTO:proj_id,units_id,ref_long,ref_lat or"
    5019             :                  "AUTO:proj_id,ref_long,ref_lat");
    5020           1 :         return OGRERR_FAILURE;
    5021             :     }
    5022             : 
    5023           2 :     CSLDestroy(papszTokens);
    5024           2 :     papszTokens = nullptr;
    5025             : 
    5026             :     /* -------------------------------------------------------------------- */
    5027             :     /*      Build coordsys.                                                 */
    5028             :     /* -------------------------------------------------------------------- */
    5029           2 :     Clear();
    5030             : 
    5031             :     /* -------------------------------------------------------------------- */
    5032             :     /*      Set WGS84.                                                      */
    5033             :     /* -------------------------------------------------------------------- */
    5034           2 :     SetWellKnownGeogCS("WGS84");
    5035             : 
    5036           2 :     switch (nProjId)
    5037             :     {
    5038           2 :         case 42001:  // Auto UTM
    5039           2 :             SetUTM(static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
    5040             :                    dfRefLat >= 0.0);
    5041           2 :             break;
    5042             : 
    5043           0 :         case 42002:  // Auto TM (strangely very UTM-like).
    5044           0 :             SetTM(0, dfRefLong, 0.9996, 500000.0,
    5045             :                   (dfRefLat >= 0.0) ? 0.0 : 10000000.0);
    5046           0 :             break;
    5047             : 
    5048           0 :         case 42003:  // Auto Orthographic.
    5049           0 :             SetOrthographic(dfRefLat, dfRefLong, 0.0, 0.0);
    5050           0 :             break;
    5051             : 
    5052           0 :         case 42004:  // Auto Equirectangular
    5053           0 :             SetEquirectangular(dfRefLat, dfRefLong, 0.0, 0.0);
    5054           0 :             break;
    5055             : 
    5056           0 :         case 42005:
    5057           0 :             SetMollweide(dfRefLong, 0.0, 0.0);
    5058           0 :             break;
    5059             : 
    5060           0 :         default:
    5061           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5062             :                      "Unsupported projection id in importFromWMSAUTO(): %d",
    5063             :                      nProjId);
    5064           0 :             return OGRERR_FAILURE;
    5065             :     }
    5066             : 
    5067             :     /* -------------------------------------------------------------------- */
    5068             :     /*      Set units.                                                      */
    5069             :     /* -------------------------------------------------------------------- */
    5070             : 
    5071           2 :     switch (nUnitsId)
    5072             :     {
    5073           2 :         case 9001:
    5074           2 :             SetTargetLinearUnits(nullptr, SRS_UL_METER, 1.0, "EPSG", "9001");
    5075           2 :             break;
    5076             : 
    5077           0 :         case 9002:
    5078           0 :             SetTargetLinearUnits(nullptr, "Foot", 0.3048, "EPSG", "9002");
    5079           0 :             break;
    5080             : 
    5081           0 :         case 9003:
    5082           0 :             SetTargetLinearUnits(nullptr, "US survey foot",
    5083             :                                  CPLAtof(SRS_UL_US_FOOT_CONV), "EPSG", "9003");
    5084           0 :             break;
    5085             : 
    5086           0 :         default:
    5087           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5088             :                      "Unsupported units code (%d).", nUnitsId);
    5089           0 :             return OGRERR_FAILURE;
    5090             :             break;
    5091             :     }
    5092             : 
    5093           2 :     return OGRERR_NONE;
    5094             : #endif
    5095             : }
    5096             : 
    5097             : /************************************************************************/
    5098             : /*                            GetSemiMajor()                            */
    5099             : /************************************************************************/
    5100             : 
    5101             : /**
    5102             :  * \brief Get spheroid semi major axis (in metres starting with GDAL 3.0)
    5103             :  *
    5104             :  * This method does the same thing as the C function OSRGetSemiMajor().
    5105             :  *
    5106             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if semi major axis
    5107             :  * can be found.
    5108             :  *
    5109             :  * @return semi-major axis, or SRS_WGS84_SEMIMAJOR if it can't be found.
    5110             :  */
    5111             : 
    5112        5884 : double OGRSpatialReference::GetSemiMajor(OGRErr *pnErr) const
    5113             : 
    5114             : {
    5115       11768 :     TAKE_OPTIONAL_LOCK();
    5116             : 
    5117        5884 :     if (pnErr != nullptr)
    5118        3166 :         *pnErr = OGRERR_FAILURE;
    5119             : 
    5120        5884 :     d->refreshProjObj();
    5121        5884 :     if (!d->m_pj_crs)
    5122         111 :         return SRS_WGS84_SEMIMAJOR;
    5123             : 
    5124        5773 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5125        5773 :     if (!ellps)
    5126           5 :         return SRS_WGS84_SEMIMAJOR;
    5127             : 
    5128        5768 :     double dfSemiMajor = 0.0;
    5129        5768 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, &dfSemiMajor,
    5130             :                                   nullptr, nullptr, nullptr);
    5131        5768 :     proj_destroy(ellps);
    5132             : 
    5133        5768 :     if (dfSemiMajor > 0)
    5134             :     {
    5135        5768 :         if (pnErr != nullptr)
    5136        3052 :             *pnErr = OGRERR_NONE;
    5137        5768 :         return dfSemiMajor;
    5138             :     }
    5139             : 
    5140           0 :     return SRS_WGS84_SEMIMAJOR;
    5141             : }
    5142             : 
    5143             : /************************************************************************/
    5144             : /*                          OSRGetSemiMajor()                           */
    5145             : /************************************************************************/
    5146             : 
    5147             : /**
    5148             :  * \brief Get spheroid semi major axis.
    5149             :  *
    5150             :  * This function is the same as OGRSpatialReference::GetSemiMajor()
    5151             :  */
    5152          67 : double OSRGetSemiMajor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5153             : 
    5154             : {
    5155          67 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMajor", 0);
    5156             : 
    5157          67 :     return ToPointer(hSRS)->GetSemiMajor(pnErr);
    5158             : }
    5159             : 
    5160             : /************************************************************************/
    5161             : /*                          GetInvFlattening()                          */
    5162             : /************************************************************************/
    5163             : 
    5164             : /**
    5165             :  * \brief Get spheroid inverse flattening.
    5166             :  *
    5167             :  * This method does the same thing as the C function OSRGetInvFlattening().
    5168             :  *
    5169             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if no inverse flattening
    5170             :  * can be found.
    5171             :  *
    5172             :  * @return inverse flattening, or SRS_WGS84_INVFLATTENING if it can't be found.
    5173             :  */
    5174             : 
    5175        4137 : double OGRSpatialReference::GetInvFlattening(OGRErr *pnErr) const
    5176             : 
    5177             : {
    5178        8274 :     TAKE_OPTIONAL_LOCK();
    5179             : 
    5180        4137 :     if (pnErr != nullptr)
    5181        3084 :         *pnErr = OGRERR_FAILURE;
    5182             : 
    5183        4137 :     d->refreshProjObj();
    5184        4137 :     if (!d->m_pj_crs)
    5185         111 :         return SRS_WGS84_INVFLATTENING;
    5186             : 
    5187        4026 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5188        4026 :     if (!ellps)
    5189           2 :         return SRS_WGS84_INVFLATTENING;
    5190             : 
    5191        4024 :     double dfInvFlattening = -1.0;
    5192        4024 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, nullptr, nullptr,
    5193             :                                   nullptr, &dfInvFlattening);
    5194        4024 :     proj_destroy(ellps);
    5195             : 
    5196        4024 :     if (dfInvFlattening >= 0.0)
    5197             :     {
    5198        4024 :         if (pnErr != nullptr)
    5199        2973 :             *pnErr = OGRERR_NONE;
    5200        4024 :         return dfInvFlattening;
    5201             :     }
    5202             : 
    5203           0 :     return SRS_WGS84_INVFLATTENING;
    5204             : }
    5205             : 
    5206             : /************************************************************************/
    5207             : /*                        OSRGetInvFlattening()                         */
    5208             : /************************************************************************/
    5209             : 
    5210             : /**
    5211             :  * \brief Get spheroid inverse flattening.
    5212             :  *
    5213             :  * This function is the same as OGRSpatialReference::GetInvFlattening()
    5214             :  */
    5215          10 : double OSRGetInvFlattening(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5216             : 
    5217             : {
    5218          10 :     VALIDATE_POINTER1(hSRS, "OSRGetInvFlattening", 0);
    5219             : 
    5220          10 :     return ToPointer(hSRS)->GetInvFlattening(pnErr);
    5221             : }
    5222             : 
    5223             : /************************************************************************/
    5224             : /*                           GetEccentricity()                          */
    5225             : /************************************************************************/
    5226             : 
    5227             : /**
    5228             :  * \brief Get spheroid eccentricity
    5229             :  *
    5230             :  * @return eccentricity (or -1 in case of error)
    5231             :  * @since GDAL 2.3
    5232             :  */
    5233             : 
    5234           0 : double OGRSpatialReference::GetEccentricity() const
    5235             : 
    5236             : {
    5237           0 :     OGRErr eErr = OGRERR_NONE;
    5238           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5239           0 :     if (eErr != OGRERR_NONE)
    5240             :     {
    5241           0 :         return -1.0;
    5242             :     }
    5243           0 :     if (dfInvFlattening == 0.0)
    5244           0 :         return 0.0;
    5245           0 :     if (dfInvFlattening < 0.5)
    5246           0 :         return -1.0;
    5247           0 :     return sqrt(2.0 / dfInvFlattening -
    5248           0 :                 1.0 / (dfInvFlattening * dfInvFlattening));
    5249             : }
    5250             : 
    5251             : /************************************************************************/
    5252             : /*                      GetSquaredEccentricity()                        */
    5253             : /************************************************************************/
    5254             : 
    5255             : /**
    5256             :  * \brief Get spheroid squared eccentricity
    5257             :  *
    5258             :  * @return squared eccentricity (or -1 in case of error)
    5259             :  * @since GDAL 2.3
    5260             :  */
    5261             : 
    5262           0 : double OGRSpatialReference::GetSquaredEccentricity() const
    5263             : 
    5264             : {
    5265           0 :     OGRErr eErr = OGRERR_NONE;
    5266           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5267           0 :     if (eErr != OGRERR_NONE)
    5268             :     {
    5269           0 :         return -1.0;
    5270             :     }
    5271           0 :     if (dfInvFlattening == 0.0)
    5272           0 :         return 0.0;
    5273           0 :     if (dfInvFlattening < 0.5)
    5274           0 :         return -1.0;
    5275           0 :     return 2.0 / dfInvFlattening - 1.0 / (dfInvFlattening * dfInvFlattening);
    5276             : }
    5277             : 
    5278             : /************************************************************************/
    5279             : /*                            GetSemiMinor()                            */
    5280             : /************************************************************************/
    5281             : 
    5282             : /**
    5283             :  * \brief Get spheroid semi minor axis.
    5284             :  *
    5285             :  * This method does the same thing as the C function OSRGetSemiMinor().
    5286             :  *
    5287             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if semi minor axis
    5288             :  * can be found.
    5289             :  *
    5290             :  * @return semi-minor axis, or WGS84 semi minor if it can't be found.
    5291             :  */
    5292             : 
    5293         637 : double OGRSpatialReference::GetSemiMinor(OGRErr *pnErr) const
    5294             : 
    5295             : {
    5296         637 :     const double dfSemiMajor = GetSemiMajor(pnErr);
    5297         637 :     const double dfInvFlattening = GetInvFlattening(pnErr);
    5298             : 
    5299         637 :     return OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
    5300             : }
    5301             : 
    5302             : /************************************************************************/
    5303             : /*                          OSRGetSemiMinor()                           */
    5304             : /************************************************************************/
    5305             : 
    5306             : /**
    5307             :  * \brief Get spheroid semi minor axis.
    5308             :  *
    5309             :  * This function is the same as OGRSpatialReference::GetSemiMinor()
    5310             :  */
    5311           4 : double OSRGetSemiMinor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5312             : 
    5313             : {
    5314           4 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMinor", 0);
    5315             : 
    5316           4 :     return ToPointer(hSRS)->GetSemiMinor(pnErr);
    5317             : }
    5318             : 
    5319             : /************************************************************************/
    5320             : /*                             SetLocalCS()                             */
    5321             : /************************************************************************/
    5322             : 
    5323             : /**
    5324             :  * \brief Set the user visible LOCAL_CS name.
    5325             :  *
    5326             :  * This method is the same as the C function OSRSetLocalCS().
    5327             :  *
    5328             :  * This method will ensure a LOCAL_CS node is created as the root,
    5329             :  * and set the provided name on it.  It must be used before SetLinearUnits().
    5330             :  *
    5331             :  * @param pszName the user visible name to assign.  Not used as a key.
    5332             :  *
    5333             :  * @return OGRERR_NONE on success.
    5334             :  */
    5335             : 
    5336        2877 : OGRErr OGRSpatialReference::SetLocalCS(const char *pszName)
    5337             : 
    5338             : {
    5339        5754 :     TAKE_OPTIONAL_LOCK();
    5340             : 
    5341        2877 :     if (d->m_pjType == PJ_TYPE_UNKNOWN ||
    5342           0 :         d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    5343             :     {
    5344        2877 :         d->setPjCRS(proj_create_engineering_crs(d->getPROJContext(), pszName));
    5345             :     }
    5346             :     else
    5347             :     {
    5348           0 :         CPLDebug("OGR",
    5349             :                  "OGRSpatialReference::SetLocalCS(%s) failed.  "
    5350             :                  "It appears an incompatible object already exists.",
    5351             :                  pszName);
    5352           0 :         return OGRERR_FAILURE;
    5353             :     }
    5354             : 
    5355        2877 :     return OGRERR_NONE;
    5356             : }
    5357             : 
    5358             : /************************************************************************/
    5359             : /*                           OSRSetLocalCS()                            */
    5360             : /************************************************************************/
    5361             : 
    5362             : /**
    5363             :  * \brief Set the user visible LOCAL_CS name.
    5364             :  *
    5365             :  * This function is the same as OGRSpatialReference::SetLocalCS()
    5366             :  */
    5367           1 : OGRErr OSRSetLocalCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5368             : 
    5369             : {
    5370           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLocalCS", OGRERR_FAILURE);
    5371             : 
    5372           1 :     return ToPointer(hSRS)->SetLocalCS(pszName);
    5373             : }
    5374             : 
    5375             : /************************************************************************/
    5376             : /*                             SetGeocCS()                              */
    5377             : /************************************************************************/
    5378             : 
    5379             : /**
    5380             :  * \brief Set the user visible GEOCCS name.
    5381             :  *
    5382             :  * This method is the same as the C function OSRSetGeocCS().
    5383             : 
    5384             :  * This method will ensure a GEOCCS node is created as the root,
    5385             :  * and set the provided name on it.  If used on a GEOGCS coordinate system,
    5386             :  * the DATUM and PRIMEM nodes from the GEOGCS will be transferred over to
    5387             :  * the GEOGCS.
    5388             :  *
    5389             :  * @param pszName the user visible name to assign.  Not used as a key.
    5390             :  *
    5391             :  * @return OGRERR_NONE on success.
    5392             :  *
    5393             :  * @since OGR 1.9.0
    5394             :  */
    5395             : 
    5396           6 : OGRErr OGRSpatialReference::SetGeocCS(const char *pszName)
    5397             : 
    5398             : {
    5399          12 :     TAKE_OPTIONAL_LOCK();
    5400             : 
    5401           6 :     OGRErr eErr = OGRERR_NONE;
    5402           6 :     d->refreshProjObj();
    5403           6 :     d->demoteFromBoundCRS();
    5404           6 :     if (d->m_pjType == PJ_TYPE_UNKNOWN)
    5405             :     {
    5406           3 :         d->setPjCRS(proj_create_geocentric_crs(
    5407             :             d->getPROJContext(), pszName, "World Geodetic System 1984",
    5408             :             "WGS 84", SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING,
    5409             :             SRS_PM_GREENWICH, 0.0, SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV),
    5410             :             "Metre", 1.0));
    5411             :     }
    5412           3 :     else if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    5413             :     {
    5414           1 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5415             :     }
    5416           3 :     else if (d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    5417           1 :              d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    5418             :     {
    5419           1 :         auto datum = proj_crs_get_datum(d->getPROJContext(), d->m_pj_crs);
    5420             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    5421             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    5422             :         if (datum == nullptr)
    5423             :         {
    5424             :             datum =
    5425             :                 proj_crs_get_datum_ensemble(d->getPROJContext(), d->m_pj_crs);
    5426             :         }
    5427             : #endif
    5428           1 :         if (datum == nullptr)
    5429             :         {
    5430           0 :             d->undoDemoteFromBoundCRS();
    5431           0 :             return OGRERR_FAILURE;
    5432             :         }
    5433             : 
    5434           1 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    5435           1 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, nullptr,
    5436             :             0.0);
    5437           1 :         d->setPjCRS(pj_crs);
    5438             : 
    5439           1 :         proj_destroy(datum);
    5440             :     }
    5441             :     else
    5442             :     {
    5443           1 :         CPLDebug("OGR",
    5444             :                  "OGRSpatialReference::SetGeocCS(%s) failed.  "
    5445             :                  "It appears an incompatible object already exists.",
    5446             :                  pszName);
    5447           1 :         eErr = OGRERR_FAILURE;
    5448             :     }
    5449           6 :     d->undoDemoteFromBoundCRS();
    5450             : 
    5451           6 :     return eErr;
    5452             : }
    5453             : 
    5454             : /************************************************************************/
    5455             : /*                            OSRSetGeocCS()                            */
    5456             : /************************************************************************/
    5457             : 
    5458             : /**
    5459             :  * \brief Set the user visible PROJCS name.
    5460             :  *
    5461             :  * This function is the same as OGRSpatialReference::SetGeocCS()
    5462             :  *
    5463             :  * @since OGR 1.9.0
    5464             :  */
    5465           4 : OGRErr OSRSetGeocCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5466             : 
    5467             : {
    5468           4 :     VALIDATE_POINTER1(hSRS, "OSRSetGeocCS", OGRERR_FAILURE);
    5469             : 
    5470           4 :     return ToPointer(hSRS)->SetGeocCS(pszName);
    5471             : }
    5472             : 
    5473             : /************************************************************************/
    5474             : /*                             SetVertCS()                              */
    5475             : /************************************************************************/
    5476             : 
    5477             : /**
    5478             :  * \brief Set the user visible VERT_CS name.
    5479             :  *
    5480             :  * This method is the same as the C function OSRSetVertCS().
    5481             : 
    5482             :  * This method will ensure a VERT_CS node is created if needed.  If the
    5483             :  * existing coordinate system is GEOGCS or PROJCS rooted, then it will be
    5484             :  * turned into a COMPD_CS.
    5485             :  *
    5486             :  * @param pszVertCSName the user visible name of the vertical coordinate
    5487             :  * system. Not used as a key.
    5488             :  *
    5489             :  * @param pszVertDatumName the user visible name of the vertical datum.  It
    5490             :  * is helpful if this matches the EPSG name.
    5491             :  *
    5492             :  * @param nVertDatumType the OGC vertical datum type. Ignored
    5493             :  *
    5494             :  * @return OGRERR_NONE on success.
    5495             :  *
    5496             :  * @since OGR 1.9.0
    5497             :  */
    5498             : 
    5499           1 : OGRErr OGRSpatialReference::SetVertCS(const char *pszVertCSName,
    5500             :                                       const char *pszVertDatumName,
    5501             :                                       int nVertDatumType)
    5502             : 
    5503             : {
    5504           1 :     TAKE_OPTIONAL_LOCK();
    5505             : 
    5506           1 :     CPL_IGNORE_RET_VAL(nVertDatumType);
    5507             : 
    5508           1 :     d->refreshProjObj();
    5509             : 
    5510           1 :     auto vertCRS = proj_create_vertical_crs(d->getPROJContext(), pszVertCSName,
    5511             :                                             pszVertDatumName, nullptr, 0.0);
    5512             : 
    5513             :     /* -------------------------------------------------------------------- */
    5514             :     /*      Handle the case where we want to make a compound coordinate     */
    5515             :     /*      system.                                                         */
    5516             :     /* -------------------------------------------------------------------- */
    5517           1 :     if (IsProjected() || IsGeographic())
    5518             :     {
    5519           1 :         auto compoundCRS = proj_create_compound_crs(
    5520           1 :             d->getPROJContext(), nullptr, d->m_pj_crs, vertCRS);
    5521           1 :         proj_destroy(vertCRS);
    5522           1 :         d->setPjCRS(compoundCRS);
    5523             :     }
    5524             :     else
    5525             :     {
    5526           0 :         d->setPjCRS(vertCRS);
    5527             :     }
    5528           2 :     return OGRERR_NONE;
    5529             : }
    5530             : 
    5531             : /************************************************************************/
    5532             : /*                            OSRSetVertCS()                            */
    5533             : /************************************************************************/
    5534             : 
    5535             : /**
    5536             :  * \brief Setup the vertical coordinate system.
    5537             :  *
    5538             :  * This function is the same as OGRSpatialReference::SetVertCS()
    5539             :  *
    5540             :  * @since OGR 1.9.0
    5541             :  */
    5542           0 : OGRErr OSRSetVertCS(OGRSpatialReferenceH hSRS, const char *pszVertCSName,
    5543             :                     const char *pszVertDatumName, int nVertDatumType)
    5544             : 
    5545             : {
    5546           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVertCS", OGRERR_FAILURE);
    5547             : 
    5548           0 :     return ToPointer(hSRS)->SetVertCS(pszVertCSName, pszVertDatumName,
    5549           0 :                                       nVertDatumType);
    5550             : }
    5551             : 
    5552             : /************************************************************************/
    5553             : /*                           SetCompoundCS()                            */
    5554             : /************************************************************************/
    5555             : 
    5556             : /**
    5557             :  * \brief Setup a compound coordinate system.
    5558             :  *
    5559             :  * This method is the same as the C function OSRSetCompoundCS().
    5560             : 
    5561             :  * This method is replace the current SRS with a COMPD_CS coordinate system
    5562             :  * consisting of the passed in horizontal and vertical coordinate systems.
    5563             :  *
    5564             :  * @param pszName the name of the compound coordinate system.
    5565             :  *
    5566             :  * @param poHorizSRS the horizontal SRS (PROJCS or GEOGCS).
    5567             :  *
    5568             :  * @param poVertSRS the vertical SRS (VERT_CS).
    5569             :  *
    5570             :  * @return OGRERR_NONE on success.
    5571             :  */
    5572             : 
    5573          92 : OGRErr OGRSpatialReference::SetCompoundCS(const char *pszName,
    5574             :                                           const OGRSpatialReference *poHorizSRS,
    5575             :                                           const OGRSpatialReference *poVertSRS)
    5576             : 
    5577             : {
    5578         184 :     TAKE_OPTIONAL_LOCK();
    5579             : 
    5580             :     /* -------------------------------------------------------------------- */
    5581             :     /*      Verify these are legal horizontal and vertical coordinate       */
    5582             :     /*      systems.                                                        */
    5583             :     /* -------------------------------------------------------------------- */
    5584          92 :     if (!poVertSRS->IsVertical())
    5585             :     {
    5586           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5587             :                  "SetCompoundCS() fails, vertical component is not VERT_CS.");
    5588           0 :         return OGRERR_FAILURE;
    5589             :     }
    5590          92 :     if (!poHorizSRS->IsProjected() && !poHorizSRS->IsGeographic())
    5591             :     {
    5592           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5593             :                  "SetCompoundCS() fails, horizontal component is not PROJCS or "
    5594             :                  "GEOGCS.");
    5595           0 :         return OGRERR_FAILURE;
    5596             :     }
    5597             : 
    5598             :     /* -------------------------------------------------------------------- */
    5599             :     /*      Replace with compound srs.                                      */
    5600             :     /* -------------------------------------------------------------------- */
    5601          92 :     Clear();
    5602             : 
    5603          92 :     auto compoundCRS = proj_create_compound_crs(d->getPROJContext(), pszName,
    5604          92 :                                                 poHorizSRS->d->m_pj_crs,
    5605          92 :                                                 poVertSRS->d->m_pj_crs);
    5606          92 :     d->setPjCRS(compoundCRS);
    5607             : 
    5608          92 :     return OGRERR_NONE;
    5609             : }
    5610             : 
    5611             : /************************************************************************/
    5612             : /*                          OSRSetCompoundCS()                          */
    5613             : /************************************************************************/
    5614             : 
    5615             : /**
    5616             :  * \brief Setup a compound coordinate system.
    5617             :  *
    5618             :  * This function is the same as OGRSpatialReference::SetCompoundCS()
    5619             :  */
    5620           8 : OGRErr OSRSetCompoundCS(OGRSpatialReferenceH hSRS, const char *pszName,
    5621             :                         OGRSpatialReferenceH hHorizSRS,
    5622             :                         OGRSpatialReferenceH hVertSRS)
    5623             : 
    5624             : {
    5625           8 :     VALIDATE_POINTER1(hSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5626           8 :     VALIDATE_POINTER1(hHorizSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5627           8 :     VALIDATE_POINTER1(hVertSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5628             : 
    5629          16 :     return ToPointer(hSRS)->SetCompoundCS(pszName, ToPointer(hHorizSRS),
    5630          16 :                                           ToPointer(hVertSRS));
    5631             : }
    5632             : 
    5633             : /************************************************************************/
    5634             : /*                             SetProjCS()                              */
    5635             : /************************************************************************/
    5636             : 
    5637             : /**
    5638             :  * \brief Set the user visible PROJCS name.
    5639             :  *
    5640             :  * This method is the same as the C function OSRSetProjCS().
    5641             :  *
    5642             :  * This method will ensure a PROJCS node is created as the root,
    5643             :  * and set the provided name on it.  If used on a GEOGCS coordinate system,
    5644             :  * the GEOGCS node will be demoted to be a child of the new PROJCS root.
    5645             :  *
    5646             :  * @param pszName the user visible name to assign.  Not used as a key.
    5647             :  *
    5648             :  * @return OGRERR_NONE on success.
    5649             :  */
    5650             : 
    5651        4025 : OGRErr OGRSpatialReference::SetProjCS(const char *pszName)
    5652             : 
    5653             : {
    5654        4025 :     TAKE_OPTIONAL_LOCK();
    5655             : 
    5656        4025 :     d->refreshProjObj();
    5657        4025 :     d->demoteFromBoundCRS();
    5658        4025 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    5659             :     {
    5660         476 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5661             :     }
    5662             :     else
    5663             :     {
    5664        3549 :         auto dummyConv = proj_create_conversion(d->getPROJContext(), nullptr,
    5665             :                                                 nullptr, nullptr, nullptr,
    5666             :                                                 nullptr, nullptr, 0, nullptr);
    5667        3549 :         auto cs = proj_create_cartesian_2D_cs(
    5668             :             d->getPROJContext(), PJ_CART2D_EASTING_NORTHING, nullptr, 0);
    5669             : 
    5670        3549 :         auto projCRS = proj_create_projected_crs(
    5671        3549 :             d->getPROJContext(), pszName, d->getGeodBaseCRS(), dummyConv, cs);
    5672        3549 :         proj_destroy(dummyConv);
    5673        3549 :         proj_destroy(cs);
    5674             : 
    5675        3549 :         d->setPjCRS(projCRS);
    5676             :     }
    5677        4025 :     d->undoDemoteFromBoundCRS();
    5678        8050 :     return OGRERR_NONE;
    5679             : }
    5680             : 
    5681             : /************************************************************************/
    5682             : /*                            OSRSetProjCS()                            */
    5683             : /************************************************************************/
    5684             : 
    5685             : /**
    5686             :  * \brief Set the user visible PROJCS name.
    5687             :  *
    5688             :  * This function is the same as OGRSpatialReference::SetProjCS()
    5689             :  */
    5690           1 : OGRErr OSRSetProjCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5691             : 
    5692             : {
    5693           1 :     VALIDATE_POINTER1(hSRS, "OSRSetProjCS", OGRERR_FAILURE);
    5694             : 
    5695           1 :     return ToPointer(hSRS)->SetProjCS(pszName);
    5696             : }
    5697             : 
    5698             : /************************************************************************/
    5699             : /*                           SetProjection()                            */
    5700             : /************************************************************************/
    5701             : 
    5702             : /**
    5703             :  * \brief Set a projection name.
    5704             :  *
    5705             :  * This method is the same as the C function OSRSetProjection().
    5706             :  *
    5707             :  * @param pszProjection the projection name, which should be selected from
    5708             :  * the macros in ogr_srs_api.h, such as SRS_PT_TRANSVERSE_MERCATOR.
    5709             :  *
    5710             :  * @return OGRERR_NONE on success.
    5711             :  */
    5712             : 
    5713          23 : OGRErr OGRSpatialReference::SetProjection(const char *pszProjection)
    5714             : 
    5715             : {
    5716          46 :     TAKE_OPTIONAL_LOCK();
    5717             : 
    5718          23 :     OGR_SRSNode *poGeogCS = nullptr;
    5719             : 
    5720          23 :     if (GetRoot() != nullptr && EQUAL(d->m_poRoot->GetValue(), "GEOGCS"))
    5721             :     {
    5722           4 :         poGeogCS = d->m_poRoot;
    5723           4 :         d->m_poRoot = nullptr;
    5724             :     }
    5725             : 
    5726          23 :     if (!GetAttrNode("PROJCS"))
    5727             :     {
    5728          11 :         SetNode("PROJCS", "unnamed");
    5729             :     }
    5730             : 
    5731          23 :     const OGRErr eErr = SetNode("PROJCS|PROJECTION", pszProjection);
    5732          23 :     if (eErr != OGRERR_NONE)
    5733           0 :         return eErr;
    5734             : 
    5735          23 :     if (poGeogCS != nullptr)
    5736           4 :         d->m_poRoot->InsertChild(poGeogCS, 1);
    5737             : 
    5738          23 :     return OGRERR_NONE;
    5739             : }
    5740             : 
    5741             : /************************************************************************/
    5742             : /*                            OSRSetProjection()                        */
    5743             : /************************************************************************/
    5744             : 
    5745             : /**
    5746             :  * \brief Set a projection name.
    5747             :  *
    5748             :  * This function is the same as OGRSpatialReference::SetProjection()
    5749             :  */
    5750           0 : OGRErr OSRSetProjection(OGRSpatialReferenceH hSRS, const char *pszProjection)
    5751             : 
    5752             : {
    5753           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjection", OGRERR_FAILURE);
    5754             : 
    5755           0 :     return ToPointer(hSRS)->SetProjection(pszProjection);
    5756             : }
    5757             : 
    5758             : /************************************************************************/
    5759             : /*                      GetWKT2ProjectionMethod()                       */
    5760             : /************************************************************************/
    5761             : 
    5762             : /**
    5763             :  * \brief Returns info on the projection method, based on WKT2 naming
    5764             :  * conventions.
    5765             :  *
    5766             :  * The returned strings are short lived and should be considered to be
    5767             :  * invalidated by any further call to the GDAL API.
    5768             :  *
    5769             :  * @param[out] ppszMethodName Pointer to a string that will receive the
    5770             :  * projection method name.
    5771             :  * @param[out] ppszMethodAuthName null pointer, or pointer to a string that will
    5772             :  * receive the name of the authority that defines the projection method.
    5773             :  * *ppszMethodAuthName may be nullptr if the projection method is not linked to
    5774             :  * an authority.
    5775             :  * @param[out] ppszMethodCode null pointer, or pointer to a string that will
    5776             :  * receive the code that defines the projection method.
    5777             :  * *ppszMethodCode may be nullptr if the projection method is not linked to
    5778             :  * an authority.
    5779             :  *
    5780             :  * @return OGRERR_NONE on success.
    5781             :  */
    5782             : OGRErr
    5783           1 : OGRSpatialReference::GetWKT2ProjectionMethod(const char **ppszMethodName,
    5784             :                                              const char **ppszMethodAuthName,
    5785             :                                              const char **ppszMethodCode) const
    5786             : {
    5787           2 :     TAKE_OPTIONAL_LOCK();
    5788             : 
    5789           1 :     auto conv = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    5790           1 :     if (!conv)
    5791           0 :         return OGRERR_FAILURE;
    5792           1 :     const char *pszTmpMethodName = "";
    5793           1 :     const char *pszTmpMethodAuthName = "";
    5794           1 :     const char *pszTmpMethodCode = "";
    5795           1 :     int ret = proj_coordoperation_get_method_info(
    5796             :         d->getPROJContext(), conv, &pszTmpMethodName, &pszTmpMethodAuthName,
    5797             :         &pszTmpMethodCode);
    5798             :     // "Internalize" temporary strings returned by PROJ
    5799           1 :     CPLAssert(pszTmpMethodName);
    5800           1 :     if (ppszMethodName)
    5801           1 :         *ppszMethodName = CPLSPrintf("%s", pszTmpMethodName);
    5802           1 :     if (ppszMethodAuthName)
    5803           0 :         *ppszMethodAuthName = pszTmpMethodAuthName
    5804           0 :                                   ? CPLSPrintf("%s", pszTmpMethodAuthName)
    5805           0 :                                   : nullptr;
    5806           1 :     if (ppszMethodCode)
    5807           0 :         *ppszMethodCode =
    5808           0 :             pszTmpMethodCode ? CPLSPrintf("%s", pszTmpMethodCode) : nullptr;
    5809           1 :     proj_destroy(conv);
    5810           1 :     return ret ? OGRERR_NONE : OGRERR_FAILURE;
    5811             : }
    5812             : 
    5813             : /************************************************************************/
    5814             : /*                            SetProjParm()                             */
    5815             : /************************************************************************/
    5816             : 
    5817             : /**
    5818             :  * \brief Set a projection parameter value.
    5819             :  *
    5820             :  * Adds a new PARAMETER under the PROJCS with the indicated name and value.
    5821             :  *
    5822             :  * This method is the same as the C function OSRSetProjParm().
    5823             :  *
    5824             :  * Please check https://gdal.org/proj_list pages for
    5825             :  * legal parameter names for specific projections.
    5826             :  *
    5827             :  *
    5828             :  * @param pszParamName the parameter name, which should be selected from
    5829             :  * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
    5830             :  *
    5831             :  * @param dfValue value to assign.
    5832             :  *
    5833             :  * @return OGRERR_NONE on success.
    5834             :  */
    5835             : 
    5836         129 : OGRErr OGRSpatialReference::SetProjParm(const char *pszParamName,
    5837             :                                         double dfValue)
    5838             : 
    5839             : {
    5840         258 :     TAKE_OPTIONAL_LOCK();
    5841             : 
    5842         129 :     OGR_SRSNode *poPROJCS = GetAttrNode("PROJCS");
    5843             : 
    5844         129 :     if (poPROJCS == nullptr)
    5845           3 :         return OGRERR_FAILURE;
    5846             : 
    5847         126 :     char szValue[64] = {'\0'};
    5848         126 :     OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    5849             : 
    5850             :     /* -------------------------------------------------------------------- */
    5851             :     /*      Try to find existing parameter with this name.                  */
    5852             :     /* -------------------------------------------------------------------- */
    5853        1030 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    5854             :     {
    5855         943 :         OGR_SRSNode *poParam = poPROJCS->GetChild(iChild);
    5856             : 
    5857        1242 :         if (EQUAL(poParam->GetValue(), "PARAMETER") &&
    5858        1242 :             poParam->GetChildCount() == 2 &&
    5859         299 :             EQUAL(poParam->GetChild(0)->GetValue(), pszParamName))
    5860             :         {
    5861          39 :             poParam->GetChild(1)->SetValue(szValue);
    5862          39 :             return OGRERR_NONE;
    5863             :         }
    5864             :     }
    5865             : 
    5866             :     /* -------------------------------------------------------------------- */
    5867             :     /*      Otherwise create a new parameter and append.                    */
    5868             :     /* -------------------------------------------------------------------- */
    5869          87 :     OGR_SRSNode *poParam = new OGR_SRSNode("PARAMETER");
    5870          87 :     poParam->AddChild(new OGR_SRSNode(pszParamName));
    5871          87 :     poParam->AddChild(new OGR_SRSNode(szValue));
    5872             : 
    5873          87 :     poPROJCS->AddChild(poParam);
    5874             : 
    5875          87 :     return OGRERR_NONE;
    5876             : }
    5877             : 
    5878             : /************************************************************************/
    5879             : /*                           OSRSetProjParm()                           */
    5880             : /************************************************************************/
    5881             : 
    5882             : /**
    5883             :  * \brief Set a projection parameter value.
    5884             :  *
    5885             :  * This function is the same as OGRSpatialReference::SetProjParm()
    5886             :  */
    5887           0 : OGRErr OSRSetProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    5888             :                       double dfValue)
    5889             : 
    5890             : {
    5891           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjParm", OGRERR_FAILURE);
    5892             : 
    5893           0 :     return ToPointer(hSRS)->SetProjParm(pszParamName, dfValue);
    5894             : }
    5895             : 
    5896             : /************************************************************************/
    5897             : /*                            FindProjParm()                            */
    5898             : /************************************************************************/
    5899             : 
    5900             : /**
    5901             :  * \brief Return the child index of the named projection parameter on
    5902             :  * its parent PROJCS node.
    5903             :  *
    5904             :  * @param pszParameter projection parameter to look for
    5905             :  * @param poPROJCS projection CS node to look in. If NULL is passed,
    5906             :  *        the PROJCS node of the SpatialReference object will be searched.
    5907             :  *
    5908             :  * @return the child index of the named projection parameter. -1 on failure
    5909             :  */
    5910        4391 : int OGRSpatialReference::FindProjParm(const char *pszParameter,
    5911             :                                       const OGR_SRSNode *poPROJCS) const
    5912             : 
    5913             : {
    5914        8782 :     TAKE_OPTIONAL_LOCK();
    5915             : 
    5916        4391 :     if (poPROJCS == nullptr)
    5917           0 :         poPROJCS = GetAttrNode("PROJCS");
    5918             : 
    5919        4391 :     if (poPROJCS == nullptr)
    5920           0 :         return -1;
    5921             : 
    5922             :     /* -------------------------------------------------------------------- */
    5923             :     /*      Search for requested parameter.                                 */
    5924             :     /* -------------------------------------------------------------------- */
    5925        4391 :     bool bIsWKT2 = false;
    5926       28028 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    5927             :     {
    5928       27605 :         const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    5929             : 
    5930       27605 :         if (poParameter->GetChildCount() >= 2)
    5931             :         {
    5932       18864 :             const char *pszValue = poParameter->GetValue();
    5933       31680 :             if (EQUAL(pszValue, "PARAMETER") &&
    5934       12816 :                 EQUAL(poPROJCS->GetChild(iChild)->GetChild(0)->GetValue(),
    5935             :                       pszParameter))
    5936             :             {
    5937        3968 :                 return iChild;
    5938             :             }
    5939       14896 :             else if (EQUAL(pszValue, "METHOD"))
    5940             :             {
    5941          41 :                 bIsWKT2 = true;
    5942             :             }
    5943             :         }
    5944             :     }
    5945             : 
    5946             :     /* -------------------------------------------------------------------- */
    5947             :     /*      Try similar names, for selected parameters.                     */
    5948             :     /* -------------------------------------------------------------------- */
    5949         423 :     if (EQUAL(pszParameter, SRS_PP_LATITUDE_OF_ORIGIN))
    5950             :     {
    5951         190 :         if (bIsWKT2)
    5952             :         {
    5953           8 :             int iChild = FindProjParm(
    5954             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    5955           8 :             if (iChild == -1)
    5956           3 :                 iChild = FindProjParm(
    5957             :                     EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, poPROJCS);
    5958           8 :             return iChild;
    5959             :         }
    5960         182 :         return FindProjParm(SRS_PP_LATITUDE_OF_CENTER, poPROJCS);
    5961             :     }
    5962             : 
    5963         233 :     if (EQUAL(pszParameter, SRS_PP_CENTRAL_MERIDIAN))
    5964             :     {
    5965          32 :         if (bIsWKT2)
    5966             :         {
    5967           9 :             int iChild = FindProjParm(
    5968             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    5969           9 :             if (iChild == -1)
    5970           0 :                 iChild = FindProjParm(
    5971             :                     EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, poPROJCS);
    5972           9 :             return iChild;
    5973             :         }
    5974          23 :         int iChild = FindProjParm(SRS_PP_LONGITUDE_OF_CENTER, poPROJCS);
    5975          23 :         if (iChild == -1)
    5976           0 :             iChild = FindProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, poPROJCS);
    5977          23 :         return iChild;
    5978             :     }
    5979             : 
    5980         201 :     return -1;
    5981             : }
    5982             : 
    5983             : /************************************************************************/
    5984             : /*                            GetProjParm()                             */
    5985             : /************************************************************************/
    5986             : 
    5987             : /**
    5988             :  * \brief Fetch a projection parameter value.
    5989             :  *
    5990             :  * NOTE: This code should be modified to translate non degree angles into
    5991             :  * degrees based on the GEOGCS unit.  This has not yet been done.
    5992             :  *
    5993             :  * This method is the same as the C function OSRGetProjParm().
    5994             :  *
    5995             :  * @param pszName the name of the parameter to fetch, from the set of
    5996             :  * SRS_PP codes in ogr_srs_api.h.
    5997             :  *
    5998             :  * @param dfDefaultValue the value to return if this parameter doesn't exist.
    5999             :  *
    6000             :  * @param pnErr place to put error code on failure.  Ignored if NULL.
    6001             :  *
    6002             :  * @return value of parameter.
    6003             :  */
    6004             : 
    6005        4416 : double OGRSpatialReference::GetProjParm(const char *pszName,
    6006             :                                         double dfDefaultValue,
    6007             :                                         OGRErr *pnErr) const
    6008             : 
    6009             : {
    6010        8832 :     TAKE_OPTIONAL_LOCK();
    6011             : 
    6012        4416 :     d->refreshProjObj();
    6013        4416 :     GetRoot();  // force update of d->m_bNodesWKT2
    6014             : 
    6015        4416 :     if (pnErr != nullptr)
    6016        3450 :         *pnErr = OGRERR_NONE;
    6017             : 
    6018             :     /* -------------------------------------------------------------------- */
    6019             :     /*      Find the desired parameter.                                     */
    6020             :     /* -------------------------------------------------------------------- */
    6021             :     const OGR_SRSNode *poPROJCS =
    6022        4416 :         GetAttrNode(d->m_bNodesWKT2 ? "CONVERSION" : "PROJCS");
    6023        4416 :     if (poPROJCS == nullptr)
    6024             :     {
    6025         250 :         if (pnErr != nullptr)
    6026         250 :             *pnErr = OGRERR_FAILURE;
    6027         250 :         return dfDefaultValue;
    6028             :     }
    6029             : 
    6030        4166 :     const int iChild = FindProjParm(pszName, poPROJCS);
    6031        4166 :     if (iChild == -1)
    6032             :     {
    6033         198 :         if (IsProjected() && GetAxesCount() == 3)
    6034             :         {
    6035           3 :             OGRSpatialReference *poSRSTmp = Clone();
    6036           3 :             poSRSTmp->DemoteTo2D(nullptr);
    6037             :             const double dfRet =
    6038           3 :                 poSRSTmp->GetProjParm(pszName, dfDefaultValue, pnErr);
    6039           3 :             delete poSRSTmp;
    6040           3 :             return dfRet;
    6041             :         }
    6042             : 
    6043         195 :         if (pnErr != nullptr)
    6044         173 :             *pnErr = OGRERR_FAILURE;
    6045         195 :         return dfDefaultValue;
    6046             :     }
    6047             : 
    6048        3968 :     const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    6049        3968 :     return CPLAtof(poParameter->GetChild(1)->GetValue());
    6050             : }
    6051             : 
    6052             : /************************************************************************/
    6053             : /*                           OSRGetProjParm()                           */
    6054             : /************************************************************************/
    6055             : 
    6056             : /**
    6057             :  * \brief Fetch a projection parameter value.
    6058             :  *
    6059             :  * This function is the same as OGRSpatialReference::GetProjParm()
    6060             :  */
    6061          90 : double OSRGetProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6062             :                       double dfDefaultValue, OGRErr *pnErr)
    6063             : 
    6064             : {
    6065          90 :     VALIDATE_POINTER1(hSRS, "OSRGetProjParm", 0);
    6066             : 
    6067          90 :     return ToPointer(hSRS)->GetProjParm(pszName, dfDefaultValue, pnErr);
    6068             : }
    6069             : 
    6070             : /************************************************************************/
    6071             : /*                          GetNormProjParm()                           */
    6072             : /************************************************************************/
    6073             : 
    6074             : /**
    6075             :  * \brief Fetch a normalized projection parameter value.
    6076             :  *
    6077             :  * This method is the same as GetProjParm() except that the value of
    6078             :  * the parameter is "normalized" into degrees or meters depending on
    6079             :  * whether it is linear or angular.
    6080             :  *
    6081             :  * This method is the same as the C function OSRGetNormProjParm().
    6082             :  *
    6083             :  * @param pszName the name of the parameter to fetch, from the set of
    6084             :  * SRS_PP codes in ogr_srs_api.h.
    6085             :  *
    6086             :  * @param dfDefaultValue the value to return if this parameter doesn't exist.
    6087             :  *
    6088             :  * @param pnErr place to put error code on failure.  Ignored if NULL.
    6089             :  *
    6090             :  * @return value of parameter.
    6091             :  */
    6092             : 
    6093        3425 : double OGRSpatialReference::GetNormProjParm(const char *pszName,
    6094             :                                             double dfDefaultValue,
    6095             :                                             OGRErr *pnErr) const
    6096             : 
    6097             : {
    6098        6850 :     TAKE_OPTIONAL_LOCK();
    6099             : 
    6100        3425 :     GetNormInfo();
    6101             : 
    6102        3425 :     OGRErr nError = OGRERR_NONE;
    6103        3425 :     double dfRawResult = GetProjParm(pszName, dfDefaultValue, &nError);
    6104        3425 :     if (pnErr != nullptr)
    6105           0 :         *pnErr = nError;
    6106             : 
    6107             :     // If we got the default just return it unadjusted.
    6108        3425 :     if (nError != OGRERR_NONE)
    6109         423 :         return dfRawResult;
    6110             : 
    6111        3002 :     if (d->dfToDegrees != 1.0 && IsAngularParameter(pszName))
    6112           8 :         dfRawResult *= d->dfToDegrees;
    6113             : 
    6114        3002 :     if (d->dfToMeter != 1.0 && IsLinearParameter(pszName))
    6115           5 :         return dfRawResult * d->dfToMeter;
    6116             : 
    6117        2997 :     return dfRawResult;
    6118             : }
    6119             : 
    6120             : /************************************************************************/
    6121             : /*                         OSRGetNormProjParm()                         */
    6122             : /************************************************************************/
    6123             : 
    6124             : /**
    6125             :  * \brief This function is the same as OGRSpatialReference::
    6126             :  *
    6127             :  * This function is the same as OGRSpatialReference::GetNormProjParm()
    6128             :  */
    6129           1 : double OSRGetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6130             :                           double dfDefaultValue, OGRErr *pnErr)
    6131             : 
    6132             : {
    6133           1 :     VALIDATE_POINTER1(hSRS, "OSRGetNormProjParm", 0);
    6134             : 
    6135           1 :     return ToPointer(hSRS)->GetNormProjParm(pszName, dfDefaultValue, pnErr);
    6136             : }
    6137             : 
    6138             : /************************************************************************/
    6139             : /*                          SetNormProjParm()                           */
    6140             : /************************************************************************/
    6141             : 
    6142             : /**
    6143             :  * \brief Set a projection parameter with a normalized value.
    6144             :  *
    6145             :  * This method is the same as SetProjParm() except that the value of
    6146             :  * the parameter passed in is assumed to be in "normalized" form (decimal
    6147             :  * degrees for angular values, meters for linear values.  The values are
    6148             :  * converted in a form suitable for the GEOGCS and linear units in effect.
    6149             :  *
    6150             :  * This method is the same as the C function OSRSetNormProjParm().
    6151             :  *
    6152             :  * @param pszName the parameter name, which should be selected from
    6153             :  * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
    6154             :  *
    6155             :  * @param dfValue value to assign.
    6156             :  *
    6157             :  * @return OGRERR_NONE on success.
    6158             :  */
    6159             : 
    6160          91 : OGRErr OGRSpatialReference::SetNormProjParm(const char *pszName, double dfValue)
    6161             : 
    6162             : {
    6163         182 :     TAKE_OPTIONAL_LOCK();
    6164             : 
    6165          91 :     GetNormInfo();
    6166             : 
    6167          91 :     if (d->dfToDegrees != 0.0 &&
    6168          91 :         (d->dfToDegrees != 1.0 || d->dfFromGreenwich != 0.0) &&
    6169           0 :         IsAngularParameter(pszName))
    6170             :     {
    6171           0 :         dfValue /= d->dfToDegrees;
    6172             :     }
    6173          95 :     else if (d->dfToMeter != 1.0 && d->dfToMeter != 0.0 &&
    6174           4 :              IsLinearParameter(pszName))
    6175           4 :         dfValue /= d->dfToMeter;
    6176             : 
    6177         182 :     return SetProjParm(pszName, dfValue);
    6178             : }
    6179             : 
    6180             : /************************************************************************/
    6181             : /*                         OSRSetNormProjParm()                         */
    6182             : /************************************************************************/
    6183             : 
    6184             : /**
    6185             :  * \brief Set a projection parameter with a normalized value.
    6186             :  *
    6187             :  * This function is the same as OGRSpatialReference::SetNormProjParm()
    6188             :  */
    6189           0 : OGRErr OSRSetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    6190             :                           double dfValue)
    6191             : 
    6192             : {
    6193           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNormProjParm", OGRERR_FAILURE);
    6194             : 
    6195           0 :     return ToPointer(hSRS)->SetNormProjParm(pszParamName, dfValue);
    6196             : }
    6197             : 
    6198             : /************************************************************************/
    6199             : /*                               SetTM()                                */
    6200             : /************************************************************************/
    6201             : 
    6202         428 : OGRErr OGRSpatialReference::SetTM(double dfCenterLat, double dfCenterLong,
    6203             :                                   double dfScale, double dfFalseEasting,
    6204             :                                   double dfFalseNorthing)
    6205             : 
    6206             : {
    6207         856 :     TAKE_OPTIONAL_LOCK();
    6208             : 
    6209         428 :     return d->replaceConversionAndUnref(
    6210             :         proj_create_conversion_transverse_mercator(
    6211             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    6212         856 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6213             : }
    6214             : 
    6215             : /************************************************************************/
    6216             : /*                              OSRSetTM()                              */
    6217             : /************************************************************************/
    6218             : 
    6219           1 : OGRErr OSRSetTM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6220             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    6221             :                 double dfFalseNorthing)
    6222             : 
    6223             : {
    6224           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTM", OGRERR_FAILURE);
    6225             : 
    6226           1 :     return ToPointer(hSRS)->SetTM(dfCenterLat, dfCenterLong, dfScale,
    6227           1 :                                   dfFalseEasting, dfFalseNorthing);
    6228             : }
    6229             : 
    6230             : /************************************************************************/
    6231             : /*                            SetTMVariant()                            */
    6232             : /************************************************************************/
    6233             : 
    6234           0 : OGRErr OGRSpatialReference::SetTMVariant(const char *pszVariantName,
    6235             :                                          double dfCenterLat,
    6236             :                                          double dfCenterLong, double dfScale,
    6237             :                                          double dfFalseEasting,
    6238             :                                          double dfFalseNorthing)
    6239             : 
    6240             : {
    6241           0 :     TAKE_OPTIONAL_LOCK();
    6242             : 
    6243           0 :     SetProjection(pszVariantName);
    6244           0 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6245           0 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6246           0 :     SetNormProjParm(SRS_PP_SCALE_FACTOR, dfScale);
    6247           0 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6248           0 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6249             : 
    6250           0 :     return OGRERR_NONE;
    6251             : }
    6252             : 
    6253             : /************************************************************************/
    6254             : /*                          OSRSetTMVariant()                           */
    6255             : /************************************************************************/
    6256             : 
    6257           0 : OGRErr OSRSetTMVariant(OGRSpatialReferenceH hSRS, const char *pszVariantName,
    6258             :                        double dfCenterLat, double dfCenterLong, double dfScale,
    6259             :                        double dfFalseEasting, double dfFalseNorthing)
    6260             : 
    6261             : {
    6262           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMVariant", OGRERR_FAILURE);
    6263             : 
    6264           0 :     return ToPointer(hSRS)->SetTMVariant(pszVariantName, dfCenterLat,
    6265             :                                          dfCenterLong, dfScale, dfFalseEasting,
    6266           0 :                                          dfFalseNorthing);
    6267             : }
    6268             : 
    6269             : /************************************************************************/
    6270             : /*                              SetTMSO()                               */
    6271             : /************************************************************************/
    6272             : 
    6273           3 : OGRErr OGRSpatialReference::SetTMSO(double dfCenterLat, double dfCenterLong,
    6274             :                                     double dfScale, double dfFalseEasting,
    6275             :                                     double dfFalseNorthing)
    6276             : 
    6277             : {
    6278           6 :     TAKE_OPTIONAL_LOCK();
    6279             : 
    6280           3 :     auto conv = proj_create_conversion_transverse_mercator_south_oriented(
    6281             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
    6282             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6283             : 
    6284           3 :     const char *pszName = nullptr;
    6285           3 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    6286           3 :     CPLString osName = pszName ? pszName : "";
    6287             : 
    6288           3 :     d->refreshProjObj();
    6289             : 
    6290           3 :     d->demoteFromBoundCRS();
    6291             : 
    6292           3 :     auto cs = proj_create_cartesian_2D_cs(
    6293             :         d->getPROJContext(), PJ_CART2D_WESTING_SOUTHING,
    6294           3 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    6295             :     auto projCRS =
    6296           3 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    6297           3 :                                   d->getGeodBaseCRS(), conv, cs);
    6298           3 :     proj_destroy(conv);
    6299           3 :     proj_destroy(cs);
    6300             : 
    6301           3 :     d->setPjCRS(projCRS);
    6302             : 
    6303           3 :     d->undoDemoteFromBoundCRS();
    6304             : 
    6305           6 :     return OGRERR_NONE;
    6306             : }
    6307             : 
    6308             : /************************************************************************/
    6309             : /*                             OSRSetTMSO()                             */
    6310             : /************************************************************************/
    6311             : 
    6312           0 : OGRErr OSRSetTMSO(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6313             :                   double dfCenterLong, double dfScale, double dfFalseEasting,
    6314             :                   double dfFalseNorthing)
    6315             : 
    6316             : {
    6317           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMSO", OGRERR_FAILURE);
    6318             : 
    6319           0 :     return ToPointer(hSRS)->SetTMSO(dfCenterLat, dfCenterLong, dfScale,
    6320           0 :                                     dfFalseEasting, dfFalseNorthing);
    6321             : }
    6322             : 
    6323             : /************************************************************************/
    6324             : /*                              SetTPED()                               */
    6325             : /************************************************************************/
    6326             : 
    6327           1 : OGRErr OGRSpatialReference::SetTPED(double dfLat1, double dfLong1,
    6328             :                                     double dfLat2, double dfLong2,
    6329             :                                     double dfFalseEasting,
    6330             :                                     double dfFalseNorthing)
    6331             : 
    6332             : {
    6333           2 :     TAKE_OPTIONAL_LOCK();
    6334             : 
    6335           1 :     return d->replaceConversionAndUnref(
    6336             :         proj_create_conversion_two_point_equidistant(
    6337             :             d->getPROJContext(), dfLat1, dfLong1, dfLat2, dfLong2,
    6338           2 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6339             : }
    6340             : 
    6341             : /************************************************************************/
    6342             : /*                             OSRSetTPED()                             */
    6343             : /************************************************************************/
    6344             : 
    6345           0 : OGRErr OSRSetTPED(OGRSpatialReferenceH hSRS, double dfLat1, double dfLong1,
    6346             :                   double dfLat2, double dfLong2, double dfFalseEasting,
    6347             :                   double dfFalseNorthing)
    6348             : 
    6349             : {
    6350           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTPED", OGRERR_FAILURE);
    6351             : 
    6352           0 :     return ToPointer(hSRS)->SetTPED(dfLat1, dfLong1, dfLat2, dfLong2,
    6353           0 :                                     dfFalseEasting, dfFalseNorthing);
    6354             : }
    6355             : 
    6356             : /************************************************************************/
    6357             : /*                               SetTMG()                               */
    6358             : /************************************************************************/
    6359             : 
    6360           0 : OGRErr OGRSpatialReference::SetTMG(double dfCenterLat, double dfCenterLong,
    6361             :                                    double dfFalseEasting,
    6362             :                                    double dfFalseNorthing)
    6363             : 
    6364             : {
    6365           0 :     TAKE_OPTIONAL_LOCK();
    6366             : 
    6367           0 :     return d->replaceConversionAndUnref(
    6368             :         proj_create_conversion_tunisia_mapping_grid(
    6369             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6370           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6371             : }
    6372             : 
    6373             : /************************************************************************/
    6374             : /*                             OSRSetTMG()                              */
    6375             : /************************************************************************/
    6376             : 
    6377           0 : OGRErr OSRSetTMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6378             :                  double dfCenterLong, double dfFalseEasting,
    6379             :                  double dfFalseNorthing)
    6380             : 
    6381             : {
    6382           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMG", OGRERR_FAILURE);
    6383             : 
    6384           0 :     return ToPointer(hSRS)->SetTMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    6385           0 :                                    dfFalseNorthing);
    6386             : }
    6387             : 
    6388             : /************************************************************************/
    6389             : /*                              SetACEA()                               */
    6390             : /************************************************************************/
    6391             : 
    6392          40 : OGRErr OGRSpatialReference::SetACEA(double dfStdP1, double dfStdP2,
    6393             :                                     double dfCenterLat, double dfCenterLong,
    6394             :                                     double dfFalseEasting,
    6395             :                                     double dfFalseNorthing)
    6396             : 
    6397             : {
    6398          80 :     TAKE_OPTIONAL_LOCK();
    6399             : 
    6400             :     // Note different order of parameters. The one in PROJ is conformant with
    6401             :     // EPSG
    6402          40 :     return d->replaceConversionAndUnref(
    6403             :         proj_create_conversion_albers_equal_area(
    6404             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6405          80 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6406             : }
    6407             : 
    6408             : /************************************************************************/
    6409             : /*                             OSRSetACEA()                             */
    6410             : /************************************************************************/
    6411             : 
    6412           0 : OGRErr OSRSetACEA(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6413             :                   double dfCenterLat, double dfCenterLong,
    6414             :                   double dfFalseEasting, double dfFalseNorthing)
    6415             : 
    6416             : {
    6417           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6418             : 
    6419           0 :     return ToPointer(hSRS)->SetACEA(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6420           0 :                                     dfFalseEasting, dfFalseNorthing);
    6421             : }
    6422             : 
    6423             : /************************************************************************/
    6424             : /*                               SetAE()                                */
    6425             : /************************************************************************/
    6426             : 
    6427          21 : OGRErr OGRSpatialReference::SetAE(double dfCenterLat, double dfCenterLong,
    6428             :                                   double dfFalseEasting, double dfFalseNorthing)
    6429             : 
    6430             : {
    6431          42 :     TAKE_OPTIONAL_LOCK();
    6432             : 
    6433          21 :     return d->replaceConversionAndUnref(
    6434             :         proj_create_conversion_azimuthal_equidistant(
    6435             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6436          42 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6437             : }
    6438             : 
    6439             : /************************************************************************/
    6440             : /*                              OSRSetAE()                              */
    6441             : /************************************************************************/
    6442             : 
    6443           0 : OGRErr OSRSetAE(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6444             :                 double dfCenterLong, double dfFalseEasting,
    6445             :                 double dfFalseNorthing)
    6446             : 
    6447             : {
    6448           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6449             : 
    6450           0 :     return ToPointer(hSRS)->SetAE(dfCenterLat, dfCenterLong, dfFalseEasting,
    6451           0 :                                   dfFalseNorthing);
    6452             : }
    6453             : 
    6454             : /************************************************************************/
    6455             : /*                              SetBonne()                              */
    6456             : /************************************************************************/
    6457             : 
    6458           1 : OGRErr OGRSpatialReference::SetBonne(double dfStdP1, double dfCentralMeridian,
    6459             :                                      double dfFalseEasting,
    6460             :                                      double dfFalseNorthing)
    6461             : 
    6462             : {
    6463           2 :     TAKE_OPTIONAL_LOCK();
    6464             : 
    6465           1 :     return d->replaceConversionAndUnref(proj_create_conversion_bonne(
    6466             :         d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6467           2 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6468             : }
    6469             : 
    6470             : /************************************************************************/
    6471             : /*                            OSRSetBonne()                             */
    6472             : /************************************************************************/
    6473             : 
    6474           0 : OGRErr OSRSetBonne(OGRSpatialReferenceH hSRS, double dfStdP1,
    6475             :                    double dfCentralMeridian, double dfFalseEasting,
    6476             :                    double dfFalseNorthing)
    6477             : 
    6478             : {
    6479           0 :     VALIDATE_POINTER1(hSRS, "OSRSetBonne", OGRERR_FAILURE);
    6480             : 
    6481           0 :     return ToPointer(hSRS)->SetBonne(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6482           0 :                                      dfFalseNorthing);
    6483             : }
    6484             : 
    6485             : /************************************************************************/
    6486             : /*                               SetCEA()                               */
    6487             : /************************************************************************/
    6488             : 
    6489           4 : OGRErr OGRSpatialReference::SetCEA(double dfStdP1, double dfCentralMeridian,
    6490             :                                    double dfFalseEasting,
    6491             :                                    double dfFalseNorthing)
    6492             : 
    6493             : {
    6494           8 :     TAKE_OPTIONAL_LOCK();
    6495             : 
    6496           4 :     return d->replaceConversionAndUnref(
    6497             :         proj_create_conversion_lambert_cylindrical_equal_area(
    6498             :             d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6499           8 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6500             : }
    6501             : 
    6502             : /************************************************************************/
    6503             : /*                             OSRSetCEA()                              */
    6504             : /************************************************************************/
    6505             : 
    6506           0 : OGRErr OSRSetCEA(OGRSpatialReferenceH hSRS, double dfStdP1,
    6507             :                  double dfCentralMeridian, double dfFalseEasting,
    6508             :                  double dfFalseNorthing)
    6509             : 
    6510             : {
    6511           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCEA", OGRERR_FAILURE);
    6512             : 
    6513           0 :     return ToPointer(hSRS)->SetCEA(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6514           0 :                                    dfFalseNorthing);
    6515             : }
    6516             : 
    6517             : /************************************************************************/
    6518             : /*                               SetCS()                                */
    6519             : /************************************************************************/
    6520             : 
    6521           5 : OGRErr OGRSpatialReference::SetCS(double dfCenterLat, double dfCenterLong,
    6522             :                                   double dfFalseEasting, double dfFalseNorthing)
    6523             : 
    6524             : {
    6525          10 :     TAKE_OPTIONAL_LOCK();
    6526             : 
    6527           5 :     return d->replaceConversionAndUnref(proj_create_conversion_cassini_soldner(
    6528             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6529          10 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6530             : }
    6531             : 
    6532             : /************************************************************************/
    6533             : /*                              OSRSetCS()                              */
    6534             : /************************************************************************/
    6535             : 
    6536           0 : OGRErr OSRSetCS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6537             :                 double dfCenterLong, double dfFalseEasting,
    6538             :                 double dfFalseNorthing)
    6539             : 
    6540             : {
    6541           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCS", OGRERR_FAILURE);
    6542             : 
    6543           0 :     return ToPointer(hSRS)->SetCS(dfCenterLat, dfCenterLong, dfFalseEasting,
    6544           0 :                                   dfFalseNorthing);
    6545             : }
    6546             : 
    6547             : /************************************************************************/
    6548             : /*                               SetEC()                                */
    6549             : /************************************************************************/
    6550             : 
    6551           7 : OGRErr OGRSpatialReference::SetEC(double dfStdP1, double dfStdP2,
    6552             :                                   double dfCenterLat, double dfCenterLong,
    6553             :                                   double dfFalseEasting, double dfFalseNorthing)
    6554             : 
    6555             : {
    6556          14 :     TAKE_OPTIONAL_LOCK();
    6557             : 
    6558             :     // Note: different order of arguments
    6559           7 :     return d->replaceConversionAndUnref(
    6560             :         proj_create_conversion_equidistant_conic(
    6561             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6562          14 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6563             : }
    6564             : 
    6565             : /************************************************************************/
    6566             : /*                              OSRSetEC()                              */
    6567             : /************************************************************************/
    6568             : 
    6569           0 : OGRErr OSRSetEC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6570             :                 double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    6571             :                 double dfFalseNorthing)
    6572             : 
    6573             : {
    6574           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEC", OGRERR_FAILURE);
    6575             : 
    6576           0 :     return ToPointer(hSRS)->SetEC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6577           0 :                                   dfFalseEasting, dfFalseNorthing);
    6578             : }
    6579             : 
    6580             : /************************************************************************/
    6581             : /*                             SetEckert()                              */
    6582             : /************************************************************************/
    6583             : 
    6584          10 : OGRErr OGRSpatialReference::SetEckert(int nVariation,  // 1-6.
    6585             :                                       double dfCentralMeridian,
    6586             :                                       double dfFalseEasting,
    6587             :                                       double dfFalseNorthing)
    6588             : 
    6589             : {
    6590          20 :     TAKE_OPTIONAL_LOCK();
    6591             : 
    6592             :     PJ *conv;
    6593          10 :     if (nVariation == 1)
    6594             :     {
    6595           1 :         conv = proj_create_conversion_eckert_i(
    6596             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6597             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6598             :     }
    6599           9 :     else if (nVariation == 2)
    6600             :     {
    6601           1 :         conv = proj_create_conversion_eckert_ii(
    6602             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6603             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6604             :     }
    6605           8 :     else if (nVariation == 3)
    6606             :     {
    6607           1 :         conv = proj_create_conversion_eckert_iii(
    6608             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6609             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6610             :     }
    6611           7 :     else if (nVariation == 4)
    6612             :     {
    6613           3 :         conv = proj_create_conversion_eckert_iv(
    6614             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6615             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6616             :     }
    6617           4 :     else if (nVariation == 5)
    6618             :     {
    6619           1 :         conv = proj_create_conversion_eckert_v(
    6620             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6621             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6622             :     }
    6623           3 :     else if (nVariation == 6)
    6624             :     {
    6625           3 :         conv = proj_create_conversion_eckert_vi(
    6626             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6627             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6628             :     }
    6629             :     else
    6630             :     {
    6631           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6632             :                  "Unsupported Eckert variation (%d).", nVariation);
    6633           0 :         return OGRERR_UNSUPPORTED_SRS;
    6634             :     }
    6635             : 
    6636          10 :     return d->replaceConversionAndUnref(conv);
    6637             : }
    6638             : 
    6639             : /************************************************************************/
    6640             : /*                            OSRSetEckert()                            */
    6641             : /************************************************************************/
    6642             : 
    6643           0 : OGRErr OSRSetEckert(OGRSpatialReferenceH hSRS, int nVariation,
    6644             :                     double dfCentralMeridian, double dfFalseEasting,
    6645             :                     double dfFalseNorthing)
    6646             : 
    6647             : {
    6648           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckert", OGRERR_FAILURE);
    6649             : 
    6650           0 :     return ToPointer(hSRS)->SetEckert(nVariation, dfCentralMeridian,
    6651           0 :                                       dfFalseEasting, dfFalseNorthing);
    6652             : }
    6653             : 
    6654             : /************************************************************************/
    6655             : /*                            SetEckertIV()                             */
    6656             : /*                                                                      */
    6657             : /*      Deprecated                                                      */
    6658             : /************************************************************************/
    6659             : 
    6660           2 : OGRErr OGRSpatialReference::SetEckertIV(double dfCentralMeridian,
    6661             :                                         double dfFalseEasting,
    6662             :                                         double dfFalseNorthing)
    6663             : 
    6664             : {
    6665           2 :     return SetEckert(4, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6666             : }
    6667             : 
    6668             : /************************************************************************/
    6669             : /*                           OSRSetEckertIV()                           */
    6670             : /************************************************************************/
    6671             : 
    6672           0 : OGRErr OSRSetEckertIV(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6673             :                       double dfFalseEasting, double dfFalseNorthing)
    6674             : 
    6675             : {
    6676           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertIV", OGRERR_FAILURE);
    6677             : 
    6678           0 :     return ToPointer(hSRS)->SetEckertIV(dfCentralMeridian, dfFalseEasting,
    6679           0 :                                         dfFalseNorthing);
    6680             : }
    6681             : 
    6682             : /************************************************************************/
    6683             : /*                            SetEckertVI()                             */
    6684             : /*                                                                      */
    6685             : /*      Deprecated                                                      */
    6686             : /************************************************************************/
    6687             : 
    6688           2 : OGRErr OGRSpatialReference::SetEckertVI(double dfCentralMeridian,
    6689             :                                         double dfFalseEasting,
    6690             :                                         double dfFalseNorthing)
    6691             : 
    6692             : {
    6693           2 :     return SetEckert(6, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6694             : }
    6695             : 
    6696             : /************************************************************************/
    6697             : /*                           OSRSetEckertVI()                           */
    6698             : /************************************************************************/
    6699             : 
    6700           0 : OGRErr OSRSetEckertVI(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6701             :                       double dfFalseEasting, double dfFalseNorthing)
    6702             : 
    6703             : {
    6704           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertVI", OGRERR_FAILURE);
    6705             : 
    6706           0 :     return ToPointer(hSRS)->SetEckertVI(dfCentralMeridian, dfFalseEasting,
    6707           0 :                                         dfFalseNorthing);
    6708             : }
    6709             : 
    6710             : /************************************************************************/
    6711             : /*                         SetEquirectangular()                         */
    6712             : /************************************************************************/
    6713             : 
    6714           2 : OGRErr OGRSpatialReference::SetEquirectangular(double dfCenterLat,
    6715             :                                                double dfCenterLong,
    6716             :                                                double dfFalseEasting,
    6717             :                                                double dfFalseNorthing)
    6718             : 
    6719             : {
    6720           4 :     TAKE_OPTIONAL_LOCK();
    6721             : 
    6722           2 :     if (dfCenterLat == 0.0)
    6723             :     {
    6724           0 :         return d->replaceConversionAndUnref(
    6725             :             proj_create_conversion_equidistant_cylindrical(
    6726             :                 d->getPROJContext(), 0.0, dfCenterLong, dfFalseEasting,
    6727           0 :                 dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6728             :     }
    6729             : 
    6730             :     // Non-standard extension with non-zero latitude of origin
    6731           2 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6732           2 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6733           2 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6734           2 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6735           2 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6736             : 
    6737           2 :     return OGRERR_NONE;
    6738             : }
    6739             : 
    6740             : /************************************************************************/
    6741             : /*                       OSRSetEquirectangular()                        */
    6742             : /************************************************************************/
    6743             : 
    6744           0 : OGRErr OSRSetEquirectangular(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6745             :                              double dfCenterLong, double dfFalseEasting,
    6746             :                              double dfFalseNorthing)
    6747             : 
    6748             : {
    6749           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular", OGRERR_FAILURE);
    6750             : 
    6751           0 :     return ToPointer(hSRS)->SetEquirectangular(dfCenterLat, dfCenterLong,
    6752           0 :                                                dfFalseEasting, dfFalseNorthing);
    6753             : }
    6754             : 
    6755             : /************************************************************************/
    6756             : /*                         SetEquirectangular2()                        */
    6757             : /* Generalized form                                                     */
    6758             : /************************************************************************/
    6759             : 
    6760         175 : OGRErr OGRSpatialReference::SetEquirectangular2(double dfCenterLat,
    6761             :                                                 double dfCenterLong,
    6762             :                                                 double dfStdParallel1,
    6763             :                                                 double dfFalseEasting,
    6764             :                                                 double dfFalseNorthing)
    6765             : 
    6766             : {
    6767         350 :     TAKE_OPTIONAL_LOCK();
    6768             : 
    6769         175 :     if (dfCenterLat == 0.0)
    6770             :     {
    6771         170 :         return d->replaceConversionAndUnref(
    6772             :             proj_create_conversion_equidistant_cylindrical(
    6773             :                 d->getPROJContext(), dfStdParallel1, dfCenterLong,
    6774         170 :                 dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6775             :     }
    6776             : 
    6777             :     // Non-standard extension with non-zero latitude of origin
    6778           5 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6779           5 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6780           5 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6781           5 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdParallel1);
    6782           5 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6783           5 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6784             : 
    6785           5 :     return OGRERR_NONE;
    6786             : }
    6787             : 
    6788             : /************************************************************************/
    6789             : /*                       OSRSetEquirectangular2()                       */
    6790             : /************************************************************************/
    6791             : 
    6792           3 : OGRErr OSRSetEquirectangular2(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6793             :                               double dfCenterLong, double dfStdParallel1,
    6794             :                               double dfFalseEasting, double dfFalseNorthing)
    6795             : 
    6796             : {
    6797           3 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular2", OGRERR_FAILURE);
    6798             : 
    6799           3 :     return ToPointer(hSRS)->SetEquirectangular2(dfCenterLat, dfCenterLong,
    6800             :                                                 dfStdParallel1, dfFalseEasting,
    6801           3 :                                                 dfFalseNorthing);
    6802             : }
    6803             : 
    6804             : /************************************************************************/
    6805             : /*                               SetGS()                                */
    6806             : /************************************************************************/
    6807             : 
    6808           5 : OGRErr OGRSpatialReference::SetGS(double dfCentralMeridian,
    6809             :                                   double dfFalseEasting, double dfFalseNorthing)
    6810             : 
    6811             : {
    6812           5 :     return d->replaceConversionAndUnref(proj_create_conversion_gall(
    6813             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    6814           5 :         nullptr, 0.0, nullptr, 0.0));
    6815             : }
    6816             : 
    6817             : /************************************************************************/
    6818             : /*                              OSRSetGS()                              */
    6819             : /************************************************************************/
    6820             : 
    6821           2 : OGRErr OSRSetGS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6822             :                 double dfFalseEasting, double dfFalseNorthing)
    6823             : 
    6824             : {
    6825           2 :     VALIDATE_POINTER1(hSRS, "OSRSetGS", OGRERR_FAILURE);
    6826             : 
    6827           2 :     return ToPointer(hSRS)->SetGS(dfCentralMeridian, dfFalseEasting,
    6828           2 :                                   dfFalseNorthing);
    6829             : }
    6830             : 
    6831             : /************************************************************************/
    6832             : /*                               SetGH()                                */
    6833             : /************************************************************************/
    6834             : 
    6835           0 : OGRErr OGRSpatialReference::SetGH(double dfCentralMeridian,
    6836             :                                   double dfFalseEasting, double dfFalseNorthing)
    6837             : 
    6838             : {
    6839           0 :     TAKE_OPTIONAL_LOCK();
    6840             : 
    6841           0 :     return d->replaceConversionAndUnref(proj_create_conversion_goode_homolosine(
    6842             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    6843           0 :         nullptr, 0.0, nullptr, 0.0));
    6844             : }
    6845             : 
    6846             : /************************************************************************/
    6847             : /*                              OSRSetGH()                              */
    6848             : /************************************************************************/
    6849             : 
    6850           0 : OGRErr OSRSetGH(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6851             :                 double dfFalseEasting, double dfFalseNorthing)
    6852             : 
    6853             : {
    6854           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGH", OGRERR_FAILURE);
    6855             : 
    6856           0 :     return ToPointer(hSRS)->SetGH(dfCentralMeridian, dfFalseEasting,
    6857           0 :                                   dfFalseNorthing);
    6858             : }
    6859             : 
    6860             : /************************************************************************/
    6861             : /*                              SetIGH()                                */
    6862             : /************************************************************************/
    6863             : 
    6864           0 : OGRErr OGRSpatialReference::SetIGH()
    6865             : 
    6866             : {
    6867           0 :     TAKE_OPTIONAL_LOCK();
    6868             : 
    6869           0 :     return d->replaceConversionAndUnref(
    6870             :         proj_create_conversion_interrupted_goode_homolosine(
    6871           0 :             d->getPROJContext(), 0.0, 0.0, 0.0, nullptr, 0.0, nullptr, 0.0));
    6872             : }
    6873             : 
    6874             : /************************************************************************/
    6875             : /*                              OSRSetIGH()                             */
    6876             : /************************************************************************/
    6877             : 
    6878           0 : OGRErr OSRSetIGH(OGRSpatialReferenceH hSRS)
    6879             : 
    6880             : {
    6881           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIGH", OGRERR_FAILURE);
    6882             : 
    6883           0 :     return ToPointer(hSRS)->SetIGH();
    6884             : }
    6885             : 
    6886             : /************************************************************************/
    6887             : /*                              SetGEOS()                               */
    6888             : /************************************************************************/
    6889             : 
    6890           3 : OGRErr OGRSpatialReference::SetGEOS(double dfCentralMeridian,
    6891             :                                     double dfSatelliteHeight,
    6892             :                                     double dfFalseEasting,
    6893             :                                     double dfFalseNorthing)
    6894             : 
    6895             : {
    6896           6 :     TAKE_OPTIONAL_LOCK();
    6897             : 
    6898           3 :     return d->replaceConversionAndUnref(
    6899             :         proj_create_conversion_geostationary_satellite_sweep_y(
    6900             :             d->getPROJContext(), dfCentralMeridian, dfSatelliteHeight,
    6901           6 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6902             : }
    6903             : 
    6904             : /************************************************************************/
    6905             : /*                              OSRSetGEOS()                             */
    6906             : /************************************************************************/
    6907             : 
    6908           0 : OGRErr OSRSetGEOS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6909             :                   double dfSatelliteHeight, double dfFalseEasting,
    6910             :                   double dfFalseNorthing)
    6911             : 
    6912             : {
    6913           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGEOS", OGRERR_FAILURE);
    6914             : 
    6915           0 :     return ToPointer(hSRS)->SetGEOS(dfCentralMeridian, dfSatelliteHeight,
    6916           0 :                                     dfFalseEasting, dfFalseNorthing);
    6917             : }
    6918             : 
    6919             : /************************************************************************/
    6920             : /*                       SetGaussSchreiberTMercator()                   */
    6921             : /************************************************************************/
    6922             : 
    6923           0 : OGRErr OGRSpatialReference::SetGaussSchreiberTMercator(double dfCenterLat,
    6924             :                                                        double dfCenterLong,
    6925             :                                                        double dfScale,
    6926             :                                                        double dfFalseEasting,
    6927             :                                                        double dfFalseNorthing)
    6928             : 
    6929             : {
    6930           0 :     TAKE_OPTIONAL_LOCK();
    6931             : 
    6932           0 :     return d->replaceConversionAndUnref(
    6933             :         proj_create_conversion_gauss_schreiber_transverse_mercator(
    6934             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    6935           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6936             : }
    6937             : 
    6938             : /************************************************************************/
    6939             : /*                     OSRSetGaussSchreiberTMercator()                  */
    6940             : /************************************************************************/
    6941             : 
    6942           0 : OGRErr OSRSetGaussSchreiberTMercator(OGRSpatialReferenceH hSRS,
    6943             :                                      double dfCenterLat, double dfCenterLong,
    6944             :                                      double dfScale, double dfFalseEasting,
    6945             :                                      double dfFalseNorthing)
    6946             : 
    6947             : {
    6948           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGaussSchreiberTMercator", OGRERR_FAILURE);
    6949             : 
    6950           0 :     return ToPointer(hSRS)->SetGaussSchreiberTMercator(
    6951           0 :         dfCenterLat, dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing);
    6952             : }
    6953             : 
    6954             : /************************************************************************/
    6955             : /*                            SetGnomonic()                             */
    6956             : /************************************************************************/
    6957             : 
    6958           2 : OGRErr OGRSpatialReference::SetGnomonic(double dfCenterLat, double dfCenterLong,
    6959             :                                         double dfFalseEasting,
    6960             :                                         double dfFalseNorthing)
    6961             : 
    6962             : {
    6963           4 :     TAKE_OPTIONAL_LOCK();
    6964             : 
    6965           2 :     return d->replaceConversionAndUnref(proj_create_conversion_gnomonic(
    6966             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6967           4 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6968             : }
    6969             : 
    6970             : /************************************************************************/
    6971             : /*                           OSRSetGnomonic()                           */
    6972             : /************************************************************************/
    6973             : 
    6974           0 : OGRErr OSRSetGnomonic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6975             :                       double dfCenterLong, double dfFalseEasting,
    6976             :                       double dfFalseNorthing)
    6977             : 
    6978             : {
    6979           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGnomonic", OGRERR_FAILURE);
    6980             : 
    6981           0 :     return ToPointer(hSRS)->SetGnomonic(dfCenterLat, dfCenterLong,
    6982           0 :                                         dfFalseEasting, dfFalseNorthing);
    6983             : }
    6984             : 
    6985             : /************************************************************************/
    6986             : /*                              SetHOMAC()                              */
    6987             : /************************************************************************/
    6988             : 
    6989             : /**
    6990             :  * \brief Set an Hotine Oblique Mercator Azimuth Center projection using
    6991             :  * azimuth angle.
    6992             :  *
    6993             :  * This projection corresponds to EPSG projection method 9815, also
    6994             :  * sometimes known as hotine oblique mercator (variant B).
    6995             :  *
    6996             :  * This method does the same thing as the C function OSRSetHOMAC().
    6997             :  *
    6998             :  * @param dfCenterLat Latitude of the projection origin.
    6999             :  * @param dfCenterLong Longitude of the projection origin.
    7000             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7001             :  * centerline.
    7002             :  * @param dfRectToSkew Angle from Rectified to Skew Grid
    7003             :  * @param dfScale Scale factor applies to the projection origin.
    7004             :  * @param dfFalseEasting False easting.
    7005             :  * @param dfFalseNorthing False northing.
    7006             :  *
    7007             :  * @return OGRERR_NONE on success.
    7008             :  */
    7009             : 
    7010           4 : OGRErr OGRSpatialReference::SetHOMAC(double dfCenterLat, double dfCenterLong,
    7011             :                                      double dfAzimuth, double dfRectToSkew,
    7012             :                                      double dfScale, double dfFalseEasting,
    7013             :                                      double dfFalseNorthing)
    7014             : 
    7015             : {
    7016           8 :     TAKE_OPTIONAL_LOCK();
    7017             : 
    7018           4 :     return d->replaceConversionAndUnref(
    7019             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    7020             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7021             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7022           8 :             0.0, nullptr, 0.0));
    7023             : }
    7024             : 
    7025             : /************************************************************************/
    7026             : /*                            OSRSetHOMAC()                             */
    7027             : /************************************************************************/
    7028             : 
    7029             : /**
    7030             :  * \brief Set an Oblique Mercator projection using azimuth angle.
    7031             :  *
    7032             :  * This is the same as the C++ method OGRSpatialReference::SetHOMAC()
    7033             :  */
    7034           0 : OGRErr OSRSetHOMAC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7035             :                    double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7036             :                    double dfScale, double dfFalseEasting,
    7037             :                    double dfFalseNorthing)
    7038             : 
    7039             : {
    7040           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOMAC", OGRERR_FAILURE);
    7041             : 
    7042           0 :     return ToPointer(hSRS)->SetHOMAC(dfCenterLat, dfCenterLong, dfAzimuth,
    7043             :                                      dfRectToSkew, dfScale, dfFalseEasting,
    7044           0 :                                      dfFalseNorthing);
    7045             : }
    7046             : 
    7047             : /************************************************************************/
    7048             : /*                               SetHOM()                               */
    7049             : /************************************************************************/
    7050             : 
    7051             : /**
    7052             :  * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
    7053             :  *
    7054             :  * This projection corresponds to EPSG projection method 9812, also
    7055             :  * sometimes known as hotine oblique mercator (variant A)..
    7056             :  *
    7057             :  * This method does the same thing as the C function OSRSetHOM().
    7058             :  *
    7059             :  * @param dfCenterLat Latitude of the projection origin.
    7060             :  * @param dfCenterLong Longitude of the projection origin.
    7061             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7062             :  * centerline.
    7063             :  * @param dfRectToSkew Angle from Rectified to Skew Grid
    7064             :  * @param dfScale Scale factor applies to the projection origin.
    7065             :  * @param dfFalseEasting False easting.
    7066             :  * @param dfFalseNorthing False northing.
    7067             :  *
    7068             :  * @return OGRERR_NONE on success.
    7069             :  */
    7070             : 
    7071          13 : OGRErr OGRSpatialReference::SetHOM(double dfCenterLat, double dfCenterLong,
    7072             :                                    double dfAzimuth, double dfRectToSkew,
    7073             :                                    double dfScale, double dfFalseEasting,
    7074             :                                    double dfFalseNorthing)
    7075             : 
    7076             : {
    7077          26 :     TAKE_OPTIONAL_LOCK();
    7078             : 
    7079          13 :     return d->replaceConversionAndUnref(
    7080             :         proj_create_conversion_hotine_oblique_mercator_variant_a(
    7081             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7082             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7083          26 :             0.0, nullptr, 0.0));
    7084             : }
    7085             : 
    7086             : /************************************************************************/
    7087             : /*                             OSRSetHOM()                              */
    7088             : /************************************************************************/
    7089             : /**
    7090             :  * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
    7091             :  *
    7092             :  * This is the same as the C++ method OGRSpatialReference::SetHOM()
    7093             :  */
    7094           0 : OGRErr OSRSetHOM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7095             :                  double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7096             :                  double dfScale, double dfFalseEasting, double dfFalseNorthing)
    7097             : 
    7098             : {
    7099           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM", OGRERR_FAILURE);
    7100             : 
    7101           0 :     return ToPointer(hSRS)->SetHOM(dfCenterLat, dfCenterLong, dfAzimuth,
    7102             :                                    dfRectToSkew, dfScale, dfFalseEasting,
    7103           0 :                                    dfFalseNorthing);
    7104             : }
    7105             : 
    7106             : /************************************************************************/
    7107             : /*                             SetHOM2PNO()                             */
    7108             : /************************************************************************/
    7109             : 
    7110             : /**
    7111             :  * \brief Set a Hotine Oblique Mercator projection using two points on
    7112             :  * projection centerline.
    7113             :  *
    7114             :  * This method does the same thing as the C function OSRSetHOM2PNO().
    7115             :  *
    7116             :  * @param dfCenterLat Latitude of the projection origin.
    7117             :  * @param dfLat1 Latitude of the first point on center line.
    7118             :  * @param dfLong1 Longitude of the first point on center line.
    7119             :  * @param dfLat2 Latitude of the second point on center line.
    7120             :  * @param dfLong2 Longitude of the second point on center line.
    7121             :  * @param dfScale Scale factor applies to the projection origin.
    7122             :  * @param dfFalseEasting False easting.
    7123             :  * @param dfFalseNorthing False northing.
    7124             :  *
    7125             :  * @return OGRERR_NONE on success.
    7126             :  */
    7127             : 
    7128           3 : OGRErr OGRSpatialReference::SetHOM2PNO(double dfCenterLat, double dfLat1,
    7129             :                                        double dfLong1, double dfLat2,
    7130             :                                        double dfLong2, double dfScale,
    7131             :                                        double dfFalseEasting,
    7132             :                                        double dfFalseNorthing)
    7133             : 
    7134             : {
    7135           6 :     TAKE_OPTIONAL_LOCK();
    7136             : 
    7137           3 :     return d->replaceConversionAndUnref(
    7138             :         proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin(
    7139             :             d->getPROJContext(), dfCenterLat, dfLat1, dfLong1, dfLat2, dfLong2,
    7140             :             dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    7141           6 :             0.0));
    7142             : }
    7143             : 
    7144             : /************************************************************************/
    7145             : /*                           OSRSetHOM2PNO()                            */
    7146             : /************************************************************************/
    7147             : /**
    7148             :  * \brief Set a Hotine Oblique Mercator projection using two points on
    7149             :  *  projection centerline.
    7150             :  *
    7151             :  * This is the same as the C++ method OGRSpatialReference::SetHOM2PNO()
    7152             :  */
    7153           0 : OGRErr OSRSetHOM2PNO(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7154             :                      double dfLat1, double dfLong1, double dfLat2,
    7155             :                      double dfLong2, double dfScale, double dfFalseEasting,
    7156             :                      double dfFalseNorthing)
    7157             : 
    7158             : {
    7159           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM2PNO", OGRERR_FAILURE);
    7160             : 
    7161           0 :     return ToPointer(hSRS)->SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
    7162             :                                        dfLong2, dfScale, dfFalseEasting,
    7163           0 :                                        dfFalseNorthing);
    7164             : }
    7165             : 
    7166             : /************************************************************************/
    7167             : /*                               SetLOM()                               */
    7168             : /************************************************************************/
    7169             : 
    7170             : /**
    7171             :  * \brief Set a Laborde Oblique Mercator projection.
    7172             :  *
    7173             :  * @param dfCenterLat Latitude of the projection origin.
    7174             :  * @param dfCenterLong Longitude of the projection origin.
    7175             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7176             :  * centerline.
    7177             :  * @param dfScale Scale factor on the initiali line
    7178             :  * @param dfFalseEasting False easting.
    7179             :  * @param dfFalseNorthing False northing.
    7180             :  *
    7181             :  * @return OGRERR_NONE on success.
    7182             :  */
    7183             : 
    7184           0 : OGRErr OGRSpatialReference::SetLOM(double dfCenterLat, double dfCenterLong,
    7185             :                                    double dfAzimuth, double dfScale,
    7186             :                                    double dfFalseEasting,
    7187             :                                    double dfFalseNorthing)
    7188             : 
    7189             : {
    7190           0 :     TAKE_OPTIONAL_LOCK();
    7191             : 
    7192           0 :     return d->replaceConversionAndUnref(
    7193             :         proj_create_conversion_laborde_oblique_mercator(
    7194             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth, dfScale,
    7195           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7196             : }
    7197             : 
    7198             : /************************************************************************/
    7199             : /*                            SetIWMPolyconic()                         */
    7200             : /************************************************************************/
    7201             : 
    7202           0 : OGRErr OGRSpatialReference::SetIWMPolyconic(double dfLat1, double dfLat2,
    7203             :                                             double dfCenterLong,
    7204             :                                             double dfFalseEasting,
    7205             :                                             double dfFalseNorthing)
    7206             : 
    7207             : {
    7208           0 :     TAKE_OPTIONAL_LOCK();
    7209             : 
    7210           0 :     return d->replaceConversionAndUnref(
    7211             :         proj_create_conversion_international_map_world_polyconic(
    7212             :             d->getPROJContext(), dfCenterLong, dfLat1, dfLat2, dfFalseEasting,
    7213           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7214             : }
    7215             : 
    7216             : /************************************************************************/
    7217             : /*                          OSRSetIWMPolyconic()                        */
    7218             : /************************************************************************/
    7219             : 
    7220           0 : OGRErr OSRSetIWMPolyconic(OGRSpatialReferenceH hSRS, double dfLat1,
    7221             :                           double dfLat2, double dfCenterLong,
    7222             :                           double dfFalseEasting, double dfFalseNorthing)
    7223             : 
    7224             : {
    7225           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIWMPolyconic", OGRERR_FAILURE);
    7226             : 
    7227           0 :     return ToPointer(hSRS)->SetIWMPolyconic(dfLat1, dfLat2, dfCenterLong,
    7228           0 :                                             dfFalseEasting, dfFalseNorthing);
    7229             : }
    7230             : 
    7231             : /************************************************************************/
    7232             : /*                             SetKrovak()                              */
    7233             : /************************************************************************/
    7234             : 
    7235             : /** Krovak east-north projection.
    7236             :  *
    7237             :  * Note that dfAzimuth and dfPseudoStdParallel1 are ignored when exporting
    7238             :  * to PROJ and should be respectively set to 30.28813972222222 and 78.5
    7239             :  */
    7240           3 : OGRErr OGRSpatialReference::SetKrovak(double dfCenterLat, double dfCenterLong,
    7241             :                                       double dfAzimuth,
    7242             :                                       double dfPseudoStdParallel1,
    7243             :                                       double dfScale, double dfFalseEasting,
    7244             :                                       double dfFalseNorthing)
    7245             : 
    7246             : {
    7247           6 :     TAKE_OPTIONAL_LOCK();
    7248             : 
    7249           3 :     return d->replaceConversionAndUnref(
    7250             :         proj_create_conversion_krovak_north_oriented(
    7251             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7252             :             dfPseudoStdParallel1, dfScale, dfFalseEasting, dfFalseNorthing,
    7253           6 :             nullptr, 0.0, nullptr, 0.0));
    7254             : }
    7255             : 
    7256             : /************************************************************************/
    7257             : /*                            OSRSetKrovak()                            */
    7258             : /************************************************************************/
    7259             : 
    7260           0 : OGRErr OSRSetKrovak(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7261             :                     double dfCenterLong, double dfAzimuth,
    7262             :                     double dfPseudoStdParallel1, double dfScale,
    7263             :                     double dfFalseEasting, double dfFalseNorthing)
    7264             : 
    7265             : {
    7266           0 :     VALIDATE_POINTER1(hSRS, "OSRSetKrovak", OGRERR_FAILURE);
    7267             : 
    7268           0 :     return ToPointer(hSRS)->SetKrovak(dfCenterLat, dfCenterLong, dfAzimuth,
    7269             :                                       dfPseudoStdParallel1, dfScale,
    7270           0 :                                       dfFalseEasting, dfFalseNorthing);
    7271             : }
    7272             : 
    7273             : /************************************************************************/
    7274             : /*                              SetLAEA()                               */
    7275             : /************************************************************************/
    7276             : 
    7277          18 : OGRErr OGRSpatialReference::SetLAEA(double dfCenterLat, double dfCenterLong,
    7278             :                                     double dfFalseEasting,
    7279             :                                     double dfFalseNorthing)
    7280             : 
    7281             : {
    7282          36 :     TAKE_OPTIONAL_LOCK();
    7283             : 
    7284          18 :     auto conv = proj_create_conversion_lambert_azimuthal_equal_area(
    7285             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7286             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    7287             : 
    7288          18 :     const char *pszName = nullptr;
    7289          18 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7290          18 :     CPLString osName = pszName ? pszName : "";
    7291             : 
    7292          18 :     d->refreshProjObj();
    7293             : 
    7294          18 :     d->demoteFromBoundCRS();
    7295             : 
    7296          18 :     auto cs = proj_create_cartesian_2D_cs(
    7297             :         d->getPROJContext(),
    7298          18 :         std::fabs(dfCenterLat - 90) < 1e-10 && dfCenterLong == 0
    7299             :             ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7300           0 :         : std::fabs(dfCenterLat - -90) < 1e-10 && dfCenterLong == 0
    7301          15 :             ? PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH
    7302             :             : PJ_CART2D_EASTING_NORTHING,
    7303          18 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7304             :     auto projCRS =
    7305          18 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7306          18 :                                   d->getGeodBaseCRS(), conv, cs);
    7307          18 :     proj_destroy(conv);
    7308          18 :     proj_destroy(cs);
    7309             : 
    7310          18 :     d->setPjCRS(projCRS);
    7311             : 
    7312          18 :     d->undoDemoteFromBoundCRS();
    7313             : 
    7314          36 :     return OGRERR_NONE;
    7315             : }
    7316             : 
    7317             : /************************************************************************/
    7318             : /*                             OSRSetLAEA()                             */
    7319             : /************************************************************************/
    7320             : 
    7321           0 : OGRErr OSRSetLAEA(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7322             :                   double dfCenterLong, double dfFalseEasting,
    7323             :                   double dfFalseNorthing)
    7324             : 
    7325             : {
    7326           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLAEA", OGRERR_FAILURE);
    7327             : 
    7328           0 :     return ToPointer(hSRS)->SetLAEA(dfCenterLat, dfCenterLong, dfFalseEasting,
    7329           0 :                                     dfFalseNorthing);
    7330             : }
    7331             : 
    7332             : /************************************************************************/
    7333             : /*                               SetLCC()                               */
    7334             : /************************************************************************/
    7335             : 
    7336         151 : OGRErr OGRSpatialReference::SetLCC(double dfStdP1, double dfStdP2,
    7337             :                                    double dfCenterLat, double dfCenterLong,
    7338             :                                    double dfFalseEasting,
    7339             :                                    double dfFalseNorthing)
    7340             : 
    7341             : {
    7342         302 :     TAKE_OPTIONAL_LOCK();
    7343             : 
    7344         151 :     return d->replaceConversionAndUnref(
    7345             :         proj_create_conversion_lambert_conic_conformal_2sp(
    7346             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7347         302 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7348             : }
    7349             : 
    7350             : /************************************************************************/
    7351             : /*                             OSRSetLCC()                              */
    7352             : /************************************************************************/
    7353             : 
    7354           1 : OGRErr OSRSetLCC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7355             :                  double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    7356             :                  double dfFalseNorthing)
    7357             : 
    7358             : {
    7359           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC", OGRERR_FAILURE);
    7360             : 
    7361           1 :     return ToPointer(hSRS)->SetLCC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7362           1 :                                    dfFalseEasting, dfFalseNorthing);
    7363             : }
    7364             : 
    7365             : /************************************************************************/
    7366             : /*                             SetLCC1SP()                              */
    7367             : /************************************************************************/
    7368             : 
    7369          10 : OGRErr OGRSpatialReference::SetLCC1SP(double dfCenterLat, double dfCenterLong,
    7370             :                                       double dfScale, double dfFalseEasting,
    7371             :                                       double dfFalseNorthing)
    7372             : 
    7373             : {
    7374          20 :     TAKE_OPTIONAL_LOCK();
    7375             : 
    7376          10 :     return d->replaceConversionAndUnref(
    7377             :         proj_create_conversion_lambert_conic_conformal_1sp(
    7378             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7379          20 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7380             : }
    7381             : 
    7382             : /************************************************************************/
    7383             : /*                            OSRSetLCC1SP()                            */
    7384             : /************************************************************************/
    7385             : 
    7386           0 : OGRErr OSRSetLCC1SP(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7387             :                     double dfCenterLong, double dfScale, double dfFalseEasting,
    7388             :                     double dfFalseNorthing)
    7389             : 
    7390             : {
    7391           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC1SP", OGRERR_FAILURE);
    7392             : 
    7393           0 :     return ToPointer(hSRS)->SetLCC1SP(dfCenterLat, dfCenterLong, dfScale,
    7394           0 :                                       dfFalseEasting, dfFalseNorthing);
    7395             : }
    7396             : 
    7397             : /************************************************************************/
    7398             : /*                              SetLCCB()                               */
    7399             : /************************************************************************/
    7400             : 
    7401           2 : OGRErr OGRSpatialReference::SetLCCB(double dfStdP1, double dfStdP2,
    7402             :                                     double dfCenterLat, double dfCenterLong,
    7403             :                                     double dfFalseEasting,
    7404             :                                     double dfFalseNorthing)
    7405             : 
    7406             : {
    7407           4 :     TAKE_OPTIONAL_LOCK();
    7408             : 
    7409           2 :     return d->replaceConversionAndUnref(
    7410             :         proj_create_conversion_lambert_conic_conformal_2sp_belgium(
    7411             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7412           4 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7413             : }
    7414             : 
    7415             : /************************************************************************/
    7416             : /*                             OSRSetLCCB()                             */
    7417             : /************************************************************************/
    7418             : 
    7419           0 : OGRErr OSRSetLCCB(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7420             :                   double dfCenterLat, double dfCenterLong,
    7421             :                   double dfFalseEasting, double dfFalseNorthing)
    7422             : 
    7423             : {
    7424           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCCB", OGRERR_FAILURE);
    7425             : 
    7426           0 :     return ToPointer(hSRS)->SetLCCB(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7427           0 :                                     dfFalseEasting, dfFalseNorthing);
    7428             : }
    7429             : 
    7430             : /************************************************************************/
    7431             : /*                               SetMC()                                */
    7432             : /************************************************************************/
    7433             : 
    7434           4 : OGRErr OGRSpatialReference::SetMC(double dfCenterLat, double dfCenterLong,
    7435             :                                   double dfFalseEasting, double dfFalseNorthing)
    7436             : 
    7437             : {
    7438           8 :     TAKE_OPTIONAL_LOCK();
    7439             : 
    7440             :     (void)dfCenterLat;  // ignored
    7441             : 
    7442           4 :     return d->replaceConversionAndUnref(
    7443             :         proj_create_conversion_miller_cylindrical(
    7444             :             d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7445           8 :             nullptr, 0, nullptr, 0));
    7446             : }
    7447             : 
    7448             : /************************************************************************/
    7449             : /*                              OSRSetMC()                              */
    7450             : /************************************************************************/
    7451             : 
    7452           0 : OGRErr OSRSetMC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7453             :                 double dfCenterLong, double dfFalseEasting,
    7454             :                 double dfFalseNorthing)
    7455             : 
    7456             : {
    7457           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMC", OGRERR_FAILURE);
    7458             : 
    7459           0 :     return ToPointer(hSRS)->SetMC(dfCenterLat, dfCenterLong, dfFalseEasting,
    7460           0 :                                   dfFalseNorthing);
    7461             : }
    7462             : 
    7463             : /************************************************************************/
    7464             : /*                            SetMercator()                             */
    7465             : /************************************************************************/
    7466             : 
    7467          59 : OGRErr OGRSpatialReference::SetMercator(double dfCenterLat, double dfCenterLong,
    7468             :                                         double dfScale, double dfFalseEasting,
    7469             :                                         double dfFalseNorthing)
    7470             : 
    7471             : {
    7472         118 :     TAKE_OPTIONAL_LOCK();
    7473             : 
    7474          59 :     if (dfCenterLat != 0.0 && dfScale == 1.0)
    7475             :     {
    7476             :         // Not sure this is correct, but this is how it has been used
    7477             :         // historically
    7478           0 :         return SetMercator2SP(dfCenterLat, 0.0, dfCenterLong, dfFalseEasting,
    7479           0 :                               dfFalseNorthing);
    7480             :     }
    7481          59 :     return d->replaceConversionAndUnref(
    7482             :         proj_create_conversion_mercator_variant_a(
    7483             :             d->getPROJContext(),
    7484             :             dfCenterLat,  // should be zero
    7485             :             dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0,
    7486          59 :             nullptr, 0));
    7487             : }
    7488             : 
    7489             : /************************************************************************/
    7490             : /*                           OSRSetMercator()                           */
    7491             : /************************************************************************/
    7492             : 
    7493           2 : OGRErr OSRSetMercator(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7494             :                       double dfCenterLong, double dfScale,
    7495             :                       double dfFalseEasting, double dfFalseNorthing)
    7496             : 
    7497             : {
    7498           2 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator", OGRERR_FAILURE);
    7499             : 
    7500           2 :     return ToPointer(hSRS)->SetMercator(dfCenterLat, dfCenterLong, dfScale,
    7501           2 :                                         dfFalseEasting, dfFalseNorthing);
    7502             : }
    7503             : 
    7504             : /************************************************************************/
    7505             : /*                           SetMercator2SP()                           */
    7506             : /************************************************************************/
    7507             : 
    7508          31 : OGRErr OGRSpatialReference::SetMercator2SP(double dfStdP1, double dfCenterLat,
    7509             :                                            double dfCenterLong,
    7510             :                                            double dfFalseEasting,
    7511             :                                            double dfFalseNorthing)
    7512             : 
    7513             : {
    7514          31 :     if (dfCenterLat == 0.0)
    7515             :     {
    7516          30 :         return d->replaceConversionAndUnref(
    7517             :             proj_create_conversion_mercator_variant_b(
    7518             :                 d->getPROJContext(), dfStdP1, dfCenterLong, dfFalseEasting,
    7519          30 :                 dfFalseNorthing, nullptr, 0, nullptr, 0));
    7520             :     }
    7521             : 
    7522           1 :     TAKE_OPTIONAL_LOCK();
    7523             : 
    7524           1 :     SetProjection(SRS_PT_MERCATOR_2SP);
    7525             : 
    7526           1 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdP1);
    7527           1 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    7528           1 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    7529           1 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    7530           1 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    7531             : 
    7532           1 :     return OGRERR_NONE;
    7533             : }
    7534             : 
    7535             : /************************************************************************/
    7536             : /*                         OSRSetMercator2SP()                          */
    7537             : /************************************************************************/
    7538             : 
    7539           1 : OGRErr OSRSetMercator2SP(OGRSpatialReferenceH hSRS, double dfStdP1,
    7540             :                          double dfCenterLat, double dfCenterLong,
    7541             :                          double dfFalseEasting, double dfFalseNorthing)
    7542             : 
    7543             : {
    7544           1 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator2SP", OGRERR_FAILURE);
    7545             : 
    7546           1 :     return ToPointer(hSRS)->SetMercator2SP(dfStdP1, dfCenterLat, dfCenterLong,
    7547           1 :                                            dfFalseEasting, dfFalseNorthing);
    7548             : }
    7549             : 
    7550             : /************************************************************************/
    7551             : /*                            SetMollweide()                            */
    7552             : /************************************************************************/
    7553             : 
    7554           3 : OGRErr OGRSpatialReference::SetMollweide(double dfCentralMeridian,
    7555             :                                          double dfFalseEasting,
    7556             :                                          double dfFalseNorthing)
    7557             : 
    7558             : {
    7559           6 :     TAKE_OPTIONAL_LOCK();
    7560             : 
    7561           3 :     return d->replaceConversionAndUnref(proj_create_conversion_mollweide(
    7562             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    7563           6 :         nullptr, 0, nullptr, 0));
    7564             : }
    7565             : 
    7566             : /************************************************************************/
    7567             : /*                          OSRSetMollweide()                           */
    7568             : /************************************************************************/
    7569             : 
    7570           0 : OGRErr OSRSetMollweide(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7571             :                        double dfFalseEasting, double dfFalseNorthing)
    7572             : 
    7573             : {
    7574           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMollweide", OGRERR_FAILURE);
    7575             : 
    7576           0 :     return ToPointer(hSRS)->SetMollweide(dfCentralMeridian, dfFalseEasting,
    7577           0 :                                          dfFalseNorthing);
    7578             : }
    7579             : 
    7580             : /************************************************************************/
    7581             : /*                              SetNZMG()                               */
    7582             : /************************************************************************/
    7583             : 
    7584           6 : OGRErr OGRSpatialReference::SetNZMG(double dfCenterLat, double dfCenterLong,
    7585             :                                     double dfFalseEasting,
    7586             :                                     double dfFalseNorthing)
    7587             : 
    7588             : {
    7589          12 :     TAKE_OPTIONAL_LOCK();
    7590             : 
    7591           6 :     return d->replaceConversionAndUnref(
    7592             :         proj_create_conversion_new_zealand_mapping_grid(
    7593             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7594          12 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7595             : }
    7596             : 
    7597             : /************************************************************************/
    7598             : /*                             OSRSetNZMG()                             */
    7599             : /************************************************************************/
    7600             : 
    7601           0 : OGRErr OSRSetNZMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7602             :                   double dfCenterLong, double dfFalseEasting,
    7603             :                   double dfFalseNorthing)
    7604             : 
    7605             : {
    7606           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNZMG", OGRERR_FAILURE);
    7607             : 
    7608           0 :     return ToPointer(hSRS)->SetNZMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    7609           0 :                                     dfFalseNorthing);
    7610             : }
    7611             : 
    7612             : /************************************************************************/
    7613             : /*                               SetOS()                                */
    7614             : /************************************************************************/
    7615             : 
    7616           6 : OGRErr OGRSpatialReference::SetOS(double dfOriginLat, double dfCMeridian,
    7617             :                                   double dfScale, double dfFalseEasting,
    7618             :                                   double dfFalseNorthing)
    7619             : 
    7620             : {
    7621          12 :     TAKE_OPTIONAL_LOCK();
    7622             : 
    7623           6 :     return d->replaceConversionAndUnref(
    7624             :         proj_create_conversion_oblique_stereographic(
    7625             :             d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale,
    7626          12 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7627             : }
    7628             : 
    7629             : /************************************************************************/
    7630             : /*                              OSRSetOS()                              */
    7631             : /************************************************************************/
    7632             : 
    7633           0 : OGRErr OSRSetOS(OGRSpatialReferenceH hSRS, double dfOriginLat,
    7634             :                 double dfCMeridian, double dfScale, double dfFalseEasting,
    7635             :                 double dfFalseNorthing)
    7636             : 
    7637             : {
    7638           0 :     VALIDATE_POINTER1(hSRS, "OSRSetOS", OGRERR_FAILURE);
    7639             : 
    7640           0 :     return ToPointer(hSRS)->SetOS(dfOriginLat, dfCMeridian, dfScale,
    7641           0 :                                   dfFalseEasting, dfFalseNorthing);
    7642             : }
    7643             : 
    7644             : /************************************************************************/
    7645             : /*                          SetOrthographic()                           */
    7646             : /************************************************************************/
    7647             : 
    7648           7 : OGRErr OGRSpatialReference::SetOrthographic(double dfCenterLat,
    7649             :                                             double dfCenterLong,
    7650             :                                             double dfFalseEasting,
    7651             :                                             double dfFalseNorthing)
    7652             : 
    7653             : {
    7654          14 :     TAKE_OPTIONAL_LOCK();
    7655             : 
    7656           7 :     return d->replaceConversionAndUnref(proj_create_conversion_orthographic(
    7657             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7658          14 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    7659             : }
    7660             : 
    7661             : /************************************************************************/
    7662             : /*                         OSRSetOrthographic()                         */
    7663             : /************************************************************************/
    7664             : 
    7665           1 : OGRErr OSRSetOrthographic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7666             :                           double dfCenterLong, double dfFalseEasting,
    7667             :                           double dfFalseNorthing)
    7668             : 
    7669             : {
    7670           1 :     VALIDATE_POINTER1(hSRS, "OSRSetOrthographic", OGRERR_FAILURE);
    7671             : 
    7672           1 :     return ToPointer(hSRS)->SetOrthographic(dfCenterLat, dfCenterLong,
    7673           1 :                                             dfFalseEasting, dfFalseNorthing);
    7674             : }
    7675             : 
    7676             : /************************************************************************/
    7677             : /*                            SetPolyconic()                            */
    7678             : /************************************************************************/
    7679             : 
    7680           7 : OGRErr OGRSpatialReference::SetPolyconic(double dfCenterLat,
    7681             :                                          double dfCenterLong,
    7682             :                                          double dfFalseEasting,
    7683             :                                          double dfFalseNorthing)
    7684             : 
    7685             : {
    7686          14 :     TAKE_OPTIONAL_LOCK();
    7687             : 
    7688             :     // note: it seems that by some definitions this should include a
    7689             :     //       scale_factor parameter.
    7690           7 :     return d->replaceConversionAndUnref(
    7691             :         proj_create_conversion_american_polyconic(
    7692             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7693          14 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7694             : }
    7695             : 
    7696             : /************************************************************************/
    7697             : /*                          OSRSetPolyconic()                           */
    7698             : /************************************************************************/
    7699             : 
    7700           0 : OGRErr OSRSetPolyconic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7701             :                        double dfCenterLong, double dfFalseEasting,
    7702             :                        double dfFalseNorthing)
    7703             : 
    7704             : {
    7705           0 :     VALIDATE_POINTER1(hSRS, "OSRSetPolyconic", OGRERR_FAILURE);
    7706             : 
    7707           0 :     return ToPointer(hSRS)->SetPolyconic(dfCenterLat, dfCenterLong,
    7708           0 :                                          dfFalseEasting, dfFalseNorthing);
    7709             : }
    7710             : 
    7711             : /************************************************************************/
    7712             : /*                               SetPS()                                */
    7713             : /************************************************************************/
    7714             : 
    7715             : /** Sets a Polar Stereographic projection.
    7716             :  *
    7717             :  * Two variants are possible:
    7718             :  * - Polar Stereographic Variant A: dfCenterLat must be +/- 90° and is
    7719             :  *   interpreted as the latitude of origin, combined with the scale factor
    7720             :  * - Polar Stereographic Variant B: dfCenterLat is different from +/- 90° and
    7721             :  *   is interpreted as the latitude of true scale. In that situation, dfScale
    7722             :  *   must be set to 1 (it is ignored in the projection parameters)
    7723             :  */
    7724          30 : OGRErr OGRSpatialReference::SetPS(double dfCenterLat, double dfCenterLong,
    7725             :                                   double dfScale, double dfFalseEasting,
    7726             :                                   double dfFalseNorthing)
    7727             : 
    7728             : {
    7729          60 :     TAKE_OPTIONAL_LOCK();
    7730             : 
    7731             :     PJ *conv;
    7732          30 :     if (dfScale == 1.0 && std::abs(std::abs(dfCenterLat) - 90) > 1e-8)
    7733             :     {
    7734          20 :         conv = proj_create_conversion_polar_stereographic_variant_b(
    7735             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7736             :             dfFalseNorthing, nullptr, 0, nullptr, 0);
    7737             :     }
    7738             :     else
    7739             :     {
    7740          10 :         conv = proj_create_conversion_polar_stereographic_variant_a(
    7741             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7742             :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0);
    7743             :     }
    7744             : 
    7745          30 :     const char *pszName = nullptr;
    7746          30 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7747          30 :     CPLString osName = pszName ? pszName : "";
    7748             : 
    7749          30 :     d->refreshProjObj();
    7750             : 
    7751          30 :     d->demoteFromBoundCRS();
    7752             : 
    7753          30 :     auto cs = proj_create_cartesian_2D_cs(
    7754             :         d->getPROJContext(),
    7755             :         dfCenterLat > 0 ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7756             :                         : PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH,
    7757          30 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7758             :     auto projCRS =
    7759          30 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7760          30 :                                   d->getGeodBaseCRS(), conv, cs);
    7761          30 :     proj_destroy(conv);
    7762          30 :     proj_destroy(cs);
    7763             : 
    7764          30 :     d->setPjCRS(projCRS);
    7765             : 
    7766          30 :     d->undoDemoteFromBoundCRS();
    7767             : 
    7768          60 :     return OGRERR_NONE;
    7769             : }
    7770             : 
    7771             : /************************************************************************/
    7772             : /*                              OSRSetPS()                              */
    7773             : /************************************************************************/
    7774             : 
    7775           1 : OGRErr OSRSetPS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7776             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    7777             :                 double dfFalseNorthing)
    7778             : 
    7779             : {
    7780           1 :     VALIDATE_POINTER1(hSRS, "OSRSetPS", OGRERR_FAILURE);
    7781             : 
    7782           1 :     return ToPointer(hSRS)->SetPS(dfCenterLat, dfCenterLong, dfScale,
    7783           1 :                                   dfFalseEasting, dfFalseNorthing);
    7784             : }
    7785             : 
    7786             : /************************************************************************/
    7787             : /*                            SetRobinson()                             */
    7788             : /************************************************************************/
    7789             : 
    7790           4 : OGRErr OGRSpatialReference::SetRobinson(double dfCenterLong,
    7791             :                                         double dfFalseEasting,
    7792             :                                         double dfFalseNorthing)
    7793             : 
    7794             : {
    7795           8 :     TAKE_OPTIONAL_LOCK();
    7796             : 
    7797           4 :     return d->replaceConversionAndUnref(proj_create_conversion_robinson(
    7798             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7799           8 :         nullptr, 0, nullptr, 0));
    7800             : }
    7801             : 
    7802             : /************************************************************************/
    7803             : /*                           OSRSetRobinson()                           */
    7804             : /************************************************************************/
    7805             : 
    7806           0 : OGRErr OSRSetRobinson(OGRSpatialReferenceH hSRS, double dfCenterLong,
    7807             :                       double dfFalseEasting, double dfFalseNorthing)
    7808             : 
    7809             : {
    7810           0 :     VALIDATE_POINTER1(hSRS, "OSRSetRobinson", OGRERR_FAILURE);
    7811             : 
    7812           0 :     return ToPointer(hSRS)->SetRobinson(dfCenterLong, dfFalseEasting,
    7813           0 :                                         dfFalseNorthing);
    7814             : }
    7815             : 
    7816             : /************************************************************************/
    7817             : /*                           SetSinusoidal()                            */
    7818             : /************************************************************************/
    7819             : 
    7820          33 : OGRErr OGRSpatialReference::SetSinusoidal(double dfCenterLong,
    7821             :                                           double dfFalseEasting,
    7822             :                                           double dfFalseNorthing)
    7823             : 
    7824             : {
    7825          66 :     TAKE_OPTIONAL_LOCK();
    7826             : 
    7827          33 :     return d->replaceConversionAndUnref(proj_create_conversion_sinusoidal(
    7828             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7829          66 :         nullptr, 0, nullptr, 0));
    7830             : }
    7831             : 
    7832             : /************************************************************************/
    7833             : /*                          OSRSetSinusoidal()                          */
    7834             : /************************************************************************/
    7835             : 
    7836           1 : OGRErr OSRSetSinusoidal(OGRSpatialReferenceH hSRS, double dfCenterLong,
    7837             :                         double dfFalseEasting, double dfFalseNorthing)
    7838             : 
    7839             : {
    7840           1 :     VALIDATE_POINTER1(hSRS, "OSRSetSinusoidal", OGRERR_FAILURE);
    7841             : 
    7842           1 :     return ToPointer(hSRS)->SetSinusoidal(dfCenterLong, dfFalseEasting,
    7843           1 :                                           dfFalseNorthing);
    7844             : }
    7845             : 
    7846             : /************************************************************************/
    7847             : /*                          SetStereographic()                          */
    7848             : /************************************************************************/
    7849             : 
    7850           2 : OGRErr OGRSpatialReference::SetStereographic(double dfOriginLat,
    7851             :                                              double dfCMeridian, double dfScale,
    7852             :                                              double dfFalseEasting,
    7853             :                                              double dfFalseNorthing)
    7854             : 
    7855             : {
    7856           4 :     TAKE_OPTIONAL_LOCK();
    7857             : 
    7858           2 :     return d->replaceConversionAndUnref(proj_create_conversion_stereographic(
    7859             :         d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale, dfFalseEasting,
    7860           4 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    7861             : }
    7862             : 
    7863             : /************************************************************************/
    7864             : /*                        OSRSetStereographic()                         */
    7865             : /************************************************************************/
    7866             : 
    7867           0 : OGRErr OSRSetStereographic(OGRSpatialReferenceH hSRS, double dfOriginLat,
    7868             :                            double dfCMeridian, double dfScale,
    7869             :                            double dfFalseEasting, double dfFalseNorthing)
    7870             : 
    7871             : {
    7872           0 :     VALIDATE_POINTER1(hSRS, "OSRSetStereographic", OGRERR_FAILURE);
    7873             : 
    7874           0 :     return ToPointer(hSRS)->SetStereographic(dfOriginLat, dfCMeridian, dfScale,
    7875           0 :                                              dfFalseEasting, dfFalseNorthing);
    7876             : }
    7877             : 
    7878             : /************************************************************************/
    7879             : /*                               SetSOC()                               */
    7880             : /*                                                                      */
    7881             : /*      NOTE: This definition isn't really used in practice any more    */
    7882             : /*      and should be considered deprecated.  It seems that swiss       */
    7883             : /*      oblique mercator is now define as Hotine_Oblique_Mercator       */
    7884             : /*      with an azimuth of 90 and a rectified_grid_angle of 90.  See    */
    7885             : /*      EPSG:2056 and Bug 423.                                          */
    7886             : /************************************************************************/
    7887             : 
    7888           2 : OGRErr OGRSpatialReference::SetSOC(double dfLatitudeOfOrigin,
    7889             :                                    double dfCentralMeridian,
    7890             :                                    double dfFalseEasting,
    7891             :                                    double dfFalseNorthing)
    7892             : 
    7893             : {
    7894           4 :     TAKE_OPTIONAL_LOCK();
    7895             : 
    7896           2 :     return d->replaceConversionAndUnref(
    7897             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    7898             :             d->getPROJContext(), dfLatitudeOfOrigin, dfCentralMeridian, 90.0,
    7899             :             90.0, 1.0, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    7900           4 :             0.0));
    7901             : #if 0
    7902             :     SetProjection( SRS_PT_SWISS_OBLIQUE_CYLINDRICAL );
    7903             :     SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfLatitudeOfOrigin );
    7904             :     SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    7905             :     SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    7906             :     SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );
    7907             : 
    7908             :     return OGRERR_NONE;
    7909             : #endif
    7910             : }
    7911             : 
    7912             : /************************************************************************/
    7913             : /*                             OSRSetSOC()                              */
    7914             : /************************************************************************/
    7915             : 
    7916           0 : OGRErr OSRSetSOC(OGRSpatialReferenceH hSRS, double dfLatitudeOfOrigin,
    7917             :                  double dfCentralMeridian, double dfFalseEasting,
    7918             :                  double dfFalseNorthing)
    7919             : 
    7920             : {
    7921           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSOC", OGRERR_FAILURE);
    7922             : 
    7923           0 :     return ToPointer(hSRS)->SetSOC(dfLatitudeOfOrigin, dfCentralMeridian,
    7924           0 :                                    dfFalseEasting, dfFalseNorthing);
    7925             : }
    7926             : 
    7927             : /************************************************************************/
    7928             : /*                               SetVDG()                               */
    7929             : /************************************************************************/
    7930             : 
    7931           2 : OGRErr OGRSpatialReference::SetVDG(double dfCMeridian, double dfFalseEasting,
    7932             :                                    double dfFalseNorthing)
    7933             : 
    7934             : {
    7935           4 :     TAKE_OPTIONAL_LOCK();
    7936             : 
    7937           2 :     return d->replaceConversionAndUnref(proj_create_conversion_van_der_grinten(
    7938             :         d->getPROJContext(), dfCMeridian, dfFalseEasting, dfFalseNorthing,
    7939           4 :         nullptr, 0, nullptr, 0));
    7940             : }
    7941             : 
    7942             : /************************************************************************/
    7943             : /*                             OSRSetVDG()                              */
    7944             : /************************************************************************/
    7945             : 
    7946           0 : OGRErr OSRSetVDG(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7947             :                  double dfFalseEasting, double dfFalseNorthing)
    7948             : 
    7949             : {
    7950           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVDG", OGRERR_FAILURE);
    7951             : 
    7952           0 :     return ToPointer(hSRS)->SetVDG(dfCentralMeridian, dfFalseEasting,
    7953           0 :                                    dfFalseNorthing);
    7954             : }
    7955             : 
    7956             : /************************************************************************/
    7957             : /*                               SetUTM()                               */
    7958             : /************************************************************************/
    7959             : 
    7960             : /**
    7961             :  * \brief Set UTM projection definition.
    7962             :  *
    7963             :  * This will generate a projection definition with the full set of
    7964             :  * transverse mercator projection parameters for the given UTM zone.
    7965             :  * If no PROJCS[] description is set yet, one will be set to look
    7966             :  * like "UTM Zone %d, {Northern, Southern} Hemisphere".
    7967             :  *
    7968             :  * This method is the same as the C function OSRSetUTM().
    7969             :  *
    7970             :  * @param nZone UTM zone.
    7971             :  *
    7972             :  * @param bNorth TRUE for northern hemisphere, or FALSE for southern
    7973             :  * hemisphere.
    7974             :  *
    7975             :  * @return OGRERR_NONE on success.
    7976             :  */
    7977             : 
    7978         319 : OGRErr OGRSpatialReference::SetUTM(int nZone, int bNorth)
    7979             : 
    7980             : {
    7981         638 :     TAKE_OPTIONAL_LOCK();
    7982             : 
    7983         319 :     if (nZone < 0 || nZone > 60)
    7984             :     {
    7985           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid zone: %d", nZone);
    7986           0 :         return OGRERR_FAILURE;
    7987             :     }
    7988             : 
    7989         319 :     return d->replaceConversionAndUnref(
    7990         319 :         proj_create_conversion_utm(d->getPROJContext(), nZone, bNorth));
    7991             : }
    7992             : 
    7993             : /************************************************************************/
    7994             : /*                             OSRSetUTM()                              */
    7995             : /************************************************************************/
    7996             : 
    7997             : /**
    7998             :  * \brief Set UTM projection definition.
    7999             :  *
    8000             :  * This is the same as the C++ method OGRSpatialReference::SetUTM()
    8001             :  */
    8002          19 : OGRErr OSRSetUTM(OGRSpatialReferenceH hSRS, int nZone, int bNorth)
    8003             : 
    8004             : {
    8005          19 :     VALIDATE_POINTER1(hSRS, "OSRSetUTM", OGRERR_FAILURE);
    8006             : 
    8007          19 :     return ToPointer(hSRS)->SetUTM(nZone, bNorth);
    8008             : }
    8009             : 
    8010             : /************************************************************************/
    8011             : /*                             GetUTMZone()                             */
    8012             : /*                                                                      */
    8013             : /*      Returns zero if it isn't UTM.                                   */
    8014             : /************************************************************************/
    8015             : 
    8016             : /**
    8017             :  * \brief Get utm zone information.
    8018             :  *
    8019             :  * This is the same as the C function OSRGetUTMZone().
    8020             :  *
    8021             :  * In SWIG bindings (Python, Java, etc) the GetUTMZone() method returns a
    8022             :  * zone which is negative in the southern hemisphere instead of having the
    8023             :  * pbNorth flag used in the C and C++ interface.
    8024             :  *
    8025             :  * @param pbNorth pointer to in to set to TRUE if northern hemisphere, or
    8026             :  * FALSE if southern.
    8027             :  *
    8028             :  * @return UTM zone number or zero if this isn't a UTM definition.
    8029             :  */
    8030             : 
    8031         589 : int OGRSpatialReference::GetUTMZone(int *pbNorth) const
    8032             : 
    8033             : {
    8034        1178 :     TAKE_OPTIONAL_LOCK();
    8035             : 
    8036         589 :     if (IsProjected() && GetAxesCount() == 3)
    8037             :     {
    8038           1 :         OGRSpatialReference *poSRSTmp = Clone();
    8039           1 :         poSRSTmp->DemoteTo2D(nullptr);
    8040           1 :         const int nZone = poSRSTmp->GetUTMZone(pbNorth);
    8041           1 :         delete poSRSTmp;
    8042           1 :         return nZone;
    8043             :     }
    8044             : 
    8045         588 :     const char *pszProjection = GetAttrValue("PROJECTION");
    8046             : 
    8047         588 :     if (pszProjection == nullptr ||
    8048         514 :         !EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    8049         266 :         return 0;
    8050             : 
    8051         322 :     if (GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) != 0.0)
    8052           5 :         return 0;
    8053             : 
    8054         317 :     if (GetProjParm(SRS_PP_SCALE_FACTOR, 1.0) != 0.9996)
    8055          15 :         return 0;
    8056             : 
    8057         302 :     if (fabs(GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0) - 500000.0) > 0.001)
    8058           3 :         return 0;
    8059             : 
    8060         299 :     const double dfFalseNorthing = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    8061             : 
    8062         299 :     if (dfFalseNorthing != 0.0 && fabs(dfFalseNorthing - 10000000.0) > 0.001)
    8063           0 :         return 0;
    8064             : 
    8065         299 :     if (pbNorth != nullptr)
    8066         235 :         *pbNorth = (dfFalseNorthing == 0);
    8067             : 
    8068             :     const double dfCentralMeridian =
    8069         299 :         GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    8070         299 :     const double dfZone = (dfCentralMeridian + 186.0) / 6.0;
    8071             : 
    8072         598 :     if (dfCentralMeridian < -177.00001 || dfCentralMeridian > 177.000001 ||
    8073         897 :         std::isnan(dfZone) ||
    8074         299 :         std::abs(dfZone - static_cast<int>(dfZone) - 0.5) > 0.00001)
    8075           0 :         return 0;
    8076             : 
    8077         299 :     return static_cast<int>(dfZone);
    8078             : }
    8079             : 
    8080             : /************************************************************************/
    8081             : /*                           OSRGetUTMZone()                            */
    8082             : /************************************************************************/
    8083             : 
    8084             : /**
    8085             :  * \brief Get utm zone information.
    8086             :  *
    8087             :  * This is the same as the C++ method OGRSpatialReference::GetUTMZone()
    8088             :  */
    8089           6 : int OSRGetUTMZone(OGRSpatialReferenceH hSRS, int *pbNorth)
    8090             : 
    8091             : {
    8092           6 :     VALIDATE_POINTER1(hSRS, "OSRGetUTMZone", 0);
    8093             : 
    8094           6 :     return ToPointer(hSRS)->GetUTMZone(pbNorth);
    8095             : }
    8096             : 
    8097             : /************************************************************************/
    8098             : /*                             SetWagner()                              */
    8099             : /************************************************************************/
    8100             : 
    8101           0 : OGRErr OGRSpatialReference::SetWagner(int nVariation,  // 1--7.
    8102             :                                       double dfCenterLat, double dfFalseEasting,
    8103             :                                       double dfFalseNorthing)
    8104             : 
    8105             : {
    8106           0 :     TAKE_OPTIONAL_LOCK();
    8107             : 
    8108             :     PJ *conv;
    8109           0 :     if (nVariation == 1)
    8110             :     {
    8111           0 :         conv = proj_create_conversion_wagner_i(d->getPROJContext(), 0.0,
    8112             :                                                dfFalseEasting, dfFalseNorthing,
    8113             :                                                nullptr, 0.0, nullptr, 0.0);
    8114             :     }
    8115           0 :     else if (nVariation == 2)
    8116             :     {
    8117           0 :         conv = proj_create_conversion_wagner_ii(d->getPROJContext(), 0.0,
    8118             :                                                 dfFalseEasting, dfFalseNorthing,
    8119             :                                                 nullptr, 0.0, nullptr, 0.0);
    8120             :     }
    8121           0 :     else if (nVariation == 3)
    8122             :     {
    8123           0 :         conv = proj_create_conversion_wagner_iii(
    8124             :             d->getPROJContext(), dfCenterLat, 0.0, dfFalseEasting,
    8125             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    8126             :     }
    8127           0 :     else if (nVariation == 4)
    8128             :     {
    8129           0 :         conv = proj_create_conversion_wagner_iv(d->getPROJContext(), 0.0,
    8130             :                                                 dfFalseEasting, dfFalseNorthing,
    8131             :                                                 nullptr, 0.0, nullptr, 0.0);
    8132             :     }
    8133           0 :     else if (nVariation == 5)
    8134             :     {
    8135           0 :         conv = proj_create_conversion_wagner_v(d->getPROJContext(), 0.0,
    8136             :                                                dfFalseEasting, dfFalseNorthing,
    8137             :                                                nullptr, 0.0, nullptr, 0.0);
    8138             :     }
    8139           0 :     else if (nVariation == 6)
    8140             :     {
    8141           0 :         conv = proj_create_conversion_wagner_vi(d->getPROJContext(), 0.0,
    8142             :                                                 dfFalseEasting, dfFalseNorthing,
    8143             :                                                 nullptr, 0.0, nullptr, 0.0);
    8144             :     }
    8145           0 :     else if (nVariation == 7)
    8146             :     {
    8147           0 :         conv = proj_create_conversion_wagner_vii(
    8148             :             d->getPROJContext(), 0.0, dfFalseEasting, dfFalseNorthing, nullptr,
    8149             :             0.0, nullptr, 0.0);
    8150             :     }
    8151             :     else
    8152             :     {
    8153           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8154             :                  "Unsupported Wagner variation (%d).", nVariation);
    8155           0 :         return OGRERR_UNSUPPORTED_SRS;
    8156             :     }
    8157             : 
    8158           0 :     return d->replaceConversionAndUnref(conv);
    8159             : }
    8160             : 
    8161             : /************************************************************************/
    8162             : /*                            OSRSetWagner()                            */
    8163             : /************************************************************************/
    8164             : 
    8165           0 : OGRErr OSRSetWagner(OGRSpatialReferenceH hSRS, int nVariation,
    8166             :                     double dfCenterLat, double dfFalseEasting,
    8167             :                     double dfFalseNorthing)
    8168             : 
    8169             : {
    8170           0 :     VALIDATE_POINTER1(hSRS, "OSRSetWagner", OGRERR_FAILURE);
    8171             : 
    8172           0 :     return ToPointer(hSRS)->SetWagner(nVariation, dfCenterLat, dfFalseEasting,
    8173           0 :                                       dfFalseNorthing);
    8174             : }
    8175             : 
    8176             : /************************************************************************/
    8177             : /*                            SetQSC()                     */
    8178             : /************************************************************************/
    8179             : 
    8180           0 : OGRErr OGRSpatialReference::SetQSC(double dfCenterLat, double dfCenterLong)
    8181             : {
    8182           0 :     TAKE_OPTIONAL_LOCK();
    8183             : 
    8184           0 :     return d->replaceConversionAndUnref(
    8185             :         proj_create_conversion_quadrilateralized_spherical_cube(
    8186             :             d->getPROJContext(), dfCenterLat, dfCenterLong, 0.0, 0.0, nullptr,
    8187           0 :             0, nullptr, 0));
    8188             : }
    8189             : 
    8190             : /************************************************************************/
    8191             : /*                           OSRSetQSC()                   */
    8192             : /************************************************************************/
    8193             : 
    8194           0 : OGRErr OSRSetQSC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    8195             :                  double dfCenterLong)
    8196             : 
    8197             : {
    8198           0 :     VALIDATE_POINTER1(hSRS, "OSRSetQSC", OGRERR_FAILURE);
    8199             : 
    8200           0 :     return ToPointer(hSRS)->SetQSC(dfCenterLat, dfCenterLong);
    8201             : }
    8202             : 
    8203             : /************************************************************************/
    8204             : /*                            SetSCH()                     */
    8205             : /************************************************************************/
    8206             : 
    8207           0 : OGRErr OGRSpatialReference::SetSCH(double dfPegLat, double dfPegLong,
    8208             :                                    double dfPegHeading, double dfPegHgt)
    8209             : 
    8210             : {
    8211           0 :     TAKE_OPTIONAL_LOCK();
    8212             : 
    8213           0 :     return d->replaceConversionAndUnref(
    8214             :         proj_create_conversion_spherical_cross_track_height(
    8215             :             d->getPROJContext(), dfPegLat, dfPegLong, dfPegHeading, dfPegHgt,
    8216           0 :             nullptr, 0, nullptr, 0));
    8217             : }
    8218             : 
    8219             : /************************************************************************/
    8220             : /*                           OSRSetSCH()                   */
    8221             : /************************************************************************/
    8222             : 
    8223           0 : OGRErr OSRSetSCH(OGRSpatialReferenceH hSRS, double dfPegLat, double dfPegLong,
    8224             :                  double dfPegHeading, double dfPegHgt)
    8225             : 
    8226             : {
    8227           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSCH", OGRERR_FAILURE);
    8228             : 
    8229           0 :     return ToPointer(hSRS)->SetSCH(dfPegLat, dfPegLong, dfPegHeading, dfPegHgt);
    8230             : }
    8231             : 
    8232             : /************************************************************************/
    8233             : /*                         SetVerticalPerspective()                     */
    8234             : /************************************************************************/
    8235             : 
    8236           3 : OGRErr OGRSpatialReference::SetVerticalPerspective(
    8237             :     double dfTopoOriginLat, double dfTopoOriginLon, double dfTopoOriginHeight,
    8238             :     double dfViewPointHeight, double dfFalseEasting, double dfFalseNorthing)
    8239             : {
    8240           6 :     TAKE_OPTIONAL_LOCK();
    8241             : 
    8242           3 :     return d->replaceConversionAndUnref(
    8243             :         proj_create_conversion_vertical_perspective(
    8244             :             d->getPROJContext(), dfTopoOriginLat, dfTopoOriginLon,
    8245             :             dfTopoOriginHeight, dfViewPointHeight, dfFalseEasting,
    8246           6 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    8247             : }
    8248             : 
    8249             : /************************************************************************/
    8250             : /*                       OSRSetVerticalPerspective()                    */
    8251             : /************************************************************************/
    8252             : 
    8253           1 : OGRErr OSRSetVerticalPerspective(OGRSpatialReferenceH hSRS,
    8254             :                                  double dfTopoOriginLat, double dfTopoOriginLon,
    8255             :                                  double dfTopoOriginHeight,
    8256             :                                  double dfViewPointHeight,
    8257             :                                  double dfFalseEasting, double dfFalseNorthing)
    8258             : 
    8259             : {
    8260           1 :     VALIDATE_POINTER1(hSRS, "OSRSetVerticalPerspective", OGRERR_FAILURE);
    8261             : 
    8262           1 :     return ToPointer(hSRS)->SetVerticalPerspective(
    8263             :         dfTopoOriginLat, dfTopoOriginLon, dfTopoOriginHeight, dfViewPointHeight,
    8264           1 :         dfFalseEasting, dfFalseNorthing);
    8265             : }
    8266             : 
    8267             : /************************************************************************/
    8268             : /*             SetDerivedGeogCRSWithPoleRotationGRIBConvention()        */
    8269             : /************************************************************************/
    8270             : 
    8271           2 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationGRIBConvention(
    8272             :     const char *pszCRSName, double dfSouthPoleLat, double dfSouthPoleLon,
    8273             :     double dfAxisRotation)
    8274             : {
    8275           4 :     TAKE_OPTIONAL_LOCK();
    8276             : 
    8277           2 :     d->refreshProjObj();
    8278           2 :     if (!d->m_pj_crs)
    8279           0 :         return OGRERR_FAILURE;
    8280           2 :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8281           0 :         return OGRERR_FAILURE;
    8282           2 :     auto ctxt = d->getPROJContext();
    8283           2 :     auto conv = proj_create_conversion_pole_rotation_grib_convention(
    8284             :         ctxt, dfSouthPoleLat, dfSouthPoleLon, dfAxisRotation, nullptr, 0);
    8285           2 :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8286           4 :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8287           2 :                                                    d->m_pj_crs, conv, cs));
    8288           2 :     proj_destroy(conv);
    8289           2 :     proj_destroy(cs);
    8290           2 :     return OGRERR_NONE;
    8291             : }
    8292             : 
    8293             : /************************************************************************/
    8294             : /*         SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention()        */
    8295             : /************************************************************************/
    8296             : 
    8297           3 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention(
    8298             :     const char *pszCRSName, double dfGridNorthPoleLat,
    8299             :     double dfGridNorthPoleLon, double dfNorthPoleGridLon)
    8300             : {
    8301           3 :     TAKE_OPTIONAL_LOCK();
    8302             : 
    8303             : #if PROJ_VERSION_MAJOR > 8 ||                                                  \
    8304             :     (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR >= 2)
    8305             :     d->refreshProjObj();
    8306             :     if (!d->m_pj_crs)
    8307             :         return OGRERR_FAILURE;
    8308             :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8309             :         return OGRERR_FAILURE;
    8310             :     auto ctxt = d->getPROJContext();
    8311             :     auto conv = proj_create_conversion_pole_rotation_netcdf_cf_convention(
    8312             :         ctxt, dfGridNorthPoleLat, dfGridNorthPoleLon, dfNorthPoleGridLon,
    8313             :         nullptr, 0);
    8314             :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8315             :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8316             :                                                    d->m_pj_crs, conv, cs));
    8317             :     proj_destroy(conv);
    8318             :     proj_destroy(cs);
    8319             :     return OGRERR_NONE;
    8320             : #else
    8321             :     (void)pszCRSName;
    8322           3 :     SetProjection("Rotated_pole");
    8323           3 :     SetExtension(
    8324             :         "PROJCS", "PROJ4",
    8325             :         CPLSPrintf("+proj=ob_tran +o_proj=longlat +lon_0=%.17g +o_lon_p=%.17g "
    8326             :                    "+o_lat_p=%.17g +a=%.17g +b=%.17g "
    8327             :                    "+to_meter=0.0174532925199433 "
    8328             :                    "+wktext",
    8329             :                    180.0 + dfGridNorthPoleLon, dfNorthPoleGridLon,
    8330             :                    dfGridNorthPoleLat, GetSemiMajor(nullptr),
    8331             :                    GetSemiMinor(nullptr)));
    8332           6 :     return OGRERR_NONE;
    8333             : #endif
    8334             : }
    8335             : 
    8336             : /************************************************************************/
    8337             : /*                            SetAuthority()                            */
    8338             : /************************************************************************/
    8339             : 
    8340             : /**
    8341             :  * \brief Set the authority for a node.
    8342             :  *
    8343             :  * This method is the same as the C function OSRSetAuthority().
    8344             :  *
    8345             :  * @param pszTargetKey the partial or complete path to the node to
    8346             :  * set an authority on.  i.e. "PROJCS", "GEOGCS" or "GEOGCS|UNIT".
    8347             :  *
    8348             :  * @param pszAuthority authority name, such as "EPSG".
    8349             :  *
    8350             :  * @param nCode code for value with this authority.
    8351             :  *
    8352             :  * @return OGRERR_NONE on success.
    8353             :  */
    8354             : 
    8355       10329 : OGRErr OGRSpatialReference::SetAuthority(const char *pszTargetKey,
    8356             :                                          const char *pszAuthority, int nCode)
    8357             : 
    8358             : {
    8359       20658 :     TAKE_OPTIONAL_LOCK();
    8360             : 
    8361       10329 :     d->refreshProjObj();
    8362       10329 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8363             : 
    8364       10329 :     if (pszTargetKey == nullptr)
    8365             :     {
    8366         263 :         if (!d->m_pj_crs)
    8367           0 :             return OGRERR_FAILURE;
    8368         263 :         CPLString osCode;
    8369         263 :         osCode.Printf("%d", nCode);
    8370         263 :         d->demoteFromBoundCRS();
    8371         263 :         d->setPjCRS(proj_alter_id(d->getPROJContext(), d->m_pj_crs,
    8372             :                                   pszAuthority, osCode.c_str()));
    8373         263 :         d->undoDemoteFromBoundCRS();
    8374         263 :         return OGRERR_NONE;
    8375             :     }
    8376             : 
    8377       10066 :     d->demoteFromBoundCRS();
    8378       10066 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS && EQUAL(pszTargetKey, "GEOGCS"))
    8379             :     {
    8380        3325 :         CPLString osCode;
    8381        3325 :         osCode.Printf("%d", nCode);
    8382             :         auto newGeogCRS =
    8383        3325 :             proj_alter_id(d->getPROJContext(), d->getGeodBaseCRS(),
    8384             :                           pszAuthority, osCode.c_str());
    8385             : 
    8386             :         auto conv =
    8387        3325 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    8388             : 
    8389        3325 :         auto projCRS = proj_create_projected_crs(
    8390             :             d->getPROJContext(), d->getProjCRSName(), newGeogCRS, conv,
    8391        3325 :             d->getProjCRSCoordSys());
    8392             : 
    8393             :         // Preserve existing id on the PROJCRS
    8394        3325 :         const char *pszProjCRSAuthName = proj_get_id_auth_name(d->m_pj_crs, 0);
    8395        3325 :         const char *pszProjCRSCode = proj_get_id_code(d->m_pj_crs, 0);
    8396        3325 :         if (pszProjCRSAuthName && pszProjCRSCode)
    8397             :         {
    8398             :             auto projCRSWithId =
    8399           0 :                 proj_alter_id(d->getPROJContext(), projCRS, pszProjCRSAuthName,
    8400             :                               pszProjCRSCode);
    8401           0 :             proj_destroy(projCRS);
    8402           0 :             projCRS = projCRSWithId;
    8403             :         }
    8404             : 
    8405        3325 :         proj_destroy(newGeogCRS);
    8406        3325 :         proj_destroy(conv);
    8407             : 
    8408        3325 :         d->setPjCRS(projCRS);
    8409        3325 :         d->undoDemoteFromBoundCRS();
    8410        3325 :         return OGRERR_NONE;
    8411             :     }
    8412        6741 :     d->undoDemoteFromBoundCRS();
    8413             : 
    8414             :     /* -------------------------------------------------------------------- */
    8415             :     /*      Find the node below which the authority should be put.          */
    8416             :     /* -------------------------------------------------------------------- */
    8417        6741 :     OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8418             : 
    8419        6741 :     if (poNode == nullptr)
    8420           0 :         return OGRERR_FAILURE;
    8421             : 
    8422             :     /* -------------------------------------------------------------------- */
    8423             :     /*      If there is an existing AUTHORITY child blow it away before     */
    8424             :     /*      trying to set a new one.                                        */
    8425             :     /* -------------------------------------------------------------------- */
    8426        6741 :     int iOldChild = poNode->FindChild("AUTHORITY");
    8427        6741 :     if (iOldChild != -1)
    8428           5 :         poNode->DestroyChild(iOldChild);
    8429             : 
    8430             :     /* -------------------------------------------------------------------- */
    8431             :     /*      Create a new authority node.                                    */
    8432             :     /* -------------------------------------------------------------------- */
    8433        6741 :     char szCode[32] = {};
    8434             : 
    8435        6741 :     snprintf(szCode, sizeof(szCode), "%d", nCode);
    8436             : 
    8437        6741 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("AUTHORITY");
    8438        6741 :     poAuthNode->AddChild(new OGR_SRSNode(pszAuthority));
    8439        6741 :     poAuthNode->AddChild(new OGR_SRSNode(szCode));
    8440             : 
    8441        6741 :     poNode->AddChild(poAuthNode);
    8442             : 
    8443        6741 :     return OGRERR_NONE;
    8444             : }
    8445             : 
    8446             : /************************************************************************/
    8447             : /*                          OSRSetAuthority()                           */
    8448             : /************************************************************************/
    8449             : 
    8450             : /**
    8451             :  * \brief Set the authority for a node.
    8452             :  *
    8453             :  * This function is the same as OGRSpatialReference::SetAuthority().
    8454             :  */
    8455           0 : OGRErr OSRSetAuthority(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
    8456             :                        const char *pszAuthority, int nCode)
    8457             : 
    8458             : {
    8459           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAuthority", OGRERR_FAILURE);
    8460             : 
    8461           0 :     return ToPointer(hSRS)->SetAuthority(pszTargetKey, pszAuthority, nCode);
    8462             : }
    8463             : 
    8464             : /************************************************************************/
    8465             : /*                          GetAuthorityCode()                          */
    8466             : /************************************************************************/
    8467             : 
    8468             : /**
    8469             :  * \brief Get the authority code for a node.
    8470             :  *
    8471             :  * This method is used to query an AUTHORITY[] node from within the
    8472             :  * WKT tree, and fetch the code value.
    8473             :  *
    8474             :  * While in theory values may be non-numeric, for the EPSG authority all
    8475             :  * code values should be integral.
    8476             :  *
    8477             :  * This method is the same as the C function OSRGetAuthorityCode().
    8478             :  *
    8479             :  * @param pszTargetKey the partial or complete path to the node to
    8480             :  * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
    8481             :  * search for an authority node on the root element.
    8482             :  *
    8483             :  * @return value code from authority node, or NULL on failure.  The value
    8484             :  * returned is internal and should not be freed or modified.
    8485             :  */
    8486             : 
    8487             : const char *
    8488       25376 : OGRSpatialReference::GetAuthorityCode(const char *pszTargetKey) const
    8489             : 
    8490             : {
    8491       50752 :     TAKE_OPTIONAL_LOCK();
    8492             : 
    8493       25376 :     d->refreshProjObj();
    8494       25376 :     const char *pszInputTargetKey = pszTargetKey;
    8495       25376 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8496       25376 :     if (pszTargetKey == nullptr)
    8497             :     {
    8498       18101 :         if (!d->m_pj_crs)
    8499             :         {
    8500          13 :             return nullptr;
    8501             :         }
    8502       18088 :         d->demoteFromBoundCRS();
    8503       18088 :         auto ret = proj_get_id_code(d->m_pj_crs, 0);
    8504       18088 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8505             :         {
    8506        1064 :             auto ctxt = d->getPROJContext();
    8507        1064 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8508        1064 :             if (cs)
    8509             :             {
    8510        1064 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8511        1064 :                 proj_destroy(cs);
    8512        1064 :                 if (axisCount == 3)
    8513             :                 {
    8514             :                     // This might come from a COMPD_CS with a VERT_DATUM type =
    8515             :                     // 2002 in which case, using the WKT1 representation will
    8516             :                     // enable us to recover the EPSG code.
    8517          14 :                     pszTargetKey = pszInputTargetKey;
    8518             :                 }
    8519             :             }
    8520             :         }
    8521       18088 :         d->undoDemoteFromBoundCRS();
    8522       18088 :         if (ret != nullptr || pszTargetKey == nullptr)
    8523             :         {
    8524       18088 :             return ret;
    8525             :         }
    8526             :     }
    8527             : 
    8528             :     // Special key for that context
    8529        7279 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8530           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8531             :     {
    8532           4 :         auto ctxt = d->getPROJContext();
    8533           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8534           4 :         if (crs)
    8535             :         {
    8536           4 :             const char *ret = proj_get_id_code(crs, 0);
    8537           4 :             if (ret)
    8538           4 :                 ret = CPLSPrintf("%s", ret);
    8539           4 :             proj_destroy(crs);
    8540           4 :             return ret;
    8541             :         }
    8542             :     }
    8543        7275 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8544           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8545             :     {
    8546           4 :         auto ctxt = d->getPROJContext();
    8547           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8548           4 :         if (crs)
    8549             :         {
    8550           4 :             const char *ret = proj_get_id_code(crs, 0);
    8551           4 :             if (ret)
    8552           4 :                 ret = CPLSPrintf("%s", ret);
    8553           4 :             proj_destroy(crs);
    8554           4 :             return ret;
    8555             :         }
    8556             :     }
    8557             : 
    8558             :     /* -------------------------------------------------------------------- */
    8559             :     /*      Find the node below which the authority should be put.          */
    8560             :     /* -------------------------------------------------------------------- */
    8561        7267 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8562             : 
    8563        7267 :     if (poNode == nullptr)
    8564         102 :         return nullptr;
    8565             : 
    8566             :     /* -------------------------------------------------------------------- */
    8567             :     /*      Fetch AUTHORITY child if there is one.                          */
    8568             :     /* -------------------------------------------------------------------- */
    8569        7165 :     if (poNode->FindChild("AUTHORITY") == -1)
    8570         182 :         return nullptr;
    8571             : 
    8572        6983 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8573             : 
    8574             :     /* -------------------------------------------------------------------- */
    8575             :     /*      Create a new authority node.                                    */
    8576             :     /* -------------------------------------------------------------------- */
    8577        6983 :     if (poNode->GetChildCount() < 2)
    8578           0 :         return nullptr;
    8579             : 
    8580        6983 :     return poNode->GetChild(1)->GetValue();
    8581             : }
    8582             : 
    8583             : /************************************************************************/
    8584             : /*                          OSRGetAuthorityCode()                       */
    8585             : /************************************************************************/
    8586             : 
    8587             : /**
    8588             :  * \brief Get the authority code for a node.
    8589             :  *
    8590             :  * This function is the same as OGRSpatialReference::GetAuthorityCode().
    8591             :  */
    8592         661 : const char *OSRGetAuthorityCode(OGRSpatialReferenceH hSRS,
    8593             :                                 const char *pszTargetKey)
    8594             : 
    8595             : {
    8596         661 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityCode", nullptr);
    8597             : 
    8598         661 :     return ToPointer(hSRS)->GetAuthorityCode(pszTargetKey);
    8599             : }
    8600             : 
    8601             : /************************************************************************/
    8602             : /*                          GetAuthorityName()                          */
    8603             : /************************************************************************/
    8604             : 
    8605             : /**
    8606             :  * \brief Get the authority name for a node.
    8607             :  *
    8608             :  * This method is used to query an AUTHORITY[] node from within the
    8609             :  * WKT tree, and fetch the authority name value.
    8610             :  *
    8611             :  * The most common authority is "EPSG".
    8612             :  *
    8613             :  * This method is the same as the C function OSRGetAuthorityName().
    8614             :  *
    8615             :  * @param pszTargetKey the partial or complete path to the node to
    8616             :  * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
    8617             :  * search for an authority node on the root element.
    8618             :  *
    8619             :  * @return value code from authority node, or NULL on failure. The value
    8620             :  * returned is internal and should not be freed or modified.
    8621             :  */
    8622             : 
    8623             : const char *
    8624       45783 : OGRSpatialReference::GetAuthorityName(const char *pszTargetKey) const
    8625             : 
    8626             : {
    8627       91566 :     TAKE_OPTIONAL_LOCK();
    8628             : 
    8629       45783 :     d->refreshProjObj();
    8630       45783 :     const char *pszInputTargetKey = pszTargetKey;
    8631       45783 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8632       45783 :     if (pszTargetKey == nullptr)
    8633             :     {
    8634       19833 :         if (!d->m_pj_crs)
    8635             :         {
    8636          14 :             return nullptr;
    8637             :         }
    8638       19819 :         d->demoteFromBoundCRS();
    8639       19819 :         auto ret = proj_get_id_auth_name(d->m_pj_crs, 0);
    8640       19819 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8641             :         {
    8642         787 :             auto ctxt = d->getPROJContext();
    8643         787 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8644         787 :             if (cs)
    8645             :             {
    8646         787 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8647         787 :                 proj_destroy(cs);
    8648         787 :                 if (axisCount == 3)
    8649             :                 {
    8650             :                     // This might come from a COMPD_CS with a VERT_DATUM type =
    8651             :                     // 2002 in which case, using the WKT1 representation will
    8652             :                     // enable us to recover the EPSG code.
    8653          14 :                     pszTargetKey = pszInputTargetKey;
    8654             :                 }
    8655             :             }
    8656             :         }
    8657       19819 :         d->undoDemoteFromBoundCRS();
    8658       19819 :         if (ret != nullptr || pszTargetKey == nullptr)
    8659             :         {
    8660       19819 :             return ret;
    8661             :         }
    8662             :     }
    8663             : 
    8664             :     // Special key for that context
    8665       25954 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8666           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8667             :     {
    8668           4 :         auto ctxt = d->getPROJContext();
    8669           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8670           4 :         if (crs)
    8671             :         {
    8672           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8673           4 :             if (ret)
    8674           4 :                 ret = CPLSPrintf("%s", ret);
    8675           4 :             proj_destroy(crs);
    8676           4 :             return ret;
    8677             :         }
    8678             :     }
    8679       25950 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8680           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8681             :     {
    8682           4 :         auto ctxt = d->getPROJContext();
    8683           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8684           4 :         if (crs)
    8685             :         {
    8686           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8687           4 :             if (ret)
    8688           4 :                 ret = CPLSPrintf("%s", ret);
    8689           4 :             proj_destroy(crs);
    8690           4 :             return ret;
    8691             :         }
    8692             :     }
    8693             : 
    8694             :     /* -------------------------------------------------------------------- */
    8695             :     /*      Find the node below which the authority should be put.          */
    8696             :     /* -------------------------------------------------------------------- */
    8697       25942 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8698             : 
    8699       25942 :     if (poNode == nullptr)
    8700       11103 :         return nullptr;
    8701             : 
    8702             :     /* -------------------------------------------------------------------- */
    8703             :     /*      Fetch AUTHORITY child if there is one.                          */
    8704             :     /* -------------------------------------------------------------------- */
    8705       14839 :     if (poNode->FindChild("AUTHORITY") == -1)
    8706        1441 :         return nullptr;
    8707             : 
    8708       13398 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8709             : 
    8710             :     /* -------------------------------------------------------------------- */
    8711             :     /*      Create a new authority node.                                    */
    8712             :     /* -------------------------------------------------------------------- */
    8713       13398 :     if (poNode->GetChildCount() < 2)
    8714           0 :         return nullptr;
    8715             : 
    8716       13398 :     return poNode->GetChild(0)->GetValue();
    8717             : }
    8718             : 
    8719             : /************************************************************************/
    8720             : /*                        OSRGetAuthorityName()                         */
    8721             : /************************************************************************/
    8722             : 
    8723             : /**
    8724             :  * \brief Get the authority name for a node.
    8725             :  *
    8726             :  * This function is the same as OGRSpatialReference::GetAuthorityName().
    8727             :  */
    8728         201 : const char *OSRGetAuthorityName(OGRSpatialReferenceH hSRS,
    8729             :                                 const char *pszTargetKey)
    8730             : 
    8731             : {
    8732         201 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityName", nullptr);
    8733             : 
    8734         201 :     return ToPointer(hSRS)->GetAuthorityName(pszTargetKey);
    8735             : }
    8736             : 
    8737             : /************************************************************************/
    8738             : /*                          GetOGCURN()                                 */
    8739             : /************************************************************************/
    8740             : 
    8741             : /**
    8742             :  * \brief Get a OGC URN string describing the CRS, when possible
    8743             :  *
    8744             :  * This method assumes that the CRS has a top-level identifier, or is
    8745             :  * a compound CRS whose horizontal and vertical parts have a top-level
    8746             :  * identifier.
    8747             :  *
    8748             :  * @return a string to free with CPLFree(), or nullptr when no result can be
    8749             :  * generated
    8750             :  *
    8751             :  * @since GDAL 3.5
    8752             :  */
    8753             : 
    8754          59 : char *OGRSpatialReference::GetOGCURN() const
    8755             : 
    8756             : {
    8757         118 :     TAKE_OPTIONAL_LOCK();
    8758             : 
    8759          59 :     const char *pszAuthName = GetAuthorityName(nullptr);
    8760          59 :     const char *pszAuthCode = GetAuthorityCode(nullptr);
    8761          59 :     if (pszAuthName && pszAuthCode)
    8762          56 :         return CPLStrdup(
    8763          56 :             CPLSPrintf("urn:ogc:def:crs:%s::%s", pszAuthName, pszAuthCode));
    8764           3 :     if (d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    8765           2 :         return nullptr;
    8766           1 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    8767           1 :     auto vertCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    8768           1 :     char *pszRet = nullptr;
    8769           1 :     if (horizCRS && vertCRS)
    8770             :     {
    8771           1 :         auto horizAuthName = proj_get_id_auth_name(horizCRS, 0);
    8772           1 :         auto horizAuthCode = proj_get_id_code(horizCRS, 0);
    8773           1 :         auto vertAuthName = proj_get_id_auth_name(vertCRS, 0);
    8774           1 :         auto vertAuthCode = proj_get_id_code(vertCRS, 0);
    8775           1 :         if (horizAuthName && horizAuthCode && vertAuthName && vertAuthCode)
    8776             :         {
    8777           1 :             pszRet = CPLStrdup(CPLSPrintf(
    8778             :                 "urn:ogc:def:crs,crs:%s::%s,crs:%s::%s", horizAuthName,
    8779             :                 horizAuthCode, vertAuthName, vertAuthCode));
    8780             :         }
    8781             :     }
    8782           1 :     proj_destroy(horizCRS);
    8783           1 :     proj_destroy(vertCRS);
    8784           1 :     return pszRet;
    8785             : }
    8786             : 
    8787             : /************************************************************************/
    8788             : /*                           StripVertical()                            */
    8789             : /************************************************************************/
    8790             : 
    8791             : /**
    8792             :  * \brief Convert a compound cs into a horizontal CS.
    8793             :  *
    8794             :  * If this SRS is of type COMPD_CS[] then the vertical CS and the root COMPD_CS
    8795             :  * nodes are stripped resulting and only the horizontal coordinate system
    8796             :  * portion remains (normally PROJCS, GEOGCS or LOCAL_CS).
    8797             :  *
    8798             :  * If this is not a compound coordinate system then nothing is changed.
    8799             :  *
    8800             :  * This method is the same as the C function OSRStripVertical().
    8801             :  *
    8802             :  * @since OGR 1.8.0
    8803             :  */
    8804             : 
    8805          44 : OGRErr OGRSpatialReference::StripVertical()
    8806             : 
    8807             : {
    8808          88 :     TAKE_OPTIONAL_LOCK();
    8809             : 
    8810          44 :     d->refreshProjObj();
    8811          44 :     d->demoteFromBoundCRS();
    8812          44 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    8813             :     {
    8814           0 :         d->undoDemoteFromBoundCRS();
    8815           0 :         return OGRERR_NONE;
    8816             :     }
    8817          44 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    8818          44 :     if (!horizCRS)
    8819             :     {
    8820           0 :         d->undoDemoteFromBoundCRS();
    8821           0 :         return OGRERR_FAILURE;
    8822             :     }
    8823             : 
    8824          44 :     bool reuseExistingBoundCRS = false;
    8825          44 :     if (d->m_pj_bound_crs_target)
    8826             :     {
    8827           4 :         auto type = proj_get_type(d->m_pj_bound_crs_target);
    8828           8 :         reuseExistingBoundCRS = type == PJ_TYPE_GEOCENTRIC_CRS ||
    8829           8 :                                 type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    8830             :                                 type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    8831             :     }
    8832             : 
    8833          44 :     if (reuseExistingBoundCRS)
    8834             :     {
    8835           4 :         auto newBoundCRS = proj_crs_create_bound_crs(
    8836           4 :             d->getPROJContext(), horizCRS, d->m_pj_bound_crs_target,
    8837           4 :             d->m_pj_bound_crs_co);
    8838           4 :         proj_destroy(horizCRS);
    8839           4 :         d->undoDemoteFromBoundCRS();
    8840           4 :         d->setPjCRS(newBoundCRS);
    8841             :     }
    8842             :     else
    8843             :     {
    8844          40 :         d->undoDemoteFromBoundCRS();
    8845          40 :         d->setPjCRS(horizCRS);
    8846             :     }
    8847             : 
    8848          44 :     return OGRERR_NONE;
    8849             : }
    8850             : 
    8851             : /************************************************************************/
    8852             : /*                            OSRStripVertical()                             */
    8853             : /************************************************************************/
    8854             : /**
    8855             :  * \brief Convert a compound cs into a horizontal CS.
    8856             :  *
    8857             :  * This function is the same as the C++ method
    8858             :  * OGRSpatialReference::StripVertical().
    8859             :  */
    8860           1 : OGRErr OSRStripVertical(OGRSpatialReferenceH hSRS)
    8861             : 
    8862             : {
    8863           1 :     VALIDATE_POINTER1(hSRS, "OSRStripVertical", OGRERR_FAILURE);
    8864             : 
    8865           1 :     return OGRSpatialReference::FromHandle(hSRS)->StripVertical();
    8866             : }
    8867             : 
    8868             : /************************************************************************/
    8869             : /*                   StripTOWGS84IfKnownDatumAndAllowed()               */
    8870             : /************************************************************************/
    8871             : 
    8872             : /**
    8873             :  * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
    8874             :  *        and this is allowed by the user.
    8875             :  *
    8876             :  * The default behavior is to remove TOWGS84 information if the CRS has a
    8877             :  * known horizontal datum. This can be disabled by setting the
    8878             :  * OSR_STRIP_TOWGS84 configuration option to NO.
    8879             :  *
    8880             :  * @return true if TOWGS84 has been removed.
    8881             :  * @since OGR 3.1.0
    8882             :  */
    8883             : 
    8884        8032 : bool OGRSpatialReference::StripTOWGS84IfKnownDatumAndAllowed()
    8885             : {
    8886        8032 :     if (CPLTestBool(CPLGetConfigOption("OSR_STRIP_TOWGS84", "YES")))
    8887             :     {
    8888        8029 :         if (StripTOWGS84IfKnownDatum())
    8889             :         {
    8890          71 :             CPLDebug("OSR", "TOWGS84 information has been removed. "
    8891             :                             "It can be kept by setting the OSR_STRIP_TOWGS84 "
    8892             :                             "configuration option to NO");
    8893          71 :             return true;
    8894             :         }
    8895             :     }
    8896        7961 :     return false;
    8897             : }
    8898             : 
    8899             : /************************************************************************/
    8900             : /*                      StripTOWGS84IfKnownDatum()                      */
    8901             : /************************************************************************/
    8902             : 
    8903             : /**
    8904             :  * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
    8905             :  *
    8906             :  * @return true if TOWGS84 has been removed.
    8907             :  * @since OGR 3.1.0
    8908             :  */
    8909             : 
    8910        8035 : bool OGRSpatialReference::StripTOWGS84IfKnownDatum()
    8911             : 
    8912             : {
    8913       16070 :     TAKE_OPTIONAL_LOCK();
    8914             : 
    8915        8035 :     d->refreshProjObj();
    8916        8035 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_BOUND_CRS)
    8917             :     {
    8918        7944 :         return false;
    8919             :     }
    8920          91 :     auto ctxt = d->getPROJContext();
    8921          91 :     auto baseCRS = proj_get_source_crs(ctxt, d->m_pj_crs);
    8922          91 :     if (proj_get_type(baseCRS) == PJ_TYPE_COMPOUND_CRS)
    8923             :     {
    8924           3 :         proj_destroy(baseCRS);
    8925           3 :         return false;
    8926             :     }
    8927             : 
    8928             :     // Known base CRS code ? Return base CRS
    8929          88 :     const char *pszCode = proj_get_id_code(baseCRS, 0);
    8930          88 :     if (pszCode)
    8931             :     {
    8932           2 :         d->setPjCRS(baseCRS);
    8933           2 :         return true;
    8934             :     }
    8935             : 
    8936          86 :     auto datum = proj_crs_get_datum(ctxt, baseCRS);
    8937             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    8938             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    8939             :     if (datum == nullptr)
    8940             :     {
    8941             :         datum = proj_crs_get_datum_ensemble(ctxt, baseCRS);
    8942             :     }
    8943             : #endif
    8944          86 :     if (!datum)
    8945             :     {
    8946           0 :         proj_destroy(baseCRS);
    8947           0 :         return false;
    8948             :     }
    8949             : 
    8950             :     // Known datum code ? Return base CRS
    8951          86 :     pszCode = proj_get_id_code(datum, 0);
    8952          86 :     if (pszCode)
    8953             :     {
    8954           3 :         proj_destroy(datum);
    8955           3 :         d->setPjCRS(baseCRS);
    8956           3 :         return true;
    8957             :     }
    8958             : 
    8959          83 :     const char *name = proj_get_name(datum);
    8960          83 :     if (EQUAL(name, "unknown"))
    8961             :     {
    8962           1 :         proj_destroy(datum);
    8963           1 :         proj_destroy(baseCRS);
    8964           1 :         return false;
    8965             :     }
    8966          82 :     const PJ_TYPE type = PJ_TYPE_GEODETIC_REFERENCE_FRAME;
    8967             :     PJ_OBJ_LIST *list =
    8968          82 :         proj_create_from_name(ctxt, nullptr, name, &type, 1, false, 1, nullptr);
    8969             : 
    8970          82 :     bool knownDatumName = false;
    8971          82 :     if (list)
    8972             :     {
    8973          82 :         if (proj_list_get_count(list) == 1)
    8974             :         {
    8975          69 :             knownDatumName = true;
    8976             :         }
    8977          82 :         proj_list_destroy(list);
    8978             :     }
    8979             : 
    8980          82 :     proj_destroy(datum);
    8981          82 :     if (knownDatumName)
    8982             :     {
    8983          69 :         d->setPjCRS(baseCRS);
    8984          69 :         return true;
    8985             :     }
    8986          13 :     proj_destroy(baseCRS);
    8987          13 :     return false;
    8988             : }
    8989             : 
    8990             : /************************************************************************/
    8991             : /*                             IsCompound()                             */
    8992             : /************************************************************************/
    8993             : 
    8994             : /**
    8995             :  * \brief Check if coordinate system is compound.
    8996             :  *
    8997             :  * This method is the same as the C function OSRIsCompound().
    8998             :  *
    8999             :  * @return TRUE if this is rooted with a COMPD_CS node.
    9000             :  */
    9001             : 
    9002       37870 : int OGRSpatialReference::IsCompound() const
    9003             : 
    9004             : {
    9005       37870 :     TAKE_OPTIONAL_LOCK();
    9006             : 
    9007       37870 :     d->refreshProjObj();
    9008       37870 :     d->demoteFromBoundCRS();
    9009       37870 :     bool isCompound = d->m_pjType == PJ_TYPE_COMPOUND_CRS;
    9010       37870 :     d->undoDemoteFromBoundCRS();
    9011       75740 :     return isCompound;
    9012             : }
    9013             : 
    9014             : /************************************************************************/
    9015             : /*                           OSRIsCompound()                            */
    9016             : /************************************************************************/
    9017             : 
    9018             : /**
    9019             :  * \brief Check if the coordinate system is compound.
    9020             :  *
    9021             :  * This function is the same as OGRSpatialReference::IsCompound().
    9022             :  */
    9023           5 : int OSRIsCompound(OGRSpatialReferenceH hSRS)
    9024             : 
    9025             : {
    9026           5 :     VALIDATE_POINTER1(hSRS, "OSRIsCompound", 0);
    9027             : 
    9028           5 :     return ToPointer(hSRS)->IsCompound();
    9029             : }
    9030             : 
    9031             : /************************************************************************/
    9032             : /*                            IsProjected()                             */
    9033             : /************************************************************************/
    9034             : 
    9035             : /**
    9036             :  * \brief Check if projected coordinate system.
    9037             :  *
    9038             :  * This method is the same as the C function OSRIsProjected().
    9039             :  *
    9040             :  * @return TRUE if this contains a PROJCS node indicating a it is a
    9041             :  * projected coordinate system. Also if it is a CompoundCRS made of a
    9042             :  * ProjectedCRS
    9043             :  */
    9044             : 
    9045       36154 : int OGRSpatialReference::IsProjected() const
    9046             : 
    9047             : {
    9048       36154 :     TAKE_OPTIONAL_LOCK();
    9049             : 
    9050       36154 :     d->refreshProjObj();
    9051       36154 :     d->demoteFromBoundCRS();
    9052       36154 :     bool isProjected = d->m_pjType == PJ_TYPE_PROJECTED_CRS;
    9053       36154 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9054             :     {
    9055             :         auto horizCRS =
    9056         142 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9057         142 :         if (horizCRS)
    9058             :         {
    9059         142 :             auto horizCRSType = proj_get_type(horizCRS);
    9060         142 :             isProjected = horizCRSType == PJ_TYPE_PROJECTED_CRS;
    9061         142 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9062             :             {
    9063           6 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9064           6 :                 if (base)
    9065             :                 {
    9066           6 :                     isProjected = proj_get_type(base) == PJ_TYPE_PROJECTED_CRS;
    9067           6 :                     proj_destroy(base);
    9068             :                 }
    9069             :             }
    9070         142 :             proj_destroy(horizCRS);
    9071             :         }
    9072             :     }
    9073       36154 :     d->undoDemoteFromBoundCRS();
    9074       72308 :     return isProjected;
    9075             : }
    9076             : 
    9077             : /************************************************************************/
    9078             : /*                           OSRIsProjected()                           */
    9079             : /************************************************************************/
    9080             : /**
    9081             :  * \brief Check if projected coordinate system.
    9082             :  *
    9083             :  * This function is the same as OGRSpatialReference::IsProjected().
    9084             :  */
    9085         413 : int OSRIsProjected(OGRSpatialReferenceH hSRS)
    9086             : 
    9087             : {
    9088         413 :     VALIDATE_POINTER1(hSRS, "OSRIsProjected", 0);
    9089             : 
    9090         413 :     return ToPointer(hSRS)->IsProjected();
    9091             : }
    9092             : 
    9093             : /************************************************************************/
    9094             : /*                            IsGeocentric()                            */
    9095             : /************************************************************************/
    9096             : 
    9097             : /**
    9098             :  * \brief Check if geocentric coordinate system.
    9099             :  *
    9100             :  * This method is the same as the C function OSRIsGeocentric().
    9101             :  *
    9102             :  * @return TRUE if this contains a GEOCCS node indicating a it is a
    9103             :  * geocentric coordinate system.
    9104             :  *
    9105             :  * @since OGR 1.9.0
    9106             :  */
    9107             : 
    9108       15650 : int OGRSpatialReference::IsGeocentric() const
    9109             : 
    9110             : {
    9111       15650 :     TAKE_OPTIONAL_LOCK();
    9112             : 
    9113       15650 :     d->refreshProjObj();
    9114       15650 :     d->demoteFromBoundCRS();
    9115       15650 :     bool isGeocentric = d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS;
    9116       15650 :     d->undoDemoteFromBoundCRS();
    9117       31300 :     return isGeocentric;
    9118             : }
    9119             : 
    9120             : /************************************************************************/
    9121             : /*                           OSRIsGeocentric()                          */
    9122             : /************************************************************************/
    9123             : /**
    9124             :  * \brief Check if geocentric coordinate system.
    9125             :  *
    9126             :  * This function is the same as OGRSpatialReference::IsGeocentric().
    9127             :  *
    9128             :  * @since OGR 1.9.0
    9129             :  */
    9130           2 : int OSRIsGeocentric(OGRSpatialReferenceH hSRS)
    9131             : 
    9132             : {
    9133           2 :     VALIDATE_POINTER1(hSRS, "OSRIsGeocentric", 0);
    9134             : 
    9135           2 :     return ToPointer(hSRS)->IsGeocentric();
    9136             : }
    9137             : 
    9138             : /************************************************************************/
    9139             : /*                            IsEmpty()                                 */
    9140             : /************************************************************************/
    9141             : 
    9142             : /**
    9143             :  * \brief Return if the SRS is not set.
    9144             :  */
    9145             : 
    9146      100419 : bool OGRSpatialReference::IsEmpty() const
    9147             : {
    9148      100419 :     TAKE_OPTIONAL_LOCK();
    9149             : 
    9150      100419 :     d->refreshProjObj();
    9151      200838 :     return d->m_pj_crs == nullptr;
    9152             : }
    9153             : 
    9154             : /************************************************************************/
    9155             : /*                            IsGeographic()                            */
    9156             : /************************************************************************/
    9157             : 
    9158             : /**
    9159             :  * \brief Check if geographic coordinate system.
    9160             :  *
    9161             :  * This method is the same as the C function OSRIsGeographic().
    9162             :  *
    9163             :  * @return TRUE if this spatial reference is geographic ... that is the
    9164             :  * root is a GEOGCS node. Also if it is a CompoundCRS made of a
    9165             :  * GeographicCRS
    9166             :  */
    9167             : 
    9168       48979 : int OGRSpatialReference::IsGeographic() const
    9169             : 
    9170             : {
    9171       48979 :     TAKE_OPTIONAL_LOCK();
    9172             : 
    9173       48979 :     d->refreshProjObj();
    9174       48979 :     d->demoteFromBoundCRS();
    9175       70351 :     bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9176       21372 :                   d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9177       48979 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9178             :     {
    9179             :         auto horizCRS =
    9180         291 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9181         291 :         if (horizCRS)
    9182             :         {
    9183         291 :             auto horizCRSType = proj_get_type(horizCRS);
    9184         291 :             isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9185             :                      horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9186         291 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9187             :             {
    9188          13 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9189          13 :                 if (base)
    9190             :                 {
    9191          13 :                     horizCRSType = proj_get_type(base);
    9192          13 :                     isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9193             :                              horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9194          13 :                     proj_destroy(base);
    9195             :                 }
    9196             :             }
    9197         291 :             proj_destroy(horizCRS);
    9198             :         }
    9199             :     }
    9200       48979 :     d->undoDemoteFromBoundCRS();
    9201       97958 :     return isGeog;
    9202             : }
    9203             : 
    9204             : /************************************************************************/
    9205             : /*                          OSRIsGeographic()                           */
    9206             : /************************************************************************/
    9207             : /**
    9208             :  * \brief Check if geographic coordinate system.
    9209             :  *
    9210             :  * This function is the same as OGRSpatialReference::IsGeographic().
    9211             :  */
    9212         334 : int OSRIsGeographic(OGRSpatialReferenceH hSRS)
    9213             : 
    9214             : {
    9215         334 :     VALIDATE_POINTER1(hSRS, "OSRIsGeographic", 0);
    9216             : 
    9217         334 :     return ToPointer(hSRS)->IsGeographic();
    9218             : }
    9219             : 
    9220             : /************************************************************************/
    9221             : /*                      IsDerivedGeographic()                           */
    9222             : /************************************************************************/
    9223             : 
    9224             : /**
    9225             :  * \brief Check if the CRS is a derived geographic coordinate system.
    9226             :  * (for example a rotated long/lat grid)
    9227             :  *
    9228             :  * This method is the same as the C function OSRIsDerivedGeographic().
    9229             :  *
    9230             :  * @since GDAL 3.1.0 and PROJ 6.3.0
    9231             :  */
    9232             : 
    9233       14889 : int OGRSpatialReference::IsDerivedGeographic() const
    9234             : 
    9235             : {
    9236       14889 :     TAKE_OPTIONAL_LOCK();
    9237             : 
    9238       14889 :     d->refreshProjObj();
    9239       14889 :     d->demoteFromBoundCRS();
    9240       24134 :     const bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9241        9245 :                         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9242             :     const bool isDerivedGeographic =
    9243       14889 :         isGeog && proj_is_derived_crs(d->getPROJContext(), d->m_pj_crs);
    9244       14889 :     d->undoDemoteFromBoundCRS();
    9245       29778 :     return isDerivedGeographic ? TRUE : FALSE;
    9246             : }
    9247             : 
    9248             : /************************************************************************/
    9249             : /*                      OSRIsDerivedGeographic()                        */
    9250             : /************************************************************************/
    9251             : /**
    9252             :  * \brief Check if the CRS is a derived geographic coordinate system.
    9253             :  * (for example a rotated long/lat grid)
    9254             :  *
    9255             :  * This function is the same as OGRSpatialReference::IsDerivedGeographic().
    9256             :  */
    9257           1 : int OSRIsDerivedGeographic(OGRSpatialReferenceH hSRS)
    9258             : 
    9259             : {
    9260           1 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedGeographic", 0);
    9261             : 
    9262           1 :     return ToPointer(hSRS)->IsDerivedGeographic();
    9263             : }
    9264             : 
    9265             : /************************************************************************/
    9266             : /*                      IsDerivedProjected()                            */
    9267             : /************************************************************************/
    9268             : 
    9269             : /**
    9270             :  * \brief Check if the CRS is a derived projected coordinate system.
    9271             :  *
    9272             :  * This method is the same as the C function OSRIsDerivedGeographic().
    9273             :  *
    9274             :  * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
    9275             :  */
    9276             : 
    9277           0 : int OGRSpatialReference::IsDerivedProjected() const
    9278             : 
    9279             : {
    9280             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
    9281             :     TAKE_OPTIONAL_LOCK();
    9282             :     d->refreshProjObj();
    9283             :     d->demoteFromBoundCRS();
    9284             :     const bool isDerivedProjected =
    9285             :         d->m_pjType == PJ_TYPE_DERIVED_PROJECTED_CRS;
    9286             :     d->undoDemoteFromBoundCRS();
    9287             :     return isDerivedProjected ? TRUE : FALSE;
    9288             : #else
    9289           0 :     return FALSE;
    9290             : #endif
    9291             : }
    9292             : 
    9293             : /************************************************************************/
    9294             : /*                      OSRIsDerivedProjected()                         */
    9295             : /************************************************************************/
    9296             : /**
    9297             :  * \brief Check if the CRS is a derived projected coordinate system.
    9298             :  *
    9299             :  * This function is the same as OGRSpatialReference::IsDerivedProjected().
    9300             :  *
    9301             :  * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
    9302             :  */
    9303           0 : int OSRIsDerivedProjected(OGRSpatialReferenceH hSRS)
    9304             : 
    9305             : {
    9306           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedProjected", 0);
    9307             : 
    9308           0 :     return ToPointer(hSRS)->IsDerivedProjected();
    9309             : }
    9310             : 
    9311             : /************************************************************************/
    9312             : /*                              IsLocal()                               */
    9313             : /************************************************************************/
    9314             : 
    9315             : /**
    9316             :  * \brief Check if local coordinate system.
    9317             :  *
    9318             :  * This method is the same as the C function OSRIsLocal().
    9319             :  *
    9320             :  * @return TRUE if this spatial reference is local ... that is the
    9321             :  * root is a LOCAL_CS node.
    9322             :  */
    9323             : 
    9324        7355 : int OGRSpatialReference::IsLocal() const
    9325             : 
    9326             : {
    9327        7355 :     TAKE_OPTIONAL_LOCK();
    9328        7355 :     d->refreshProjObj();
    9329       14710 :     return d->m_pjType == PJ_TYPE_ENGINEERING_CRS;
    9330             : }
    9331             : 
    9332             : /************************************************************************/
    9333             : /*                          OSRIsLocal()                                */
    9334             : /************************************************************************/
    9335             : /**
    9336             :  * \brief Check if local coordinate system.
    9337             :  *
    9338             :  * This function is the same as OGRSpatialReference::IsLocal().
    9339             :  */
    9340           8 : int OSRIsLocal(OGRSpatialReferenceH hSRS)
    9341             : 
    9342             : {
    9343           8 :     VALIDATE_POINTER1(hSRS, "OSRIsLocal", 0);
    9344             : 
    9345           8 :     return ToPointer(hSRS)->IsLocal();
    9346             : }
    9347             : 
    9348             : /************************************************************************/
    9349             : /*                            IsVertical()                              */
    9350             : /************************************************************************/
    9351             : 
    9352             : /**
    9353             :  * \brief Check if vertical coordinate system.
    9354             :  *
    9355             :  * This method is the same as the C function OSRIsVertical().
    9356             :  *
    9357             :  * @return TRUE if this contains a VERT_CS node indicating a it is a
    9358             :  * vertical coordinate system. Also if it is a CompoundCRS made of a
    9359             :  * VerticalCRS
    9360             :  *
    9361             :  * @since OGR 1.8.0
    9362             :  */
    9363             : 
    9364        8122 : int OGRSpatialReference::IsVertical() const
    9365             : 
    9366             : {
    9367        8122 :     TAKE_OPTIONAL_LOCK();
    9368        8122 :     d->refreshProjObj();
    9369        8122 :     d->demoteFromBoundCRS();
    9370        8122 :     bool isVertical = d->m_pjType == PJ_TYPE_VERTICAL_CRS;
    9371        8122 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9372             :     {
    9373             :         auto vertCRS =
    9374          32 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    9375          32 :         if (vertCRS)
    9376             :         {
    9377          32 :             const auto vertCRSType = proj_get_type(vertCRS);
    9378          32 :             isVertical = vertCRSType == PJ_TYPE_VERTICAL_CRS;
    9379          32 :             if (vertCRSType == PJ_TYPE_BOUND_CRS)
    9380             :             {
    9381           0 :                 auto base = proj_get_source_crs(d->getPROJContext(), vertCRS);
    9382           0 :                 if (base)
    9383             :                 {
    9384           0 :                     isVertical = proj_get_type(base) == PJ_TYPE_VERTICAL_CRS;
    9385           0 :                     proj_destroy(base);
    9386             :                 }
    9387             :             }
    9388          32 :             proj_destroy(vertCRS);
    9389             :         }
    9390             :     }
    9391        8122 :     d->undoDemoteFromBoundCRS();
    9392       16244 :     return isVertical;
    9393             : }
    9394             : 
    9395             : /************************************************************************/
    9396             : /*                           OSRIsVertical()                            */
    9397             : /************************************************************************/
    9398             : /**
    9399             :  * \brief Check if vertical coordinate system.
    9400             :  *
    9401             :  * This function is the same as OGRSpatialReference::IsVertical().
    9402             :  *
    9403             :  * @since OGR 1.8.0
    9404             :  */
    9405           0 : int OSRIsVertical(OGRSpatialReferenceH hSRS)
    9406             : 
    9407             : {
    9408           0 :     VALIDATE_POINTER1(hSRS, "OSRIsVertical", 0);
    9409             : 
    9410           0 :     return ToPointer(hSRS)->IsVertical();
    9411             : }
    9412             : 
    9413             : /************************************************************************/
    9414             : /*                            IsDynamic()                               */
    9415             : /************************************************************************/
    9416             : 
    9417             : /**
    9418             :  * \brief Check if a CRS is a dynamic CRS.
    9419             :  *
    9420             :  * A dynamic CRS relies on a dynamic datum, that is a datum that is not
    9421             :  * plate-fixed.
    9422             :  *
    9423             :  * This method is the same as the C function OSRIsDynamic().
    9424             :  *
    9425             :  * @return true if the CRS is dynamic
    9426             :  *
    9427             :  * @since OGR 3.4.0
    9428             :  *
    9429             :  * @see HasPointMotionOperation()
    9430             :  */
    9431             : 
    9432       12280 : bool OGRSpatialReference::IsDynamic() const
    9433             : 
    9434             : {
    9435       12280 :     TAKE_OPTIONAL_LOCK();
    9436       12280 :     bool isDynamic = false;
    9437       12280 :     d->refreshProjObj();
    9438       12280 :     d->demoteFromBoundCRS();
    9439       12280 :     auto ctxt = d->getPROJContext();
    9440       12280 :     PJ *horiz = nullptr;
    9441       12280 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9442             :     {
    9443          96 :         horiz = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    9444             :     }
    9445       12184 :     else if (d->m_pj_crs)
    9446             :     {
    9447       12103 :         horiz = proj_clone(ctxt, d->m_pj_crs);
    9448             :     }
    9449       12280 :     if (horiz && proj_get_type(horiz) == PJ_TYPE_BOUND_CRS)
    9450             :     {
    9451           6 :         auto baseCRS = proj_get_source_crs(ctxt, horiz);
    9452           6 :         if (baseCRS)
    9453             :         {
    9454           6 :             proj_destroy(horiz);
    9455           6 :             horiz = baseCRS;
    9456             :         }
    9457             :     }
    9458       12280 :     auto datum = horiz ? proj_crs_get_datum(ctxt, horiz) : nullptr;
    9459       12280 :     if (datum)
    9460             :     {
    9461       12177 :         const auto type = proj_get_type(datum);
    9462       12177 :         isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9463             :                     type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9464       12177 :         if (!isDynamic)
    9465             :         {
    9466       12177 :             const char *auth_name = proj_get_id_auth_name(datum, 0);
    9467       12177 :             const char *code = proj_get_id_code(datum, 0);
    9468       12177 :             if (auth_name && code && EQUAL(auth_name, "EPSG") &&
    9469       11779 :                 EQUAL(code, "6326"))
    9470             :             {
    9471        7257 :                 isDynamic = true;
    9472             :             }
    9473             :         }
    9474       12177 :         proj_destroy(datum);
    9475             :     }
    9476             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9477             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9478             :     else
    9479             :     {
    9480             :         auto ensemble =
    9481             :             horiz ? proj_crs_get_datum_ensemble(ctxt, horiz) : nullptr;
    9482             :         if (ensemble)
    9483             :         {
    9484             :             auto member = proj_datum_ensemble_get_member(ctxt, ensemble, 0);
    9485             :             if (member)
    9486             :             {
    9487             :                 const auto type = proj_get_type(member);
    9488             :                 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9489             :                             type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9490             :                 proj_destroy(member);
    9491             :             }
    9492             :             proj_destroy(ensemble);
    9493             :         }
    9494             :     }
    9495             : #endif
    9496       12280 :     proj_destroy(horiz);
    9497       12280 :     d->undoDemoteFromBoundCRS();
    9498       24560 :     return isDynamic;
    9499             : }
    9500             : 
    9501             : /************************************************************************/
    9502             : /*                           OSRIsDynamic()                             */
    9503             : /************************************************************************/
    9504             : /**
    9505             :  * \brief Check if a CRS is a dynamic CRS.
    9506             :  *
    9507             :  * A dynamic CRS relies on a dynamic datum, that is a datum that is not
    9508             :  * plate-fixed.
    9509             :  *
    9510             :  * This function is the same as OGRSpatialReference::IsDynamic().
    9511             :  *
    9512             :  * @since OGR 3.4.0
    9513             :  */
    9514           0 : int OSRIsDynamic(OGRSpatialReferenceH hSRS)
    9515             : 
    9516             : {
    9517           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDynamic", 0);
    9518             : 
    9519           0 :     return ToPointer(hSRS)->IsDynamic();
    9520             : }
    9521             : 
    9522             : /************************************************************************/
    9523             : /*                         HasPointMotionOperation()                    */
    9524             : /************************************************************************/
    9525             : 
    9526             : /**
    9527             :  * \brief Check if a CRS has at least an associated point motion operation.
    9528             :  *
    9529             :  * Some CRS are not formally declared as dynamic, but may behave as such
    9530             :  * in practice due to the presence of point motion operation, to perform
    9531             :  * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
    9532             :  *
    9533             :  * @return true if the CRS has at least an associated point motion operation.
    9534             :  *
    9535             :  * @since OGR 3.8.0 and PROJ 9.4.0
    9536             :  *
    9537             :  * @see IsDynamic()
    9538             :  */
    9539             : 
    9540           5 : bool OGRSpatialReference::HasPointMotionOperation() const
    9541             : 
    9542             : {
    9543             : #if PROJ_VERSION_MAJOR > 9 ||                                                  \
    9544             :     (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 4)
    9545             :     TAKE_OPTIONAL_LOCK();
    9546             :     d->refreshProjObj();
    9547             :     d->demoteFromBoundCRS();
    9548             :     auto ctxt = d->getPROJContext();
    9549             :     auto res =
    9550             :         CPL_TO_BOOL(proj_crs_has_point_motion_operation(ctxt, d->m_pj_crs));
    9551             :     d->undoDemoteFromBoundCRS();
    9552             :     return res;
    9553             : #else
    9554           5 :     return false;
    9555             : #endif
    9556             : }
    9557             : 
    9558             : /************************************************************************/
    9559             : /*                      OSRHasPointMotionOperation()                    */
    9560             : /************************************************************************/
    9561             : 
    9562             : /**
    9563             :  * \brief Check if a CRS has at least an associated point motion operation.
    9564             :  *
    9565             :  * Some CRS are not formally declared as dynamic, but may behave as such
    9566             :  * in practice due to the presence of point motion operation, to perform
    9567             :  * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
    9568             :  *
    9569             :  * This function is the same as OGRSpatialReference::HasPointMotionOperation().
    9570             :  *
    9571             :  * @since OGR 3.8.0 and PROJ 9.4.0
    9572             :  */
    9573           0 : int OSRHasPointMotionOperation(OGRSpatialReferenceH hSRS)
    9574             : 
    9575             : {
    9576           0 :     VALIDATE_POINTER1(hSRS, "OSRHasPointMotionOperation", 0);
    9577             : 
    9578           0 :     return ToPointer(hSRS)->HasPointMotionOperation();
    9579             : }
    9580             : 
    9581             : /************************************************************************/
    9582             : /*                            CloneGeogCS()                             */
    9583             : /************************************************************************/
    9584             : 
    9585             : /**
    9586             :  * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
    9587             :  * object.
    9588             :  *
    9589             :  * @return a new SRS, which becomes the responsibility of the caller.
    9590             :  */
    9591        3579 : OGRSpatialReference *OGRSpatialReference::CloneGeogCS() const
    9592             : 
    9593             : {
    9594        7158 :     TAKE_OPTIONAL_LOCK();
    9595        3579 :     d->refreshProjObj();
    9596        3579 :     if (d->m_pj_crs)
    9597             :     {
    9598        3579 :         if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    9599           0 :             return nullptr;
    9600             : 
    9601             :         auto geodCRS =
    9602        3579 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9603        3579 :         if (geodCRS)
    9604             :         {
    9605        3579 :             OGRSpatialReference *poNewSRS = new OGRSpatialReference();
    9606        3579 :             if (d->m_pjType == PJ_TYPE_BOUND_CRS)
    9607             :             {
    9608             :                 PJ *hub_crs =
    9609          13 :                     proj_get_target_crs(d->getPROJContext(), d->m_pj_crs);
    9610          13 :                 PJ *co = proj_crs_get_coordoperation(d->getPROJContext(),
    9611          13 :                                                      d->m_pj_crs);
    9612          13 :                 auto temp = proj_crs_create_bound_crs(d->getPROJContext(),
    9613             :                                                       geodCRS, hub_crs, co);
    9614          13 :                 proj_destroy(geodCRS);
    9615          13 :                 geodCRS = temp;
    9616          13 :                 proj_destroy(hub_crs);
    9617          13 :                 proj_destroy(co);
    9618             :             }
    9619             : 
    9620             :             /* --------------------------------------------------------------------
    9621             :              */
    9622             :             /*      We have to reconstruct the GEOGCS node for geocentric */
    9623             :             /*      coordinate systems. */
    9624             :             /* --------------------------------------------------------------------
    9625             :              */
    9626        3579 :             if (proj_get_type(geodCRS) == PJ_TYPE_GEOCENTRIC_CRS)
    9627             :             {
    9628           0 :                 auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    9629             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9630             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9631             :                 if (datum == nullptr)
    9632             :                 {
    9633             :                     datum = proj_crs_get_datum_ensemble(d->getPROJContext(),
    9634             :                                                         geodCRS);
    9635             :                 }
    9636             : #endif
    9637           0 :                 if (datum)
    9638             :                 {
    9639           0 :                     auto cs = proj_create_ellipsoidal_2D_cs(
    9640             :                         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE,
    9641             :                         nullptr, 0);
    9642           0 :                     auto temp = proj_create_geographic_crs_from_datum(
    9643             :                         d->getPROJContext(), "unnamed", datum, cs);
    9644           0 :                     proj_destroy(datum);
    9645           0 :                     proj_destroy(cs);
    9646           0 :                     proj_destroy(geodCRS);
    9647           0 :                     geodCRS = temp;
    9648             :                 }
    9649             :             }
    9650             : 
    9651        3579 :             poNewSRS->d->setPjCRS(geodCRS);
    9652        3579 :             if (d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
    9653        2362 :                 poNewSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    9654        3579 :             return poNewSRS;
    9655             :         }
    9656             :     }
    9657           0 :     return nullptr;
    9658             : }
    9659             : 
    9660             : /************************************************************************/
    9661             : /*                           OSRCloneGeogCS()                           */
    9662             : /************************************************************************/
    9663             : /**
    9664             :  * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
    9665             :  * object.
    9666             :  *
    9667             :  * This function is the same as OGRSpatialReference::CloneGeogCS().
    9668             :  */
    9669         150 : OGRSpatialReferenceH CPL_STDCALL OSRCloneGeogCS(OGRSpatialReferenceH hSource)
    9670             : 
    9671             : {
    9672         150 :     VALIDATE_POINTER1(hSource, "OSRCloneGeogCS", nullptr);
    9673             : 
    9674         150 :     return ToHandle(ToPointer(hSource)->CloneGeogCS());
    9675             : }
    9676             : 
    9677             : /************************************************************************/
    9678             : /*                            IsSameGeogCS()                            */
    9679             : /************************************************************************/
    9680             : 
    9681             : /**
    9682             :  * \brief Do the GeogCS'es match?
    9683             :  *
    9684             :  * This method is the same as the C function OSRIsSameGeogCS().
    9685             :  *
    9686             :  * @param poOther the SRS being compared against.
    9687             :  *
    9688             :  * @return TRUE if they are the same or FALSE otherwise.
    9689             :  */
    9690             : 
    9691        7364 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther) const
    9692             : 
    9693             : {
    9694        7364 :     return IsSameGeogCS(poOther, nullptr);
    9695             : }
    9696             : 
    9697             : /**
    9698             :  * \brief Do the GeogCS'es match?
    9699             :  *
    9700             :  * This method is the same as the C function OSRIsSameGeogCS().
    9701             :  *
    9702             :  * @param poOther the SRS being compared against.
    9703             :  * @param papszOptions options. ignored
    9704             :  *
    9705             :  * @return TRUE if they are the same or FALSE otherwise.
    9706             :  */
    9707             : 
    9708        7364 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther,
    9709             :                                       const char *const *papszOptions) const
    9710             : 
    9711             : {
    9712       14728 :     TAKE_OPTIONAL_LOCK();
    9713             : 
    9714        7364 :     CPL_IGNORE_RET_VAL(papszOptions);
    9715             : 
    9716        7364 :     d->refreshProjObj();
    9717        7364 :     poOther->d->refreshProjObj();
    9718        7364 :     if (!d->m_pj_crs || !poOther->d->m_pj_crs)
    9719           0 :         return FALSE;
    9720        7364 :     if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9721        7364 :         d->m_pjType == PJ_TYPE_VERTICAL_CRS ||
    9722       22091 :         poOther->d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9723        7363 :         poOther->d->m_pjType == PJ_TYPE_VERTICAL_CRS)
    9724             :     {
    9725           0 :         return FALSE;
    9726             :     }
    9727             : 
    9728        7364 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9729             :     auto otherGeodCRS =
    9730        7364 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poOther->d->m_pj_crs);
    9731        7364 :     if (!geodCRS || !otherGeodCRS)
    9732             :     {
    9733           0 :         proj_destroy(geodCRS);
    9734           0 :         proj_destroy(otherGeodCRS);
    9735           0 :         return FALSE;
    9736             :     }
    9737             : 
    9738        7364 :     int ret = proj_is_equivalent_to(
    9739             :         geodCRS, otherGeodCRS, PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS);
    9740             : 
    9741        7364 :     proj_destroy(geodCRS);
    9742        7364 :     proj_destroy(otherGeodCRS);
    9743        7364 :     return ret;
    9744             : }
    9745             : 
    9746             : /************************************************************************/
    9747             : /*                          OSRIsSameGeogCS()                           */
    9748             : /************************************************************************/
    9749             : 
    9750             : /**
    9751             :  * \brief Do the GeogCS'es match?
    9752             :  *
    9753             :  * This function is the same as OGRSpatialReference::IsSameGeogCS().
    9754             :  */
    9755           0 : int OSRIsSameGeogCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9756             : 
    9757             : {
    9758           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameGeogCS", 0);
    9759           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameGeogCS", 0);
    9760             : 
    9761           0 :     return ToPointer(hSRS1)->IsSameGeogCS(ToPointer(hSRS2));
    9762             : }
    9763             : 
    9764             : /************************************************************************/
    9765             : /*                            IsSameVertCS()                            */
    9766             : /************************************************************************/
    9767             : 
    9768             : /**
    9769             :  * \brief Do the VertCS'es match?
    9770             :  *
    9771             :  * This method is the same as the C function OSRIsSameVertCS().
    9772             :  *
    9773             :  * @param poOther the SRS being compared against.
    9774             :  *
    9775             :  * @return TRUE if they are the same or FALSE otherwise.
    9776             :  */
    9777             : 
    9778           0 : int OGRSpatialReference::IsSameVertCS(const OGRSpatialReference *poOther) const
    9779             : 
    9780             : {
    9781           0 :     TAKE_OPTIONAL_LOCK();
    9782             : 
    9783             :     /* -------------------------------------------------------------------- */
    9784             :     /*      Does the datum name match?                                      */
    9785             :     /* -------------------------------------------------------------------- */
    9786           0 :     const char *pszThisValue = this->GetAttrValue("VERT_DATUM");
    9787           0 :     const char *pszOtherValue = poOther->GetAttrValue("VERT_DATUM");
    9788             : 
    9789           0 :     if (pszThisValue == nullptr || pszOtherValue == nullptr ||
    9790           0 :         !EQUAL(pszThisValue, pszOtherValue))
    9791           0 :         return FALSE;
    9792             : 
    9793             :     /* -------------------------------------------------------------------- */
    9794             :     /*      Do the units match?                                             */
    9795             :     /* -------------------------------------------------------------------- */
    9796           0 :     pszThisValue = this->GetAttrValue("VERT_CS|UNIT", 1);
    9797           0 :     if (pszThisValue == nullptr)
    9798           0 :         pszThisValue = "1.0";
    9799             : 
    9800           0 :     pszOtherValue = poOther->GetAttrValue("VERT_CS|UNIT", 1);
    9801           0 :     if (pszOtherValue == nullptr)
    9802           0 :         pszOtherValue = "1.0";
    9803             : 
    9804           0 :     if (std::abs(CPLAtof(pszOtherValue) - CPLAtof(pszThisValue)) > 0.00000001)
    9805           0 :         return FALSE;
    9806             : 
    9807           0 :     return TRUE;
    9808             : }
    9809             : 
    9810             : /************************************************************************/
    9811             : /*                          OSRIsSameVertCS()                           */
    9812             : /************************************************************************/
    9813             : 
    9814             : /**
    9815             :  * \brief Do the VertCS'es match?
    9816             :  *
    9817             :  * This function is the same as OGRSpatialReference::IsSameVertCS().
    9818             :  */
    9819           0 : int OSRIsSameVertCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9820             : 
    9821             : {
    9822           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameVertCS", 0);
    9823           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameVertCS", 0);
    9824             : 
    9825           0 :     return ToPointer(hSRS1)->IsSameVertCS(ToPointer(hSRS2));
    9826             : }
    9827             : 
    9828             : /************************************************************************/
    9829             : /*                               IsSame()                               */
    9830             : /************************************************************************/
    9831             : 
    9832             : /**
    9833             :  * \brief Do these two spatial references describe the same system ?
    9834             :  *
    9835             :  * @param poOtherSRS the SRS being compared to.
    9836             :  *
    9837             :  * @return TRUE if equivalent or FALSE otherwise.
    9838             :  */
    9839             : 
    9840        4472 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS) const
    9841             : 
    9842             : {
    9843        4472 :     return IsSame(poOtherSRS, nullptr);
    9844             : }
    9845             : 
    9846             : /**
    9847             :  * \brief Do these two spatial references describe the same system ?
    9848             :  *
    9849             :  * This also takes into account the data axis to CRS axis mapping by default
    9850             :  *
    9851             :  * @param poOtherSRS the SRS being compared to.
    9852             :  * @param papszOptions options. NULL or NULL terminated list of options.
    9853             :  * Currently supported options are:
    9854             :  * <ul>
    9855             :  * <li>IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES/NO. Defaults to NO</li>
    9856             :  * <li>CRITERION=STRICT/EQUIVALENT/EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.
    9857             :  *     Defaults to EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.</li>
    9858             :  * <li>IGNORE_COORDINATE_EPOCH=YES/NO. Defaults to NO</li>
    9859             :  * </ul>
    9860             :  *
    9861             :  * @return TRUE if equivalent or FALSE otherwise.
    9862             :  */
    9863             : 
    9864       13045 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS,
    9865             :                                 const char *const *papszOptions) const
    9866             : 
    9867             : {
    9868       26089 :     TAKE_OPTIONAL_LOCK();
    9869             : 
    9870       13045 :     d->refreshProjObj();
    9871       13045 :     poOtherSRS->d->refreshProjObj();
    9872       13045 :     if (!d->m_pj_crs || !poOtherSRS->d->m_pj_crs)
    9873          51 :         return d->m_pj_crs == poOtherSRS->d->m_pj_crs;
    9874       12994 :     if (!CPLTestBool(CSLFetchNameValueDef(
    9875             :             papszOptions, "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING", "NO")))
    9876             :     {
    9877       12424 :         if (d->m_axisMapping != poOtherSRS->d->m_axisMapping)
    9878        2116 :             return false;
    9879             :     }
    9880             : 
    9881       10878 :     if (!CPLTestBool(CSLFetchNameValueDef(papszOptions,
    9882             :                                           "IGNORE_COORDINATE_EPOCH", "NO")))
    9883             :     {
    9884       10543 :         if (d->m_coordinateEpoch != poOtherSRS->d->m_coordinateEpoch)
    9885          27 :             return false;
    9886             :     }
    9887             : 
    9888       10851 :     bool reboundSelf = false;
    9889       10851 :     bool reboundOther = false;
    9890       10902 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS &&
    9891          51 :         poOtherSRS->d->m_pjType != PJ_TYPE_BOUND_CRS)
    9892             :     {
    9893          14 :         d->demoteFromBoundCRS();
    9894          14 :         reboundSelf = true;
    9895             :     }
    9896       21637 :     else if (d->m_pjType != PJ_TYPE_BOUND_CRS &&
    9897       10800 :              poOtherSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
    9898             :     {
    9899          28 :         poOtherSRS->d->demoteFromBoundCRS();
    9900          28 :         reboundOther = true;
    9901             :     }
    9902             : 
    9903       10851 :     PJ_COMPARISON_CRITERION criterion =
    9904             :         PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS;
    9905       10851 :     const char *pszCriterion = CSLFetchNameValueDef(
    9906             :         papszOptions, "CRITERION", "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS");
    9907       10851 :     if (EQUAL(pszCriterion, "STRICT"))
    9908           0 :         criterion = PJ_COMP_STRICT;
    9909       10851 :     else if (EQUAL(pszCriterion, "EQUIVALENT"))
    9910        6196 :         criterion = PJ_COMP_EQUIVALENT;
    9911        4655 :     else if (!EQUAL(pszCriterion, "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS"))
    9912             :     {
    9913           0 :         CPLError(CE_Warning, CPLE_NotSupported,
    9914             :                  "Unsupported value for CRITERION: %s", pszCriterion);
    9915             :     }
    9916             :     int ret =
    9917       10851 :         proj_is_equivalent_to(d->m_pj_crs, poOtherSRS->d->m_pj_crs, criterion);
    9918       10851 :     if (reboundSelf)
    9919          14 :         d->undoDemoteFromBoundCRS();
    9920       10851 :     if (reboundOther)
    9921          28 :         poOtherSRS->d->undoDemoteFromBoundCRS();
    9922             : 
    9923       10850 :     return ret;
    9924             : }
    9925             : 
    9926             : /************************************************************************/
    9927             : /*                             OSRIsSame()                              */
    9928             : /************************************************************************/
    9929             : 
    9930             : /**
    9931             :  * \brief Do these two spatial references describe the same system ?
    9932             :  *
    9933             :  * This function is the same as OGRSpatialReference::IsSame().
    9934             :  */
    9935          30 : int OSRIsSame(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9936             : 
    9937             : {
    9938          30 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
    9939          30 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
    9940             : 
    9941          30 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2));
    9942             : }
    9943             : 
    9944             : /************************************************************************/
    9945             : /*                             OSRIsSameEx()                            */
    9946             : /************************************************************************/
    9947             : 
    9948             : /**
    9949             :  * \brief Do these two spatial references describe the same system ?
    9950             :  *
    9951             :  * This function is the same as OGRSpatialReference::IsSame().
    9952             :  */
    9953         585 : int OSRIsSameEx(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2,
    9954             :                 const char *const *papszOptions)
    9955             : {
    9956         585 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
    9957         585 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
    9958             : 
    9959         585 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2), papszOptions);
    9960             : }
    9961             : 
    9962             : /************************************************************************/
    9963             : /*                    convertToOtherProjection()                        */
    9964             : /************************************************************************/
    9965             : 
    9966             : /**
    9967             :  * \brief Convert to another equivalent projection
    9968             :  *
    9969             :  * Currently implemented:
    9970             :  * <ul>
    9971             :  * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
    9972             :  * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
    9973             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
    9974             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
    9975             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
    9976             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
    9977             :  * </ul>
    9978             :  *
    9979             :  * @param pszTargetProjection target projection.
    9980             :  * @param papszOptions lists of options. None supported currently.
    9981             :  * @return a new SRS, or NULL in case of error.
    9982             :  *
    9983             :  * @since GDAL 2.3
    9984             :  */
    9985          89 : OGRSpatialReference *OGRSpatialReference::convertToOtherProjection(
    9986             :     const char *pszTargetProjection,
    9987             :     CPL_UNUSED const char *const *papszOptions) const
    9988             : {
    9989         178 :     TAKE_OPTIONAL_LOCK();
    9990             : 
    9991          89 :     if (pszTargetProjection == nullptr)
    9992           1 :         return nullptr;
    9993             :     int new_code;
    9994          88 :     if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_1SP))
    9995             :     {
    9996           6 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A;
    9997             :     }
    9998          82 :     else if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_2SP))
    9999             :     {
   10000           5 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B;
   10001             :     }
   10002          77 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
   10003             :     {
   10004          65 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP;
   10005             :     }
   10006          12 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
   10007             :     {
   10008          11 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP;
   10009             :     }
   10010             :     else
   10011             :     {
   10012           1 :         return nullptr;
   10013             :     }
   10014             : 
   10015          87 :     d->refreshProjObj();
   10016          87 :     d->demoteFromBoundCRS();
   10017          87 :     OGRSpatialReference *poNewSRS = nullptr;
   10018          87 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
   10019             :     {
   10020             :         auto conv =
   10021          86 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10022          86 :         auto new_conv = proj_convert_conversion_to_other_method(
   10023             :             d->getPROJContext(), conv, new_code, nullptr);
   10024          86 :         proj_destroy(conv);
   10025          86 :         if (new_conv)
   10026             :         {
   10027             :             auto geodCRS =
   10028          72 :                 proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10029          72 :             auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   10030          72 :                                                      d->m_pj_crs);
   10031          72 :             if (geodCRS && cs)
   10032             :             {
   10033          72 :                 auto new_proj_crs = proj_create_projected_crs(
   10034          72 :                     d->getPROJContext(), proj_get_name(d->m_pj_crs), geodCRS,
   10035             :                     new_conv, cs);
   10036          72 :                 proj_destroy(new_conv);
   10037          72 :                 if (new_proj_crs)
   10038             :                 {
   10039          72 :                     poNewSRS = new OGRSpatialReference();
   10040             : 
   10041          72 :                     if (d->m_pj_bound_crs_target && d->m_pj_bound_crs_co)
   10042             :                     {
   10043           9 :                         auto boundCRS = proj_crs_create_bound_crs(
   10044             :                             d->getPROJContext(), new_proj_crs,
   10045           9 :                             d->m_pj_bound_crs_target, d->m_pj_bound_crs_co);
   10046           9 :                         if (boundCRS)
   10047             :                         {
   10048           9 :                             proj_destroy(new_proj_crs);
   10049           9 :                             new_proj_crs = boundCRS;
   10050             :                         }
   10051             :                     }
   10052             : 
   10053          72 :                     poNewSRS->d->setPjCRS(new_proj_crs);
   10054             :                 }
   10055             :             }
   10056          72 :             proj_destroy(geodCRS);
   10057          72 :             proj_destroy(cs);
   10058             :         }
   10059             :     }
   10060          87 :     d->undoDemoteFromBoundCRS();
   10061          87 :     return poNewSRS;
   10062             : }
   10063             : 
   10064             : /************************************************************************/
   10065             : /*                    OSRConvertToOtherProjection()                     */
   10066             : /************************************************************************/
   10067             : 
   10068             : /**
   10069             :  * \brief Convert to another equivalent projection
   10070             :  *
   10071             :  * Currently implemented:
   10072             :  * <ul>
   10073             :  * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
   10074             :  * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
   10075             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
   10076             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
   10077             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
   10078             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
   10079             :  * </ul>
   10080             :  *
   10081             :  * @param hSRS source SRS
   10082             :  * @param pszTargetProjection target projection.
   10083             :  * @param papszOptions lists of options. None supported currently.
   10084             :  * @return a new SRS, or NULL in case of error.
   10085             :  *
   10086             :  * @since GDAL 2.3
   10087             :  */
   10088             : OGRSpatialReferenceH
   10089          28 : OSRConvertToOtherProjection(OGRSpatialReferenceH hSRS,
   10090             :                             const char *pszTargetProjection,
   10091             :                             const char *const *papszOptions)
   10092             : {
   10093          28 :     VALIDATE_POINTER1(hSRS, "OSRConvertToOtherProjection", nullptr);
   10094          28 :     return ToHandle(ToPointer(hSRS)->convertToOtherProjection(
   10095          28 :         pszTargetProjection, papszOptions));
   10096             : }
   10097             : 
   10098             : /************************************************************************/
   10099             : /*                           OSRFindMatches()                           */
   10100             : /************************************************************************/
   10101             : 
   10102             : /**
   10103             :  * \brief Try to identify a match between the passed SRS and a related SRS
   10104             :  * in a catalog.
   10105             :  *
   10106             :  * Matching may be partial, or may fail.
   10107             :  * Returned entries will be sorted by decreasing match confidence (first
   10108             :  * entry has the highest match confidence).
   10109             :  *
   10110             :  * The exact way matching is done may change in future versions. Starting with
   10111             :  * GDAL 3.0, it relies on PROJ' proj_identify() function.
   10112             :  *
   10113             :  * This function is the same as OGRSpatialReference::FindMatches().
   10114             :  *
   10115             :  * @param hSRS SRS to match
   10116             :  * @param papszOptions NULL terminated list of options or NULL
   10117             :  * @param pnEntries Output parameter. Number of values in the returned array.
   10118             :  * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
   10119             :  * will be allocated to an array of *pnEntries whose values between 0 and 100
   10120             :  * indicate the confidence in the match. 100 is the highest confidence level.
   10121             :  * The array must be freed with CPLFree().
   10122             :  *
   10123             :  * @return an array of SRS that match the passed SRS, or NULL. Must be freed
   10124             :  * with OSRFreeSRSArray()
   10125             :  *
   10126             :  * @since GDAL 2.3
   10127             :  */
   10128           8 : OGRSpatialReferenceH *OSRFindMatches(OGRSpatialReferenceH hSRS,
   10129             :                                      char **papszOptions, int *pnEntries,
   10130             :                                      int **ppanMatchConfidence)
   10131             : {
   10132           8 :     if (pnEntries)
   10133           8 :         *pnEntries = 0;
   10134           8 :     if (ppanMatchConfidence)
   10135           8 :         *ppanMatchConfidence = nullptr;
   10136           8 :     VALIDATE_POINTER1(hSRS, "OSRFindMatches", nullptr);
   10137             : 
   10138           8 :     OGRSpatialReference *poSRS = ToPointer(hSRS);
   10139           8 :     return poSRS->FindMatches(papszOptions, pnEntries, ppanMatchConfidence);
   10140             : }
   10141             : 
   10142             : /************************************************************************/
   10143             : /*                           OSRFreeSRSArray()                          */
   10144             : /************************************************************************/
   10145             : 
   10146             : /**
   10147             :  * \brief Free return of OSRIdentifyMatches()
   10148             :  *
   10149             :  * @param pahSRS array of SRS (must be NULL terminated)
   10150             :  * @since GDAL 2.3
   10151             :  */
   10152         183 : void OSRFreeSRSArray(OGRSpatialReferenceH *pahSRS)
   10153             : {
   10154         183 :     if (pahSRS != nullptr)
   10155             :     {
   10156        1726 :         for (int i = 0; pahSRS[i] != nullptr; ++i)
   10157             :         {
   10158        1561 :             OSRRelease(pahSRS[i]);
   10159             :         }
   10160         165 :         CPLFree(pahSRS);
   10161             :     }
   10162         183 : }
   10163             : 
   10164             : /************************************************************************/
   10165             : /*                         FindBestMatch()                              */
   10166             : /************************************************************************/
   10167             : 
   10168             : /**
   10169             :  * \brief Try to identify the best match between the passed SRS and a related
   10170             :  * SRS in a catalog.
   10171             :  *
   10172             :  * This is a wrapper over OGRSpatialReference::FindMatches() that takes care
   10173             :  * of filtering its output.
   10174             :  * Only matches whose confidence is greater or equal to nMinimumMatchConfidence
   10175             :  * will be considered. If there is a single match, it is returned.
   10176             :  * If there are several matches, only return the one under the
   10177             :  * pszPreferredAuthority, if there is a single one under that authority.
   10178             :  *
   10179             :  * @param nMinimumMatchConfidence Minimum match confidence (value between 0 and
   10180             :  * 100). If set to 0, 90 is used.
   10181             :  * @param pszPreferredAuthority Preferred CRS authority. If set to nullptr,
   10182             :  * "EPSG" is used.
   10183             :  * @param papszOptions NULL terminated list of options or NULL. No option is
   10184             :  * defined at time of writing.
   10185             :  *
   10186             :  * @return a new OGRSpatialReference* object to free with Release(), or nullptr
   10187             :  *
   10188             :  * @since GDAL 3.6
   10189             :  * @see OGRSpatialReference::FindMatches()
   10190             :  */
   10191             : OGRSpatialReference *
   10192        1249 : OGRSpatialReference::FindBestMatch(int nMinimumMatchConfidence,
   10193             :                                    const char *pszPreferredAuthority,
   10194             :                                    CSLConstList papszOptions) const
   10195             : {
   10196        2498 :     TAKE_OPTIONAL_LOCK();
   10197             : 
   10198        1249 :     CPL_IGNORE_RET_VAL(papszOptions);  // ignored for now.
   10199             : 
   10200        1249 :     if (nMinimumMatchConfidence == 0)
   10201           0 :         nMinimumMatchConfidence = 90;
   10202        1249 :     if (pszPreferredAuthority == nullptr)
   10203         200 :         pszPreferredAuthority = "EPSG";
   10204             : 
   10205             :     // Try to identify the CRS with the database
   10206        1249 :     int nEntries = 0;
   10207        1249 :     int *panConfidence = nullptr;
   10208             :     OGRSpatialReferenceH *pahSRS =
   10209        1249 :         FindMatches(nullptr, &nEntries, &panConfidence);
   10210        1249 :     if (nEntries == 1 && panConfidence[0] >= nMinimumMatchConfidence)
   10211             :     {
   10212        2198 :         std::vector<double> adfTOWGS84(7);
   10213        1099 :         if (GetTOWGS84(&adfTOWGS84[0], 7) != OGRERR_NONE)
   10214             :         {
   10215        1098 :             adfTOWGS84.clear();
   10216             :         }
   10217             : 
   10218        1099 :         auto poSRS = OGRSpatialReference::FromHandle(pahSRS[0]);
   10219             : 
   10220             :         auto poBaseGeogCRS =
   10221        1099 :             std::unique_ptr<OGRSpatialReference>(poSRS->CloneGeogCS());
   10222             : 
   10223             :         // If the base geographic SRS of the SRS is EPSG:4326
   10224             :         // with TOWGS84[0,0,0,0,0,0], then just use the official
   10225             :         // SRS code
   10226             :         // Same with EPSG:4258 (ETRS89), since it's the only known
   10227             :         // TOWGS84[] style transformation to WGS 84, and given the
   10228             :         // "fuzzy" nature of both ETRS89 and WGS 84, there's little
   10229             :         // chance that a non-NULL TOWGS84[] will emerge.
   10230        1099 :         const char *pszAuthorityName = nullptr;
   10231        1099 :         const char *pszAuthorityCode = nullptr;
   10232        1099 :         const char *pszBaseAuthorityName = nullptr;
   10233        1099 :         const char *pszBaseAuthorityCode = nullptr;
   10234        2198 :         if (adfTOWGS84 == std::vector<double>(7) &&
   10235           1 :             (pszAuthorityName = poSRS->GetAuthorityName(nullptr)) != nullptr &&
   10236           1 :             EQUAL(pszAuthorityName, "EPSG") &&
   10237           1 :             (pszAuthorityCode = poSRS->GetAuthorityCode(nullptr)) != nullptr &&
   10238           1 :             (pszBaseAuthorityName = poBaseGeogCRS->GetAuthorityName(nullptr)) !=
   10239           1 :                 nullptr &&
   10240           1 :             EQUAL(pszBaseAuthorityName, "EPSG") &&
   10241           1 :             (pszBaseAuthorityCode = poBaseGeogCRS->GetAuthorityCode(nullptr)) !=
   10242        2199 :                 nullptr &&
   10243           1 :             (EQUAL(pszBaseAuthorityCode, "4326") ||
   10244           1 :              EQUAL(pszBaseAuthorityCode, "4258")))
   10245             :         {
   10246           1 :             poSRS->importFromEPSG(atoi(pszAuthorityCode));
   10247             :         }
   10248             : 
   10249        1099 :         CPLFree(pahSRS);
   10250        1099 :         CPLFree(panConfidence);
   10251             : 
   10252        1099 :         return poSRS;
   10253             :     }
   10254             :     else
   10255             :     {
   10256             :         // If there are several matches >= nMinimumMatchConfidence, take the
   10257             :         // only one that is under pszPreferredAuthority
   10258         150 :         int iBestEntry = -1;
   10259        1661 :         for (int i = 0; i < nEntries; i++)
   10260             :         {
   10261        1511 :             if (panConfidence[i] >= nMinimumMatchConfidence)
   10262             :             {
   10263             :                 const char *pszAuthName =
   10264           3 :                     OGRSpatialReference::FromHandle(pahSRS[i])
   10265           3 :                         ->GetAuthorityName(nullptr);
   10266           3 :                 if (pszAuthName != nullptr &&
   10267           3 :                     EQUAL(pszAuthName, pszPreferredAuthority))
   10268             :                 {
   10269           3 :                     if (iBestEntry < 0)
   10270           3 :                         iBestEntry = i;
   10271             :                     else
   10272             :                     {
   10273           0 :                         iBestEntry = -1;
   10274           0 :                         break;
   10275             :                     }
   10276             :                 }
   10277             :             }
   10278             :         }
   10279         150 :         if (iBestEntry >= 0)
   10280             :         {
   10281           3 :             auto poRet = OGRSpatialReference::FromHandle(pahSRS[0])->Clone();
   10282           3 :             OSRFreeSRSArray(pahSRS);
   10283           3 :             CPLFree(panConfidence);
   10284           3 :             return poRet;
   10285             :         }
   10286             :     }
   10287         147 :     OSRFreeSRSArray(pahSRS);
   10288         147 :     CPLFree(panConfidence);
   10289         147 :     return nullptr;
   10290             : }
   10291             : 
   10292             : /************************************************************************/
   10293             : /*                             SetTOWGS84()                             */
   10294             : /************************************************************************/
   10295             : 
   10296             : /**
   10297             :  * \brief Set the Bursa-Wolf conversion to WGS84.
   10298             :  *
   10299             :  * This will create the TOWGS84 node as a child of the DATUM.  It will fail
   10300             :  * if there is no existing DATUM node. It will replace
   10301             :  * an existing TOWGS84 node if there is one.
   10302             :  *
   10303             :  * The parameters have the same meaning as EPSG transformation 9606
   10304             :  * (Position Vector 7-param. transformation).
   10305             :  *
   10306             :  * This method is the same as the C function OSRSetTOWGS84().
   10307             :  *
   10308             :  * @param dfDX X child in meters.
   10309             :  * @param dfDY Y child in meters.
   10310             :  * @param dfDZ Z child in meters.
   10311             :  * @param dfEX X rotation in arc seconds (optional, defaults to zero).
   10312             :  * @param dfEY Y rotation in arc seconds (optional, defaults to zero).
   10313             :  * @param dfEZ Z rotation in arc seconds (optional, defaults to zero).
   10314             :  * @param dfPPM scaling factor (parts per million).
   10315             :  *
   10316             :  * @return OGRERR_NONE on success.
   10317             :  */
   10318             : 
   10319         103 : OGRErr OGRSpatialReference::SetTOWGS84(double dfDX, double dfDY, double dfDZ,
   10320             :                                        double dfEX, double dfEY, double dfEZ,
   10321             :                                        double dfPPM)
   10322             : 
   10323             : {
   10324         206 :     TAKE_OPTIONAL_LOCK();
   10325             : 
   10326         103 :     d->refreshProjObj();
   10327         103 :     if (d->m_pj_crs == nullptr)
   10328             :     {
   10329           0 :         return OGRERR_FAILURE;
   10330             :     }
   10331             : 
   10332             :     // Remove existing BoundCRS
   10333         103 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS)
   10334             :     {
   10335           0 :         auto baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
   10336           0 :         if (!baseCRS)
   10337           0 :             return OGRERR_FAILURE;
   10338           0 :         d->setPjCRS(baseCRS);
   10339             :     }
   10340             : 
   10341             :     PJ_PARAM_DESCRIPTION params[7];
   10342             : 
   10343         103 :     params[0].name = EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION;
   10344         103 :     params[0].auth_name = "EPSG";
   10345         103 :     params[0].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
   10346         103 :     params[0].value = dfDX;
   10347         103 :     params[0].unit_name = "metre";
   10348         103 :     params[0].unit_conv_factor = 1.0;
   10349         103 :     params[0].unit_type = PJ_UT_LINEAR;
   10350             : 
   10351         103 :     params[1].name = EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION;
   10352         103 :     params[1].auth_name = "EPSG";
   10353         103 :     params[1].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
   10354         103 :     params[1].value = dfDY;
   10355         103 :     params[1].unit_name = "metre";
   10356         103 :     params[1].unit_conv_factor = 1.0;
   10357         103 :     params[1].unit_type = PJ_UT_LINEAR;
   10358             : 
   10359         103 :     params[2].name = EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION;
   10360         103 :     params[2].auth_name = "EPSG";
   10361         103 :     params[2].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
   10362         103 :     params[2].value = dfDZ;
   10363         103 :     params[2].unit_name = "metre";
   10364         103 :     params[2].unit_conv_factor = 1.0;
   10365         103 :     params[2].unit_type = PJ_UT_LINEAR;
   10366             : 
   10367         103 :     params[3].name = EPSG_NAME_PARAMETER_X_AXIS_ROTATION;
   10368         103 :     params[3].auth_name = "EPSG";
   10369         103 :     params[3].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_ROTATION);
   10370         103 :     params[3].value = dfEX;
   10371         103 :     params[3].unit_name = "arc-second";
   10372         103 :     params[3].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10373         103 :     params[3].unit_type = PJ_UT_ANGULAR;
   10374             : 
   10375         103 :     params[4].name = EPSG_NAME_PARAMETER_Y_AXIS_ROTATION;
   10376         103 :     params[4].auth_name = "EPSG";
   10377         103 :     params[4].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION);
   10378         103 :     params[4].value = dfEY;
   10379         103 :     params[4].unit_name = "arc-second";
   10380         103 :     params[4].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10381         103 :     params[4].unit_type = PJ_UT_ANGULAR;
   10382             : 
   10383         103 :     params[5].name = EPSG_NAME_PARAMETER_Z_AXIS_ROTATION;
   10384         103 :     params[5].auth_name = "EPSG";
   10385         103 :     params[5].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION);
   10386         103 :     params[5].value = dfEZ;
   10387         103 :     params[5].unit_name = "arc-second";
   10388         103 :     params[5].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10389         103 :     params[5].unit_type = PJ_UT_ANGULAR;
   10390             : 
   10391         103 :     params[6].name = EPSG_NAME_PARAMETER_SCALE_DIFFERENCE;
   10392         103 :     params[6].auth_name = "EPSG";
   10393         103 :     params[6].code = XSTRINGIFY(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE);
   10394         103 :     params[6].value = dfPPM;
   10395         103 :     params[6].unit_name = "parts per million";
   10396         103 :     params[6].unit_conv_factor = 1e-6;
   10397         103 :     params[6].unit_type = PJ_UT_SCALE;
   10398             : 
   10399             :     auto sourceCRS =
   10400         103 :         proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10401         103 :     if (!sourceCRS)
   10402             :     {
   10403           0 :         return OGRERR_FAILURE;
   10404             :     }
   10405             : 
   10406         103 :     const auto sourceType = proj_get_type(sourceCRS);
   10407             : 
   10408         103 :     auto targetCRS = proj_create_from_database(
   10409             :         d->getPROJContext(), "EPSG",
   10410             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS   ? "4326"
   10411             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS ? "4979"
   10412             :                                                   : "4978",
   10413             :         PJ_CATEGORY_CRS, false, nullptr);
   10414         103 :     if (!targetCRS)
   10415             :     {
   10416           0 :         proj_destroy(sourceCRS);
   10417           0 :         return OGRERR_FAILURE;
   10418             :     }
   10419             : 
   10420         206 :     CPLString osMethodCode;
   10421             :     osMethodCode.Printf("%d",
   10422             :                         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10423             :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10424             :                         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10425           0 :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10426         103 :                             : EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC);
   10427             : 
   10428         103 :     auto transf = proj_create_transformation(
   10429             :         d->getPROJContext(), "Transformation to WGS84", nullptr, nullptr,
   10430             :         sourceCRS, targetCRS, nullptr,
   10431             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10432             :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10433             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10434           0 :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10435             :             : EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC,
   10436             :         "EPSG", osMethodCode.c_str(), 7, params, -1);
   10437         103 :     proj_destroy(sourceCRS);
   10438         103 :     if (!transf)
   10439             :     {
   10440           0 :         proj_destroy(targetCRS);
   10441           0 :         return OGRERR_FAILURE;
   10442             :     }
   10443             : 
   10444         103 :     auto newBoundCRS = proj_crs_create_bound_crs(
   10445         103 :         d->getPROJContext(), d->m_pj_crs, targetCRS, transf);
   10446         103 :     proj_destroy(transf);
   10447         103 :     proj_destroy(targetCRS);
   10448         103 :     if (!newBoundCRS)
   10449             :     {
   10450           0 :         return OGRERR_FAILURE;
   10451             :     }
   10452             : 
   10453         103 :     d->setPjCRS(newBoundCRS);
   10454         103 :     return OGRERR_NONE;
   10455             : }
   10456             : 
   10457             : /************************************************************************/
   10458             : /*                           OSRSetTOWGS84()                            */
   10459             : /************************************************************************/
   10460             : 
   10461             : /**
   10462             :  * \brief Set the Bursa-Wolf conversion to WGS84.
   10463             :  *
   10464             :  * This function is the same as OGRSpatialReference::SetTOWGS84().
   10465             :  */
   10466           4 : OGRErr OSRSetTOWGS84(OGRSpatialReferenceH hSRS, double dfDX, double dfDY,
   10467             :                      double dfDZ, double dfEX, double dfEY, double dfEZ,
   10468             :                      double dfPPM)
   10469             : 
   10470             : {
   10471           4 :     VALIDATE_POINTER1(hSRS, "OSRSetTOWGS84", OGRERR_FAILURE);
   10472             : 
   10473           4 :     return ToPointer(hSRS)->SetTOWGS84(dfDX, dfDY, dfDZ, dfEX, dfEY, dfEZ,
   10474           4 :                                        dfPPM);
   10475             : }
   10476             : 
   10477             : /************************************************************************/
   10478             : /*                             GetTOWGS84()                             */
   10479             : /************************************************************************/
   10480             : 
   10481             : /**
   10482             :  * \brief Fetch TOWGS84 parameters, if available.
   10483             :  *
   10484             :  * The parameters have the same meaning as EPSG transformation 9606
   10485             :  * (Position Vector 7-param. transformation).
   10486             :  *
   10487             :  * @param padfCoeff array into which up to 7 coefficients are placed.
   10488             :  * @param nCoeffCount size of padfCoeff - defaults to 7.
   10489             :  *
   10490             :  * @return OGRERR_NONE on success, or OGRERR_FAILURE if there is no
   10491             :  * TOWGS84 node available.
   10492             :  */
   10493             : 
   10494        4312 : OGRErr OGRSpatialReference::GetTOWGS84(double *padfCoeff, int nCoeffCount) const
   10495             : 
   10496             : {
   10497        8624 :     TAKE_OPTIONAL_LOCK();
   10498             : 
   10499        4312 :     d->refreshProjObj();
   10500        4312 :     if (d->m_pjType != PJ_TYPE_BOUND_CRS)
   10501        4264 :         return OGRERR_FAILURE;
   10502             : 
   10503          48 :     memset(padfCoeff, 0, sizeof(double) * nCoeffCount);
   10504             : 
   10505          48 :     auto transf = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10506          48 :     int success = proj_coordoperation_get_towgs84_values(
   10507             :         d->getPROJContext(), transf, padfCoeff, nCoeffCount, false);
   10508          48 :     proj_destroy(transf);
   10509             : 
   10510          48 :     return success ? OGRERR_NONE : OGRERR_FAILURE;
   10511             : }
   10512             : 
   10513             : /************************************************************************/
   10514             : /*                           OSRGetTOWGS84()                            */
   10515             : /************************************************************************/
   10516             : 
   10517             : /**
   10518             :  * \brief Fetch TOWGS84 parameters, if available.
   10519             :  *
   10520             :  * This function is the same as OGRSpatialReference::GetTOWGS84().
   10521             :  */
   10522          10 : OGRErr OSRGetTOWGS84(OGRSpatialReferenceH hSRS, double *padfCoeff,
   10523             :                      int nCoeffCount)
   10524             : 
   10525             : {
   10526          10 :     VALIDATE_POINTER1(hSRS, "OSRGetTOWGS84", OGRERR_FAILURE);
   10527             : 
   10528          10 :     return ToPointer(hSRS)->GetTOWGS84(padfCoeff, nCoeffCount);
   10529             : }
   10530             : 
   10531             : /************************************************************************/
   10532             : /*                         IsAngularParameter()                         */
   10533             : /************************************************************************/
   10534             : 
   10535             : /** Is the passed projection parameter an angular one?
   10536             :  *
   10537             :  * @return TRUE or FALSE
   10538             :  */
   10539             : 
   10540             : /* static */
   10541          10 : int OGRSpatialReference::IsAngularParameter(const char *pszParameterName)
   10542             : 
   10543             : {
   10544          10 :     if (STARTS_WITH_CI(pszParameterName, "long") ||
   10545          10 :         STARTS_WITH_CI(pszParameterName, "lati") ||
   10546           7 :         EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN) ||
   10547           4 :         STARTS_WITH_CI(pszParameterName, "standard_parallel") ||
   10548           2 :         EQUAL(pszParameterName, SRS_PP_AZIMUTH) ||
   10549           2 :         EQUAL(pszParameterName, SRS_PP_RECTIFIED_GRID_ANGLE))
   10550           8 :         return TRUE;
   10551             : 
   10552           2 :     return FALSE;
   10553             : }
   10554             : 
   10555             : /************************************************************************/
   10556             : /*                        IsLongitudeParameter()                        */
   10557             : /************************************************************************/
   10558             : 
   10559             : /** Is the passed projection parameter an angular longitude
   10560             :  * (relative to a prime meridian)?
   10561             :  *
   10562             :  * @return TRUE or FALSE
   10563             :  */
   10564             : 
   10565             : /* static */
   10566           0 : int OGRSpatialReference::IsLongitudeParameter(const char *pszParameterName)
   10567             : 
   10568             : {
   10569           0 :     if (STARTS_WITH_CI(pszParameterName, "long") ||
   10570           0 :         EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN))
   10571           0 :         return TRUE;
   10572             : 
   10573           0 :     return FALSE;
   10574             : }
   10575             : 
   10576             : /************************************************************************/
   10577             : /*                         IsLinearParameter()                          */
   10578             : /************************************************************************/
   10579             : 
   10580             : /** Is the passed projection parameter an linear one measured in meters or
   10581             :  * some similar linear measure.
   10582             :  *
   10583             :  * @return TRUE or FALSE
   10584             :  */
   10585             : 
   10586             : /* static */
   10587          43 : int OGRSpatialReference::IsLinearParameter(const char *pszParameterName)
   10588             : 
   10589             : {
   10590          43 :     if (STARTS_WITH_CI(pszParameterName, "false_") ||
   10591          34 :         EQUAL(pszParameterName, SRS_PP_SATELLITE_HEIGHT))
   10592           9 :         return TRUE;
   10593             : 
   10594          34 :     return FALSE;
   10595             : }
   10596             : 
   10597             : /************************************************************************/
   10598             : /*                            GetNormInfo()                             */
   10599             : /************************************************************************/
   10600             : 
   10601             : /**
   10602             :  * \brief Set the internal information for normalizing linear, and angular
   10603             :  * values.
   10604             :  */
   10605        3516 : void OGRSpatialReference::GetNormInfo() const
   10606             : 
   10607             : {
   10608        3516 :     TAKE_OPTIONAL_LOCK();
   10609             : 
   10610        3516 :     if (d->bNormInfoSet)
   10611        2466 :         return;
   10612             : 
   10613             :     /* -------------------------------------------------------------------- */
   10614             :     /*      Initialize values.                                              */
   10615             :     /* -------------------------------------------------------------------- */
   10616        1050 :     d->bNormInfoSet = TRUE;
   10617             : 
   10618        1050 :     d->dfFromGreenwich = GetPrimeMeridian(nullptr);
   10619        1050 :     d->dfToMeter = GetLinearUnits(nullptr);
   10620        1050 :     d->dfToDegrees = GetAngularUnits(nullptr) / CPLAtof(SRS_UA_DEGREE_CONV);
   10621        1050 :     if (fabs(d->dfToDegrees - 1.0) < 0.000000001)
   10622        1047 :         d->dfToDegrees = 1.0;
   10623             : }
   10624             : 
   10625             : /************************************************************************/
   10626             : /*                            GetExtension()                            */
   10627             : /************************************************************************/
   10628             : 
   10629             : /**
   10630             :  * \brief Fetch extension value.
   10631             :  *
   10632             :  * Fetch the value of the named EXTENSION item for the identified
   10633             :  * target node.
   10634             :  *
   10635             :  * @param pszTargetKey the name or path to the parent node of the EXTENSION.
   10636             :  * @param pszName the name of the extension being fetched.
   10637             :  * @param pszDefault the value to return if the extension is not found.
   10638             :  *
   10639             :  * @return node value if successful or pszDefault on failure.
   10640             :  */
   10641             : 
   10642        9039 : const char *OGRSpatialReference::GetExtension(const char *pszTargetKey,
   10643             :                                               const char *pszName,
   10644             :                                               const char *pszDefault) const
   10645             : 
   10646             : {
   10647       18078 :     TAKE_OPTIONAL_LOCK();
   10648             : 
   10649             :     /* -------------------------------------------------------------------- */
   10650             :     /*      Find the target node.                                           */
   10651             :     /* -------------------------------------------------------------------- */
   10652             :     const OGR_SRSNode *poNode =
   10653        9039 :         pszTargetKey == nullptr ? GetRoot() : GetAttrNode(pszTargetKey);
   10654             : 
   10655        9039 :     if (poNode == nullptr)
   10656        2186 :         return nullptr;
   10657             : 
   10658             :     /* -------------------------------------------------------------------- */
   10659             :     /*      Fetch matching EXTENSION if there is one.                       */
   10660             :     /* -------------------------------------------------------------------- */
   10661       50707 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10662             :     {
   10663       43876 :         const OGR_SRSNode *poChild = poNode->GetChild(i);
   10664             : 
   10665       43900 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10666          24 :             poChild->GetChildCount() >= 2)
   10667             :         {
   10668          24 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10669          22 :                 return poChild->GetChild(1)->GetValue();
   10670             :         }
   10671             :     }
   10672             : 
   10673        6831 :     return pszDefault;
   10674             : }
   10675             : 
   10676             : /************************************************************************/
   10677             : /*                            SetExtension()                            */
   10678             : /************************************************************************/
   10679             : /**
   10680             :  * \brief Set extension value.
   10681             :  *
   10682             :  * Set the value of the named EXTENSION item for the identified
   10683             :  * target node.
   10684             :  *
   10685             :  * @param pszTargetKey the name or path to the parent node of the EXTENSION.
   10686             :  * @param pszName the name of the extension being fetched.
   10687             :  * @param pszValue the value to set
   10688             :  *
   10689             :  * @return OGRERR_NONE on success
   10690             :  */
   10691             : 
   10692          20 : OGRErr OGRSpatialReference::SetExtension(const char *pszTargetKey,
   10693             :                                          const char *pszName,
   10694             :                                          const char *pszValue)
   10695             : 
   10696             : {
   10697          40 :     TAKE_OPTIONAL_LOCK();
   10698             : 
   10699             :     /* -------------------------------------------------------------------- */
   10700             :     /*      Find the target node.                                           */
   10701             :     /* -------------------------------------------------------------------- */
   10702          20 :     OGR_SRSNode *poNode = nullptr;
   10703             : 
   10704          20 :     if (pszTargetKey == nullptr)
   10705           0 :         poNode = GetRoot();
   10706             :     else
   10707          20 :         poNode = GetAttrNode(pszTargetKey);
   10708             : 
   10709          20 :     if (poNode == nullptr)
   10710           0 :         return OGRERR_FAILURE;
   10711             : 
   10712             :     /* -------------------------------------------------------------------- */
   10713             :     /*      Fetch matching EXTENSION if there is one.                       */
   10714             :     /* -------------------------------------------------------------------- */
   10715         151 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10716             :     {
   10717         137 :         OGR_SRSNode *poChild = poNode->GetChild(i);
   10718             : 
   10719         143 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10720           6 :             poChild->GetChildCount() >= 2)
   10721             :         {
   10722           6 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10723             :             {
   10724           6 :                 poChild->GetChild(1)->SetValue(pszValue);
   10725           6 :                 return OGRERR_NONE;
   10726             :             }
   10727             :         }
   10728             :     }
   10729             : 
   10730             :     /* -------------------------------------------------------------------- */
   10731             :     /*      Create a new EXTENSION node.                                    */
   10732             :     /* -------------------------------------------------------------------- */
   10733          14 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("EXTENSION");
   10734          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszName));
   10735          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszValue));
   10736             : 
   10737          14 :     poNode->AddChild(poAuthNode);
   10738             : 
   10739          14 :     return OGRERR_NONE;
   10740             : }
   10741             : 
   10742             : /************************************************************************/
   10743             : /*                             OSRCleanup()                             */
   10744             : /************************************************************************/
   10745             : 
   10746             : static void CleanupSRSWGS84Mutex();
   10747             : 
   10748             : /**
   10749             :  * \brief Cleanup cached SRS related memory.
   10750             :  *
   10751             :  * This function will attempt to cleanup any cache spatial reference
   10752             :  * related information, such as cached tables of coordinate systems.
   10753             :  *
   10754             :  * This function should not be called concurrently with any other GDAL/OGR
   10755             :  * function. It is meant at being called once before process termination
   10756             :  * (typically from the main thread). CPLCleanupTLS() might be used to clean
   10757             :  * thread-specific resources before thread termination.
   10758             :  */
   10759         927 : void OSRCleanup(void)
   10760             : 
   10761             : {
   10762         927 :     OGRCTDumpStatistics();
   10763         927 :     CSVDeaccess(nullptr);
   10764         927 :     CleanupSRSWGS84Mutex();
   10765         927 :     OSRCTCleanCache();
   10766         927 :     OSRCleanupTLSContext();
   10767         927 : }
   10768             : 
   10769             : /************************************************************************/
   10770             : /*                              GetAxesCount()                          */
   10771             : /************************************************************************/
   10772             : 
   10773             : /**
   10774             :  * \brief Return the number of axis of the coordinate system of the CRS.
   10775             :  *
   10776             :  * @since GDAL 3.0
   10777             :  */
   10778       34495 : int OGRSpatialReference::GetAxesCount() const
   10779             : {
   10780       68990 :     TAKE_OPTIONAL_LOCK();
   10781             : 
   10782       34495 :     int axisCount = 0;
   10783       34495 :     d->refreshProjObj();
   10784       34495 :     if (d->m_pj_crs == nullptr)
   10785             :     {
   10786           0 :         return 0;
   10787             :     }
   10788       34495 :     d->demoteFromBoundCRS();
   10789       34495 :     auto ctxt = d->getPROJContext();
   10790       34495 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   10791             :     {
   10792          29 :         for (int i = 0;; i++)
   10793             :         {
   10794          87 :             auto subCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, i);
   10795          87 :             if (!subCRS)
   10796          29 :                 break;
   10797          58 :             if (proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
   10798             :             {
   10799          17 :                 auto baseCRS = proj_get_source_crs(ctxt, subCRS);
   10800          17 :                 if (baseCRS)
   10801             :                 {
   10802          17 :                     proj_destroy(subCRS);
   10803          17 :                     subCRS = baseCRS;
   10804             :                 }
   10805             :             }
   10806          58 :             auto cs = proj_crs_get_coordinate_system(ctxt, subCRS);
   10807          58 :             if (cs)
   10808             :             {
   10809          58 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
   10810          58 :                 proj_destroy(cs);
   10811             :             }
   10812          58 :             proj_destroy(subCRS);
   10813          58 :         }
   10814             :     }
   10815             :     else
   10816             :     {
   10817       34466 :         auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   10818       34466 :         if (cs)
   10819             :         {
   10820       34466 :             axisCount = proj_cs_get_axis_count(ctxt, cs);
   10821       34466 :             proj_destroy(cs);
   10822             :         }
   10823             :     }
   10824       34495 :     d->undoDemoteFromBoundCRS();
   10825       34495 :     return axisCount;
   10826             : }
   10827             : 
   10828             : /************************************************************************/
   10829             : /*                           OSRGetAxesCount()                          */
   10830             : /************************************************************************/
   10831             : 
   10832             : /**
   10833             :  * \brief Return the number of axis of the coordinate system of the CRS.
   10834             :  *
   10835             :  * This method is the equivalent of the C++ method
   10836             :  * OGRSpatialReference::GetAxesCount()
   10837             :  *
   10838             :  * @since GDAL 3.1
   10839             :  */
   10840           6 : int OSRGetAxesCount(OGRSpatialReferenceH hSRS)
   10841             : 
   10842             : {
   10843           6 :     VALIDATE_POINTER1(hSRS, "OSRGetAxesCount", 0);
   10844             : 
   10845           6 :     return ToPointer(hSRS)->GetAxesCount();
   10846             : }
   10847             : 
   10848             : /************************************************************************/
   10849             : /*                              GetAxis()                               */
   10850             : /************************************************************************/
   10851             : 
   10852             : /**
   10853             :  * \brief Fetch the orientation of one axis.
   10854             :  *
   10855             :  * Fetches the request axis (iAxis - zero based) from the
   10856             :  * indicated portion of the coordinate system (pszTargetKey) which
   10857             :  * should be either "GEOGCS" or "PROJCS".
   10858             :  *
   10859             :  * No CPLError is issued on routine failures (such as not finding the AXIS).
   10860             :  *
   10861             :  * This method is equivalent to the C function OSRGetAxis().
   10862             :  *
   10863             :  * @param pszTargetKey the coordinate system part to query ("PROJCS" or
   10864             :  * "GEOGCS").
   10865             :  * @param iAxis the axis to query (0 for first, 1 for second, 2 for third).
   10866             :  * @param peOrientation location into which to place the fetch orientation, may
   10867             :  * be NULL.
   10868             :  * @param pdfConvUnit (GDAL >= 3.4) Location into which to place axis conversion
   10869             :  * factor. May be NULL. Only set if pszTargetKey == NULL
   10870             :  *
   10871             :  * @return the name of the axis or NULL on failure.
   10872             :  */
   10873             : 
   10874        5444 : const char *OGRSpatialReference::GetAxis(const char *pszTargetKey, int iAxis,
   10875             :                                          OGRAxisOrientation *peOrientation,
   10876             :                                          double *pdfConvUnit) const
   10877             : 
   10878             : {
   10879       10888 :     TAKE_OPTIONAL_LOCK();
   10880             : 
   10881        5444 :     if (peOrientation != nullptr)
   10882        5351 :         *peOrientation = OAO_Other;
   10883        5444 :     if (pdfConvUnit != nullptr)
   10884          85 :         *pdfConvUnit = 0;
   10885             : 
   10886        5444 :     d->refreshProjObj();
   10887        5444 :     if (d->m_pj_crs == nullptr)
   10888             :     {
   10889           1 :         return nullptr;
   10890             :     }
   10891             : 
   10892        5443 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
   10893        5443 :     if (pszTargetKey == nullptr && iAxis <= 2)
   10894             :     {
   10895        5443 :         auto ctxt = d->getPROJContext();
   10896             : 
   10897        5443 :         int iAxisModified = iAxis;
   10898             : 
   10899        5443 :         d->demoteFromBoundCRS();
   10900             : 
   10901        5443 :         PJ *cs = nullptr;
   10902        5443 :         if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   10903             :         {
   10904         134 :             auto horizCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   10905         134 :             if (horizCRS)
   10906             :             {
   10907         134 :                 if (proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
   10908             :                 {
   10909           6 :                     auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
   10910           6 :                     if (baseCRS)
   10911             :                     {
   10912           6 :                         proj_destroy(horizCRS);
   10913           6 :                         horizCRS = baseCRS;
   10914             :                     }
   10915             :                 }
   10916         134 :                 cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
   10917         134 :                 proj_destroy(horizCRS);
   10918         134 :                 if (cs)
   10919             :                 {
   10920         134 :                     if (iAxisModified >= proj_cs_get_axis_count(ctxt, cs))
   10921             :                     {
   10922          44 :                         iAxisModified -= proj_cs_get_axis_count(ctxt, cs);
   10923          44 :                         proj_destroy(cs);
   10924          44 :                         cs = nullptr;
   10925             :                     }
   10926             :                 }
   10927             :             }
   10928             : 
   10929         134 :             if (cs == nullptr)
   10930             :             {
   10931          44 :                 auto vertCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
   10932          44 :                 if (vertCRS)
   10933             :                 {
   10934          44 :                     if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
   10935             :                     {
   10936          30 :                         auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
   10937          30 :                         if (baseCRS)
   10938             :                         {
   10939          30 :                             proj_destroy(vertCRS);
   10940          30 :                             vertCRS = baseCRS;
   10941             :                         }
   10942             :                     }
   10943             : 
   10944          44 :                     cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
   10945          44 :                     proj_destroy(vertCRS);
   10946             :                 }
   10947             :             }
   10948             :         }
   10949             :         else
   10950             :         {
   10951        5309 :             cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   10952             :         }
   10953             : 
   10954        5443 :         if (cs)
   10955             :         {
   10956        5443 :             const char *pszName = nullptr;
   10957        5443 :             const char *pszOrientation = nullptr;
   10958        5443 :             double dfConvFactor = 0.0;
   10959        5443 :             proj_cs_get_axis_info(ctxt, cs, iAxisModified, &pszName, nullptr,
   10960             :                                   &pszOrientation, &dfConvFactor, nullptr,
   10961             :                                   nullptr, nullptr);
   10962             : 
   10963        5443 :             if (pdfConvUnit != nullptr)
   10964             :             {
   10965          85 :                 *pdfConvUnit = dfConvFactor;
   10966             :             }
   10967             : 
   10968        5443 :             if (pszName && pszOrientation)
   10969             :             {
   10970        5443 :                 d->m_osAxisName[iAxis] = pszName;
   10971        5443 :                 if (peOrientation)
   10972             :                 {
   10973        5350 :                     if (EQUAL(pszOrientation, "NORTH"))
   10974        3420 :                         *peOrientation = OAO_North;
   10975        1930 :                     else if (EQUAL(pszOrientation, "EAST"))
   10976        1888 :                         *peOrientation = OAO_East;
   10977          42 :                     else if (EQUAL(pszOrientation, "SOUTH"))
   10978          31 :                         *peOrientation = OAO_South;
   10979          11 :                     else if (EQUAL(pszOrientation, "WEST"))
   10980           0 :                         *peOrientation = OAO_West;
   10981          11 :                     else if (EQUAL(pszOrientation, "UP"))
   10982           1 :                         *peOrientation = OAO_Up;
   10983          10 :                     else if (EQUAL(pszOrientation, "DOWN"))
   10984           0 :                         *peOrientation = OAO_Down;
   10985             :                 }
   10986        5443 :                 proj_destroy(cs);
   10987        5443 :                 d->undoDemoteFromBoundCRS();
   10988        5443 :                 return d->m_osAxisName[iAxis].c_str();
   10989             :             }
   10990           0 :             proj_destroy(cs);
   10991             :         }
   10992           0 :         d->undoDemoteFromBoundCRS();
   10993             :     }
   10994             : 
   10995             :     /* -------------------------------------------------------------------- */
   10996             :     /*      Find the target node.                                           */
   10997             :     /* -------------------------------------------------------------------- */
   10998           0 :     const OGR_SRSNode *poNode = nullptr;
   10999             : 
   11000           0 :     if (pszTargetKey == nullptr)
   11001           0 :         poNode = GetRoot();
   11002             :     else
   11003           0 :         poNode = GetAttrNode(pszTargetKey);
   11004             : 
   11005           0 :     if (poNode == nullptr)
   11006           0 :         return nullptr;
   11007             : 
   11008             :     /* -------------------------------------------------------------------- */
   11009             :     /*      Find desired child AXIS.                                        */
   11010             :     /* -------------------------------------------------------------------- */
   11011           0 :     const OGR_SRSNode *poAxis = nullptr;
   11012           0 :     const int nChildCount = poNode->GetChildCount();
   11013             : 
   11014           0 :     for (int iChild = 0; iChild < nChildCount; iChild++)
   11015             :     {
   11016           0 :         const OGR_SRSNode *poChild = poNode->GetChild(iChild);
   11017             : 
   11018           0 :         if (!EQUAL(poChild->GetValue(), "AXIS"))
   11019           0 :             continue;
   11020             : 
   11021           0 :         if (iAxis == 0)
   11022             :         {
   11023           0 :             poAxis = poChild;
   11024           0 :             break;
   11025             :         }
   11026           0 :         iAxis--;
   11027             :     }
   11028             : 
   11029           0 :     if (poAxis == nullptr)
   11030           0 :         return nullptr;
   11031             : 
   11032           0 :     if (poAxis->GetChildCount() < 2)
   11033           0 :         return nullptr;
   11034             : 
   11035             :     /* -------------------------------------------------------------------- */
   11036             :     /*      Extract name and orientation if possible.                       */
   11037             :     /* -------------------------------------------------------------------- */
   11038           0 :     if (peOrientation != nullptr)
   11039             :     {
   11040           0 :         const char *pszOrientation = poAxis->GetChild(1)->GetValue();
   11041             : 
   11042           0 :         if (EQUAL(pszOrientation, "NORTH"))
   11043           0 :             *peOrientation = OAO_North;
   11044           0 :         else if (EQUAL(pszOrientation, "EAST"))
   11045           0 :             *peOrientation = OAO_East;
   11046           0 :         else if (EQUAL(pszOrientation, "SOUTH"))
   11047           0 :             *peOrientation = OAO_South;
   11048           0 :         else if (EQUAL(pszOrientation, "WEST"))
   11049           0 :             *peOrientation = OAO_West;
   11050           0 :         else if (EQUAL(pszOrientation, "UP"))
   11051           0 :             *peOrientation = OAO_Up;
   11052           0 :         else if (EQUAL(pszOrientation, "DOWN"))
   11053           0 :             *peOrientation = OAO_Down;
   11054           0 :         else if (EQUAL(pszOrientation, "OTHER"))
   11055           0 :             *peOrientation = OAO_Other;
   11056             :         else
   11057             :         {
   11058           0 :             CPLDebug("OSR", "Unrecognized orientation value '%s'.",
   11059             :                      pszOrientation);
   11060             :         }
   11061             :     }
   11062             : 
   11063           0 :     return poAxis->GetChild(0)->GetValue();
   11064             : }
   11065             : 
   11066             : /************************************************************************/
   11067             : /*                             OSRGetAxis()                             */
   11068             : /************************************************************************/
   11069             : 
   11070             : /**
   11071             :  * \brief Fetch the orientation of one axis.
   11072             :  *
   11073             :  * This method is the equivalent of the C++ method OGRSpatialReference::GetAxis
   11074             :  */
   11075          13 : const char *OSRGetAxis(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11076             :                        int iAxis, OGRAxisOrientation *peOrientation)
   11077             : 
   11078             : {
   11079          13 :     VALIDATE_POINTER1(hSRS, "OSRGetAxis", nullptr);
   11080             : 
   11081          13 :     return ToPointer(hSRS)->GetAxis(pszTargetKey, iAxis, peOrientation);
   11082             : }
   11083             : 
   11084             : /************************************************************************/
   11085             : /*                         OSRAxisEnumToName()                          */
   11086             : /************************************************************************/
   11087             : 
   11088             : /**
   11089             :  * \brief Return the string representation for the OGRAxisOrientation
   11090             :  * enumeration.
   11091             :  *
   11092             :  * For example "NORTH" for OAO_North.
   11093             :  *
   11094             :  * @return an internal string
   11095             :  */
   11096         392 : const char *OSRAxisEnumToName(OGRAxisOrientation eOrientation)
   11097             : 
   11098             : {
   11099         392 :     if (eOrientation == OAO_North)
   11100         196 :         return "NORTH";
   11101         196 :     if (eOrientation == OAO_East)
   11102         196 :         return "EAST";
   11103           0 :     if (eOrientation == OAO_South)
   11104           0 :         return "SOUTH";
   11105           0 :     if (eOrientation == OAO_West)
   11106           0 :         return "WEST";
   11107           0 :     if (eOrientation == OAO_Up)
   11108           0 :         return "UP";
   11109           0 :     if (eOrientation == OAO_Down)
   11110           0 :         return "DOWN";
   11111           0 :     if (eOrientation == OAO_Other)
   11112           0 :         return "OTHER";
   11113             : 
   11114           0 :     return "UNKNOWN";
   11115             : }
   11116             : 
   11117             : /************************************************************************/
   11118             : /*                              SetAxes()                               */
   11119             : /************************************************************************/
   11120             : 
   11121             : /**
   11122             :  * \brief Set the axes for a coordinate system.
   11123             :  *
   11124             :  * Set the names, and orientations of the axes for either a projected
   11125             :  * (PROJCS) or geographic (GEOGCS) coordinate system.
   11126             :  *
   11127             :  * This method is equivalent to the C function OSRSetAxes().
   11128             :  *
   11129             :  * @param pszTargetKey either "PROJCS" or "GEOGCS", must already exist in SRS.
   11130             :  * @param pszXAxisName name of first axis, normally "Long" or "Easting".
   11131             :  * @param eXAxisOrientation normally OAO_East.
   11132             :  * @param pszYAxisName name of second axis, normally "Lat" or "Northing".
   11133             :  * @param eYAxisOrientation normally OAO_North.
   11134             :  *
   11135             :  * @return OGRERR_NONE on success or an error code.
   11136             :  */
   11137             : 
   11138         196 : OGRErr OGRSpatialReference::SetAxes(const char *pszTargetKey,
   11139             :                                     const char *pszXAxisName,
   11140             :                                     OGRAxisOrientation eXAxisOrientation,
   11141             :                                     const char *pszYAxisName,
   11142             :                                     OGRAxisOrientation eYAxisOrientation)
   11143             : 
   11144             : {
   11145         392 :     TAKE_OPTIONAL_LOCK();
   11146             : 
   11147             :     /* -------------------------------------------------------------------- */
   11148             :     /*      Find the target node.                                           */
   11149             :     /* -------------------------------------------------------------------- */
   11150         196 :     OGR_SRSNode *poNode = nullptr;
   11151             : 
   11152         196 :     if (pszTargetKey == nullptr)
   11153         196 :         poNode = GetRoot();
   11154             :     else
   11155           0 :         poNode = GetAttrNode(pszTargetKey);
   11156             : 
   11157         196 :     if (poNode == nullptr)
   11158           0 :         return OGRERR_FAILURE;
   11159             : 
   11160             :     /* -------------------------------------------------------------------- */
   11161             :     /*      Strip any existing AXIS children.                               */
   11162             :     /* -------------------------------------------------------------------- */
   11163         588 :     while (poNode->FindChild("AXIS") >= 0)
   11164         392 :         poNode->DestroyChild(poNode->FindChild("AXIS"));
   11165             : 
   11166             :     /* -------------------------------------------------------------------- */
   11167             :     /*      Insert desired axes                                             */
   11168             :     /* -------------------------------------------------------------------- */
   11169         196 :     OGR_SRSNode *poAxis = new OGR_SRSNode("AXIS");
   11170             : 
   11171         196 :     poAxis->AddChild(new OGR_SRSNode(pszXAxisName));
   11172         196 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eXAxisOrientation)));
   11173             : 
   11174         196 :     poNode->AddChild(poAxis);
   11175             : 
   11176         196 :     poAxis = new OGR_SRSNode("AXIS");
   11177             : 
   11178         196 :     poAxis->AddChild(new OGR_SRSNode(pszYAxisName));
   11179         196 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eYAxisOrientation)));
   11180             : 
   11181         196 :     poNode->AddChild(poAxis);
   11182             : 
   11183         196 :     return OGRERR_NONE;
   11184             : }
   11185             : 
   11186             : /************************************************************************/
   11187             : /*                             OSRSetAxes()                             */
   11188             : /************************************************************************/
   11189             : /**
   11190             :  * \brief Set the axes for a coordinate system.
   11191             :  *
   11192             :  * This method is the equivalent of the C++ method OGRSpatialReference::SetAxes
   11193             :  */
   11194           0 : OGRErr OSRSetAxes(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11195             :                   const char *pszXAxisName,
   11196             :                   OGRAxisOrientation eXAxisOrientation,
   11197             :                   const char *pszYAxisName,
   11198             :                   OGRAxisOrientation eYAxisOrientation)
   11199             : {
   11200           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAxes", OGRERR_FAILURE);
   11201             : 
   11202           0 :     return ToPointer(hSRS)->SetAxes(pszTargetKey, pszXAxisName,
   11203             :                                     eXAxisOrientation, pszYAxisName,
   11204           0 :                                     eYAxisOrientation);
   11205             : }
   11206             : 
   11207             : /************************************************************************/
   11208             : /*                       OSRExportToMICoordSys()                        */
   11209             : /************************************************************************/
   11210             : /**
   11211             :  * \brief Export coordinate system in Mapinfo style CoordSys format.
   11212             :  *
   11213             :  * This method is the equivalent of the C++ method
   11214             :  * OGRSpatialReference::exportToMICoordSys
   11215             :  */
   11216           5 : OGRErr OSRExportToMICoordSys(OGRSpatialReferenceH hSRS, char **ppszReturn)
   11217             : 
   11218             : {
   11219           5 :     VALIDATE_POINTER1(hSRS, "OSRExportToMICoordSys", OGRERR_FAILURE);
   11220             : 
   11221           5 :     *ppszReturn = nullptr;
   11222             : 
   11223           5 :     return ToPointer(hSRS)->exportToMICoordSys(ppszReturn);
   11224             : }
   11225             : 
   11226             : /************************************************************************/
   11227             : /*                         exportToMICoordSys()                         */
   11228             : /************************************************************************/
   11229             : 
   11230             : /**
   11231             :  * \brief Export coordinate system in Mapinfo style CoordSys format.
   11232             :  *
   11233             :  * Note that the returned WKT string should be freed with
   11234             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
   11235             :  *
   11236             :  * This method is the same as the C function OSRExportToMICoordSys().
   11237             :  *
   11238             :  * @param ppszResult pointer to which dynamically allocated Mapinfo CoordSys
   11239             :  * definition will be assigned.
   11240             :  *
   11241             :  * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
   11242             :  * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
   11243             :  */
   11244             : 
   11245           7 : OGRErr OGRSpatialReference::exportToMICoordSys(char **ppszResult) const
   11246             : 
   11247             : {
   11248           7 :     *ppszResult = MITABSpatialRef2CoordSys(this);
   11249           7 :     if (*ppszResult != nullptr && strlen(*ppszResult) > 0)
   11250           7 :         return OGRERR_NONE;
   11251             : 
   11252           0 :     return OGRERR_FAILURE;
   11253             : }
   11254             : 
   11255             : /************************************************************************/
   11256             : /*                       OSRImportFromMICoordSys()                      */
   11257             : /************************************************************************/
   11258             : /**
   11259             :  * \brief Import Mapinfo style CoordSys definition.
   11260             :  *
   11261             :  * This method is the equivalent of the C++ method
   11262             :  * OGRSpatialReference::importFromMICoordSys
   11263             :  */
   11264             : 
   11265           3 : OGRErr OSRImportFromMICoordSys(OGRSpatialReferenceH hSRS,
   11266             :                                const char *pszCoordSys)
   11267             : 
   11268             : {
   11269           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromMICoordSys", OGRERR_FAILURE);
   11270             : 
   11271           3 :     return ToPointer(hSRS)->importFromMICoordSys(pszCoordSys);
   11272             : }
   11273             : 
   11274             : /************************************************************************/
   11275             : /*                        importFromMICoordSys()                        */
   11276             : /************************************************************************/
   11277             : 
   11278             : /**
   11279             :  * \brief Import Mapinfo style CoordSys definition.
   11280             :  *
   11281             :  * The OGRSpatialReference is initialized from the passed Mapinfo style CoordSys
   11282             :  * definition string.
   11283             :  *
   11284             :  * This method is the equivalent of the C function OSRImportFromMICoordSys().
   11285             :  *
   11286             :  * @param pszCoordSys Mapinfo style CoordSys definition string.
   11287             :  *
   11288             :  * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
   11289             :  * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
   11290             :  */
   11291             : 
   11292          17 : OGRErr OGRSpatialReference::importFromMICoordSys(const char *pszCoordSys)
   11293             : 
   11294             : {
   11295          17 :     OGRSpatialReference *poResult = MITABCoordSys2SpatialRef(pszCoordSys);
   11296             : 
   11297          17 :     if (poResult == nullptr)
   11298           0 :         return OGRERR_FAILURE;
   11299             : 
   11300          17 :     *this = *poResult;
   11301          17 :     delete poResult;
   11302             : 
   11303          17 :     return OGRERR_NONE;
   11304             : }
   11305             : 
   11306             : /************************************************************************/
   11307             : /*                        OSRCalcInvFlattening()                        */
   11308             : /************************************************************************/
   11309             : 
   11310             : /**
   11311             :  * \brief Compute inverse flattening from semi-major and semi-minor axis
   11312             :  *
   11313             :  * @param dfSemiMajor Semi-major axis length.
   11314             :  * @param dfSemiMinor Semi-minor axis length.
   11315             :  *
   11316             :  * @return inverse flattening, or 0 if both axis are equal.
   11317             :  * @since GDAL 2.0
   11318             :  */
   11319             : 
   11320        7465 : double OSRCalcInvFlattening(double dfSemiMajor, double dfSemiMinor)
   11321             : {
   11322        7465 :     if (fabs(dfSemiMajor - dfSemiMinor) < 1e-1)
   11323          27 :         return 0;
   11324        7438 :     if (dfSemiMajor <= 0 || dfSemiMinor <= 0 || dfSemiMinor > dfSemiMajor)
   11325             :     {
   11326           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11327             :                  "OSRCalcInvFlattening(): Wrong input values");
   11328           0 :         return 0;
   11329             :     }
   11330             : 
   11331        7438 :     return dfSemiMajor / (dfSemiMajor - dfSemiMinor);
   11332             : }
   11333             : 
   11334             : /************************************************************************/
   11335             : /*                        OSRCalcInvFlattening()                        */
   11336             : /************************************************************************/
   11337             : 
   11338             : /**
   11339             :  * \brief Compute semi-minor axis from semi-major axis and inverse flattening.
   11340             :  *
   11341             :  * @param dfSemiMajor Semi-major axis length.
   11342             :  * @param dfInvFlattening Inverse flattening or 0 for sphere.
   11343             :  *
   11344             :  * @return semi-minor axis
   11345             :  * @since GDAL 2.0
   11346             :  */
   11347             : 
   11348         641 : double OSRCalcSemiMinorFromInvFlattening(double dfSemiMajor,
   11349             :                                          double dfInvFlattening)
   11350             : {
   11351         641 :     if (fabs(dfInvFlattening) < 0.000000000001)
   11352         101 :         return dfSemiMajor;
   11353         540 :     if (dfSemiMajor <= 0.0 || dfInvFlattening <= 1.0)
   11354             :     {
   11355           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11356             :                  "OSRCalcSemiMinorFromInvFlattening(): Wrong input values");
   11357           0 :         return dfSemiMajor;
   11358             :     }
   11359             : 
   11360         540 :     return dfSemiMajor * (1.0 - 1.0 / dfInvFlattening);
   11361             : }
   11362             : 
   11363             : /************************************************************************/
   11364             : /*                        GetWGS84SRS()                                 */
   11365             : /************************************************************************/
   11366             : 
   11367             : static OGRSpatialReference *poSRSWGS84 = nullptr;
   11368             : static CPLMutex *hMutex = nullptr;
   11369             : 
   11370             : /**
   11371             :  * \brief Returns an instance of a SRS object with WGS84 WKT.
   11372             :  *
   11373             :  * Note: the instance will have
   11374             :  * SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
   11375             :  *
   11376             :  * The reference counter of the returned object is not increased by this
   11377             :  * operation.
   11378             :  *
   11379             :  * @return instance.
   11380             :  * @since GDAL 2.0
   11381             :  */
   11382             : 
   11383         823 : OGRSpatialReference *OGRSpatialReference::GetWGS84SRS()
   11384             : {
   11385         823 :     CPLMutexHolderD(&hMutex);
   11386         823 :     if (poSRSWGS84 == nullptr)
   11387             :     {
   11388           4 :         poSRSWGS84 = new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
   11389           4 :         poSRSWGS84->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
   11390             :     }
   11391        1646 :     return poSRSWGS84;
   11392             : }
   11393             : 
   11394             : /************************************************************************/
   11395             : /*                        CleanupSRSWGS84Mutex()                       */
   11396             : /************************************************************************/
   11397             : 
   11398         927 : static void CleanupSRSWGS84Mutex()
   11399             : {
   11400         927 :     if (hMutex != nullptr)
   11401             :     {
   11402           2 :         poSRSWGS84->Release();
   11403           2 :         poSRSWGS84 = nullptr;
   11404           2 :         CPLDestroyMutex(hMutex);
   11405           2 :         hMutex = nullptr;
   11406             :     }
   11407         927 : }
   11408             : 
   11409             : /************************************************************************/
   11410             : /*                         OSRImportFromProj4()                         */
   11411             : /************************************************************************/
   11412             : /**
   11413             :  * \brief Import PROJ coordinate string.
   11414             :  *
   11415             :  * This function is the same as OGRSpatialReference::importFromProj4().
   11416             :  */
   11417         186 : OGRErr OSRImportFromProj4(OGRSpatialReferenceH hSRS, const char *pszProj4)
   11418             : 
   11419             : {
   11420         186 :     VALIDATE_POINTER1(hSRS, "OSRImportFromProj4", OGRERR_FAILURE);
   11421             : 
   11422         186 :     return OGRSpatialReference::FromHandle(hSRS)->importFromProj4(pszProj4);
   11423             : }
   11424             : 
   11425             : /************************************************************************/
   11426             : /*                          importFromProj4()                           */
   11427             : /************************************************************************/
   11428             : 
   11429             : /**
   11430             :  * \brief Import PROJ coordinate string.
   11431             :  *
   11432             :  * The OGRSpatialReference is initialized from the passed PROJs style
   11433             :  * coordinate system string.
   11434             :  *
   11435             :  * Example:
   11436             :  *   pszProj4 = "+proj=utm +zone=11 +datum=WGS84"
   11437             :  *
   11438             :  * It is also possible to import "+init=epsg:n" style definitions. Those are
   11439             :  * a legacy syntax that should be avoided in the future. In particular they will
   11440             :  * result in CRS objects whose axis order might not correspond to the official
   11441             :  * EPSG axis order.
   11442             :  *
   11443             :  * This method is the equivalent of the C function OSRImportFromProj4().
   11444             :  *
   11445             :  * @param pszProj4 the PROJ style string.
   11446             :  *
   11447             :  * @return OGRERR_NONE on success or OGRERR_CORRUPT_DATA on failure.
   11448             :  */
   11449             : 
   11450         627 : OGRErr OGRSpatialReference::importFromProj4(const char *pszProj4)
   11451             : 
   11452             : {
   11453        1254 :     TAKE_OPTIONAL_LOCK();
   11454             : 
   11455         627 :     if (strlen(pszProj4) >= 10000)
   11456             :     {
   11457           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Too long PROJ string");
   11458           1 :         return OGRERR_CORRUPT_DATA;
   11459             :     }
   11460             : 
   11461             :     /* -------------------------------------------------------------------- */
   11462             :     /*      Clear any existing definition.                                  */
   11463             :     /* -------------------------------------------------------------------- */
   11464         626 :     Clear();
   11465             : 
   11466         626 :     CPLString osProj4(pszProj4);
   11467         626 :     if (osProj4.find("type=crs") == std::string::npos)
   11468             :     {
   11469         617 :         osProj4 += " +type=crs";
   11470             :     }
   11471             : 
   11472         628 :     if (osProj4.find("+init=epsg:") != std::string::npos &&
   11473           2 :         getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr)
   11474             :     {
   11475           2 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11476             :                      "+init=epsg:XXXX syntax is deprecated. It might return "
   11477             :                      "a CRS with a non-EPSG compliant axis order.");
   11478             :     }
   11479         626 :     proj_context_use_proj4_init_rules(d->getPROJContext(), true);
   11480         626 :     d->setPjCRS(proj_create(d->getPROJContext(), osProj4.c_str()));
   11481         626 :     proj_context_use_proj4_init_rules(d->getPROJContext(), false);
   11482         626 :     return d->m_pj_crs ? OGRERR_NONE : OGRERR_CORRUPT_DATA;
   11483             : }
   11484             : 
   11485             : /************************************************************************/
   11486             : /*                          OSRExportToProj4()                          */
   11487             : /************************************************************************/
   11488             : /**
   11489             :  * \brief Export coordinate system in PROJ.4 legacy format.
   11490             :  *
   11491             :  * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
   11492             :  * PROJ &gt;= 6 is significantly different from earlier versions. In particular
   11493             :  * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
   11494             :  * will be missing most of the time. PROJ strings to encode CRS should be
   11495             :  * considered as a legacy solution. Using a AUTHORITY:CODE or WKT representation
   11496             :  * is the recommended way.
   11497             :  *
   11498             :  * This function is the same as OGRSpatialReference::exportToProj4().
   11499             :  */
   11500         427 : OGRErr CPL_STDCALL OSRExportToProj4(OGRSpatialReferenceH hSRS,
   11501             :                                     char **ppszReturn)
   11502             : 
   11503             : {
   11504         427 :     VALIDATE_POINTER1(hSRS, "OSRExportToProj4", OGRERR_FAILURE);
   11505             : 
   11506         427 :     *ppszReturn = nullptr;
   11507             : 
   11508         427 :     return OGRSpatialReference::FromHandle(hSRS)->exportToProj4(ppszReturn);
   11509             : }
   11510             : 
   11511             : /************************************************************************/
   11512             : /*                           exportToProj4()                            */
   11513             : /************************************************************************/
   11514             : 
   11515             : /**
   11516             :  * \brief Export coordinate system in PROJ.4 legacy format.
   11517             :  *
   11518             :  * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
   11519             :  * PROJ &gt;= 6 is significantly different from earlier versions. In particular
   11520             :  * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
   11521             :  * will be missing most of the time. PROJ strings to encode CRS should be
   11522             :  * considered as a a legacy solution. Using a AUTHORITY:CODE or WKT
   11523             :  * representation is the recommended way.
   11524             :  *
   11525             :  * Converts the loaded coordinate reference system into PROJ format
   11526             :  * to the extent possible.  The string returned in ppszProj4 should be
   11527             :  * deallocated by the caller with CPLFree() when no longer needed.
   11528             :  *
   11529             :  * LOCAL_CS coordinate systems are not translatable.  An empty string
   11530             :  * will be returned along with OGRERR_NONE.
   11531             :  *
   11532             :  * Special processing for Transverse Mercator:
   11533             :  * Starting with GDAL 3.0, if the OSR_USE_APPROX_TMERC configuration option is
   11534             :  * set to YES, the PROJ definition built from the SRS will use the +approx flag
   11535             :  * for the tmerc and utm projection methods, rather than the more accurate
   11536             :  * method.
   11537             :  *
   11538             :  * Starting with GDAL 3.0.3, this method will try to add a +towgs84 parameter,
   11539             :  * if there's none attached yet to the SRS and if the SRS has a EPSG code.
   11540             :  * See the AddGuessedTOWGS84() method for how this +towgs84 parameter may be
   11541             :  * added. This automatic addition may be disabled by setting the
   11542             :  * OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4 configuration option to NO.
   11543             :  *
   11544             :  * This method is the equivalent of the C function OSRExportToProj4().
   11545             :  *
   11546             :  * @param ppszProj4 pointer to which dynamically allocated PROJ definition
   11547             :  * will be assigned.
   11548             :  *
   11549             :  * @return OGRERR_NONE on success or an error code on failure.
   11550             :  */
   11551             : 
   11552        1394 : OGRErr OGRSpatialReference::exportToProj4(char **ppszProj4) const
   11553             : 
   11554             : {
   11555             :     // In the past calling this method was thread-safe, even if we never
   11556             :     // guaranteed it. Now proj_as_proj_string() will cache the result
   11557             :     // internally, so this is no longer thread-safe.
   11558        2788 :     std::lock_guard oLock(d->m_mutex);
   11559             : 
   11560        1394 :     d->refreshProjObj();
   11561        1394 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
   11562             :     {
   11563          25 :         *ppszProj4 = CPLStrdup("");
   11564          25 :         return OGRERR_FAILURE;
   11565             :     }
   11566             : 
   11567             :     // OSR_USE_ETMERC is here just for legacy
   11568        1369 :     bool bForceApproxTMerc = false;
   11569        1369 :     const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr);
   11570        1369 :     if (pszUseETMERC && pszUseETMERC[0])
   11571             :     {
   11572           0 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11573             :                      "OSR_USE_ETMERC is a legacy configuration option, which "
   11574             :                      "now has only effect when set to NO (YES is the default). "
   11575             :                      "Use OSR_USE_APPROX_TMERC=YES instead");
   11576           0 :         bForceApproxTMerc = !CPLTestBool(pszUseETMERC);
   11577             :     }
   11578             :     else
   11579             :     {
   11580             :         const char *pszUseApproxTMERC =
   11581        1369 :             CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr);
   11582        1369 :         if (pszUseApproxTMERC && pszUseApproxTMERC[0])
   11583             :         {
   11584           2 :             bForceApproxTMerc = CPLTestBool(pszUseApproxTMERC);
   11585             :         }
   11586             :     }
   11587        1369 :     const char *options[] = {
   11588        1369 :         bForceApproxTMerc ? "USE_APPROX_TMERC=YES" : nullptr, nullptr};
   11589             : 
   11590        1369 :     const char *projString = proj_as_proj_string(
   11591        1369 :         d->getPROJContext(), d->m_pj_crs, PJ_PROJ_4, options);
   11592             : 
   11593        1369 :     PJ *boundCRS = nullptr;
   11594        2734 :     if (projString &&
   11595        1365 :         (strstr(projString, "+datum=") == nullptr ||
   11596        2744 :          d->m_pjType == PJ_TYPE_COMPOUND_CRS) &&
   11597         535 :         CPLTestBool(
   11598             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4", "YES")))
   11599             :     {
   11600         535 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   11601         535 :             d->getPROJContext(), d->m_pj_crs, true,
   11602         535 :             strstr(projString, "+datum=") == nullptr);
   11603         535 :         if (boundCRS)
   11604             :         {
   11605         216 :             projString = proj_as_proj_string(d->getPROJContext(), boundCRS,
   11606             :                                              PJ_PROJ_4, options);
   11607             :         }
   11608             :     }
   11609             : 
   11610        1369 :     if (projString == nullptr)
   11611             :     {
   11612           4 :         *ppszProj4 = CPLStrdup("");
   11613           4 :         proj_destroy(boundCRS);
   11614           4 :         return OGRERR_FAILURE;
   11615             :     }
   11616        1365 :     *ppszProj4 = CPLStrdup(projString);
   11617        1365 :     proj_destroy(boundCRS);
   11618        1365 :     char *pszTypeCrs = strstr(*ppszProj4, " +type=crs");
   11619        1365 :     if (pszTypeCrs)
   11620        1365 :         *pszTypeCrs = '\0';
   11621        1365 :     return OGRERR_NONE;
   11622             : }
   11623             : 
   11624             : /************************************************************************/
   11625             : /*                            morphToESRI()                             */
   11626             : /************************************************************************/
   11627             : /**
   11628             :  * \brief Convert in place to ESRI WKT format.
   11629             :  *
   11630             :  * The value nodes of this coordinate system are modified in various manners
   11631             :  * more closely map onto the ESRI concept of WKT format.  This includes
   11632             :  * renaming a variety of projections and arguments, and stripping out
   11633             :  * nodes note recognised by ESRI (like AUTHORITY and AXIS).
   11634             :  *
   11635             :  * \note Since GDAL 3.0, this function has only user-visible effects at
   11636             :  * exportToWkt() time. It is recommended to use instead exportToWkt(char**,
   11637             :  * const char* const char*) const with options having FORMAT=WKT1_ESRI.
   11638             :  *
   11639             :  * This does the same as the C function OSRMorphToESRI().
   11640             :  *
   11641             :  * @return OGRERR_NONE unless something goes badly wrong.
   11642             :  * @deprecated
   11643             :  */
   11644             : 
   11645         261 : OGRErr OGRSpatialReference::morphToESRI()
   11646             : 
   11647             : {
   11648         261 :     TAKE_OPTIONAL_LOCK();
   11649             : 
   11650         261 :     d->refreshProjObj();
   11651         261 :     d->setMorphToESRI(true);
   11652             : 
   11653         522 :     return OGRERR_NONE;
   11654             : }
   11655             : 
   11656             : /************************************************************************/
   11657             : /*                           OSRMorphToESRI()                           */
   11658             : /************************************************************************/
   11659             : 
   11660             : /**
   11661             :  * \brief Convert in place to ESRI WKT format.
   11662             :  *
   11663             :  * This function is the same as the C++ method
   11664             :  * OGRSpatialReference::morphToESRI().
   11665             :  */
   11666         102 : OGRErr OSRMorphToESRI(OGRSpatialReferenceH hSRS)
   11667             : 
   11668             : {
   11669         102 :     VALIDATE_POINTER1(hSRS, "OSRMorphToESRI", OGRERR_FAILURE);
   11670             : 
   11671         102 :     return OGRSpatialReference::FromHandle(hSRS)->morphToESRI();
   11672             : }
   11673             : 
   11674             : /************************************************************************/
   11675             : /*                           morphFromESRI()                            */
   11676             : /************************************************************************/
   11677             : 
   11678             : /**
   11679             :  * \brief Convert in place from ESRI WKT format.
   11680             :  *
   11681             :  * The value notes of this coordinate system are modified in various manners
   11682             :  * to adhere more closely to the WKT standard.  This mostly involves
   11683             :  * translating a variety of ESRI names for projections, arguments and
   11684             :  * datums to "standard" names, as defined by Adam Gawne-Cain's reference
   11685             :  * translation of EPSG to WKT for the CT specification.
   11686             :  *
   11687             :  * \note Since GDAL 3.0, this function is essentially a no-operation, since
   11688             :  * morphing from ESRI is automatically done by importFromWkt(). Its only
   11689             :  * effect is to undo the effect of a potential prior call to morphToESRI().
   11690             :  *
   11691             :  * This does the same as the C function OSRMorphFromESRI().
   11692             :  *
   11693             :  * @return OGRERR_NONE unless something goes badly wrong.
   11694             :  * @deprecated
   11695             :  */
   11696             : 
   11697          21 : OGRErr OGRSpatialReference::morphFromESRI()
   11698             : 
   11699             : {
   11700          21 :     TAKE_OPTIONAL_LOCK();
   11701             : 
   11702          21 :     d->refreshProjObj();
   11703          21 :     d->setMorphToESRI(false);
   11704             : 
   11705          42 :     return OGRERR_NONE;
   11706             : }
   11707             : 
   11708             : /************************************************************************/
   11709             : /*                          OSRMorphFromESRI()                          */
   11710             : /************************************************************************/
   11711             : 
   11712             : /**
   11713             :  * \brief Convert in place from ESRI WKT format.
   11714             :  *
   11715             :  * This function is the same as the C++ method
   11716             :  * OGRSpatialReference::morphFromESRI().
   11717             :  */
   11718          20 : OGRErr OSRMorphFromESRI(OGRSpatialReferenceH hSRS)
   11719             : 
   11720             : {
   11721          20 :     VALIDATE_POINTER1(hSRS, "OSRMorphFromESRI", OGRERR_FAILURE);
   11722             : 
   11723          20 :     return OGRSpatialReference::FromHandle(hSRS)->morphFromESRI();
   11724             : }
   11725             : 
   11726             : /************************************************************************/
   11727             : /*                            FindMatches()                             */
   11728             : /************************************************************************/
   11729             : 
   11730             : /**
   11731             :  * \brief Try to identify a match between the passed SRS and a related SRS
   11732             :  * in a catalog.
   11733             :  *
   11734             :  * Matching may be partial, or may fail.
   11735             :  * Returned entries will be sorted by decreasing match confidence (first
   11736             :  * entry has the highest match confidence).
   11737             :  *
   11738             :  * The exact way matching is done may change in future versions. Starting with
   11739             :  * GDAL 3.0, it relies on PROJ' proj_identify() function.
   11740             :  *
   11741             :  * This method is the same as OSRFindMatches().
   11742             :  *
   11743             :  * @param papszOptions NULL terminated list of options or NULL
   11744             :  * @param pnEntries Output parameter. Number of values in the returned array.
   11745             :  * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
   11746             :  * will be allocated to an array of *pnEntries whose values between 0 and 100
   11747             :  * indicate the confidence in the match. 100 is the highest confidence level.
   11748             :  * The array must be freed with CPLFree().
   11749             :  *
   11750             :  * @return an array of SRS that match the passed SRS, or NULL. Must be freed
   11751             :  * with OSRFreeSRSArray()
   11752             :  *
   11753             :  * @since GDAL 2.3
   11754             :  *
   11755             :  * @see OGRSpatialReference::FindBestMatch()
   11756             :  */
   11757             : OGRSpatialReferenceH *
   11758        1262 : OGRSpatialReference::FindMatches(char **papszOptions, int *pnEntries,
   11759             :                                  int **ppanMatchConfidence) const
   11760             : {
   11761        2524 :     TAKE_OPTIONAL_LOCK();
   11762             : 
   11763        1262 :     CPL_IGNORE_RET_VAL(papszOptions);
   11764             : 
   11765        1262 :     if (pnEntries)
   11766        1262 :         *pnEntries = 0;
   11767        1262 :     if (ppanMatchConfidence)
   11768        1262 :         *ppanMatchConfidence = nullptr;
   11769             : 
   11770        1262 :     d->refreshProjObj();
   11771        1262 :     if (!d->m_pj_crs)
   11772           0 :         return nullptr;
   11773             : 
   11774        1262 :     int *panConfidence = nullptr;
   11775        1262 :     auto ctxt = d->getPROJContext();
   11776             :     auto list =
   11777        1262 :         proj_identify(ctxt, d->m_pj_crs, nullptr, nullptr, &panConfidence);
   11778        1262 :     if (!list)
   11779           0 :         return nullptr;
   11780             : 
   11781        1262 :     const int nMatches = proj_list_get_count(list);
   11782             : 
   11783        1262 :     if (pnEntries)
   11784        1262 :         *pnEntries = static_cast<int>(nMatches);
   11785             :     OGRSpatialReferenceH *pahRet = static_cast<OGRSpatialReferenceH *>(
   11786        1262 :         CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   11787        1262 :     if (ppanMatchConfidence)
   11788             :     {
   11789        1262 :         *ppanMatchConfidence =
   11790        1262 :             static_cast<int *>(CPLMalloc(sizeof(int) * (nMatches + 1)));
   11791             :     }
   11792             : 
   11793        1262 :     bool bSortAgain = false;
   11794             : 
   11795        3917 :     for (int i = 0; i < nMatches; i++)
   11796             :     {
   11797        2655 :         PJ *obj = proj_list_get(ctxt, list, i);
   11798        2655 :         CPLAssert(obj);
   11799        2655 :         OGRSpatialReference *poSRS = new OGRSpatialReference();
   11800        2655 :         poSRS->d->setPjCRS(obj);
   11801        2655 :         pahRet[i] = ToHandle(poSRS);
   11802             : 
   11803             :         // Identify matches that only differ by axis order
   11804           9 :         if (panConfidence[i] == 50 && GetAxesCount() == 2 &&
   11805        2673 :             poSRS->GetAxesCount() == 2 &&
   11806        2664 :             GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2})
   11807             :         {
   11808           9 :             OGRAxisOrientation eThisAxis0 = OAO_Other;
   11809           9 :             OGRAxisOrientation eThisAxis1 = OAO_Other;
   11810           9 :             OGRAxisOrientation eSRSAxis0 = OAO_Other;
   11811           9 :             OGRAxisOrientation eSRSAxis1 = OAO_Other;
   11812           9 :             GetAxis(nullptr, 0, &eThisAxis0);
   11813           9 :             GetAxis(nullptr, 1, &eThisAxis1);
   11814           9 :             poSRS->GetAxis(nullptr, 0, &eSRSAxis0);
   11815           9 :             poSRS->GetAxis(nullptr, 1, &eSRSAxis1);
   11816           9 :             if (eThisAxis0 == OAO_East && eThisAxis1 == OAO_North &&
   11817           9 :                 eSRSAxis0 == OAO_North && eSRSAxis1 == OAO_East)
   11818             :             {
   11819             :                 auto pj_crs_normalized =
   11820           9 :                     proj_normalize_for_visualization(ctxt, poSRS->d->m_pj_crs);
   11821           9 :                 if (pj_crs_normalized)
   11822             :                 {
   11823           9 :                     if (proj_is_equivalent_to(d->m_pj_crs, pj_crs_normalized,
   11824           9 :                                               PJ_COMP_EQUIVALENT))
   11825             :                     {
   11826           3 :                         bSortAgain = true;
   11827           3 :                         panConfidence[i] = 90;
   11828           3 :                         poSRS->SetDataAxisToSRSAxisMapping({2, 1});
   11829             :                     }
   11830           9 :                     proj_destroy(pj_crs_normalized);
   11831             :                 }
   11832             :             }
   11833             :         }
   11834             : 
   11835        2655 :         if (ppanMatchConfidence)
   11836        2655 :             (*ppanMatchConfidence)[i] = panConfidence[i];
   11837             :     }
   11838             : 
   11839        1262 :     if (bSortAgain)
   11840             :     {
   11841           3 :         std::vector<int> anIndices;
   11842          12 :         for (int i = 0; i < nMatches; ++i)
   11843           9 :             anIndices.push_back(i);
   11844             : 
   11845           3 :         std::stable_sort(anIndices.begin(), anIndices.end(),
   11846           9 :                          [&panConfidence](int i, int j)
   11847           9 :                          { return panConfidence[i] > panConfidence[j]; });
   11848             : 
   11849             :         OGRSpatialReferenceH *pahRetSorted =
   11850             :             static_cast<OGRSpatialReferenceH *>(
   11851           3 :                 CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   11852          12 :         for (int i = 0; i < nMatches; ++i)
   11853             :         {
   11854           9 :             pahRetSorted[i] = pahRet[anIndices[i]];
   11855           9 :             if (ppanMatchConfidence)
   11856           9 :                 (*ppanMatchConfidence)[i] = panConfidence[anIndices[i]];
   11857             :         }
   11858           3 :         CPLFree(pahRet);
   11859           3 :         pahRet = pahRetSorted;
   11860             :     }
   11861             : 
   11862        1262 :     pahRet[nMatches] = nullptr;
   11863        1262 :     proj_list_destroy(list);
   11864        1262 :     proj_int_list_destroy(panConfidence);
   11865             : 
   11866        1262 :     return pahRet;
   11867             : }
   11868             : 
   11869             : /************************************************************************/
   11870             : /*                          importFromEPSGA()                           */
   11871             : /************************************************************************/
   11872             : 
   11873             : /**
   11874             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   11875             :  * code.
   11876             :  *
   11877             :  * This method will initialize the spatial reference based on the
   11878             :  * passed in EPSG CRS code found in the PROJ database.
   11879             :  *
   11880             :  * Since GDAL 3.0, this method is identical to importFromEPSG().
   11881             :  *
   11882             :  * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
   11883             :  * 7-parameter Helmert transformation to WGS84 when there is one and only one
   11884             :  * such method available for the CRS. This behavior might not always be
   11885             :  * desirable, so starting with GDAL 3.0.3, this is no longer done unless
   11886             :  * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
   11887             :  * The AddGuessedTOWGS84() method can also be used for that purpose.
   11888             :  *
   11889             :  * The method will also by default substitute a deprecated EPSG code by its
   11890             :  * non-deprecated replacement. If this behavior is not desired, the
   11891             :  * OSR_USE_NON_DEPRECATED configuration option can be set to NO.
   11892             :  *
   11893             :  * This method is the same as the C function OSRImportFromEPSGA().
   11894             :  *
   11895             :  * @param nCode a CRS code.
   11896             :  *
   11897             :  * @return OGRERR_NONE on success, or an error code on failure.
   11898             :  */
   11899             : 
   11900       35353 : OGRErr OGRSpatialReference::importFromEPSGA(int nCode)
   11901             : 
   11902             : {
   11903       70706 :     TAKE_OPTIONAL_LOCK();
   11904             : 
   11905       35353 :     Clear();
   11906             : 
   11907             :     const char *pszUseNonDeprecated =
   11908       35353 :         CPLGetConfigOption("OSR_USE_NON_DEPRECATED", nullptr);
   11909             :     const bool bUseNonDeprecated =
   11910       35353 :         CPLTestBool(pszUseNonDeprecated ? pszUseNonDeprecated : "YES");
   11911       35353 :     const bool bAddTOWGS84 = CPLTestBool(
   11912             :         CPLGetConfigOption("OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG", "NO"));
   11913       35353 :     auto tlsCache = OSRGetProjTLSCache();
   11914       35353 :     if (tlsCache)
   11915             :     {
   11916             :         auto cachedObj =
   11917       35353 :             tlsCache->GetPJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84);
   11918       35353 :         if (cachedObj)
   11919             :         {
   11920       27311 :             d->setPjCRS(cachedObj);
   11921       27311 :             return OGRERR_NONE;
   11922             :         }
   11923             :     }
   11924             : 
   11925       16084 :     CPLString osCode;
   11926        8042 :     osCode.Printf("%d", nCode);
   11927             :     PJ *obj;
   11928        8042 :     constexpr int FIRST_NON_DEPRECATED_ESRI_CODE = 53001;
   11929        8042 :     if (nCode < FIRST_NON_DEPRECATED_ESRI_CODE)
   11930             :     {
   11931        8038 :         obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   11932             :                                         osCode.c_str(), PJ_CATEGORY_CRS, true,
   11933             :                                         nullptr);
   11934        8038 :         if (!obj)
   11935             :         {
   11936          23 :             return OGRERR_FAILURE;
   11937             :         }
   11938             :     }
   11939             :     else
   11940             :     {
   11941             :         // Likely to be an ESRI CRS...
   11942           4 :         CPLErr eLastErrorType = CE_None;
   11943           4 :         CPLErrorNum eLastErrorNum = CPLE_None;
   11944           4 :         std::string osLastErrorMsg;
   11945           4 :         bool bIsESRI = false;
   11946             :         {
   11947           8 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
   11948           4 :             CPLErrorReset();
   11949           4 :             obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   11950             :                                             osCode.c_str(), PJ_CATEGORY_CRS,
   11951             :                                             true, nullptr);
   11952           4 :             if (!obj)
   11953             :             {
   11954           1 :                 eLastErrorType = CPLGetLastErrorType();
   11955           1 :                 eLastErrorNum = CPLGetLastErrorNo();
   11956           1 :                 osLastErrorMsg = CPLGetLastErrorMsg();
   11957           1 :                 obj = proj_create_from_database(d->getPROJContext(), "ESRI",
   11958             :                                                 osCode.c_str(), PJ_CATEGORY_CRS,
   11959             :                                                 true, nullptr);
   11960           1 :                 if (obj)
   11961           0 :                     bIsESRI = true;
   11962             :             }
   11963             :         }
   11964           4 :         if (!obj)
   11965             :         {
   11966           1 :             if (eLastErrorType != CE_None)
   11967           1 :                 CPLError(eLastErrorType, eLastErrorNum, "%s",
   11968             :                          osLastErrorMsg.c_str());
   11969           1 :             return OGRERR_FAILURE;
   11970             :         }
   11971           3 :         if (bIsESRI)
   11972             :         {
   11973           0 :             CPLError(CE_Warning, CPLE_AppDefined,
   11974             :                      "EPSG:%d is not a valid CRS code, but ESRI:%d is. "
   11975             :                      "Assuming ESRI:%d was meant",
   11976             :                      nCode, nCode, nCode);
   11977             :         }
   11978             :     }
   11979             : 
   11980        8018 :     if (bUseNonDeprecated && proj_is_deprecated(obj))
   11981             :     {
   11982         409 :         auto list = proj_get_non_deprecated(d->getPROJContext(), obj);
   11983         409 :         if (list)
   11984             :         {
   11985         409 :             const auto count = proj_list_get_count(list);
   11986         409 :             if (count == 1)
   11987             :             {
   11988             :                 auto nonDeprecated =
   11989         359 :                     proj_list_get(d->getPROJContext(), list, 0);
   11990         359 :                 if (nonDeprecated)
   11991             :                 {
   11992         359 :                     if (pszUseNonDeprecated == nullptr)
   11993             :                     {
   11994             :                         const char *pszNewAuth =
   11995         359 :                             proj_get_id_auth_name(nonDeprecated, 0);
   11996             :                         const char *pszNewCode =
   11997         359 :                             proj_get_id_code(nonDeprecated, 0);
   11998         359 :                         CPLError(CE_Warning, CPLE_AppDefined,
   11999             :                                  "CRS EPSG:%d is deprecated. "
   12000             :                                  "Its non-deprecated replacement %s:%s "
   12001             :                                  "will be used instead. "
   12002             :                                  "To use the original CRS, set the "
   12003             :                                  "OSR_USE_NON_DEPRECATED "
   12004             :                                  "configuration option to NO.",
   12005             :                                  nCode, pszNewAuth ? pszNewAuth : "(null)",
   12006             :                                  pszNewCode ? pszNewCode : "(null)");
   12007             :                     }
   12008         359 :                     proj_destroy(obj);
   12009         359 :                     obj = nonDeprecated;
   12010             :                 }
   12011             :             }
   12012             :         }
   12013         409 :         proj_list_destroy(list);
   12014             :     }
   12015             : 
   12016        8018 :     if (bAddTOWGS84)
   12017             :     {
   12018           1 :         auto boundCRS = proj_crs_create_bound_crs_to_WGS84(d->getPROJContext(),
   12019             :                                                            obj, nullptr);
   12020           1 :         if (boundCRS)
   12021             :         {
   12022           1 :             proj_destroy(obj);
   12023           1 :             obj = boundCRS;
   12024             :         }
   12025             :     }
   12026             : 
   12027        8018 :     d->setPjCRS(obj);
   12028             : 
   12029        8018 :     if (tlsCache)
   12030             :     {
   12031        8018 :         tlsCache->CachePJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84,
   12032             :                                      obj);
   12033             :     }
   12034             : 
   12035        8018 :     return OGRERR_NONE;
   12036             : }
   12037             : 
   12038             : /************************************************************************/
   12039             : /*                          AddGuessedTOWGS84()                         */
   12040             : /************************************************************************/
   12041             : 
   12042             : /**
   12043             :  * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
   12044             :  * to WGS84.
   12045             :  *
   12046             :  * This method try to attach a 3-parameter or 7-parameter Helmert transformation
   12047             :  * to WGS84 when there is one and only one such method available for the CRS.
   12048             :  * Note: this is more restrictive to how GDAL < 3 worked.
   12049             :  *
   12050             :  * This method is the same as the C function OSRAddGuessedTOWGS84().
   12051             :  *
   12052             :  * @return OGRERR_NONE on success, or an error code on failure (the CRS has
   12053             :  * already a transformation to WGS84 or none matching could be found).
   12054             :  *
   12055             :  * @since GDAL 3.0.3
   12056             :  */
   12057          18 : OGRErr OGRSpatialReference::AddGuessedTOWGS84()
   12058             : {
   12059          36 :     TAKE_OPTIONAL_LOCK();
   12060             : 
   12061          18 :     d->refreshProjObj();
   12062          18 :     if (!d->m_pj_crs)
   12063           0 :         return OGRERR_FAILURE;
   12064          18 :     auto boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   12065          18 :         d->getPROJContext(), d->m_pj_crs, false, true);
   12066          18 :     if (!boundCRS)
   12067             :     {
   12068           0 :         return OGRERR_FAILURE;
   12069             :     }
   12070          18 :     d->setPjCRS(boundCRS);
   12071          18 :     return OGRERR_NONE;
   12072             : }
   12073             : 
   12074             : /************************************************************************/
   12075             : /*                         OSRImportFromEPSGA()                         */
   12076             : /************************************************************************/
   12077             : 
   12078             : /**
   12079             :  * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
   12080             :  * to WGS84.
   12081             :  *
   12082             :  * This function is the same as OGRSpatialReference::AddGuessedTOWGS84().
   12083             :  *
   12084             :  * @since GDAL 3.0.3
   12085             :  */
   12086             : 
   12087           2 : OGRErr OSRAddGuessedTOWGS84(OGRSpatialReferenceH hSRS)
   12088             : 
   12089             : {
   12090           2 :     VALIDATE_POINTER1(hSRS, "OSRAddGuessedTOWGS84", OGRERR_FAILURE);
   12091             : 
   12092           2 :     return OGRSpatialReference::FromHandle(hSRS)->AddGuessedTOWGS84();
   12093             : }
   12094             : 
   12095             : /************************************************************************/
   12096             : /*                         OSRImportFromEPSGA()                         */
   12097             : /************************************************************************/
   12098             : 
   12099             : /**
   12100             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12101             :  * code.
   12102             :  *
   12103             :  * This function is the same as OGRSpatialReference::importFromEPSGA().
   12104             :  */
   12105             : 
   12106           3 : OGRErr CPL_STDCALL OSRImportFromEPSGA(OGRSpatialReferenceH hSRS, int nCode)
   12107             : 
   12108             : {
   12109           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSGA", OGRERR_FAILURE);
   12110             : 
   12111           3 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSGA(nCode);
   12112             : }
   12113             : 
   12114             : /************************************************************************/
   12115             : /*                           importFromEPSG()                           */
   12116             : /************************************************************************/
   12117             : 
   12118             : /**
   12119             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12120             :  * code.
   12121             :  *
   12122             :  * This method will initialize the spatial reference based on the
   12123             :  * passed in EPSG CRS code found in the PROJ database.
   12124             :  *
   12125             :  * This method is the same as the C function OSRImportFromEPSG().
   12126             :  *
   12127             :  * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
   12128             :  * 7-parameter Helmert transformation to WGS84 when there is one and only one
   12129             :  * such method available for the CRS. This behavior might not always be
   12130             :  * desirable, so starting with GDAL 3.0.3, this is no longer done unless
   12131             :  * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
   12132             :  *
   12133             :  * @param nCode a GCS or PCS code from the horizontal coordinate system table.
   12134             :  *
   12135             :  * @return OGRERR_NONE on success, or an error code on failure.
   12136             :  */
   12137             : 
   12138       34530 : OGRErr OGRSpatialReference::importFromEPSG(int nCode)
   12139             : 
   12140             : {
   12141       34530 :     return importFromEPSGA(nCode);
   12142             : }
   12143             : 
   12144             : /************************************************************************/
   12145             : /*                         OSRImportFromEPSG()                          */
   12146             : /************************************************************************/
   12147             : 
   12148             : /**
   12149             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12150             :  * code.
   12151             :  *
   12152             :  * This function is the same as OGRSpatialReference::importFromEPSG().
   12153             :  */
   12154             : 
   12155        1414 : OGRErr CPL_STDCALL OSRImportFromEPSG(OGRSpatialReferenceH hSRS, int nCode)
   12156             : 
   12157             : {
   12158        1414 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSG", OGRERR_FAILURE);
   12159             : 
   12160        1414 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSG(nCode);
   12161             : }
   12162             : 
   12163             : /************************************************************************/
   12164             : /*                        EPSGTreatsAsLatLong()                         */
   12165             : /************************************************************************/
   12166             : 
   12167             : /**
   12168             :  * \brief This method returns TRUE if this geographic coordinate
   12169             :  * system should be treated as having lat/long coordinate ordering.
   12170             :  *
   12171             :  * Currently this returns TRUE for all geographic coordinate systems
   12172             :  * with axes set defining it as lat, long (prior to GDAL 3.10, it
   12173             :  * also checked that the CRS had belonged to EPSG authority, but this check
   12174             :  * has now been removed).
   12175             :  *
   12176             :  * \note Important change of behavior since GDAL 3.0. In previous versions,
   12177             :  * geographic CRS imported with importFromEPSG() would cause this method to
   12178             :  * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
   12179             :  * is now equivalent to importFromEPSGA().
   12180             :  *
   12181             :  * FALSE will be returned for all coordinate systems that are not geographic,
   12182             :  * or whose axes ordering is not latitude, longitude.
   12183             :  *
   12184             :  * This method is the same as the C function OSREPSGTreatsAsLatLong().
   12185             :  *
   12186             :  * @return TRUE or FALSE.
   12187             :  */
   12188             : 
   12189         712 : int OGRSpatialReference::EPSGTreatsAsLatLong() const
   12190             : 
   12191             : {
   12192        1424 :     TAKE_OPTIONAL_LOCK();
   12193             : 
   12194         712 :     if (!IsGeographic())
   12195         566 :         return FALSE;
   12196             : 
   12197         146 :     d->demoteFromBoundCRS();
   12198             : 
   12199         146 :     bool ret = false;
   12200         146 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12201             :     {
   12202             :         auto horizCRS =
   12203           3 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
   12204           3 :         if (horizCRS)
   12205             :         {
   12206             :             auto cs =
   12207           3 :                 proj_crs_get_coordinate_system(d->getPROJContext(), horizCRS);
   12208           3 :             if (cs)
   12209             :             {
   12210           3 :                 const char *pszDirection = nullptr;
   12211           3 :                 if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12212             :                                           nullptr, &pszDirection, nullptr,
   12213           3 :                                           nullptr, nullptr, nullptr))
   12214             :                 {
   12215           3 :                     if (EQUAL(pszDirection, "north"))
   12216             :                     {
   12217           3 :                         ret = true;
   12218             :                     }
   12219             :                 }
   12220             : 
   12221           3 :                 proj_destroy(cs);
   12222             :             }
   12223             : 
   12224           3 :             proj_destroy(horizCRS);
   12225             :         }
   12226             :     }
   12227             :     else
   12228             :     {
   12229             :         auto cs =
   12230         143 :             proj_crs_get_coordinate_system(d->getPROJContext(), d->m_pj_crs);
   12231         143 :         if (cs)
   12232             :         {
   12233         143 :             const char *pszDirection = nullptr;
   12234         143 :             if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12235             :                                       nullptr, &pszDirection, nullptr, nullptr,
   12236         143 :                                       nullptr, nullptr))
   12237             :             {
   12238         143 :                 if (EQUAL(pszDirection, "north"))
   12239             :                 {
   12240         116 :                     ret = true;
   12241             :                 }
   12242             :             }
   12243             : 
   12244         143 :             proj_destroy(cs);
   12245             :         }
   12246             :     }
   12247         146 :     d->undoDemoteFromBoundCRS();
   12248             : 
   12249         146 :     return ret;
   12250             : }
   12251             : 
   12252             : /************************************************************************/
   12253             : /*                       OSREPSGTreatsAsLatLong()                       */
   12254             : /************************************************************************/
   12255             : 
   12256             : /**
   12257             :  * \brief This function returns TRUE if this geographic coordinate
   12258             :  * system should be treated as having lat/long coordinate ordering.
   12259             :  *
   12260             :  * This function is the same as OGRSpatialReference::OSREPSGTreatsAsLatLong().
   12261             :  */
   12262             : 
   12263         180 : int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH hSRS)
   12264             : 
   12265             : {
   12266         180 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsLatLong", OGRERR_FAILURE);
   12267             : 
   12268         180 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsLatLong();
   12269             : }
   12270             : 
   12271             : /************************************************************************/
   12272             : /*                     EPSGTreatsAsNorthingEasting()                    */
   12273             : /************************************************************************/
   12274             : 
   12275             : /**
   12276             :  * \brief This method returns TRUE if this projected coordinate
   12277             :  * system should be treated as having northing/easting coordinate ordering.
   12278             :  *
   12279             :  * Currently this returns TRUE for all projected coordinate systems
   12280             :  * with axes set defining it as northing, easting (prior to GDAL 3.10, it
   12281             :  * also checked that the CRS had belonged to EPSG authority, but this check
   12282             :  * has now been removed).
   12283             :  *
   12284             :  * \note Important change of behavior since GDAL 3.0. In previous versions,
   12285             :  * projected CRS with northing, easting axis order imported with
   12286             :  * importFromEPSG() would cause this method to
   12287             :  * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
   12288             :  * is now equivalent to importFromEPSGA().
   12289             :  *
   12290             :  * FALSE will be returned for all coordinate systems that are not projected,
   12291             :  * or whose axes ordering is not northing, easting.
   12292             :  *
   12293             :  * This method is the same as the C function EPSGTreatsAsNorthingEasting().
   12294             :  *
   12295             :  * @return TRUE or FALSE.
   12296             :  *
   12297             :  * @since OGR 1.10.0
   12298             :  */
   12299             : 
   12300         603 : int OGRSpatialReference::EPSGTreatsAsNorthingEasting() const
   12301             : 
   12302             : {
   12303        1206 :     TAKE_OPTIONAL_LOCK();
   12304             : 
   12305         603 :     if (!IsProjected())
   12306          24 :         return FALSE;
   12307             : 
   12308         579 :     d->demoteFromBoundCRS();
   12309             :     PJ *projCRS;
   12310         579 :     const auto ctxt = d->getPROJContext();
   12311         579 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12312             :     {
   12313           4 :         projCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   12314           4 :         if (!projCRS || proj_get_type(projCRS) != PJ_TYPE_PROJECTED_CRS)
   12315             :         {
   12316           0 :             d->undoDemoteFromBoundCRS();
   12317           0 :             proj_destroy(projCRS);
   12318           0 :             return FALSE;
   12319             :         }
   12320             :     }
   12321             :     else
   12322             :     {
   12323         575 :         projCRS = proj_clone(ctxt, d->m_pj_crs);
   12324             :     }
   12325             : 
   12326         579 :     bool ret = false;
   12327         579 :     auto cs = proj_crs_get_coordinate_system(ctxt, projCRS);
   12328         579 :     proj_destroy(projCRS);
   12329         579 :     d->undoDemoteFromBoundCRS();
   12330             : 
   12331         579 :     if (cs)
   12332             :     {
   12333         579 :         ret = isNorthEastAxisOrder(ctxt, cs);
   12334         579 :         proj_destroy(cs);
   12335             :     }
   12336             : 
   12337         579 :     return ret;
   12338             : }
   12339             : 
   12340             : /************************************************************************/
   12341             : /*                     OSREPSGTreatsAsNorthingEasting()                 */
   12342             : /************************************************************************/
   12343             : 
   12344             : /**
   12345             :  * \brief This function returns TRUE if this projected coordinate
   12346             :  * system should be treated as having northing/easting coordinate ordering.
   12347             :  *
   12348             :  * This function is the same as
   12349             :  * OGRSpatialReference::EPSGTreatsAsNorthingEasting().
   12350             :  *
   12351             :  * @since OGR 1.10.0
   12352             :  */
   12353             : 
   12354         187 : int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH hSRS)
   12355             : 
   12356             : {
   12357         187 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsNorthingEasting", OGRERR_FAILURE);
   12358             : 
   12359         187 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsNorthingEasting();
   12360             : }
   12361             : 
   12362             : /************************************************************************/
   12363             : /*                     ImportFromESRIWisconsinWKT()                     */
   12364             : /*                                                                      */
   12365             : /*      Search a ESRI State Plane WKT and import it.                    */
   12366             : /************************************************************************/
   12367             : 
   12368             : // This is only used by the HFA driver and somewhat dubious we really need that
   12369             : // Coming from an old ESRI merge
   12370             : 
   12371           1 : OGRErr OGRSpatialReference::ImportFromESRIWisconsinWKT(const char *prjName,
   12372             :                                                        double centralMeridian,
   12373             :                                                        double latOfOrigin,
   12374             :                                                        const char *unitsName,
   12375             :                                                        const char *crsName)
   12376             : {
   12377           2 :     TAKE_OPTIONAL_LOCK();
   12378             : 
   12379           1 :     if (centralMeridian < -93 || centralMeridian > -87)
   12380           0 :         return OGRERR_FAILURE;
   12381           1 :     if (latOfOrigin < 40 || latOfOrigin > 47)
   12382           0 :         return OGRERR_FAILURE;
   12383             : 
   12384             :     // If the CS name is known.
   12385           1 :     if (!prjName && !unitsName && crsName)
   12386             :     {
   12387           0 :         const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12388           0 :         PJ_OBJ_LIST *list = proj_create_from_name(
   12389             :             d->getPROJContext(), "ESRI", crsName, &type, 1, false, 1, nullptr);
   12390           0 :         if (list)
   12391             :         {
   12392           0 :             if (proj_list_get_count(list) == 1)
   12393             :             {
   12394           0 :                 auto crs = proj_list_get(d->getPROJContext(), list, 0);
   12395           0 :                 if (crs)
   12396             :                 {
   12397           0 :                     Clear();
   12398           0 :                     d->setPjCRS(crs);
   12399           0 :                     proj_list_destroy(list);
   12400           0 :                     return OGRERR_NONE;
   12401             :                 }
   12402             :             }
   12403           0 :             proj_list_destroy(list);
   12404             :         }
   12405           0 :         return OGRERR_FAILURE;
   12406             :     }
   12407             : 
   12408           1 :     if (prjName == nullptr || unitsName == nullptr)
   12409             :     {
   12410           0 :         return OGRERR_FAILURE;
   12411             :     }
   12412             : 
   12413           1 :     const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12414           1 :     PJ_OBJ_LIST *list = proj_create_from_name(d->getPROJContext(), "ESRI",
   12415             :                                               "NAD_1983_HARN_WISCRS_", &type, 1,
   12416             :                                               true, 0, nullptr);
   12417           1 :     if (list)
   12418             :     {
   12419           1 :         const auto listSize = proj_list_get_count(list);
   12420           8 :         for (int i = 0; i < listSize; i++)
   12421             :         {
   12422           8 :             auto crs = proj_list_get(d->getPROJContext(), list, i);
   12423           8 :             if (!crs)
   12424             :             {
   12425           7 :                 continue;
   12426             :             }
   12427             : 
   12428           8 :             auto conv = proj_crs_get_coordoperation(d->getPROJContext(), crs);
   12429           8 :             if (!conv)
   12430             :             {
   12431           0 :                 proj_destroy(crs);
   12432           0 :                 continue;
   12433             :             }
   12434           8 :             const char *pszMethodCode = nullptr;
   12435           8 :             proj_coordoperation_get_method_info(
   12436             :                 d->getPROJContext(), conv, nullptr, nullptr, &pszMethodCode);
   12437           8 :             const int nMethodCode = atoi(pszMethodCode ? pszMethodCode : "0");
   12438           8 :             if (!((EQUAL(prjName, SRS_PT_TRANSVERSE_MERCATOR) &&
   12439             :                    nMethodCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) ||
   12440           3 :                   (EQUAL(prjName, "Lambert_Conformal_Conic") &&
   12441             :                    nMethodCode ==
   12442             :                        EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)))
   12443             :             {
   12444           3 :                 proj_destroy(crs);
   12445           3 :                 proj_destroy(conv);
   12446           3 :                 continue;
   12447             :             }
   12448             : 
   12449             :             auto coordSys =
   12450           5 :                 proj_crs_get_coordinate_system(d->getPROJContext(), crs);
   12451           5 :             if (!coordSys)
   12452             :             {
   12453           0 :                 proj_destroy(crs);
   12454           0 :                 proj_destroy(conv);
   12455           0 :                 continue;
   12456             :             }
   12457             : 
   12458           5 :             double dfConvFactor = 0.0;
   12459           5 :             proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
   12460             :                                   nullptr, nullptr, &dfConvFactor, nullptr,
   12461             :                                   nullptr, nullptr);
   12462           5 :             proj_destroy(coordSys);
   12463             : 
   12464           6 :             if ((EQUAL(unitsName, "meters") && dfConvFactor != 1.0) ||
   12465           1 :                 (!EQUAL(unitsName, "meters") &&
   12466           0 :                  std::fabs(dfConvFactor - CPLAtof(SRS_UL_US_FOOT_CONV)) >
   12467             :                      1e-10))
   12468             :             {
   12469           4 :                 proj_destroy(crs);
   12470           4 :                 proj_destroy(conv);
   12471           4 :                 continue;
   12472             :             }
   12473             : 
   12474           1 :             int idx_lat = proj_coordoperation_get_param_index(
   12475             :                 d->getPROJContext(), conv,
   12476             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
   12477           1 :             double valueLat = -1000;
   12478           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lat,
   12479             :                                           nullptr, nullptr, nullptr, &valueLat,
   12480             :                                           nullptr, nullptr, nullptr, nullptr,
   12481             :                                           nullptr, nullptr);
   12482           1 :             int idx_lon = proj_coordoperation_get_param_index(
   12483             :                 d->getPROJContext(), conv,
   12484             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN);
   12485           1 :             double valueLong = -1000;
   12486           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lon,
   12487             :                                           nullptr, nullptr, nullptr, &valueLong,
   12488             :                                           nullptr, nullptr, nullptr, nullptr,
   12489             :                                           nullptr, nullptr);
   12490           1 :             if (std::fabs(centralMeridian - valueLong) <= 1e-10 &&
   12491           1 :                 std::fabs(latOfOrigin - valueLat) <= 1e-10)
   12492             :             {
   12493           1 :                 Clear();
   12494           1 :                 d->setPjCRS(crs);
   12495           1 :                 proj_list_destroy(list);
   12496           1 :                 proj_destroy(conv);
   12497           1 :                 return OGRERR_NONE;
   12498             :             }
   12499             : 
   12500           0 :             proj_destroy(crs);
   12501           0 :             proj_destroy(conv);
   12502             :         }
   12503           0 :         proj_list_destroy(list);
   12504             :     }
   12505             : 
   12506           0 :     return OGRERR_FAILURE;
   12507             : }
   12508             : 
   12509             : /************************************************************************/
   12510             : /*                      GetAxisMappingStrategy()                        */
   12511             : /************************************************************************/
   12512             : 
   12513             : /** \brief Return the data axis to CRS axis mapping strategy.
   12514             :  *
   12515             :  * <ul>
   12516             :  * <li>OAMS_TRADITIONAL_GIS_ORDER means that for geographic CRS with
   12517             :  *     lat/long order, the data will still be long/lat ordered. Similarly for
   12518             :  *     a projected CRS with northing/easting order, the data will still be
   12519             :  *     easting/northing ordered.
   12520             :  * <li>OAMS_AUTHORITY_COMPLIANT means that the data axis will be identical to
   12521             :  *     the CRS axis.
   12522             :  * <li>OAMS_CUSTOM means that the data axis are customly defined with
   12523             :  *     SetDataAxisToSRSAxisMapping()
   12524             :  * </ul>
   12525             :  * @return the data axis to CRS axis mapping strategy.
   12526             :  * @since GDAL 3.0
   12527             :  */
   12528          72 : OSRAxisMappingStrategy OGRSpatialReference::GetAxisMappingStrategy() const
   12529             : {
   12530          72 :     TAKE_OPTIONAL_LOCK();
   12531             : 
   12532         144 :     return d->m_axisMappingStrategy;
   12533             : }
   12534             : 
   12535             : /************************************************************************/
   12536             : /*                      OSRGetAxisMappingStrategy()                     */
   12537             : /************************************************************************/
   12538             : 
   12539             : /** \brief Return the data axis to CRS axis mapping strategy.
   12540             :  *
   12541             :  * See OGRSpatialReference::GetAxisMappingStrategy()
   12542             :  * @since GDAL 3.0
   12543             :  */
   12544          37 : OSRAxisMappingStrategy OSRGetAxisMappingStrategy(OGRSpatialReferenceH hSRS)
   12545             : {
   12546          37 :     VALIDATE_POINTER1(hSRS, "OSRGetAxisMappingStrategy", OAMS_CUSTOM);
   12547             : 
   12548          37 :     return OGRSpatialReference::FromHandle(hSRS)->GetAxisMappingStrategy();
   12549             : }
   12550             : 
   12551             : /************************************************************************/
   12552             : /*                      SetAxisMappingStrategy()                        */
   12553             : /************************************************************************/
   12554             : 
   12555             : /** \brief Set the data axis to CRS axis mapping strategy.
   12556             :  *
   12557             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
   12558             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
   12559             :  * later being the default value when the option is not set) to control the
   12560             :  * value of the data axis to CRS axis mapping strategy when a
   12561             :  * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
   12562             :  * override this default value.
   12563             :  *
   12564             :  * See OGRSpatialReference::GetAxisMappingStrategy()
   12565             :  * @since GDAL 3.0
   12566             :  */
   12567       75923 : void OGRSpatialReference::SetAxisMappingStrategy(
   12568             :     OSRAxisMappingStrategy strategy)
   12569             : {
   12570      151675 :     TAKE_OPTIONAL_LOCK();
   12571             : 
   12572       75819 :     d->m_axisMappingStrategy = strategy;
   12573       75807 :     d->refreshAxisMapping();
   12574       75730 : }
   12575             : 
   12576             : /************************************************************************/
   12577             : /*                      OSRSetAxisMappingStrategy()                     */
   12578             : /************************************************************************/
   12579             : 
   12580             : /** \brief Set the data axis to CRS axis mapping strategy.
   12581             :  *
   12582             :  * See OGRSpatialReference::SetAxisMappingStrategy()
   12583             :  * @since GDAL 3.0
   12584             :  */
   12585         821 : void OSRSetAxisMappingStrategy(OGRSpatialReferenceH hSRS,
   12586             :                                OSRAxisMappingStrategy strategy)
   12587             : {
   12588         821 :     VALIDATE_POINTER0(hSRS, "OSRSetAxisMappingStrategy");
   12589             : 
   12590         821 :     OGRSpatialReference::FromHandle(hSRS)->SetAxisMappingStrategy(strategy);
   12591             : }
   12592             : 
   12593             : /************************************************************************/
   12594             : /*                      GetDataAxisToSRSAxisMapping()                   */
   12595             : /************************************************************************/
   12596             : 
   12597             : /** \brief Return the data axis to SRS axis mapping.
   12598             :  *
   12599             :  * The number of elements of the vector will be the number of axis of the CRS.
   12600             :  * Values start at 1.
   12601             :  *
   12602             :  * If m = GetDataAxisToSRSAxisMapping(), then m[0] is the data axis number
   12603             :  * for the first axis of the CRS.
   12604             :  *
   12605             :  * @since GDAL 3.0
   12606             :  */
   12607     3687850 : const std::vector<int> &OGRSpatialReference::GetDataAxisToSRSAxisMapping() const
   12608             : {
   12609     3687850 :     TAKE_OPTIONAL_LOCK();
   12610             : 
   12611     7375650 :     return d->m_axisMapping;
   12612             : }
   12613             : 
   12614             : /************************************************************************/
   12615             : /*                     OSRGetDataAxisToSRSAxisMapping()                 */
   12616             : /************************************************************************/
   12617             : 
   12618             : /** \brief Return the data axis to SRS axis mapping.
   12619             :  *
   12620             :  * See OGRSpatialReference::GetDataAxisToSRSAxisMapping()
   12621             :  *
   12622             :  * @since GDAL 3.0
   12623             :  */
   12624         200 : const int *OSRGetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12625             :                                           int *pnCount)
   12626             : {
   12627         200 :     VALIDATE_POINTER1(hSRS, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12628         200 :     VALIDATE_POINTER1(pnCount, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12629             : 
   12630             :     const auto &v =
   12631         200 :         OGRSpatialReference::FromHandle(hSRS)->GetDataAxisToSRSAxisMapping();
   12632         200 :     *pnCount = static_cast<int>(v.size());
   12633         200 :     return v.data();
   12634             : }
   12635             : 
   12636             : /************************************************************************/
   12637             : /*                      SetDataAxisToSRSAxisMapping()                   */
   12638             : /************************************************************************/
   12639             : 
   12640             : /** \brief Set a custom data axis to CRS axis mapping.
   12641             :  *
   12642             :  * The number of elements of the mapping vector should be the number of axis
   12643             :  * of the CRS (as returned by GetAxesCount()) (although this method does not
   12644             :  * check that, beyond checking there are at least 2 elements, so that this
   12645             :  * method and setting the CRS can be done in any order).
   12646             :  * This is taken into account by OGRCoordinateTransformation to transform the
   12647             :  * order of coordinates to the order expected by the CRS before
   12648             :  * transformation, and back to the data order after transformation.
   12649             :  *
   12650             :  * The mapping[i] value (one based) represents the data axis number for the i(th)
   12651             :  * axis of the CRS. A negative value can also be used to ask for a sign
   12652             :  * reversal during coordinate transformation (to deal with northing vs southing,
   12653             :  * easting vs westing, heights vs depths).
   12654             :  *
   12655             :  * When used with OGRCoordinateTransformation,
   12656             :  * - the only valid values for mapping[0] (data axis number for the first axis
   12657             :  *   of the CRS) are 1, 2, -1, -2.
   12658             :  * - the only valid values for mapping[1] (data axis number for the second axis
   12659             :  *   of the CRS) are 1, 2, -1, -2.
   12660             :  *  - the only valid values mapping[2] are 3 or -3.
   12661             :  * Note: this method does not validate the values of mapping[].
   12662             :  *
   12663             :  * mapping=[2,1] typically expresses the inversion of axis between the data
   12664             :  * axis and the CRS axis for a 2D CRS.
   12665             :  *
   12666             :  * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
   12667             :  *
   12668             :  * This is the same as the C function OSRSetDataAxisToSRSAxisMapping().
   12669             :  *
   12670             :  * @param mapping The new data axis to CRS axis mapping.
   12671             :  *
   12672             :  * @since GDAL 3.0
   12673             :  * @see OGRSpatialReference::GetDataAxisToSRSAxisMapping()
   12674             :  */
   12675        6605 : OGRErr OGRSpatialReference::SetDataAxisToSRSAxisMapping(
   12676             :     const std::vector<int> &mapping)
   12677             : {
   12678       13210 :     TAKE_OPTIONAL_LOCK();
   12679             : 
   12680        6605 :     if (mapping.size() < 2)
   12681           0 :         return OGRERR_FAILURE;
   12682        6605 :     d->m_axisMappingStrategy = OAMS_CUSTOM;
   12683        6605 :     d->m_axisMapping = mapping;
   12684        6605 :     return OGRERR_NONE;
   12685             : }
   12686             : 
   12687             : /************************************************************************/
   12688             : /*                     OSRSetDataAxisToSRSAxisMapping()                 */
   12689             : /************************************************************************/
   12690             : 
   12691             : /** \brief Set a custom data axis to CRS axis mapping.
   12692             :  *
   12693             :  * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
   12694             :  *
   12695             :  * This is the same as the C++ method
   12696             :  * OGRSpatialReference::SetDataAxisToSRSAxisMapping()
   12697             :  *
   12698             :  * @since GDAL 3.1
   12699             :  */
   12700          15 : OGRErr OSRSetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12701             :                                       int nMappingSize, const int *panMapping)
   12702             : {
   12703          15 :     VALIDATE_POINTER1(hSRS, "OSRSetDataAxisToSRSAxisMapping", OGRERR_FAILURE);
   12704          15 :     VALIDATE_POINTER1(panMapping, "OSRSetDataAxisToSRSAxisMapping",
   12705             :                       OGRERR_FAILURE);
   12706             : 
   12707          15 :     if (nMappingSize < 0)
   12708           0 :         return OGRERR_FAILURE;
   12709             : 
   12710          30 :     std::vector<int> mapping(nMappingSize);
   12711          15 :     if (nMappingSize)
   12712          15 :         memcpy(&mapping[0], panMapping, nMappingSize * sizeof(int));
   12713          15 :     return OGRSpatialReference::FromHandle(hSRS)->SetDataAxisToSRSAxisMapping(
   12714          15 :         mapping);
   12715             : }
   12716             : 
   12717             : /************************************************************************/
   12718             : /*                               GetAreaOfUse()                         */
   12719             : /************************************************************************/
   12720             : 
   12721             : /** \brief Return the area of use of the CRS.
   12722             :  *
   12723             :  * This method is the same as the OSRGetAreaOfUse() function.
   12724             :  *
   12725             :  * @param pdfWestLongitudeDeg Pointer to a double to receive the western-most
   12726             :  * longitude, expressed in degree. Might be NULL. If the returned value is
   12727             :  * -1000, the bounding box is unknown.
   12728             :  * @param pdfSouthLatitudeDeg Pointer to a double to receive the southern-most
   12729             :  * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
   12730             :  * the bounding box is unknown.
   12731             :  * @param pdfEastLongitudeDeg Pointer to a double to receive the eastern-most
   12732             :  * longitude, expressed in degree. Might be NULL. If the returned value is
   12733             :  * -1000, the bounding box is unknown.
   12734             :  * @param pdfNorthLatitudeDeg Pointer to a double to receive the northern-most
   12735             :  * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
   12736             :  * the bounding box is unknown.
   12737             :  * @param ppszAreaName Pointer to a string to receive the name of the area of
   12738             :  * use. Might be NULL. Note that *ppszAreaName is short-lived and might be
   12739             :  * invalidated by further calls.
   12740             :  * @return true in case of success
   12741             :  * @since GDAL 3.0
   12742             :  */
   12743          32 : bool OGRSpatialReference::GetAreaOfUse(double *pdfWestLongitudeDeg,
   12744             :                                        double *pdfSouthLatitudeDeg,
   12745             :                                        double *pdfEastLongitudeDeg,
   12746             :                                        double *pdfNorthLatitudeDeg,
   12747             :                                        const char **ppszAreaName) const
   12748             : {
   12749          64 :     TAKE_OPTIONAL_LOCK();
   12750             : 
   12751          32 :     d->refreshProjObj();
   12752          32 :     if (!d->m_pj_crs)
   12753             :     {
   12754           0 :         return false;
   12755             :     }
   12756          32 :     d->demoteFromBoundCRS();
   12757          32 :     const char *pszAreaName = nullptr;
   12758          32 :     int bSuccess = proj_get_area_of_use(
   12759          32 :         d->getPROJContext(), d->m_pj_crs, pdfWestLongitudeDeg,
   12760             :         pdfSouthLatitudeDeg, pdfEastLongitudeDeg, pdfNorthLatitudeDeg,
   12761             :         &pszAreaName);
   12762          32 :     d->undoDemoteFromBoundCRS();
   12763          32 :     d->m_osAreaName = pszAreaName ? pszAreaName : "";
   12764          32 :     if (ppszAreaName)
   12765           1 :         *ppszAreaName = d->m_osAreaName.c_str();
   12766          32 :     return CPL_TO_BOOL(bSuccess);
   12767             : }
   12768             : 
   12769             : /************************************************************************/
   12770             : /*                               GetAreaOfUse()                         */
   12771             : /************************************************************************/
   12772             : 
   12773             : /** \brief Return the area of use of the CRS.
   12774             :  *
   12775             :  * This function is the same as the OGRSpatialReference::GetAreaOfUse() method.
   12776             :  *
   12777             :  * @since GDAL 3.0
   12778             :  */
   12779           1 : int OSRGetAreaOfUse(OGRSpatialReferenceH hSRS, double *pdfWestLongitudeDeg,
   12780             :                     double *pdfSouthLatitudeDeg, double *pdfEastLongitudeDeg,
   12781             :                     double *pdfNorthLatitudeDeg, const char **ppszAreaName)
   12782             : {
   12783           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAreaOfUse", FALSE);
   12784             : 
   12785           1 :     return OGRSpatialReference::FromHandle(hSRS)->GetAreaOfUse(
   12786             :         pdfWestLongitudeDeg, pdfSouthLatitudeDeg, pdfEastLongitudeDeg,
   12787           1 :         pdfNorthLatitudeDeg, ppszAreaName);
   12788             : }
   12789             : 
   12790             : /************************************************************************/
   12791             : /*                     OSRGetCRSInfoListFromDatabase()                  */
   12792             : /************************************************************************/
   12793             : 
   12794             : /** \brief Enumerate CRS objects from the database.
   12795             :  *
   12796             :  * The returned object is an array of OSRCRSInfo* pointers, whose last
   12797             :  * entry is NULL. This array should be freed with OSRDestroyCRSInfoList()
   12798             :  *
   12799             :  * @param pszAuthName Authority name, used to restrict the search.
   12800             :  * Or NULL for all authorities.
   12801             :  * @param params Additional criteria. Must be set to NULL for now.
   12802             :  * @param pnOutResultCount Output parameter pointing to an integer to receive
   12803             :  * the size of the result list. Might be NULL
   12804             :  * @return an array of OSRCRSInfo* pointers to be freed with
   12805             :  * OSRDestroyCRSInfoList(), or NULL in case of error.
   12806             :  *
   12807             :  * @since GDAL 3.0
   12808             :  */
   12809             : OSRCRSInfo **
   12810          12 : OSRGetCRSInfoListFromDatabase(const char *pszAuthName,
   12811             :                               CPL_UNUSED const OSRCRSListParameters *params,
   12812             :                               int *pnOutResultCount)
   12813             : {
   12814          12 :     int nResultCount = 0;
   12815          12 :     auto projList = proj_get_crs_info_list_from_database(
   12816             :         OSRGetProjTLSContext(), pszAuthName, nullptr, &nResultCount);
   12817          12 :     if (pnOutResultCount)
   12818          12 :         *pnOutResultCount = nResultCount;
   12819          12 :     if (!projList)
   12820             :     {
   12821           0 :         return nullptr;
   12822             :     }
   12823          12 :     auto res = new OSRCRSInfo *[nResultCount + 1];
   12824       56167 :     for (int i = 0; i < nResultCount; i++)
   12825             :     {
   12826       56155 :         res[i] = new OSRCRSInfo;
   12827      112310 :         res[i]->pszAuthName = projList[i]->auth_name
   12828       56155 :                                   ? CPLStrdup(projList[i]->auth_name)
   12829             :                                   : nullptr;
   12830       56155 :         res[i]->pszCode =
   12831       56155 :             projList[i]->code ? CPLStrdup(projList[i]->code) : nullptr;
   12832       56155 :         res[i]->pszName =
   12833       56155 :             projList[i]->name ? CPLStrdup(projList[i]->name) : nullptr;
   12834       56155 :         res[i]->eType = OSR_CRS_TYPE_OTHER;
   12835       56155 :         switch (projList[i]->type)
   12836             :         {
   12837        5273 :             case PJ_TYPE_GEOGRAPHIC_2D_CRS:
   12838        5273 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_2D;
   12839        5273 :                 break;
   12840        1821 :             case PJ_TYPE_GEOGRAPHIC_3D_CRS:
   12841        1821 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_3D;
   12842        1821 :                 break;
   12843        1910 :             case PJ_TYPE_GEOCENTRIC_CRS:
   12844        1910 :                 res[i]->eType = OSR_CRS_TYPE_GEOCENTRIC;
   12845        1910 :                 break;
   12846       42888 :             case PJ_TYPE_PROJECTED_CRS:
   12847       42888 :                 res[i]->eType = OSR_CRS_TYPE_PROJECTED;
   12848       42888 :                 break;
   12849        1820 :             case PJ_TYPE_VERTICAL_CRS:
   12850        1820 :                 res[i]->eType = OSR_CRS_TYPE_VERTICAL;
   12851        1820 :                 break;
   12852        2443 :             case PJ_TYPE_COMPOUND_CRS:
   12853        2443 :                 res[i]->eType = OSR_CRS_TYPE_COMPOUND;
   12854        2443 :                 break;
   12855           0 :             default:
   12856           0 :                 break;
   12857             :         }
   12858       56155 :         res[i]->bDeprecated = projList[i]->deprecated;
   12859       56155 :         res[i]->bBboxValid = projList[i]->bbox_valid;
   12860       56155 :         res[i]->dfWestLongitudeDeg = projList[i]->west_lon_degree;
   12861       56155 :         res[i]->dfSouthLatitudeDeg = projList[i]->south_lat_degree;
   12862       56155 :         res[i]->dfEastLongitudeDeg = projList[i]->east_lon_degree;
   12863       56155 :         res[i]->dfNorthLatitudeDeg = projList[i]->north_lat_degree;
   12864      112310 :         res[i]->pszAreaName = projList[i]->area_name
   12865       56155 :                                   ? CPLStrdup(projList[i]->area_name)
   12866             :                                   : nullptr;
   12867       56155 :         res[i]->pszProjectionMethod =
   12868       56155 :             projList[i]->projection_method_name
   12869       56155 :                 ? CPLStrdup(projList[i]->projection_method_name)
   12870             :                 : nullptr;
   12871             :     }
   12872          12 :     res[nResultCount] = nullptr;
   12873          12 :     proj_crs_info_list_destroy(projList);
   12874          12 :     return res;
   12875             : }
   12876             : 
   12877             : /************************************************************************/
   12878             : /*                        OSRDestroyCRSInfoList()                       */
   12879             : /************************************************************************/
   12880             : 
   12881             : /** \brief Destroy the result returned by
   12882             :  * OSRGetCRSInfoListFromDatabase().
   12883             :  *
   12884             :  * @since GDAL 3.0
   12885             :  */
   12886          12 : void OSRDestroyCRSInfoList(OSRCRSInfo **list)
   12887             : {
   12888          12 :     if (list)
   12889             :     {
   12890       56167 :         for (int i = 0; list[i] != nullptr; i++)
   12891             :         {
   12892       56155 :             CPLFree(list[i]->pszAuthName);
   12893       56155 :             CPLFree(list[i]->pszCode);
   12894       56155 :             CPLFree(list[i]->pszName);
   12895       56155 :             CPLFree(list[i]->pszAreaName);
   12896       56155 :             CPLFree(list[i]->pszProjectionMethod);
   12897       56155 :             delete list[i];
   12898             :         }
   12899          12 :         delete[] list;
   12900             :     }
   12901          12 : }
   12902             : 
   12903             : /************************************************************************/
   12904             : /*                   OSRGetAuthorityListFromDatabase()                  */
   12905             : /************************************************************************/
   12906             : 
   12907             : /** \brief Return the list of CRS authorities used in the PROJ database.
   12908             :  *
   12909             :  * Such as "EPSG", "ESRI", "PROJ", "IGNF", "IAU_2015", etc.
   12910             :  *
   12911             :  * This is a direct mapping of https://proj.org/en/latest/development/reference/functions.html#c.proj_get_authorities_from_database
   12912             :  *
   12913             :  * @return nullptr in case of error, or a NULL terminated list of strings to
   12914             :  * free with CSLDestroy()
   12915             :  * @since GDAL 3.10
   12916             :  */
   12917           2 : char **OSRGetAuthorityListFromDatabase()
   12918             : {
   12919             :     PROJ_STRING_LIST list =
   12920           2 :         proj_get_authorities_from_database(OSRGetProjTLSContext());
   12921           2 :     if (!list)
   12922             :     {
   12923           0 :         return nullptr;
   12924             :     }
   12925           2 :     int count = 0;
   12926          12 :     while (list[count])
   12927          10 :         ++count;
   12928           2 :     char **res = static_cast<char **>(CPLCalloc(count + 1, sizeof(char *)));
   12929          12 :     for (int i = 0; i < count; ++i)
   12930          10 :         res[i] = CPLStrdup(list[i]);
   12931           2 :     proj_string_list_destroy(list);
   12932           2 :     return res;
   12933             : }
   12934             : 
   12935             : /************************************************************************/
   12936             : /*                    UpdateCoordinateSystemFromGeogCRS()               */
   12937             : /************************************************************************/
   12938             : 
   12939             : /*! @cond Doxygen_Suppress */
   12940             : /** \brief Used by gt_wkt_srs.cpp to create projected 3D CRS. Internal use only
   12941             :  *
   12942             :  * @since GDAL 3.1
   12943             :  */
   12944           1 : void OGRSpatialReference::UpdateCoordinateSystemFromGeogCRS()
   12945             : {
   12946           1 :     TAKE_OPTIONAL_LOCK();
   12947             : 
   12948           1 :     d->refreshProjObj();
   12949           1 :     if (!d->m_pj_crs)
   12950           0 :         return;
   12951           1 :     if (d->m_pjType != PJ_TYPE_PROJECTED_CRS)
   12952           0 :         return;
   12953           1 :     if (GetAxesCount() == 3)
   12954           0 :         return;
   12955           1 :     auto ctxt = d->getPROJContext();
   12956           1 :     auto baseCRS = proj_crs_get_geodetic_crs(ctxt, d->m_pj_crs);
   12957           1 :     if (!baseCRS)
   12958           0 :         return;
   12959           1 :     auto baseCRSCS = proj_crs_get_coordinate_system(ctxt, baseCRS);
   12960           1 :     if (!baseCRSCS)
   12961             :     {
   12962           0 :         proj_destroy(baseCRS);
   12963           0 :         return;
   12964             :     }
   12965           1 :     if (proj_cs_get_axis_count(ctxt, baseCRSCS) != 3)
   12966             :     {
   12967           0 :         proj_destroy(baseCRSCS);
   12968           0 :         proj_destroy(baseCRS);
   12969           0 :         return;
   12970             :     }
   12971           1 :     auto projCS = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   12972           1 :     if (!projCS || proj_cs_get_axis_count(ctxt, projCS) != 2)
   12973             :     {
   12974           0 :         proj_destroy(baseCRSCS);
   12975           0 :         proj_destroy(baseCRS);
   12976           0 :         proj_destroy(projCS);
   12977           0 :         return;
   12978             :     }
   12979             : 
   12980             :     PJ_AXIS_DESCRIPTION axis[3];
   12981           4 :     for (int i = 0; i < 3; i++)
   12982             :     {
   12983           3 :         const char *name = nullptr;
   12984           3 :         const char *abbreviation = nullptr;
   12985           3 :         const char *direction = nullptr;
   12986           3 :         double unit_conv_factor = 0;
   12987           3 :         const char *unit_name = nullptr;
   12988           3 :         proj_cs_get_axis_info(ctxt, i < 2 ? projCS : baseCRSCS, i, &name,
   12989             :                               &abbreviation, &direction, &unit_conv_factor,
   12990             :                               &unit_name, nullptr, nullptr);
   12991           3 :         axis[i].name = CPLStrdup(name);
   12992           3 :         axis[i].abbreviation = CPLStrdup(abbreviation);
   12993           3 :         axis[i].direction = CPLStrdup(direction);
   12994           3 :         axis[i].unit_name = CPLStrdup(unit_name);
   12995           3 :         axis[i].unit_conv_factor = unit_conv_factor;
   12996           3 :         axis[i].unit_type = PJ_UT_LINEAR;
   12997             :     }
   12998           1 :     proj_destroy(baseCRSCS);
   12999           1 :     proj_destroy(projCS);
   13000           1 :     auto cs = proj_create_cs(ctxt, PJ_CS_TYPE_CARTESIAN, 3, axis);
   13001           4 :     for (int i = 0; i < 3; i++)
   13002             :     {
   13003           3 :         CPLFree(axis[i].name);
   13004           3 :         CPLFree(axis[i].abbreviation);
   13005           3 :         CPLFree(axis[i].direction);
   13006           3 :         CPLFree(axis[i].unit_name);
   13007             :     }
   13008           1 :     if (!cs)
   13009             :     {
   13010           0 :         proj_destroy(baseCRS);
   13011           0 :         return;
   13012             :     }
   13013           1 :     auto conversion = proj_crs_get_coordoperation(ctxt, d->m_pj_crs);
   13014           1 :     auto crs = proj_create_projected_crs(ctxt, d->getProjCRSName(), baseCRS,
   13015             :                                          conversion, cs);
   13016           1 :     proj_destroy(baseCRS);
   13017           1 :     proj_destroy(conversion);
   13018           1 :     proj_destroy(cs);
   13019           1 :     d->setPjCRS(crs);
   13020             : }
   13021             : 
   13022             : /*! @endcond */
   13023             : 
   13024             : /************************************************************************/
   13025             : /*                             PromoteTo3D()                            */
   13026             : /************************************************************************/
   13027             : 
   13028             : /** \brief "Promotes" a 2D CRS to a 3D CRS one.
   13029             :  *
   13030             :  * The new axis will be ellipsoidal height, oriented upwards, and with metre
   13031             :  * units.
   13032             :  *
   13033             :  * @param pszName New name for the CRS. If set to NULL, the previous name will
   13034             :  * be used.
   13035             :  * @return OGRERR_NONE if no error occurred.
   13036             :  * @since GDAL 3.1 and PROJ 6.3
   13037             :  */
   13038          42 : OGRErr OGRSpatialReference::PromoteTo3D(const char *pszName)
   13039             : {
   13040          84 :     TAKE_OPTIONAL_LOCK();
   13041             : 
   13042          42 :     d->refreshProjObj();
   13043          42 :     if (!d->m_pj_crs)
   13044           0 :         return OGRERR_FAILURE;
   13045             :     auto newPj =
   13046          42 :         proj_crs_promote_to_3D(d->getPROJContext(), pszName, d->m_pj_crs);
   13047          42 :     if (!newPj)
   13048           0 :         return OGRERR_FAILURE;
   13049          42 :     d->setPjCRS(newPj);
   13050          42 :     return OGRERR_NONE;
   13051             : }
   13052             : 
   13053             : /************************************************************************/
   13054             : /*                             OSRPromoteTo3D()                         */
   13055             : /************************************************************************/
   13056             : 
   13057             : /** \brief "Promotes" a 2D CRS to a 3D CRS one.
   13058             :  *
   13059             :  * See OGRSpatialReference::PromoteTo3D()
   13060             :  *
   13061             :  * @since GDAL 3.1 and PROJ 6.3
   13062             :  */
   13063           3 : OGRErr OSRPromoteTo3D(OGRSpatialReferenceH hSRS, const char *pszName)
   13064             : {
   13065           3 :     VALIDATE_POINTER1(hSRS, "OSRPromoteTo3D", OGRERR_FAILURE);
   13066             : 
   13067           3 :     return OGRSpatialReference::FromHandle(hSRS)->PromoteTo3D(pszName);
   13068             : }
   13069             : 
   13070             : /************************************************************************/
   13071             : /*                             DemoteTo2D()                             */
   13072             : /************************************************************************/
   13073             : 
   13074             : /** \brief "Demote" a 3D CRS to a 2D CRS one.
   13075             :  *
   13076             :  * @param pszName New name for the CRS. If set to NULL, the previous name will
   13077             :  * be used.
   13078             :  * @return OGRERR_NONE if no error occurred.
   13079             :  * @since GDAL 3.2 and PROJ 6.3
   13080             :  */
   13081          45 : OGRErr OGRSpatialReference::DemoteTo2D(const char *pszName)
   13082             : {
   13083          90 :     TAKE_OPTIONAL_LOCK();
   13084             : 
   13085          45 :     d->refreshProjObj();
   13086          45 :     if (!d->m_pj_crs)
   13087           0 :         return OGRERR_FAILURE;
   13088             :     auto newPj =
   13089          45 :         proj_crs_demote_to_2D(d->getPROJContext(), pszName, d->m_pj_crs);
   13090          45 :     if (!newPj)
   13091           0 :         return OGRERR_FAILURE;
   13092          45 :     d->setPjCRS(newPj);
   13093          45 :     return OGRERR_NONE;
   13094             : }
   13095             : 
   13096             : /************************************************************************/
   13097             : /*                             OSRDemoteTo2D()                          */
   13098             : /************************************************************************/
   13099             : 
   13100             : /** \brief "Demote" a 3D CRS to a 2D CRS one.
   13101             :  *
   13102             :  * See OGRSpatialReference::DemoteTo2D()
   13103             :  *
   13104             :  * @since GDAL 3.2 and PROJ 6.3
   13105             :  */
   13106           1 : OGRErr OSRDemoteTo2D(OGRSpatialReferenceH hSRS, const char *pszName)
   13107             : {
   13108           1 :     VALIDATE_POINTER1(hSRS, "OSRDemoteTo2D", OGRERR_FAILURE);
   13109             : 
   13110           1 :     return OGRSpatialReference::FromHandle(hSRS)->DemoteTo2D(pszName);
   13111             : }
   13112             : 
   13113             : /************************************************************************/
   13114             : /*                           GetEPSGGeogCS()                            */
   13115             : /************************************************************************/
   13116             : 
   13117             : /** Try to establish what the EPSG code for this coordinate systems
   13118             :  * GEOGCS might be.  Returns -1 if no reasonable guess can be made.
   13119             :  *
   13120             :  * @return EPSG code
   13121             :  */
   13122             : 
   13123         335 : int OGRSpatialReference::GetEPSGGeogCS() const
   13124             : 
   13125             : {
   13126         670 :     TAKE_OPTIONAL_LOCK();
   13127             : 
   13128             :     /* -------------------------------------------------------------------- */
   13129             :     /*      Check axis order.                                               */
   13130             :     /* -------------------------------------------------------------------- */
   13131         670 :     auto poGeogCRS = std::unique_ptr<OGRSpatialReference>(CloneGeogCS());
   13132         335 :     if (!poGeogCRS)
   13133           0 :         return -1;
   13134             : 
   13135         335 :     bool ret = false;
   13136         335 :     poGeogCRS->d->demoteFromBoundCRS();
   13137         335 :     auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   13138         335 :                                              poGeogCRS->d->m_pj_crs);
   13139         335 :     poGeogCRS->d->undoDemoteFromBoundCRS();
   13140         335 :     if (cs)
   13141             :     {
   13142         335 :         const char *pszDirection = nullptr;
   13143         335 :         if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr, nullptr,
   13144             :                                   &pszDirection, nullptr, nullptr, nullptr,
   13145         335 :                                   nullptr))
   13146             :         {
   13147         335 :             if (EQUAL(pszDirection, "north"))
   13148             :             {
   13149         136 :                 ret = true;
   13150             :             }
   13151             :         }
   13152             : 
   13153         335 :         proj_destroy(cs);
   13154             :     }
   13155         335 :     if (!ret)
   13156         199 :         return -1;
   13157             : 
   13158             :     /* -------------------------------------------------------------------- */
   13159             :     /*      Do we already have it?                                          */
   13160             :     /* -------------------------------------------------------------------- */
   13161         136 :     const char *pszAuthName = GetAuthorityName("GEOGCS");
   13162         136 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg"))
   13163          61 :         return atoi(GetAuthorityCode("GEOGCS"));
   13164             : 
   13165             :     /* -------------------------------------------------------------------- */
   13166             :     /*      Get the datum and geogcs names.                                 */
   13167             :     /* -------------------------------------------------------------------- */
   13168             : 
   13169          75 :     const char *pszGEOGCS = GetAttrValue("GEOGCS");
   13170          75 :     const char *pszDatum = GetAttrValue("DATUM");
   13171             : 
   13172             :     // We can only operate on coordinate systems with a geogcs.
   13173         150 :     OGRSpatialReference oSRSTmp;
   13174          75 :     if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13175             :     {
   13176             :         // Calling GetAttrValue("GEOGCS") will fail on a CRS that can't be
   13177             :         // export to WKT1, so try to extract the geographic CRS through PROJ
   13178             :         // API with CopyGeogCSFrom() and get the nodes' values from it.
   13179           1 :         oSRSTmp.CopyGeogCSFrom(this);
   13180           1 :         pszGEOGCS = oSRSTmp.GetAttrValue("GEOGCS");
   13181           1 :         pszDatum = oSRSTmp.GetAttrValue("DATUM");
   13182           1 :         if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13183             :         {
   13184           0 :             return -1;
   13185             :         }
   13186             :     }
   13187             : 
   13188             :     // Lookup geographic CRS name
   13189          75 :     const PJ_TYPE type = PJ_TYPE_GEOGRAPHIC_2D_CRS;
   13190          75 :     PJ_OBJ_LIST *list = proj_create_from_name(
   13191             :         d->getPROJContext(), nullptr, pszGEOGCS, &type, 1, false, 1, nullptr);
   13192          75 :     if (list)
   13193             :     {
   13194          75 :         const auto listSize = proj_list_get_count(list);
   13195          75 :         if (listSize == 1)
   13196             :         {
   13197          49 :             auto crs = proj_list_get(d->getPROJContext(), list, 0);
   13198          49 :             if (crs)
   13199             :             {
   13200          49 :                 pszAuthName = proj_get_id_auth_name(crs, 0);
   13201          49 :                 const char *pszCode = proj_get_id_code(crs, 0);
   13202          49 :                 if (pszAuthName && pszCode && EQUAL(pszAuthName, "EPSG"))
   13203             :                 {
   13204          47 :                     const int nCode = atoi(pszCode);
   13205          47 :                     proj_destroy(crs);
   13206          47 :                     proj_list_destroy(list);
   13207          47 :                     return nCode;
   13208             :                 }
   13209           2 :                 proj_destroy(crs);
   13210             :             }
   13211             :         }
   13212          28 :         proj_list_destroy(list);
   13213             :     }
   13214             : 
   13215             :     /* -------------------------------------------------------------------- */
   13216             :     /*      Is this a "well known" geographic coordinate system?            */
   13217             :     /* -------------------------------------------------------------------- */
   13218          84 :     const bool bWGS = strstr(pszGEOGCS, "WGS") != nullptr ||
   13219          28 :                       strstr(pszDatum, "WGS") ||
   13220          28 :                       strstr(pszGEOGCS, "World Geodetic System") ||
   13221          28 :                       strstr(pszGEOGCS, "World_Geodetic_System") ||
   13222          84 :                       strstr(pszDatum, "World Geodetic System") ||
   13223          28 :                       strstr(pszDatum, "World_Geodetic_System");
   13224             : 
   13225          84 :     const bool bNAD = strstr(pszGEOGCS, "NAD") != nullptr ||
   13226          28 :                       strstr(pszDatum, "NAD") ||
   13227          28 :                       strstr(pszGEOGCS, "North American") ||
   13228          28 :                       strstr(pszGEOGCS, "North_American") ||
   13229          84 :                       strstr(pszDatum, "North American") ||
   13230          28 :                       strstr(pszDatum, "North_American");
   13231             : 
   13232          28 :     if (bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")))
   13233           0 :         return 4326;
   13234             : 
   13235          28 :     if (bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")))
   13236           0 :         return 4322;
   13237             : 
   13238             :     // This is questionable as there are several 'flavors' of NAD83 that
   13239             :     // are not the same as 4269
   13240          28 :     if (bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")))
   13241           0 :         return 4269;
   13242             : 
   13243          28 :     if (bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")))
   13244           0 :         return 4267;
   13245             : 
   13246             :     /* -------------------------------------------------------------------- */
   13247             :     /*      If we know the datum, associate the most likely GCS with        */
   13248             :     /*      it.                                                             */
   13249             :     /* -------------------------------------------------------------------- */
   13250          28 :     const OGRSpatialReference &oActiveObj = oSRSTmp.IsEmpty() ? *this : oSRSTmp;
   13251          28 :     pszAuthName = oActiveObj.GetAuthorityName("GEOGCS|DATUM");
   13252          28 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") &&
   13253           0 :         GetPrimeMeridian() == 0.0)
   13254             :     {
   13255           0 :         const int nDatum = atoi(oActiveObj.GetAuthorityCode("GEOGCS|DATUM"));
   13256             : 
   13257           0 :         if (nDatum >= 6000 && nDatum <= 6999)
   13258           0 :             return nDatum - 2000;
   13259             :     }
   13260             : 
   13261          28 :     return -1;
   13262             : }
   13263             : 
   13264             : /************************************************************************/
   13265             : /*                          SetCoordinateEpoch()                        */
   13266             : /************************************************************************/
   13267             : 
   13268             : /** Set the coordinate epoch, as decimal year.
   13269             :  *
   13270             :  * In a dynamic CRS, coordinates of a point on the surface of the Earth may
   13271             :  * change with time. To be unambiguous the coordinates must always be qualified
   13272             :  * with the epoch at which they are valid. The coordinate epoch is not
   13273             :  * necessarily the epoch at which the observation was collected.
   13274             :  *
   13275             :  * Pedantically the coordinate epoch of an observation belongs to the
   13276             :  * observation, and not to the CRS, however it is often more practical to
   13277             :  * bind it to the CRS. The coordinate epoch should be specified for dynamic
   13278             :  * CRS (see IsDynamic())
   13279             :  *
   13280             :  * This method is the same as the OSRSetCoordinateEpoch() function.
   13281             :  *
   13282             :  * @param dfCoordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3)
   13283             :  * @since OGR 3.4
   13284             :  */
   13285             : 
   13286         802 : void OGRSpatialReference::SetCoordinateEpoch(double dfCoordinateEpoch)
   13287             : {
   13288         802 :     d->m_coordinateEpoch = dfCoordinateEpoch;
   13289         802 : }
   13290             : 
   13291             : /************************************************************************/
   13292             : /*                      OSRSetCoordinateEpoch()                         */
   13293             : /************************************************************************/
   13294             : 
   13295             : /** \brief Set the coordinate epoch, as decimal year.
   13296             :  *
   13297             :  * See OGRSpatialReference::SetCoordinateEpoch()
   13298             :  *
   13299             :  * @since OGR 3.4
   13300             :  */
   13301          31 : void OSRSetCoordinateEpoch(OGRSpatialReferenceH hSRS, double dfCoordinateEpoch)
   13302             : {
   13303          31 :     VALIDATE_POINTER0(hSRS, "OSRSetCoordinateEpoch");
   13304             : 
   13305          31 :     return OGRSpatialReference::FromHandle(hSRS)->SetCoordinateEpoch(
   13306          31 :         dfCoordinateEpoch);
   13307             : }
   13308             : 
   13309             : /************************************************************************/
   13310             : /*                          GetCoordinateEpoch()                        */
   13311             : /************************************************************************/
   13312             : 
   13313             : /** Return the coordinate epoch, as decimal year.
   13314             :  *
   13315             :  * In a dynamic CRS, coordinates of a point on the surface of the Earth may
   13316             :  * change with time. To be unambiguous the coordinates must always be qualified
   13317             :  * with the epoch at which they are valid. The coordinate epoch is not
   13318             :  * necessarily the epoch at which the observation was collected.
   13319             :  *
   13320             :  * Pedantically the coordinate epoch of an observation belongs to the
   13321             :  * observation, and not to the CRS, however it is often more practical to
   13322             :  * bind it to the CRS. The coordinate epoch should be specified for dynamic
   13323             :  * CRS (see IsDynamic())
   13324             :  *
   13325             :  * This method is the same as the OSRGetCoordinateEpoch() function.
   13326             :  *
   13327             :  * @return coordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3), or 0
   13328             :  *                         if not set, or relevant.
   13329             :  * @since OGR 3.4
   13330             :  */
   13331             : 
   13332       10620 : double OGRSpatialReference::GetCoordinateEpoch() const
   13333             : {
   13334       10620 :     return d->m_coordinateEpoch;
   13335             : }
   13336             : 
   13337             : /************************************************************************/
   13338             : /*                      OSRGetCoordinateEpoch()                        */
   13339             : /************************************************************************/
   13340             : 
   13341             : /** \brief Get the coordinate epoch, as decimal year.
   13342             :  *
   13343             :  * See OGRSpatialReference::GetCoordinateEpoch()
   13344             :  *
   13345             :  * @since OGR 3.4
   13346             :  */
   13347         622 : double OSRGetCoordinateEpoch(OGRSpatialReferenceH hSRS)
   13348             : {
   13349         622 :     VALIDATE_POINTER1(hSRS, "OSRGetCoordinateEpoch", 0);
   13350             : 
   13351         622 :     return OGRSpatialReference::FromHandle(hSRS)->GetCoordinateEpoch();
   13352             : }

Generated by: LCOV version 1.14