LCOV - code coverage report
Current view: top level - ogr - ogrspatialreference.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 3477 4268 81.5 %
Date: 2026-07-09 08:35:43 Functions: 300 377 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             : bool GDALThreadLocalDatasetCacheIsInDestruction();
      49             : 
      50             : // Exists since 8.0.1
      51             : #ifndef PROJ_AT_LEAST_VERSION
      52             : #define PROJ_COMPUTE_VERSION(maj, min, patch)                                  \
      53             :     ((maj) * 10000 + (min) * 100 + (patch))
      54             : #define PROJ_VERSION_NUMBER                                                    \
      55             :     PROJ_COMPUTE_VERSION(PROJ_VERSION_MAJOR, PROJ_VERSION_MINOR,               \
      56             :                          PROJ_VERSION_PATCH)
      57             : #define PROJ_AT_LEAST_VERSION(maj, min, patch)                                 \
      58             :     (PROJ_VERSION_NUMBER >= PROJ_COMPUTE_VERSION(maj, min, patch))
      59             : #endif
      60             : 
      61             : #define STRINGIFY(s) #s
      62             : #define XSTRINGIFY(s) STRINGIFY(s)
      63             : 
      64             : struct OGRSpatialReference::Private
      65             : {
      66             :     struct Listener final : public OGR_SRSNode::Listener
      67             :     {
      68             :         OGRSpatialReference::Private *m_poObj = nullptr;
      69             : 
      70      282385 :         explicit Listener(OGRSpatialReference::Private *poObj) : m_poObj(poObj)
      71             :         {
      72      282385 :         }
      73             : 
      74             :         Listener(const Listener &) = delete;
      75             :         Listener &operator=(const Listener &) = delete;
      76             : 
      77             :         void notifyChange(OGR_SRSNode *) override;
      78             :     };
      79             : 
      80             :     OGRSpatialReference *m_poSelf = nullptr;
      81             :     PJ *m_pj_crs = nullptr;
      82             : 
      83             :     // Temporary state used for object construction
      84             :     PJ_TYPE m_pjType = PJ_TYPE_UNKNOWN;
      85             :     CPLString m_osPrimeMeridianName{};
      86             :     CPLString m_osAngularUnits{};
      87             :     CPLString m_osLinearUnits{};
      88             :     CPLString m_osAxisName[3]{};
      89             : 
      90             :     std::vector<std::string> m_wktImportWarnings{};
      91             :     std::vector<std::string> m_wktImportErrors{};
      92             :     CPLString m_osAreaName{};
      93             :     CPLString m_celestialBodyName{};
      94             : 
      95             :     bool m_bIsThreadSafe = false;
      96             :     bool m_bNodesChanged = false;
      97             :     bool m_bNodesWKT2 = false;
      98             :     OGR_SRSNode *m_poRoot = nullptr;
      99             : 
     100             :     double dfFromGreenwich = 0.0;
     101             :     double dfToMeter = 0.0;
     102             :     double dfToDegrees = 0.0;
     103             :     double m_dfAngularUnitToRadian = 0.0;
     104             : 
     105             :     int nRefCount = 1;
     106             :     int bNormInfoSet = FALSE;
     107             : 
     108             :     PJ *m_pj_geod_base_crs_temp = nullptr;
     109             :     PJ *m_pj_proj_crs_cs_temp = nullptr;
     110             : 
     111             :     bool m_pj_crs_modified_during_demote = false;
     112             :     PJ *m_pj_bound_crs_target = nullptr;
     113             :     PJ *m_pj_bound_crs_co = nullptr;
     114             :     PJ *m_pj_crs_backup = nullptr;
     115             :     OGR_SRSNode *m_poRootBackup = nullptr;
     116             : 
     117             :     bool m_bMorphToESRI = false;
     118             :     bool m_bHasCenterLong = false;
     119             : 
     120             :     std::shared_ptr<Listener> m_poListener{};
     121             : 
     122             :     std::recursive_mutex m_mutex{};
     123             : 
     124             :     OSRAxisMappingStrategy m_axisMappingStrategy = OAMS_AUTHORITY_COMPLIANT;
     125             :     std::vector<int> m_axisMapping{1, 2, 3};
     126             : 
     127             :     double m_coordinateEpoch = 0;  // as decimal year
     128             : 
     129             :     explicit Private(OGRSpatialReference *poSelf);
     130             :     ~Private();
     131             :     Private(const Private &) = delete;
     132             :     Private &operator=(const Private &) = delete;
     133             : 
     134           2 :     void SetThreadSafe()
     135             :     {
     136           2 :         m_bIsThreadSafe = true;
     137           2 :     }
     138             : 
     139             :     void clear();
     140             :     void setPjCRS(PJ *pj_crsIn, bool doRefreshAxisMapping = true);
     141             :     void setRoot(OGR_SRSNode *poRoot);
     142             :     void refreshProjObj();
     143             :     void nodesChanged();
     144             :     void refreshRootFromProjObj(bool bForceWKT2);
     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     1306230 :     PJ_CONTEXT *getPROJContext()
     159             :     {
     160     1306230 :         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    18467700 :         explicit OptionalLockGuard(Private *p) : m_private(*p)
     180             :         {
     181    18467700 :             if (m_private.m_bIsThreadSafe)
     182        3798 :                 m_private.m_mutex.lock();
     183    18467700 :         }
     184             : 
     185    18467700 :         ~OptionalLockGuard()
     186    18467700 :         {
     187    18467700 :             if (m_private.m_bIsThreadSafe)
     188        3798 :                 m_private.m_mutex.unlock();
     189    18467700 :         }
     190             :     };
     191             : 
     192    18467700 :     inline OptionalLockGuard GetOptionalLockGuard()
     193             :     {
     194    18467700 :         return OptionalLockGuard(this);
     195             :     }
     196             : };
     197             : 
     198     2448570 : void OGRSpatialReference::Private::Listener::notifyChange(OGR_SRSNode *)
     199             : {
     200     2448570 :     m_poObj->nodesChanged();
     201     2448570 : }
     202             : 
     203             : #define TAKE_OPTIONAL_LOCK()                                                   \
     204             :     auto lock = d->GetOptionalLockGuard();                                     \
     205             :     CPL_IGNORE_RET_VAL(lock)
     206             : 
     207      282385 : static OSRAxisMappingStrategy GetDefaultAxisMappingStrategy()
     208             : {
     209             :     const char *pszDefaultAMS =
     210      282385 :         CPLGetConfigOption("OSR_DEFAULT_AXIS_MAPPING_STRATEGY", nullptr);
     211      282385 :     if (pszDefaultAMS)
     212             :     {
     213           1 :         if (EQUAL(pszDefaultAMS, "AUTHORITY_COMPLIANT"))
     214           0 :             return OAMS_AUTHORITY_COMPLIANT;
     215           1 :         else if (EQUAL(pszDefaultAMS, "TRADITIONAL_GIS_ORDER"))
     216           1 :             return OAMS_TRADITIONAL_GIS_ORDER;
     217             :         else
     218             :         {
     219           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     220             :                      "Illegal value for OSR_DEFAULT_AXIS_MAPPING_STRATEGY = %s",
     221             :                      pszDefaultAMS);
     222             :         }
     223             :     }
     224      282384 :     return OAMS_AUTHORITY_COMPLIANT;
     225             : }
     226             : 
     227      282385 : OGRSpatialReference::Private::Private(OGRSpatialReference *poSelf)
     228      282385 :     : m_poSelf(poSelf), m_poListener(std::make_shared<Listener>(this))
     229             : {
     230             :     // Get the default value for m_axisMappingStrategy from the
     231             :     // OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration option, if set.
     232      282385 :     m_axisMappingStrategy = GetDefaultAxisMappingStrategy();
     233      282385 : }
     234             : 
     235     1125580 : OGRSpatialReference::Private::~Private()
     236             : {
     237             :     // In case we destroy the object not in the thread that created it,
     238             :     // we need to reassign the PROJ context. Having the context bundled inside
     239             :     // PJ* deeply sucks...
     240      281395 :     PJ_CONTEXT *pj_context_to_destroy = nullptr;
     241             :     PJ_CONTEXT *ctxt;
     242      281395 :     if (GDALThreadLocalDatasetCacheIsInDestruction())
     243             :     {
     244         185 :         pj_context_to_destroy = proj_context_create();
     245         185 :         ctxt = pj_context_to_destroy;
     246             :     }
     247             :     else
     248             :     {
     249      281210 :         ctxt = getPROJContext();
     250             :     }
     251             : 
     252      281395 :     proj_assign_context(m_pj_crs, ctxt);
     253      281395 :     proj_destroy(m_pj_crs);
     254             : 
     255      281395 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     256      281395 :     proj_destroy(m_pj_geod_base_crs_temp);
     257             : 
     258      281395 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     259      281395 :     proj_destroy(m_pj_proj_crs_cs_temp);
     260             : 
     261      281395 :     proj_assign_context(m_pj_bound_crs_target, ctxt);
     262      281395 :     proj_destroy(m_pj_bound_crs_target);
     263             : 
     264      281395 :     proj_assign_context(m_pj_bound_crs_co, ctxt);
     265      281395 :     proj_destroy(m_pj_bound_crs_co);
     266             : 
     267      281395 :     proj_assign_context(m_pj_crs_backup, ctxt);
     268      281395 :     proj_destroy(m_pj_crs_backup);
     269             : 
     270      281395 :     delete m_poRootBackup;
     271      281395 :     delete m_poRoot;
     272      281395 :     proj_context_destroy(pj_context_to_destroy);
     273      281395 : }
     274             : 
     275      137403 : void OGRSpatialReference::Private::clear()
     276             : {
     277      137403 :     proj_assign_context(m_pj_crs, getPROJContext());
     278      137403 :     proj_destroy(m_pj_crs);
     279      137403 :     m_pj_crs = nullptr;
     280             : 
     281      137403 :     delete m_poRoot;
     282      137403 :     m_poRoot = nullptr;
     283      137403 :     m_bNodesChanged = false;
     284             : 
     285      137403 :     m_wktImportWarnings.clear();
     286      137403 :     m_wktImportErrors.clear();
     287             : 
     288      137403 :     m_pj_crs_modified_during_demote = false;
     289      137403 :     m_pjType = PJ_TYPE_UNKNOWN;
     290      137403 :     m_osPrimeMeridianName.clear();
     291      137403 :     m_osAngularUnits.clear();
     292      137403 :     m_osLinearUnits.clear();
     293             : 
     294      137403 :     bNormInfoSet = FALSE;
     295      137403 :     dfFromGreenwich = 1.0;
     296      137403 :     dfToMeter = 1.0;
     297      137403 :     dfToDegrees = 1.0;
     298      137403 :     m_dfAngularUnitToRadian = 0.0;
     299             : 
     300      137403 :     m_bMorphToESRI = false;
     301      137403 :     m_bHasCenterLong = false;
     302             : 
     303      137403 :     m_coordinateEpoch = 0.0;
     304      137403 : }
     305             : 
     306       30501 : void OGRSpatialReference::Private::setRoot(OGR_SRSNode *poRoot)
     307             : {
     308       30501 :     m_poRoot = poRoot;
     309       30501 :     if (m_poRoot)
     310             :     {
     311       30501 :         m_poRoot->RegisterListener(m_poListener);
     312             :     }
     313       30501 :     nodesChanged();
     314       30501 : }
     315             : 
     316      201275 : void OGRSpatialReference::Private::setPjCRS(PJ *pj_crsIn,
     317             :                                             bool doRefreshAxisMapping)
     318             : {
     319      201275 :     auto ctxt = getPROJContext();
     320             : 
     321             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
     322             :     if (proj_get_type(pj_crsIn) == PJ_TYPE_COORDINATE_METADATA)
     323             :     {
     324             :         const double dfEpoch =
     325             :             proj_coordinate_metadata_get_epoch(ctxt, pj_crsIn);
     326             :         if (!std::isnan(dfEpoch))
     327             :         {
     328             :             m_poSelf->SetCoordinateEpoch(dfEpoch);
     329             :         }
     330             :         auto crs = proj_get_source_crs(ctxt, pj_crsIn);
     331             :         proj_destroy(pj_crsIn);
     332             :         pj_crsIn = crs;
     333             :     }
     334             : #endif
     335             : 
     336      201275 :     proj_assign_context(m_pj_crs, ctxt);
     337      201275 :     proj_destroy(m_pj_crs);
     338      201275 :     m_pj_crs = pj_crsIn;
     339      201275 :     if (m_pj_crs)
     340             :     {
     341      201222 :         m_pjType = proj_get_type(m_pj_crs);
     342             :     }
     343      201275 :     if (m_pj_crs_backup)
     344             :     {
     345          21 :         m_pj_crs_modified_during_demote = true;
     346             :     }
     347      201275 :     invalidateNodes();
     348      201275 :     if (doRefreshAxisMapping)
     349             :     {
     350      201255 :         refreshAxisMapping();
     351             :     }
     352      201275 : }
     353             : 
     354      781589 : void OGRSpatialReference::Private::refreshProjObj()
     355             : {
     356      781589 :     if (m_bNodesChanged && m_poRoot)
     357             :     {
     358        9012 :         char *pszWKT = nullptr;
     359        9012 :         m_poRoot->exportToWkt(&pszWKT);
     360        9012 :         auto poRootBackup = m_poRoot;
     361        9012 :         m_poRoot = nullptr;
     362        9012 :         const double dfCoordinateEpochBackup = m_coordinateEpoch;
     363        9012 :         clear();
     364        9012 :         m_coordinateEpoch = dfCoordinateEpochBackup;
     365        9012 :         m_bHasCenterLong = strstr(pszWKT, "CENTER_LONG") != nullptr;
     366             : 
     367        9012 :         const char *const options[] = {
     368             :             "STRICT=NO",
     369             : #if PROJ_AT_LEAST_VERSION(9, 1, 0)
     370             :             "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=NO",
     371             : #endif
     372             :             nullptr};
     373        9012 :         PROJ_STRING_LIST warnings = nullptr;
     374        9012 :         PROJ_STRING_LIST errors = nullptr;
     375        9012 :         setPjCRS(proj_create_from_wkt(getPROJContext(), pszWKT, options,
     376             :                                       &warnings, &errors));
     377       17641 :         for (auto iter = warnings; iter && *iter; ++iter)
     378             :         {
     379        8629 :             m_wktImportWarnings.push_back(*iter);
     380             :         }
     381        9228 :         for (auto iter = errors; iter && *iter; ++iter)
     382             :         {
     383         216 :             m_wktImportErrors.push_back(*iter);
     384             :         }
     385        9012 :         proj_string_list_destroy(warnings);
     386        9012 :         proj_string_list_destroy(errors);
     387             : 
     388        9012 :         CPLFree(pszWKT);
     389             : 
     390        9012 :         m_poRoot = poRootBackup;
     391        9012 :         m_bNodesChanged = false;
     392             :     }
     393      781589 : }
     394             : 
     395       32642 : void OGRSpatialReference::Private::refreshRootFromProjObj(bool bForceWKT2)
     396             : {
     397       32642 :     CPLAssert(m_poRoot == nullptr);
     398             : 
     399       32642 :     if (m_pj_crs)
     400             :     {
     401       60912 :         CPLStringList aosOptions;
     402       30456 :         if (!m_bMorphToESRI)
     403             :         {
     404       30452 :             aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
     405       30452 :             aosOptions.SetNameValue("MULTILINE", "NO");
     406             :         }
     407       30456 :         aosOptions.SetNameValue("STRICT", "NO");
     408             : 
     409       30456 :         const char *pszWKT = nullptr;
     410       30456 :         if (!bForceWKT2)
     411             :         {
     412       30438 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     413       30438 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs,
     414       30438 :                                  m_bMorphToESRI ? PJ_WKT1_ESRI : PJ_WKT1_GDAL,
     415       30438 :                                  aosOptions.List());
     416       30438 :             m_bNodesWKT2 = false;
     417             :         }
     418       30456 :         if (!m_bMorphToESRI && pszWKT == nullptr)
     419             :         {
     420         114 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs, PJ_WKT2_2018,
     421         114 :                                  aosOptions.List());
     422         114 :             m_bNodesWKT2 = true;
     423             :         }
     424       30456 :         if (pszWKT)
     425             :         {
     426       30456 :             auto root = new OGR_SRSNode();
     427       30456 :             setRoot(root);
     428       30456 :             root->importFromWkt(&pszWKT);
     429       30456 :             m_bNodesChanged = false;
     430             :         }
     431             :     }
     432       32642 : }
     433             : 
     434      235373 : static bool isNorthEastAxisOrder(PJ_CONTEXT *ctx, PJ *cs)
     435             : {
     436      235373 :     const char *pszName1 = nullptr;
     437      235373 :     const char *pszDirection1 = nullptr;
     438      235373 :     proj_cs_get_axis_info(ctx, cs, 0, &pszName1, nullptr, &pszDirection1,
     439             :                           nullptr, nullptr, nullptr, nullptr);
     440      235373 :     const char *pszName2 = nullptr;
     441      235373 :     const char *pszDirection2 = nullptr;
     442      235373 :     proj_cs_get_axis_info(ctx, cs, 1, &pszName2, nullptr, &pszDirection2,
     443             :                           nullptr, nullptr, nullptr, nullptr);
     444      235373 :     if (pszDirection1 && EQUAL(pszDirection1, "north") && pszDirection2 &&
     445      104580 :         EQUAL(pszDirection2, "east"))
     446             :     {
     447      102920 :         return true;
     448             :     }
     449      132453 :     if (pszDirection1 && pszDirection2 &&
     450      132453 :         ((EQUAL(pszDirection1, "north") && EQUAL(pszDirection2, "north")) ||
     451      130810 :          (EQUAL(pszDirection1, "south") && EQUAL(pszDirection2, "south"))) &&
     452        3424 :         pszName1 && STARTS_WITH_CI(pszName1, "northing") && pszName2 &&
     453        1395 :         STARTS_WITH_CI(pszName2, "easting"))
     454             :     {
     455        1395 :         return true;
     456             :     }
     457      131058 :     return false;
     458             : }
     459             : 
     460      296945 : void OGRSpatialReference::Private::refreshAxisMapping()
     461             : {
     462      296945 :     if (!m_pj_crs || m_axisMappingStrategy == OAMS_CUSTOM)
     463       62149 :         return;
     464             : 
     465      234796 :     bool doUndoDemote = false;
     466      234796 :     if (m_pj_crs_backup == nullptr)
     467             :     {
     468      234775 :         doUndoDemote = true;
     469      234775 :         demoteFromBoundCRS();
     470             :     }
     471      234796 :     const auto ctxt = getPROJContext();
     472      234796 :     PJ *horizCRS = nullptr;
     473      234796 :     int axisCount = 0;
     474      234796 :     if (m_pjType == PJ_TYPE_VERTICAL_CRS)
     475             :     {
     476         311 :         axisCount = 1;
     477             :     }
     478      234485 :     else if (m_pjType == PJ_TYPE_COMPOUND_CRS)
     479             :     {
     480        1272 :         horizCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 0);
     481        1272 :         if (horizCRS && proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
     482             :         {
     483         222 :             auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
     484         222 :             if (baseCRS)
     485             :             {
     486         222 :                 proj_destroy(horizCRS);
     487         222 :                 horizCRS = baseCRS;
     488             :             }
     489             :         }
     490             : 
     491        1272 :         auto vertCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 1);
     492        1272 :         if (vertCRS)
     493             :         {
     494        1269 :             if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
     495             :             {
     496         398 :                 auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
     497         398 :                 if (baseCRS)
     498             :                 {
     499         398 :                     proj_destroy(vertCRS);
     500         398 :                     vertCRS = baseCRS;
     501             :                 }
     502             :             }
     503             : 
     504        1269 :             auto cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
     505        1269 :             if (cs)
     506             :             {
     507        1269 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
     508        1269 :                 proj_destroy(cs);
     509             :             }
     510        1269 :             proj_destroy(vertCRS);
     511             :         }
     512             :     }
     513             :     else
     514             :     {
     515      233213 :         horizCRS = m_pj_crs;
     516             :     }
     517             : 
     518      234796 :     bool bSwitchForGisFriendlyOrder = false;
     519      234796 :     if (horizCRS)
     520             :     {
     521      234482 :         auto cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
     522      234482 :         if (cs)
     523             :         {
     524      234482 :             int nHorizCSAxisCount = proj_cs_get_axis_count(ctxt, cs);
     525      234482 :             axisCount += nHorizCSAxisCount;
     526      234482 :             if (nHorizCSAxisCount >= 2)
     527             :             {
     528      234472 :                 bSwitchForGisFriendlyOrder = isNorthEastAxisOrder(ctxt, cs);
     529             :             }
     530      234482 :             proj_destroy(cs);
     531             :         }
     532             :     }
     533      234796 :     if (horizCRS != m_pj_crs)
     534             :     {
     535        1583 :         proj_destroy(horizCRS);
     536             :     }
     537      234796 :     if (doUndoDemote)
     538             :     {
     539      234775 :         undoDemoteFromBoundCRS();
     540             :     }
     541             : 
     542      234796 :     m_axisMapping.resize(axisCount);
     543      234796 :     if (m_axisMappingStrategy == OAMS_AUTHORITY_COMPLIANT ||
     544       70400 :         !bSwitchForGisFriendlyOrder)
     545             :     {
     546      608392 :         for (int i = 0; i < axisCount; i++)
     547             :         {
     548      406107 :             m_axisMapping[i] = i + 1;
     549      202285 :         }
     550             :     }
     551             :     else
     552             :     {
     553       32511 :         m_axisMapping[0] = 2;
     554       32511 :         m_axisMapping[1] = 1;
     555       32511 :         if (axisCount == 3)
     556             :         {
     557         511 :             m_axisMapping[2] = 3;
     558             :         }
     559             :     }
     560             : }
     561             : 
     562     2479070 : void OGRSpatialReference::Private::nodesChanged()
     563             : {
     564     2479070 :     m_bNodesChanged = true;
     565     2479070 : }
     566             : 
     567      201550 : void OGRSpatialReference::Private::invalidateNodes()
     568             : {
     569      201550 :     delete m_poRoot;
     570      201550 :     m_poRoot = nullptr;
     571      201550 :     m_bNodesChanged = false;
     572      201550 : }
     573             : 
     574         257 : void OGRSpatialReference::Private::setMorphToESRI(bool b)
     575             : {
     576         257 :     invalidateNodes();
     577         257 :     m_bMorphToESRI = b;
     578         257 : }
     579             : 
     580      688801 : void OGRSpatialReference::Private::demoteFromBoundCRS()
     581             : {
     582      688801 :     CPLAssert(m_pj_bound_crs_target == nullptr);
     583      688801 :     CPLAssert(m_pj_bound_crs_co == nullptr);
     584      688801 :     CPLAssert(m_poRootBackup == nullptr);
     585      688801 :     CPLAssert(m_pj_crs_backup == nullptr);
     586             : 
     587      688801 :     m_pj_crs_modified_during_demote = false;
     588             : 
     589      688801 :     if (m_pjType == PJ_TYPE_BOUND_CRS)
     590             :     {
     591        2753 :         auto baseCRS = proj_get_source_crs(getPROJContext(), m_pj_crs);
     592        2753 :         m_pj_bound_crs_target = proj_get_target_crs(getPROJContext(), m_pj_crs);
     593        2753 :         m_pj_bound_crs_co =
     594        2753 :             proj_crs_get_coordoperation(getPROJContext(), m_pj_crs);
     595             : 
     596        2753 :         m_poRootBackup = m_poRoot;
     597        2753 :         m_poRoot = nullptr;
     598        2753 :         m_pj_crs_backup = m_pj_crs;
     599        2753 :         m_pj_crs = baseCRS;
     600        2753 :         m_pjType = proj_get_type(m_pj_crs);
     601             :     }
     602      688801 : }
     603             : 
     604      688801 : void OGRSpatialReference::Private::undoDemoteFromBoundCRS()
     605             : {
     606      688801 :     if (m_pj_bound_crs_target)
     607             :     {
     608        2753 :         CPLAssert(m_poRoot == nullptr);
     609        2753 :         CPLAssert(m_pj_crs);
     610        2753 :         if (!m_pj_crs_modified_during_demote)
     611             :         {
     612        2733 :             proj_destroy(m_pj_crs);
     613        2733 :             m_pj_crs = m_pj_crs_backup;
     614        2733 :             m_pjType = proj_get_type(m_pj_crs);
     615        2733 :             m_poRoot = m_poRootBackup;
     616             :         }
     617             :         else
     618             :         {
     619          20 :             delete m_poRootBackup;
     620          20 :             m_poRootBackup = nullptr;
     621          20 :             proj_destroy(m_pj_crs_backup);
     622          20 :             m_pj_crs_backup = nullptr;
     623          20 :             setPjCRS(proj_crs_create_bound_crs(getPROJContext(), m_pj_crs,
     624          20 :                                                m_pj_bound_crs_target,
     625          20 :                                                m_pj_bound_crs_co),
     626             :                      false);
     627             :         }
     628             :     }
     629             : 
     630      688801 :     m_poRootBackup = nullptr;
     631      688801 :     m_pj_crs_backup = nullptr;
     632      688801 :     proj_destroy(m_pj_bound_crs_target);
     633      688801 :     m_pj_bound_crs_target = nullptr;
     634      688801 :     proj_destroy(m_pj_bound_crs_co);
     635      688801 :     m_pj_bound_crs_co = nullptr;
     636      688801 :     m_pj_crs_modified_during_demote = false;
     637      688801 : }
     638             : 
     639      174916 : const char *OGRSpatialReference::Private::nullifyTargetKeyIfPossible(
     640             :     const char *pszTargetKey)
     641             : {
     642      174916 :     if (pszTargetKey)
     643             :     {
     644       62460 :         demoteFromBoundCRS();
     645       62460 :         if ((m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     646       32806 :              m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS) &&
     647       29732 :             EQUAL(pszTargetKey, "GEOGCS"))
     648             :         {
     649        7194 :             pszTargetKey = nullptr;
     650             :         }
     651       55266 :         else if (m_pjType == PJ_TYPE_GEOCENTRIC_CRS &&
     652          20 :                  EQUAL(pszTargetKey, "GEOCCS"))
     653             :         {
     654           0 :             pszTargetKey = nullptr;
     655             :         }
     656       55266 :         else if (m_pjType == PJ_TYPE_PROJECTED_CRS &&
     657       31416 :                  EQUAL(pszTargetKey, "PROJCS"))
     658             :         {
     659        4416 :             pszTargetKey = nullptr;
     660             :         }
     661       50850 :         else if (m_pjType == PJ_TYPE_VERTICAL_CRS &&
     662           4 :                  EQUAL(pszTargetKey, "VERT_CS"))
     663             :         {
     664           2 :             pszTargetKey = nullptr;
     665             :         }
     666       62460 :         undoDemoteFromBoundCRS();
     667             :     }
     668      174916 :     return pszTargetKey;
     669             : }
     670             : 
     671       10038 : PJ *OGRSpatialReference::Private::getGeodBaseCRS()
     672             : {
     673       10038 :     if (m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     674        9985 :         m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
     675             :     {
     676          53 :         return m_pj_crs;
     677             :     }
     678             : 
     679        9985 :     auto ctxt = getPROJContext();
     680        9985 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     681             :     {
     682        4610 :         proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     683        4610 :         proj_destroy(m_pj_geod_base_crs_temp);
     684        4610 :         m_pj_geod_base_crs_temp = proj_crs_get_geodetic_crs(ctxt, m_pj_crs);
     685        4610 :         return m_pj_geod_base_crs_temp;
     686             :     }
     687             : 
     688        5375 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     689        5375 :     proj_destroy(m_pj_geod_base_crs_temp);
     690        5375 :     auto cs = proj_create_ellipsoidal_2D_cs(ctxt, PJ_ELLPS2D_LATITUDE_LONGITUDE,
     691             :                                             nullptr, 0);
     692        5375 :     m_pj_geod_base_crs_temp = proj_create_geographic_crs(
     693             :         ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84",
     694             :         SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING, SRS_PM_GREENWICH, 0.0,
     695             :         SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV), cs);
     696        5375 :     proj_destroy(cs);
     697             : 
     698        5375 :     return m_pj_geod_base_crs_temp;
     699             : }
     700             : 
     701        5575 : PJ *OGRSpatialReference::Private::getProjCRSCoordSys()
     702             : {
     703        5575 :     auto ctxt = getPROJContext();
     704        5575 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     705             :     {
     706        4595 :         proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     707        4595 :         proj_destroy(m_pj_proj_crs_cs_temp);
     708        4595 :         m_pj_proj_crs_cs_temp =
     709        4595 :             proj_crs_get_coordinate_system(getPROJContext(), m_pj_crs);
     710        4595 :         return m_pj_proj_crs_cs_temp;
     711             :     }
     712             : 
     713         980 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     714         980 :     proj_destroy(m_pj_proj_crs_cs_temp);
     715         980 :     m_pj_proj_crs_cs_temp = proj_create_cartesian_2D_cs(
     716             :         ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0);
     717         980 :     return m_pj_proj_crs_cs_temp;
     718             : }
     719             : 
     720        5628 : const char *OGRSpatialReference::Private::getProjCRSName()
     721             : {
     722        5628 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     723             :     {
     724        4611 :         return proj_get_name(m_pj_crs);
     725             :     }
     726             : 
     727        1017 :     return "unnamed";
     728             : }
     729             : 
     730        1389 : OGRErr OGRSpatialReference::Private::replaceConversionAndUnref(PJ *conv)
     731             : {
     732        1389 :     refreshProjObj();
     733             : 
     734        1389 :     demoteFromBoundCRS();
     735             : 
     736             :     auto projCRS =
     737        1389 :         proj_create_projected_crs(getPROJContext(), getProjCRSName(),
     738        1389 :                                   getGeodBaseCRS(), conv, getProjCRSCoordSys());
     739        1389 :     proj_destroy(conv);
     740             : 
     741        1389 :     setPjCRS(projCRS);
     742             : 
     743        1389 :     undoDemoteFromBoundCRS();
     744        1389 :     return OGRERR_NONE;
     745             : }
     746             : 
     747             : /************************************************************************/
     748             : /*                             ToPointer()                              */
     749             : /************************************************************************/
     750             : 
     751       27684 : static inline OGRSpatialReference *ToPointer(OGRSpatialReferenceH hSRS)
     752             : {
     753       27684 :     return OGRSpatialReference::FromHandle(hSRS);
     754             : }
     755             : 
     756             : /************************************************************************/
     757             : /*                              ToHandle()                              */
     758             : /************************************************************************/
     759             : 
     760        4938 : static inline OGRSpatialReferenceH ToHandle(OGRSpatialReference *poSRS)
     761             : {
     762        4938 :     return OGRSpatialReference::ToHandle(poSRS);
     763             : }
     764             : 
     765             : /************************************************************************/
     766             : /*                          OGRsnPrintDouble()                          */
     767             : /************************************************************************/
     768             : 
     769             : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue);
     770             : 
     771         126 : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue)
     772             : 
     773             : {
     774         126 :     CPLsnprintf(pszStrBuf, size, "%.16g", dfValue);
     775             : 
     776         126 :     const size_t nLen = strlen(pszStrBuf);
     777             : 
     778             :     // The following hack is intended to truncate some "precision" in cases
     779             :     // that appear to be roundoff error.
     780         126 :     if (nLen > 15 && (strcmp(pszStrBuf + nLen - 6, "999999") == 0 ||
     781           8 :                       strcmp(pszStrBuf + nLen - 6, "000001") == 0))
     782             :     {
     783           0 :         CPLsnprintf(pszStrBuf, size, "%.15g", dfValue);
     784             :     }
     785             : 
     786             :     // Force to user periods regardless of locale.
     787         126 :     if (strchr(pszStrBuf, ',') != nullptr)
     788             :     {
     789           0 :         char *const pszDelim = strchr(pszStrBuf, ',');
     790           0 :         *pszDelim = '.';
     791             :     }
     792         126 : }
     793             : 
     794             : /************************************************************************/
     795             : /*                        OGRSpatialReference()                         */
     796             : /************************************************************************/
     797             : 
     798             : /**
     799             :  * \brief Constructor.
     800             :  *
     801             :  * This constructor takes an optional string argument which if passed
     802             :  * should be a WKT representation of an SRS.  Passing this is equivalent
     803             :  * to not passing it, and then calling importFromWkt() with the WKT string.
     804             :  *
     805             :  * Note that newly created objects are given a reference count of one.
     806             :  *
     807             :  * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
     808             :  * object are assumed to be in the order of the axis of the CRS definition
     809             :  (which
     810             :  * for example means latitude first, longitude second for geographic CRS
     811             :  belonging
     812             :  * to the EPSG authority). It is possible to define a data axis to CRS axis
     813             :  * mapping strategy with the SetAxisMappingStrategy() method.
     814             :  *
     815             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
     816             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
     817             :  later
     818             :  * being the default value when the option is not set) to control the value of
     819             :  the
     820             :  * data axis to CRS axis mapping strategy when a OSRSpatialReference object is
     821             :  * created. Calling SetAxisMappingStrategy() will override this default value.
     822             : 
     823             :  * The C function OSRNewSpatialReference() does the same thing as this
     824             :  * constructor.
     825             :  *
     826             :  * @param pszWKT well known text definition to which the object should
     827             :  * be initialized, or NULL (the default).
     828             :  */
     829             : 
     830      279636 : OGRSpatialReference::OGRSpatialReference(const char *pszWKT)
     831      279636 :     : d(new Private(this))
     832             : {
     833      279636 :     if (pszWKT != nullptr)
     834        1253 :         importFromWkt(pszWKT);
     835      279636 : }
     836             : 
     837             : /************************************************************************/
     838             : /*                       OSRNewSpatialReference()                       */
     839             : /************************************************************************/
     840             : 
     841             : /**
     842             :  * \brief Constructor.
     843             :  *
     844             :  * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
     845             :  * object are assumed to be in the order of the axis of the CRS definition
     846             :  * (which for example means latitude first, longitude second for geographic CRS
     847             :  * belonging to the EPSG authority). It is possible to define a data axis to CRS
     848             :  * axis mapping strategy with the SetAxisMappingStrategy() method.
     849             :  *
     850             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
     851             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
     852             :  * later being the default value when the option is not set) to control the
     853             :  * value of the data axis to CRS axis mapping strategy when a
     854             :  * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
     855             :  * override this default value.
     856             :  *
     857             :  * This function is the same as OGRSpatialReference::OGRSpatialReference()
     858             :  */
     859        3426 : OGRSpatialReferenceH CPL_STDCALL OSRNewSpatialReference(const char *pszWKT)
     860             : 
     861             : {
     862        3426 :     OGRSpatialReference *poSRS = new OGRSpatialReference();
     863             : 
     864        3426 :     if (pszWKT != nullptr && strlen(pszWKT) > 0)
     865             :     {
     866          66 :         if (poSRS->importFromWkt(pszWKT) != OGRERR_NONE)
     867             :         {
     868           0 :             delete poSRS;
     869           0 :             poSRS = nullptr;
     870             :         }
     871             :     }
     872             : 
     873        3426 :     return ToHandle(poSRS);
     874             : }
     875             : 
     876             : /************************************************************************/
     877             : /*                        OGRSpatialReference()                         */
     878             : /************************************************************************/
     879             : 
     880             : /** Copy constructor. See also Clone().
     881             :  * @param oOther other spatial reference
     882             :  */
     883        2749 : OGRSpatialReference::OGRSpatialReference(const OGRSpatialReference &oOther)
     884        2749 :     : d(new Private(this))
     885             : {
     886        2749 :     *this = oOther;
     887        2749 : }
     888             : 
     889             : /************************************************************************/
     890             : /*                        OGRSpatialReference()                         */
     891             : /************************************************************************/
     892             : 
     893             : /** Move constructor.
     894             :  * @param oOther other spatial reference
     895             :  */
     896           1 : OGRSpatialReference::OGRSpatialReference(OGRSpatialReference &&oOther)
     897           1 :     : d(std::move(oOther.d))
     898             : {
     899           1 : }
     900             : 
     901             : /************************************************************************/
     902             : /*                        ~OGRSpatialReference()                        */
     903             : /************************************************************************/
     904             : 
     905             : /**
     906             :  * \brief OGRSpatialReference destructor.
     907             :  *
     908             :  * The C function OSRDestroySpatialReference() does the same thing as this
     909             :  * method. Preferred C++ method : OGRSpatialReference::DestroySpatialReference()
     910             :  *
     911             :  * @deprecated
     912             :  */
     913             : 
     914      341897 : OGRSpatialReference::~OGRSpatialReference()
     915             : 
     916             : {
     917      341897 : }
     918             : 
     919             : /************************************************************************/
     920             : /*                      DestroySpatialReference()                       */
     921             : /************************************************************************/
     922             : 
     923             : /**
     924             :  * \brief OGRSpatialReference destructor.
     925             :  *
     926             :  * This static method will destroy a OGRSpatialReference.  It is
     927             :  * equivalent to calling delete on the object, but it ensures that the
     928             :  * deallocation is properly executed within the OGR libraries heap on
     929             :  * platforms where this can matter (win32).
     930             :  *
     931             :  * This function is the same as OSRDestroySpatialReference()
     932             :  *
     933             :  * @param poSRS the object to delete
     934             :  *
     935             :  */
     936             : 
     937           0 : void OGRSpatialReference::DestroySpatialReference(OGRSpatialReference *poSRS)
     938             : {
     939           0 :     delete poSRS;
     940           0 : }
     941             : 
     942             : /************************************************************************/
     943             : /*                     OSRDestroySpatialReference()                     */
     944             : /************************************************************************/
     945             : 
     946             : /**
     947             :  * \brief OGRSpatialReference destructor.
     948             :  *
     949             :  * This function is the same as OGRSpatialReference::~OGRSpatialReference()
     950             :  * and OGRSpatialReference::DestroySpatialReference()
     951             :  *
     952             :  * @param hSRS the object to delete
     953             :  */
     954        9812 : void CPL_STDCALL OSRDestroySpatialReference(OGRSpatialReferenceH hSRS)
     955             : 
     956             : {
     957        9812 :     delete ToPointer(hSRS);
     958        9812 : }
     959             : 
     960             : /************************************************************************/
     961             : /*                               Clear()                                */
     962             : /************************************************************************/
     963             : 
     964             : /**
     965             :  * \brief Wipe current definition.
     966             :  *
     967             :  * Returns OGRSpatialReference to a state with no definition, as it
     968             :  * exists when first created.  It does not affect reference counts.
     969             :  */
     970             : 
     971      128391 : void OGRSpatialReference::Clear()
     972             : 
     973             : {
     974      128391 :     d->clear();
     975      128391 : }
     976             : 
     977             : /************************************************************************/
     978             : /*                             operator=()                              */
     979             : /************************************************************************/
     980             : 
     981             : /** Assignment operator.
     982             :  * @param oSource SRS to assign to *this
     983             :  * @return *this
     984             :  */
     985             : OGRSpatialReference &
     986       28642 : OGRSpatialReference::operator=(const OGRSpatialReference &oSource)
     987             : 
     988             : {
     989       28642 :     if (&oSource != this)
     990             :     {
     991       28641 :         Clear();
     992             : #ifdef CPPCHECK
     993             :         // Otherwise cppcheck would protest that nRefCount isn't modified
     994             :         d->nRefCount = (d->nRefCount + 1) - 1;
     995             : #endif
     996             : 
     997       28641 :         oSource.d->refreshProjObj();
     998       28641 :         if (oSource.d->m_pj_crs)
     999       28187 :             d->setPjCRS(proj_clone(d->getPROJContext(), oSource.d->m_pj_crs));
    1000       28641 :         if (oSource.d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
    1001       13565 :             SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1002       15076 :         else if (oSource.d->m_axisMappingStrategy == OAMS_CUSTOM)
    1003         144 :             SetDataAxisToSRSAxisMapping(oSource.d->m_axisMapping);
    1004             : 
    1005       28641 :         d->m_coordinateEpoch = oSource.d->m_coordinateEpoch;
    1006             :     }
    1007             : 
    1008       28642 :     return *this;
    1009             : }
    1010             : 
    1011             : /************************************************************************/
    1012             : /*                             operator=()                              */
    1013             : /************************************************************************/
    1014             : 
    1015             : /** Move assignment operator.
    1016             :  * @param oSource SRS to assign to *this
    1017             :  * @return *this
    1018             :  */
    1019             : OGRSpatialReference &
    1020        5341 : OGRSpatialReference::operator=(OGRSpatialReference &&oSource)
    1021             : 
    1022             : {
    1023        5341 :     if (&oSource != this)
    1024             :     {
    1025        5340 :         d = std::move(oSource.d);
    1026             :     }
    1027             : 
    1028        5341 :     return *this;
    1029             : }
    1030             : 
    1031             : /************************************************************************/
    1032             : /*                       AssignAndSetThreadSafe()                       */
    1033             : /************************************************************************/
    1034             : 
    1035             : /** Assignment method, with thread-safety.
    1036             :  *
    1037             :  * Same as an assignment operator, but asking also that the *this instance
    1038             :  * becomes thread-safe.
    1039             :  *
    1040             :  * @param oSource SRS to assign to *this
    1041             :  * @return *this
    1042             :  * @since 3.10
    1043             :  */
    1044             : 
    1045             : OGRSpatialReference &
    1046           2 : OGRSpatialReference::AssignAndSetThreadSafe(const OGRSpatialReference &oSource)
    1047             : {
    1048           2 :     *this = oSource;
    1049           2 :     d->SetThreadSafe();
    1050           2 :     return *this;
    1051             : }
    1052             : 
    1053             : /************************************************************************/
    1054             : /*                             Reference()                              */
    1055             : /************************************************************************/
    1056             : 
    1057             : /**
    1058             :  * \brief Increments the reference count by one.
    1059             :  *
    1060             :  * The reference count is used keep track of the number of OGRGeometry objects
    1061             :  * referencing this SRS.
    1062             :  *
    1063             :  * The method does the same thing as the C function OSRReference().
    1064             :  *
    1065             :  * @return the updated reference count.
    1066             :  */
    1067             : 
    1068     3209750 : int OGRSpatialReference::Reference()
    1069             : 
    1070             : {
    1071     3209750 :     return CPLAtomicInc(&d->nRefCount);
    1072             : }
    1073             : 
    1074             : /************************************************************************/
    1075             : /*                            OSRReference()                            */
    1076             : /************************************************************************/
    1077             : 
    1078             : /**
    1079             :  * \brief Increments the reference count by one.
    1080             :  *
    1081             :  * This function is the same as OGRSpatialReference::Reference()
    1082             :  */
    1083        1095 : int OSRReference(OGRSpatialReferenceH hSRS)
    1084             : 
    1085             : {
    1086        1095 :     VALIDATE_POINTER1(hSRS, "OSRReference", 0);
    1087             : 
    1088        1095 :     return ToPointer(hSRS)->Reference();
    1089             : }
    1090             : 
    1091             : /************************************************************************/
    1092             : /*                            Dereference()                             */
    1093             : /************************************************************************/
    1094             : 
    1095             : /**
    1096             :  * \brief Decrements the reference count by one.
    1097             :  *
    1098             :  * The method does the same thing as the C function OSRDereference().
    1099             :  *
    1100             :  * @return the updated reference count.
    1101             :  */
    1102             : 
    1103     3252900 : int OGRSpatialReference::Dereference()
    1104             : 
    1105             : {
    1106     3252900 :     if (d->nRefCount <= 0)
    1107           0 :         CPLDebug("OSR",
    1108             :                  "Dereference() called on an object with refcount %d,"
    1109             :                  "likely already destroyed!",
    1110           0 :                  d->nRefCount);
    1111     3252900 :     return CPLAtomicDec(&d->nRefCount);
    1112             : }
    1113             : 
    1114             : /************************************************************************/
    1115             : /*                           OSRDereference()                           */
    1116             : /************************************************************************/
    1117             : 
    1118             : /**
    1119             :  * \brief Decrements the reference count by one.
    1120             :  *
    1121             :  * This function is the same as OGRSpatialReference::Dereference()
    1122             :  */
    1123           0 : int OSRDereference(OGRSpatialReferenceH hSRS)
    1124             : 
    1125             : {
    1126           0 :     VALIDATE_POINTER1(hSRS, "OSRDereference", 0);
    1127             : 
    1128           0 :     return ToPointer(hSRS)->Dereference();
    1129             : }
    1130             : 
    1131             : /************************************************************************/
    1132             : /*                         GetReferenceCount()                          */
    1133             : /************************************************************************/
    1134             : 
    1135             : /**
    1136             :  * \brief Fetch current reference count.
    1137             :  *
    1138             :  * @return the current reference count.
    1139             :  */
    1140         180 : int OGRSpatialReference::GetReferenceCount() const
    1141             : {
    1142         180 :     return d->nRefCount;
    1143             : }
    1144             : 
    1145             : /************************************************************************/
    1146             : /*                              Release()                               */
    1147             : /************************************************************************/
    1148             : 
    1149             : /**
    1150             :  * \brief Decrements the reference count by one, and destroy if zero.
    1151             :  *
    1152             :  * The method does the same thing as the C function OSRRelease().
    1153             :  */
    1154             : 
    1155     3252640 : void OGRSpatialReference::Release()
    1156             : 
    1157             : {
    1158     3252640 :     if (Dereference() <= 0)
    1159       43085 :         delete this;
    1160     3252640 : }
    1161             : 
    1162             : /************************************************************************/
    1163             : /*                             OSRRelease()                             */
    1164             : /************************************************************************/
    1165             : 
    1166             : /**
    1167             :  * \brief Decrements the reference count by one, and destroy if zero.
    1168             :  *
    1169             :  * This function is the same as OGRSpatialReference::Release()
    1170             :  */
    1171        6924 : void OSRRelease(OGRSpatialReferenceH hSRS)
    1172             : 
    1173             : {
    1174        6924 :     VALIDATE_POINTER0(hSRS, "OSRRelease");
    1175             : 
    1176        6924 :     ToPointer(hSRS)->Release();
    1177             : }
    1178             : 
    1179       96048 : OGR_SRSNode *OGRSpatialReference::GetRoot()
    1180             : {
    1181       96048 :     TAKE_OPTIONAL_LOCK();
    1182             : 
    1183       96048 :     if (!d->m_poRoot)
    1184             :     {
    1185       29218 :         d->refreshRootFromProjObj(false);
    1186             :     }
    1187      192096 :     return d->m_poRoot;
    1188             : }
    1189             : 
    1190        8815 : const OGR_SRSNode *OGRSpatialReference::GetRoot() const
    1191             : {
    1192        8815 :     TAKE_OPTIONAL_LOCK();
    1193             : 
    1194        8815 :     if (!d->m_poRoot)
    1195             :     {
    1196        3406 :         d->refreshRootFromProjObj(false);
    1197             :     }
    1198       17630 :     return d->m_poRoot;
    1199             : }
    1200             : 
    1201             : /************************************************************************/
    1202             : /*                              SetRoot()                               */
    1203             : /************************************************************************/
    1204             : 
    1205             : /**
    1206             :  * \brief Set the root SRS node.
    1207             :  *
    1208             :  * If the object has an existing tree of OGR_SRSNodes, they are destroyed
    1209             :  * as part of assigning the new root.  Ownership of the passed OGR_SRSNode is
    1210             :  * is assumed by the OGRSpatialReference.
    1211             :  *
    1212             :  * @param poNewRoot object to assign as root.
    1213             :  */
    1214             : 
    1215          45 : void OGRSpatialReference::SetRoot(OGR_SRSNode *poNewRoot)
    1216             : 
    1217             : {
    1218          45 :     if (d->m_poRoot != poNewRoot)
    1219             :     {
    1220          45 :         delete d->m_poRoot;
    1221          45 :         d->setRoot(poNewRoot);
    1222             :     }
    1223          45 : }
    1224             : 
    1225             : /************************************************************************/
    1226             : /*                            GetAttrNode()                             */
    1227             : /************************************************************************/
    1228             : 
    1229             : /**
    1230             :  * \brief Find named node in tree.
    1231             :  *
    1232             :  * This method does a pre-order traversal of the node tree searching for
    1233             :  * a node with this exact value (case insensitive), and returns it.  Leaf
    1234             :  * nodes are not considered, under the assumption that they are just
    1235             :  * attribute value nodes.
    1236             :  *
    1237             :  * If a node appears more than once in the tree (such as UNIT for instance),
    1238             :  * the first encountered will be returned.  Use GetNode() on a subtree to be
    1239             :  * more specific.
    1240             :  *
    1241             :  * @param pszNodePath the name of the node to search for.  May contain multiple
    1242             :  * components such as "GEOGCS|UNIT".
    1243             :  *
    1244             :  * @return a pointer to the node found, or NULL if none.
    1245             :  */
    1246             : 
    1247       92900 : OGR_SRSNode *OGRSpatialReference::GetAttrNode(const char *pszNodePath)
    1248             : 
    1249             : {
    1250       92900 :     if (strstr(pszNodePath, "CONVERSION") && !d->m_bNodesWKT2)
    1251             :     {
    1252          18 :         d->invalidateNodes();
    1253          18 :         d->refreshRootFromProjObj(/* bForceWKT2 = */ true);
    1254             :     }
    1255             : 
    1256       92900 :     if (strchr(pszNodePath, '|') == nullptr)
    1257             :     {
    1258             :         // Fast path
    1259       51581 :         OGR_SRSNode *poNode = GetRoot();
    1260       51581 :         if (poNode)
    1261       50361 :             poNode = poNode->GetNode(pszNodePath);
    1262       51581 :         return poNode;
    1263             :     }
    1264             : 
    1265             :     char **papszPathTokens =
    1266       41319 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    1267             : 
    1268       41319 :     if (CSLCount(papszPathTokens) < 1)
    1269             :     {
    1270           0 :         CSLDestroy(papszPathTokens);
    1271           0 :         return nullptr;
    1272             :     }
    1273             : 
    1274       41319 :     OGR_SRSNode *poNode = GetRoot();
    1275      125774 :     for (int i = 0; poNode != nullptr && papszPathTokens[i] != nullptr; i++)
    1276             :     {
    1277       84455 :         poNode = poNode->GetNode(papszPathTokens[i]);
    1278             :     }
    1279             : 
    1280       41319 :     CSLDestroy(papszPathTokens);
    1281             : 
    1282       41319 :     return poNode;
    1283             : }
    1284             : 
    1285             : /**
    1286             :  * \brief Find named node in tree.
    1287             :  *
    1288             :  * This method does a pre-order traversal of the node tree searching for
    1289             :  * a node with this exact value (case insensitive), and returns it.  Leaf
    1290             :  * nodes are not considered, under the assumption that they are just
    1291             :  * attribute value nodes.
    1292             :  *
    1293             :  * If a node appears more than once in the tree (such as UNIT for instance),
    1294             :  * the first encountered will be returned.  Use GetNode() on a subtree to be
    1295             :  * more specific.
    1296             :  *
    1297             :  * @param pszNodePath the name of the node to search for.  May contain multiple
    1298             :  * components such as "GEOGCS|UNIT".
    1299             :  *
    1300             :  * @return a pointer to the node found, or NULL if none.
    1301             :  */
    1302             : 
    1303             : const OGR_SRSNode *
    1304       84137 : OGRSpatialReference::GetAttrNode(const char *pszNodePath) const
    1305             : 
    1306             : {
    1307             :     OGR_SRSNode *poNode =
    1308       84137 :         const_cast<OGRSpatialReference *>(this)->GetAttrNode(pszNodePath);
    1309             : 
    1310       84137 :     return poNode;
    1311             : }
    1312             : 
    1313             : /************************************************************************/
    1314             : /*                            GetAttrValue()                            */
    1315             : /************************************************************************/
    1316             : 
    1317             : /**
    1318             :  * \brief Fetch indicated attribute of named node.
    1319             :  *
    1320             :  * This method uses GetAttrNode() to find the named node, and then extracts
    1321             :  * the value of the indicated child.  Thus a call to GetAttrValue("UNIT",1)
    1322             :  * would return the second child of the UNIT node, which is normally the
    1323             :  * length of the linear unit in meters.
    1324             :  *
    1325             :  * This method does the same thing as the C function OSRGetAttrValue().
    1326             :  *
    1327             :  * @param pszNodeName the tree node to look for (case insensitive).
    1328             :  * @param iAttr the child of the node to fetch (zero based).
    1329             :  *
    1330             :  * @return the requested value, or NULL if it fails for any reason.
    1331             :  */
    1332             : 
    1333       25174 : const char *OGRSpatialReference::GetAttrValue(const char *pszNodeName,
    1334             :                                               int iAttr) const
    1335             : 
    1336             : {
    1337       25174 :     const OGR_SRSNode *poNode = GetAttrNode(pszNodeName);
    1338       25174 :     if (poNode == nullptr)
    1339             :     {
    1340       10787 :         if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJECTION"))
    1341             :         {
    1342          14 :             return GetAttrValue("METHOD", iAttr);
    1343             :         }
    1344       10773 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS|PROJECTION"))
    1345             :         {
    1346           0 :             return GetAttrValue("PROJCRS|METHOD", iAttr);
    1347             :         }
    1348       10773 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS"))
    1349             :         {
    1350           1 :             return GetAttrValue("PROJCRS", iAttr);
    1351             :         }
    1352       10772 :         return nullptr;
    1353             :     }
    1354             : 
    1355       14387 :     if (iAttr < 0 || iAttr >= poNode->GetChildCount())
    1356           0 :         return nullptr;
    1357             : 
    1358       14387 :     return poNode->GetChild(iAttr)->GetValue();
    1359             : }
    1360             : 
    1361             : /************************************************************************/
    1362             : /*                          OSRGetAttrValue()                           */
    1363             : /************************************************************************/
    1364             : 
    1365             : /**
    1366             :  * \brief Fetch indicated attribute of named node.
    1367             :  *
    1368             :  * This function is the same as OGRSpatialReference::GetAttrValue()
    1369             :  */
    1370          36 : const char *CPL_STDCALL OSRGetAttrValue(OGRSpatialReferenceH hSRS,
    1371             :                                         const char *pszKey, int iChild)
    1372             : 
    1373             : {
    1374          36 :     VALIDATE_POINTER1(hSRS, "OSRGetAttrValue", nullptr);
    1375             : 
    1376          36 :     return ToPointer(hSRS)->GetAttrValue(pszKey, iChild);
    1377             : }
    1378             : 
    1379             : /************************************************************************/
    1380             : /*                              GetName()                               */
    1381             : /************************************************************************/
    1382             : 
    1383             : /**
    1384             :  * \brief Return the CRS name.
    1385             :  *
    1386             :  * The returned value is only short lived and should not be used after other
    1387             :  * calls to methods on this object.
    1388             :  *
    1389             :  * @since GDAL 3.0
    1390             :  */
    1391             : 
    1392        8997 : const char *OGRSpatialReference::GetName() const
    1393             : {
    1394       17994 :     TAKE_OPTIONAL_LOCK();
    1395             : 
    1396        8997 :     d->refreshProjObj();
    1397        8997 :     if (!d->m_pj_crs)
    1398         113 :         return nullptr;
    1399        8884 :     const char *pszName = proj_get_name(d->m_pj_crs);
    1400             : #if PROJ_VERSION_NUMBER == PROJ_COMPUTE_VERSION(8, 2, 0)
    1401             :     if (d->m_pjType == PJ_TYPE_BOUND_CRS && EQUAL(pszName, "SOURCECRS"))
    1402             :     {
    1403             :         // Work around a bug of PROJ 8.2.0 (fixed in 8.2.1)
    1404             :         PJ *baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
    1405             :         if (baseCRS)
    1406             :         {
    1407             :             pszName = proj_get_name(baseCRS);
    1408             :             // pszName still remains valid after proj_destroy(), since
    1409             :             // d->m_pj_crs keeps a reference to the base CRS C++ object.
    1410             :             proj_destroy(baseCRS);
    1411             :         }
    1412             :     }
    1413             : #endif
    1414        8884 :     return pszName;
    1415             : }
    1416             : 
    1417             : /************************************************************************/
    1418             : /*                             OSRGetName()                             */
    1419             : /************************************************************************/
    1420             : 
    1421             : /**
    1422             :  * \brief Return the CRS name.
    1423             :  *
    1424             :  * The returned value is only short lived and should not be used after other
    1425             :  * calls to methods on this object.
    1426             :  *
    1427             :  * @since GDAL 3.0
    1428             :  */
    1429          47 : const char *OSRGetName(OGRSpatialReferenceH hSRS)
    1430             : 
    1431             : {
    1432          47 :     VALIDATE_POINTER1(hSRS, "OSRGetName", nullptr);
    1433             : 
    1434          47 :     return ToPointer(hSRS)->GetName();
    1435             : }
    1436             : 
    1437             : /************************************************************************/
    1438             : /*                        GetCelestialBodyName()                        */
    1439             : /************************************************************************/
    1440             : 
    1441             : /**
    1442             :  * \brief Return the name of the celestial body of this CRS.
    1443             :  *
    1444             :  * e.g. "Earth" for an Earth CRS
    1445             :  *
    1446             :  * The returned value is only short lived and should not be used after other
    1447             :  * calls to methods on this object.
    1448             :  *
    1449             :  * @since GDAL 3.12 and PROJ 8.1
    1450             :  */
    1451             : 
    1452           4 : const char *OGRSpatialReference::GetCelestialBodyName() const
    1453             : {
    1454             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    1455             : 
    1456             :     TAKE_OPTIONAL_LOCK();
    1457             : 
    1458             :     d->refreshProjObj();
    1459             :     if (!d->m_pj_crs)
    1460             :         return nullptr;
    1461             :     d->demoteFromBoundCRS();
    1462             :     const char *name =
    1463             :         proj_get_celestial_body_name(d->getPROJContext(), d->m_pj_crs);
    1464             :     if (name)
    1465             :     {
    1466             :         d->m_celestialBodyName = name;
    1467             :     }
    1468             :     d->undoDemoteFromBoundCRS();
    1469             :     return d->m_celestialBodyName.c_str();
    1470             : #else
    1471           4 :     if (std::fabs(GetSemiMajor(nullptr) - SRS_WGS84_SEMIMAJOR) <=
    1472             :         0.05 * SRS_WGS84_SEMIMAJOR)
    1473           4 :         return "Earth";
    1474           0 :     const char *pszAuthName = GetAuthorityName();
    1475           0 :     if (pszAuthName && EQUAL(pszAuthName, "EPSG"))
    1476           0 :         return "Earth";
    1477           0 :     return nullptr;
    1478             : #endif
    1479             : }
    1480             : 
    1481             : /************************************************************************/
    1482             : /*                      OSRGetCelestialBodyName()                       */
    1483             : /************************************************************************/
    1484             : 
    1485             : /**
    1486             :  * \brief Return the name of the celestial body of this CRS.
    1487             :  *
    1488             :  * e.g. "Earth" for an Earth CRS
    1489             :  *
    1490             :  * The returned value is only short lived and should not be used after other
    1491             :  * calls to methods on this object.
    1492             :  *
    1493             :  * @since GDAL 3.12 and PROJ 8.1
    1494             :  */
    1495             : 
    1496           1 : const char *OSRGetCelestialBodyName(OGRSpatialReferenceH hSRS)
    1497             : 
    1498             : {
    1499           1 :     VALIDATE_POINTER1(hSRS, "GetCelestialBodyName", nullptr);
    1500             : 
    1501           1 :     return ToPointer(hSRS)->GetCelestialBodyName();
    1502             : }
    1503             : 
    1504             : /************************************************************************/
    1505             : /*                               Clone()                                */
    1506             : /************************************************************************/
    1507             : 
    1508             : /**
    1509             :  * \brief Make a duplicate of this OGRSpatialReference.
    1510             :  *
    1511             :  * This method is the same as the C function OSRClone().
    1512             :  *
    1513             :  * @return a new SRS, which becomes the responsibility of the caller.
    1514             :  */
    1515             : 
    1516       32463 : OGRSpatialReference *OGRSpatialReference::Clone() const
    1517             : 
    1518             : {
    1519       32463 :     OGRSpatialReference *poNewRef = new OGRSpatialReference();
    1520             : 
    1521       32463 :     TAKE_OPTIONAL_LOCK();
    1522             : 
    1523       32463 :     d->refreshProjObj();
    1524       32463 :     if (d->m_pj_crs != nullptr)
    1525       32419 :         poNewRef->d->setPjCRS(proj_clone(d->getPROJContext(), d->m_pj_crs));
    1526       32463 :     if (d->m_bHasCenterLong && d->m_poRoot)
    1527             :     {
    1528           0 :         poNewRef->d->setRoot(d->m_poRoot->Clone());
    1529             :     }
    1530       32463 :     poNewRef->d->m_axisMapping = d->m_axisMapping;
    1531       32463 :     poNewRef->d->m_axisMappingStrategy = d->m_axisMappingStrategy;
    1532       32463 :     poNewRef->d->m_coordinateEpoch = d->m_coordinateEpoch;
    1533       64926 :     return poNewRef;
    1534             : }
    1535             : 
    1536             : /************************************************************************/
    1537             : /*                              OSRClone()                              */
    1538             : /************************************************************************/
    1539             : 
    1540             : /**
    1541             :  * \brief Make a duplicate of this OGRSpatialReference.
    1542             :  *
    1543             :  * This function is the same as OGRSpatialReference::Clone()
    1544             :  */
    1545        1357 : OGRSpatialReferenceH CPL_STDCALL OSRClone(OGRSpatialReferenceH hSRS)
    1546             : 
    1547             : {
    1548        1357 :     VALIDATE_POINTER1(hSRS, "OSRClone", nullptr);
    1549             : 
    1550        1357 :     return ToHandle(ToPointer(hSRS)->Clone());
    1551             : }
    1552             : 
    1553             : /************************************************************************/
    1554             : /*                            dumpReadable()                            */
    1555             : /************************************************************************/
    1556             : 
    1557             : /** Dump pretty wkt to stdout, mostly for debugging.
    1558             :  */
    1559           0 : void OGRSpatialReference::dumpReadable()
    1560             : 
    1561             : {
    1562           0 :     char *pszPrettyWkt = nullptr;
    1563             : 
    1564           0 :     const char *const apszOptions[] = {"FORMAT=WKT2", "MULTILINE=YES", nullptr};
    1565           0 :     exportToWkt(&pszPrettyWkt, apszOptions);
    1566           0 :     printf("%s\n", pszPrettyWkt); /*ok*/
    1567           0 :     CPLFree(pszPrettyWkt);
    1568           0 : }
    1569             : 
    1570             : /************************************************************************/
    1571             : /*                         exportToPrettyWkt()                          */
    1572             : /************************************************************************/
    1573             : 
    1574             : /**
    1575             :  * Convert this SRS into a nicely formatted WKT 1 string for display to a
    1576             :  * person.
    1577             :  *
    1578             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1579             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1580             :  *
    1581             :  * Note that the returned WKT string should be freed with
    1582             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1583             :  *
    1584             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1585             :  * option. Valid values are the one of the FORMAT option of
    1586             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1587             :  *
    1588             :  * This method is the same as the C function OSRExportToPrettyWkt().
    1589             :  *
    1590             :  * @param ppszResult the resulting string is returned in this pointer.
    1591             :  * @param bSimplify TRUE if the AXIS, AUTHORITY and EXTENSION nodes should be
    1592             :  *   stripped off.
    1593             :  *
    1594             :  * @return OGRERR_NONE if successful.
    1595             :  */
    1596             : 
    1597          58 : OGRErr OGRSpatialReference::exportToPrettyWkt(char **ppszResult,
    1598             :                                               int bSimplify) const
    1599             : 
    1600             : {
    1601         116 :     CPLStringList aosOptions;
    1602          58 :     aosOptions.SetNameValue("MULTILINE", "YES");
    1603          58 :     if (bSimplify)
    1604             :     {
    1605           0 :         aosOptions.SetNameValue("FORMAT", "WKT1_SIMPLE");
    1606             :     }
    1607         116 :     return exportToWkt(ppszResult, aosOptions.List());
    1608             : }
    1609             : 
    1610             : /************************************************************************/
    1611             : /*                        OSRExportToPrettyWkt()                        */
    1612             : /************************************************************************/
    1613             : 
    1614             : /**
    1615             :  * \brief Convert this SRS into a nicely formatted WKT 1 string for display to a
    1616             :  * person.
    1617             :  *
    1618             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1619             :  * option. Valid values are the one of the FORMAT option of
    1620             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1621             :  *
    1622             :  * This function is the same as OGRSpatialReference::exportToPrettyWkt().
    1623             :  */
    1624             : 
    1625          56 : OGRErr CPL_STDCALL OSRExportToPrettyWkt(OGRSpatialReferenceH hSRS,
    1626             :                                         char **ppszReturn, int bSimplify)
    1627             : 
    1628             : {
    1629          56 :     VALIDATE_POINTER1(hSRS, "OSRExportToPrettyWkt", OGRERR_FAILURE);
    1630             : 
    1631          56 :     *ppszReturn = nullptr;
    1632             : 
    1633          56 :     return ToPointer(hSRS)->exportToPrettyWkt(ppszReturn, bSimplify);
    1634             : }
    1635             : 
    1636             : /************************************************************************/
    1637             : /*                            exportToWkt()                             */
    1638             : /************************************************************************/
    1639             : 
    1640             : /**
    1641             :  * \brief Convert this SRS into WKT 1 format.
    1642             :  *
    1643             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1644             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1645             :  *
    1646             :  * Note that the returned WKT string should be freed with
    1647             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1648             :  *
    1649             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1650             :  * option. Valid values are the one of the FORMAT option of
    1651             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1652             :  *
    1653             :  * This method is the same as the C function OSRExportToWkt().
    1654             :  *
    1655             :  * @param ppszResult the resulting string is returned in this pointer.
    1656             :  *
    1657             :  * @return OGRERR_NONE if successful.
    1658             :  */
    1659             : 
    1660       12395 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult) const
    1661             : 
    1662             : {
    1663       12395 :     return exportToWkt(ppszResult, nullptr);
    1664             : }
    1665             : 
    1666             : /************************************************************************/
    1667             : /*              GDAL_proj_crs_create_bound_crs_to_WGS84()               */
    1668             : /************************************************************************/
    1669             : 
    1670         562 : static PJ *GDAL_proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, PJ *pj,
    1671             :                                                    bool onlyIfEPSGCode,
    1672             :                                                    bool canModifyHorizPart)
    1673             : {
    1674         562 :     PJ *ret = nullptr;
    1675         562 :     if (proj_get_type(pj) == PJ_TYPE_COMPOUND_CRS)
    1676             :     {
    1677          13 :         auto horizCRS = proj_crs_get_sub_crs(ctx, pj, 0);
    1678          13 :         auto vertCRS = proj_crs_get_sub_crs(ctx, pj, 1);
    1679          13 :         if (horizCRS && proj_get_type(horizCRS) != PJ_TYPE_BOUND_CRS &&
    1680          26 :             vertCRS &&
    1681          10 :             (!onlyIfEPSGCode || proj_get_id_auth_name(horizCRS, 0) != nullptr))
    1682             :         {
    1683             :             auto boundHoriz =
    1684             :                 canModifyHorizPart
    1685           3 :                     ? proj_crs_create_bound_crs_to_WGS84(ctx, horizCRS, nullptr)
    1686           3 :                     : proj_clone(ctx, horizCRS);
    1687             :             auto boundVert =
    1688           3 :                 proj_crs_create_bound_crs_to_WGS84(ctx, vertCRS, nullptr);
    1689           3 :             if (boundHoriz && boundVert)
    1690             :             {
    1691           3 :                 ret = proj_create_compound_crs(ctx, proj_get_name(pj),
    1692             :                                                boundHoriz, boundVert);
    1693             :             }
    1694           3 :             proj_destroy(boundHoriz);
    1695           3 :             proj_destroy(boundVert);
    1696             :         }
    1697          13 :         proj_destroy(horizCRS);
    1698          13 :         proj_destroy(vertCRS);
    1699             :     }
    1700        1058 :     else if (proj_get_type(pj) != PJ_TYPE_BOUND_CRS &&
    1701         509 :              (!onlyIfEPSGCode || proj_get_id_auth_name(pj, 0) != nullptr))
    1702             :     {
    1703         189 :         ret = proj_crs_create_bound_crs_to_WGS84(ctx, pj, nullptr);
    1704             :     }
    1705         562 :     return ret;
    1706             : }
    1707             : 
    1708             : /************************************************************************/
    1709             : /*                            exportToWkt()                             */
    1710             : /************************************************************************/
    1711             : 
    1712             : /**
    1713             :  * Convert this SRS into a WKT string.
    1714             :  *
    1715             :  * Note that the returned WKT string should be freed with
    1716             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1717             :  *
    1718             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1719             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1720             :  *
    1721             :  * @param ppszResult the resulting string is returned in this pointer.
    1722             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    1723             :  * supported options are
    1724             :  * <ul>
    1725             :  * <li>MULTILINE=YES/NO. Defaults to NO.</li>
    1726             :  * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
    1727             :  *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
    1728             :  *     node is returned.
    1729             :  *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
    1730             :  *     node is returned.
    1731             :  *     WKT1 is an alias of WKT1_GDAL.
    1732             :  *     WKT2 will default to the latest revision implemented (currently
    1733             :  *     WKT2_2018) WKT2_2019 can be used as an alias of WKT2_2018 since GDAL 3.2
    1734             :  * </li>
    1735             :  * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
    1736             :  * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
    1737             :  * be exported as a compound CRS whose vertical part represents an ellipsoidal
    1738             :  * height (for example for use with LAS 1.4 WKT1).
    1739             :  * Requires PROJ 7.2.1 and GDAL 3.2.1.</li>
    1740             :  * </ul>
    1741             :  *
    1742             :  * Starting with GDAL 3.0.3, if the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
    1743             :  * configuration option is set to YES, when exporting to WKT1_GDAL, this method
    1744             :  * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
    1745             :  * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
    1746             :  * TOWGS84[] node may be added.
    1747             :  *
    1748             :  * @return OGRERR_NONE if successful.
    1749             :  * @since GDAL 3.0
    1750             :  */
    1751             : 
    1752       17526 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult,
    1753             :                                         const char *const *papszOptions) const
    1754             : {
    1755             :     // In the past calling this method was thread-safe, even if we never
    1756             :     // guaranteed it. Now proj_as_wkt() will cache the result internally,
    1757             :     // so this is no longer thread-safe.
    1758       35052 :     std::lock_guard oLock(d->m_mutex);
    1759             : 
    1760       17526 :     d->refreshProjObj();
    1761       17526 :     if (!d->m_pj_crs)
    1762             :     {
    1763          21 :         *ppszResult = CPLStrdup("");
    1764          21 :         return OGRERR_FAILURE;
    1765             :     }
    1766             : 
    1767       17505 :     if (d->m_bHasCenterLong && d->m_poRoot && !d->m_bMorphToESRI)
    1768             :     {
    1769           0 :         return d->m_poRoot->exportToWkt(ppszResult);
    1770             :     }
    1771             : 
    1772       17505 :     auto ctxt = d->getPROJContext();
    1773       17505 :     auto wktFormat = PJ_WKT1_GDAL;
    1774             :     const char *pszFormat =
    1775       17505 :         CSLFetchNameValueDef(papszOptions, "FORMAT",
    1776             :                              CPLGetConfigOption("OSR_WKT_FORMAT", "DEFAULT"));
    1777       17505 :     if (EQUAL(pszFormat, "DEFAULT"))
    1778       14640 :         pszFormat = "";
    1779             : 
    1780       17505 :     if (EQUAL(pszFormat, "WKT1_ESRI") || d->m_bMorphToESRI)
    1781             :     {
    1782         672 :         wktFormat = PJ_WKT1_ESRI;
    1783             :     }
    1784       16833 :     else if (EQUAL(pszFormat, "WKT1") || EQUAL(pszFormat, "WKT1_GDAL") ||
    1785       16114 :              EQUAL(pszFormat, "WKT1_SIMPLE") || EQUAL(pszFormat, "SFSQL"))
    1786             :     {
    1787         724 :         wktFormat = PJ_WKT1_GDAL;
    1788             :     }
    1789       16109 :     else if (EQUAL(pszFormat, "WKT2_2015"))
    1790             :     {
    1791         363 :         wktFormat = PJ_WKT2_2015;
    1792             :     }
    1793       15746 :     else if (EQUAL(pszFormat, "WKT2") || EQUAL(pszFormat, "WKT2_2018") ||
    1794       15346 :              EQUAL(pszFormat, "WKT2_2019"))
    1795             :     {
    1796        1336 :         wktFormat = PJ_WKT2_2018;
    1797             :     }
    1798       14410 :     else if (pszFormat[0] == '\0')
    1799             :     {
    1800             :         // cppcheck-suppress knownConditionTrueFalse
    1801       14410 :         if (IsDerivedGeographic())
    1802             :         {
    1803           2 :             wktFormat = PJ_WKT2_2018;
    1804             :         }
    1805       28182 :         else if ((IsGeographic() || IsProjected()) && !IsCompound() &&
    1806       13774 :                  GetAxesCount() == 3)
    1807             :         {
    1808          80 :             wktFormat = PJ_WKT2_2018;
    1809             :         }
    1810             :     }
    1811             :     else
    1812             :     {
    1813           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unsupported value for FORMAT");
    1814           0 :         *ppszResult = CPLStrdup("");
    1815           0 :         return OGRERR_FAILURE;
    1816             :     }
    1817             : 
    1818       35010 :     CPLStringList aosOptions;
    1819       17505 :     if (wktFormat != PJ_WKT1_ESRI)
    1820             :     {
    1821       16833 :         aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
    1822             :     }
    1823             :     aosOptions.SetNameValue(
    1824       17505 :         "MULTILINE", CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO"));
    1825             : 
    1826       17505 :     const char *pszAllowEllpsHeightAsVertCS = CSLFetchNameValue(
    1827             :         papszOptions, "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS");
    1828       17505 :     if (pszAllowEllpsHeightAsVertCS)
    1829             :     {
    1830             :         aosOptions.SetNameValue("ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS",
    1831           0 :                                 pszAllowEllpsHeightAsVertCS);
    1832             :     }
    1833             : 
    1834       17505 :     PJ *boundCRS = nullptr;
    1835       32557 :     if (wktFormat == PJ_WKT1_GDAL &&
    1836       15052 :         CPLTestBool(CSLFetchNameValueDef(
    1837             :             papszOptions, "ADD_TOWGS84_ON_EXPORT_TO_WKT1",
    1838             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1", "NO"))))
    1839             :     {
    1840           0 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
    1841           0 :             d->getPROJContext(), d->m_pj_crs, true, true);
    1842             :     }
    1843             : 
    1844       35010 :     CPLErrorAccumulator oErrorAccumulator;
    1845             :     const char *pszWKT;
    1846             :     {
    1847       17505 :         auto oAccumulator = oErrorAccumulator.InstallForCurrentScope();
    1848       17505 :         CPL_IGNORE_RET_VAL(oAccumulator);
    1849       17505 :         pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs, wktFormat,
    1850       17505 :                              aosOptions.List());
    1851             :     }
    1852       17507 :     for (const auto &oError : oErrorAccumulator.GetErrors())
    1853             :     {
    1854          32 :         if (pszFormat[0] == '\0' &&
    1855          14 :             (oError.msg.find("Unsupported conversion method") !=
    1856           2 :                  std::string::npos ||
    1857           2 :              oError.msg.find("can only be exported to WKT2") !=
    1858           0 :                  std::string::npos ||
    1859           0 :              oError.msg.find("can only be exported since WKT2:2019") !=
    1860             :                  std::string::npos))
    1861             :         {
    1862          14 :             CPLErrorReset();
    1863             :             // If we cannot export in the default mode (WKT1), retry with WKT2
    1864          14 :             pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs,
    1865          14 :                                  PJ_WKT2_2018, aosOptions.List());
    1866          14 :             break;
    1867             :         }
    1868           2 :         CPLError(oError.type, oError.no, "%s", oError.msg.c_str());
    1869             :     }
    1870             : 
    1871       17505 :     if (!pszWKT)
    1872             :     {
    1873           2 :         *ppszResult = CPLStrdup("");
    1874           2 :         proj_destroy(boundCRS);
    1875           2 :         return OGRERR_FAILURE;
    1876             :     }
    1877             : 
    1878       17503 :     if (EQUAL(pszFormat, "SFSQL") || EQUAL(pszFormat, "WKT1_SIMPLE"))
    1879             :     {
    1880           5 :         OGR_SRSNode oRoot;
    1881           5 :         oRoot.importFromWkt(&pszWKT);
    1882           5 :         oRoot.StripNodes("AXIS");
    1883           5 :         if (EQUAL(pszFormat, "SFSQL"))
    1884             :         {
    1885           3 :             oRoot.StripNodes("TOWGS84");
    1886             :         }
    1887           5 :         oRoot.StripNodes("AUTHORITY");
    1888           5 :         oRoot.StripNodes("EXTENSION");
    1889             :         OGRErr eErr;
    1890           5 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO")))
    1891           2 :             eErr = oRoot.exportToPrettyWkt(ppszResult, 1);
    1892             :         else
    1893           3 :             eErr = oRoot.exportToWkt(ppszResult);
    1894           5 :         proj_destroy(boundCRS);
    1895           5 :         return eErr;
    1896             :     }
    1897             : 
    1898       17498 :     *ppszResult = CPLStrdup(pszWKT);
    1899             : 
    1900             : #if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
    1901       17498 :     if (wktFormat == PJ_WKT2_2018)
    1902             :     {
    1903             :         // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
    1904             :         // related to a wrong EPSG code assigned to UTM South conversions
    1905        1418 :         char *pszPtr = strstr(*ppszResult, "CONVERSION[\"UTM zone ");
    1906        1418 :         if (pszPtr)
    1907             :         {
    1908         349 :             pszPtr += strlen("CONVERSION[\"UTM zone ");
    1909         349 :             const int nZone = atoi(pszPtr);
    1910        1046 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    1911         697 :                 ++pszPtr;
    1912         349 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' &&
    1913           1 :                 pszPtr[1] == '"' && pszPtr[2] == ',')
    1914             :             {
    1915           1 :                 pszPtr += 3;
    1916           1 :                 int nLevel = 0;
    1917           1 :                 bool bInString = false;
    1918             :                 // Find the ID node corresponding to this CONVERSION node
    1919         480 :                 while (*pszPtr)
    1920             :                 {
    1921         480 :                     if (bInString)
    1922             :                     {
    1923         197 :                         if (*pszPtr == '"' && pszPtr[1] == '"')
    1924             :                         {
    1925           0 :                             ++pszPtr;
    1926             :                         }
    1927         197 :                         else if (*pszPtr == '"')
    1928             :                         {
    1929          17 :                             bInString = false;
    1930             :                         }
    1931             :                     }
    1932         283 :                     else if (nLevel == 0 && STARTS_WITH_CI(pszPtr, "ID["))
    1933             :                     {
    1934           1 :                         if (STARTS_WITH_CI(pszPtr, CPLSPrintf("ID[\"EPSG\",%d]",
    1935             :                                                               17000 + nZone)))
    1936             :                         {
    1937           1 :                             CPLAssert(pszPtr[11] == '7');
    1938           1 :                             CPLAssert(pszPtr[12] == '0');
    1939           1 :                             pszPtr[11] = '6';
    1940           1 :                             pszPtr[12] = '1';
    1941             :                         }
    1942           1 :                         break;
    1943             :                     }
    1944         282 :                     else if (*pszPtr == '"')
    1945             :                     {
    1946          17 :                         bInString = true;
    1947             :                     }
    1948         265 :                     else if (*pszPtr == '[')
    1949             :                     {
    1950          17 :                         ++nLevel;
    1951             :                     }
    1952         248 :                     else if (*pszPtr == ']')
    1953             :                     {
    1954          17 :                         --nLevel;
    1955             :                     }
    1956             : 
    1957         479 :                     ++pszPtr;
    1958             :                 }
    1959             :             }
    1960             :         }
    1961             :     }
    1962             : #endif
    1963             : 
    1964       17498 :     proj_destroy(boundCRS);
    1965       17498 :     return OGRERR_NONE;
    1966             : }
    1967             : 
    1968             : /************************************************************************/
    1969             : /*                            exportToWkt()                             */
    1970             : /************************************************************************/
    1971             : 
    1972             : /**
    1973             :  * Convert this SRS into a WKT string.
    1974             :  *
    1975             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1976             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1977             :  *
    1978             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    1979             :  * supported options are
    1980             :  * <ul>
    1981             :  * <li>MULTILINE=YES/NO. Defaults to NO.</li>
    1982             :  * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
    1983             :  *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
    1984             :  *     node is returned.
    1985             :  *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
    1986             :  *     node is returned.
    1987             :  *     WKT1 is an alias of WKT1_GDAL.
    1988             :  *     WKT2 will default to the latest revision implemented (currently
    1989             :  *     WKT2_2019)
    1990             :  * </li>
    1991             :  * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
    1992             :  * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
    1993             :  * be exported as a compound CRS whose vertical part represents an ellipsoidal
    1994             :  * height (for example for use with LAS 1.4 WKT1).
    1995             :  * Requires PROJ 7.2.1.</li>
    1996             :  * </ul>
    1997             :  *
    1998             :  * If the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
    1999             :  * configuration option is set to YES, when exporting to WKT1_GDAL, this method
    2000             :  * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
    2001             :  * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
    2002             :  * TOWGS84[] node may be added.
    2003             :  *
    2004             :  * @return a non-empty string if successful.
    2005             :  * @since GDAL 3.9
    2006             :  */
    2007             : 
    2008             : std::string
    2009         806 : OGRSpatialReference::exportToWkt(const char *const *papszOptions) const
    2010             : {
    2011         806 :     std::string osWKT;
    2012         806 :     char *pszWKT = nullptr;
    2013         806 :     if (exportToWkt(&pszWKT, papszOptions) == OGRERR_NONE)
    2014         793 :         osWKT = pszWKT;
    2015         806 :     CPLFree(pszWKT);
    2016        1612 :     return osWKT;
    2017             : }
    2018             : 
    2019             : /************************************************************************/
    2020             : /*                           OSRExportToWkt()                           */
    2021             : /************************************************************************/
    2022             : 
    2023             : /**
    2024             :  * \brief Convert this SRS into WKT 1 format.
    2025             :  *
    2026             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2027             :  * Issues</a> page for implementation details of WKT in OGR.
    2028             :  *
    2029             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    2030             :  * option. Valid values are the one of the FORMAT option of
    2031             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    2032             :  *
    2033             :  * This function is the same as OGRSpatialReference::exportToWkt().
    2034             :  */
    2035             : 
    2036        1077 : OGRErr CPL_STDCALL OSRExportToWkt(OGRSpatialReferenceH hSRS, char **ppszReturn)
    2037             : 
    2038             : {
    2039        1077 :     VALIDATE_POINTER1(hSRS, "OSRExportToWkt", OGRERR_FAILURE);
    2040             : 
    2041        1077 :     *ppszReturn = nullptr;
    2042             : 
    2043        1077 :     return ToPointer(hSRS)->exportToWkt(ppszReturn);
    2044             : }
    2045             : 
    2046             : /************************************************************************/
    2047             : /*                          OSRExportToWktEx()                          */
    2048             : /************************************************************************/
    2049             : 
    2050             : /**
    2051             :  * \brief Convert this SRS into WKT format.
    2052             :  *
    2053             :  * This function is the same as OGRSpatialReference::exportToWkt(char **
    2054             :  * ppszResult,const char* const* papszOptions ) const
    2055             :  *
    2056             :  * @since GDAL 3.0
    2057             :  */
    2058             : 
    2059        1301 : OGRErr OSRExportToWktEx(OGRSpatialReferenceH hSRS, char **ppszReturn,
    2060             :                         const char *const *papszOptions)
    2061             : {
    2062        1301 :     VALIDATE_POINTER1(hSRS, "OSRExportToWktEx", OGRERR_FAILURE);
    2063             : 
    2064        1301 :     *ppszReturn = nullptr;
    2065             : 
    2066        1301 :     return ToPointer(hSRS)->exportToWkt(ppszReturn, papszOptions);
    2067             : }
    2068             : 
    2069             : /************************************************************************/
    2070             : /*                          exportToPROJJSON()                          */
    2071             : /************************************************************************/
    2072             : 
    2073             : /**
    2074             :  * Convert this SRS into a PROJJSON string.
    2075             :  *
    2076             :  * Note that the returned JSON string should be freed with
    2077             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    2078             :  *
    2079             :  * @param ppszResult the resulting string is returned in this pointer.
    2080             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    2081             :  * supported options are
    2082             :  * <ul>
    2083             :  * <li>MULTILINE=YES/NO. Defaults to YES</li>
    2084             :  * <li>INDENTATION_WIDTH=number. Defaults to 2 (when multiline output is
    2085             :  * on).</li>
    2086             :  * <li>SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to
    2087             :  * disable it.</li>
    2088             :  * </ul>
    2089             :  *
    2090             :  * @return OGRERR_NONE if successful.
    2091             :  * @since GDAL 3.1 and PROJ 6.2
    2092             :  */
    2093             : 
    2094        2968 : OGRErr OGRSpatialReference::exportToPROJJSON(
    2095             :     char **ppszResult, CPL_UNUSED const char *const *papszOptions) const
    2096             : {
    2097        5936 :     TAKE_OPTIONAL_LOCK();
    2098             : 
    2099        2968 :     d->refreshProjObj();
    2100        2968 :     if (!d->m_pj_crs)
    2101             :     {
    2102           1 :         *ppszResult = nullptr;
    2103           1 :         return OGRERR_FAILURE;
    2104             :     }
    2105             : 
    2106             :     const char *pszPROJJSON =
    2107        2967 :         proj_as_projjson(d->getPROJContext(), d->m_pj_crs, papszOptions);
    2108             : 
    2109        2967 :     if (!pszPROJJSON)
    2110             :     {
    2111           0 :         *ppszResult = CPLStrdup("");
    2112           0 :         return OGRERR_FAILURE;
    2113             :     }
    2114             : 
    2115        2967 :     *ppszResult = CPLStrdup(pszPROJJSON);
    2116             : 
    2117             : #if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
    2118             :     {
    2119             :         // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
    2120             :         // related to a wrong EPSG code assigned to UTM South conversions
    2121        2967 :         char *pszPtr = strstr(*ppszResult, "\"name\": \"UTM zone ");
    2122        2967 :         if (pszPtr)
    2123             :         {
    2124         257 :             pszPtr += strlen("\"name\": \"UTM zone ");
    2125         257 :             const int nZone = atoi(pszPtr);
    2126         770 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    2127         513 :                 ++pszPtr;
    2128         257 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' && pszPtr[1] == '"')
    2129             :             {
    2130           3 :                 pszPtr += 2;
    2131           3 :                 int nLevel = 0;
    2132           3 :                 bool bInString = false;
    2133             :                 // Find the id node corresponding to this conversion node
    2134        3773 :                 while (*pszPtr)
    2135             :                 {
    2136        3773 :                     if (bInString)
    2137             :                     {
    2138        1384 :                         if (*pszPtr == '\\')
    2139             :                         {
    2140           0 :                             ++pszPtr;
    2141             :                         }
    2142        1384 :                         else if (*pszPtr == '"')
    2143             :                         {
    2144         175 :                             bInString = false;
    2145             :                         }
    2146             :                     }
    2147        2389 :                     else if (nLevel == 0 && STARTS_WITH(pszPtr, "\"id\": {"))
    2148             :                     {
    2149           3 :                         const char *pszNextEndCurl = strchr(pszPtr, '}');
    2150             :                         const char *pszAuthEPSG =
    2151           3 :                             strstr(pszPtr, "\"authority\": \"EPSG\"");
    2152           3 :                         char *pszCode = strstr(
    2153             :                             pszPtr, CPLSPrintf("\"code\": %d", 17000 + nZone));
    2154           3 :                         if (pszAuthEPSG && pszCode && pszNextEndCurl &&
    2155           3 :                             pszNextEndCurl - pszAuthEPSG > 0 &&
    2156           3 :                             pszNextEndCurl - pszCode > 0)
    2157             :                         {
    2158           3 :                             CPLAssert(pszCode[9] == '7');
    2159           3 :                             CPLAssert(pszCode[10] == '0');
    2160           3 :                             pszCode[9] = '6';
    2161           3 :                             pszCode[10] = '1';
    2162             :                         }
    2163           3 :                         break;
    2164             :                     }
    2165        2386 :                     else if (*pszPtr == '"')
    2166             :                     {
    2167         175 :                         bInString = true;
    2168             :                     }
    2169        2211 :                     else if (*pszPtr == '{' || *pszPtr == '[')
    2170             :                     {
    2171          43 :                         ++nLevel;
    2172             :                     }
    2173        2168 :                     else if (*pszPtr == '}' || *pszPtr == ']')
    2174             :                     {
    2175          43 :                         --nLevel;
    2176             :                     }
    2177             : 
    2178        3770 :                     ++pszPtr;
    2179             :                 }
    2180             :             }
    2181             :         }
    2182             :     }
    2183             : #endif
    2184             : 
    2185        2967 :     return OGRERR_NONE;
    2186             : }
    2187             : 
    2188             : /************************************************************************/
    2189             : /*                        OSRExportToPROJJSON()                         */
    2190             : /************************************************************************/
    2191             : 
    2192             : /**
    2193             :  * \brief Convert this SRS into PROJJSON format.
    2194             :  *
    2195             :  * This function is the same as OGRSpatialReference::exportToPROJJSON() const
    2196             :  *
    2197             :  * @since GDAL 3.1 and PROJ 6.2
    2198             :  */
    2199             : 
    2200           2 : OGRErr OSRExportToPROJJSON(OGRSpatialReferenceH hSRS, char **ppszReturn,
    2201             :                            const char *const *papszOptions)
    2202             : {
    2203           2 :     VALIDATE_POINTER1(hSRS, "OSRExportToPROJJSON", OGRERR_FAILURE);
    2204             : 
    2205           2 :     *ppszReturn = nullptr;
    2206             : 
    2207           2 :     return ToPointer(hSRS)->exportToPROJJSON(ppszReturn, papszOptions);
    2208             : }
    2209             : 
    2210             : /************************************************************************/
    2211             : /*                           importFromWkt()                            */
    2212             : /************************************************************************/
    2213             : 
    2214             : /**
    2215             :  * \brief Import from WKT string.
    2216             :  *
    2217             :  * This method will wipe the existing SRS definition, and
    2218             :  * reassign it based on the contents of the passed WKT string.  Only as
    2219             :  * much of the input string as needed to construct this SRS is consumed from
    2220             :  * the input string, and the input string pointer
    2221             :  * is then updated to point to the remaining (unused) input.
    2222             :  *
    2223             :  * Starting with PROJ 9.2, if invoked on a COORDINATEMETADATA[] construct,
    2224             :  * the CRS contained in it will be used to fill the OGRSpatialReference object,
    2225             :  * and the coordinate epoch potentially present used as the coordinate epoch
    2226             :  * property of the OGRSpatialReference object.
    2227             :  *
    2228             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2229             :  * Issues</a> page for implementation details of WKT in OGR.
    2230             :  *
    2231             :  * This method is the same as the C function OSRImportFromWkt().
    2232             :  *
    2233             :  * @param ppszInput Pointer to pointer to input.  The pointer is updated to
    2234             :  * point to remaining unused input text.
    2235             :  *
    2236             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2237             :  * fails for any reason.
    2238             :  */
    2239             : 
    2240       21090 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput)
    2241             : 
    2242             : {
    2243       21090 :     return importFromWkt(ppszInput, nullptr);
    2244             : }
    2245             : 
    2246             : /************************************************************************/
    2247             : /*                           importFromWkt()                            */
    2248             : /************************************************************************/
    2249             : 
    2250             : /*! @cond Doxygen_Suppress */
    2251             : 
    2252          21 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput,
    2253             :                                           CSLConstList papszOptions)
    2254             : 
    2255             : {
    2256          21 :     return importFromWkt(&pszInput, papszOptions);
    2257             : }
    2258             : 
    2259       21111 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput,
    2260             :                                           CSLConstList papszOptions)
    2261             : 
    2262             : {
    2263       42222 :     TAKE_OPTIONAL_LOCK();
    2264             : 
    2265       21111 :     if (!ppszInput || !*ppszInput)
    2266           0 :         return OGRERR_FAILURE;
    2267             : 
    2268       21111 :     if (strlen(*ppszInput) > 100 * 1000 &&
    2269           0 :         CPLTestBool(CPLGetConfigOption("OSR_IMPORT_FROM_WKT_LIMIT", "YES")))
    2270             :     {
    2271           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2272             :                  "Suspiciously large input for importFromWkt(). Rejecting it. "
    2273             :                  "You can remove this limitation by definition the "
    2274             :                  "OSR_IMPORT_FROM_WKT_LIMIT configuration option to NO.");
    2275           0 :         return OGRERR_FAILURE;
    2276             :     }
    2277             : 
    2278       21111 :     Clear();
    2279             : 
    2280       21111 :     bool canCache = false;
    2281       21111 :     auto tlsCache = OSRGetProjTLSCache();
    2282       42222 :     std::string osWkt;
    2283       21111 :     if (**ppszInput)
    2284             :     {
    2285       20556 :         osWkt = *ppszInput;
    2286       20556 :         auto cachedObj = tlsCache->GetPJForWKT(osWkt);
    2287       20556 :         if (cachedObj)
    2288             :         {
    2289       18543 :             d->setPjCRS(cachedObj);
    2290             :         }
    2291             :         else
    2292             :         {
    2293        4026 :             CPLStringList aosOptions(papszOptions);
    2294        2013 :             if (aosOptions.FetchNameValue("STRICT") == nullptr)
    2295        2013 :                 aosOptions.SetNameValue("STRICT", "NO");
    2296        2013 :             PROJ_STRING_LIST warnings = nullptr;
    2297        2013 :             PROJ_STRING_LIST errors = nullptr;
    2298        2013 :             auto ctxt = d->getPROJContext();
    2299        2013 :             auto pj = proj_create_from_wkt(ctxt, *ppszInput, aosOptions.List(),
    2300             :                                            &warnings, &errors);
    2301        2013 :             d->setPjCRS(pj);
    2302             : 
    2303        2066 :             for (auto iter = warnings; iter && *iter; ++iter)
    2304             :             {
    2305          53 :                 d->m_wktImportWarnings.push_back(*iter);
    2306             :             }
    2307        2250 :             for (auto iter = errors; iter && *iter; ++iter)
    2308             :             {
    2309         237 :                 d->m_wktImportErrors.push_back(*iter);
    2310         237 :                 if (!d->m_pj_crs)
    2311             :                 {
    2312          34 :                     CPLError(CE_Failure, CPLE_AppDefined, "%s", *iter);
    2313             :                 }
    2314             :             }
    2315        2013 :             if (warnings == nullptr && errors == nullptr)
    2316             :             {
    2317        1732 :                 canCache = true;
    2318             :             }
    2319        2013 :             proj_string_list_destroy(warnings);
    2320        2013 :             proj_string_list_destroy(errors);
    2321             :         }
    2322             :     }
    2323       21111 :     if (!d->m_pj_crs)
    2324         589 :         return OGRERR_CORRUPT_DATA;
    2325             : 
    2326             :     // Only accept CRS objects
    2327       20522 :     if (!proj_is_crs(d->m_pj_crs))
    2328             :     {
    2329           0 :         Clear();
    2330           0 :         return OGRERR_CORRUPT_DATA;
    2331             :     }
    2332             : 
    2333       20522 :     if (canCache)
    2334             :     {
    2335        1732 :         tlsCache->CachePJForWKT(osWkt, d->m_pj_crs);
    2336             :     }
    2337             : 
    2338       20522 :     if (strstr(*ppszInput, "CENTER_LONG"))
    2339             :     {
    2340           0 :         auto poRoot = new OGR_SRSNode();
    2341           0 :         d->setRoot(poRoot);
    2342           0 :         const char *pszTmp = *ppszInput;
    2343           0 :         poRoot->importFromWkt(&pszTmp);
    2344           0 :         d->m_bHasCenterLong = true;
    2345             :     }
    2346             : 
    2347             :     // TODO? we don't really update correctly since we assume that the
    2348             :     // passed string is only WKT.
    2349       20522 :     *ppszInput += strlen(*ppszInput);
    2350       20522 :     return OGRERR_NONE;
    2351             : 
    2352             : #if no_longer_implemented_for_now
    2353             :     /* -------------------------------------------------------------------- */
    2354             :     /*      The following seems to try and detect and unconsumed            */
    2355             :     /*      VERTCS[] coordinate system definition (ESRI style) and to       */
    2356             :     /*      import and attach it to the existing root.  Likely we will      */
    2357             :     /*      need to extend this somewhat to bring it into an acceptable     */
    2358             :     /*      OGRSpatialReference organization at some point.                 */
    2359             :     /* -------------------------------------------------------------------- */
    2360             :     if (strlen(*ppszInput) > 0 && strstr(*ppszInput, "VERTCS"))
    2361             :     {
    2362             :         if (((*ppszInput)[0]) == ',')
    2363             :             (*ppszInput)++;
    2364             :         OGR_SRSNode *poNewChild = new OGR_SRSNode();
    2365             :         poRoot->AddChild(poNewChild);
    2366             :         return poNewChild->importFromWkt(ppszInput);
    2367             :     }
    2368             : #endif
    2369             : }
    2370             : 
    2371             : /*! @endcond */
    2372             : 
    2373             : /**
    2374             :  * \brief Import from WKT string.
    2375             :  *
    2376             :  * This method will wipe the existing SRS definition, and
    2377             :  * reassign it based on the contents of the passed WKT string.  Only as
    2378             :  * much of the input string as needed to construct this SRS is consumed from
    2379             :  * the input string, and the input string pointer
    2380             :  * is then updated to point to the remaining (unused) input.
    2381             :  *
    2382             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2383             :  * Issues</a> page for implementation details of WKT in OGR.
    2384             :  *
    2385             :  * This method is the same as the C function OSRImportFromWkt().
    2386             :  *
    2387             :  * @param ppszInput Pointer to pointer to input.  The pointer is updated to
    2388             :  * point to remaining unused input text.
    2389             :  *
    2390             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2391             :  * fails for any reason.
    2392             :  * @deprecated Use importFromWkt(const char**) or importFromWkt(const
    2393             :  * char*)
    2394             :  */
    2395             : 
    2396           0 : OGRErr OGRSpatialReference::importFromWkt(char **ppszInput)
    2397             : 
    2398             : {
    2399           0 :     return importFromWkt(const_cast<const char **>(ppszInput));
    2400             : }
    2401             : 
    2402             : /**
    2403             :  * \brief Import from WKT string.
    2404             :  *
    2405             :  * This method will wipe the existing SRS definition, and
    2406             :  * reassign it based on the contents of the passed WKT string.  Only as
    2407             :  * much of the input string as needed to construct this SRS is consumed from
    2408             :  * the input string, and the input string pointer
    2409             :  * is then updated to point to the remaining (unused) input.
    2410             :  *
    2411             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2412             :  * Issues</a> page for implementation details of WKT in OGR.
    2413             :  *
    2414             :  * @param pszInput Input WKT
    2415             :  *
    2416             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2417             :  * fails for any reason.
    2418             :  */
    2419             : 
    2420       20827 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput)
    2421             : {
    2422       20827 :     return importFromWkt(&pszInput);
    2423             : }
    2424             : 
    2425             : /************************************************************************/
    2426             : /*                              Validate()                              */
    2427             : /************************************************************************/
    2428             : 
    2429             : /**
    2430             :  * \brief Validate CRS imported with importFromWkt() or with modified with
    2431             :  * direct node manipulations. Otherwise the CRS should be always valid.
    2432             :  *
    2433             :  * This method attempts to verify that the spatial reference system is
    2434             :  * well formed, and consists of known tokens.  The validation is not
    2435             :  * comprehensive.
    2436             :  *
    2437             :  * This method is the same as the C function OSRValidate().
    2438             :  *
    2439             :  * @return OGRERR_NONE if all is fine, OGRERR_CORRUPT_DATA if the SRS is
    2440             :  * not well formed, and OGRERR_UNSUPPORTED_SRS if the SRS is well formed,
    2441             :  * but contains non-standard PROJECTION[] values.
    2442             :  */
    2443             : 
    2444         116 : OGRErr OGRSpatialReference::Validate() const
    2445             : 
    2446             : {
    2447         232 :     TAKE_OPTIONAL_LOCK();
    2448             : 
    2449         154 :     for (const auto &str : d->m_wktImportErrors)
    2450             :     {
    2451          38 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2452             :     }
    2453         116 :     for (const auto &str : d->m_wktImportWarnings)
    2454             :     {
    2455           0 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2456             :     }
    2457         116 :     if (!d->m_pj_crs || !d->m_wktImportErrors.empty())
    2458             :     {
    2459          37 :         return OGRERR_CORRUPT_DATA;
    2460             :     }
    2461          79 :     if (!d->m_wktImportWarnings.empty())
    2462             :     {
    2463           0 :         return OGRERR_UNSUPPORTED_SRS;
    2464             :     }
    2465          79 :     return OGRERR_NONE;
    2466             : }
    2467             : 
    2468             : /************************************************************************/
    2469             : /*                            OSRValidate()                             */
    2470             : /************************************************************************/
    2471             : /**
    2472             :  * \brief Validate SRS tokens.
    2473             :  *
    2474             :  * This function is the same as the C++ method OGRSpatialReference::Validate().
    2475             :  */
    2476         114 : OGRErr OSRValidate(OGRSpatialReferenceH hSRS)
    2477             : 
    2478             : {
    2479         114 :     VALIDATE_POINTER1(hSRS, "OSRValidate", OGRERR_FAILURE);
    2480             : 
    2481         114 :     return OGRSpatialReference::FromHandle(hSRS)->Validate();
    2482             : }
    2483             : 
    2484             : /************************************************************************/
    2485             : /*                          OSRImportFromWkt()                          */
    2486             : /************************************************************************/
    2487             : 
    2488             : /**
    2489             :  * \brief Import from WKT string.
    2490             :  *
    2491             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2492             :  * Issues</a> page for implementation details of WKT in OGR.
    2493             :  *
    2494             :  * This function is the same as OGRSpatialReference::importFromWkt().
    2495             :  */
    2496             : 
    2497         263 : OGRErr OSRImportFromWkt(OGRSpatialReferenceH hSRS, char **ppszInput)
    2498             : 
    2499             : {
    2500         263 :     VALIDATE_POINTER1(hSRS, "OSRImportFromWkt", OGRERR_FAILURE);
    2501             : 
    2502         263 :     return ToPointer(hSRS)->importFromWkt(const_cast<const char **>(ppszInput));
    2503             : }
    2504             : 
    2505             : /************************************************************************/
    2506             : /*                              SetNode()                               */
    2507             : /************************************************************************/
    2508             : 
    2509             : /**
    2510             :  * \brief Set attribute value in spatial reference.
    2511             :  *
    2512             :  * Missing intermediate nodes in the path will be created if not already
    2513             :  * in existence.  If the attribute has no children one will be created and
    2514             :  * assigned the value otherwise the zeroth child will be assigned the value.
    2515             :  *
    2516             :  * This method does the same as the C function OSRSetAttrValue().
    2517             :  *
    2518             :  * @param pszNodePath full path to attribute to be set.  For instance
    2519             :  * "PROJCS|GEOGCS|UNIT".
    2520             :  *
    2521             :  * @param pszNewNodeValue value to be assigned to node, such as "meter".
    2522             :  * This may be NULL if you just want to force creation of the intermediate
    2523             :  * path.
    2524             :  *
    2525             :  * @return OGRERR_NONE on success.
    2526             :  */
    2527             : 
    2528         587 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath,
    2529             :                                     const char *pszNewNodeValue)
    2530             : 
    2531             : {
    2532        1174 :     TAKE_OPTIONAL_LOCK();
    2533             : 
    2534             :     char **papszPathTokens =
    2535         587 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    2536             : 
    2537         587 :     if (CSLCount(papszPathTokens) < 1)
    2538             :     {
    2539           0 :         CSLDestroy(papszPathTokens);
    2540           0 :         return OGRERR_FAILURE;
    2541             :     }
    2542             : 
    2543        1024 :     if (GetRoot() == nullptr ||
    2544         437 :         !EQUAL(papszPathTokens[0], GetRoot()->GetValue()))
    2545             :     {
    2546         271 :         if (EQUAL(papszPathTokens[0], "PROJCS") &&
    2547         117 :             CSLCount(papszPathTokens) == 1)
    2548             :         {
    2549         117 :             CSLDestroy(papszPathTokens);
    2550         117 :             return SetProjCS(pszNewNodeValue);
    2551             :         }
    2552             :         else
    2553             :         {
    2554          37 :             SetRoot(new OGR_SRSNode(papszPathTokens[0]));
    2555             :         }
    2556             :     }
    2557             : 
    2558         470 :     OGR_SRSNode *poNode = GetRoot();
    2559         728 :     for (int i = 1; papszPathTokens[i] != nullptr; i++)
    2560             :     {
    2561         258 :         int j = 0;  // Used after for.
    2562             : 
    2563         645 :         for (; j < poNode->GetChildCount(); j++)
    2564             :         {
    2565         585 :             if (EQUAL(poNode->GetChild(j)->GetValue(), papszPathTokens[i]))
    2566             :             {
    2567         198 :                 poNode = poNode->GetChild(j);
    2568         198 :                 j = -1;
    2569         198 :                 break;
    2570             :             }
    2571             :         }
    2572             : 
    2573         258 :         if (j != -1)
    2574             :         {
    2575          60 :             OGR_SRSNode *poNewNode = new OGR_SRSNode(papszPathTokens[i]);
    2576          60 :             poNode->AddChild(poNewNode);
    2577          60 :             poNode = poNewNode;
    2578             :         }
    2579             :     }
    2580             : 
    2581         470 :     CSLDestroy(papszPathTokens);
    2582             : 
    2583         470 :     if (pszNewNodeValue != nullptr)
    2584             :     {
    2585         470 :         if (poNode->GetChildCount() > 0)
    2586         373 :             poNode->GetChild(0)->SetValue(pszNewNodeValue);
    2587             :         else
    2588          97 :             poNode->AddChild(new OGR_SRSNode(pszNewNodeValue));
    2589             :     };
    2590         470 :     return OGRERR_NONE;
    2591             : }
    2592             : 
    2593             : /************************************************************************/
    2594             : /*                          OSRSetAttrValue()                           */
    2595             : /************************************************************************/
    2596             : 
    2597             : /**
    2598             :  * \brief Set attribute value in spatial reference.
    2599             :  *
    2600             :  * This function is the same as OGRSpatialReference::SetNode()
    2601             :  */
    2602           1 : OGRErr CPL_STDCALL OSRSetAttrValue(OGRSpatialReferenceH hSRS,
    2603             :                                    const char *pszPath, const char *pszValue)
    2604             : 
    2605             : {
    2606           1 :     VALIDATE_POINTER1(hSRS, "OSRSetAttrValue", OGRERR_FAILURE);
    2607             : 
    2608           1 :     return ToPointer(hSRS)->SetNode(pszPath, pszValue);
    2609             : }
    2610             : 
    2611             : /************************************************************************/
    2612             : /*                              SetNode()                               */
    2613             : /************************************************************************/
    2614             : 
    2615             : /**
    2616             :  * \brief Set attribute value in spatial reference.
    2617             :  *
    2618             :  * Missing intermediate nodes in the path will be created if not already
    2619             :  * in existence.  If the attribute has no children one will be created and
    2620             :  * assigned the value otherwise the zeroth child will be assigned the value.
    2621             :  *
    2622             :  * This method does the same as the C function OSRSetAttrValue().
    2623             :  *
    2624             :  * @param pszNodePath full path to attribute to be set.  For instance
    2625             :  * "PROJCS|GEOGCS|UNIT".
    2626             :  *
    2627             :  * @param dfValue value to be assigned to node.
    2628             :  *
    2629             :  * @return OGRERR_NONE on success.
    2630             :  */
    2631             : 
    2632           0 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath, double dfValue)
    2633             : 
    2634             : {
    2635           0 :     char szValue[64] = {'\0'};
    2636             : 
    2637           0 :     if (std::abs(dfValue - static_cast<int>(dfValue)) == 0.0)
    2638           0 :         snprintf(szValue, sizeof(szValue), "%d", static_cast<int>(dfValue));
    2639             :     else
    2640           0 :         OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    2641             : 
    2642           0 :     return SetNode(pszNodePath, szValue);
    2643             : }
    2644             : 
    2645             : /************************************************************************/
    2646             : /*                          SetAngularUnits()                           */
    2647             : /************************************************************************/
    2648             : 
    2649             : /**
    2650             :  * \brief Set the angular units for the geographic coordinate system.
    2651             :  *
    2652             :  * This method creates a UNIT subnode with the specified values as a
    2653             :  * child of the GEOGCS node.
    2654             :  *
    2655             :  * This method does the same as the C function OSRSetAngularUnits().
    2656             :  *
    2657             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2658             :  * names can be found in ogr_srs_api.h such as SRS_UA_DEGREE.
    2659             :  *
    2660             :  * @param dfInRadians the value to multiple by an angle in the indicated
    2661             :  * units to transform to radians.  Some standard conversion factors can
    2662             :  * be found in ogr_srs_api.h.
    2663             :  *
    2664             :  * @return OGRERR_NONE on success.
    2665             :  */
    2666             : 
    2667        1601 : OGRErr OGRSpatialReference::SetAngularUnits(const char *pszUnitsName,
    2668             :                                             double dfInRadians)
    2669             : 
    2670             : {
    2671        3202 :     TAKE_OPTIONAL_LOCK();
    2672             : 
    2673        1601 :     d->bNormInfoSet = FALSE;
    2674             : 
    2675        1601 :     d->refreshProjObj();
    2676        1601 :     if (!d->m_pj_crs)
    2677           0 :         return OGRERR_FAILURE;
    2678        1601 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2679        1601 :     if (!geodCRS)
    2680           0 :         return OGRERR_FAILURE;
    2681        1601 :     proj_destroy(geodCRS);
    2682        1601 :     d->demoteFromBoundCRS();
    2683        1601 :     d->setPjCRS(proj_crs_alter_cs_angular_unit(d->getPROJContext(), d->m_pj_crs,
    2684             :                                                pszUnitsName, dfInRadians,
    2685             :                                                nullptr, nullptr));
    2686        1601 :     d->undoDemoteFromBoundCRS();
    2687             : 
    2688        1601 :     d->m_osAngularUnits = pszUnitsName;
    2689        1601 :     d->m_dfAngularUnitToRadian = dfInRadians;
    2690             : 
    2691        1601 :     return OGRERR_NONE;
    2692             : }
    2693             : 
    2694             : /************************************************************************/
    2695             : /*                         OSRSetAngularUnits()                         */
    2696             : /************************************************************************/
    2697             : 
    2698             : /**
    2699             :  * \brief Set the angular units for the geographic coordinate system.
    2700             :  *
    2701             :  * This function is the same as OGRSpatialReference::SetAngularUnits()
    2702             :  */
    2703          49 : OGRErr OSRSetAngularUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2704             :                           double dfInRadians)
    2705             : 
    2706             : {
    2707          49 :     VALIDATE_POINTER1(hSRS, "OSRSetAngularUnits", OGRERR_FAILURE);
    2708             : 
    2709          49 :     return ToPointer(hSRS)->SetAngularUnits(pszUnits, dfInRadians);
    2710             : }
    2711             : 
    2712             : /************************************************************************/
    2713             : /*                          GetAngularUnits()                           */
    2714             : /************************************************************************/
    2715             : 
    2716             : /**
    2717             :  * \brief Fetch angular geographic coordinate system units.
    2718             :  *
    2719             :  * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
    2720             :  * will be assumed.  This method only checks directly under the GEOGCS node
    2721             :  * for units.
    2722             :  *
    2723             :  * This method does the same thing as the C function OSRGetAngularUnits().
    2724             :  *
    2725             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    2726             :  * The returned value remains internal to the OGRSpatialReference and should
    2727             :  * not be freed, or modified.  It may be invalidated on the next
    2728             :  * OGRSpatialReference call.
    2729             :  *
    2730             :  * @return the value to multiply by angular distances to transform them to
    2731             :  * radians.
    2732             :  */
    2733             : 
    2734        8853 : double OGRSpatialReference::GetAngularUnits(const char **ppszName) const
    2735             : 
    2736             : {
    2737       17706 :     TAKE_OPTIONAL_LOCK();
    2738             : 
    2739        8853 :     d->refreshProjObj();
    2740             : 
    2741        8853 :     if (!d->m_osAngularUnits.empty())
    2742             :     {
    2743        2429 :         if (ppszName != nullptr)
    2744         185 :             *ppszName = d->m_osAngularUnits.c_str();
    2745        2429 :         return d->m_dfAngularUnitToRadian;
    2746             :     }
    2747             : 
    2748             :     do
    2749             :     {
    2750        6424 :         if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    2751             :         {
    2752         113 :             break;
    2753             :         }
    2754             : 
    2755             :         auto geodCRS =
    2756        6313 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2757        6313 :         if (!geodCRS)
    2758             :         {
    2759           0 :             break;
    2760             :         }
    2761             :         auto coordSys =
    2762        6313 :             proj_crs_get_coordinate_system(d->getPROJContext(), geodCRS);
    2763        6313 :         proj_destroy(geodCRS);
    2764        6313 :         if (!coordSys)
    2765             :         {
    2766           0 :             break;
    2767             :         }
    2768        6313 :         if (proj_cs_get_type(d->getPROJContext(), coordSys) !=
    2769             :             PJ_CS_TYPE_ELLIPSOIDAL)
    2770             :         {
    2771           2 :             proj_destroy(coordSys);
    2772           2 :             break;
    2773             :         }
    2774             : 
    2775        6311 :         double dfConvFactor = 0.0;
    2776        6311 :         const char *pszUnitName = nullptr;
    2777        6311 :         if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
    2778             :                                    nullptr, nullptr, &dfConvFactor,
    2779             :                                    &pszUnitName, nullptr, nullptr))
    2780             :         {
    2781           0 :             proj_destroy(coordSys);
    2782           0 :             break;
    2783             :         }
    2784             : 
    2785        6311 :         d->m_osAngularUnits = pszUnitName;
    2786             : 
    2787        6311 :         proj_destroy(coordSys);
    2788        6311 :         d->m_dfAngularUnitToRadian = dfConvFactor;
    2789             :     } while (false);
    2790             : 
    2791        6424 :     if (d->m_osAngularUnits.empty())
    2792             :     {
    2793         113 :         d->m_osAngularUnits = "degree";
    2794         113 :         d->m_dfAngularUnitToRadian = CPLAtof(SRS_UA_DEGREE_CONV);
    2795             :     }
    2796             : 
    2797        6424 :     if (ppszName != nullptr)
    2798        3291 :         *ppszName = d->m_osAngularUnits.c_str();
    2799        6424 :     return d->m_dfAngularUnitToRadian;
    2800             : }
    2801             : 
    2802             : /**
    2803             :  * \brief Fetch angular geographic coordinate system units.
    2804             :  *
    2805             :  * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
    2806             :  * will be assumed.  This method only checks directly under the GEOGCS node
    2807             :  * for units.
    2808             :  *
    2809             :  * This method does the same thing as the C function OSRGetAngularUnits().
    2810             :  *
    2811             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    2812             :  * The returned value remains internal to the OGRSpatialReference and should
    2813             :  * not be freed, or modified.  It may be invalidated on the next
    2814             :  * OGRSpatialReference call.
    2815             :  *
    2816             :  * @return the value to multiply by angular distances to transform them to
    2817             :  * radians.
    2818             :  * @deprecated Use GetAngularUnits(const char**) const.
    2819             :  */
    2820             : 
    2821           0 : double OGRSpatialReference::GetAngularUnits(char **ppszName) const
    2822             : 
    2823             : {
    2824           0 :     return GetAngularUnits(const_cast<const char **>(ppszName));
    2825             : }
    2826             : 
    2827             : /************************************************************************/
    2828             : /*                         OSRGetAngularUnits()                         */
    2829             : /************************************************************************/
    2830             : 
    2831             : /**
    2832             :  * \brief Fetch angular geographic coordinate system units.
    2833             :  *
    2834             :  * This function is the same as OGRSpatialReference::GetAngularUnits()
    2835             :  */
    2836           1 : double OSRGetAngularUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    2837             : 
    2838             : {
    2839           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAngularUnits", 0);
    2840             : 
    2841           1 :     return ToPointer(hSRS)->GetAngularUnits(
    2842           1 :         const_cast<const char **>(ppszName));
    2843             : }
    2844             : 
    2845             : /************************************************************************/
    2846             : /*                 SetLinearUnitsAndUpdateParameters()                  */
    2847             : /************************************************************************/
    2848             : 
    2849             : /**
    2850             :  * \brief Set the linear units for the projection.
    2851             :  *
    2852             :  * This method creates a UNIT subnode with the specified values as a
    2853             :  * child of the PROJCS or LOCAL_CS node.   It works the same as the
    2854             :  * SetLinearUnits() method, but it also updates all existing linear
    2855             :  * projection parameter values from the old units to the new units.
    2856             :  *
    2857             :  * @param pszName the units name to be used.  Some preferred units
    2858             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2859             :  * and SRS_UL_US_FOOT.
    2860             :  *
    2861             :  * @param dfInMeters the value to multiple by a length in the indicated
    2862             :  * units to transform to meters.  Some standard conversion factors can
    2863             :  * be found in ogr_srs_api.h.
    2864             :  *
    2865             :  * @param pszUnitAuthority Unit authority name. Or nullptr
    2866             :  *
    2867             :  * @param pszUnitCode Unit code. Or nullptr
    2868             :  *
    2869             :  * @return OGRERR_NONE on success.
    2870             :  */
    2871             : 
    2872          38 : OGRErr OGRSpatialReference::SetLinearUnitsAndUpdateParameters(
    2873             :     const char *pszName, double dfInMeters, const char *pszUnitAuthority,
    2874             :     const char *pszUnitCode)
    2875             : 
    2876             : {
    2877          76 :     TAKE_OPTIONAL_LOCK();
    2878             : 
    2879          38 :     if (dfInMeters <= 0.0)
    2880           0 :         return OGRERR_FAILURE;
    2881             : 
    2882          38 :     d->refreshProjObj();
    2883          38 :     if (!d->m_pj_crs)
    2884           0 :         return OGRERR_FAILURE;
    2885             : 
    2886          38 :     d->demoteFromBoundCRS();
    2887          38 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    2888             :     {
    2889          76 :         d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    2890          38 :             d->getPROJContext(), d->m_pj_crs, pszName, dfInMeters,
    2891             :             pszUnitAuthority, pszUnitCode, true));
    2892             :     }
    2893          38 :     d->setPjCRS(proj_crs_alter_cs_linear_unit(d->getPROJContext(), d->m_pj_crs,
    2894             :                                               pszName, dfInMeters,
    2895             :                                               pszUnitAuthority, pszUnitCode));
    2896          38 :     d->undoDemoteFromBoundCRS();
    2897             : 
    2898          38 :     d->m_osLinearUnits = pszName;
    2899          38 :     d->dfToMeter = dfInMeters;
    2900             : 
    2901          38 :     return OGRERR_NONE;
    2902             : }
    2903             : 
    2904             : /************************************************************************/
    2905             : /*                OSRSetLinearUnitsAndUpdateParameters()                */
    2906             : /************************************************************************/
    2907             : 
    2908             : /**
    2909             :  * \brief Set the linear units for the projection.
    2910             :  *
    2911             :  * This function is the same as
    2912             :  *   OGRSpatialReference::SetLinearUnitsAndUpdateParameters()
    2913             :  */
    2914           1 : OGRErr OSRSetLinearUnitsAndUpdateParameters(OGRSpatialReferenceH hSRS,
    2915             :                                             const char *pszUnits,
    2916             :                                             double dfInMeters)
    2917             : 
    2918             : {
    2919           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnitsAndUpdateParameters",
    2920             :                       OGRERR_FAILURE);
    2921             : 
    2922           1 :     return ToPointer(hSRS)->SetLinearUnitsAndUpdateParameters(pszUnits,
    2923           1 :                                                               dfInMeters);
    2924             : }
    2925             : 
    2926             : /************************************************************************/
    2927             : /*                           SetLinearUnits()                           */
    2928             : /************************************************************************/
    2929             : 
    2930             : /**
    2931             :  * \brief Set the linear units for the projection.
    2932             :  *
    2933             :  * This method creates a UNIT subnode with the specified values as a
    2934             :  * child of the PROJCS, GEOCCS, GEOGCS or LOCAL_CS node. When called on a
    2935             :  * Geographic 3D CRS the vertical axis units will be set.
    2936             :  *
    2937             :  * This method does the same as the C function OSRSetLinearUnits().
    2938             :  *
    2939             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2940             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2941             :  * and SRS_UL_US_FOOT.
    2942             :  *
    2943             :  * @param dfInMeters the value to multiple by a length in the indicated
    2944             :  * units to transform to meters.  Some standard conversion factors can
    2945             :  * be found in ogr_srs_api.h.
    2946             :  *
    2947             :  * @return OGRERR_NONE on success.
    2948             :  */
    2949             : 
    2950        7483 : OGRErr OGRSpatialReference::SetLinearUnits(const char *pszUnitsName,
    2951             :                                            double dfInMeters)
    2952             : 
    2953             : {
    2954        7483 :     return SetTargetLinearUnits(nullptr, pszUnitsName, dfInMeters);
    2955             : }
    2956             : 
    2957             : /************************************************************************/
    2958             : /*                         OSRSetLinearUnits()                          */
    2959             : /************************************************************************/
    2960             : 
    2961             : /**
    2962             :  * \brief Set the linear units for the projection.
    2963             :  *
    2964             :  * This function is the same as OGRSpatialReference::SetLinearUnits()
    2965             :  */
    2966           7 : OGRErr OSRSetLinearUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2967             :                          double dfInMeters)
    2968             : 
    2969             : {
    2970           7 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnits", OGRERR_FAILURE);
    2971             : 
    2972           7 :     return ToPointer(hSRS)->SetLinearUnits(pszUnits, dfInMeters);
    2973             : }
    2974             : 
    2975             : /************************************************************************/
    2976             : /*                        SetTargetLinearUnits()                        */
    2977             : /************************************************************************/
    2978             : 
    2979             : /**
    2980             :  * \brief Set the linear units for the projection.
    2981             :  *
    2982             :  * This method creates a UNIT subnode with the specified values as a
    2983             :  * child of the target node.
    2984             :  *
    2985             :  * This method does the same as the C function OSRSetTargetLinearUnits().
    2986             :  *
    2987             :  * @param pszTargetKey the keyword to set the linear units for.
    2988             :  * i.e. "PROJCS" or "VERT_CS"
    2989             :  *
    2990             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2991             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2992             :  * and SRS_UL_US_FOOT.
    2993             :  *
    2994             :  * @param dfInMeters the value to multiple by a length in the indicated
    2995             :  * units to transform to meters.  Some standard conversion factors can
    2996             :  * be found in ogr_srs_api.h.
    2997             :  *
    2998             :  * @param pszUnitAuthority Unit authority name. Or nullptr
    2999             :  *
    3000             :  * @param pszUnitCode Unit code. Or nullptr
    3001             :  *
    3002             :  * @return OGRERR_NONE on success.
    3003             :  *
    3004             :  */
    3005             : 
    3006       12013 : OGRErr OGRSpatialReference::SetTargetLinearUnits(const char *pszTargetKey,
    3007             :                                                  const char *pszUnitsName,
    3008             :                                                  double dfInMeters,
    3009             :                                                  const char *pszUnitAuthority,
    3010             :                                                  const char *pszUnitCode)
    3011             : 
    3012             : {
    3013       24026 :     TAKE_OPTIONAL_LOCK();
    3014             : 
    3015       12013 :     if (dfInMeters <= 0.0)
    3016           0 :         return OGRERR_FAILURE;
    3017             : 
    3018       12013 :     d->refreshProjObj();
    3019       12013 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    3020       12013 :     if (pszTargetKey == nullptr)
    3021             :     {
    3022       12013 :         if (!d->m_pj_crs)
    3023           0 :             return OGRERR_FAILURE;
    3024             : 
    3025       12013 :         d->demoteFromBoundCRS();
    3026       12013 :         if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3027             :         {
    3028       18224 :             d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    3029        9112 :                 d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    3030             :                 pszUnitAuthority, pszUnitCode, false));
    3031             :         }
    3032       24026 :         d->setPjCRS(proj_crs_alter_cs_linear_unit(
    3033       12013 :             d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    3034             :             pszUnitAuthority, pszUnitCode));
    3035       12013 :         d->undoDemoteFromBoundCRS();
    3036             : 
    3037       12013 :         d->m_osLinearUnits = pszUnitsName;
    3038       12013 :         d->dfToMeter = dfInMeters;
    3039             : 
    3040       12013 :         return OGRERR_NONE;
    3041             :     }
    3042             : 
    3043           0 :     OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
    3044             : 
    3045           0 :     if (poCS == nullptr)
    3046           0 :         return OGRERR_FAILURE;
    3047             : 
    3048           0 :     char szValue[128] = {'\0'};
    3049           0 :     if (dfInMeters < std::numeric_limits<int>::max() &&
    3050           0 :         dfInMeters > std::numeric_limits<int>::min() &&
    3051           0 :         dfInMeters == static_cast<int>(dfInMeters))
    3052           0 :         snprintf(szValue, sizeof(szValue), "%d", static_cast<int>(dfInMeters));
    3053             :     else
    3054           0 :         OGRsnPrintDouble(szValue, sizeof(szValue), dfInMeters);
    3055             : 
    3056           0 :     OGR_SRSNode *poUnits = nullptr;
    3057           0 :     if (poCS->FindChild("UNIT") >= 0)
    3058             :     {
    3059           0 :         poUnits = poCS->GetChild(poCS->FindChild("UNIT"));
    3060           0 :         if (poUnits->GetChildCount() < 2)
    3061           0 :             return OGRERR_FAILURE;
    3062           0 :         poUnits->GetChild(0)->SetValue(pszUnitsName);
    3063           0 :         poUnits->GetChild(1)->SetValue(szValue);
    3064           0 :         if (poUnits->FindChild("AUTHORITY") != -1)
    3065           0 :             poUnits->DestroyChild(poUnits->FindChild("AUTHORITY"));
    3066             :     }
    3067             :     else
    3068             :     {
    3069           0 :         poUnits = new OGR_SRSNode("UNIT");
    3070           0 :         poUnits->AddChild(new OGR_SRSNode(pszUnitsName));
    3071           0 :         poUnits->AddChild(new OGR_SRSNode(szValue));
    3072             : 
    3073           0 :         poCS->AddChild(poUnits);
    3074             :     }
    3075             : 
    3076           0 :     return OGRERR_NONE;
    3077             : }
    3078             : 
    3079             : /************************************************************************/
    3080             : /*                         OSRSetLinearUnits()                          */
    3081             : /************************************************************************/
    3082             : 
    3083             : /**
    3084             :  * \brief Set the linear units for the target node.
    3085             :  *
    3086             :  * This function is the same as OGRSpatialReference::SetTargetLinearUnits()
    3087             :  *
    3088             :  */
    3089           1 : OGRErr OSRSetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3090             :                                const char *pszTargetKey, const char *pszUnits,
    3091             :                                double dfInMeters)
    3092             : 
    3093             : {
    3094           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTargetLinearUnits", OGRERR_FAILURE);
    3095             : 
    3096           1 :     return ToPointer(hSRS)->SetTargetLinearUnits(pszTargetKey, pszUnits,
    3097           1 :                                                  dfInMeters);
    3098             : }
    3099             : 
    3100             : /************************************************************************/
    3101             : /*                           GetLinearUnits()                           */
    3102             : /************************************************************************/
    3103             : 
    3104             : /**
    3105             :  * \brief Fetch linear projection units.
    3106             :  *
    3107             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3108             :  * This method only checks directly under the PROJCS, GEOCCS, GEOGCS or
    3109             :  * LOCAL_CS node for units. When called on a Geographic 3D CRS the vertical
    3110             :  * axis units will be returned.
    3111             :  *
    3112             :  * This method does the same thing as the C function OSRGetLinearUnits()
    3113             :  *
    3114             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3115             :  * The returned value remains internal to the OGRSpatialReference and should
    3116             :  * not be freed, or modified.  It may be invalidated on the next
    3117             :  * OGRSpatialReference call.
    3118             :  *
    3119             :  * @return the value to multiply by linear distances to transform them to
    3120             :  * meters.
    3121             :  * @deprecated Use GetLinearUnits(const char**) const.
    3122             :  */
    3123             : 
    3124           0 : double OGRSpatialReference::GetLinearUnits(char **ppszName) const
    3125             : 
    3126             : {
    3127           0 :     return GetTargetLinearUnits(nullptr, const_cast<const char **>(ppszName));
    3128             : }
    3129             : 
    3130             : /**
    3131             :  * \brief Fetch linear projection units.
    3132             :  *
    3133             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3134             :  * This method only checks directly under the PROJCS, GEOCCS or LOCAL_CS node
    3135             :  * for units.
    3136             :  *
    3137             :  * This method does the same thing as the C function OSRGetLinearUnits()
    3138             :  *
    3139             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3140             :  * The returned value remains internal to the OGRSpatialReference and should
    3141             :  * not be freed, or modified.  It may be invalidated on the next
    3142             :  * OGRSpatialReference call.
    3143             :  *
    3144             :  * @return the value to multiply by linear distances to transform them to
    3145             :  * meters.
    3146             :  */
    3147             : 
    3148       21876 : double OGRSpatialReference::GetLinearUnits(const char **ppszName) const
    3149             : 
    3150             : {
    3151       21876 :     return GetTargetLinearUnits(nullptr, ppszName);
    3152             : }
    3153             : 
    3154             : /************************************************************************/
    3155             : /*                         OSRGetLinearUnits()                          */
    3156             : /************************************************************************/
    3157             : 
    3158             : /**
    3159             :  * \brief Fetch linear projection units.
    3160             :  *
    3161             :  * This function is the same as OGRSpatialReference::GetLinearUnits()
    3162             :  */
    3163         255 : double OSRGetLinearUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    3164             : 
    3165             : {
    3166         255 :     VALIDATE_POINTER1(hSRS, "OSRGetLinearUnits", 0);
    3167             : 
    3168         255 :     return ToPointer(hSRS)->GetLinearUnits(const_cast<const char **>(ppszName));
    3169             : }
    3170             : 
    3171             : /************************************************************************/
    3172             : /*                        GetTargetLinearUnits()                        */
    3173             : /************************************************************************/
    3174             : 
    3175             : /**
    3176             :  * \brief Fetch linear units for target.
    3177             :  *
    3178             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3179             :  *
    3180             :  * This method does the same thing as the C function OSRGetTargetLinearUnits()
    3181             :  *
    3182             :  * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
    3183             :  * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
    3184             :  * GEOCCS, GEOGCS and VERT_CS are looked up)
    3185             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3186             :  * The returned value remains internal to the OGRSpatialReference and should not
    3187             :  * be freed, or modified.  It may be invalidated on the next
    3188             :  * OGRSpatialReference call. ppszName can be set to NULL.
    3189             :  *
    3190             :  * @return the value to multiply by linear distances to transform them to
    3191             :  * meters.
    3192             :  *
    3193             :  * @deprecated Use GetTargetLinearUnits(const char*, const char**)
    3194             :  * const.
    3195             :  */
    3196             : 
    3197       22030 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3198             :                                                  const char **ppszName) const
    3199             : 
    3200             : {
    3201       44060 :     TAKE_OPTIONAL_LOCK();
    3202             : 
    3203       22030 :     d->refreshProjObj();
    3204             : 
    3205       22030 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    3206       22030 :     if (pszTargetKey == nullptr)
    3207             :     {
    3208             :         // Use cached result if available
    3209       21938 :         if (!d->m_osLinearUnits.empty())
    3210             :         {
    3211        9285 :             if (ppszName)
    3212        8407 :                 *ppszName = d->m_osLinearUnits.c_str();
    3213        9285 :             return d->dfToMeter;
    3214             :         }
    3215             : 
    3216             :         while (true)
    3217             :         {
    3218       12653 :             if (d->m_pj_crs == nullptr)
    3219             :             {
    3220         244 :                 break;
    3221             :             }
    3222             : 
    3223       12409 :             d->demoteFromBoundCRS();
    3224       12409 :             PJ *coordSys = nullptr;
    3225       12409 :             if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    3226             :             {
    3227          37 :                 for (int iComponent = 0; iComponent < 2; iComponent++)
    3228             :                 {
    3229          37 :                     auto subCRS = proj_crs_get_sub_crs(d->getPROJContext(),
    3230          37 :                                                        d->m_pj_crs, iComponent);
    3231          37 :                     if (subCRS && proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
    3232             :                     {
    3233             :                         auto temp =
    3234           0 :                             proj_get_source_crs(d->getPROJContext(), subCRS);
    3235           0 :                         proj_destroy(subCRS);
    3236           0 :                         subCRS = temp;
    3237             :                     }
    3238          74 :                     if (subCRS &&
    3239          37 :                         (proj_get_type(subCRS) == PJ_TYPE_PROJECTED_CRS ||
    3240          16 :                          proj_get_type(subCRS) == PJ_TYPE_ENGINEERING_CRS ||
    3241          12 :                          proj_get_type(subCRS) == PJ_TYPE_VERTICAL_CRS))
    3242             :                     {
    3243          31 :                         coordSys = proj_crs_get_coordinate_system(
    3244             :                             d->getPROJContext(), subCRS);
    3245          31 :                         proj_destroy(subCRS);
    3246          31 :                         break;
    3247             :                     }
    3248           6 :                     else if (subCRS)
    3249             :                     {
    3250           6 :                         proj_destroy(subCRS);
    3251             :                     }
    3252             :                 }
    3253          31 :                 if (coordSys == nullptr)
    3254             :                 {
    3255           0 :                     d->undoDemoteFromBoundCRS();
    3256           0 :                     break;
    3257             :                 }
    3258             :             }
    3259             :             else
    3260             :             {
    3261       12378 :                 coordSys = proj_crs_get_coordinate_system(d->getPROJContext(),
    3262       12378 :                                                           d->m_pj_crs);
    3263             :             }
    3264             : 
    3265       12409 :             d->undoDemoteFromBoundCRS();
    3266       12409 :             if (!coordSys)
    3267             :             {
    3268           0 :                 break;
    3269             :             }
    3270       12409 :             auto csType = proj_cs_get_type(d->getPROJContext(), coordSys);
    3271             : 
    3272       12409 :             if (csType != PJ_CS_TYPE_CARTESIAN &&
    3273        2296 :                 csType != PJ_CS_TYPE_VERTICAL &&
    3274           0 :                 csType != PJ_CS_TYPE_ELLIPSOIDAL &&
    3275             :                 csType != PJ_CS_TYPE_SPHERICAL)
    3276             :             {
    3277           0 :                 proj_destroy(coordSys);
    3278           0 :                 break;
    3279             :             }
    3280             : 
    3281       12409 :             int axis = 0;
    3282             : 
    3283       12409 :             if (csType == PJ_CS_TYPE_ELLIPSOIDAL ||
    3284             :                 csType == PJ_CS_TYPE_SPHERICAL)
    3285             :             {
    3286             :                 const int axisCount =
    3287        2296 :                     proj_cs_get_axis_count(d->getPROJContext(), coordSys);
    3288             : 
    3289        2296 :                 if (axisCount == 3)
    3290             :                 {
    3291           4 :                     axis = 2;
    3292             :                 }
    3293             :                 else
    3294             :                 {
    3295        2292 :                     proj_destroy(coordSys);
    3296        2292 :                     break;
    3297             :                 }
    3298             :             }
    3299             : 
    3300       10117 :             double dfConvFactor = 0.0;
    3301       10117 :             const char *pszUnitName = nullptr;
    3302       10117 :             if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, axis,
    3303             :                                        nullptr, nullptr, nullptr, &dfConvFactor,
    3304             :                                        &pszUnitName, nullptr, nullptr))
    3305             :             {
    3306           0 :                 proj_destroy(coordSys);
    3307           0 :                 break;
    3308             :             }
    3309             : 
    3310       10117 :             d->m_osLinearUnits = pszUnitName;
    3311       10117 :             d->dfToMeter = dfConvFactor;
    3312       10117 :             if (ppszName)
    3313        1305 :                 *ppszName = d->m_osLinearUnits.c_str();
    3314             : 
    3315       10117 :             proj_destroy(coordSys);
    3316       10117 :             return dfConvFactor;
    3317             :         }
    3318             : 
    3319        2536 :         d->m_osLinearUnits = "unknown";
    3320        2536 :         d->dfToMeter = 1.0;
    3321             : 
    3322        2536 :         if (ppszName != nullptr)
    3323        2350 :             *ppszName = d->m_osLinearUnits.c_str();
    3324        2536 :         return 1.0;
    3325             :     }
    3326             : 
    3327          92 :     const OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
    3328             : 
    3329          92 :     if (ppszName != nullptr)
    3330          38 :         *ppszName = "unknown";
    3331             : 
    3332          92 :     if (poCS == nullptr)
    3333          53 :         return 1.0;
    3334             : 
    3335         117 :     for (int iChild = 0; iChild < poCS->GetChildCount(); iChild++)
    3336             :     {
    3337         117 :         const OGR_SRSNode *poChild = poCS->GetChild(iChild);
    3338             : 
    3339         117 :         if (EQUAL(poChild->GetValue(), "UNIT") && poChild->GetChildCount() >= 2)
    3340             :         {
    3341          39 :             if (ppszName != nullptr)
    3342          38 :                 *ppszName = poChild->GetChild(0)->GetValue();
    3343             : 
    3344          39 :             return CPLAtof(poChild->GetChild(1)->GetValue());
    3345             :         }
    3346             :     }
    3347             : 
    3348           0 :     return 1.0;
    3349             : }
    3350             : 
    3351             : /**
    3352             :  * \brief Fetch linear units for target.
    3353             :  *
    3354             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3355             :  *
    3356             :  * This method does the same thing as the C function OSRGetTargetLinearUnits()
    3357             :  *
    3358             :  * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
    3359             :  * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
    3360             :  * GEOCCS and VERT_CS are looked up)
    3361             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3362             :  * The returned value remains internal to the OGRSpatialReference and should not
    3363             :  * be freed, or modified.  It may be invalidated on the next
    3364             :  * OGRSpatialReference call. ppszName can be set to NULL.
    3365             :  *
    3366             :  * @return the value to multiply by linear distances to transform them to
    3367             :  * meters.
    3368             :  *
    3369             :  */
    3370             : 
    3371           0 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3372             :                                                  char **ppszName) const
    3373             : 
    3374             : {
    3375           0 :     return GetTargetLinearUnits(pszTargetKey,
    3376           0 :                                 const_cast<const char **>(ppszName));
    3377             : }
    3378             : 
    3379             : /************************************************************************/
    3380             : /*                      OSRGetTargetLinearUnits()                       */
    3381             : /************************************************************************/
    3382             : 
    3383             : /**
    3384             :  * \brief Fetch linear projection units.
    3385             :  *
    3386             :  * This function is the same as OGRSpatialReference::GetTargetLinearUnits()
    3387             :  *
    3388             :  */
    3389           4 : double OSRGetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3390             :                                const char *pszTargetKey, char **ppszName)
    3391             : 
    3392             : {
    3393           4 :     VALIDATE_POINTER1(hSRS, "OSRGetTargetLinearUnits", 0);
    3394             : 
    3395           4 :     return ToPointer(hSRS)->GetTargetLinearUnits(
    3396           4 :         pszTargetKey, const_cast<const char **>(ppszName));
    3397             : }
    3398             : 
    3399             : /************************************************************************/
    3400             : /*                          GetPrimeMeridian()                          */
    3401             : /************************************************************************/
    3402             : 
    3403             : /**
    3404             :  * \brief Fetch prime meridian info.
    3405             :  *
    3406             :  * Returns the offset of the prime meridian from greenwich in degrees,
    3407             :  * and the prime meridian name (if requested).   If no PRIMEM value exists
    3408             :  * in the coordinate system definition a value of "Greenwich" and an
    3409             :  * offset of 0.0 is assumed.
    3410             :  *
    3411             :  * If the prime meridian name is returned, the pointer is to an internal
    3412             :  * copy of the name. It should not be freed, altered or depended on after
    3413             :  * the next OGR call.
    3414             :  *
    3415             :  * This method is the same as the C function OSRGetPrimeMeridian().
    3416             :  *
    3417             :  * @param ppszName return location for prime meridian name.  If NULL, name
    3418             :  * is not returned.
    3419             :  *
    3420             :  * @return the offset to the GEOGCS prime meridian from greenwich in decimal
    3421             :  * degrees.
    3422             :  * @deprecated Use GetPrimeMeridian(const char**) const.
    3423             :  */
    3424             : 
    3425        1550 : double OGRSpatialReference::GetPrimeMeridian(const char **ppszName) const
    3426             : 
    3427             : {
    3428        3100 :     TAKE_OPTIONAL_LOCK();
    3429             : 
    3430        1550 :     d->refreshProjObj();
    3431             : 
    3432        1550 :     if (!d->m_osPrimeMeridianName.empty())
    3433             :     {
    3434          87 :         if (ppszName != nullptr)
    3435          11 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3436          87 :         return d->dfFromGreenwich;
    3437             :     }
    3438             : 
    3439             :     while (true)
    3440             :     {
    3441        1463 :         if (!d->m_pj_crs)
    3442           0 :             break;
    3443             : 
    3444        1463 :         auto pm = proj_get_prime_meridian(d->getPROJContext(), d->m_pj_crs);
    3445        1463 :         if (!pm)
    3446           0 :             break;
    3447             : 
    3448        1463 :         d->m_osPrimeMeridianName = proj_get_name(pm);
    3449        1463 :         if (ppszName)
    3450          20 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3451        1463 :         double dfLongitude = 0.0;
    3452        1463 :         double dfConvFactor = 0.0;
    3453        1463 :         proj_prime_meridian_get_parameters(
    3454             :             d->getPROJContext(), pm, &dfLongitude, &dfConvFactor, nullptr);
    3455        1463 :         proj_destroy(pm);
    3456        2926 :         d->dfFromGreenwich =
    3457        1463 :             dfLongitude * dfConvFactor / CPLAtof(SRS_UA_DEGREE_CONV);
    3458        1463 :         return d->dfFromGreenwich;
    3459             :     }
    3460             : 
    3461           0 :     d->m_osPrimeMeridianName = SRS_PM_GREENWICH;
    3462           0 :     d->dfFromGreenwich = 0.0;
    3463           0 :     if (ppszName != nullptr)
    3464           0 :         *ppszName = d->m_osPrimeMeridianName.c_str();
    3465           0 :     return d->dfFromGreenwich;
    3466             : }
    3467             : 
    3468             : /**
    3469             :  * \brief Fetch prime meridian info.
    3470             :  *
    3471             :  * Returns the offset of the prime meridian from greenwich in degrees,
    3472             :  * and the prime meridian name (if requested).   If no PRIMEM value exists
    3473             :  * in the coordinate system definition a value of "Greenwich" and an
    3474             :  * offset of 0.0 is assumed.
    3475             :  *
    3476             :  * If the prime meridian name is returned, the pointer is to an internal
    3477             :  * copy of the name. It should not be freed, altered or depended on after
    3478             :  * the next OGR call.
    3479             :  *
    3480             :  * This method is the same as the C function OSRGetPrimeMeridian().
    3481             :  *
    3482             :  * @param ppszName return location for prime meridian name.  If NULL, name
    3483             :  * is not returned.
    3484             :  *
    3485             :  * @return the offset to the GEOGCS prime meridian from greenwich in decimal
    3486             :  * degrees.
    3487             :  */
    3488             : 
    3489           0 : double OGRSpatialReference::GetPrimeMeridian(char **ppszName) const
    3490             : 
    3491             : {
    3492           0 :     return GetPrimeMeridian(const_cast<const char **>(ppszName));
    3493             : }
    3494             : 
    3495             : /************************************************************************/
    3496             : /*                        OSRGetPrimeMeridian()                         */
    3497             : /************************************************************************/
    3498             : 
    3499             : /**
    3500             :  * \brief Fetch prime meridian info.
    3501             :  *
    3502             :  * This function is the same as OGRSpatialReference::GetPrimeMeridian()
    3503             :  */
    3504           0 : double OSRGetPrimeMeridian(OGRSpatialReferenceH hSRS, char **ppszName)
    3505             : 
    3506             : {
    3507           0 :     VALIDATE_POINTER1(hSRS, "OSRGetPrimeMeridian", 0);
    3508             : 
    3509           0 :     return ToPointer(hSRS)->GetPrimeMeridian(
    3510           0 :         const_cast<const char **>(ppszName));
    3511             : }
    3512             : 
    3513             : /************************************************************************/
    3514             : /*                             SetGeogCS()                              */
    3515             : /************************************************************************/
    3516             : 
    3517             : /**
    3518             :  * \brief Set geographic coordinate system.
    3519             :  *
    3520             :  * This method is used to set the datum, ellipsoid, prime meridian and
    3521             :  * angular units for a geographic coordinate system.  It can be used on its
    3522             :  * own to establish a geographic spatial reference, or applied to a
    3523             :  * projected coordinate system to establish the underlying geographic
    3524             :  * coordinate system.
    3525             :  *
    3526             :  * This method does the same as the C function OSRSetGeogCS().
    3527             :  *
    3528             :  * @param pszGeogName user visible name for the geographic coordinate system
    3529             :  * (not to serve as a key).
    3530             :  *
    3531             :  * @param pszDatumName key name for this datum.  The OpenGIS specification
    3532             :  * lists some known values, and otherwise EPSG datum names with a standard
    3533             :  * transformation are considered legal keys.
    3534             :  *
    3535             :  * @param pszSpheroidName user visible spheroid name (not to serve as a key)
    3536             :  *
    3537             :  * @param dfSemiMajor the semi major axis of the spheroid.
    3538             :  *
    3539             :  * @param dfInvFlattening the inverse flattening for the spheroid.
    3540             :  * This can be computed from the semi minor axis as
    3541             :  * 1/f = 1.0 / (1.0 - semiminor/semimajor).
    3542             :  *
    3543             :  * @param pszPMName the name of the prime meridian (not to serve as a key)
    3544             :  * If this is NULL a default value of "Greenwich" will be used.
    3545             :  *
    3546             :  * @param dfPMOffset the longitude of Greenwich relative to this prime
    3547             :  * meridian. Always in Degrees
    3548             :  *
    3549             :  * @param pszAngularUnits the angular units name (see ogr_srs_api.h for some
    3550             :  * standard names).  If NULL a value of "degrees" will be assumed.
    3551             :  *
    3552             :  * @param dfConvertToRadians value to multiply angular units by to transform
    3553             :  * them to radians.  A value of SRS_UA_DEGREE_CONV will be used if
    3554             :  * pszAngularUnits is NULL.
    3555             :  *
    3556             :  * @return OGRERR_NONE on success.
    3557             :  */
    3558             : 
    3559        9831 : OGRErr OGRSpatialReference::SetGeogCS(
    3560             :     const char *pszGeogName, const char *pszDatumName,
    3561             :     const char *pszSpheroidName, double dfSemiMajor, double dfInvFlattening,
    3562             :     const char *pszPMName, double dfPMOffset, const char *pszAngularUnits,
    3563             :     double dfConvertToRadians)
    3564             : 
    3565             : {
    3566       19662 :     TAKE_OPTIONAL_LOCK();
    3567             : 
    3568        9831 :     d->bNormInfoSet = FALSE;
    3569        9831 :     d->m_osAngularUnits.clear();
    3570        9831 :     d->m_dfAngularUnitToRadian = 0.0;
    3571        9831 :     d->m_osPrimeMeridianName.clear();
    3572        9831 :     d->dfFromGreenwich = 0.0;
    3573             : 
    3574             :     /* -------------------------------------------------------------------- */
    3575             :     /*      For a geocentric coordinate system we want to set the datum     */
    3576             :     /*      and ellipsoid based on the GEOGCS.  Create the GEOGCS in a      */
    3577             :     /*      temporary srs and use the copy method which has special         */
    3578             :     /*      handling for GEOCCS.                                            */
    3579             :     /* -------------------------------------------------------------------- */
    3580        9831 :     if (IsGeocentric())
    3581             :     {
    3582           4 :         OGRSpatialReference oGCS;
    3583             : 
    3584           2 :         oGCS.SetGeogCS(pszGeogName, pszDatumName, pszSpheroidName, dfSemiMajor,
    3585             :                        dfInvFlattening, pszPMName, dfPMOffset, pszAngularUnits,
    3586             :                        dfConvertToRadians);
    3587           2 :         return CopyGeogCSFrom(&oGCS);
    3588             :     }
    3589             : 
    3590        9829 :     auto cs = proj_create_ellipsoidal_2D_cs(
    3591             :         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE, pszAngularUnits,
    3592             :         dfConvertToRadians);
    3593             :     // Prime meridian expressed in Degree
    3594        9829 :     auto obj = proj_create_geographic_crs(
    3595             :         d->getPROJContext(), pszGeogName, pszDatumName, pszSpheroidName,
    3596             :         dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset, nullptr, 0.0, cs);
    3597        9829 :     proj_destroy(cs);
    3598             : 
    3599       14910 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    3600        5081 :         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    3601             :     {
    3602        4748 :         d->setPjCRS(obj);
    3603             :     }
    3604        5081 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3605             :     {
    3606       10162 :         d->setPjCRS(
    3607        5081 :             proj_crs_alter_geodetic_crs(d->getPROJContext(), d->m_pj_crs, obj));
    3608        5081 :         proj_destroy(obj);
    3609             :     }
    3610             :     else
    3611             :     {
    3612           0 :         proj_destroy(obj);
    3613             :     }
    3614             : 
    3615        9829 :     return OGRERR_NONE;
    3616             : }
    3617             : 
    3618             : /************************************************************************/
    3619             : /*                            OSRSetGeogCS()                            */
    3620             : /************************************************************************/
    3621             : 
    3622             : /**
    3623             :  * \brief Set geographic coordinate system.
    3624             :  *
    3625             :  * This function is the same as OGRSpatialReference::SetGeogCS()
    3626             :  */
    3627          18 : OGRErr OSRSetGeogCS(OGRSpatialReferenceH hSRS, const char *pszGeogName,
    3628             :                     const char *pszDatumName, const char *pszSpheroidName,
    3629             :                     double dfSemiMajor, double dfInvFlattening,
    3630             :                     const char *pszPMName, double dfPMOffset,
    3631             :                     const char *pszAngularUnits, double dfConvertToRadians)
    3632             : 
    3633             : {
    3634          18 :     VALIDATE_POINTER1(hSRS, "OSRSetGeogCS", OGRERR_FAILURE);
    3635             : 
    3636          18 :     return ToPointer(hSRS)->SetGeogCS(pszGeogName, pszDatumName,
    3637             :                                       pszSpheroidName, dfSemiMajor,
    3638             :                                       dfInvFlattening, pszPMName, dfPMOffset,
    3639          18 :                                       pszAngularUnits, dfConvertToRadians);
    3640             : }
    3641             : 
    3642             : /************************************************************************/
    3643             : /*                         SetWellKnownGeogCS()                         */
    3644             : /************************************************************************/
    3645             : 
    3646             : /**
    3647             :  * \brief Set a GeogCS based on well known name.
    3648             :  *
    3649             :  * This may be called on an empty OGRSpatialReference to make a geographic
    3650             :  * coordinate system, or on something with an existing PROJCS node to
    3651             :  * set the underlying geographic coordinate system of a projected coordinate
    3652             :  * system.
    3653             :  *
    3654             :  * The following well known text values are currently supported,
    3655             :  * Except for "EPSG:n", the others are without dependency on EPSG data files:
    3656             :  * <ul>
    3657             :  * <li> "EPSG:n": where n is the code a Geographic coordinate reference system.
    3658             :  * <li> "WGS84": same as "EPSG:4326" (axis order lat/long).
    3659             :  * <li> "WGS72": same as "EPSG:4322" (axis order lat/long).
    3660             :  * <li> "NAD83": same as "EPSG:4269" (axis order lat/long).
    3661             :  * <li> "NAD27": same as "EPSG:4267" (axis order lat/long).
    3662             :  * <li> "CRS84", "CRS:84": same as "WGS84" but with axis order long/lat.
    3663             :  * <li> "CRS72", "CRS:72": same as "WGS72" but with axis order long/lat.
    3664             :  * <li> "CRS27", "CRS:27": same as "NAD27" but with axis order long/lat.
    3665             :  * </ul>
    3666             :  *
    3667             :  * @param pszName name of well known geographic coordinate system.
    3668             :  * @return OGRERR_NONE on success, or OGRERR_FAILURE if the name isn't
    3669             :  * recognised, the target object is already initialized, or an EPSG value
    3670             :  * can't be successfully looked up.
    3671             :  */
    3672             : 
    3673        4953 : OGRErr OGRSpatialReference::SetWellKnownGeogCS(const char *pszName)
    3674             : 
    3675             : {
    3676        9906 :     TAKE_OPTIONAL_LOCK();
    3677             : 
    3678             :     /* -------------------------------------------------------------------- */
    3679             :     /*      Check for EPSG authority numbers.                               */
    3680             :     /* -------------------------------------------------------------------- */
    3681        4953 :     if (STARTS_WITH_CI(pszName, "EPSG:") || STARTS_WITH_CI(pszName, "EPSGA:"))
    3682             :     {
    3683          84 :         OGRSpatialReference oSRS2;
    3684          42 :         const OGRErr eErr = oSRS2.importFromEPSG(atoi(pszName + 5));
    3685          42 :         if (eErr != OGRERR_NONE)
    3686           0 :             return eErr;
    3687             : 
    3688          42 :         if (!oSRS2.IsGeographic())
    3689           0 :             return OGRERR_FAILURE;
    3690             : 
    3691          42 :         return CopyGeogCSFrom(&oSRS2);
    3692             :     }
    3693             : 
    3694             :     /* -------------------------------------------------------------------- */
    3695             :     /*      Check for simple names.                                         */
    3696             :     /* -------------------------------------------------------------------- */
    3697        4911 :     const char *pszWKT = nullptr;
    3698             : 
    3699        4911 :     if (EQUAL(pszName, "WGS84"))
    3700             :     {
    3701        2793 :         pszWKT = SRS_WKT_WGS84_LAT_LONG;
    3702             :     }
    3703        2118 :     else if (EQUAL(pszName, "CRS84") || EQUAL(pszName, "CRS:84"))
    3704             :     {
    3705        1177 :         pszWKT =
    3706             :             "GEOGCRS[\"WGS 84 (CRS84)\",DATUM[\"World Geodetic System 1984\","
    3707             :             "ELLIPSOID[\"WGS "
    3708             :             "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],"
    3709             :             "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3710             :             "CS[ellipsoidal,2],AXIS[\"geodetic longitude (Lon)\",east,ORDER[1],"
    3711             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3712             :             "AXIS[\"geodetic latitude (Lat)\",north,ORDER[2],"
    3713             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3714             :             "USAGE[SCOPE[\"unknown\"],AREA[\"World\"],BBOX[-90,-180,90,180]],"
    3715             :             "ID[\"OGC\",\"CRS84\"]]";
    3716             :     }
    3717         941 :     else if (EQUAL(pszName, "WGS72"))
    3718          19 :         pszWKT =
    3719             :             "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
    3720             :             "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",\"7043\"]],"
    3721             :             "AUTHORITY[\"EPSG\",\"6322\"]],"
    3722             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3723             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3724             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3725             :             "AUTHORITY[\"EPSG\",\"4322\"]]";
    3726             : 
    3727         922 :     else if (EQUAL(pszName, "NAD27"))
    3728         136 :         pszWKT =
    3729             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3730             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3731             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3732             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3733             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3734             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3735             :             "AUTHORITY[\"EPSG\",\"4267\"]]";
    3736             : 
    3737         786 :     else if (EQUAL(pszName, "CRS27") || EQUAL(pszName, "CRS:27"))
    3738           0 :         pszWKT =
    3739             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3740             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3741             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3742             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3743             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3744             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3745             : 
    3746         786 :     else if (EQUAL(pszName, "NAD83"))
    3747         782 :         pszWKT =
    3748             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3749             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3750             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3751             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3752             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3753             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3754             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],AUTHORITY["
    3755             :             "\"EPSG\",\"4269\"]]";
    3756             : 
    3757           4 :     else if (EQUAL(pszName, "CRS83") || EQUAL(pszName, "CRS:83"))
    3758           0 :         pszWKT =
    3759             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3760             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3761             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3762             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3763             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3764             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3765             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3766             : 
    3767             :     else
    3768           4 :         return OGRERR_FAILURE;
    3769             : 
    3770             :     /* -------------------------------------------------------------------- */
    3771             :     /*      Import the WKT                                                  */
    3772             :     /* -------------------------------------------------------------------- */
    3773        9814 :     OGRSpatialReference oSRS2;
    3774        4907 :     const OGRErr eErr = oSRS2.importFromWkt(pszWKT);
    3775        4907 :     if (eErr != OGRERR_NONE)
    3776           0 :         return eErr;
    3777             : 
    3778             :     /* -------------------------------------------------------------------- */
    3779             :     /*      Copy over.                                                      */
    3780             :     /* -------------------------------------------------------------------- */
    3781        4907 :     return CopyGeogCSFrom(&oSRS2);
    3782             : }
    3783             : 
    3784             : /************************************************************************/
    3785             : /*                       OSRSetWellKnownGeogCS()                        */
    3786             : /************************************************************************/
    3787             : 
    3788             : /**
    3789             :  * \brief Set a GeogCS based on well known name.
    3790             :  *
    3791             :  * This function is the same as OGRSpatialReference::SetWellKnownGeogCS()
    3792             :  */
    3793         155 : OGRErr OSRSetWellKnownGeogCS(OGRSpatialReferenceH hSRS, const char *pszName)
    3794             : 
    3795             : {
    3796         155 :     VALIDATE_POINTER1(hSRS, "OSRSetWellKnownGeogCS", OGRERR_FAILURE);
    3797             : 
    3798         155 :     return ToPointer(hSRS)->SetWellKnownGeogCS(pszName);
    3799             : }
    3800             : 
    3801             : /************************************************************************/
    3802             : /*                           CopyGeogCSFrom()                           */
    3803             : /************************************************************************/
    3804             : 
    3805             : /**
    3806             :  * \brief Copy GEOGCS from another OGRSpatialReference.
    3807             :  *
    3808             :  * The GEOGCS information is copied into this OGRSpatialReference from another.
    3809             :  * If this object has a PROJCS root already, the GEOGCS is installed within
    3810             :  * it, otherwise it is installed as the root.
    3811             :  *
    3812             :  * @param poSrcSRS the spatial reference to copy the GEOGCS information from.
    3813             :  *
    3814             :  * @return OGRERR_NONE on success or an error code.
    3815             :  */
    3816        5415 : OGRErr OGRSpatialReference::CopyGeogCSFrom(const OGRSpatialReference *poSrcSRS)
    3817             : 
    3818             : {
    3819        5415 :     return CopyGeogCSFrom(poSrcSRS, false);
    3820             : }
    3821             : 
    3822             : /**
    3823             :  * \brief Copy GEOGCS from another OGRSpatialReference.
    3824             :  *
    3825             :  * The GEOGCS information is copied into this OGRSpatialReference from another.
    3826             :  * If this object has a PROJCS root already, the GEOGCS is installed within
    3827             :  * it, otherwise it is installed as the root.
    3828             :  *
    3829             :  * @param poSrcSRS the spatial reference to copy the GEOGCS information from.
    3830             :  * @param bInnerMostGeogCRS Whether the inner-most geographic CRS must be used.
    3831             :  *                          This setting makes a difference if this CRS is a
    3832             :  *                          derived geographic CRS. Setting bInnerMostGeogCRS
    3833             :  *                          to true will then extract its base CRS.
    3834             :  *
    3835             :  * @return OGRERR_NONE on success or an error code.
    3836             :  * @since 3.13.2
    3837             :  */
    3838        5571 : OGRErr OGRSpatialReference::CopyGeogCSFrom(const OGRSpatialReference *poSrcSRS,
    3839             :                                            bool bInnerMostGeogCRS)
    3840             : 
    3841             : {
    3842       11142 :     TAKE_OPTIONAL_LOCK();
    3843             : 
    3844        5571 :     d->bNormInfoSet = FALSE;
    3845        5571 :     d->m_osAngularUnits.clear();
    3846        5571 :     d->m_dfAngularUnitToRadian = 0.0;
    3847        5571 :     d->m_osPrimeMeridianName.clear();
    3848        5571 :     d->dfFromGreenwich = 0.0;
    3849             : 
    3850        5571 :     d->refreshProjObj();
    3851        5571 :     poSrcSRS->d->refreshProjObj();
    3852        5571 :     if (!poSrcSRS->d->m_pj_crs)
    3853             :     {
    3854           1 :         return OGRERR_FAILURE;
    3855             :     }
    3856             :     auto geodCRS =
    3857        5570 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3858        5570 :     if (!geodCRS)
    3859             :     {
    3860           0 :         return OGRERR_FAILURE;
    3861             :     }
    3862             : 
    3863        5570 :     if (bInnerMostGeogCRS && poSrcSRS->IsDerivedGeographic())
    3864             :     {
    3865           0 :         auto baseCRS = proj_get_source_crs(d->getPROJContext(), geodCRS);
    3866           0 :         if (!baseCRS)
    3867           0 :             return OGRERR_FAILURE;
    3868           0 :         proj_destroy(geodCRS);
    3869           0 :         geodCRS = baseCRS;
    3870             :     }
    3871             : 
    3872             :     /* -------------------------------------------------------------------- */
    3873             :     /*      Handle geocentric coordinate systems specially.  We just        */
    3874             :     /*      want to copy the DATUM.                                         */
    3875             :     /* -------------------------------------------------------------------- */
    3876        5570 :     if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    3877             :     {
    3878           3 :         auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    3879             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    3880             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    3881             :         if (datum == nullptr)
    3882             :         {
    3883             :             datum = proj_crs_get_datum_ensemble(d->getPROJContext(), geodCRS);
    3884             :         }
    3885             : #endif
    3886           3 :         if (datum == nullptr)
    3887             :         {
    3888           0 :             proj_destroy(geodCRS);
    3889           0 :             return OGRERR_FAILURE;
    3890             :         }
    3891             : 
    3892           3 :         const char *pszUnitName = nullptr;
    3893           3 :         double unitConvFactor = GetLinearUnits(&pszUnitName);
    3894             : 
    3895           3 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    3896           3 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, pszUnitName,
    3897             :             unitConvFactor);
    3898           3 :         proj_destroy(datum);
    3899             : 
    3900           3 :         d->setPjCRS(pj_crs);
    3901             :     }
    3902             : 
    3903        5567 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3904             :     {
    3905         319 :         auto pj_crs = proj_crs_alter_geodetic_crs(d->getPROJContext(),
    3906         319 :                                                   d->m_pj_crs, geodCRS);
    3907         319 :         d->setPjCRS(pj_crs);
    3908             :     }
    3909             : 
    3910             :     else
    3911             :     {
    3912        5248 :         d->setPjCRS(proj_clone(d->getPROJContext(), geodCRS));
    3913             :     }
    3914             : 
    3915             :     // Apply TOWGS84 of source CRS
    3916        5570 :     if (poSrcSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
    3917             :     {
    3918             :         auto target =
    3919           1 :             proj_get_target_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3920           1 :         auto co = proj_crs_get_coordoperation(d->getPROJContext(),
    3921           1 :                                               poSrcSRS->d->m_pj_crs);
    3922           1 :         d->setPjCRS(proj_crs_create_bound_crs(d->getPROJContext(), d->m_pj_crs,
    3923             :                                               target, co));
    3924           1 :         proj_destroy(target);
    3925           1 :         proj_destroy(co);
    3926             :     }
    3927             : 
    3928        5570 :     proj_destroy(geodCRS);
    3929             : 
    3930        5570 :     return OGRERR_NONE;
    3931             : }
    3932             : 
    3933             : /************************************************************************/
    3934             : /*                         OSRCopyGeogCSFrom()                          */
    3935             : /************************************************************************/
    3936             : 
    3937             : /**
    3938             :  * \brief Copy GEOGCS from another OGRSpatialReference.
    3939             :  *
    3940             :  * This function is the same as OGRSpatialReference::CopyGeogCSFrom()
    3941             :  */
    3942           1 : OGRErr OSRCopyGeogCSFrom(OGRSpatialReferenceH hSRS,
    3943             :                          const OGRSpatialReferenceH hSrcSRS)
    3944             : 
    3945             : {
    3946           1 :     VALIDATE_POINTER1(hSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3947           1 :     VALIDATE_POINTER1(hSrcSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3948             : 
    3949           1 :     return ToPointer(hSRS)->CopyGeogCSFrom(ToPointer(hSrcSRS));
    3950             : }
    3951             : 
    3952             : /************************************************************************/
    3953             : /*                SET_FROM_USER_INPUT_LIMITATIONS_get()                 */
    3954             : /************************************************************************/
    3955             : 
    3956             : /** Limitations for OGRSpatialReference::SetFromUserInput().
    3957             :  *
    3958             :  * Currently ALLOW_NETWORK_ACCESS=NO and ALLOW_FILE_ACCESS=NO.
    3959             :  */
    3960             : const char *const OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS[] = {
    3961             :     "ALLOW_NETWORK_ACCESS=NO", "ALLOW_FILE_ACCESS=NO", nullptr};
    3962             : 
    3963             : /**
    3964             :  * \brief Return OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS
    3965             :  */
    3966        2777 : CSLConstList OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()
    3967             : {
    3968        2777 :     return SET_FROM_USER_INPUT_LIMITATIONS;
    3969             : }
    3970             : 
    3971             : /************************************************************************/
    3972             : /*                   RemoveIDFromMemberOfEnsembles()                    */
    3973             : /************************************************************************/
    3974             : 
    3975             : // cppcheck-suppress constParameterReference
    3976         243 : static void RemoveIDFromMemberOfEnsembles(CPLJSONObject &obj)
    3977             : {
    3978             :     // Remove "id" from members of datum ensembles for compatibility with
    3979             :     // older PROJ versions
    3980             :     // Cf https://github.com/opengeospatial/geoparquet/discussions/110
    3981             :     // and https://github.com/OSGeo/PROJ/pull/3221
    3982         243 :     if (obj.GetType() == CPLJSONObject::Type::Object)
    3983             :     {
    3984         300 :         for (auto &subObj : obj.GetChildren())
    3985             :         {
    3986         235 :             RemoveIDFromMemberOfEnsembles(subObj);
    3987             :         }
    3988             :     }
    3989         198 :     else if (obj.GetType() == CPLJSONObject::Type::Array &&
    3990         198 :              obj.GetName() == "members")
    3991             :     {
    3992          60 :         for (auto &subObj : obj.ToArray())
    3993             :         {
    3994          52 :             if (subObj.GetType() == CPLJSONObject::Type::Object)
    3995             :             {
    3996          51 :                 subObj.Delete("id");
    3997             :             }
    3998             :         }
    3999             :     }
    4000         243 : }
    4001             : 
    4002             : /************************************************************************/
    4003             : /*                          SetFromUserInput()                          */
    4004             : /************************************************************************/
    4005             : 
    4006             : /**
    4007             :  * \brief Set spatial reference from various text formats.
    4008             :  *
    4009             :  * This method will examine the provided input, and try to deduce the
    4010             :  * format, and then use it to initialize the spatial reference system.  It
    4011             :  * may take the following forms:
    4012             :  *
    4013             :  * <ol>
    4014             :  * <li> Well Known Text definition - passed on to importFromWkt().
    4015             :  * <li> "EPSG:n" - number passed on to importFromEPSG().
    4016             :  * <li> "EPSGA:n" - number passed on to importFromEPSGA().
    4017             :  * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
    4018             :  * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
    4019             :  * <li> PROJ.4 definitions - passed on to importFromProj4().
    4020             :  * <li> filename - file read for WKT, XML or PROJ.4 definition.
    4021             :  * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
    4022             :  * WGS84 or WGS72.
    4023             :  * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
    4024             :  * <li> PROJJSON (PROJ &gt;= 6.2)
    4025             :  * </ol>
    4026             :  *
    4027             :  * It is expected that this method will be extended in the future to support
    4028             :  * XML and perhaps a simplified "minilanguage" for indicating common UTM and
    4029             :  * State Plane definitions.
    4030             :  *
    4031             :  * This method is intended to be flexible, but by its nature it is
    4032             :  * imprecise as it must guess information about the format intended.  When
    4033             :  * possible applications should call the specific method appropriate if the
    4034             :  * input is known to be in a particular format.
    4035             :  *
    4036             :  * This method does the same thing as the OSRSetFromUserInput() function.
    4037             :  *
    4038             :  * @param pszDefinition text definition to try to deduce SRS from.
    4039             :  *
    4040             :  * @return OGRERR_NONE on success, or an error code if the name isn't
    4041             :  * recognised, the definition is corrupt, or an EPSG value can't be
    4042             :  * successfully looked up.
    4043             :  */
    4044             : 
    4045       23602 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition)
    4046             : {
    4047       23602 :     return SetFromUserInput(pszDefinition, nullptr);
    4048             : }
    4049             : 
    4050             : /**
    4051             :  * \brief Set spatial reference from various text formats.
    4052             :  *
    4053             :  * This method will examine the provided input, and try to deduce the
    4054             :  * format, and then use it to initialize the spatial reference system.  It
    4055             :  * may take the following forms:
    4056             :  *
    4057             :  * <ol>
    4058             :  * <li> Well Known Text definition - passed on to importFromWkt().
    4059             :  * <li> "EPSG:n" - number passed on to importFromEPSG().
    4060             :  * <li> "EPSGA:n" - number passed on to importFromEPSGA().
    4061             :  * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
    4062             :  * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
    4063             :  * <li> PROJ.4 definitions - passed on to importFromProj4().
    4064             :  * <li> filename - file read for WKT, XML or PROJ.4 definition.
    4065             :  * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
    4066             :  * WGS84 or WGS72.
    4067             :  * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
    4068             :  * <li> PROJJSON (PROJ &gt;= 6.2)
    4069             :  * </ol>
    4070             :  *
    4071             :  * It is expected that this method will be extended in the future to support
    4072             :  * XML and perhaps a simplified "minilanguage" for indicating common UTM and
    4073             :  * State Plane definitions.
    4074             :  *
    4075             :  * This method is intended to be flexible, but by its nature it is
    4076             :  * imprecise as it must guess information about the format intended.  When
    4077             :  * possible applications should call the specific method appropriate if the
    4078             :  * input is known to be in a particular format.
    4079             :  *
    4080             :  * This method does the same thing as the OSRSetFromUserInput() and
    4081             :  * OSRSetFromUserInputEx() functions.
    4082             :  *
    4083             :  * @param pszDefinition text definition to try to deduce SRS from.
    4084             :  *
    4085             :  * @param papszOptions NULL terminated list of options, or NULL.
    4086             :  * <ol>
    4087             :  * <li> ALLOW_NETWORK_ACCESS=YES/NO.
    4088             :  *      Whether http:// or https:// access is allowed. Defaults to YES.
    4089             :  * <li> ALLOW_FILE_ACCESS=YES/NO.
    4090             :  *      Whether reading a file using the Virtual File System layer is allowed
    4091             :  *      (can also involve network access). Defaults to YES.
    4092             :  * </ol>
    4093             :  *
    4094             :  * @return OGRERR_NONE on success, or an error code if the name isn't
    4095             :  * recognised, the definition is corrupt, or an EPSG value can't be
    4096             :  * successfully looked up.
    4097             :  */
    4098             : 
    4099       31320 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition,
    4100             :                                              CSLConstList papszOptions)
    4101             : {
    4102       62640 :     TAKE_OPTIONAL_LOCK();
    4103             : 
    4104             :     // Skip leading white space
    4105       31322 :     while (isspace(static_cast<unsigned char>(*pszDefinition)))
    4106           2 :         pszDefinition++;
    4107             : 
    4108       31320 :     if (STARTS_WITH_CI(pszDefinition, "ESRI::"))
    4109             :     {
    4110           1 :         pszDefinition += 6;
    4111             :     }
    4112             : 
    4113             :     /* -------------------------------------------------------------------- */
    4114             :     /*      Is it a recognised syntax?                                      */
    4115             :     /* -------------------------------------------------------------------- */
    4116       31320 :     const char *const wktKeywords[] = {
    4117             :         // WKT1
    4118             :         "GEOGCS", "GEOCCS", "PROJCS", "VERT_CS", "COMPD_CS", "LOCAL_CS",
    4119             :         // WKT2"
    4120             :         "GEODCRS", "GEOGCRS", "GEODETICCRS", "GEOGRAPHICCRS", "PROJCRS",
    4121             :         "PROJECTEDCRS", "VERTCRS", "VERTICALCRS", "COMPOUNDCRS", "ENGCRS",
    4122             :         "ENGINEERINGCRS", "BOUNDCRS", "DERIVEDPROJCRS", "COORDINATEMETADATA"};
    4123      485880 :     for (const char *keyword : wktKeywords)
    4124             :     {
    4125      463850 :         if (STARTS_WITH_CI(pszDefinition, keyword))
    4126             :         {
    4127        9290 :             return importFromWkt(pszDefinition);
    4128             :         }
    4129             :     }
    4130             : 
    4131       22030 :     const bool bStartsWithEPSG = STARTS_WITH_CI(pszDefinition, "EPSG:");
    4132       22030 :     if (bStartsWithEPSG || STARTS_WITH_CI(pszDefinition, "EPSGA:"))
    4133             :     {
    4134       12364 :         OGRErr eStatus = OGRERR_NONE;
    4135             : 
    4136       12364 :         if (strchr(pszDefinition, '+') || strchr(pszDefinition, '@'))
    4137             :         {
    4138             :             // Use proj_create() as it allows things like EPSG:3157+4617
    4139             :             // that are not normally supported by the below code that
    4140             :             // builds manually a compound CRS
    4141          69 :             PJ *pj = proj_create(d->getPROJContext(), pszDefinition);
    4142          69 :             if (!pj)
    4143             :             {
    4144           1 :                 return OGRERR_FAILURE;
    4145             :             }
    4146          68 :             Clear();
    4147          68 :             d->setPjCRS(pj);
    4148          68 :             return OGRERR_NONE;
    4149             :         }
    4150             :         else
    4151             :         {
    4152             :             eStatus =
    4153       12295 :                 importFromEPSG(atoi(pszDefinition + (bStartsWithEPSG ? 5 : 6)));
    4154             :         }
    4155             : 
    4156       12295 :         return eStatus;
    4157             :     }
    4158             : 
    4159        9666 :     if (STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs:") ||
    4160        8941 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs,crs:") ||
    4161        8940 :         STARTS_WITH_CI(pszDefinition, "urn:x-ogc:def:crs:") ||
    4162        8882 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:crs:") ||
    4163        8882 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:def:crs:") ||
    4164        8882 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:coordinateMetadata:"))
    4165         784 :         return importFromURN(pszDefinition);
    4166             : 
    4167        8882 :     if (STARTS_WITH_CI(pszDefinition, "http://opengis.net/def/crs") ||
    4168        8880 :         STARTS_WITH_CI(pszDefinition, "https://opengis.net/def/crs") ||
    4169        8879 :         STARTS_WITH_CI(pszDefinition, "http://www.opengis.net/def/crs") ||
    4170        1721 :         STARTS_WITH_CI(pszDefinition, "https://www.opengis.net/def/crs") ||
    4171        1720 :         STARTS_WITH_CI(pszDefinition, "www.opengis.net/def/crs"))
    4172        7162 :         return importFromCRSURL(pszDefinition);
    4173             : 
    4174        1720 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    4175           1 :         return importFromWMSAUTO(pszDefinition);
    4176             : 
    4177             :     // WMS/WCS OGC codes like OGC:CRS84.
    4178        1719 :     if (EQUAL(pszDefinition, "OGC:CRS84"))
    4179          79 :         return SetWellKnownGeogCS(pszDefinition + 4);
    4180             : 
    4181        1640 :     if (STARTS_WITH_CI(pszDefinition, "CRS:"))
    4182           1 :         return SetWellKnownGeogCS(pszDefinition);
    4183             : 
    4184        1639 :     if (STARTS_WITH_CI(pszDefinition, "DICT:") && strstr(pszDefinition, ","))
    4185             :     {
    4186           0 :         char *pszFile = CPLStrdup(pszDefinition + 5);
    4187           0 :         char *pszCode = strstr(pszFile, ",") + 1;
    4188             : 
    4189           0 :         pszCode[-1] = '\0';
    4190             : 
    4191           0 :         OGRErr err = importFromDict(pszFile, pszCode);
    4192           0 :         CPLFree(pszFile);
    4193             : 
    4194           0 :         return err;
    4195             :     }
    4196             : 
    4197        1639 :     if (EQUAL(pszDefinition, "NAD27") || EQUAL(pszDefinition, "NAD83") ||
    4198        1634 :         EQUAL(pszDefinition, "WGS84") || EQUAL(pszDefinition, "WGS72"))
    4199             :     {
    4200         593 :         Clear();
    4201         593 :         return SetWellKnownGeogCS(pszDefinition);
    4202             :     }
    4203             : 
    4204             :     // PROJJSON
    4205        1046 :     if (pszDefinition[0] == '{' && strstr(pszDefinition, "\"type\"") &&
    4206         179 :         (strstr(pszDefinition, "GeodeticCRS") ||
    4207         179 :          strstr(pszDefinition, "GeographicCRS") ||
    4208         129 :          strstr(pszDefinition, "ProjectedCRS") ||
    4209           0 :          strstr(pszDefinition, "VerticalCRS") ||
    4210           0 :          strstr(pszDefinition, "BoundCRS") ||
    4211           0 :          strstr(pszDefinition, "CompoundCRS") ||
    4212           0 :          strstr(pszDefinition, "DerivedGeodeticCRS") ||
    4213           0 :          strstr(pszDefinition, "DerivedGeographicCRS") ||
    4214           0 :          strstr(pszDefinition, "DerivedProjectedCRS") ||
    4215           0 :          strstr(pszDefinition, "DerivedVerticalCRS") ||
    4216           0 :          strstr(pszDefinition, "EngineeringCRS") ||
    4217           0 :          strstr(pszDefinition, "DerivedEngineeringCRS") ||
    4218           0 :          strstr(pszDefinition, "ParametricCRS") ||
    4219           0 :          strstr(pszDefinition, "DerivedParametricCRS") ||
    4220           0 :          strstr(pszDefinition, "TemporalCRS") ||
    4221           0 :          strstr(pszDefinition, "DerivedTemporalCRS")))
    4222             :     {
    4223             :         PJ *pj;
    4224         179 :         if (strstr(pszDefinition, "datum_ensemble") != nullptr)
    4225             :         {
    4226             :             // PROJ < 9.0.1 doesn't like a datum_ensemble whose member have
    4227             :             // a unknown id.
    4228           8 :             CPLJSONDocument oCRSDoc;
    4229           8 :             if (!oCRSDoc.LoadMemory(pszDefinition))
    4230           0 :                 return OGRERR_CORRUPT_DATA;
    4231           8 :             CPLJSONObject oCRSRoot = oCRSDoc.GetRoot();
    4232           8 :             RemoveIDFromMemberOfEnsembles(oCRSRoot);
    4233           8 :             pj = proj_create(d->getPROJContext(), oCRSRoot.ToString().c_str());
    4234             :         }
    4235             :         else
    4236             :         {
    4237         171 :             pj = proj_create(d->getPROJContext(), pszDefinition);
    4238             :         }
    4239         179 :         if (!pj)
    4240             :         {
    4241           2 :             return OGRERR_FAILURE;
    4242             :         }
    4243         177 :         Clear();
    4244         177 :         d->setPjCRS(pj);
    4245         177 :         return OGRERR_NONE;
    4246             :     }
    4247             : 
    4248         867 :     if (strstr(pszDefinition, "+proj") != nullptr ||
    4249         407 :         strstr(pszDefinition, "+init") != nullptr)
    4250         460 :         return importFromProj4(pszDefinition);
    4251             : 
    4252         407 :     if (STARTS_WITH_CI(pszDefinition, "http://") ||
    4253         382 :         STARTS_WITH_CI(pszDefinition, "https://"))
    4254             :     {
    4255          26 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
    4256             :                                              "ALLOW_NETWORK_ACCESS", "YES")))
    4257           0 :             return importFromUrl(pszDefinition);
    4258             : 
    4259          26 :         CPLError(CE_Failure, CPLE_AppDefined,
    4260             :                  "Cannot import %s due to ALLOW_NETWORK_ACCESS=NO",
    4261             :                  pszDefinition);
    4262          26 :         return OGRERR_FAILURE;
    4263             :     }
    4264             : 
    4265         381 :     if (EQUAL(pszDefinition, "osgb:BNG"))
    4266             :     {
    4267           8 :         return importFromEPSG(27700);
    4268             :     }
    4269             : 
    4270             :     // Used by German CityGML files
    4271         373 :     if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH"))
    4272             :     {
    4273             :         // "ETRS89 / UTM Zone 32N + DHHN92 height"
    4274           0 :         return SetFromUserInput("EPSG:25832+5783");
    4275             :     }
    4276         373 :     else if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN2016_NH"))
    4277             :     {
    4278             :         // "ETRS89 / UTM Zone 32N + DHHN2016 height"
    4279           3 :         return SetFromUserInput("EPSG:25832+7837");
    4280             :     }
    4281             : 
    4282             :     // Used by  Japan's Fundamental Geospatial Data (FGD) GML
    4283         370 :     if (EQUAL(pszDefinition, "fguuid:jgd2001.bl"))
    4284           0 :         return importFromEPSG(4612);  // JGD2000 (slight difference in years)
    4285         370 :     else if (EQUAL(pszDefinition, "fguuid:jgd2011.bl"))
    4286           8 :         return importFromEPSG(6668);  // JGD2011
    4287         362 :     else if (EQUAL(pszDefinition, "fguuid:jgd2024.bl"))
    4288             :     {
    4289             :         // FIXME when EPSG attributes a CRS code
    4290           3 :         return importFromWkt(
    4291             :             "GEOGCRS[\"JGD2024\",\n"
    4292             :             "    DATUM[\"Japanese Geodetic Datum 2024\",\n"
    4293             :             "        ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
    4294             :             "            LENGTHUNIT[\"metre\",1]]],\n"
    4295             :             "    PRIMEM[\"Greenwich\",0,\n"
    4296             :             "        ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4297             :             "    CS[ellipsoidal,2],\n"
    4298             :             "        AXIS[\"geodetic latitude (Lat)\",north,\n"
    4299             :             "            ORDER[1],\n"
    4300             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4301             :             "        AXIS[\"geodetic longitude (Lon)\",east,\n"
    4302             :             "            ORDER[2],\n"
    4303             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4304             :             "    USAGE[\n"
    4305             :             "        SCOPE[\"Horizontal component of 3D system.\"],\n"
    4306             :             "        AREA[\"Japan - onshore and offshore.\"],\n"
    4307           3 :             "        BBOX[17.09,122.38,46.05,157.65]]]");
    4308             :     }
    4309             : 
    4310             :     // Deal with IGNF:xxx, ESRI:xxx, etc from the PROJ database
    4311         359 :     const char *pszDot = strrchr(pszDefinition, ':');
    4312         359 :     if (pszDot)
    4313             :     {
    4314         125 :         CPLString osPrefix(pszDefinition, pszDot - pszDefinition);
    4315             :         auto authorities =
    4316         125 :             proj_get_authorities_from_database(d->getPROJContext());
    4317         125 :         if (authorities)
    4318             :         {
    4319         125 :             std::set<std::string> aosCandidateAuthorities;
    4320         290 :             for (auto iter = authorities; *iter; ++iter)
    4321             :             {
    4322         288 :                 if (*iter == osPrefix)
    4323             :                 {
    4324         123 :                     aosCandidateAuthorities.clear();
    4325         123 :                     aosCandidateAuthorities.insert(*iter);
    4326         123 :                     break;
    4327             :                 }
    4328             :                 // Deal with "IAU_2015" as authority in the list and input
    4329             :                 // "IAU:code"
    4330         165 :                 else if (strncmp(*iter, osPrefix.c_str(), osPrefix.size()) ==
    4331         165 :                              0 &&
    4332           0 :                          (*iter)[osPrefix.size()] == '_')
    4333             :                 {
    4334           0 :                     aosCandidateAuthorities.insert(*iter);
    4335             :                 }
    4336             :                 // Deal with "IAU_2015" as authority in the list and input
    4337             :                 // "IAU:2015:code"
    4338         330 :                 else if (osPrefix.find(':') != std::string::npos &&
    4339         165 :                          osPrefix.size() == strlen(*iter) &&
    4340         165 :                          CPLString(osPrefix).replaceAll(':', '_') == *iter)
    4341             :                 {
    4342           0 :                     aosCandidateAuthorities.clear();
    4343           0 :                     aosCandidateAuthorities.insert(*iter);
    4344           0 :                     break;
    4345             :                 }
    4346             :             }
    4347             : 
    4348         125 :             proj_string_list_destroy(authorities);
    4349             : 
    4350         125 :             if (!aosCandidateAuthorities.empty())
    4351             :             {
    4352         123 :                 auto obj = proj_create_from_database(
    4353             :                     d->getPROJContext(),
    4354         123 :                     aosCandidateAuthorities.rbegin()->c_str(), pszDot + 1,
    4355             :                     PJ_CATEGORY_CRS, false, nullptr);
    4356         123 :                 if (!obj)
    4357             :                 {
    4358          16 :                     return OGRERR_FAILURE;
    4359             :                 }
    4360         107 :                 Clear();
    4361         107 :                 d->setPjCRS(obj);
    4362         107 :                 return OGRERR_NONE;
    4363             :             }
    4364             :         }
    4365             :     }
    4366             : 
    4367             :     /* -------------------------------------------------------------------- */
    4368             :     /*      Try to open it as a file.                                       */
    4369             :     /* -------------------------------------------------------------------- */
    4370         236 :     if (!CPLTestBool(
    4371             :             CSLFetchNameValueDef(papszOptions, "ALLOW_FILE_ACCESS", "YES")))
    4372             :     {
    4373             :         VSIStatBufL sStat;
    4374          40 :         if (STARTS_WITH(pszDefinition, "/vsi") ||
    4375          20 :             VSIStatExL(pszDefinition, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
    4376             :         {
    4377           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4378             :                      "Cannot import %s due to ALLOW_FILE_ACCESS=NO",
    4379             :                      pszDefinition);
    4380           0 :             return OGRERR_FAILURE;
    4381             :         }
    4382             :         // We used to silently return an error without a CE_Failure message
    4383             :         // Cf https://github.com/Toblerity/Fiona/issues/1063
    4384          20 :         return OGRERR_CORRUPT_DATA;
    4385             :     }
    4386             : 
    4387         432 :     CPLConfigOptionSetter oSetter("CPL_ALLOW_VSISTDIN", "NO", true);
    4388         216 :     VSILFILE *const fp = VSIFOpenL(pszDefinition, "rt");
    4389         216 :     if (fp == nullptr)
    4390         213 :         return OGRERR_CORRUPT_DATA;
    4391             : 
    4392           3 :     const size_t nBufMax = 100000;
    4393           3 :     char *const pszBuffer = static_cast<char *>(CPLMalloc(nBufMax));
    4394           3 :     const size_t nBytes = VSIFReadL(pszBuffer, 1, nBufMax - 1, fp);
    4395           3 :     VSIFCloseL(fp);
    4396             : 
    4397           3 :     if (nBytes == nBufMax - 1)
    4398             :     {
    4399           0 :         CPLDebug("OGR",
    4400             :                  "OGRSpatialReference::SetFromUserInput(%s), opened file "
    4401             :                  "but it is to large for our generous buffer.  Is it really "
    4402             :                  "just a WKT definition?",
    4403             :                  pszDefinition);
    4404           0 :         CPLFree(pszBuffer);
    4405           0 :         return OGRERR_FAILURE;
    4406             :     }
    4407             : 
    4408           3 :     pszBuffer[nBytes] = '\0';
    4409             : 
    4410           3 :     char *pszBufPtr = pszBuffer;
    4411           3 :     while (pszBufPtr[0] == ' ' || pszBufPtr[0] == '\n')
    4412           0 :         pszBufPtr++;
    4413             : 
    4414           3 :     OGRErr err = OGRERR_NONE;
    4415           3 :     if (pszBufPtr[0] == '<')
    4416           0 :         err = importFromXML(pszBufPtr);
    4417           3 :     else if ((strstr(pszBuffer, "+proj") != nullptr ||
    4418           3 :               strstr(pszBuffer, "+init") != nullptr) &&
    4419           0 :              (strstr(pszBuffer, "EXTENSION") == nullptr &&
    4420           0 :               strstr(pszBuffer, "extension") == nullptr))
    4421           0 :         err = importFromProj4(pszBufPtr);
    4422             :     else
    4423             :     {
    4424           3 :         if (STARTS_WITH_CI(pszBufPtr, "ESRI::"))
    4425             :         {
    4426           0 :             pszBufPtr += 6;
    4427             :         }
    4428             : 
    4429             :         // coverity[tainted_data]
    4430           3 :         err = importFromWkt(pszBufPtr);
    4431             :     }
    4432             : 
    4433           3 :     CPLFree(pszBuffer);
    4434             : 
    4435           3 :     return err;
    4436             : }
    4437             : 
    4438             : /************************************************************************/
    4439             : /*                        OSRSetFromUserInput()                         */
    4440             : /************************************************************************/
    4441             : 
    4442             : /**
    4443             :  * \brief Set spatial reference from various text formats.
    4444             :  *
    4445             :  * This function is the same as OGRSpatialReference::SetFromUserInput()
    4446             :  *
    4447             :  * \see OSRSetFromUserInputEx() for a variant allowing to pass options.
    4448             :  */
    4449         383 : OGRErr CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS,
    4450             :                                        const char *pszDef)
    4451             : 
    4452             : {
    4453         383 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInput", OGRERR_FAILURE);
    4454             : 
    4455         383 :     return ToPointer(hSRS)->SetFromUserInput(pszDef);
    4456             : }
    4457             : 
    4458             : /************************************************************************/
    4459             : /*                       OSRSetFromUserInputEx()                        */
    4460             : /************************************************************************/
    4461             : 
    4462             : /**
    4463             :  * \brief Set spatial reference from various text formats.
    4464             :  *
    4465             :  * This function is the same as OGRSpatialReference::SetFromUserInput().
    4466             :  *
    4467             :  * @since GDAL 3.9
    4468             :  */
    4469        1265 : OGRErr OSRSetFromUserInputEx(OGRSpatialReferenceH hSRS, const char *pszDef,
    4470             :                              CSLConstList papszOptions)
    4471             : 
    4472             : {
    4473        1265 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInputEx", OGRERR_FAILURE);
    4474             : 
    4475        1265 :     return ToPointer(hSRS)->SetFromUserInput(pszDef, papszOptions);
    4476             : }
    4477             : 
    4478             : /************************************************************************/
    4479             : /*                           ImportFromUrl()                            */
    4480             : /************************************************************************/
    4481             : 
    4482             : /**
    4483             :  * \brief Set spatial reference from a URL.
    4484             :  *
    4485             :  * This method will download the spatial reference at a given URL and
    4486             :  * feed it into SetFromUserInput for you.
    4487             :  *
    4488             :  * This method does the same thing as the OSRImportFromUrl() function.
    4489             :  *
    4490             :  * @param pszUrl text definition to try to deduce SRS from.
    4491             :  *
    4492             :  * @return OGRERR_NONE on success, or an error code with the curl
    4493             :  * error message if it is unable to download data.
    4494             :  */
    4495             : 
    4496           5 : OGRErr OGRSpatialReference::importFromUrl(const char *pszUrl)
    4497             : 
    4498             : {
    4499          10 :     TAKE_OPTIONAL_LOCK();
    4500             : 
    4501           5 :     if (!STARTS_WITH_CI(pszUrl, "http://") &&
    4502           3 :         !STARTS_WITH_CI(pszUrl, "https://"))
    4503             :     {
    4504           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    4505             :                  "The given string is not recognized as a URL"
    4506             :                  "starting with 'http://' -- %s",
    4507             :                  pszUrl);
    4508           2 :         return OGRERR_FAILURE;
    4509             :     }
    4510             : 
    4511             :     /* -------------------------------------------------------------------- */
    4512             :     /*      Fetch the result.                                               */
    4513             :     /* -------------------------------------------------------------------- */
    4514           3 :     CPLErrorReset();
    4515             : 
    4516           6 :     std::string osUrl(pszUrl);
    4517             :     // We have historically supported "http://spatialreference.org/ref/AUTHNAME/CODE/"
    4518             :     // as a valid URL since we used a "Accept: application/x-ogcwkt" header
    4519             :     // to query WKT. To allow a static server to be used, rather append a
    4520             :     // "ogcwkt/" suffix.
    4521           2 :     for (const char *pszPrefix : {"https://spatialreference.org/ref/",
    4522           5 :                                   "http://spatialreference.org/ref/"})
    4523             :     {
    4524           5 :         if (STARTS_WITH(pszUrl, pszPrefix))
    4525             :         {
    4526             :             const CPLStringList aosTokens(
    4527           6 :                 CSLTokenizeString2(pszUrl + strlen(pszPrefix), "/", 0));
    4528           3 :             if (aosTokens.size() == 2)
    4529             :             {
    4530           2 :                 osUrl = "https://spatialreference.org/ref/";
    4531           2 :                 osUrl += aosTokens[0];  // authority
    4532           2 :                 osUrl += '/';
    4533           2 :                 osUrl += aosTokens[1];  // code
    4534           2 :                 osUrl += "/ogcwkt/";
    4535             :             }
    4536           3 :             break;
    4537             :         }
    4538             :     }
    4539             : 
    4540           3 :     const char *pszTimeout = "TIMEOUT=10";
    4541           3 :     char *apszOptions[] = {const_cast<char *>(pszTimeout), nullptr};
    4542             : 
    4543           3 :     CPLHTTPResult *psResult = CPLHTTPFetch(osUrl.c_str(), apszOptions);
    4544             : 
    4545             :     /* -------------------------------------------------------------------- */
    4546             :     /*      Try to handle errors.                                           */
    4547             :     /* -------------------------------------------------------------------- */
    4548             : 
    4549           3 :     if (psResult == nullptr)
    4550           0 :         return OGRERR_FAILURE;
    4551           6 :     if (psResult->nDataLen == 0 || CPLGetLastErrorNo() != 0 ||
    4552           3 :         psResult->pabyData == nullptr)
    4553             :     {
    4554           0 :         if (CPLGetLastErrorNo() == 0)
    4555             :         {
    4556           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4557             :                      "No data was returned from the given URL");
    4558             :         }
    4559           0 :         CPLHTTPDestroyResult(psResult);
    4560           0 :         return OGRERR_FAILURE;
    4561             :     }
    4562             : 
    4563           3 :     if (psResult->nStatus != 0)
    4564             :     {
    4565           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
    4566             :                  psResult->nStatus, psResult->pszErrBuf);
    4567           0 :         CPLHTTPDestroyResult(psResult);
    4568           0 :         return OGRERR_FAILURE;
    4569             :     }
    4570             : 
    4571           3 :     const char *pszData = reinterpret_cast<const char *>(psResult->pabyData);
    4572           3 :     if (STARTS_WITH_CI(pszData, "http://") ||
    4573           3 :         STARTS_WITH_CI(pszData, "https://"))
    4574             :     {
    4575           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4576             :                  "The data that was downloaded also starts with 'http://' "
    4577             :                  "and cannot be passed into SetFromUserInput.  Is this "
    4578             :                  "really a spatial reference definition? ");
    4579           0 :         CPLHTTPDestroyResult(psResult);
    4580           0 :         return OGRERR_FAILURE;
    4581             :     }
    4582           3 :     if (OGRERR_NONE != SetFromUserInput(pszData))
    4583             :     {
    4584           0 :         CPLHTTPDestroyResult(psResult);
    4585           0 :         return OGRERR_FAILURE;
    4586             :     }
    4587             : 
    4588           3 :     CPLHTTPDestroyResult(psResult);
    4589           3 :     return OGRERR_NONE;
    4590             : }
    4591             : 
    4592             : /************************************************************************/
    4593             : /*                          OSRimportFromUrl()                          */
    4594             : /************************************************************************/
    4595             : 
    4596             : /**
    4597             :  * \brief Set spatial reference from a URL.
    4598             :  *
    4599             :  * This function is the same as OGRSpatialReference::importFromUrl()
    4600             :  */
    4601           3 : OGRErr OSRImportFromUrl(OGRSpatialReferenceH hSRS, const char *pszUrl)
    4602             : 
    4603             : {
    4604           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromUrl", OGRERR_FAILURE);
    4605             : 
    4606           3 :     return ToPointer(hSRS)->importFromUrl(pszUrl);
    4607             : }
    4608             : 
    4609             : /************************************************************************/
    4610             : /*                         importFromURNPart()                          */
    4611             : /************************************************************************/
    4612        7373 : OGRErr OGRSpatialReference::importFromURNPart(const char *pszAuthority,
    4613             :                                               const char *pszCode,
    4614             :                                               const char *pszURN)
    4615             : {
    4616             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4617             :     (void)this;
    4618             :     (void)pszAuthority;
    4619             :     (void)pszCode;
    4620             :     (void)pszURN;
    4621             :     return OGRERR_FAILURE;
    4622             : #else
    4623             :     /* -------------------------------------------------------------------- */
    4624             :     /*      Is this an EPSG code? Note that we import it with EPSG          */
    4625             :     /*      preferred axis ordering for geographic coordinate systems.      */
    4626             :     /* -------------------------------------------------------------------- */
    4627        7373 :     if (STARTS_WITH_CI(pszAuthority, "EPSG"))
    4628        6273 :         return importFromEPSGA(atoi(pszCode));
    4629             : 
    4630             :     /* -------------------------------------------------------------------- */
    4631             :     /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
    4632             :     /* -------------------------------------------------------------------- */
    4633        1100 :     if (STARTS_WITH_CI(pszAuthority, "IAU"))
    4634           0 :         return importFromDict("IAU2000.wkt", pszCode);
    4635             : 
    4636             :     /* -------------------------------------------------------------------- */
    4637             :     /*      Is this an OGC code?                                            */
    4638             :     /* -------------------------------------------------------------------- */
    4639        1100 :     if (!STARTS_WITH_CI(pszAuthority, "OGC"))
    4640             :     {
    4641           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4642             :                  "URN %s has unrecognized authority.", pszURN);
    4643           1 :         return OGRERR_FAILURE;
    4644             :     }
    4645             : 
    4646        1099 :     if (STARTS_WITH_CI(pszCode, "CRS84"))
    4647        1087 :         return SetWellKnownGeogCS(pszCode);
    4648          12 :     else if (STARTS_WITH_CI(pszCode, "CRS83"))
    4649           0 :         return SetWellKnownGeogCS(pszCode);
    4650          12 :     else if (STARTS_WITH_CI(pszCode, "CRS27"))
    4651           0 :         return SetWellKnownGeogCS(pszCode);
    4652          12 :     else if (STARTS_WITH_CI(pszCode, "84"))  // urn:ogc:def:crs:OGC:2:84
    4653          10 :         return SetWellKnownGeogCS("CRS84");
    4654             : 
    4655             :     /* -------------------------------------------------------------------- */
    4656             :     /*      Handle auto codes.  We need to convert from format              */
    4657             :     /*      AUTO42001:99:8888 to format AUTO:42001,99,8888.                 */
    4658             :     /* -------------------------------------------------------------------- */
    4659           2 :     else if (STARTS_WITH_CI(pszCode, "AUTO"))
    4660             :     {
    4661           2 :         char szWMSAuto[100] = {'\0'};
    4662             : 
    4663           2 :         if (strlen(pszCode) > sizeof(szWMSAuto) - 2)
    4664           0 :             return OGRERR_FAILURE;
    4665             : 
    4666           2 :         snprintf(szWMSAuto, sizeof(szWMSAuto), "AUTO:%s", pszCode + 4);
    4667          28 :         for (int i = 5; szWMSAuto[i] != '\0'; i++)
    4668             :         {
    4669          26 :             if (szWMSAuto[i] == ':')
    4670           4 :                 szWMSAuto[i] = ',';
    4671             :         }
    4672             : 
    4673           2 :         return importFromWMSAUTO(szWMSAuto);
    4674             :     }
    4675             : 
    4676             :     /* -------------------------------------------------------------------- */
    4677             :     /*      Not a recognise OGC item.                                       */
    4678             :     /* -------------------------------------------------------------------- */
    4679           0 :     CPLError(CE_Failure, CPLE_AppDefined, "URN %s value not supported.",
    4680             :              pszURN);
    4681             : 
    4682           0 :     return OGRERR_FAILURE;
    4683             : #endif
    4684             : }
    4685             : 
    4686             : /************************************************************************/
    4687             : /*                           importFromURN()                            */
    4688             : /*                                                                      */
    4689             : /*      See OGC recommendation paper 06-023r1 or later for details.     */
    4690             : /************************************************************************/
    4691             : 
    4692             : /**
    4693             :  * \brief Initialize from OGC URN.
    4694             :  *
    4695             :  * Initializes this spatial reference from a coordinate system defined
    4696             :  * by an OGC URN prefixed with "urn:ogc:def:crs:" per recommendation
    4697             :  * paper 06-023r1.  Currently EPSG and OGC authority values are supported,
    4698             :  * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
    4699             :  *
    4700             :  * This method is also support through SetFromUserInput() which can
    4701             :  * normally be used for URNs.
    4702             :  *
    4703             :  * @param pszURN the urn string.
    4704             :  *
    4705             :  * @return OGRERR_NONE on success or an error code.
    4706             :  */
    4707             : 
    4708         845 : OGRErr OGRSpatialReference::importFromURN(const char *pszURN)
    4709             : 
    4710             : {
    4711         845 :     constexpr const char *EPSG_URN_CRS_PREFIX = "urn:ogc:def:crs:EPSG::";
    4712        1605 :     if (STARTS_WITH(pszURN, EPSG_URN_CRS_PREFIX) &&
    4713         760 :         CPLGetValueType(pszURN + strlen(EPSG_URN_CRS_PREFIX)) ==
    4714             :             CPL_VALUE_INTEGER)
    4715             :     {
    4716         757 :         return importFromEPSG(atoi(pszURN + strlen(EPSG_URN_CRS_PREFIX)));
    4717             :     }
    4718             : 
    4719         176 :     TAKE_OPTIONAL_LOCK();
    4720             : 
    4721             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4722             : 
    4723             :     // PROJ 8.2.0 has support for IAU codes now.
    4724             : #if !PROJ_AT_LEAST_VERSION(8, 2, 0)
    4725             :     /* -------------------------------------------------------------------- */
    4726             :     /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
    4727             :     /* -------------------------------------------------------------------- */
    4728             :     const char *pszIAU = strstr(pszURN, "IAU");
    4729             :     if (pszIAU)
    4730             :     {
    4731             :         const char *pszCode = strchr(pszIAU, ':');
    4732             :         if (pszCode)
    4733             :         {
    4734             :             ++pszCode;
    4735             :             if (*pszCode == ':')
    4736             :                 ++pszCode;
    4737             :             return importFromDict("IAU2000.wkt", pszCode);
    4738             :         }
    4739             :     }
    4740             : #endif
    4741             : 
    4742             :     if (strlen(pszURN) >= 1000)
    4743             :     {
    4744             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4745             :         return OGRERR_CORRUPT_DATA;
    4746             :     }
    4747             :     auto obj = proj_create(d->getPROJContext(), pszURN);
    4748             :     if (!obj)
    4749             :     {
    4750             :         return OGRERR_FAILURE;
    4751             :     }
    4752             :     Clear();
    4753             :     d->setPjCRS(obj);
    4754             :     return OGRERR_NONE;
    4755             : #else
    4756          88 :     const char *pszCur = nullptr;
    4757             : 
    4758          88 :     if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs:"))
    4759          23 :         pszCur = pszURN + 16;
    4760          65 :     else if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs,crs:"))
    4761           1 :         pszCur = pszURN + 20;
    4762          64 :     else if (STARTS_WITH_CI(pszURN, "urn:x-ogc:def:crs:"))
    4763          62 :         pszCur = pszURN + 18;
    4764           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:crs:"))
    4765           0 :         pszCur = pszURN + 16;
    4766           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:def:crs:"))
    4767           0 :         pszCur = pszURN + 20;
    4768             :     else
    4769             :     {
    4770           2 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4771             :                  pszURN);
    4772           2 :         return OGRERR_FAILURE;
    4773             :     }
    4774             : 
    4775             :     /* -------------------------------------------------------------------- */
    4776             :     /*      Clear any existing definition.                                  */
    4777             :     /* -------------------------------------------------------------------- */
    4778          86 :     Clear();
    4779             : 
    4780             :     /* -------------------------------------------------------------------- */
    4781             :     /*      Find code (ignoring version) out of string like:                */
    4782             :     /*                                                                      */
    4783             :     /*      authority:[version]:code                                        */
    4784             :     /* -------------------------------------------------------------------- */
    4785          86 :     const char *pszAuthority = pszCur;
    4786             : 
    4787             :     // skip authority
    4788         414 :     while (*pszCur != ':' && *pszCur)
    4789         328 :         pszCur++;
    4790          86 :     if (*pszCur == ':')
    4791          86 :         pszCur++;
    4792             : 
    4793             :     // skip version
    4794          86 :     const char *pszBeforeVersion = pszCur;
    4795         387 :     while (*pszCur != ':' && *pszCur)
    4796         301 :         pszCur++;
    4797          86 :     if (*pszCur == ':')
    4798          58 :         pszCur++;
    4799             :     else
    4800             :         // We come here in the case, the content to parse is authority:code
    4801             :         // (instead of authority::code) which is probably illegal according to
    4802             :         // http://www.opengeospatial.org/ogcUrnPolicy but such content is found
    4803             :         // for example in what is returned by GeoServer.
    4804          28 :         pszCur = pszBeforeVersion;
    4805             : 
    4806          86 :     const char *pszCode = pszCur;
    4807             : 
    4808          86 :     const char *pszComma = strchr(pszCur, ',');
    4809          86 :     if (pszComma == nullptr)
    4810          85 :         return importFromURNPart(pszAuthority, pszCode, pszURN);
    4811             : 
    4812             :     // There's a second part with the vertical SRS.
    4813           1 :     pszCur = pszComma + 1;
    4814           1 :     if (!STARTS_WITH(pszCur, "crs:"))
    4815             :     {
    4816           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4817             :                  pszURN);
    4818           0 :         return OGRERR_FAILURE;
    4819             :     }
    4820             : 
    4821           1 :     pszCur += 4;
    4822             : 
    4823           1 :     char *pszFirstCode = CPLStrdup(pszCode);
    4824           1 :     pszFirstCode[pszComma - pszCode] = '\0';
    4825           1 :     OGRErr eStatus = importFromURNPart(pszAuthority, pszFirstCode, pszURN);
    4826           1 :     CPLFree(pszFirstCode);
    4827             : 
    4828             :     // Do we want to turn this into a compound definition
    4829             :     // with a vertical datum?
    4830           1 :     if (eStatus != OGRERR_NONE)
    4831           0 :         return eStatus;
    4832             : 
    4833             :     /* -------------------------------------------------------------------- */
    4834             :     /*      Find code (ignoring version) out of string like:                */
    4835             :     /*                                                                      */
    4836             :     /*      authority:[version]:code                                        */
    4837             :     /* -------------------------------------------------------------------- */
    4838           1 :     pszAuthority = pszCur;
    4839             : 
    4840             :     // skip authority
    4841           5 :     while (*pszCur != ':' && *pszCur)
    4842           4 :         pszCur++;
    4843           1 :     if (*pszCur == ':')
    4844           1 :         pszCur++;
    4845             : 
    4846             :     // skip version
    4847           1 :     pszBeforeVersion = pszCur;
    4848           1 :     while (*pszCur != ':' && *pszCur)
    4849           0 :         pszCur++;
    4850           1 :     if (*pszCur == ':')
    4851           1 :         pszCur++;
    4852             :     else
    4853           0 :         pszCur = pszBeforeVersion;
    4854             : 
    4855           1 :     pszCode = pszCur;
    4856             : 
    4857           2 :     OGRSpatialReference oVertSRS;
    4858           1 :     eStatus = oVertSRS.importFromURNPart(pszAuthority, pszCode, pszURN);
    4859           1 :     if (eStatus == OGRERR_NONE)
    4860             :     {
    4861           1 :         OGRSpatialReference oHorizSRS(*this);
    4862             : 
    4863           1 :         Clear();
    4864             : 
    4865           1 :         oHorizSRS.d->refreshProjObj();
    4866           1 :         oVertSRS.d->refreshProjObj();
    4867           1 :         if (!oHorizSRS.d->m_pj_crs || !oVertSRS.d->m_pj_crs)
    4868           0 :             return OGRERR_FAILURE;
    4869             : 
    4870           1 :         const char *pszHorizName = proj_get_name(oHorizSRS.d->m_pj_crs);
    4871           1 :         const char *pszVertName = proj_get_name(oVertSRS.d->m_pj_crs);
    4872             : 
    4873           2 :         CPLString osName = pszHorizName ? pszHorizName : "";
    4874           1 :         osName += " + ";
    4875           1 :         osName += pszVertName ? pszVertName : "";
    4876             : 
    4877           1 :         SetCompoundCS(osName, &oHorizSRS, &oVertSRS);
    4878             :     }
    4879             : 
    4880           1 :     return eStatus;
    4881             : #endif
    4882             : }
    4883             : 
    4884             : /************************************************************************/
    4885             : /*                           importFromCRSURL()                         */
    4886             : /*                                                                      */
    4887             : /*      See OGC Best Practice document 11-135 for details.              */
    4888             : /************************************************************************/
    4889             : 
    4890             : /**
    4891             :  * \brief Initialize from OGC URL.
    4892             :  *
    4893             :  * Initializes this spatial reference from a coordinate system defined
    4894             :  * by an OGC URL prefixed with "http://opengis.net/def/crs" per best practice
    4895             :  * paper 11-135.  Currently EPSG and OGC authority values are supported,
    4896             :  * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
    4897             :  *
    4898             :  * This method is also supported through SetFromUserInput() which can
    4899             :  * normally be used for URLs.
    4900             :  *
    4901             :  * @param pszURL the URL string.
    4902             :  *
    4903             :  * @return OGRERR_NONE on success or an error code.
    4904             :  */
    4905             : 
    4906        7299 : OGRErr OGRSpatialReference::importFromCRSURL(const char *pszURL)
    4907             : 
    4908             : {
    4909       14598 :     TAKE_OPTIONAL_LOCK();
    4910             : 
    4911             : #if !PROJ_AT_LEAST_VERSION(9, 1, 0)
    4912        7299 :     if (strcmp(pszURL, "http://www.opengis.net/def/crs/OGC/0/CRS84h") == 0)
    4913             :     {
    4914          12 :         PJ *obj = proj_create(
    4915             :             d->getPROJContext(),
    4916             :             "GEOGCRS[\"WGS 84 longitude-latitude-height\",\n"
    4917             :             "    ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n"
    4918             :             "        MEMBER[\"World Geodetic System 1984 (Transit)\"],\n"
    4919             :             "        MEMBER[\"World Geodetic System 1984 (G730)\"],\n"
    4920             :             "        MEMBER[\"World Geodetic System 1984 (G873)\"],\n"
    4921             :             "        MEMBER[\"World Geodetic System 1984 (G1150)\"],\n"
    4922             :             "        MEMBER[\"World Geodetic System 1984 (G1674)\"],\n"
    4923             :             "        MEMBER[\"World Geodetic System 1984 (G1762)\"],\n"
    4924             :             "        MEMBER[\"World Geodetic System 1984 (G2139)\"],\n"
    4925             :             "        MEMBER[\"World Geodetic System 1984 (G2296)\"],\n"
    4926             :             "        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
    4927             :             "            LENGTHUNIT[\"metre\",1]],\n"
    4928             :             "        ENSEMBLEACCURACY[2.0]],\n"
    4929             :             "    PRIMEM[\"Greenwich\",0,\n"
    4930             :             "        ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4931             :             "    CS[ellipsoidal,3],\n"
    4932             :             "        AXIS[\"geodetic longitude (Lon)\",east,\n"
    4933             :             "            ORDER[1],\n"
    4934             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4935             :             "        AXIS[\"geodetic latitude (Lat)\",north,\n"
    4936             :             "            ORDER[2],\n"
    4937             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4938             :             "        AXIS[\"ellipsoidal height (h)\",up,\n"
    4939             :             "            ORDER[3],\n"
    4940             :             "            LENGTHUNIT[\"metre\",1]],\n"
    4941             :             "    USAGE[\n"
    4942             :             "        SCOPE[\"3D system frequently used in GIS, Web APIs and "
    4943             :             "Web applications\"],\n"
    4944             :             "        AREA[\"World.\"],\n"
    4945             :             "        BBOX[-90,-180,90,180]],\n"
    4946             :             "    ID[\"OGC\",\"CRS84h\"]]");
    4947          12 :         if (!obj)
    4948             :         {
    4949           0 :             return OGRERR_FAILURE;
    4950             :         }
    4951          12 :         Clear();
    4952          12 :         d->setPjCRS(obj);
    4953          12 :         return OGRERR_NONE;
    4954             :     }
    4955             : #endif
    4956             : 
    4957             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4958             :     if (strlen(pszURL) >= 10000)
    4959             :     {
    4960             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4961             :         return OGRERR_CORRUPT_DATA;
    4962             :     }
    4963             : 
    4964             :     PJ *obj;
    4965             : #if !PROJ_AT_LEAST_VERSION(9, 2, 0)
    4966             :     if (STARTS_WITH(pszURL, "http://www.opengis.net/def/crs/IAU/0/"))
    4967             :     {
    4968             :         obj = proj_create(
    4969             :             d->getPROJContext(),
    4970             :             CPLSPrintf("IAU:%s",
    4971             :                        pszURL +
    4972             :                            strlen("http://www.opengis.net/def/crs/IAU/0/")));
    4973             :     }
    4974             :     else
    4975             : #endif
    4976             :     {
    4977             :         obj = proj_create(d->getPROJContext(), pszURL);
    4978             :     }
    4979             :     if (!obj)
    4980             :     {
    4981             :         return OGRERR_FAILURE;
    4982             :     }
    4983             :     Clear();
    4984             :     d->setPjCRS(obj);
    4985             :     return OGRERR_NONE;
    4986             : #else
    4987        7287 :     const char *pszCur = nullptr;
    4988             : 
    4989        7287 :     if (STARTS_WITH_CI(pszURL, "http://opengis.net/def/crs"))
    4990           2 :         pszCur = pszURL + 26;
    4991        7285 :     else if (STARTS_WITH_CI(pszURL, "https://opengis.net/def/crs"))
    4992           1 :         pszCur = pszURL + 27;
    4993        7284 :     else if (STARTS_WITH_CI(pszURL, "http://www.opengis.net/def/crs"))
    4994        7283 :         pszCur = pszURL + 30;
    4995           1 :     else if (STARTS_WITH_CI(pszURL, "https://www.opengis.net/def/crs"))
    4996           1 :         pszCur = pszURL + 31;
    4997           0 :     else if (STARTS_WITH_CI(pszURL, "www.opengis.net/def/crs"))
    4998           0 :         pszCur = pszURL + 23;
    4999             :     else
    5000             :     {
    5001           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s not a supported format.",
    5002             :                  pszURL);
    5003           0 :         return OGRERR_FAILURE;
    5004             :     }
    5005             : 
    5006        7287 :     if (*pszCur == '\0')
    5007             :     {
    5008           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s malformed.", pszURL);
    5009           0 :         return OGRERR_FAILURE;
    5010             :     }
    5011             : 
    5012             :     /* -------------------------------------------------------------------- */
    5013             :     /*      Clear any existing definition.                                  */
    5014             :     /* -------------------------------------------------------------------- */
    5015        7287 :     Clear();
    5016             : 
    5017        7287 :     if (STARTS_WITH_CI(pszCur, "-compound?1="))
    5018             :     {
    5019             :         /* --------------------------------------------------------------------
    5020             :          */
    5021             :         /*      It's a compound CRS, of the form: */
    5022             :         /*                                                                      */
    5023             :         /*      http://opengis.net/def/crs-compound?1=URL1&2=URL2&3=URL3&.. */
    5024             :         /* --------------------------------------------------------------------
    5025             :          */
    5026           1 :         pszCur += 12;
    5027             : 
    5028             :         // Extract each component CRS URL.
    5029           1 :         int iComponentUrl = 2;
    5030             : 
    5031           2 :         CPLString osName = "";
    5032           1 :         Clear();
    5033             : 
    5034           3 :         while (iComponentUrl != -1)
    5035             :         {
    5036           2 :             char searchStr[15] = {};
    5037           2 :             snprintf(searchStr, sizeof(searchStr), "&%d=", iComponentUrl);
    5038             : 
    5039           2 :             const char *pszUrlEnd = strstr(pszCur, searchStr);
    5040             : 
    5041             :             // Figure out the next component URL.
    5042           2 :             char *pszComponentUrl = nullptr;
    5043             : 
    5044           2 :             if (pszUrlEnd)
    5045             :             {
    5046           1 :                 size_t nLen = pszUrlEnd - pszCur;
    5047           1 :                 pszComponentUrl = static_cast<char *>(CPLMalloc(nLen + 1));
    5048           1 :                 strncpy(pszComponentUrl, pszCur, nLen);
    5049           1 :                 pszComponentUrl[nLen] = '\0';
    5050             : 
    5051           1 :                 ++iComponentUrl;
    5052           1 :                 pszCur += nLen + strlen(searchStr);
    5053             :             }
    5054             :             else
    5055             :             {
    5056           1 :                 if (iComponentUrl == 2)
    5057             :                 {
    5058           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5059             :                              "Compound CRS URLs must have at least two "
    5060             :                              "component CRSs.");
    5061           0 :                     return OGRERR_FAILURE;
    5062             :                 }
    5063             :                 else
    5064             :                 {
    5065           1 :                     pszComponentUrl = CPLStrdup(pszCur);
    5066             :                     // no more components
    5067           1 :                     iComponentUrl = -1;
    5068             :                 }
    5069             :             }
    5070             : 
    5071           2 :             OGRSpatialReference oComponentSRS;
    5072           2 :             OGRErr eStatus = oComponentSRS.importFromCRSURL(pszComponentUrl);
    5073             : 
    5074           2 :             CPLFree(pszComponentUrl);
    5075           2 :             pszComponentUrl = nullptr;
    5076             : 
    5077           2 :             if (eStatus == OGRERR_NONE)
    5078             :             {
    5079           2 :                 if (osName.length() != 0)
    5080             :                 {
    5081           1 :                     osName += " + ";
    5082             :                 }
    5083           2 :                 osName += oComponentSRS.GetRoot()->GetValue();
    5084           2 :                 SetNode("COMPD_CS", osName);
    5085           2 :                 GetRoot()->AddChild(oComponentSRS.GetRoot()->Clone());
    5086             :             }
    5087             :             else
    5088           0 :                 return eStatus;
    5089             :         }
    5090             : 
    5091           1 :         return OGRERR_NONE;
    5092             :     }
    5093             : 
    5094             :     /* -------------------------------------------------------------------- */
    5095             :     /*      It's a normal CRS URL, of the form:                             */
    5096             :     /*                                                                      */
    5097             :     /*      http://opengis.net/def/crs/AUTHORITY/VERSION/CODE               */
    5098             :     /* -------------------------------------------------------------------- */
    5099        7286 :     ++pszCur;
    5100        7286 :     const char *pszAuthority = pszCur;
    5101             : 
    5102             :     // skip authority
    5103      135343 :     while (*pszCur != '/' && *pszCur)
    5104      128057 :         pszCur++;
    5105        7286 :     if (*pszCur == '/')
    5106        7285 :         pszCur++;
    5107             : 
    5108             :     // skip version
    5109       16709 :     while (*pszCur != '/' && *pszCur)
    5110        9423 :         pszCur++;
    5111        7286 :     if (*pszCur == '/')
    5112        7285 :         pszCur++;
    5113             : 
    5114        7286 :     const char *pszCode = pszCur;
    5115             : 
    5116        7286 :     return importFromURNPart(pszAuthority, pszCode, pszURL);
    5117             : #endif
    5118             : }
    5119             : 
    5120             : /************************************************************************/
    5121             : /*                         importFromWMSAUTO()                          */
    5122             : /************************************************************************/
    5123             : 
    5124             : /**
    5125             :  * \brief Initialize from WMSAUTO string.
    5126             :  *
    5127             :  * Note that the WMS 1.3 specification does not include the
    5128             :  * units code, while apparently earlier specs do.  We try to
    5129             :  * guess around this.
    5130             :  *
    5131             :  * @param pszDefinition the WMSAUTO string
    5132             :  *
    5133             :  * @return OGRERR_NONE on success or an error code.
    5134             :  */
    5135           3 : OGRErr OGRSpatialReference::importFromWMSAUTO(const char *pszDefinition)
    5136             : 
    5137             : {
    5138           6 :     TAKE_OPTIONAL_LOCK();
    5139             : 
    5140             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    5141             :     if (strlen(pszDefinition) >= 10000)
    5142             :     {
    5143             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    5144             :         return OGRERR_CORRUPT_DATA;
    5145             :     }
    5146             : 
    5147             :     auto obj = proj_create(d->getPROJContext(), pszDefinition);
    5148             :     if (!obj)
    5149             :     {
    5150             :         return OGRERR_FAILURE;
    5151             :     }
    5152             :     Clear();
    5153             :     d->setPjCRS(obj);
    5154             :     return OGRERR_NONE;
    5155             : #else
    5156             :     int nProjId, nUnitsId;
    5157           3 :     double dfRefLong, dfRefLat = 0.0;
    5158             : 
    5159             :     /* -------------------------------------------------------------------- */
    5160             :     /*      Tokenize                                                        */
    5161             :     /* -------------------------------------------------------------------- */
    5162           3 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    5163           3 :         pszDefinition += 5;
    5164             : 
    5165             :     char **papszTokens =
    5166           3 :         CSLTokenizeStringComplex(pszDefinition, ",", FALSE, TRUE);
    5167             : 
    5168           3 :     if (CSLCount(papszTokens) == 4)
    5169             :     {
    5170           0 :         nProjId = atoi(papszTokens[0]);
    5171           0 :         nUnitsId = atoi(papszTokens[1]);
    5172           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    5173           0 :         dfRefLat = CPLAtof(papszTokens[3]);
    5174             :     }
    5175           3 :     else if (CSLCount(papszTokens) == 3 && atoi(papszTokens[0]) == 42005)
    5176             :     {
    5177           0 :         nProjId = atoi(papszTokens[0]);
    5178           0 :         nUnitsId = atoi(papszTokens[1]);
    5179           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    5180           0 :         dfRefLat = 0.0;
    5181             :     }
    5182           3 :     else if (CSLCount(papszTokens) == 3)
    5183             :     {
    5184           2 :         nProjId = atoi(papszTokens[0]);
    5185           2 :         nUnitsId = 9001;
    5186           2 :         dfRefLong = CPLAtof(papszTokens[1]);
    5187           2 :         dfRefLat = CPLAtof(papszTokens[2]);
    5188             :     }
    5189           1 :     else if (CSLCount(papszTokens) == 2 && atoi(papszTokens[0]) == 42005)
    5190             :     {
    5191           0 :         nProjId = atoi(papszTokens[0]);
    5192           0 :         nUnitsId = 9001;
    5193           0 :         dfRefLong = CPLAtof(papszTokens[1]);
    5194             :     }
    5195             :     else
    5196             :     {
    5197           1 :         CSLDestroy(papszTokens);
    5198           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    5199             :                  "AUTO projection has wrong number of arguments, expected\n"
    5200             :                  "AUTO:proj_id,units_id,ref_long,ref_lat or"
    5201             :                  "AUTO:proj_id,ref_long,ref_lat");
    5202           1 :         return OGRERR_FAILURE;
    5203             :     }
    5204             : 
    5205           2 :     CSLDestroy(papszTokens);
    5206           2 :     papszTokens = nullptr;
    5207             : 
    5208             :     /* -------------------------------------------------------------------- */
    5209             :     /*      Build coordsys.                                                 */
    5210             :     /* -------------------------------------------------------------------- */
    5211           2 :     Clear();
    5212             : 
    5213             :     /* -------------------------------------------------------------------- */
    5214             :     /*      Set WGS84.                                                      */
    5215             :     /* -------------------------------------------------------------------- */
    5216           2 :     SetWellKnownGeogCS("WGS84");
    5217             : 
    5218           2 :     switch (nProjId)
    5219             :     {
    5220           2 :         case 42001:  // Auto UTM
    5221           2 :             SetUTM(static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
    5222             :                    dfRefLat >= 0.0);
    5223           2 :             break;
    5224             : 
    5225           0 :         case 42002:  // Auto TM (strangely very UTM-like).
    5226           0 :             SetTM(0, dfRefLong, 0.9996, 500000.0,
    5227             :                   (dfRefLat >= 0.0) ? 0.0 : 10000000.0);
    5228           0 :             break;
    5229             : 
    5230           0 :         case 42003:  // Auto Orthographic.
    5231           0 :             SetOrthographic(dfRefLat, dfRefLong, 0.0, 0.0);
    5232           0 :             break;
    5233             : 
    5234           0 :         case 42004:  // Auto Equirectangular
    5235           0 :             SetEquirectangular(dfRefLat, dfRefLong, 0.0, 0.0);
    5236           0 :             break;
    5237             : 
    5238           0 :         case 42005:
    5239           0 :             SetMollweide(dfRefLong, 0.0, 0.0);
    5240           0 :             break;
    5241             : 
    5242           0 :         default:
    5243           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5244             :                      "Unsupported projection id in importFromWMSAUTO(): %d",
    5245             :                      nProjId);
    5246           0 :             return OGRERR_FAILURE;
    5247             :     }
    5248             : 
    5249             :     /* -------------------------------------------------------------------- */
    5250             :     /*      Set units.                                                      */
    5251             :     /* -------------------------------------------------------------------- */
    5252             : 
    5253           2 :     switch (nUnitsId)
    5254             :     {
    5255           2 :         case 9001:
    5256           2 :             SetTargetLinearUnits(nullptr, SRS_UL_METER, 1.0, "EPSG", "9001");
    5257           2 :             break;
    5258             : 
    5259           0 :         case 9002:
    5260           0 :             SetTargetLinearUnits(nullptr, "Foot", 0.3048, "EPSG", "9002");
    5261           0 :             break;
    5262             : 
    5263           0 :         case 9003:
    5264           0 :             SetTargetLinearUnits(nullptr, "US survey foot",
    5265             :                                  CPLAtof(SRS_UL_US_FOOT_CONV), "EPSG", "9003");
    5266           0 :             break;
    5267             : 
    5268           0 :         default:
    5269           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5270             :                      "Unsupported units code (%d).", nUnitsId);
    5271           0 :             return OGRERR_FAILURE;
    5272             :             break;
    5273             :     }
    5274             : 
    5275           2 :     return OGRERR_NONE;
    5276             : #endif
    5277             : }
    5278             : 
    5279             : /************************************************************************/
    5280             : /*                            GetSemiMajor()                            */
    5281             : /************************************************************************/
    5282             : 
    5283             : /**
    5284             :  * \brief Get spheroid semi major axis (in metres starting with GDAL 3.0)
    5285             :  *
    5286             :  * This method does the same thing as the C function OSRGetSemiMajor().
    5287             :  *
    5288             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if semi major axis
    5289             :  * can be found.
    5290             :  *
    5291             :  * @return semi-major axis, or SRS_WGS84_SEMIMAJOR if it can't be found.
    5292             :  */
    5293             : 
    5294        7004 : double OGRSpatialReference::GetSemiMajor(OGRErr *pnErr) const
    5295             : 
    5296             : {
    5297       14008 :     TAKE_OPTIONAL_LOCK();
    5298             : 
    5299        7004 :     if (pnErr != nullptr)
    5300        3634 :         *pnErr = OGRERR_FAILURE;
    5301             : 
    5302        7004 :     d->refreshProjObj();
    5303        7004 :     if (!d->m_pj_crs)
    5304         111 :         return SRS_WGS84_SEMIMAJOR;
    5305             : 
    5306        6893 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5307        6893 :     if (!ellps)
    5308           5 :         return SRS_WGS84_SEMIMAJOR;
    5309             : 
    5310        6888 :     double dfSemiMajor = 0.0;
    5311        6888 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, &dfSemiMajor,
    5312             :                                   nullptr, nullptr, nullptr);
    5313        6888 :     proj_destroy(ellps);
    5314             : 
    5315        6888 :     if (dfSemiMajor > 0)
    5316             :     {
    5317        6888 :         if (pnErr != nullptr)
    5318        3520 :             *pnErr = OGRERR_NONE;
    5319        6888 :         return dfSemiMajor;
    5320             :     }
    5321             : 
    5322           0 :     return SRS_WGS84_SEMIMAJOR;
    5323             : }
    5324             : 
    5325             : /************************************************************************/
    5326             : /*                          OSRGetSemiMajor()                           */
    5327             : /************************************************************************/
    5328             : 
    5329             : /**
    5330             :  * \brief Get spheroid semi major axis.
    5331             :  *
    5332             :  * This function is the same as OGRSpatialReference::GetSemiMajor()
    5333             :  */
    5334          89 : double OSRGetSemiMajor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5335             : 
    5336             : {
    5337          89 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMajor", 0);
    5338             : 
    5339          89 :     return ToPointer(hSRS)->GetSemiMajor(pnErr);
    5340             : }
    5341             : 
    5342             : /************************************************************************/
    5343             : /*                          GetInvFlattening()                          */
    5344             : /************************************************************************/
    5345             : 
    5346             : /**
    5347             :  * \brief Get spheroid inverse flattening.
    5348             :  *
    5349             :  * This method does the same thing as the C function OSRGetInvFlattening().
    5350             :  *
    5351             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if no inverse flattening
    5352             :  * can be found.
    5353             :  *
    5354             :  * @return inverse flattening, or SRS_WGS84_INVFLATTENING if it can't be found.
    5355             :  */
    5356             : 
    5357        4610 : double OGRSpatialReference::GetInvFlattening(OGRErr *pnErr) const
    5358             : 
    5359             : {
    5360        9220 :     TAKE_OPTIONAL_LOCK();
    5361             : 
    5362        4610 :     if (pnErr != nullptr)
    5363        3524 :         *pnErr = OGRERR_FAILURE;
    5364             : 
    5365        4610 :     d->refreshProjObj();
    5366        4610 :     if (!d->m_pj_crs)
    5367         111 :         return SRS_WGS84_INVFLATTENING;
    5368             : 
    5369        4499 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5370        4499 :     if (!ellps)
    5371           2 :         return SRS_WGS84_INVFLATTENING;
    5372             : 
    5373        4497 :     double dfInvFlattening = -1.0;
    5374        4497 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, nullptr, nullptr,
    5375             :                                   nullptr, &dfInvFlattening);
    5376        4497 :     proj_destroy(ellps);
    5377             : 
    5378        4497 :     if (dfInvFlattening >= 0.0)
    5379             :     {
    5380        4497 :         if (pnErr != nullptr)
    5381        3413 :             *pnErr = OGRERR_NONE;
    5382        4497 :         return dfInvFlattening;
    5383             :     }
    5384             : 
    5385           0 :     return SRS_WGS84_INVFLATTENING;
    5386             : }
    5387             : 
    5388             : /************************************************************************/
    5389             : /*                        OSRGetInvFlattening()                         */
    5390             : /************************************************************************/
    5391             : 
    5392             : /**
    5393             :  * \brief Get spheroid inverse flattening.
    5394             :  *
    5395             :  * This function is the same as OGRSpatialReference::GetInvFlattening()
    5396             :  */
    5397          10 : double OSRGetInvFlattening(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5398             : 
    5399             : {
    5400          10 :     VALIDATE_POINTER1(hSRS, "OSRGetInvFlattening", 0);
    5401             : 
    5402          10 :     return ToPointer(hSRS)->GetInvFlattening(pnErr);
    5403             : }
    5404             : 
    5405             : /************************************************************************/
    5406             : /*                          GetEccentricity()                           */
    5407             : /************************************************************************/
    5408             : 
    5409             : /**
    5410             :  * \brief Get spheroid eccentricity
    5411             :  *
    5412             :  * @return eccentricity (or -1 in case of error)
    5413             :  */
    5414             : 
    5415           0 : double OGRSpatialReference::GetEccentricity() const
    5416             : 
    5417             : {
    5418           0 :     OGRErr eErr = OGRERR_NONE;
    5419           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5420           0 :     if (eErr != OGRERR_NONE)
    5421             :     {
    5422           0 :         return -1.0;
    5423             :     }
    5424           0 :     if (dfInvFlattening == 0.0)
    5425           0 :         return 0.0;
    5426           0 :     if (dfInvFlattening < 0.5)
    5427           0 :         return -1.0;
    5428           0 :     return sqrt(2.0 / dfInvFlattening -
    5429           0 :                 1.0 / (dfInvFlattening * dfInvFlattening));
    5430             : }
    5431             : 
    5432             : /************************************************************************/
    5433             : /*                       GetSquaredEccentricity()                       */
    5434             : /************************************************************************/
    5435             : 
    5436             : /**
    5437             :  * \brief Get spheroid squared eccentricity
    5438             :  *
    5439             :  * @return squared eccentricity (or -1 in case of error)
    5440             :  */
    5441             : 
    5442           0 : double OGRSpatialReference::GetSquaredEccentricity() const
    5443             : 
    5444             : {
    5445           0 :     OGRErr eErr = OGRERR_NONE;
    5446           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5447           0 :     if (eErr != OGRERR_NONE)
    5448             :     {
    5449           0 :         return -1.0;
    5450             :     }
    5451           0 :     if (dfInvFlattening == 0.0)
    5452           0 :         return 0.0;
    5453           0 :     if (dfInvFlattening < 0.5)
    5454           0 :         return -1.0;
    5455           0 :     return 2.0 / dfInvFlattening - 1.0 / (dfInvFlattening * dfInvFlattening);
    5456             : }
    5457             : 
    5458             : /************************************************************************/
    5459             : /*                            GetSemiMinor()                            */
    5460             : /************************************************************************/
    5461             : 
    5462             : /**
    5463             :  * \brief Get spheroid semi minor axis.
    5464             :  *
    5465             :  * This method does the same thing as the C function OSRGetSemiMinor().
    5466             :  *
    5467             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if semi minor axis
    5468             :  * can be found.
    5469             :  *
    5470             :  * @return semi-minor axis, or WGS84 semi minor if it can't be found.
    5471             :  */
    5472             : 
    5473         651 : double OGRSpatialReference::GetSemiMinor(OGRErr *pnErr) const
    5474             : 
    5475             : {
    5476         651 :     const double dfSemiMajor = GetSemiMajor(pnErr);
    5477         651 :     const double dfInvFlattening = GetInvFlattening(pnErr);
    5478             : 
    5479         651 :     return OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
    5480             : }
    5481             : 
    5482             : /************************************************************************/
    5483             : /*                          OSRGetSemiMinor()                           */
    5484             : /************************************************************************/
    5485             : 
    5486             : /**
    5487             :  * \brief Get spheroid semi minor axis.
    5488             :  *
    5489             :  * This function is the same as OGRSpatialReference::GetSemiMinor()
    5490             :  */
    5491           4 : double OSRGetSemiMinor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5492             : 
    5493             : {
    5494           4 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMinor", 0);
    5495             : 
    5496           4 :     return ToPointer(hSRS)->GetSemiMinor(pnErr);
    5497             : }
    5498             : 
    5499             : /************************************************************************/
    5500             : /*                             SetLocalCS()                             */
    5501             : /************************************************************************/
    5502             : 
    5503             : /**
    5504             :  * \brief Set the user visible LOCAL_CS name.
    5505             :  *
    5506             :  * This method is the same as the C function OSRSetLocalCS().
    5507             :  *
    5508             :  * This method will ensure a LOCAL_CS node is created as the root,
    5509             :  * and set the provided name on it.  It must be used before SetLinearUnits().
    5510             :  *
    5511             :  * @param pszName the user visible name to assign.  Not used as a key.
    5512             :  *
    5513             :  * @return OGRERR_NONE on success.
    5514             :  */
    5515             : 
    5516        2900 : OGRErr OGRSpatialReference::SetLocalCS(const char *pszName)
    5517             : 
    5518             : {
    5519        5800 :     TAKE_OPTIONAL_LOCK();
    5520             : 
    5521        2900 :     if (d->m_pjType == PJ_TYPE_UNKNOWN ||
    5522           0 :         d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    5523             :     {
    5524        2900 :         d->setPjCRS(proj_create_engineering_crs(d->getPROJContext(), pszName));
    5525             :     }
    5526             :     else
    5527             :     {
    5528           0 :         CPLDebug("OGR",
    5529             :                  "OGRSpatialReference::SetLocalCS(%s) failed.  "
    5530             :                  "It appears an incompatible object already exists.",
    5531             :                  pszName);
    5532           0 :         return OGRERR_FAILURE;
    5533             :     }
    5534             : 
    5535        2900 :     return OGRERR_NONE;
    5536             : }
    5537             : 
    5538             : /************************************************************************/
    5539             : /*                           OSRSetLocalCS()                            */
    5540             : /************************************************************************/
    5541             : 
    5542             : /**
    5543             :  * \brief Set the user visible LOCAL_CS name.
    5544             :  *
    5545             :  * This function is the same as OGRSpatialReference::SetLocalCS()
    5546             :  */
    5547           1 : OGRErr OSRSetLocalCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5548             : 
    5549             : {
    5550           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLocalCS", OGRERR_FAILURE);
    5551             : 
    5552           1 :     return ToPointer(hSRS)->SetLocalCS(pszName);
    5553             : }
    5554             : 
    5555             : /************************************************************************/
    5556             : /*                             SetGeocCS()                              */
    5557             : /************************************************************************/
    5558             : 
    5559             : /**
    5560             :  * \brief Set the user visible GEOCCS name.
    5561             :  *
    5562             :  * This method is the same as the C function OSRSetGeocCS().
    5563             : 
    5564             :  * This method will ensure a GEOCCS node is created as the root,
    5565             :  * and set the provided name on it.  If used on a GEOGCS coordinate system,
    5566             :  * the DATUM and PRIMEM nodes from the GEOGCS will be transferred over to
    5567             :  * the GEOGCS.
    5568             :  *
    5569             :  * @param pszName the user visible name to assign.  Not used as a key.
    5570             :  *
    5571             :  * @return OGRERR_NONE on success.
    5572             :  *
    5573             :  */
    5574             : 
    5575           6 : OGRErr OGRSpatialReference::SetGeocCS(const char *pszName)
    5576             : 
    5577             : {
    5578          12 :     TAKE_OPTIONAL_LOCK();
    5579             : 
    5580           6 :     OGRErr eErr = OGRERR_NONE;
    5581           6 :     d->refreshProjObj();
    5582           6 :     d->demoteFromBoundCRS();
    5583           6 :     if (d->m_pjType == PJ_TYPE_UNKNOWN)
    5584             :     {
    5585           3 :         d->setPjCRS(proj_create_geocentric_crs(
    5586             :             d->getPROJContext(), pszName, "World Geodetic System 1984",
    5587             :             "WGS 84", SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING,
    5588             :             SRS_PM_GREENWICH, 0.0, SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV),
    5589             :             "Metre", 1.0));
    5590             :     }
    5591           3 :     else if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    5592             :     {
    5593           1 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5594             :     }
    5595           3 :     else if (d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    5596           1 :              d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    5597             :     {
    5598           1 :         auto datum = proj_crs_get_datum(d->getPROJContext(), d->m_pj_crs);
    5599             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    5600             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    5601             :         if (datum == nullptr)
    5602             :         {
    5603             :             datum =
    5604             :                 proj_crs_get_datum_ensemble(d->getPROJContext(), d->m_pj_crs);
    5605             :         }
    5606             : #endif
    5607           1 :         if (datum == nullptr)
    5608             :         {
    5609           0 :             d->undoDemoteFromBoundCRS();
    5610           0 :             return OGRERR_FAILURE;
    5611             :         }
    5612             : 
    5613           1 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    5614           1 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, nullptr,
    5615             :             0.0);
    5616           1 :         d->setPjCRS(pj_crs);
    5617             : 
    5618           1 :         proj_destroy(datum);
    5619             :     }
    5620             :     else
    5621             :     {
    5622           1 :         CPLDebug("OGR",
    5623             :                  "OGRSpatialReference::SetGeocCS(%s) failed.  "
    5624             :                  "It appears an incompatible object already exists.",
    5625             :                  pszName);
    5626           1 :         eErr = OGRERR_FAILURE;
    5627             :     }
    5628           6 :     d->undoDemoteFromBoundCRS();
    5629             : 
    5630           6 :     return eErr;
    5631             : }
    5632             : 
    5633             : /************************************************************************/
    5634             : /*                            OSRSetGeocCS()                            */
    5635             : /************************************************************************/
    5636             : 
    5637             : /**
    5638             :  * \brief Set the user visible PROJCS name.
    5639             :  *
    5640             :  * This function is the same as OGRSpatialReference::SetGeocCS()
    5641             :  *
    5642             :  */
    5643           4 : OGRErr OSRSetGeocCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5644             : 
    5645             : {
    5646           4 :     VALIDATE_POINTER1(hSRS, "OSRSetGeocCS", OGRERR_FAILURE);
    5647             : 
    5648           4 :     return ToPointer(hSRS)->SetGeocCS(pszName);
    5649             : }
    5650             : 
    5651             : /************************************************************************/
    5652             : /*                             SetVertCS()                              */
    5653             : /************************************************************************/
    5654             : 
    5655             : /**
    5656             :  * \brief Set the user visible VERT_CS name.
    5657             :  *
    5658             :  * This method is the same as the C function OSRSetVertCS().
    5659             : 
    5660             :  * This method will ensure a VERT_CS node is created if needed.  If the
    5661             :  * existing coordinate system is GEOGCS or PROJCS rooted, then it will be
    5662             :  * turned into a COMPD_CS.
    5663             :  *
    5664             :  * @param pszVertCSName the user visible name of the vertical coordinate
    5665             :  * system. Not used as a key.
    5666             :  *
    5667             :  * @param pszVertDatumName the user visible name of the vertical datum.  It
    5668             :  * is helpful if this matches the EPSG name.
    5669             :  *
    5670             :  * @param nVertDatumType the OGC vertical datum type. Ignored
    5671             :  *
    5672             :  * @return OGRERR_NONE on success.
    5673             :  *
    5674             :  */
    5675             : 
    5676           1 : OGRErr OGRSpatialReference::SetVertCS(const char *pszVertCSName,
    5677             :                                       const char *pszVertDatumName,
    5678             :                                       int nVertDatumType)
    5679             : 
    5680             : {
    5681           1 :     TAKE_OPTIONAL_LOCK();
    5682             : 
    5683           1 :     CPL_IGNORE_RET_VAL(nVertDatumType);
    5684             : 
    5685           1 :     d->refreshProjObj();
    5686             : 
    5687           1 :     auto vertCRS = proj_create_vertical_crs(d->getPROJContext(), pszVertCSName,
    5688             :                                             pszVertDatumName, nullptr, 0.0);
    5689             : 
    5690             :     /* -------------------------------------------------------------------- */
    5691             :     /*      Handle the case where we want to make a compound coordinate     */
    5692             :     /*      system.                                                         */
    5693             :     /* -------------------------------------------------------------------- */
    5694           1 :     if (IsProjected() || IsGeographic())
    5695             :     {
    5696           1 :         auto compoundCRS = proj_create_compound_crs(
    5697           1 :             d->getPROJContext(), nullptr, d->m_pj_crs, vertCRS);
    5698           1 :         proj_destroy(vertCRS);
    5699           1 :         d->setPjCRS(compoundCRS);
    5700             :     }
    5701             :     else
    5702             :     {
    5703           0 :         d->setPjCRS(vertCRS);
    5704             :     }
    5705           2 :     return OGRERR_NONE;
    5706             : }
    5707             : 
    5708             : /************************************************************************/
    5709             : /*                            OSRSetVertCS()                            */
    5710             : /************************************************************************/
    5711             : 
    5712             : /**
    5713             :  * \brief Setup the vertical coordinate system.
    5714             :  *
    5715             :  * This function is the same as OGRSpatialReference::SetVertCS()
    5716             :  *
    5717             :  */
    5718           0 : OGRErr OSRSetVertCS(OGRSpatialReferenceH hSRS, const char *pszVertCSName,
    5719             :                     const char *pszVertDatumName, int nVertDatumType)
    5720             : 
    5721             : {
    5722           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVertCS", OGRERR_FAILURE);
    5723             : 
    5724           0 :     return ToPointer(hSRS)->SetVertCS(pszVertCSName, pszVertDatumName,
    5725           0 :                                       nVertDatumType);
    5726             : }
    5727             : 
    5728             : /************************************************************************/
    5729             : /*                           SetCompoundCS()                            */
    5730             : /************************************************************************/
    5731             : 
    5732             : /**
    5733             :  * \brief Setup a compound coordinate system.
    5734             :  *
    5735             :  * This method is the same as the C function OSRSetCompoundCS().
    5736             : 
    5737             :  * This method is replace the current SRS with a COMPD_CS coordinate system
    5738             :  * consisting of the passed in horizontal and vertical coordinate systems.
    5739             :  *
    5740             :  * @param pszName the name of the compound coordinate system.
    5741             :  *
    5742             :  * @param poHorizSRS the horizontal SRS (PROJCS or GEOGCS).
    5743             :  *
    5744             :  * @param poVertSRS the vertical SRS (VERT_CS).
    5745             :  *
    5746             :  * @return OGRERR_NONE on success.
    5747             :  */
    5748             : 
    5749         183 : OGRErr OGRSpatialReference::SetCompoundCS(const char *pszName,
    5750             :                                           const OGRSpatialReference *poHorizSRS,
    5751             :                                           const OGRSpatialReference *poVertSRS)
    5752             : 
    5753             : {
    5754         366 :     TAKE_OPTIONAL_LOCK();
    5755             : 
    5756             :     /* -------------------------------------------------------------------- */
    5757             :     /*      Verify these are legal horizontal and vertical coordinate       */
    5758             :     /*      systems.                                                        */
    5759             :     /* -------------------------------------------------------------------- */
    5760         183 :     if (!poVertSRS->IsVertical())
    5761             :     {
    5762           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5763             :                  "SetCompoundCS() fails, vertical component is not VERT_CS.");
    5764           0 :         return OGRERR_FAILURE;
    5765             :     }
    5766         183 :     if (!poHorizSRS->IsProjected() && !poHorizSRS->IsGeographic())
    5767             :     {
    5768           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5769             :                  "SetCompoundCS() fails, horizontal component is not PROJCS or "
    5770             :                  "GEOGCS.");
    5771           0 :         return OGRERR_FAILURE;
    5772             :     }
    5773             : 
    5774             :     /* -------------------------------------------------------------------- */
    5775             :     /*      Replace with compound srs.                                      */
    5776             :     /* -------------------------------------------------------------------- */
    5777         183 :     Clear();
    5778             : 
    5779         183 :     auto compoundCRS = proj_create_compound_crs(d->getPROJContext(), pszName,
    5780         183 :                                                 poHorizSRS->d->m_pj_crs,
    5781         183 :                                                 poVertSRS->d->m_pj_crs);
    5782         183 :     d->setPjCRS(compoundCRS);
    5783             : 
    5784         183 :     return OGRERR_NONE;
    5785             : }
    5786             : 
    5787             : /************************************************************************/
    5788             : /*                          OSRSetCompoundCS()                          */
    5789             : /************************************************************************/
    5790             : 
    5791             : /**
    5792             :  * \brief Setup a compound coordinate system.
    5793             :  *
    5794             :  * This function is the same as OGRSpatialReference::SetCompoundCS()
    5795             :  */
    5796           8 : OGRErr OSRSetCompoundCS(OGRSpatialReferenceH hSRS, const char *pszName,
    5797             :                         OGRSpatialReferenceH hHorizSRS,
    5798             :                         OGRSpatialReferenceH hVertSRS)
    5799             : 
    5800             : {
    5801           8 :     VALIDATE_POINTER1(hSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5802           8 :     VALIDATE_POINTER1(hHorizSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5803           8 :     VALIDATE_POINTER1(hVertSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5804             : 
    5805          16 :     return ToPointer(hSRS)->SetCompoundCS(pszName, ToPointer(hHorizSRS),
    5806          16 :                                           ToPointer(hVertSRS));
    5807             : }
    5808             : 
    5809             : /************************************************************************/
    5810             : /*                        GetCompoundComponent()                        */
    5811             : /************************************************************************/
    5812             : 
    5813             : /**
    5814             :  * \brief Get a CRS component from a CompoundCRS
    5815             :  *
    5816             :  * @param iComponent Index of the CRS component (typically 0 = horizontal, 1 =
    5817             :  * vertical)
    5818             :  * @return new OGRSpatialReference object, or NULL in case of error.
    5819             :  *
    5820             :  * @since 3.14
    5821             :  */
    5822             : 
    5823             : std::unique_ptr<OGRSpatialReference>
    5824           0 : OGRSpatialReference::GetCompoundComponent(int iComponent) const
    5825             : {
    5826           0 :     std::unique_ptr<OGRSpatialReference> poSRS;
    5827           0 :     d->refreshProjObj();
    5828           0 :     d->demoteFromBoundCRS();
    5829           0 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    5830             :     {
    5831             :         auto subCrs =
    5832           0 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, iComponent);
    5833           0 :         if (subCrs)
    5834             :         {
    5835           0 :             poSRS = std::make_unique<OGRSpatialReference>();
    5836           0 :             poSRS->d->setPjCRS(subCrs);
    5837             :         }
    5838             :     }
    5839           0 :     d->undoDemoteFromBoundCRS();
    5840           0 :     return poSRS;
    5841             : }
    5842             : 
    5843             : /************************************************************************/
    5844             : /*                             SetProjCS()                              */
    5845             : /************************************************************************/
    5846             : 
    5847             : /**
    5848             :  * \brief Set the user visible PROJCS name.
    5849             :  *
    5850             :  * This method is the same as the C function OSRSetProjCS().
    5851             :  *
    5852             :  * This method will ensure a PROJCS node is created as the root,
    5853             :  * and set the provided name on it.  If used on a GEOGCS coordinate system,
    5854             :  * the GEOGCS node will be demoted to be a child of the new PROJCS root.
    5855             :  *
    5856             :  * @param pszName the user visible name to assign.  Not used as a key.
    5857             :  *
    5858             :  * @return OGRERR_NONE on success.
    5859             :  */
    5860             : 
    5861        4910 : OGRErr OGRSpatialReference::SetProjCS(const char *pszName)
    5862             : 
    5863             : {
    5864        4910 :     TAKE_OPTIONAL_LOCK();
    5865             : 
    5866        4910 :     d->refreshProjObj();
    5867        4910 :     d->demoteFromBoundCRS();
    5868        4910 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    5869             :     {
    5870         499 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5871             :     }
    5872             :     else
    5873             :     {
    5874        4411 :         auto dummyConv = proj_create_conversion(d->getPROJContext(), nullptr,
    5875             :                                                 nullptr, nullptr, nullptr,
    5876             :                                                 nullptr, nullptr, 0, nullptr);
    5877        4411 :         auto cs = proj_create_cartesian_2D_cs(
    5878             :             d->getPROJContext(), PJ_CART2D_EASTING_NORTHING, nullptr, 0);
    5879             : 
    5880        4411 :         auto projCRS = proj_create_projected_crs(
    5881        4411 :             d->getPROJContext(), pszName, d->getGeodBaseCRS(), dummyConv, cs);
    5882        4411 :         proj_destroy(dummyConv);
    5883        4411 :         proj_destroy(cs);
    5884             : 
    5885        4411 :         d->setPjCRS(projCRS);
    5886             :     }
    5887        4910 :     d->undoDemoteFromBoundCRS();
    5888        9820 :     return OGRERR_NONE;
    5889             : }
    5890             : 
    5891             : /************************************************************************/
    5892             : /*                            OSRSetProjCS()                            */
    5893             : /************************************************************************/
    5894             : 
    5895             : /**
    5896             :  * \brief Set the user visible PROJCS name.
    5897             :  *
    5898             :  * This function is the same as OGRSpatialReference::SetProjCS()
    5899             :  */
    5900           1 : OGRErr OSRSetProjCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5901             : 
    5902             : {
    5903           1 :     VALIDATE_POINTER1(hSRS, "OSRSetProjCS", OGRERR_FAILURE);
    5904             : 
    5905           1 :     return ToPointer(hSRS)->SetProjCS(pszName);
    5906             : }
    5907             : 
    5908             : /************************************************************************/
    5909             : /*                           SetProjection()                            */
    5910             : /************************************************************************/
    5911             : 
    5912             : /**
    5913             :  * \brief Set a projection name.
    5914             :  *
    5915             :  * This method is the same as the C function OSRSetProjection().
    5916             :  *
    5917             :  * @param pszProjection the projection name, which should be selected from
    5918             :  * the macros in ogr_srs_api.h, such as SRS_PT_TRANSVERSE_MERCATOR.
    5919             :  *
    5920             :  * @return OGRERR_NONE on success.
    5921             :  */
    5922             : 
    5923          23 : OGRErr OGRSpatialReference::SetProjection(const char *pszProjection)
    5924             : 
    5925             : {
    5926          46 :     TAKE_OPTIONAL_LOCK();
    5927             : 
    5928          23 :     OGR_SRSNode *poGeogCS = nullptr;
    5929             : 
    5930          23 :     if (GetRoot() != nullptr && EQUAL(d->m_poRoot->GetValue(), "GEOGCS"))
    5931             :     {
    5932           4 :         poGeogCS = d->m_poRoot;
    5933           4 :         d->m_poRoot = nullptr;
    5934             :     }
    5935             : 
    5936          23 :     if (!GetAttrNode("PROJCS"))
    5937             :     {
    5938          11 :         SetNode("PROJCS", "unnamed");
    5939             :     }
    5940             : 
    5941          23 :     const OGRErr eErr = SetNode("PROJCS|PROJECTION", pszProjection);
    5942          23 :     if (eErr != OGRERR_NONE)
    5943           0 :         return eErr;
    5944             : 
    5945          23 :     if (poGeogCS != nullptr)
    5946           4 :         d->m_poRoot->InsertChild(poGeogCS, 1);
    5947             : 
    5948          23 :     return OGRERR_NONE;
    5949             : }
    5950             : 
    5951             : /************************************************************************/
    5952             : /*                          OSRSetProjection()                          */
    5953             : /************************************************************************/
    5954             : 
    5955             : /**
    5956             :  * \brief Set a projection name.
    5957             :  *
    5958             :  * This function is the same as OGRSpatialReference::SetProjection()
    5959             :  */
    5960           0 : OGRErr OSRSetProjection(OGRSpatialReferenceH hSRS, const char *pszProjection)
    5961             : 
    5962             : {
    5963           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjection", OGRERR_FAILURE);
    5964             : 
    5965           0 :     return ToPointer(hSRS)->SetProjection(pszProjection);
    5966             : }
    5967             : 
    5968             : /************************************************************************/
    5969             : /*                      GetWKT2ProjectionMethod()                       */
    5970             : /************************************************************************/
    5971             : 
    5972             : /**
    5973             :  * \brief Returns info on the projection method, based on WKT2 naming
    5974             :  * conventions.
    5975             :  *
    5976             :  * The returned strings are short lived and should be considered to be
    5977             :  * invalidated by any further call to the GDAL API.
    5978             :  *
    5979             :  * @param[out] ppszMethodName Pointer to a string that will receive the
    5980             :  * projection method name.
    5981             :  * @param[out] ppszMethodAuthName null pointer, or pointer to a string that will
    5982             :  * receive the name of the authority that defines the projection method.
    5983             :  * *ppszMethodAuthName may be nullptr if the projection method is not linked to
    5984             :  * an authority.
    5985             :  * @param[out] ppszMethodCode null pointer, or pointer to a string that will
    5986             :  * receive the code that defines the projection method.
    5987             :  * *ppszMethodCode may be nullptr if the projection method is not linked to
    5988             :  * an authority.
    5989             :  *
    5990             :  * @return OGRERR_NONE on success.
    5991             :  */
    5992             : OGRErr
    5993           1 : OGRSpatialReference::GetWKT2ProjectionMethod(const char **ppszMethodName,
    5994             :                                              const char **ppszMethodAuthName,
    5995             :                                              const char **ppszMethodCode) const
    5996             : {
    5997           2 :     TAKE_OPTIONAL_LOCK();
    5998             : 
    5999           1 :     auto conv = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    6000           1 :     if (!conv)
    6001           0 :         return OGRERR_FAILURE;
    6002           1 :     const char *pszTmpMethodName = "";
    6003           1 :     const char *pszTmpMethodAuthName = "";
    6004           1 :     const char *pszTmpMethodCode = "";
    6005           1 :     int ret = proj_coordoperation_get_method_info(
    6006             :         d->getPROJContext(), conv, &pszTmpMethodName, &pszTmpMethodAuthName,
    6007             :         &pszTmpMethodCode);
    6008             :     // "Internalize" temporary strings returned by PROJ
    6009           1 :     CPLAssert(pszTmpMethodName);
    6010           1 :     if (ppszMethodName)
    6011           1 :         *ppszMethodName = CPLSPrintf("%s", pszTmpMethodName);
    6012           1 :     if (ppszMethodAuthName)
    6013           0 :         *ppszMethodAuthName = pszTmpMethodAuthName
    6014           0 :                                   ? CPLSPrintf("%s", pszTmpMethodAuthName)
    6015           0 :                                   : nullptr;
    6016           1 :     if (ppszMethodCode)
    6017           0 :         *ppszMethodCode =
    6018           0 :             pszTmpMethodCode ? CPLSPrintf("%s", pszTmpMethodCode) : nullptr;
    6019           1 :     proj_destroy(conv);
    6020           1 :     return ret ? OGRERR_NONE : OGRERR_FAILURE;
    6021             : }
    6022             : 
    6023             : /************************************************************************/
    6024             : /*                            SetProjParm()                             */
    6025             : /************************************************************************/
    6026             : 
    6027             : /**
    6028             :  * \brief Set a projection parameter value.
    6029             :  *
    6030             :  * Adds a new PARAMETER under the PROJCS with the indicated name and value.
    6031             :  *
    6032             :  * This method is the same as the C function OSRSetProjParm().
    6033             :  *
    6034             :  * Please check https://gdal.org/proj_list pages for
    6035             :  * legal parameter names for specific projections.
    6036             :  *
    6037             :  *
    6038             :  * @param pszParamName the parameter name, which should be selected from
    6039             :  * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
    6040             :  *
    6041             :  * @param dfValue value to assign.
    6042             :  *
    6043             :  * @return OGRERR_NONE on success.
    6044             :  */
    6045             : 
    6046         129 : OGRErr OGRSpatialReference::SetProjParm(const char *pszParamName,
    6047             :                                         double dfValue)
    6048             : 
    6049             : {
    6050         258 :     TAKE_OPTIONAL_LOCK();
    6051             : 
    6052         129 :     OGR_SRSNode *poPROJCS = GetAttrNode("PROJCS");
    6053             : 
    6054         129 :     if (poPROJCS == nullptr)
    6055           3 :         return OGRERR_FAILURE;
    6056             : 
    6057         126 :     char szValue[64] = {'\0'};
    6058         126 :     OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    6059             : 
    6060             :     /* -------------------------------------------------------------------- */
    6061             :     /*      Try to find existing parameter with this name.                  */
    6062             :     /* -------------------------------------------------------------------- */
    6063        1030 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    6064             :     {
    6065         943 :         OGR_SRSNode *poParam = poPROJCS->GetChild(iChild);
    6066             : 
    6067        1242 :         if (EQUAL(poParam->GetValue(), "PARAMETER") &&
    6068        1242 :             poParam->GetChildCount() == 2 &&
    6069         299 :             EQUAL(poParam->GetChild(0)->GetValue(), pszParamName))
    6070             :         {
    6071          39 :             poParam->GetChild(1)->SetValue(szValue);
    6072          39 :             return OGRERR_NONE;
    6073             :         }
    6074             :     }
    6075             : 
    6076             :     /* -------------------------------------------------------------------- */
    6077             :     /*      Otherwise create a new parameter and append.                    */
    6078             :     /* -------------------------------------------------------------------- */
    6079          87 :     OGR_SRSNode *poParam = new OGR_SRSNode("PARAMETER");
    6080          87 :     poParam->AddChild(new OGR_SRSNode(pszParamName));
    6081          87 :     poParam->AddChild(new OGR_SRSNode(szValue));
    6082             : 
    6083          87 :     poPROJCS->AddChild(poParam);
    6084             : 
    6085          87 :     return OGRERR_NONE;
    6086             : }
    6087             : 
    6088             : /************************************************************************/
    6089             : /*                           OSRSetProjParm()                           */
    6090             : /************************************************************************/
    6091             : 
    6092             : /**
    6093             :  * \brief Set a projection parameter value.
    6094             :  *
    6095             :  * This function is the same as OGRSpatialReference::SetProjParm()
    6096             :  */
    6097           0 : OGRErr OSRSetProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    6098             :                       double dfValue)
    6099             : 
    6100             : {
    6101           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjParm", OGRERR_FAILURE);
    6102             : 
    6103           0 :     return ToPointer(hSRS)->SetProjParm(pszParamName, dfValue);
    6104             : }
    6105             : 
    6106             : /************************************************************************/
    6107             : /*                            FindProjParm()                            */
    6108             : /************************************************************************/
    6109             : 
    6110             : /**
    6111             :  * \brief Return the child index of the named projection parameter on
    6112             :  * its parent PROJCS node.
    6113             :  *
    6114             :  * @param pszParameter projection parameter to look for
    6115             :  * @param poPROJCS projection CS node to look in. If NULL is passed,
    6116             :  *        the PROJCS node of the SpatialReference object will be searched.
    6117             :  *
    6118             :  * @return the child index of the named projection parameter. -1 on failure
    6119             :  */
    6120        5189 : int OGRSpatialReference::FindProjParm(const char *pszParameter,
    6121             :                                       const OGR_SRSNode *poPROJCS) const
    6122             : 
    6123             : {
    6124       10378 :     TAKE_OPTIONAL_LOCK();
    6125             : 
    6126        5189 :     if (poPROJCS == nullptr)
    6127           0 :         poPROJCS = GetAttrNode("PROJCS");
    6128             : 
    6129        5189 :     if (poPROJCS == nullptr)
    6130           0 :         return -1;
    6131             : 
    6132             :     /* -------------------------------------------------------------------- */
    6133             :     /*      Search for requested parameter.                                 */
    6134             :     /* -------------------------------------------------------------------- */
    6135        5189 :     bool bIsWKT2 = false;
    6136       34199 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    6137             :     {
    6138       33508 :         const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    6139             : 
    6140       33508 :         if (poParameter->GetChildCount() >= 2)
    6141             :         {
    6142       23171 :             const char *pszValue = poParameter->GetValue();
    6143       38180 :             if (EQUAL(pszValue, "PARAMETER") &&
    6144       15009 :                 EQUAL(poPROJCS->GetChild(iChild)->GetChild(0)->GetValue(),
    6145             :                       pszParameter))
    6146             :             {
    6147        4498 :                 return iChild;
    6148             :             }
    6149       18673 :             else if (EQUAL(pszValue, "METHOD"))
    6150             :             {
    6151          41 :                 bIsWKT2 = true;
    6152             :             }
    6153             :         }
    6154             :     }
    6155             : 
    6156             :     /* -------------------------------------------------------------------- */
    6157             :     /*      Try similar names, for selected parameters.                     */
    6158             :     /* -------------------------------------------------------------------- */
    6159         691 :     if (EQUAL(pszParameter, SRS_PP_LATITUDE_OF_ORIGIN))
    6160             :     {
    6161         324 :         if (bIsWKT2)
    6162             :         {
    6163           8 :             int iChild = FindProjParm(
    6164             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    6165           8 :             if (iChild == -1)
    6166           3 :                 iChild = FindProjParm(
    6167             :                     EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, poPROJCS);
    6168           8 :             return iChild;
    6169             :         }
    6170         316 :         return FindProjParm(SRS_PP_LATITUDE_OF_CENTER, poPROJCS);
    6171             :     }
    6172             : 
    6173         367 :     if (EQUAL(pszParameter, SRS_PP_CENTRAL_MERIDIAN))
    6174             :     {
    6175          38 :         if (bIsWKT2)
    6176             :         {
    6177           9 :             int iChild = FindProjParm(
    6178             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    6179           9 :             if (iChild == -1)
    6180           0 :                 iChild = FindProjParm(
    6181             :                     EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, poPROJCS);
    6182           9 :             return iChild;
    6183             :         }
    6184          29 :         int iChild = FindProjParm(SRS_PP_LONGITUDE_OF_CENTER, poPROJCS);
    6185          29 :         if (iChild == -1)
    6186           0 :             iChild = FindProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, poPROJCS);
    6187          29 :         return iChild;
    6188             :     }
    6189             : 
    6190         329 :     return -1;
    6191             : }
    6192             : 
    6193             : /************************************************************************/
    6194             : /*                            GetProjParm()                             */
    6195             : /************************************************************************/
    6196             : 
    6197             : /**
    6198             :  * \brief Fetch a projection parameter value.
    6199             :  *
    6200             :  * NOTE: This code should be modified to translate non degree angles into
    6201             :  * degrees based on the GEOGCS unit.  This has not yet been done.
    6202             :  *
    6203             :  * This method is the same as the C function OSRGetProjParm().
    6204             :  *
    6205             :  * @param pszName the name of the parameter to fetch, from the set of
    6206             :  * SRS_PP codes in ogr_srs_api.h.
    6207             :  *
    6208             :  * @param dfDefaultValue the value to return if this parameter doesn't exist.
    6209             :  *
    6210             :  * @param pnErr place to put error code on failure.  Ignored if NULL.
    6211             :  *
    6212             :  * @return value of parameter.
    6213             :  */
    6214             : 
    6215        5082 : double OGRSpatialReference::GetProjParm(const char *pszName,
    6216             :                                         double dfDefaultValue,
    6217             :                                         OGRErr *pnErr) const
    6218             : 
    6219             : {
    6220       10164 :     TAKE_OPTIONAL_LOCK();
    6221             : 
    6222        5082 :     d->refreshProjObj();
    6223        5082 :     GetRoot();  // force update of d->m_bNodesWKT2
    6224             : 
    6225        5082 :     if (pnErr != nullptr)
    6226        4082 :         *pnErr = OGRERR_NONE;
    6227             : 
    6228             :     /* -------------------------------------------------------------------- */
    6229             :     /*      Find the desired parameter.                                     */
    6230             :     /* -------------------------------------------------------------------- */
    6231             :     const OGR_SRSNode *poPROJCS =
    6232        5082 :         GetAttrNode(d->m_bNodesWKT2 ? "CONVERSION" : "PROJCS");
    6233        5082 :     if (poPROJCS == nullptr)
    6234             :     {
    6235         258 :         if (pnErr != nullptr)
    6236         258 :             *pnErr = OGRERR_FAILURE;
    6237         258 :         return dfDefaultValue;
    6238             :     }
    6239             : 
    6240        4824 :     const int iChild = FindProjParm(pszName, poPROJCS);
    6241        4824 :     if (iChild == -1)
    6242             :     {
    6243         326 :         if (IsProjected() && GetAxesCount() == 3)
    6244             :         {
    6245           3 :             OGRSpatialReference *poSRSTmp = Clone();
    6246           3 :             poSRSTmp->DemoteTo2D(nullptr);
    6247             :             const double dfRet =
    6248           3 :                 poSRSTmp->GetProjParm(pszName, dfDefaultValue, pnErr);
    6249           3 :             delete poSRSTmp;
    6250           3 :             return dfRet;
    6251             :         }
    6252             : 
    6253         323 :         if (pnErr != nullptr)
    6254         301 :             *pnErr = OGRERR_FAILURE;
    6255         323 :         return dfDefaultValue;
    6256             :     }
    6257             : 
    6258        4498 :     const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    6259        4498 :     return CPLAtof(poParameter->GetChild(1)->GetValue());
    6260             : }
    6261             : 
    6262             : /************************************************************************/
    6263             : /*                           OSRGetProjParm()                           */
    6264             : /************************************************************************/
    6265             : 
    6266             : /**
    6267             :  * \brief Fetch a projection parameter value.
    6268             :  *
    6269             :  * This function is the same as OGRSpatialReference::GetProjParm()
    6270             :  */
    6271          90 : double OSRGetProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6272             :                       double dfDefaultValue, OGRErr *pnErr)
    6273             : 
    6274             : {
    6275          90 :     VALIDATE_POINTER1(hSRS, "OSRGetProjParm", 0);
    6276             : 
    6277          90 :     return ToPointer(hSRS)->GetProjParm(pszName, dfDefaultValue, pnErr);
    6278             : }
    6279             : 
    6280             : /************************************************************************/
    6281             : /*                          GetNormProjParm()                           */
    6282             : /************************************************************************/
    6283             : 
    6284             : /**
    6285             :  * \brief Fetch a normalized projection parameter value.
    6286             :  *
    6287             :  * This method is the same as GetProjParm() except that the value of
    6288             :  * the parameter is "normalized" into degrees or meters depending on
    6289             :  * whether it is linear or angular.
    6290             :  *
    6291             :  * This method is the same as the C function OSRGetNormProjParm().
    6292             :  *
    6293             :  * @param pszName the name of the parameter to fetch, from the set of
    6294             :  * SRS_PP codes in ogr_srs_api.h.
    6295             :  *
    6296             :  * @param dfDefaultValue the value to return if this parameter doesn't exist.
    6297             :  *
    6298             :  * @param pnErr place to put error code on failure.  Ignored if NULL.
    6299             :  *
    6300             :  * @return value of parameter.
    6301             :  */
    6302             : 
    6303        4057 : double OGRSpatialReference::GetNormProjParm(const char *pszName,
    6304             :                                             double dfDefaultValue,
    6305             :                                             OGRErr *pnErr) const
    6306             : 
    6307             : {
    6308        8114 :     TAKE_OPTIONAL_LOCK();
    6309             : 
    6310        4057 :     GetNormInfo();
    6311             : 
    6312        4057 :     OGRErr nError = OGRERR_NONE;
    6313        4057 :     double dfRawResult = GetProjParm(pszName, dfDefaultValue, &nError);
    6314        4057 :     if (pnErr != nullptr)
    6315           0 :         *pnErr = nError;
    6316             : 
    6317             :     // If we got the default just return it unadjusted.
    6318        4057 :     if (nError != OGRERR_NONE)
    6319         559 :         return dfRawResult;
    6320             : 
    6321        3498 :     if (d->dfToDegrees != 1.0 && IsAngularParameter(pszName))
    6322           8 :         dfRawResult *= d->dfToDegrees;
    6323             : 
    6324        3498 :     if (d->dfToMeter != 1.0 && IsLinearParameter(pszName))
    6325           5 :         return dfRawResult * d->dfToMeter;
    6326             : 
    6327        3493 :     return dfRawResult;
    6328             : }
    6329             : 
    6330             : /************************************************************************/
    6331             : /*                         OSRGetNormProjParm()                         */
    6332             : /************************************************************************/
    6333             : 
    6334             : /**
    6335             :  * \brief This function is the same as OGRSpatialReference::
    6336             :  *
    6337             :  * This function is the same as OGRSpatialReference::GetNormProjParm()
    6338             :  */
    6339           1 : double OSRGetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6340             :                           double dfDefaultValue, OGRErr *pnErr)
    6341             : 
    6342             : {
    6343           1 :     VALIDATE_POINTER1(hSRS, "OSRGetNormProjParm", 0);
    6344             : 
    6345           1 :     return ToPointer(hSRS)->GetNormProjParm(pszName, dfDefaultValue, pnErr);
    6346             : }
    6347             : 
    6348             : /************************************************************************/
    6349             : /*                          SetNormProjParm()                           */
    6350             : /************************************************************************/
    6351             : 
    6352             : /**
    6353             :  * \brief Set a projection parameter with a normalized value.
    6354             :  *
    6355             :  * This method is the same as SetProjParm() except that the value of
    6356             :  * the parameter passed in is assumed to be in "normalized" form (decimal
    6357             :  * degrees for angular values, meters for linear values.  The values are
    6358             :  * converted in a form suitable for the GEOGCS and linear units in effect.
    6359             :  *
    6360             :  * This method is the same as the C function OSRSetNormProjParm().
    6361             :  *
    6362             :  * @param pszName the parameter name, which should be selected from
    6363             :  * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
    6364             :  *
    6365             :  * @param dfValue value to assign.
    6366             :  *
    6367             :  * @return OGRERR_NONE on success.
    6368             :  */
    6369             : 
    6370          91 : OGRErr OGRSpatialReference::SetNormProjParm(const char *pszName, double dfValue)
    6371             : 
    6372             : {
    6373         182 :     TAKE_OPTIONAL_LOCK();
    6374             : 
    6375          91 :     GetNormInfo();
    6376             : 
    6377          91 :     if (d->dfToDegrees != 0.0 &&
    6378          91 :         (d->dfToDegrees != 1.0 || d->dfFromGreenwich != 0.0) &&
    6379           0 :         IsAngularParameter(pszName))
    6380             :     {
    6381           0 :         dfValue /= d->dfToDegrees;
    6382             :     }
    6383          95 :     else if (d->dfToMeter != 1.0 && d->dfToMeter != 0.0 &&
    6384           4 :              IsLinearParameter(pszName))
    6385           4 :         dfValue /= d->dfToMeter;
    6386             : 
    6387         182 :     return SetProjParm(pszName, dfValue);
    6388             : }
    6389             : 
    6390             : /************************************************************************/
    6391             : /*                         OSRSetNormProjParm()                         */
    6392             : /************************************************************************/
    6393             : 
    6394             : /**
    6395             :  * \brief Set a projection parameter with a normalized value.
    6396             :  *
    6397             :  * This function is the same as OGRSpatialReference::SetNormProjParm()
    6398             :  */
    6399           0 : OGRErr OSRSetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    6400             :                           double dfValue)
    6401             : 
    6402             : {
    6403           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNormProjParm", OGRERR_FAILURE);
    6404             : 
    6405           0 :     return ToPointer(hSRS)->SetNormProjParm(pszParamName, dfValue);
    6406             : }
    6407             : 
    6408             : /************************************************************************/
    6409             : /*                               SetTM()                                */
    6410             : /************************************************************************/
    6411             : 
    6412         443 : OGRErr OGRSpatialReference::SetTM(double dfCenterLat, double dfCenterLong,
    6413             :                                   double dfScale, double dfFalseEasting,
    6414             :                                   double dfFalseNorthing)
    6415             : 
    6416             : {
    6417         886 :     TAKE_OPTIONAL_LOCK();
    6418             : 
    6419         443 :     return d->replaceConversionAndUnref(
    6420             :         proj_create_conversion_transverse_mercator(
    6421             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    6422         886 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6423             : }
    6424             : 
    6425             : /************************************************************************/
    6426             : /*                              OSRSetTM()                              */
    6427             : /************************************************************************/
    6428             : 
    6429           1 : OGRErr OSRSetTM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6430             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    6431             :                 double dfFalseNorthing)
    6432             : 
    6433             : {
    6434           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTM", OGRERR_FAILURE);
    6435             : 
    6436           1 :     return ToPointer(hSRS)->SetTM(dfCenterLat, dfCenterLong, dfScale,
    6437           1 :                                   dfFalseEasting, dfFalseNorthing);
    6438             : }
    6439             : 
    6440             : /************************************************************************/
    6441             : /*                            SetTMVariant()                            */
    6442             : /************************************************************************/
    6443             : 
    6444           0 : OGRErr OGRSpatialReference::SetTMVariant(const char *pszVariantName,
    6445             :                                          double dfCenterLat,
    6446             :                                          double dfCenterLong, double dfScale,
    6447             :                                          double dfFalseEasting,
    6448             :                                          double dfFalseNorthing)
    6449             : 
    6450             : {
    6451           0 :     TAKE_OPTIONAL_LOCK();
    6452             : 
    6453           0 :     SetProjection(pszVariantName);
    6454           0 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6455           0 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6456           0 :     SetNormProjParm(SRS_PP_SCALE_FACTOR, dfScale);
    6457           0 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6458           0 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6459             : 
    6460           0 :     return OGRERR_NONE;
    6461             : }
    6462             : 
    6463             : /************************************************************************/
    6464             : /*                          OSRSetTMVariant()                           */
    6465             : /************************************************************************/
    6466             : 
    6467           0 : OGRErr OSRSetTMVariant(OGRSpatialReferenceH hSRS, const char *pszVariantName,
    6468             :                        double dfCenterLat, double dfCenterLong, double dfScale,
    6469             :                        double dfFalseEasting, double dfFalseNorthing)
    6470             : 
    6471             : {
    6472           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMVariant", OGRERR_FAILURE);
    6473             : 
    6474           0 :     return ToPointer(hSRS)->SetTMVariant(pszVariantName, dfCenterLat,
    6475             :                                          dfCenterLong, dfScale, dfFalseEasting,
    6476           0 :                                          dfFalseNorthing);
    6477             : }
    6478             : 
    6479             : /************************************************************************/
    6480             : /*                              SetTMSO()                               */
    6481             : /************************************************************************/
    6482             : 
    6483           3 : OGRErr OGRSpatialReference::SetTMSO(double dfCenterLat, double dfCenterLong,
    6484             :                                     double dfScale, double dfFalseEasting,
    6485             :                                     double dfFalseNorthing)
    6486             : 
    6487             : {
    6488           6 :     TAKE_OPTIONAL_LOCK();
    6489             : 
    6490           3 :     auto conv = proj_create_conversion_transverse_mercator_south_oriented(
    6491             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
    6492             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6493             : 
    6494           3 :     const char *pszName = nullptr;
    6495           3 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    6496           3 :     CPLString osName = pszName ? pszName : "";
    6497             : 
    6498           3 :     d->refreshProjObj();
    6499             : 
    6500           3 :     d->demoteFromBoundCRS();
    6501             : 
    6502           3 :     auto cs = proj_create_cartesian_2D_cs(
    6503             :         d->getPROJContext(), PJ_CART2D_WESTING_SOUTHING,
    6504           3 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    6505             :     auto projCRS =
    6506           3 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    6507           3 :                                   d->getGeodBaseCRS(), conv, cs);
    6508           3 :     proj_destroy(conv);
    6509           3 :     proj_destroy(cs);
    6510             : 
    6511           3 :     d->setPjCRS(projCRS);
    6512             : 
    6513           3 :     d->undoDemoteFromBoundCRS();
    6514             : 
    6515           6 :     return OGRERR_NONE;
    6516             : }
    6517             : 
    6518             : /************************************************************************/
    6519             : /*                             OSRSetTMSO()                             */
    6520             : /************************************************************************/
    6521             : 
    6522           0 : OGRErr OSRSetTMSO(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6523             :                   double dfCenterLong, double dfScale, double dfFalseEasting,
    6524             :                   double dfFalseNorthing)
    6525             : 
    6526             : {
    6527           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMSO", OGRERR_FAILURE);
    6528             : 
    6529           0 :     return ToPointer(hSRS)->SetTMSO(dfCenterLat, dfCenterLong, dfScale,
    6530           0 :                                     dfFalseEasting, dfFalseNorthing);
    6531             : }
    6532             : 
    6533             : /************************************************************************/
    6534             : /*                              SetTPED()                               */
    6535             : /************************************************************************/
    6536             : 
    6537           1 : OGRErr OGRSpatialReference::SetTPED(double dfLat1, double dfLong1,
    6538             :                                     double dfLat2, double dfLong2,
    6539             :                                     double dfFalseEasting,
    6540             :                                     double dfFalseNorthing)
    6541             : 
    6542             : {
    6543           2 :     TAKE_OPTIONAL_LOCK();
    6544             : 
    6545           1 :     return d->replaceConversionAndUnref(
    6546             :         proj_create_conversion_two_point_equidistant(
    6547             :             d->getPROJContext(), dfLat1, dfLong1, dfLat2, dfLong2,
    6548           2 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6549             : }
    6550             : 
    6551             : /************************************************************************/
    6552             : /*                             OSRSetTPED()                             */
    6553             : /************************************************************************/
    6554             : 
    6555           0 : OGRErr OSRSetTPED(OGRSpatialReferenceH hSRS, double dfLat1, double dfLong1,
    6556             :                   double dfLat2, double dfLong2, double dfFalseEasting,
    6557             :                   double dfFalseNorthing)
    6558             : 
    6559             : {
    6560           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTPED", OGRERR_FAILURE);
    6561             : 
    6562           0 :     return ToPointer(hSRS)->SetTPED(dfLat1, dfLong1, dfLat2, dfLong2,
    6563           0 :                                     dfFalseEasting, dfFalseNorthing);
    6564             : }
    6565             : 
    6566             : /************************************************************************/
    6567             : /*                               SetTMG()                               */
    6568             : /************************************************************************/
    6569             : 
    6570           0 : OGRErr OGRSpatialReference::SetTMG(double dfCenterLat, double dfCenterLong,
    6571             :                                    double dfFalseEasting,
    6572             :                                    double dfFalseNorthing)
    6573             : 
    6574             : {
    6575           0 :     TAKE_OPTIONAL_LOCK();
    6576             : 
    6577           0 :     return d->replaceConversionAndUnref(
    6578             :         proj_create_conversion_tunisia_mapping_grid(
    6579             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6580           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6581             : }
    6582             : 
    6583             : /************************************************************************/
    6584             : /*                             OSRSetTMG()                              */
    6585             : /************************************************************************/
    6586             : 
    6587           0 : OGRErr OSRSetTMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6588             :                  double dfCenterLong, double dfFalseEasting,
    6589             :                  double dfFalseNorthing)
    6590             : 
    6591             : {
    6592           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMG", OGRERR_FAILURE);
    6593             : 
    6594           0 :     return ToPointer(hSRS)->SetTMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    6595           0 :                                    dfFalseNorthing);
    6596             : }
    6597             : 
    6598             : /************************************************************************/
    6599             : /*                              SetACEA()                               */
    6600             : /************************************************************************/
    6601             : 
    6602          39 : OGRErr OGRSpatialReference::SetACEA(double dfStdP1, double dfStdP2,
    6603             :                                     double dfCenterLat, double dfCenterLong,
    6604             :                                     double dfFalseEasting,
    6605             :                                     double dfFalseNorthing)
    6606             : 
    6607             : {
    6608          78 :     TAKE_OPTIONAL_LOCK();
    6609             : 
    6610             :     // Note different order of parameters. The one in PROJ is conformant with
    6611             :     // EPSG
    6612          39 :     return d->replaceConversionAndUnref(
    6613             :         proj_create_conversion_albers_equal_area(
    6614             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6615          78 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6616             : }
    6617             : 
    6618             : /************************************************************************/
    6619             : /*                             OSRSetACEA()                             */
    6620             : /************************************************************************/
    6621             : 
    6622           0 : OGRErr OSRSetACEA(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6623             :                   double dfCenterLat, double dfCenterLong,
    6624             :                   double dfFalseEasting, double dfFalseNorthing)
    6625             : 
    6626             : {
    6627           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6628             : 
    6629           0 :     return ToPointer(hSRS)->SetACEA(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6630           0 :                                     dfFalseEasting, dfFalseNorthing);
    6631             : }
    6632             : 
    6633             : /************************************************************************/
    6634             : /*                               SetAE()                                */
    6635             : /************************************************************************/
    6636             : 
    6637          21 : OGRErr OGRSpatialReference::SetAE(double dfCenterLat, double dfCenterLong,
    6638             :                                   double dfFalseEasting, double dfFalseNorthing)
    6639             : 
    6640             : {
    6641          42 :     TAKE_OPTIONAL_LOCK();
    6642             : 
    6643          21 :     return d->replaceConversionAndUnref(
    6644             :         proj_create_conversion_azimuthal_equidistant(
    6645             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6646          42 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6647             : }
    6648             : 
    6649             : /************************************************************************/
    6650             : /*                              OSRSetAE()                              */
    6651             : /************************************************************************/
    6652             : 
    6653           0 : OGRErr OSRSetAE(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6654             :                 double dfCenterLong, double dfFalseEasting,
    6655             :                 double dfFalseNorthing)
    6656             : 
    6657             : {
    6658           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6659             : 
    6660           0 :     return ToPointer(hSRS)->SetAE(dfCenterLat, dfCenterLong, dfFalseEasting,
    6661           0 :                                   dfFalseNorthing);
    6662             : }
    6663             : 
    6664             : /************************************************************************/
    6665             : /*                              SetBonne()                              */
    6666             : /************************************************************************/
    6667             : 
    6668           1 : OGRErr OGRSpatialReference::SetBonne(double dfStdP1, double dfCentralMeridian,
    6669             :                                      double dfFalseEasting,
    6670             :                                      double dfFalseNorthing)
    6671             : 
    6672             : {
    6673           2 :     TAKE_OPTIONAL_LOCK();
    6674             : 
    6675           1 :     return d->replaceConversionAndUnref(proj_create_conversion_bonne(
    6676             :         d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6677           2 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6678             : }
    6679             : 
    6680             : /************************************************************************/
    6681             : /*                            OSRSetBonne()                             */
    6682             : /************************************************************************/
    6683             : 
    6684           0 : OGRErr OSRSetBonne(OGRSpatialReferenceH hSRS, double dfStdP1,
    6685             :                    double dfCentralMeridian, double dfFalseEasting,
    6686             :                    double dfFalseNorthing)
    6687             : 
    6688             : {
    6689           0 :     VALIDATE_POINTER1(hSRS, "OSRSetBonne", OGRERR_FAILURE);
    6690             : 
    6691           0 :     return ToPointer(hSRS)->SetBonne(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6692           0 :                                      dfFalseNorthing);
    6693             : }
    6694             : 
    6695             : /************************************************************************/
    6696             : /*                               SetCEA()                               */
    6697             : /************************************************************************/
    6698             : 
    6699           4 : OGRErr OGRSpatialReference::SetCEA(double dfStdP1, double dfCentralMeridian,
    6700             :                                    double dfFalseEasting,
    6701             :                                    double dfFalseNorthing)
    6702             : 
    6703             : {
    6704           8 :     TAKE_OPTIONAL_LOCK();
    6705             : 
    6706           4 :     return d->replaceConversionAndUnref(
    6707             :         proj_create_conversion_lambert_cylindrical_equal_area(
    6708             :             d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6709           8 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6710             : }
    6711             : 
    6712             : /************************************************************************/
    6713             : /*                             OSRSetCEA()                              */
    6714             : /************************************************************************/
    6715             : 
    6716           0 : OGRErr OSRSetCEA(OGRSpatialReferenceH hSRS, double dfStdP1,
    6717             :                  double dfCentralMeridian, double dfFalseEasting,
    6718             :                  double dfFalseNorthing)
    6719             : 
    6720             : {
    6721           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCEA", OGRERR_FAILURE);
    6722             : 
    6723           0 :     return ToPointer(hSRS)->SetCEA(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6724           0 :                                    dfFalseNorthing);
    6725             : }
    6726             : 
    6727             : /************************************************************************/
    6728             : /*                               SetCS()                                */
    6729             : /************************************************************************/
    6730             : 
    6731           5 : OGRErr OGRSpatialReference::SetCS(double dfCenterLat, double dfCenterLong,
    6732             :                                   double dfFalseEasting, double dfFalseNorthing)
    6733             : 
    6734             : {
    6735          10 :     TAKE_OPTIONAL_LOCK();
    6736             : 
    6737           5 :     return d->replaceConversionAndUnref(proj_create_conversion_cassini_soldner(
    6738             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6739          10 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6740             : }
    6741             : 
    6742             : /************************************************************************/
    6743             : /*                              OSRSetCS()                              */
    6744             : /************************************************************************/
    6745             : 
    6746           0 : OGRErr OSRSetCS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6747             :                 double dfCenterLong, double dfFalseEasting,
    6748             :                 double dfFalseNorthing)
    6749             : 
    6750             : {
    6751           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCS", OGRERR_FAILURE);
    6752             : 
    6753           0 :     return ToPointer(hSRS)->SetCS(dfCenterLat, dfCenterLong, dfFalseEasting,
    6754           0 :                                   dfFalseNorthing);
    6755             : }
    6756             : 
    6757             : /************************************************************************/
    6758             : /*                               SetEC()                                */
    6759             : /************************************************************************/
    6760             : 
    6761           7 : OGRErr OGRSpatialReference::SetEC(double dfStdP1, double dfStdP2,
    6762             :                                   double dfCenterLat, double dfCenterLong,
    6763             :                                   double dfFalseEasting, double dfFalseNorthing)
    6764             : 
    6765             : {
    6766          14 :     TAKE_OPTIONAL_LOCK();
    6767             : 
    6768             :     // Note: different order of arguments
    6769           7 :     return d->replaceConversionAndUnref(
    6770             :         proj_create_conversion_equidistant_conic(
    6771             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6772          14 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6773             : }
    6774             : 
    6775             : /************************************************************************/
    6776             : /*                              OSRSetEC()                              */
    6777             : /************************************************************************/
    6778             : 
    6779           0 : OGRErr OSRSetEC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6780             :                 double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    6781             :                 double dfFalseNorthing)
    6782             : 
    6783             : {
    6784           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEC", OGRERR_FAILURE);
    6785             : 
    6786           0 :     return ToPointer(hSRS)->SetEC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6787           0 :                                   dfFalseEasting, dfFalseNorthing);
    6788             : }
    6789             : 
    6790             : /************************************************************************/
    6791             : /*                             SetEckert()                              */
    6792             : /************************************************************************/
    6793             : 
    6794          10 : OGRErr OGRSpatialReference::SetEckert(int nVariation,  // 1-6.
    6795             :                                       double dfCentralMeridian,
    6796             :                                       double dfFalseEasting,
    6797             :                                       double dfFalseNorthing)
    6798             : 
    6799             : {
    6800          20 :     TAKE_OPTIONAL_LOCK();
    6801             : 
    6802             :     PJ *conv;
    6803          10 :     if (nVariation == 1)
    6804             :     {
    6805           1 :         conv = proj_create_conversion_eckert_i(
    6806             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6807             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6808             :     }
    6809           9 :     else if (nVariation == 2)
    6810             :     {
    6811           1 :         conv = proj_create_conversion_eckert_ii(
    6812             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6813             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6814             :     }
    6815           8 :     else if (nVariation == 3)
    6816             :     {
    6817           1 :         conv = proj_create_conversion_eckert_iii(
    6818             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6819             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6820             :     }
    6821           7 :     else if (nVariation == 4)
    6822             :     {
    6823           3 :         conv = proj_create_conversion_eckert_iv(
    6824             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6825             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6826             :     }
    6827           4 :     else if (nVariation == 5)
    6828             :     {
    6829           1 :         conv = proj_create_conversion_eckert_v(
    6830             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6831             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6832             :     }
    6833           3 :     else if (nVariation == 6)
    6834             :     {
    6835           3 :         conv = proj_create_conversion_eckert_vi(
    6836             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6837             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6838             :     }
    6839             :     else
    6840             :     {
    6841           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6842             :                  "Unsupported Eckert variation (%d).", nVariation);
    6843           0 :         return OGRERR_UNSUPPORTED_SRS;
    6844             :     }
    6845             : 
    6846          10 :     return d->replaceConversionAndUnref(conv);
    6847             : }
    6848             : 
    6849             : /************************************************************************/
    6850             : /*                            OSRSetEckert()                            */
    6851             : /************************************************************************/
    6852             : 
    6853           0 : OGRErr OSRSetEckert(OGRSpatialReferenceH hSRS, int nVariation,
    6854             :                     double dfCentralMeridian, double dfFalseEasting,
    6855             :                     double dfFalseNorthing)
    6856             : 
    6857             : {
    6858           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckert", OGRERR_FAILURE);
    6859             : 
    6860           0 :     return ToPointer(hSRS)->SetEckert(nVariation, dfCentralMeridian,
    6861           0 :                                       dfFalseEasting, dfFalseNorthing);
    6862             : }
    6863             : 
    6864             : /************************************************************************/
    6865             : /*                            SetEckertIV()                             */
    6866             : /*                                                                      */
    6867             : /*      Deprecated                                                      */
    6868             : /************************************************************************/
    6869             : 
    6870           2 : OGRErr OGRSpatialReference::SetEckertIV(double dfCentralMeridian,
    6871             :                                         double dfFalseEasting,
    6872             :                                         double dfFalseNorthing)
    6873             : 
    6874             : {
    6875           2 :     return SetEckert(4, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6876             : }
    6877             : 
    6878             : /************************************************************************/
    6879             : /*                           OSRSetEckertIV()                           */
    6880             : /************************************************************************/
    6881             : 
    6882           0 : OGRErr OSRSetEckertIV(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6883             :                       double dfFalseEasting, double dfFalseNorthing)
    6884             : 
    6885             : {
    6886           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertIV", OGRERR_FAILURE);
    6887             : 
    6888           0 :     return ToPointer(hSRS)->SetEckertIV(dfCentralMeridian, dfFalseEasting,
    6889           0 :                                         dfFalseNorthing);
    6890             : }
    6891             : 
    6892             : /************************************************************************/
    6893             : /*                            SetEckertVI()                             */
    6894             : /*                                                                      */
    6895             : /*      Deprecated                                                      */
    6896             : /************************************************************************/
    6897             : 
    6898           2 : OGRErr OGRSpatialReference::SetEckertVI(double dfCentralMeridian,
    6899             :                                         double dfFalseEasting,
    6900             :                                         double dfFalseNorthing)
    6901             : 
    6902             : {
    6903           2 :     return SetEckert(6, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6904             : }
    6905             : 
    6906             : /************************************************************************/
    6907             : /*                           OSRSetEckertVI()                           */
    6908             : /************************************************************************/
    6909             : 
    6910           0 : OGRErr OSRSetEckertVI(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6911             :                       double dfFalseEasting, double dfFalseNorthing)
    6912             : 
    6913             : {
    6914           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertVI", OGRERR_FAILURE);
    6915             : 
    6916           0 :     return ToPointer(hSRS)->SetEckertVI(dfCentralMeridian, dfFalseEasting,
    6917           0 :                                         dfFalseNorthing);
    6918             : }
    6919             : 
    6920             : /************************************************************************/
    6921             : /*                         SetEquirectangular()                         */
    6922             : /************************************************************************/
    6923             : 
    6924           2 : OGRErr OGRSpatialReference::SetEquirectangular(double dfCenterLat,
    6925             :                                                double dfCenterLong,
    6926             :                                                double dfFalseEasting,
    6927             :                                                double dfFalseNorthing)
    6928             : 
    6929             : {
    6930           4 :     TAKE_OPTIONAL_LOCK();
    6931             : 
    6932           2 :     if (dfCenterLat == 0.0)
    6933             :     {
    6934           0 :         return d->replaceConversionAndUnref(
    6935             :             proj_create_conversion_equidistant_cylindrical(
    6936             :                 d->getPROJContext(), 0.0, dfCenterLong, dfFalseEasting,
    6937           0 :                 dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6938             :     }
    6939             : 
    6940             :     // Non-standard extension with non-zero latitude of origin
    6941           2 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6942           2 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6943           2 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6944           2 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6945           2 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6946             : 
    6947           2 :     return OGRERR_NONE;
    6948             : }
    6949             : 
    6950             : /************************************************************************/
    6951             : /*                       OSRSetEquirectangular()                        */
    6952             : /************************************************************************/
    6953             : 
    6954           0 : OGRErr OSRSetEquirectangular(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6955             :                              double dfCenterLong, double dfFalseEasting,
    6956             :                              double dfFalseNorthing)
    6957             : 
    6958             : {
    6959           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular", OGRERR_FAILURE);
    6960             : 
    6961           0 :     return ToPointer(hSRS)->SetEquirectangular(dfCenterLat, dfCenterLong,
    6962           0 :                                                dfFalseEasting, dfFalseNorthing);
    6963             : }
    6964             : 
    6965             : /************************************************************************/
    6966             : /*                         SetEquirectangular2()                        */
    6967             : /* Generalized form                                                     */
    6968             : /************************************************************************/
    6969             : 
    6970         181 : OGRErr OGRSpatialReference::SetEquirectangular2(double dfCenterLat,
    6971             :                                                 double dfCenterLong,
    6972             :                                                 double dfStdParallel1,
    6973             :                                                 double dfFalseEasting,
    6974             :                                                 double dfFalseNorthing)
    6975             : 
    6976             : {
    6977         362 :     TAKE_OPTIONAL_LOCK();
    6978             : 
    6979         181 :     if (dfCenterLat == 0.0)
    6980             :     {
    6981         176 :         return d->replaceConversionAndUnref(
    6982             :             proj_create_conversion_equidistant_cylindrical(
    6983             :                 d->getPROJContext(), dfStdParallel1, dfCenterLong,
    6984         176 :                 dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6985             :     }
    6986             : 
    6987             :     // Non-standard extension with non-zero latitude of origin
    6988           5 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6989           5 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6990           5 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6991           5 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdParallel1);
    6992           5 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6993           5 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6994             : 
    6995           5 :     return OGRERR_NONE;
    6996             : }
    6997             : 
    6998             : /************************************************************************/
    6999             : /*                       OSRSetEquirectangular2()                       */
    7000             : /************************************************************************/
    7001             : 
    7002           3 : OGRErr OSRSetEquirectangular2(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7003             :                               double dfCenterLong, double dfStdParallel1,
    7004             :                               double dfFalseEasting, double dfFalseNorthing)
    7005             : 
    7006             : {
    7007           3 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular2", OGRERR_FAILURE);
    7008             : 
    7009           3 :     return ToPointer(hSRS)->SetEquirectangular2(dfCenterLat, dfCenterLong,
    7010             :                                                 dfStdParallel1, dfFalseEasting,
    7011           3 :                                                 dfFalseNorthing);
    7012             : }
    7013             : 
    7014             : /************************************************************************/
    7015             : /*                               SetGS()                                */
    7016             : /************************************************************************/
    7017             : 
    7018           5 : OGRErr OGRSpatialReference::SetGS(double dfCentralMeridian,
    7019             :                                   double dfFalseEasting, double dfFalseNorthing)
    7020             : 
    7021             : {
    7022           5 :     return d->replaceConversionAndUnref(proj_create_conversion_gall(
    7023             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    7024           5 :         nullptr, 0.0, nullptr, 0.0));
    7025             : }
    7026             : 
    7027             : /************************************************************************/
    7028             : /*                              OSRSetGS()                              */
    7029             : /************************************************************************/
    7030             : 
    7031           2 : OGRErr OSRSetGS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7032             :                 double dfFalseEasting, double dfFalseNorthing)
    7033             : 
    7034             : {
    7035           2 :     VALIDATE_POINTER1(hSRS, "OSRSetGS", OGRERR_FAILURE);
    7036             : 
    7037           2 :     return ToPointer(hSRS)->SetGS(dfCentralMeridian, dfFalseEasting,
    7038           2 :                                   dfFalseNorthing);
    7039             : }
    7040             : 
    7041             : /************************************************************************/
    7042             : /*                               SetGH()                                */
    7043             : /************************************************************************/
    7044             : 
    7045           0 : OGRErr OGRSpatialReference::SetGH(double dfCentralMeridian,
    7046             :                                   double dfFalseEasting, double dfFalseNorthing)
    7047             : 
    7048             : {
    7049           0 :     TAKE_OPTIONAL_LOCK();
    7050             : 
    7051           0 :     return d->replaceConversionAndUnref(proj_create_conversion_goode_homolosine(
    7052             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    7053           0 :         nullptr, 0.0, nullptr, 0.0));
    7054             : }
    7055             : 
    7056             : /************************************************************************/
    7057             : /*                              OSRSetGH()                              */
    7058             : /************************************************************************/
    7059             : 
    7060           0 : OGRErr OSRSetGH(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7061             :                 double dfFalseEasting, double dfFalseNorthing)
    7062             : 
    7063             : {
    7064           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGH", OGRERR_FAILURE);
    7065             : 
    7066           0 :     return ToPointer(hSRS)->SetGH(dfCentralMeridian, dfFalseEasting,
    7067           0 :                                   dfFalseNorthing);
    7068             : }
    7069             : 
    7070             : /************************************************************************/
    7071             : /*                               SetIGH()                               */
    7072             : /************************************************************************/
    7073             : 
    7074           0 : OGRErr OGRSpatialReference::SetIGH()
    7075             : 
    7076             : {
    7077           0 :     TAKE_OPTIONAL_LOCK();
    7078             : 
    7079           0 :     return d->replaceConversionAndUnref(
    7080             :         proj_create_conversion_interrupted_goode_homolosine(
    7081           0 :             d->getPROJContext(), 0.0, 0.0, 0.0, nullptr, 0.0, nullptr, 0.0));
    7082             : }
    7083             : 
    7084             : /************************************************************************/
    7085             : /*                             OSRSetIGH()                              */
    7086             : /************************************************************************/
    7087             : 
    7088           0 : OGRErr OSRSetIGH(OGRSpatialReferenceH hSRS)
    7089             : 
    7090             : {
    7091           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIGH", OGRERR_FAILURE);
    7092             : 
    7093           0 :     return ToPointer(hSRS)->SetIGH();
    7094             : }
    7095             : 
    7096             : /************************************************************************/
    7097             : /*                              SetGEOS()                               */
    7098             : /************************************************************************/
    7099             : 
    7100           3 : OGRErr OGRSpatialReference::SetGEOS(double dfCentralMeridian,
    7101             :                                     double dfSatelliteHeight,
    7102             :                                     double dfFalseEasting,
    7103             :                                     double dfFalseNorthing)
    7104             : 
    7105             : {
    7106           6 :     TAKE_OPTIONAL_LOCK();
    7107             : 
    7108           3 :     return d->replaceConversionAndUnref(
    7109             :         proj_create_conversion_geostationary_satellite_sweep_y(
    7110             :             d->getPROJContext(), dfCentralMeridian, dfSatelliteHeight,
    7111           6 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7112             : }
    7113             : 
    7114             : /************************************************************************/
    7115             : /*                             OSRSetGEOS()                             */
    7116             : /************************************************************************/
    7117             : 
    7118           0 : OGRErr OSRSetGEOS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7119             :                   double dfSatelliteHeight, double dfFalseEasting,
    7120             :                   double dfFalseNorthing)
    7121             : 
    7122             : {
    7123           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGEOS", OGRERR_FAILURE);
    7124             : 
    7125           0 :     return ToPointer(hSRS)->SetGEOS(dfCentralMeridian, dfSatelliteHeight,
    7126           0 :                                     dfFalseEasting, dfFalseNorthing);
    7127             : }
    7128             : 
    7129             : /************************************************************************/
    7130             : /*                     SetGaussSchreiberTMercator()                     */
    7131             : /************************************************************************/
    7132             : 
    7133           0 : OGRErr OGRSpatialReference::SetGaussSchreiberTMercator(double dfCenterLat,
    7134             :                                                        double dfCenterLong,
    7135             :                                                        double dfScale,
    7136             :                                                        double dfFalseEasting,
    7137             :                                                        double dfFalseNorthing)
    7138             : 
    7139             : {
    7140           0 :     TAKE_OPTIONAL_LOCK();
    7141             : 
    7142           0 :     return d->replaceConversionAndUnref(
    7143             :         proj_create_conversion_gauss_schreiber_transverse_mercator(
    7144             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7145           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7146             : }
    7147             : 
    7148             : /************************************************************************/
    7149             : /*                   OSRSetGaussSchreiberTMercator()                    */
    7150             : /************************************************************************/
    7151             : 
    7152           0 : OGRErr OSRSetGaussSchreiberTMercator(OGRSpatialReferenceH hSRS,
    7153             :                                      double dfCenterLat, double dfCenterLong,
    7154             :                                      double dfScale, double dfFalseEasting,
    7155             :                                      double dfFalseNorthing)
    7156             : 
    7157             : {
    7158           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGaussSchreiberTMercator", OGRERR_FAILURE);
    7159             : 
    7160           0 :     return ToPointer(hSRS)->SetGaussSchreiberTMercator(
    7161           0 :         dfCenterLat, dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing);
    7162             : }
    7163             : 
    7164             : /************************************************************************/
    7165             : /*                            SetGnomonic()                             */
    7166             : /************************************************************************/
    7167             : 
    7168           2 : OGRErr OGRSpatialReference::SetGnomonic(double dfCenterLat, double dfCenterLong,
    7169             :                                         double dfFalseEasting,
    7170             :                                         double dfFalseNorthing)
    7171             : 
    7172             : {
    7173           4 :     TAKE_OPTIONAL_LOCK();
    7174             : 
    7175           2 :     return d->replaceConversionAndUnref(proj_create_conversion_gnomonic(
    7176             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7177           4 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7178             : }
    7179             : 
    7180             : /************************************************************************/
    7181             : /*                           OSRSetGnomonic()                           */
    7182             : /************************************************************************/
    7183             : 
    7184           0 : OGRErr OSRSetGnomonic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7185             :                       double dfCenterLong, double dfFalseEasting,
    7186             :                       double dfFalseNorthing)
    7187             : 
    7188             : {
    7189           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGnomonic", OGRERR_FAILURE);
    7190             : 
    7191           0 :     return ToPointer(hSRS)->SetGnomonic(dfCenterLat, dfCenterLong,
    7192           0 :                                         dfFalseEasting, dfFalseNorthing);
    7193             : }
    7194             : 
    7195             : /************************************************************************/
    7196             : /*                              SetHOMAC()                              */
    7197             : /************************************************************************/
    7198             : 
    7199             : /**
    7200             :  * \brief Set an Hotine Oblique Mercator Azimuth Center projection using
    7201             :  * azimuth angle.
    7202             :  *
    7203             :  * This projection corresponds to EPSG projection method 9815, also
    7204             :  * sometimes known as hotine oblique mercator (variant B).
    7205             :  *
    7206             :  * This method does the same thing as the C function OSRSetHOMAC().
    7207             :  *
    7208             :  * @param dfCenterLat Latitude of the projection origin.
    7209             :  * @param dfCenterLong Longitude of the projection origin.
    7210             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7211             :  * centerline.
    7212             :  * @param dfRectToSkew Angle from Rectified to Skew Grid
    7213             :  * @param dfScale Scale factor applies to the projection origin.
    7214             :  * @param dfFalseEasting False easting.
    7215             :  * @param dfFalseNorthing False northing.
    7216             :  *
    7217             :  * @return OGRERR_NONE on success.
    7218             :  */
    7219             : 
    7220           4 : OGRErr OGRSpatialReference::SetHOMAC(double dfCenterLat, double dfCenterLong,
    7221             :                                      double dfAzimuth, double dfRectToSkew,
    7222             :                                      double dfScale, double dfFalseEasting,
    7223             :                                      double dfFalseNorthing)
    7224             : 
    7225             : {
    7226           8 :     TAKE_OPTIONAL_LOCK();
    7227             : 
    7228           4 :     return d->replaceConversionAndUnref(
    7229             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    7230             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7231             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7232           8 :             0.0, nullptr, 0.0));
    7233             : }
    7234             : 
    7235             : /************************************************************************/
    7236             : /*                            OSRSetHOMAC()                             */
    7237             : /************************************************************************/
    7238             : 
    7239             : /**
    7240             :  * \brief Set an Oblique Mercator projection using azimuth angle.
    7241             :  *
    7242             :  * This is the same as the C++ method OGRSpatialReference::SetHOMAC()
    7243             :  */
    7244           0 : OGRErr OSRSetHOMAC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7245             :                    double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7246             :                    double dfScale, double dfFalseEasting,
    7247             :                    double dfFalseNorthing)
    7248             : 
    7249             : {
    7250           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOMAC", OGRERR_FAILURE);
    7251             : 
    7252           0 :     return ToPointer(hSRS)->SetHOMAC(dfCenterLat, dfCenterLong, dfAzimuth,
    7253             :                                      dfRectToSkew, dfScale, dfFalseEasting,
    7254           0 :                                      dfFalseNorthing);
    7255             : }
    7256             : 
    7257             : /************************************************************************/
    7258             : /*                               SetHOM()                               */
    7259             : /************************************************************************/
    7260             : 
    7261             : /**
    7262             :  * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
    7263             :  *
    7264             :  * This projection corresponds to EPSG projection method 9812, also
    7265             :  * sometimes known as hotine oblique mercator (variant A)..
    7266             :  *
    7267             :  * This method does the same thing as the C function OSRSetHOM().
    7268             :  *
    7269             :  * @param dfCenterLat Latitude of the projection origin.
    7270             :  * @param dfCenterLong Longitude of the projection origin.
    7271             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7272             :  * centerline.
    7273             :  * @param dfRectToSkew Angle from Rectified to Skew Grid
    7274             :  * @param dfScale Scale factor applies to the projection origin.
    7275             :  * @param dfFalseEasting False easting.
    7276             :  * @param dfFalseNorthing False northing.
    7277             :  *
    7278             :  * @return OGRERR_NONE on success.
    7279             :  */
    7280             : 
    7281          13 : OGRErr OGRSpatialReference::SetHOM(double dfCenterLat, double dfCenterLong,
    7282             :                                    double dfAzimuth, double dfRectToSkew,
    7283             :                                    double dfScale, double dfFalseEasting,
    7284             :                                    double dfFalseNorthing)
    7285             : 
    7286             : {
    7287          26 :     TAKE_OPTIONAL_LOCK();
    7288             : 
    7289          13 :     return d->replaceConversionAndUnref(
    7290             :         proj_create_conversion_hotine_oblique_mercator_variant_a(
    7291             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7292             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7293          26 :             0.0, nullptr, 0.0));
    7294             : }
    7295             : 
    7296             : /************************************************************************/
    7297             : /*                             OSRSetHOM()                              */
    7298             : /************************************************************************/
    7299             : /**
    7300             :  * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
    7301             :  *
    7302             :  * This is the same as the C++ method OGRSpatialReference::SetHOM()
    7303             :  */
    7304           0 : OGRErr OSRSetHOM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7305             :                  double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7306             :                  double dfScale, double dfFalseEasting, double dfFalseNorthing)
    7307             : 
    7308             : {
    7309           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM", OGRERR_FAILURE);
    7310             : 
    7311           0 :     return ToPointer(hSRS)->SetHOM(dfCenterLat, dfCenterLong, dfAzimuth,
    7312             :                                    dfRectToSkew, dfScale, dfFalseEasting,
    7313           0 :                                    dfFalseNorthing);
    7314             : }
    7315             : 
    7316             : /************************************************************************/
    7317             : /*                             SetHOM2PNO()                             */
    7318             : /************************************************************************/
    7319             : 
    7320             : /**
    7321             :  * \brief Set a Hotine Oblique Mercator projection using two points on
    7322             :  * projection centerline.
    7323             :  *
    7324             :  * This method does the same thing as the C function OSRSetHOM2PNO().
    7325             :  *
    7326             :  * @param dfCenterLat Latitude of the projection origin.
    7327             :  * @param dfLat1 Latitude of the first point on center line.
    7328             :  * @param dfLong1 Longitude of the first point on center line.
    7329             :  * @param dfLat2 Latitude of the second point on center line.
    7330             :  * @param dfLong2 Longitude of the second point on center line.
    7331             :  * @param dfScale Scale factor applies to the projection origin.
    7332             :  * @param dfFalseEasting False easting.
    7333             :  * @param dfFalseNorthing False northing.
    7334             :  *
    7335             :  * @return OGRERR_NONE on success.
    7336             :  */
    7337             : 
    7338           3 : OGRErr OGRSpatialReference::SetHOM2PNO(double dfCenterLat, double dfLat1,
    7339             :                                        double dfLong1, double dfLat2,
    7340             :                                        double dfLong2, double dfScale,
    7341             :                                        double dfFalseEasting,
    7342             :                                        double dfFalseNorthing)
    7343             : 
    7344             : {
    7345           6 :     TAKE_OPTIONAL_LOCK();
    7346             : 
    7347           3 :     return d->replaceConversionAndUnref(
    7348             :         proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin(
    7349             :             d->getPROJContext(), dfCenterLat, dfLat1, dfLong1, dfLat2, dfLong2,
    7350             :             dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    7351           6 :             0.0));
    7352             : }
    7353             : 
    7354             : /************************************************************************/
    7355             : /*                           OSRSetHOM2PNO()                            */
    7356             : /************************************************************************/
    7357             : /**
    7358             :  * \brief Set a Hotine Oblique Mercator projection using two points on
    7359             :  *  projection centerline.
    7360             :  *
    7361             :  * This is the same as the C++ method OGRSpatialReference::SetHOM2PNO()
    7362             :  */
    7363           0 : OGRErr OSRSetHOM2PNO(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7364             :                      double dfLat1, double dfLong1, double dfLat2,
    7365             :                      double dfLong2, double dfScale, double dfFalseEasting,
    7366             :                      double dfFalseNorthing)
    7367             : 
    7368             : {
    7369           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM2PNO", OGRERR_FAILURE);
    7370             : 
    7371           0 :     return ToPointer(hSRS)->SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
    7372             :                                        dfLong2, dfScale, dfFalseEasting,
    7373           0 :                                        dfFalseNorthing);
    7374             : }
    7375             : 
    7376             : /************************************************************************/
    7377             : /*                               SetLOM()                               */
    7378             : /************************************************************************/
    7379             : 
    7380             : /**
    7381             :  * \brief Set a Laborde Oblique Mercator projection.
    7382             :  *
    7383             :  * @param dfCenterLat Latitude of the projection origin.
    7384             :  * @param dfCenterLong Longitude of the projection origin.
    7385             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7386             :  * centerline.
    7387             :  * @param dfScale Scale factor on the initial line
    7388             :  * @param dfFalseEasting False easting.
    7389             :  * @param dfFalseNorthing False northing.
    7390             :  *
    7391             :  * @return OGRERR_NONE on success.
    7392             :  */
    7393             : 
    7394           0 : OGRErr OGRSpatialReference::SetLOM(double dfCenterLat, double dfCenterLong,
    7395             :                                    double dfAzimuth, double dfScale,
    7396             :                                    double dfFalseEasting,
    7397             :                                    double dfFalseNorthing)
    7398             : 
    7399             : {
    7400           0 :     TAKE_OPTIONAL_LOCK();
    7401             : 
    7402           0 :     return d->replaceConversionAndUnref(
    7403             :         proj_create_conversion_laborde_oblique_mercator(
    7404             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth, dfScale,
    7405           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7406             : }
    7407             : 
    7408             : /************************************************************************/
    7409             : /*                          SetIWMPolyconic()                           */
    7410             : /************************************************************************/
    7411             : 
    7412           0 : OGRErr OGRSpatialReference::SetIWMPolyconic(double dfLat1, double dfLat2,
    7413             :                                             double dfCenterLong,
    7414             :                                             double dfFalseEasting,
    7415             :                                             double dfFalseNorthing)
    7416             : 
    7417             : {
    7418           0 :     TAKE_OPTIONAL_LOCK();
    7419             : 
    7420           0 :     return d->replaceConversionAndUnref(
    7421             :         proj_create_conversion_international_map_world_polyconic(
    7422             :             d->getPROJContext(), dfCenterLong, dfLat1, dfLat2, dfFalseEasting,
    7423           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7424             : }
    7425             : 
    7426             : /************************************************************************/
    7427             : /*                         OSRSetIWMPolyconic()                         */
    7428             : /************************************************************************/
    7429             : 
    7430           0 : OGRErr OSRSetIWMPolyconic(OGRSpatialReferenceH hSRS, double dfLat1,
    7431             :                           double dfLat2, double dfCenterLong,
    7432             :                           double dfFalseEasting, double dfFalseNorthing)
    7433             : 
    7434             : {
    7435           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIWMPolyconic", OGRERR_FAILURE);
    7436             : 
    7437           0 :     return ToPointer(hSRS)->SetIWMPolyconic(dfLat1, dfLat2, dfCenterLong,
    7438           0 :                                             dfFalseEasting, dfFalseNorthing);
    7439             : }
    7440             : 
    7441             : /************************************************************************/
    7442             : /*                             SetKrovak()                              */
    7443             : /************************************************************************/
    7444             : 
    7445             : /** Krovak east-north projection.
    7446             :  *
    7447             :  * Note that dfAzimuth and dfPseudoStdParallel1 are ignored when exporting
    7448             :  * to PROJ and should be respectively set to 30.28813972222222 and 78.5
    7449             :  */
    7450           3 : OGRErr OGRSpatialReference::SetKrovak(double dfCenterLat, double dfCenterLong,
    7451             :                                       double dfAzimuth,
    7452             :                                       double dfPseudoStdParallel1,
    7453             :                                       double dfScale, double dfFalseEasting,
    7454             :                                       double dfFalseNorthing)
    7455             : 
    7456             : {
    7457           6 :     TAKE_OPTIONAL_LOCK();
    7458             : 
    7459           3 :     return d->replaceConversionAndUnref(
    7460             :         proj_create_conversion_krovak_north_oriented(
    7461             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7462             :             dfPseudoStdParallel1, dfScale, dfFalseEasting, dfFalseNorthing,
    7463           6 :             nullptr, 0.0, nullptr, 0.0));
    7464             : }
    7465             : 
    7466             : /************************************************************************/
    7467             : /*                            OSRSetKrovak()                            */
    7468             : /************************************************************************/
    7469             : 
    7470           0 : OGRErr OSRSetKrovak(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7471             :                     double dfCenterLong, double dfAzimuth,
    7472             :                     double dfPseudoStdParallel1, double dfScale,
    7473             :                     double dfFalseEasting, double dfFalseNorthing)
    7474             : 
    7475             : {
    7476           0 :     VALIDATE_POINTER1(hSRS, "OSRSetKrovak", OGRERR_FAILURE);
    7477             : 
    7478           0 :     return ToPointer(hSRS)->SetKrovak(dfCenterLat, dfCenterLong, dfAzimuth,
    7479             :                                       dfPseudoStdParallel1, dfScale,
    7480           0 :                                       dfFalseEasting, dfFalseNorthing);
    7481             : }
    7482             : 
    7483             : /************************************************************************/
    7484             : /*                              SetLAEA()                               */
    7485             : /************************************************************************/
    7486             : 
    7487          17 : OGRErr OGRSpatialReference::SetLAEA(double dfCenterLat, double dfCenterLong,
    7488             :                                     double dfFalseEasting,
    7489             :                                     double dfFalseNorthing)
    7490             : 
    7491             : {
    7492          34 :     TAKE_OPTIONAL_LOCK();
    7493             : 
    7494          17 :     auto conv = proj_create_conversion_lambert_azimuthal_equal_area(
    7495             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7496             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    7497             : 
    7498          17 :     const char *pszName = nullptr;
    7499          17 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7500          17 :     CPLString osName = pszName ? pszName : "";
    7501             : 
    7502          17 :     d->refreshProjObj();
    7503             : 
    7504          17 :     d->demoteFromBoundCRS();
    7505             : 
    7506          17 :     auto cs = proj_create_cartesian_2D_cs(
    7507             :         d->getPROJContext(),
    7508          17 :         std::fabs(dfCenterLat - 90) < 1e-10 && dfCenterLong == 0
    7509             :             ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7510           0 :         : std::fabs(dfCenterLat - -90) < 1e-10 && dfCenterLong == 0
    7511          14 :             ? PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH
    7512             :             : PJ_CART2D_EASTING_NORTHING,
    7513          17 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7514             :     auto projCRS =
    7515          17 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7516          17 :                                   d->getGeodBaseCRS(), conv, cs);
    7517          17 :     proj_destroy(conv);
    7518          17 :     proj_destroy(cs);
    7519             : 
    7520          17 :     d->setPjCRS(projCRS);
    7521             : 
    7522          17 :     d->undoDemoteFromBoundCRS();
    7523             : 
    7524          34 :     return OGRERR_NONE;
    7525             : }
    7526             : 
    7527             : /************************************************************************/
    7528             : /*                             OSRSetLAEA()                             */
    7529             : /************************************************************************/
    7530             : 
    7531           0 : OGRErr OSRSetLAEA(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7532             :                   double dfCenterLong, double dfFalseEasting,
    7533             :                   double dfFalseNorthing)
    7534             : 
    7535             : {
    7536           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLAEA", OGRERR_FAILURE);
    7537             : 
    7538           0 :     return ToPointer(hSRS)->SetLAEA(dfCenterLat, dfCenterLong, dfFalseEasting,
    7539           0 :                                     dfFalseNorthing);
    7540             : }
    7541             : 
    7542             : /************************************************************************/
    7543             : /*                               SetLCC()                               */
    7544             : /************************************************************************/
    7545             : 
    7546         149 : OGRErr OGRSpatialReference::SetLCC(double dfStdP1, double dfStdP2,
    7547             :                                    double dfCenterLat, double dfCenterLong,
    7548             :                                    double dfFalseEasting,
    7549             :                                    double dfFalseNorthing)
    7550             : 
    7551             : {
    7552         298 :     TAKE_OPTIONAL_LOCK();
    7553             : 
    7554         149 :     return d->replaceConversionAndUnref(
    7555             :         proj_create_conversion_lambert_conic_conformal_2sp(
    7556             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7557         298 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7558             : }
    7559             : 
    7560             : /************************************************************************/
    7561             : /*                             OSRSetLCC()                              */
    7562             : /************************************************************************/
    7563             : 
    7564           1 : OGRErr OSRSetLCC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7565             :                  double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    7566             :                  double dfFalseNorthing)
    7567             : 
    7568             : {
    7569           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC", OGRERR_FAILURE);
    7570             : 
    7571           1 :     return ToPointer(hSRS)->SetLCC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7572           1 :                                    dfFalseEasting, dfFalseNorthing);
    7573             : }
    7574             : 
    7575             : /************************************************************************/
    7576             : /*                             SetLCC1SP()                              */
    7577             : /************************************************************************/
    7578             : 
    7579          10 : OGRErr OGRSpatialReference::SetLCC1SP(double dfCenterLat, double dfCenterLong,
    7580             :                                       double dfScale, double dfFalseEasting,
    7581             :                                       double dfFalseNorthing)
    7582             : 
    7583             : {
    7584          20 :     TAKE_OPTIONAL_LOCK();
    7585             : 
    7586          10 :     return d->replaceConversionAndUnref(
    7587             :         proj_create_conversion_lambert_conic_conformal_1sp(
    7588             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7589          20 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7590             : }
    7591             : 
    7592             : /************************************************************************/
    7593             : /*                            OSRSetLCC1SP()                            */
    7594             : /************************************************************************/
    7595             : 
    7596           0 : OGRErr OSRSetLCC1SP(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7597             :                     double dfCenterLong, double dfScale, double dfFalseEasting,
    7598             :                     double dfFalseNorthing)
    7599             : 
    7600             : {
    7601           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC1SP", OGRERR_FAILURE);
    7602             : 
    7603           0 :     return ToPointer(hSRS)->SetLCC1SP(dfCenterLat, dfCenterLong, dfScale,
    7604           0 :                                       dfFalseEasting, dfFalseNorthing);
    7605             : }
    7606             : 
    7607             : /************************************************************************/
    7608             : /*                              SetLCCB()                               */
    7609             : /************************************************************************/
    7610             : 
    7611           2 : OGRErr OGRSpatialReference::SetLCCB(double dfStdP1, double dfStdP2,
    7612             :                                     double dfCenterLat, double dfCenterLong,
    7613             :                                     double dfFalseEasting,
    7614             :                                     double dfFalseNorthing)
    7615             : 
    7616             : {
    7617           4 :     TAKE_OPTIONAL_LOCK();
    7618             : 
    7619           2 :     return d->replaceConversionAndUnref(
    7620             :         proj_create_conversion_lambert_conic_conformal_2sp_belgium(
    7621             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7622           4 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7623             : }
    7624             : 
    7625             : /************************************************************************/
    7626             : /*                             OSRSetLCCB()                             */
    7627             : /************************************************************************/
    7628             : 
    7629           0 : OGRErr OSRSetLCCB(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7630             :                   double dfCenterLat, double dfCenterLong,
    7631             :                   double dfFalseEasting, double dfFalseNorthing)
    7632             : 
    7633             : {
    7634           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCCB", OGRERR_FAILURE);
    7635             : 
    7636           0 :     return ToPointer(hSRS)->SetLCCB(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7637           0 :                                     dfFalseEasting, dfFalseNorthing);
    7638             : }
    7639             : 
    7640             : /************************************************************************/
    7641             : /*                               SetMC()                                */
    7642             : /************************************************************************/
    7643             : 
    7644           4 : OGRErr OGRSpatialReference::SetMC(double dfCenterLat, double dfCenterLong,
    7645             :                                   double dfFalseEasting, double dfFalseNorthing)
    7646             : 
    7647             : {
    7648           8 :     TAKE_OPTIONAL_LOCK();
    7649             : 
    7650             :     (void)dfCenterLat;  // ignored
    7651             : 
    7652           4 :     return d->replaceConversionAndUnref(
    7653             :         proj_create_conversion_miller_cylindrical(
    7654             :             d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7655           8 :             nullptr, 0, nullptr, 0));
    7656             : }
    7657             : 
    7658             : /************************************************************************/
    7659             : /*                              OSRSetMC()                              */
    7660             : /************************************************************************/
    7661             : 
    7662           0 : OGRErr OSRSetMC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7663             :                 double dfCenterLong, double dfFalseEasting,
    7664             :                 double dfFalseNorthing)
    7665             : 
    7666             : {
    7667           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMC", OGRERR_FAILURE);
    7668             : 
    7669           0 :     return ToPointer(hSRS)->SetMC(dfCenterLat, dfCenterLong, dfFalseEasting,
    7670           0 :                                   dfFalseNorthing);
    7671             : }
    7672             : 
    7673             : /************************************************************************/
    7674             : /*                            SetMercator()                             */
    7675             : /************************************************************************/
    7676             : 
    7677          60 : OGRErr OGRSpatialReference::SetMercator(double dfCenterLat, double dfCenterLong,
    7678             :                                         double dfScale, double dfFalseEasting,
    7679             :                                         double dfFalseNorthing)
    7680             : 
    7681             : {
    7682         120 :     TAKE_OPTIONAL_LOCK();
    7683             : 
    7684          60 :     if (dfCenterLat != 0.0 && dfScale == 1.0)
    7685             :     {
    7686             :         // Not sure this is correct, but this is how it has been used
    7687             :         // historically
    7688           0 :         return SetMercator2SP(dfCenterLat, 0.0, dfCenterLong, dfFalseEasting,
    7689           0 :                               dfFalseNorthing);
    7690             :     }
    7691          60 :     return d->replaceConversionAndUnref(
    7692             :         proj_create_conversion_mercator_variant_a(
    7693             :             d->getPROJContext(),
    7694             :             dfCenterLat,  // should be zero
    7695             :             dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0,
    7696          60 :             nullptr, 0));
    7697             : }
    7698             : 
    7699             : /************************************************************************/
    7700             : /*                           OSRSetMercator()                           */
    7701             : /************************************************************************/
    7702             : 
    7703           2 : OGRErr OSRSetMercator(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7704             :                       double dfCenterLong, double dfScale,
    7705             :                       double dfFalseEasting, double dfFalseNorthing)
    7706             : 
    7707             : {
    7708           2 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator", OGRERR_FAILURE);
    7709             : 
    7710           2 :     return ToPointer(hSRS)->SetMercator(dfCenterLat, dfCenterLong, dfScale,
    7711           2 :                                         dfFalseEasting, dfFalseNorthing);
    7712             : }
    7713             : 
    7714             : /************************************************************************/
    7715             : /*                           SetMercator2SP()                           */
    7716             : /************************************************************************/
    7717             : 
    7718          30 : OGRErr OGRSpatialReference::SetMercator2SP(double dfStdP1, double dfCenterLat,
    7719             :                                            double dfCenterLong,
    7720             :                                            double dfFalseEasting,
    7721             :                                            double dfFalseNorthing)
    7722             : 
    7723             : {
    7724          30 :     if (dfCenterLat == 0.0)
    7725             :     {
    7726          29 :         return d->replaceConversionAndUnref(
    7727             :             proj_create_conversion_mercator_variant_b(
    7728             :                 d->getPROJContext(), dfStdP1, dfCenterLong, dfFalseEasting,
    7729          29 :                 dfFalseNorthing, nullptr, 0, nullptr, 0));
    7730             :     }
    7731             : 
    7732           1 :     TAKE_OPTIONAL_LOCK();
    7733             : 
    7734           1 :     SetProjection(SRS_PT_MERCATOR_2SP);
    7735             : 
    7736           1 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdP1);
    7737           1 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    7738           1 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    7739           1 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    7740           1 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    7741             : 
    7742           1 :     return OGRERR_NONE;
    7743             : }
    7744             : 
    7745             : /************************************************************************/
    7746             : /*                         OSRSetMercator2SP()                          */
    7747             : /************************************************************************/
    7748             : 
    7749           1 : OGRErr OSRSetMercator2SP(OGRSpatialReferenceH hSRS, double dfStdP1,
    7750             :                          double dfCenterLat, double dfCenterLong,
    7751             :                          double dfFalseEasting, double dfFalseNorthing)
    7752             : 
    7753             : {
    7754           1 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator2SP", OGRERR_FAILURE);
    7755             : 
    7756           1 :     return ToPointer(hSRS)->SetMercator2SP(dfStdP1, dfCenterLat, dfCenterLong,
    7757           1 :                                            dfFalseEasting, dfFalseNorthing);
    7758             : }
    7759             : 
    7760             : /************************************************************************/
    7761             : /*                            SetMollweide()                            */
    7762             : /************************************************************************/
    7763             : 
    7764           3 : OGRErr OGRSpatialReference::SetMollweide(double dfCentralMeridian,
    7765             :                                          double dfFalseEasting,
    7766             :                                          double dfFalseNorthing)
    7767             : 
    7768             : {
    7769           6 :     TAKE_OPTIONAL_LOCK();
    7770             : 
    7771           3 :     return d->replaceConversionAndUnref(proj_create_conversion_mollweide(
    7772             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    7773           6 :         nullptr, 0, nullptr, 0));
    7774             : }
    7775             : 
    7776             : /************************************************************************/
    7777             : /*                          OSRSetMollweide()                           */
    7778             : /************************************************************************/
    7779             : 
    7780           0 : OGRErr OSRSetMollweide(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7781             :                        double dfFalseEasting, double dfFalseNorthing)
    7782             : 
    7783             : {
    7784           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMollweide", OGRERR_FAILURE);
    7785             : 
    7786           0 :     return ToPointer(hSRS)->SetMollweide(dfCentralMeridian, dfFalseEasting,
    7787           0 :                                          dfFalseNorthing);
    7788             : }
    7789             : 
    7790             : /************************************************************************/
    7791             : /*                              SetNZMG()                               */
    7792             : /************************************************************************/
    7793             : 
    7794           7 : OGRErr OGRSpatialReference::SetNZMG(double dfCenterLat, double dfCenterLong,
    7795             :                                     double dfFalseEasting,
    7796             :                                     double dfFalseNorthing)
    7797             : 
    7798             : {
    7799          14 :     TAKE_OPTIONAL_LOCK();
    7800             : 
    7801           7 :     return d->replaceConversionAndUnref(
    7802             :         proj_create_conversion_new_zealand_mapping_grid(
    7803             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7804          14 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7805             : }
    7806             : 
    7807             : /************************************************************************/
    7808             : /*                             OSRSetNZMG()                             */
    7809             : /************************************************************************/
    7810             : 
    7811           0 : OGRErr OSRSetNZMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7812             :                   double dfCenterLong, double dfFalseEasting,
    7813             :                   double dfFalseNorthing)
    7814             : 
    7815             : {
    7816           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNZMG", OGRERR_FAILURE);
    7817             : 
    7818           0 :     return ToPointer(hSRS)->SetNZMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    7819           0 :                                     dfFalseNorthing);
    7820             : }
    7821             : 
    7822             : /************************************************************************/
    7823             : /*                               SetOS()                                */
    7824             : /************************************************************************/
    7825             : 
    7826           6 : OGRErr OGRSpatialReference::SetOS(double dfOriginLat, double dfCMeridian,
    7827             :                                   double dfScale, double dfFalseEasting,
    7828             :                                   double dfFalseNorthing)
    7829             : 
    7830             : {
    7831          12 :     TAKE_OPTIONAL_LOCK();
    7832             : 
    7833           6 :     return d->replaceConversionAndUnref(
    7834             :         proj_create_conversion_oblique_stereographic(
    7835             :             d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale,
    7836          12 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7837             : }
    7838             : 
    7839             : /************************************************************************/
    7840             : /*                              OSRSetOS()                              */
    7841             : /************************************************************************/
    7842             : 
    7843           0 : OGRErr OSRSetOS(OGRSpatialReferenceH hSRS, double dfOriginLat,
    7844             :                 double dfCMeridian, double dfScale, double dfFalseEasting,
    7845             :                 double dfFalseNorthing)
    7846             : 
    7847             : {
    7848           0 :     VALIDATE_POINTER1(hSRS, "OSRSetOS", OGRERR_FAILURE);
    7849             : 
    7850           0 :     return ToPointer(hSRS)->SetOS(dfOriginLat, dfCMeridian, dfScale,
    7851           0 :                                   dfFalseEasting, dfFalseNorthing);
    7852             : }
    7853             : 
    7854             : /************************************************************************/
    7855             : /*                          SetOrthographic()                           */
    7856             : /************************************************************************/
    7857             : 
    7858           8 : OGRErr OGRSpatialReference::SetOrthographic(double dfCenterLat,
    7859             :                                             double dfCenterLong,
    7860             :                                             double dfFalseEasting,
    7861             :                                             double dfFalseNorthing)
    7862             : 
    7863             : {
    7864          16 :     TAKE_OPTIONAL_LOCK();
    7865             : 
    7866           8 :     return d->replaceConversionAndUnref(proj_create_conversion_orthographic(
    7867             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7868          16 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    7869             : }
    7870             : 
    7871             : /************************************************************************/
    7872             : /*                         OSRSetOrthographic()                         */
    7873             : /************************************************************************/
    7874             : 
    7875           1 : OGRErr OSRSetOrthographic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7876             :                           double dfCenterLong, double dfFalseEasting,
    7877             :                           double dfFalseNorthing)
    7878             : 
    7879             : {
    7880           1 :     VALIDATE_POINTER1(hSRS, "OSRSetOrthographic", OGRERR_FAILURE);
    7881             : 
    7882           1 :     return ToPointer(hSRS)->SetOrthographic(dfCenterLat, dfCenterLong,
    7883           1 :                                             dfFalseEasting, dfFalseNorthing);
    7884             : }
    7885             : 
    7886             : /************************************************************************/
    7887             : /*                            SetPolyconic()                            */
    7888             : /************************************************************************/
    7889             : 
    7890           7 : OGRErr OGRSpatialReference::SetPolyconic(double dfCenterLat,
    7891             :                                          double dfCenterLong,
    7892             :                                          double dfFalseEasting,
    7893             :                                          double dfFalseNorthing)
    7894             : 
    7895             : {
    7896          14 :     TAKE_OPTIONAL_LOCK();
    7897             : 
    7898             :     // note: it seems that by some definitions this should include a
    7899             :     //       scale_factor parameter.
    7900           7 :     return d->replaceConversionAndUnref(
    7901             :         proj_create_conversion_american_polyconic(
    7902             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7903          14 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7904             : }
    7905             : 
    7906             : /************************************************************************/
    7907             : /*                          OSRSetPolyconic()                           */
    7908             : /************************************************************************/
    7909             : 
    7910           0 : OGRErr OSRSetPolyconic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7911             :                        double dfCenterLong, double dfFalseEasting,
    7912             :                        double dfFalseNorthing)
    7913             : 
    7914             : {
    7915           0 :     VALIDATE_POINTER1(hSRS, "OSRSetPolyconic", OGRERR_FAILURE);
    7916             : 
    7917           0 :     return ToPointer(hSRS)->SetPolyconic(dfCenterLat, dfCenterLong,
    7918           0 :                                          dfFalseEasting, dfFalseNorthing);
    7919             : }
    7920             : 
    7921             : /************************************************************************/
    7922             : /*                               SetPS()                                */
    7923             : /************************************************************************/
    7924             : 
    7925             : /** Sets a Polar Stereographic projection.
    7926             :  *
    7927             :  * Two variants are possible:
    7928             :  * - Polar Stereographic Variant A: dfCenterLat must be +/- 90° and is
    7929             :  *   interpreted as the latitude of origin, combined with the scale factor
    7930             :  * - Polar Stereographic Variant B: dfCenterLat is different from +/- 90° and
    7931             :  *   is interpreted as the latitude of true scale. In that situation, dfScale
    7932             :  *   must be set to 1 (it is ignored in the projection parameters)
    7933             :  */
    7934          32 : OGRErr OGRSpatialReference::SetPS(double dfCenterLat, double dfCenterLong,
    7935             :                                   double dfScale, double dfFalseEasting,
    7936             :                                   double dfFalseNorthing)
    7937             : 
    7938             : {
    7939          64 :     TAKE_OPTIONAL_LOCK();
    7940             : 
    7941             :     PJ *conv;
    7942          32 :     if (dfScale == 1.0 && std::abs(std::abs(dfCenterLat) - 90) > 1e-8)
    7943             :     {
    7944          21 :         conv = proj_create_conversion_polar_stereographic_variant_b(
    7945             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7946             :             dfFalseNorthing, nullptr, 0, nullptr, 0);
    7947             :     }
    7948             :     else
    7949             :     {
    7950          11 :         conv = proj_create_conversion_polar_stereographic_variant_a(
    7951             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7952             :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0);
    7953             :     }
    7954             : 
    7955          32 :     const char *pszName = nullptr;
    7956          32 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7957          32 :     CPLString osName = pszName ? pszName : "";
    7958             : 
    7959          32 :     d->refreshProjObj();
    7960             : 
    7961          32 :     d->demoteFromBoundCRS();
    7962             : 
    7963          32 :     auto cs = proj_create_cartesian_2D_cs(
    7964             :         d->getPROJContext(),
    7965             :         dfCenterLat > 0 ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7966             :                         : PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH,
    7967          32 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7968             :     auto projCRS =
    7969          32 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7970          32 :                                   d->getGeodBaseCRS(), conv, cs);
    7971          32 :     proj_destroy(conv);
    7972          32 :     proj_destroy(cs);
    7973             : 
    7974          32 :     d->setPjCRS(projCRS);
    7975             : 
    7976          32 :     d->undoDemoteFromBoundCRS();
    7977             : 
    7978          64 :     return OGRERR_NONE;
    7979             : }
    7980             : 
    7981             : /************************************************************************/
    7982             : /*                              OSRSetPS()                              */
    7983             : /************************************************************************/
    7984             : 
    7985           1 : OGRErr OSRSetPS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7986             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    7987             :                 double dfFalseNorthing)
    7988             : 
    7989             : {
    7990           1 :     VALIDATE_POINTER1(hSRS, "OSRSetPS", OGRERR_FAILURE);
    7991             : 
    7992           1 :     return ToPointer(hSRS)->SetPS(dfCenterLat, dfCenterLong, dfScale,
    7993           1 :                                   dfFalseEasting, dfFalseNorthing);
    7994             : }
    7995             : 
    7996             : /************************************************************************/
    7997             : /*                            SetRobinson()                             */
    7998             : /************************************************************************/
    7999             : 
    8000           4 : OGRErr OGRSpatialReference::SetRobinson(double dfCenterLong,
    8001             :                                         double dfFalseEasting,
    8002             :                                         double dfFalseNorthing)
    8003             : 
    8004             : {
    8005           8 :     TAKE_OPTIONAL_LOCK();
    8006             : 
    8007           4 :     return d->replaceConversionAndUnref(proj_create_conversion_robinson(
    8008             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    8009           8 :         nullptr, 0, nullptr, 0));
    8010             : }
    8011             : 
    8012             : /************************************************************************/
    8013             : /*                           OSRSetRobinson()                           */
    8014             : /************************************************************************/
    8015             : 
    8016           0 : OGRErr OSRSetRobinson(OGRSpatialReferenceH hSRS, double dfCenterLong,
    8017             :                       double dfFalseEasting, double dfFalseNorthing)
    8018             : 
    8019             : {
    8020           0 :     VALIDATE_POINTER1(hSRS, "OSRSetRobinson", OGRERR_FAILURE);
    8021             : 
    8022           0 :     return ToPointer(hSRS)->SetRobinson(dfCenterLong, dfFalseEasting,
    8023           0 :                                         dfFalseNorthing);
    8024             : }
    8025             : 
    8026             : /************************************************************************/
    8027             : /*                           SetSinusoidal()                            */
    8028             : /************************************************************************/
    8029             : 
    8030          37 : OGRErr OGRSpatialReference::SetSinusoidal(double dfCenterLong,
    8031             :                                           double dfFalseEasting,
    8032             :                                           double dfFalseNorthing)
    8033             : 
    8034             : {
    8035          74 :     TAKE_OPTIONAL_LOCK();
    8036             : 
    8037          37 :     return d->replaceConversionAndUnref(proj_create_conversion_sinusoidal(
    8038             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    8039          74 :         nullptr, 0, nullptr, 0));
    8040             : }
    8041             : 
    8042             : /************************************************************************/
    8043             : /*                          OSRSetSinusoidal()                          */
    8044             : /************************************************************************/
    8045             : 
    8046           1 : OGRErr OSRSetSinusoidal(OGRSpatialReferenceH hSRS, double dfCenterLong,
    8047             :                         double dfFalseEasting, double dfFalseNorthing)
    8048             : 
    8049             : {
    8050           1 :     VALIDATE_POINTER1(hSRS, "OSRSetSinusoidal", OGRERR_FAILURE);
    8051             : 
    8052           1 :     return ToPointer(hSRS)->SetSinusoidal(dfCenterLong, dfFalseEasting,
    8053           1 :                                           dfFalseNorthing);
    8054             : }
    8055             : 
    8056             : /************************************************************************/
    8057             : /*                          SetStereographic()                          */
    8058             : /************************************************************************/
    8059             : 
    8060           2 : OGRErr OGRSpatialReference::SetStereographic(double dfOriginLat,
    8061             :                                              double dfCMeridian, double dfScale,
    8062             :                                              double dfFalseEasting,
    8063             :                                              double dfFalseNorthing)
    8064             : 
    8065             : {
    8066           4 :     TAKE_OPTIONAL_LOCK();
    8067             : 
    8068           2 :     return d->replaceConversionAndUnref(proj_create_conversion_stereographic(
    8069             :         d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale, dfFalseEasting,
    8070           4 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    8071             : }
    8072             : 
    8073             : /************************************************************************/
    8074             : /*                        OSRSetStereographic()                         */
    8075             : /************************************************************************/
    8076             : 
    8077           0 : OGRErr OSRSetStereographic(OGRSpatialReferenceH hSRS, double dfOriginLat,
    8078             :                            double dfCMeridian, double dfScale,
    8079             :                            double dfFalseEasting, double dfFalseNorthing)
    8080             : 
    8081             : {
    8082           0 :     VALIDATE_POINTER1(hSRS, "OSRSetStereographic", OGRERR_FAILURE);
    8083             : 
    8084           0 :     return ToPointer(hSRS)->SetStereographic(dfOriginLat, dfCMeridian, dfScale,
    8085           0 :                                              dfFalseEasting, dfFalseNorthing);
    8086             : }
    8087             : 
    8088             : /************************************************************************/
    8089             : /*                               SetSOC()                               */
    8090             : /*                                                                      */
    8091             : /*      NOTE: This definition isn't really used in practice any more    */
    8092             : /*      and should be considered deprecated.  It seems that swiss       */
    8093             : /*      oblique mercator is now define as Hotine_Oblique_Mercator       */
    8094             : /*      with an azimuth of 90 and a rectified_grid_angle of 90.  See    */
    8095             : /*      EPSG:2056 and Bug 423.                                          */
    8096             : /************************************************************************/
    8097             : 
    8098           2 : OGRErr OGRSpatialReference::SetSOC(double dfLatitudeOfOrigin,
    8099             :                                    double dfCentralMeridian,
    8100             :                                    double dfFalseEasting,
    8101             :                                    double dfFalseNorthing)
    8102             : 
    8103             : {
    8104           4 :     TAKE_OPTIONAL_LOCK();
    8105             : 
    8106           2 :     return d->replaceConversionAndUnref(
    8107             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    8108             :             d->getPROJContext(), dfLatitudeOfOrigin, dfCentralMeridian, 90.0,
    8109             :             90.0, 1.0, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    8110           4 :             0.0));
    8111             : #if 0
    8112             :     SetProjection( SRS_PT_SWISS_OBLIQUE_CYLINDRICAL );
    8113             :     SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfLatitudeOfOrigin );
    8114             :     SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    8115             :     SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    8116             :     SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );
    8117             : 
    8118             :     return OGRERR_NONE;
    8119             : #endif
    8120             : }
    8121             : 
    8122             : /************************************************************************/
    8123             : /*                             OSRSetSOC()                              */
    8124             : /************************************************************************/
    8125             : 
    8126           0 : OGRErr OSRSetSOC(OGRSpatialReferenceH hSRS, double dfLatitudeOfOrigin,
    8127             :                  double dfCentralMeridian, double dfFalseEasting,
    8128             :                  double dfFalseNorthing)
    8129             : 
    8130             : {
    8131           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSOC", OGRERR_FAILURE);
    8132             : 
    8133           0 :     return ToPointer(hSRS)->SetSOC(dfLatitudeOfOrigin, dfCentralMeridian,
    8134           0 :                                    dfFalseEasting, dfFalseNorthing);
    8135             : }
    8136             : 
    8137             : /************************************************************************/
    8138             : /*                               SetVDG()                               */
    8139             : /************************************************************************/
    8140             : 
    8141           2 : OGRErr OGRSpatialReference::SetVDG(double dfCMeridian, double dfFalseEasting,
    8142             :                                    double dfFalseNorthing)
    8143             : 
    8144             : {
    8145           4 :     TAKE_OPTIONAL_LOCK();
    8146             : 
    8147           2 :     return d->replaceConversionAndUnref(proj_create_conversion_van_der_grinten(
    8148             :         d->getPROJContext(), dfCMeridian, dfFalseEasting, dfFalseNorthing,
    8149           4 :         nullptr, 0, nullptr, 0));
    8150             : }
    8151             : 
    8152             : /************************************************************************/
    8153             : /*                             OSRSetVDG()                              */
    8154             : /************************************************************************/
    8155             : 
    8156           0 : OGRErr OSRSetVDG(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    8157             :                  double dfFalseEasting, double dfFalseNorthing)
    8158             : 
    8159             : {
    8160           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVDG", OGRERR_FAILURE);
    8161             : 
    8162           0 :     return ToPointer(hSRS)->SetVDG(dfCentralMeridian, dfFalseEasting,
    8163           0 :                                    dfFalseNorthing);
    8164             : }
    8165             : 
    8166             : /************************************************************************/
    8167             : /*                               SetUTM()                               */
    8168             : /************************************************************************/
    8169             : 
    8170             : /**
    8171             :  * \brief Set UTM projection definition.
    8172             :  *
    8173             :  * This will generate a projection definition with the full set of
    8174             :  * transverse mercator projection parameters for the given UTM zone.
    8175             :  * If no PROJCS[] description is set yet, one will be set to look
    8176             :  * like "UTM Zone %d, {Northern, Southern} Hemisphere".
    8177             :  *
    8178             :  * This method is the same as the C function OSRSetUTM().
    8179             :  *
    8180             :  * @param nZone UTM zone.
    8181             :  *
    8182             :  * @param bNorth TRUE for northern hemisphere, or FALSE for southern
    8183             :  * hemisphere.
    8184             :  *
    8185             :  * @return OGRERR_NONE on success.
    8186             :  */
    8187             : 
    8188         313 : OGRErr OGRSpatialReference::SetUTM(int nZone, int bNorth)
    8189             : 
    8190             : {
    8191         626 :     TAKE_OPTIONAL_LOCK();
    8192             : 
    8193         313 :     if (nZone < 0 || nZone > 60)
    8194             :     {
    8195           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid zone: %d", nZone);
    8196           0 :         return OGRERR_FAILURE;
    8197             :     }
    8198             : 
    8199         313 :     return d->replaceConversionAndUnref(
    8200         313 :         proj_create_conversion_utm(d->getPROJContext(), nZone, bNorth));
    8201             : }
    8202             : 
    8203             : /************************************************************************/
    8204             : /*                             OSRSetUTM()                              */
    8205             : /************************************************************************/
    8206             : 
    8207             : /**
    8208             :  * \brief Set UTM projection definition.
    8209             :  *
    8210             :  * This is the same as the C++ method OGRSpatialReference::SetUTM()
    8211             :  */
    8212          19 : OGRErr OSRSetUTM(OGRSpatialReferenceH hSRS, int nZone, int bNorth)
    8213             : 
    8214             : {
    8215          19 :     VALIDATE_POINTER1(hSRS, "OSRSetUTM", OGRERR_FAILURE);
    8216             : 
    8217          19 :     return ToPointer(hSRS)->SetUTM(nZone, bNorth);
    8218             : }
    8219             : 
    8220             : /************************************************************************/
    8221             : /*                             GetUTMZone()                             */
    8222             : /*                                                                      */
    8223             : /*      Returns zero if it isn't UTM.                                   */
    8224             : /************************************************************************/
    8225             : 
    8226             : /**
    8227             :  * \brief Get utm zone information.
    8228             :  *
    8229             :  * This is the same as the C function OSRGetUTMZone().
    8230             :  *
    8231             :  * In SWIG bindings (Python, Java, etc) the GetUTMZone() method returns a
    8232             :  * zone which is negative in the southern hemisphere instead of having the
    8233             :  * pbNorth flag used in the C and C++ interface.
    8234             :  *
    8235             :  * @param pbNorth pointer to in to set to TRUE if northern hemisphere, or
    8236             :  * FALSE if southern.
    8237             :  *
    8238             :  * @return UTM zone number or zero if this isn't a UTM definition.
    8239             :  */
    8240             : 
    8241         611 : int OGRSpatialReference::GetUTMZone(int *pbNorth) const
    8242             : 
    8243             : {
    8244        1222 :     TAKE_OPTIONAL_LOCK();
    8245             : 
    8246         611 :     if (IsProjected() && GetAxesCount() == 3)
    8247             :     {
    8248           1 :         OGRSpatialReference *poSRSTmp = Clone();
    8249           1 :         poSRSTmp->DemoteTo2D(nullptr);
    8250           1 :         const int nZone = poSRSTmp->GetUTMZone(pbNorth);
    8251           1 :         delete poSRSTmp;
    8252           1 :         return nZone;
    8253             :     }
    8254             : 
    8255         610 :     const char *pszProjection = GetAttrValue("PROJECTION");
    8256             : 
    8257         610 :     if (pszProjection == nullptr ||
    8258         530 :         !EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    8259         278 :         return 0;
    8260             : 
    8261         332 :     if (GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) != 0.0)
    8262           5 :         return 0;
    8263             : 
    8264         327 :     if (GetProjParm(SRS_PP_SCALE_FACTOR, 1.0) != 0.9996)
    8265          15 :         return 0;
    8266             : 
    8267         312 :     if (fabs(GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0) - 500000.0) > 0.001)
    8268           6 :         return 0;
    8269             : 
    8270         306 :     const double dfFalseNorthing = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    8271             : 
    8272         306 :     if (dfFalseNorthing != 0.0 && fabs(dfFalseNorthing - 10000000.0) > 0.001)
    8273           0 :         return 0;
    8274             : 
    8275         306 :     if (pbNorth != nullptr)
    8276         240 :         *pbNorth = (dfFalseNorthing == 0);
    8277             : 
    8278             :     const double dfCentralMeridian =
    8279         306 :         GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    8280         306 :     const double dfZone = (dfCentralMeridian + 186.0) / 6.0;
    8281             : 
    8282         612 :     if (dfCentralMeridian < -177.00001 || dfCentralMeridian > 177.000001 ||
    8283         918 :         std::isnan(dfZone) ||
    8284         306 :         std::abs(dfZone - static_cast<int>(dfZone) - 0.5) > 0.00001)
    8285           0 :         return 0;
    8286             : 
    8287         306 :     return static_cast<int>(dfZone);
    8288             : }
    8289             : 
    8290             : /************************************************************************/
    8291             : /*                           OSRGetUTMZone()                            */
    8292             : /************************************************************************/
    8293             : 
    8294             : /**
    8295             :  * \brief Get utm zone information.
    8296             :  *
    8297             :  * This is the same as the C++ method OGRSpatialReference::GetUTMZone()
    8298             :  */
    8299           6 : int OSRGetUTMZone(OGRSpatialReferenceH hSRS, int *pbNorth)
    8300             : 
    8301             : {
    8302           6 :     VALIDATE_POINTER1(hSRS, "OSRGetUTMZone", 0);
    8303             : 
    8304           6 :     return ToPointer(hSRS)->GetUTMZone(pbNorth);
    8305             : }
    8306             : 
    8307             : /************************************************************************/
    8308             : /*                             SetWagner()                              */
    8309             : /************************************************************************/
    8310             : 
    8311           0 : OGRErr OGRSpatialReference::SetWagner(int nVariation,  // 1--7.
    8312             :                                       double dfCenterLat, double dfFalseEasting,
    8313             :                                       double dfFalseNorthing)
    8314             : 
    8315             : {
    8316           0 :     TAKE_OPTIONAL_LOCK();
    8317             : 
    8318             :     PJ *conv;
    8319           0 :     if (nVariation == 1)
    8320             :     {
    8321           0 :         conv = proj_create_conversion_wagner_i(d->getPROJContext(), 0.0,
    8322             :                                                dfFalseEasting, dfFalseNorthing,
    8323             :                                                nullptr, 0.0, nullptr, 0.0);
    8324             :     }
    8325           0 :     else if (nVariation == 2)
    8326             :     {
    8327           0 :         conv = proj_create_conversion_wagner_ii(d->getPROJContext(), 0.0,
    8328             :                                                 dfFalseEasting, dfFalseNorthing,
    8329             :                                                 nullptr, 0.0, nullptr, 0.0);
    8330             :     }
    8331           0 :     else if (nVariation == 3)
    8332             :     {
    8333           0 :         conv = proj_create_conversion_wagner_iii(
    8334             :             d->getPROJContext(), dfCenterLat, 0.0, dfFalseEasting,
    8335             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    8336             :     }
    8337           0 :     else if (nVariation == 4)
    8338             :     {
    8339           0 :         conv = proj_create_conversion_wagner_iv(d->getPROJContext(), 0.0,
    8340             :                                                 dfFalseEasting, dfFalseNorthing,
    8341             :                                                 nullptr, 0.0, nullptr, 0.0);
    8342             :     }
    8343           0 :     else if (nVariation == 5)
    8344             :     {
    8345           0 :         conv = proj_create_conversion_wagner_v(d->getPROJContext(), 0.0,
    8346             :                                                dfFalseEasting, dfFalseNorthing,
    8347             :                                                nullptr, 0.0, nullptr, 0.0);
    8348             :     }
    8349           0 :     else if (nVariation == 6)
    8350             :     {
    8351           0 :         conv = proj_create_conversion_wagner_vi(d->getPROJContext(), 0.0,
    8352             :                                                 dfFalseEasting, dfFalseNorthing,
    8353             :                                                 nullptr, 0.0, nullptr, 0.0);
    8354             :     }
    8355           0 :     else if (nVariation == 7)
    8356             :     {
    8357           0 :         conv = proj_create_conversion_wagner_vii(
    8358             :             d->getPROJContext(), 0.0, dfFalseEasting, dfFalseNorthing, nullptr,
    8359             :             0.0, nullptr, 0.0);
    8360             :     }
    8361             :     else
    8362             :     {
    8363           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8364             :                  "Unsupported Wagner variation (%d).", nVariation);
    8365           0 :         return OGRERR_UNSUPPORTED_SRS;
    8366             :     }
    8367             : 
    8368           0 :     return d->replaceConversionAndUnref(conv);
    8369             : }
    8370             : 
    8371             : /************************************************************************/
    8372             : /*                            OSRSetWagner()                            */
    8373             : /************************************************************************/
    8374             : 
    8375           0 : OGRErr OSRSetWagner(OGRSpatialReferenceH hSRS, int nVariation,
    8376             :                     double dfCenterLat, double dfFalseEasting,
    8377             :                     double dfFalseNorthing)
    8378             : 
    8379             : {
    8380           0 :     VALIDATE_POINTER1(hSRS, "OSRSetWagner", OGRERR_FAILURE);
    8381             : 
    8382           0 :     return ToPointer(hSRS)->SetWagner(nVariation, dfCenterLat, dfFalseEasting,
    8383           0 :                                       dfFalseNorthing);
    8384             : }
    8385             : 
    8386             : /************************************************************************/
    8387             : /*                               SetQSC()                               */
    8388             : /************************************************************************/
    8389             : 
    8390           0 : OGRErr OGRSpatialReference::SetQSC(double dfCenterLat, double dfCenterLong)
    8391             : {
    8392           0 :     TAKE_OPTIONAL_LOCK();
    8393             : 
    8394           0 :     return d->replaceConversionAndUnref(
    8395             :         proj_create_conversion_quadrilateralized_spherical_cube(
    8396             :             d->getPROJContext(), dfCenterLat, dfCenterLong, 0.0, 0.0, nullptr,
    8397           0 :             0, nullptr, 0));
    8398             : }
    8399             : 
    8400             : /************************************************************************/
    8401             : /*                             OSRSetQSC()                              */
    8402             : /************************************************************************/
    8403             : 
    8404           0 : OGRErr OSRSetQSC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    8405             :                  double dfCenterLong)
    8406             : 
    8407             : {
    8408           0 :     VALIDATE_POINTER1(hSRS, "OSRSetQSC", OGRERR_FAILURE);
    8409             : 
    8410           0 :     return ToPointer(hSRS)->SetQSC(dfCenterLat, dfCenterLong);
    8411             : }
    8412             : 
    8413             : /************************************************************************/
    8414             : /*                               SetSCH()                               */
    8415             : /************************************************************************/
    8416             : 
    8417           0 : OGRErr OGRSpatialReference::SetSCH(double dfPegLat, double dfPegLong,
    8418             :                                    double dfPegHeading, double dfPegHgt)
    8419             : 
    8420             : {
    8421           0 :     TAKE_OPTIONAL_LOCK();
    8422             : 
    8423           0 :     return d->replaceConversionAndUnref(
    8424             :         proj_create_conversion_spherical_cross_track_height(
    8425             :             d->getPROJContext(), dfPegLat, dfPegLong, dfPegHeading, dfPegHgt,
    8426           0 :             nullptr, 0, nullptr, 0));
    8427             : }
    8428             : 
    8429             : /************************************************************************/
    8430             : /*                             OSRSetSCH()                              */
    8431             : /************************************************************************/
    8432             : 
    8433           0 : OGRErr OSRSetSCH(OGRSpatialReferenceH hSRS, double dfPegLat, double dfPegLong,
    8434             :                  double dfPegHeading, double dfPegHgt)
    8435             : 
    8436             : {
    8437           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSCH", OGRERR_FAILURE);
    8438             : 
    8439           0 :     return ToPointer(hSRS)->SetSCH(dfPegLat, dfPegLong, dfPegHeading, dfPegHgt);
    8440             : }
    8441             : 
    8442             : /************************************************************************/
    8443             : /*                       SetVerticalPerspective()                       */
    8444             : /************************************************************************/
    8445             : 
    8446           4 : OGRErr OGRSpatialReference::SetVerticalPerspective(
    8447             :     double dfTopoOriginLat, double dfTopoOriginLon, double dfTopoOriginHeight,
    8448             :     double dfViewPointHeight, double dfFalseEasting, double dfFalseNorthing)
    8449             : {
    8450           8 :     TAKE_OPTIONAL_LOCK();
    8451             : 
    8452           4 :     return d->replaceConversionAndUnref(
    8453             :         proj_create_conversion_vertical_perspective(
    8454             :             d->getPROJContext(), dfTopoOriginLat, dfTopoOriginLon,
    8455             :             dfTopoOriginHeight, dfViewPointHeight, dfFalseEasting,
    8456           8 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    8457             : }
    8458             : 
    8459             : /************************************************************************/
    8460             : /*                     OSRSetVerticalPerspective()                      */
    8461             : /************************************************************************/
    8462             : 
    8463           1 : OGRErr OSRSetVerticalPerspective(OGRSpatialReferenceH hSRS,
    8464             :                                  double dfTopoOriginLat, double dfTopoOriginLon,
    8465             :                                  double dfTopoOriginHeight,
    8466             :                                  double dfViewPointHeight,
    8467             :                                  double dfFalseEasting, double dfFalseNorthing)
    8468             : 
    8469             : {
    8470           1 :     VALIDATE_POINTER1(hSRS, "OSRSetVerticalPerspective", OGRERR_FAILURE);
    8471             : 
    8472           1 :     return ToPointer(hSRS)->SetVerticalPerspective(
    8473             :         dfTopoOriginLat, dfTopoOriginLon, dfTopoOriginHeight, dfViewPointHeight,
    8474           1 :         dfFalseEasting, dfFalseNorthing);
    8475             : }
    8476             : 
    8477             : /************************************************************************/
    8478             : /*          SetDerivedGeogCRSWithPoleRotationGRIBConvention()           */
    8479             : /************************************************************************/
    8480             : 
    8481           2 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationGRIBConvention(
    8482             :     const char *pszCRSName, double dfSouthPoleLat, double dfSouthPoleLon,
    8483             :     double dfAxisRotation)
    8484             : {
    8485           4 :     TAKE_OPTIONAL_LOCK();
    8486             : 
    8487           2 :     d->refreshProjObj();
    8488           2 :     if (!d->m_pj_crs)
    8489           0 :         return OGRERR_FAILURE;
    8490           2 :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8491           0 :         return OGRERR_FAILURE;
    8492           2 :     auto ctxt = d->getPROJContext();
    8493           2 :     auto conv = proj_create_conversion_pole_rotation_grib_convention(
    8494             :         ctxt, dfSouthPoleLat, dfSouthPoleLon, dfAxisRotation, nullptr, 0);
    8495           2 :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8496           4 :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8497           2 :                                                    d->m_pj_crs, conv, cs));
    8498           2 :     proj_destroy(conv);
    8499           2 :     proj_destroy(cs);
    8500           2 :     return OGRERR_NONE;
    8501             : }
    8502             : 
    8503             : /************************************************************************/
    8504             : /*        SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention()         */
    8505             : /************************************************************************/
    8506             : 
    8507           3 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention(
    8508             :     const char *pszCRSName, double dfGridNorthPoleLat,
    8509             :     double dfGridNorthPoleLon, double dfNorthPoleGridLon)
    8510             : {
    8511           3 :     TAKE_OPTIONAL_LOCK();
    8512             : 
    8513             : #if PROJ_VERSION_MAJOR > 8 ||                                                  \
    8514             :     (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR >= 2)
    8515             :     d->refreshProjObj();
    8516             :     if (!d->m_pj_crs)
    8517             :         return OGRERR_FAILURE;
    8518             :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8519             :         return OGRERR_FAILURE;
    8520             :     auto ctxt = d->getPROJContext();
    8521             :     auto conv = proj_create_conversion_pole_rotation_netcdf_cf_convention(
    8522             :         ctxt, dfGridNorthPoleLat, dfGridNorthPoleLon, dfNorthPoleGridLon,
    8523             :         nullptr, 0);
    8524             :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8525             :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8526             :                                                    d->m_pj_crs, conv, cs));
    8527             :     proj_destroy(conv);
    8528             :     proj_destroy(cs);
    8529             :     return OGRERR_NONE;
    8530             : #else
    8531             :     (void)pszCRSName;
    8532           3 :     SetProjection("Rotated_pole");
    8533           3 :     SetExtension(
    8534             :         "PROJCS", "PROJ4",
    8535             :         CPLSPrintf("+proj=ob_tran +o_proj=longlat +lon_0=%.17g +o_lon_p=%.17g "
    8536             :                    "+o_lat_p=%.17g +a=%.17g +b=%.17g "
    8537             :                    "+to_meter=0.0174532925199433 "
    8538             :                    "+wktext",
    8539             :                    180.0 + dfGridNorthPoleLon, dfNorthPoleGridLon,
    8540             :                    dfGridNorthPoleLat, GetSemiMajor(nullptr),
    8541             :                    GetSemiMinor(nullptr)));
    8542           6 :     return OGRERR_NONE;
    8543             : #endif
    8544             : }
    8545             : 
    8546             : /************************************************************************/
    8547             : /*                            SetAuthority()                            */
    8548             : /************************************************************************/
    8549             : 
    8550             : /**
    8551             :  * \brief Set the authority for a node.
    8552             :  *
    8553             :  * This method is the same as the C function OSRSetAuthority().
    8554             :  *
    8555             :  * @param pszTargetKey the partial or complete path to the node to
    8556             :  * set an authority on.  i.e. "PROJCS", "GEOGCS" or "GEOGCS|UNIT".
    8557             :  *
    8558             :  * @param pszAuthority authority name, such as "EPSG".
    8559             :  *
    8560             :  * @param nCode code for value with this authority.
    8561             :  *
    8562             :  * @return OGRERR_NONE on success.
    8563             :  */
    8564             : 
    8565       12924 : OGRErr OGRSpatialReference::SetAuthority(const char *pszTargetKey,
    8566             :                                          const char *pszAuthority, int nCode)
    8567             : 
    8568             : {
    8569       25848 :     TAKE_OPTIONAL_LOCK();
    8570             : 
    8571       12924 :     d->refreshProjObj();
    8572       12924 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8573             : 
    8574       12924 :     if (pszTargetKey == nullptr)
    8575             :     {
    8576         265 :         if (!d->m_pj_crs)
    8577           0 :             return OGRERR_FAILURE;
    8578         265 :         CPLString osCode;
    8579         265 :         osCode.Printf("%d", nCode);
    8580         265 :         d->demoteFromBoundCRS();
    8581         265 :         d->setPjCRS(proj_alter_id(d->getPROJContext(), d->m_pj_crs,
    8582             :                                   pszAuthority, osCode.c_str()));
    8583         265 :         d->undoDemoteFromBoundCRS();
    8584         265 :         return OGRERR_NONE;
    8585             :     }
    8586             : 
    8587       12659 :     d->demoteFromBoundCRS();
    8588       12659 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS && EQUAL(pszTargetKey, "GEOGCS"))
    8589             :     {
    8590        4186 :         CPLString osCode;
    8591        4186 :         osCode.Printf("%d", nCode);
    8592             :         auto newGeogCRS =
    8593        4186 :             proj_alter_id(d->getPROJContext(), d->getGeodBaseCRS(),
    8594             :                           pszAuthority, osCode.c_str());
    8595             : 
    8596             :         auto conv =
    8597        4186 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    8598             : 
    8599        4186 :         auto projCRS = proj_create_projected_crs(
    8600             :             d->getPROJContext(), d->getProjCRSName(), newGeogCRS, conv,
    8601        4186 :             d->getProjCRSCoordSys());
    8602             : 
    8603             :         // Preserve existing id on the PROJCRS
    8604        4186 :         const char *pszProjCRSAuthName = proj_get_id_auth_name(d->m_pj_crs, 0);
    8605        4186 :         const char *pszProjCRSCode = proj_get_id_code(d->m_pj_crs, 0);
    8606        4186 :         if (pszProjCRSAuthName && pszProjCRSCode)
    8607             :         {
    8608             :             auto projCRSWithId =
    8609           0 :                 proj_alter_id(d->getPROJContext(), projCRS, pszProjCRSAuthName,
    8610             :                               pszProjCRSCode);
    8611           0 :             proj_destroy(projCRS);
    8612           0 :             projCRS = projCRSWithId;
    8613             :         }
    8614             : 
    8615        4186 :         proj_destroy(newGeogCRS);
    8616        4186 :         proj_destroy(conv);
    8617             : 
    8618        4186 :         d->setPjCRS(projCRS);
    8619        4186 :         d->undoDemoteFromBoundCRS();
    8620        4186 :         return OGRERR_NONE;
    8621             :     }
    8622        8473 :     d->undoDemoteFromBoundCRS();
    8623             : 
    8624             :     /* -------------------------------------------------------------------- */
    8625             :     /*      Find the node below which the authority should be put.          */
    8626             :     /* -------------------------------------------------------------------- */
    8627        8473 :     OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8628             : 
    8629        8473 :     if (poNode == nullptr)
    8630           0 :         return OGRERR_FAILURE;
    8631             : 
    8632             :     /* -------------------------------------------------------------------- */
    8633             :     /*      If there is an existing AUTHORITY child blow it away before     */
    8634             :     /*      trying to set a new one.                                        */
    8635             :     /* -------------------------------------------------------------------- */
    8636        8473 :     int iOldChild = poNode->FindChild("AUTHORITY");
    8637        8473 :     if (iOldChild != -1)
    8638           5 :         poNode->DestroyChild(iOldChild);
    8639             : 
    8640             :     /* -------------------------------------------------------------------- */
    8641             :     /*      Create a new authority node.                                    */
    8642             :     /* -------------------------------------------------------------------- */
    8643        8473 :     char szCode[32] = {};
    8644             : 
    8645        8473 :     snprintf(szCode, sizeof(szCode), "%d", nCode);
    8646             : 
    8647        8473 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("AUTHORITY");
    8648        8473 :     poAuthNode->AddChild(new OGR_SRSNode(pszAuthority));
    8649        8473 :     poAuthNode->AddChild(new OGR_SRSNode(szCode));
    8650             : 
    8651        8473 :     poNode->AddChild(poAuthNode);
    8652             : 
    8653        8473 :     return OGRERR_NONE;
    8654             : }
    8655             : 
    8656             : /************************************************************************/
    8657             : /*                          OSRSetAuthority()                           */
    8658             : /************************************************************************/
    8659             : 
    8660             : /**
    8661             :  * \brief Set the authority for a node.
    8662             :  *
    8663             :  * This function is the same as OGRSpatialReference::SetAuthority().
    8664             :  */
    8665           0 : OGRErr OSRSetAuthority(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
    8666             :                        const char *pszAuthority, int nCode)
    8667             : 
    8668             : {
    8669           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAuthority", OGRERR_FAILURE);
    8670             : 
    8671           0 :     return ToPointer(hSRS)->SetAuthority(pszTargetKey, pszAuthority, nCode);
    8672             : }
    8673             : 
    8674             : /************************************************************************/
    8675             : /*                          GetAuthorityCode()                          */
    8676             : /************************************************************************/
    8677             : 
    8678             : /**
    8679             :  * \brief Get the authority code for a node.
    8680             :  *
    8681             :  * This method is used to query an AUTHORITY[] node from within the
    8682             :  * WKT tree, and fetch the code value.
    8683             :  *
    8684             :  * While in theory values may be non-numeric, for the EPSG authority all
    8685             :  * code values should be integral.
    8686             :  *
    8687             :  * This method is the same as the C function OSRGetAuthorityCode().
    8688             :  *
    8689             :  * @param pszTargetKey the partial or complete path to the node to
    8690             :  * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
    8691             :  * search for an authority node on the root element.
    8692             :  *
    8693             :  * @return value code from authority node, or NULL on failure.  The value
    8694             :  * returned is internal and should not be freed or modified.
    8695             :  */
    8696             : 
    8697             : const char *
    8698       52437 : OGRSpatialReference::GetAuthorityCode(const char *pszTargetKey) const
    8699             : 
    8700             : {
    8701      104874 :     TAKE_OPTIONAL_LOCK();
    8702             : 
    8703       52437 :     d->refreshProjObj();
    8704       52437 :     const char *pszInputTargetKey = pszTargetKey;
    8705       52437 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8706       52437 :     if (pszTargetKey == nullptr)
    8707             :     {
    8708       43902 :         if (!d->m_pj_crs)
    8709             :         {
    8710          17 :             return nullptr;
    8711             :         }
    8712       43885 :         d->demoteFromBoundCRS();
    8713       43885 :         auto ret = proj_get_id_code(d->m_pj_crs, 0);
    8714       43885 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8715             :         {
    8716        1387 :             auto ctxt = d->getPROJContext();
    8717        1387 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8718        1387 :             if (cs)
    8719             :             {
    8720        1387 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8721        1387 :                 proj_destroy(cs);
    8722        1387 :                 if (axisCount == 3)
    8723             :                 {
    8724             :                     // This might come from a COMPD_CS with a VERT_DATUM type =
    8725             :                     // 2002 in which case, using the WKT1 representation will
    8726             :                     // enable us to recover the EPSG code.
    8727          14 :                     pszTargetKey = pszInputTargetKey;
    8728             :                 }
    8729             :             }
    8730             :         }
    8731       43885 :         d->undoDemoteFromBoundCRS();
    8732       43885 :         if (ret != nullptr || pszTargetKey == nullptr)
    8733             :         {
    8734       43885 :             return ret;
    8735             :         }
    8736             :     }
    8737             : 
    8738             :     // Special key for that context
    8739        8539 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8740           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8741             :     {
    8742           4 :         auto ctxt = d->getPROJContext();
    8743           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8744           4 :         if (crs)
    8745             :         {
    8746           4 :             const char *ret = proj_get_id_code(crs, 0);
    8747           4 :             if (ret)
    8748           4 :                 ret = CPLSPrintf("%s", ret);
    8749           4 :             proj_destroy(crs);
    8750           4 :             return ret;
    8751             :         }
    8752             :     }
    8753        8535 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8754           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8755             :     {
    8756           4 :         auto ctxt = d->getPROJContext();
    8757           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8758           4 :         if (crs)
    8759             :         {
    8760           4 :             const char *ret = proj_get_id_code(crs, 0);
    8761           4 :             if (ret)
    8762           4 :                 ret = CPLSPrintf("%s", ret);
    8763           4 :             proj_destroy(crs);
    8764           4 :             return ret;
    8765             :         }
    8766             :     }
    8767             : 
    8768             :     /* -------------------------------------------------------------------- */
    8769             :     /*      Find the node below which the authority should be put.          */
    8770             :     /* -------------------------------------------------------------------- */
    8771        8527 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8772             : 
    8773        8527 :     if (poNode == nullptr)
    8774         106 :         return nullptr;
    8775             : 
    8776             :     /* -------------------------------------------------------------------- */
    8777             :     /*      Fetch AUTHORITY child if there is one.                          */
    8778             :     /* -------------------------------------------------------------------- */
    8779        8421 :     if (poNode->FindChild("AUTHORITY") == -1)
    8780         197 :         return nullptr;
    8781             : 
    8782        8224 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8783             : 
    8784             :     /* -------------------------------------------------------------------- */
    8785             :     /*      Create a new authority node.                                    */
    8786             :     /* -------------------------------------------------------------------- */
    8787        8224 :     if (poNode->GetChildCount() < 2)
    8788           0 :         return nullptr;
    8789             : 
    8790        8224 :     return poNode->GetChild(1)->GetValue();
    8791             : }
    8792             : 
    8793             : /************************************************************************/
    8794             : /*                        OSRGetAuthorityCode()                         */
    8795             : /************************************************************************/
    8796             : 
    8797             : /**
    8798             :  * \brief Get the authority code for a node.
    8799             :  *
    8800             :  * This function is the same as OGRSpatialReference::GetAuthorityCode().
    8801             :  */
    8802         830 : const char *OSRGetAuthorityCode(OGRSpatialReferenceH hSRS,
    8803             :                                 const char *pszTargetKey)
    8804             : 
    8805             : {
    8806         830 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityCode", nullptr);
    8807             : 
    8808         830 :     return ToPointer(hSRS)->GetAuthorityCode(pszTargetKey);
    8809             : }
    8810             : 
    8811             : /************************************************************************/
    8812             : /*                          GetAuthorityName()                          */
    8813             : /************************************************************************/
    8814             : 
    8815             : /**
    8816             :  * \brief Get the authority name for a node.
    8817             :  *
    8818             :  * This method is used to query an AUTHORITY[] node from within the
    8819             :  * WKT tree, and fetch the authority name value.
    8820             :  *
    8821             :  * The most common authority is "EPSG".
    8822             :  *
    8823             :  * This method is the same as the C function OSRGetAuthorityName().
    8824             :  *
    8825             :  * @param pszTargetKey the partial or complete path to the node to
    8826             :  * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
    8827             :  * search for an authority node on the root element.
    8828             :  *
    8829             :  * @return value code from authority node, or NULL on failure. The value
    8830             :  * returned is internal and should not be freed or modified.
    8831             :  */
    8832             : 
    8833             : const char *
    8834       66491 : OGRSpatialReference::GetAuthorityName(const char *pszTargetKey) const
    8835             : 
    8836             : {
    8837      132982 :     TAKE_OPTIONAL_LOCK();
    8838             : 
    8839       66491 :     d->refreshProjObj();
    8840       66491 :     const char *pszInputTargetKey = pszTargetKey;
    8841       66491 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8842       66491 :     if (pszTargetKey == nullptr)
    8843             :     {
    8844       36929 :         if (!d->m_pj_crs)
    8845             :         {
    8846          18 :             return nullptr;
    8847             :         }
    8848       36911 :         d->demoteFromBoundCRS();
    8849       36911 :         auto ret = proj_get_id_auth_name(d->m_pj_crs, 0);
    8850       36911 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8851             :         {
    8852        1008 :             auto ctxt = d->getPROJContext();
    8853        1008 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8854        1008 :             if (cs)
    8855             :             {
    8856        1008 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8857        1008 :                 proj_destroy(cs);
    8858        1008 :                 if (axisCount == 3)
    8859             :                 {
    8860             :                     // This might come from a COMPD_CS with a VERT_DATUM type =
    8861             :                     // 2002 in which case, using the WKT1 representation will
    8862             :                     // enable us to recover the EPSG code.
    8863          14 :                     pszTargetKey = pszInputTargetKey;
    8864             :                 }
    8865             :             }
    8866             :         }
    8867       36911 :         d->undoDemoteFromBoundCRS();
    8868       36911 :         if (ret != nullptr || pszTargetKey == nullptr)
    8869             :         {
    8870       36911 :             return ret;
    8871             :         }
    8872             :     }
    8873             : 
    8874             :     // Special key for that context
    8875       29566 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8876           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8877             :     {
    8878           4 :         auto ctxt = d->getPROJContext();
    8879           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8880           4 :         if (crs)
    8881             :         {
    8882           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8883           4 :             if (ret)
    8884           4 :                 ret = CPLSPrintf("%s", ret);
    8885           4 :             proj_destroy(crs);
    8886           4 :             return ret;
    8887             :         }
    8888             :     }
    8889       29562 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8890           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8891             :     {
    8892           4 :         auto ctxt = d->getPROJContext();
    8893           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8894           4 :         if (crs)
    8895             :         {
    8896           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8897           4 :             if (ret)
    8898           4 :                 ret = CPLSPrintf("%s", ret);
    8899           4 :             proj_destroy(crs);
    8900           4 :             return ret;
    8901             :         }
    8902             :     }
    8903             : 
    8904             :     /* -------------------------------------------------------------------- */
    8905             :     /*      Find the node below which the authority should be put.          */
    8906             :     /* -------------------------------------------------------------------- */
    8907       29554 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8908             : 
    8909       29554 :     if (poNode == nullptr)
    8910       12140 :         return nullptr;
    8911             : 
    8912             :     /* -------------------------------------------------------------------- */
    8913             :     /*      Fetch AUTHORITY child if there is one.                          */
    8914             :     /* -------------------------------------------------------------------- */
    8915       17414 :     if (poNode->FindChild("AUTHORITY") == -1)
    8916        1597 :         return nullptr;
    8917             : 
    8918       15817 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8919             : 
    8920             :     /* -------------------------------------------------------------------- */
    8921             :     /*      Create a new authority node.                                    */
    8922             :     /* -------------------------------------------------------------------- */
    8923       15817 :     if (poNode->GetChildCount() < 2)
    8924           0 :         return nullptr;
    8925             : 
    8926       15817 :     return poNode->GetChild(0)->GetValue();
    8927             : }
    8928             : 
    8929             : /************************************************************************/
    8930             : /*                        OSRGetAuthorityName()                         */
    8931             : /************************************************************************/
    8932             : 
    8933             : /**
    8934             :  * \brief Get the authority name for a node.
    8935             :  *
    8936             :  * This function is the same as OGRSpatialReference::GetAuthorityName().
    8937             :  */
    8938          94 : const char *OSRGetAuthorityName(OGRSpatialReferenceH hSRS,
    8939             :                                 const char *pszTargetKey)
    8940             : 
    8941             : {
    8942          94 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityName", nullptr);
    8943             : 
    8944          94 :     return ToPointer(hSRS)->GetAuthorityName(pszTargetKey);
    8945             : }
    8946             : 
    8947             : /************************************************************************/
    8948             : /*                             GetOGCURN()                              */
    8949             : /************************************************************************/
    8950             : 
    8951             : /**
    8952             :  * \brief Get a OGC URN string describing the CRS, when possible
    8953             :  *
    8954             :  * This method assumes that the CRS has a top-level identifier, or is
    8955             :  * a compound CRS whose horizontal and vertical parts have a top-level
    8956             :  * identifier.
    8957             :  *
    8958             :  * @return a string to free with CPLFree(), or nullptr when no result can be
    8959             :  * generated
    8960             :  *
    8961             :  * @since GDAL 3.5
    8962             :  */
    8963             : 
    8964          68 : char *OGRSpatialReference::GetOGCURN() const
    8965             : 
    8966             : {
    8967         136 :     TAKE_OPTIONAL_LOCK();
    8968             : 
    8969          68 :     const char *pszAuthName = GetAuthorityName();
    8970          68 :     const char *pszAuthCode = GetAuthorityCode();
    8971          68 :     if (pszAuthName && pszAuthCode)
    8972          65 :         return CPLStrdup(
    8973          65 :             CPLSPrintf("urn:ogc:def:crs:%s::%s", pszAuthName, pszAuthCode));
    8974           3 :     if (d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    8975           2 :         return nullptr;
    8976           1 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    8977           1 :     auto vertCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    8978           1 :     char *pszRet = nullptr;
    8979           1 :     if (horizCRS && vertCRS)
    8980             :     {
    8981           1 :         auto horizAuthName = proj_get_id_auth_name(horizCRS, 0);
    8982           1 :         auto horizAuthCode = proj_get_id_code(horizCRS, 0);
    8983           1 :         auto vertAuthName = proj_get_id_auth_name(vertCRS, 0);
    8984           1 :         auto vertAuthCode = proj_get_id_code(vertCRS, 0);
    8985           1 :         if (horizAuthName && horizAuthCode && vertAuthName && vertAuthCode)
    8986             :         {
    8987           1 :             pszRet = CPLStrdup(CPLSPrintf(
    8988             :                 "urn:ogc:def:crs,crs:%s::%s,crs:%s::%s", horizAuthName,
    8989             :                 horizAuthCode, vertAuthName, vertAuthCode));
    8990             :         }
    8991             :     }
    8992           1 :     proj_destroy(horizCRS);
    8993           1 :     proj_destroy(vertCRS);
    8994           1 :     return pszRet;
    8995             : }
    8996             : 
    8997             : /************************************************************************/
    8998             : /*                           StripVertical()                            */
    8999             : /************************************************************************/
    9000             : 
    9001             : /**
    9002             :  * \brief Convert a compound cs into a horizontal CS.
    9003             :  *
    9004             :  * If this SRS is of type COMPD_CS[] then the vertical CS and the root COMPD_CS
    9005             :  * nodes are stripped resulting and only the horizontal coordinate system
    9006             :  * portion remains (normally PROJCS, GEOGCS or LOCAL_CS).
    9007             :  *
    9008             :  * If this is not a compound coordinate system then nothing is changed.
    9009             :  *
    9010             :  * This method is the same as the C function OSRStripVertical().
    9011             :  *
    9012             :  */
    9013             : 
    9014          47 : OGRErr OGRSpatialReference::StripVertical()
    9015             : 
    9016             : {
    9017          94 :     TAKE_OPTIONAL_LOCK();
    9018             : 
    9019          47 :     d->refreshProjObj();
    9020          47 :     d->demoteFromBoundCRS();
    9021          47 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    9022             :     {
    9023           0 :         d->undoDemoteFromBoundCRS();
    9024           0 :         return OGRERR_NONE;
    9025             :     }
    9026          47 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9027          47 :     if (!horizCRS)
    9028             :     {
    9029           0 :         d->undoDemoteFromBoundCRS();
    9030           0 :         return OGRERR_FAILURE;
    9031             :     }
    9032             : 
    9033          47 :     bool reuseExistingBoundCRS = false;
    9034          47 :     if (d->m_pj_bound_crs_target)
    9035             :     {
    9036           4 :         auto type = proj_get_type(d->m_pj_bound_crs_target);
    9037           8 :         reuseExistingBoundCRS = type == PJ_TYPE_GEOCENTRIC_CRS ||
    9038           8 :                                 type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9039             :                                 type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9040             :     }
    9041             : 
    9042          47 :     if (reuseExistingBoundCRS)
    9043             :     {
    9044           4 :         auto newBoundCRS = proj_crs_create_bound_crs(
    9045           4 :             d->getPROJContext(), horizCRS, d->m_pj_bound_crs_target,
    9046           4 :             d->m_pj_bound_crs_co);
    9047           4 :         proj_destroy(horizCRS);
    9048           4 :         d->undoDemoteFromBoundCRS();
    9049           4 :         d->setPjCRS(newBoundCRS);
    9050             :     }
    9051             :     else
    9052             :     {
    9053          43 :         d->undoDemoteFromBoundCRS();
    9054          43 :         d->setPjCRS(horizCRS);
    9055             :     }
    9056             : 
    9057          47 :     return OGRERR_NONE;
    9058             : }
    9059             : 
    9060             : /************************************************************************/
    9061             : /*                          OSRStripVertical()                          */
    9062             : /************************************************************************/
    9063             : /**
    9064             :  * \brief Convert a compound cs into a horizontal CS.
    9065             :  *
    9066             :  * This function is the same as the C++ method
    9067             :  * OGRSpatialReference::StripVertical().
    9068             :  */
    9069           1 : OGRErr OSRStripVertical(OGRSpatialReferenceH hSRS)
    9070             : 
    9071             : {
    9072           1 :     VALIDATE_POINTER1(hSRS, "OSRStripVertical", OGRERR_FAILURE);
    9073             : 
    9074           1 :     return OGRSpatialReference::FromHandle(hSRS)->StripVertical();
    9075             : }
    9076             : 
    9077             : /************************************************************************/
    9078             : /*                 StripTOWGS84IfKnownDatumAndAllowed()                 */
    9079             : /************************************************************************/
    9080             : 
    9081             : /**
    9082             :  * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
    9083             :  *        and this is allowed by the user.
    9084             :  *
    9085             :  * The default behavior is to remove TOWGS84 information if the CRS has a
    9086             :  * known horizontal datum. This can be disabled by setting the
    9087             :  * OSR_STRIP_TOWGS84 configuration option to NO.
    9088             :  *
    9089             :  * @return true if TOWGS84 has been removed.
    9090             :  * @since OGR 3.1.0
    9091             :  */
    9092             : 
    9093        9645 : bool OGRSpatialReference::StripTOWGS84IfKnownDatumAndAllowed()
    9094             : {
    9095        9645 :     if (CPLTestBool(CPLGetConfigOption("OSR_STRIP_TOWGS84", "YES")))
    9096             :     {
    9097        9642 :         if (StripTOWGS84IfKnownDatum())
    9098             :         {
    9099          72 :             CPLDebug("OSR", "TOWGS84 information has been removed. "
    9100             :                             "It can be kept by setting the OSR_STRIP_TOWGS84 "
    9101             :                             "configuration option to NO");
    9102          72 :             return true;
    9103             :         }
    9104             :     }
    9105        9573 :     return false;
    9106             : }
    9107             : 
    9108             : /************************************************************************/
    9109             : /*                      StripTOWGS84IfKnownDatum()                      */
    9110             : /************************************************************************/
    9111             : 
    9112             : /**
    9113             :  * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
    9114             :  *
    9115             :  * @return true if TOWGS84 has been removed.
    9116             :  * @since OGR 3.1.0
    9117             :  */
    9118             : 
    9119        9648 : bool OGRSpatialReference::StripTOWGS84IfKnownDatum()
    9120             : 
    9121             : {
    9122       19296 :     TAKE_OPTIONAL_LOCK();
    9123             : 
    9124        9648 :     d->refreshProjObj();
    9125        9648 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_BOUND_CRS)
    9126             :     {
    9127        9556 :         return false;
    9128             :     }
    9129          92 :     auto ctxt = d->getPROJContext();
    9130          92 :     auto baseCRS = proj_get_source_crs(ctxt, d->m_pj_crs);
    9131          92 :     if (proj_get_type(baseCRS) == PJ_TYPE_COMPOUND_CRS)
    9132             :     {
    9133           3 :         proj_destroy(baseCRS);
    9134           3 :         return false;
    9135             :     }
    9136             : 
    9137             :     // Known base CRS code ? Return base CRS
    9138          89 :     const char *pszCode = proj_get_id_code(baseCRS, 0);
    9139          89 :     if (pszCode)
    9140             :     {
    9141           2 :         d->setPjCRS(baseCRS);
    9142           2 :         return true;
    9143             :     }
    9144             : 
    9145          87 :     auto datum = proj_crs_get_datum(ctxt, baseCRS);
    9146             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9147             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9148             :     if (datum == nullptr)
    9149             :     {
    9150             :         datum = proj_crs_get_datum_ensemble(ctxt, baseCRS);
    9151             :     }
    9152             : #endif
    9153          87 :     if (!datum)
    9154             :     {
    9155           0 :         proj_destroy(baseCRS);
    9156           0 :         return false;
    9157             :     }
    9158             : 
    9159             :     // Known datum code ? Return base CRS
    9160          87 :     pszCode = proj_get_id_code(datum, 0);
    9161          87 :     if (pszCode)
    9162             :     {
    9163           3 :         proj_destroy(datum);
    9164           3 :         d->setPjCRS(baseCRS);
    9165           3 :         return true;
    9166             :     }
    9167             : 
    9168          84 :     const char *name = proj_get_name(datum);
    9169          84 :     if (EQUAL(name, "unknown"))
    9170             :     {
    9171           1 :         proj_destroy(datum);
    9172           1 :         proj_destroy(baseCRS);
    9173           1 :         return false;
    9174             :     }
    9175          83 :     const PJ_TYPE type = PJ_TYPE_GEODETIC_REFERENCE_FRAME;
    9176             :     PJ_OBJ_LIST *list =
    9177          83 :         proj_create_from_name(ctxt, nullptr, name, &type, 1, false, 1, nullptr);
    9178             : 
    9179          83 :     bool knownDatumName = false;
    9180          83 :     if (list)
    9181             :     {
    9182          83 :         if (proj_list_get_count(list) == 1)
    9183             :         {
    9184          70 :             knownDatumName = true;
    9185             :         }
    9186          83 :         proj_list_destroy(list);
    9187             :     }
    9188             : 
    9189          83 :     proj_destroy(datum);
    9190          83 :     if (knownDatumName)
    9191             :     {
    9192          70 :         d->setPjCRS(baseCRS);
    9193          70 :         return true;
    9194             :     }
    9195          13 :     proj_destroy(baseCRS);
    9196          13 :     return false;
    9197             : }
    9198             : 
    9199             : /************************************************************************/
    9200             : /*                             IsCompound()                             */
    9201             : /************************************************************************/
    9202             : 
    9203             : /**
    9204             :  * \brief Check if coordinate system is compound.
    9205             :  *
    9206             :  * This method is the same as the C function OSRIsCompound().
    9207             :  *
    9208             :  * @return TRUE if this is rooted with a COMPD_CS node.
    9209             :  */
    9210             : 
    9211       43334 : int OGRSpatialReference::IsCompound() const
    9212             : 
    9213             : {
    9214       43334 :     TAKE_OPTIONAL_LOCK();
    9215             : 
    9216       43334 :     d->refreshProjObj();
    9217       43334 :     d->demoteFromBoundCRS();
    9218       43334 :     bool isCompound = d->m_pjType == PJ_TYPE_COMPOUND_CRS;
    9219       43334 :     d->undoDemoteFromBoundCRS();
    9220       86668 :     return isCompound;
    9221             : }
    9222             : 
    9223             : /************************************************************************/
    9224             : /*                           OSRIsCompound()                            */
    9225             : /************************************************************************/
    9226             : 
    9227             : /**
    9228             :  * \brief Check if the coordinate system is compound.
    9229             :  *
    9230             :  * This function is the same as OGRSpatialReference::IsCompound().
    9231             :  */
    9232           5 : int OSRIsCompound(OGRSpatialReferenceH hSRS)
    9233             : 
    9234             : {
    9235           5 :     VALIDATE_POINTER1(hSRS, "OSRIsCompound", 0);
    9236             : 
    9237           5 :     return ToPointer(hSRS)->IsCompound();
    9238             : }
    9239             : 
    9240             : /************************************************************************/
    9241             : /*                            IsProjected()                             */
    9242             : /************************************************************************/
    9243             : 
    9244             : /**
    9245             :  * \brief Check if projected coordinate system.
    9246             :  *
    9247             :  * This method is the same as the C function OSRIsProjected().
    9248             :  *
    9249             :  * @return TRUE if this contains a PROJCS node indicating a it is a
    9250             :  * projected coordinate system. Also if it is a CompoundCRS made of a
    9251             :  * ProjectedCRS
    9252             :  */
    9253             : 
    9254       46298 : int OGRSpatialReference::IsProjected() const
    9255             : 
    9256             : {
    9257       46298 :     TAKE_OPTIONAL_LOCK();
    9258             : 
    9259       46298 :     d->refreshProjObj();
    9260       46298 :     d->demoteFromBoundCRS();
    9261       46298 :     bool isProjected = d->m_pjType == PJ_TYPE_PROJECTED_CRS;
    9262       46298 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9263             :     {
    9264             :         auto horizCRS =
    9265         152 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9266         152 :         if (horizCRS)
    9267             :         {
    9268         152 :             auto horizCRSType = proj_get_type(horizCRS);
    9269         152 :             isProjected = horizCRSType == PJ_TYPE_PROJECTED_CRS;
    9270         152 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9271             :             {
    9272           6 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9273           6 :                 if (base)
    9274             :                 {
    9275           6 :                     isProjected = proj_get_type(base) == PJ_TYPE_PROJECTED_CRS;
    9276           6 :                     proj_destroy(base);
    9277             :                 }
    9278             :             }
    9279         152 :             proj_destroy(horizCRS);
    9280             :         }
    9281             :     }
    9282       46298 :     d->undoDemoteFromBoundCRS();
    9283       92596 :     return isProjected;
    9284             : }
    9285             : 
    9286             : /************************************************************************/
    9287             : /*                           OSRIsProjected()                           */
    9288             : /************************************************************************/
    9289             : /**
    9290             :  * \brief Check if projected coordinate system.
    9291             :  *
    9292             :  * This function is the same as OGRSpatialReference::IsProjected().
    9293             :  */
    9294         451 : int OSRIsProjected(OGRSpatialReferenceH hSRS)
    9295             : 
    9296             : {
    9297         451 :     VALIDATE_POINTER1(hSRS, "OSRIsProjected", 0);
    9298             : 
    9299         451 :     return ToPointer(hSRS)->IsProjected();
    9300             : }
    9301             : 
    9302             : /************************************************************************/
    9303             : /*                            IsGeocentric()                            */
    9304             : /************************************************************************/
    9305             : 
    9306             : /**
    9307             :  * \brief Check if geocentric coordinate system.
    9308             :  *
    9309             :  * This method is the same as the C function OSRIsGeocentric().
    9310             :  *
    9311             :  * @return TRUE if this contains a GEOCCS node indicating a it is a
    9312             :  * geocentric coordinate system.
    9313             :  *
    9314             :  */
    9315             : 
    9316       18037 : int OGRSpatialReference::IsGeocentric() const
    9317             : 
    9318             : {
    9319       18037 :     TAKE_OPTIONAL_LOCK();
    9320             : 
    9321       18037 :     d->refreshProjObj();
    9322       18037 :     d->demoteFromBoundCRS();
    9323       18037 :     bool isGeocentric = d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS;
    9324       18037 :     d->undoDemoteFromBoundCRS();
    9325       36074 :     return isGeocentric;
    9326             : }
    9327             : 
    9328             : /************************************************************************/
    9329             : /*                          OSRIsGeocentric()                           */
    9330             : /************************************************************************/
    9331             : /**
    9332             :  * \brief Check if geocentric coordinate system.
    9333             :  *
    9334             :  * This function is the same as OGRSpatialReference::IsGeocentric().
    9335             :  *
    9336             :  */
    9337           2 : int OSRIsGeocentric(OGRSpatialReferenceH hSRS)
    9338             : 
    9339             : {
    9340           2 :     VALIDATE_POINTER1(hSRS, "OSRIsGeocentric", 0);
    9341             : 
    9342           2 :     return ToPointer(hSRS)->IsGeocentric();
    9343             : }
    9344             : 
    9345             : /************************************************************************/
    9346             : /*                              IsEmpty()                               */
    9347             : /************************************************************************/
    9348             : 
    9349             : /**
    9350             :  * \brief Return if the SRS is not set.
    9351             :  */
    9352             : 
    9353      120593 : bool OGRSpatialReference::IsEmpty() const
    9354             : {
    9355      120593 :     TAKE_OPTIONAL_LOCK();
    9356             : 
    9357      120593 :     d->refreshProjObj();
    9358      241186 :     return d->m_pj_crs == nullptr;
    9359             : }
    9360             : 
    9361             : /************************************************************************/
    9362             : /*                            IsGeographic()                            */
    9363             : /************************************************************************/
    9364             : 
    9365             : /**
    9366             :  * \brief Check if geographic coordinate system.
    9367             :  *
    9368             :  * This method is the same as the C function OSRIsGeographic().
    9369             :  *
    9370             :  * @return TRUE if this spatial reference is geographic ... that is the
    9371             :  * root is a GEOGCS node. Also if it is a CompoundCRS made of a
    9372             :  * GeographicCRS
    9373             :  */
    9374             : 
    9375       64161 : int OGRSpatialReference::IsGeographic() const
    9376             : 
    9377             : {
    9378       64161 :     TAKE_OPTIONAL_LOCK();
    9379             : 
    9380       64161 :     d->refreshProjObj();
    9381       64161 :     d->demoteFromBoundCRS();
    9382       89911 :     bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9383       25750 :                   d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9384       64161 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9385             :     {
    9386             :         auto horizCRS =
    9387         329 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9388         329 :         if (horizCRS)
    9389             :         {
    9390         329 :             auto horizCRSType = proj_get_type(horizCRS);
    9391         329 :             isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9392             :                      horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9393         329 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9394             :             {
    9395          13 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9396          13 :                 if (base)
    9397             :                 {
    9398          13 :                     horizCRSType = proj_get_type(base);
    9399          13 :                     isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9400             :                              horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9401          13 :                     proj_destroy(base);
    9402             :                 }
    9403             :             }
    9404         329 :             proj_destroy(horizCRS);
    9405             :         }
    9406             :     }
    9407       64161 :     d->undoDemoteFromBoundCRS();
    9408      128322 :     return isGeog;
    9409             : }
    9410             : 
    9411             : /************************************************************************/
    9412             : /*                          OSRIsGeographic()                           */
    9413             : /************************************************************************/
    9414             : /**
    9415             :  * \brief Check if geographic coordinate system.
    9416             :  *
    9417             :  * This function is the same as OGRSpatialReference::IsGeographic().
    9418             :  */
    9419         284 : int OSRIsGeographic(OGRSpatialReferenceH hSRS)
    9420             : 
    9421             : {
    9422         284 :     VALIDATE_POINTER1(hSRS, "OSRIsGeographic", 0);
    9423             : 
    9424         284 :     return ToPointer(hSRS)->IsGeographic();
    9425             : }
    9426             : 
    9427             : /************************************************************************/
    9428             : /*                        IsDerivedGeographic()                         */
    9429             : /************************************************************************/
    9430             : 
    9431             : /**
    9432             :  * \brief Check if the CRS is a derived geographic coordinate system.
    9433             :  * (for example a rotated long/lat grid)
    9434             :  *
    9435             :  * This method is the same as the C function OSRIsDerivedGeographic().
    9436             :  *
    9437             :  * @since GDAL 3.1.0 and PROJ 6.3.0
    9438             :  */
    9439             : 
    9440       15731 : int OGRSpatialReference::IsDerivedGeographic() const
    9441             : 
    9442             : {
    9443       15731 :     TAKE_OPTIONAL_LOCK();
    9444             : 
    9445       15731 :     d->refreshProjObj();
    9446       15731 :     d->demoteFromBoundCRS();
    9447       25762 :     const bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9448       10031 :                         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9449             :     const bool isDerivedGeographic =
    9450       15731 :         isGeog && proj_is_derived_crs(d->getPROJContext(), d->m_pj_crs);
    9451       15731 :     d->undoDemoteFromBoundCRS();
    9452       31462 :     return isDerivedGeographic ? TRUE : FALSE;
    9453             : }
    9454             : 
    9455             : /************************************************************************/
    9456             : /*                       OSRIsDerivedGeographic()                       */
    9457             : /************************************************************************/
    9458             : /**
    9459             :  * \brief Check if the CRS is a derived geographic coordinate system.
    9460             :  * (for example a rotated long/lat grid)
    9461             :  *
    9462             :  * This function is the same as OGRSpatialReference::IsDerivedGeographic().
    9463             :  */
    9464           1 : int OSRIsDerivedGeographic(OGRSpatialReferenceH hSRS)
    9465             : 
    9466             : {
    9467           1 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedGeographic", 0);
    9468             : 
    9469           1 :     return ToPointer(hSRS)->IsDerivedGeographic();
    9470             : }
    9471             : 
    9472             : /************************************************************************/
    9473             : /*                         IsDerivedProjected()                         */
    9474             : /************************************************************************/
    9475             : 
    9476             : /**
    9477             :  * \brief Check if the CRS is a derived projected coordinate system.
    9478             :  *
    9479             :  * This method is the same as the C function OSRIsDerivedGeographic().
    9480             :  *
    9481             :  * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
    9482             :  */
    9483             : 
    9484           0 : int OGRSpatialReference::IsDerivedProjected() const
    9485             : 
    9486             : {
    9487             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
    9488             :     TAKE_OPTIONAL_LOCK();
    9489             :     d->refreshProjObj();
    9490             :     d->demoteFromBoundCRS();
    9491             :     const bool isDerivedProjected =
    9492             :         d->m_pjType == PJ_TYPE_DERIVED_PROJECTED_CRS;
    9493             :     d->undoDemoteFromBoundCRS();
    9494             :     return isDerivedProjected ? TRUE : FALSE;
    9495             : #else
    9496           0 :     return FALSE;
    9497             : #endif
    9498             : }
    9499             : 
    9500             : /************************************************************************/
    9501             : /*                       OSRIsDerivedProjected()                        */
    9502             : /************************************************************************/
    9503             : /**
    9504             :  * \brief Check if the CRS is a derived projected coordinate system.
    9505             :  *
    9506             :  * This function is the same as OGRSpatialReference::IsDerivedProjected().
    9507             :  *
    9508             :  * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
    9509             :  */
    9510           0 : int OSRIsDerivedProjected(OGRSpatialReferenceH hSRS)
    9511             : 
    9512             : {
    9513           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedProjected", 0);
    9514             : 
    9515           0 :     return ToPointer(hSRS)->IsDerivedProjected();
    9516             : }
    9517             : 
    9518             : /************************************************************************/
    9519             : /*                              IsLocal()                               */
    9520             : /************************************************************************/
    9521             : 
    9522             : /**
    9523             :  * \brief Check if local coordinate system.
    9524             :  *
    9525             :  * This method is the same as the C function OSRIsLocal().
    9526             :  *
    9527             :  * @return TRUE if this spatial reference is local ... that is the
    9528             :  * root is a LOCAL_CS node.
    9529             :  */
    9530             : 
    9531        8317 : int OGRSpatialReference::IsLocal() const
    9532             : 
    9533             : {
    9534        8317 :     TAKE_OPTIONAL_LOCK();
    9535        8317 :     d->refreshProjObj();
    9536       16634 :     return d->m_pjType == PJ_TYPE_ENGINEERING_CRS;
    9537             : }
    9538             : 
    9539             : /************************************************************************/
    9540             : /*                             OSRIsLocal()                             */
    9541             : /************************************************************************/
    9542             : /**
    9543             :  * \brief Check if local coordinate system.
    9544             :  *
    9545             :  * This function is the same as OGRSpatialReference::IsLocal().
    9546             :  */
    9547           8 : int OSRIsLocal(OGRSpatialReferenceH hSRS)
    9548             : 
    9549             : {
    9550           8 :     VALIDATE_POINTER1(hSRS, "OSRIsLocal", 0);
    9551             : 
    9552           8 :     return ToPointer(hSRS)->IsLocal();
    9553             : }
    9554             : 
    9555             : /************************************************************************/
    9556             : /*                             IsVertical()                             */
    9557             : /************************************************************************/
    9558             : 
    9559             : /**
    9560             :  * \brief Check if vertical coordinate system.
    9561             :  *
    9562             :  * This method is the same as the C function OSRIsVertical().
    9563             :  *
    9564             :  * @return TRUE if this contains a VERT_CS node indicating a it is a
    9565             :  * vertical coordinate system. Also if it is a CompoundCRS made of a
    9566             :  * VerticalCRS
    9567             :  *
    9568             :  */
    9569             : 
    9570        9336 : int OGRSpatialReference::IsVertical() const
    9571             : 
    9572             : {
    9573        9336 :     TAKE_OPTIONAL_LOCK();
    9574        9336 :     d->refreshProjObj();
    9575        9336 :     d->demoteFromBoundCRS();
    9576        9336 :     bool isVertical = d->m_pjType == PJ_TYPE_VERTICAL_CRS;
    9577        9336 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9578             :     {
    9579             :         auto vertCRS =
    9580          34 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    9581          34 :         if (vertCRS)
    9582             :         {
    9583          34 :             const auto vertCRSType = proj_get_type(vertCRS);
    9584          34 :             isVertical = vertCRSType == PJ_TYPE_VERTICAL_CRS;
    9585          34 :             if (vertCRSType == PJ_TYPE_BOUND_CRS)
    9586             :             {
    9587           0 :                 auto base = proj_get_source_crs(d->getPROJContext(), vertCRS);
    9588           0 :                 if (base)
    9589             :                 {
    9590           0 :                     isVertical = proj_get_type(base) == PJ_TYPE_VERTICAL_CRS;
    9591           0 :                     proj_destroy(base);
    9592             :                 }
    9593             :             }
    9594          34 :             proj_destroy(vertCRS);
    9595             :         }
    9596             :     }
    9597        9336 :     d->undoDemoteFromBoundCRS();
    9598       18672 :     return isVertical;
    9599             : }
    9600             : 
    9601             : /************************************************************************/
    9602             : /*                           OSRIsVertical()                            */
    9603             : /************************************************************************/
    9604             : /**
    9605             :  * \brief Check if vertical coordinate system.
    9606             :  *
    9607             :  * This function is the same as OGRSpatialReference::IsVertical().
    9608             :  *
    9609             :  */
    9610           0 : int OSRIsVertical(OGRSpatialReferenceH hSRS)
    9611             : 
    9612             : {
    9613           0 :     VALIDATE_POINTER1(hSRS, "OSRIsVertical", 0);
    9614             : 
    9615           0 :     return ToPointer(hSRS)->IsVertical();
    9616             : }
    9617             : 
    9618             : /************************************************************************/
    9619             : /*                             IsDynamic()                              */
    9620             : /************************************************************************/
    9621             : 
    9622             : /**
    9623             :  * \brief Check if a CRS is a dynamic CRS.
    9624             :  *
    9625             :  * A dynamic CRS relies on a dynamic datum, that is a datum that is not
    9626             :  * plate-fixed.
    9627             :  *
    9628             :  * This method is the same as the C function OSRIsDynamic().
    9629             :  *
    9630             :  * @return true if the CRS is dynamic
    9631             :  *
    9632             :  * @since OGR 3.4.0
    9633             :  *
    9634             :  * @see HasPointMotionOperation()
    9635             :  */
    9636             : 
    9637       17208 : bool OGRSpatialReference::IsDynamic() const
    9638             : 
    9639             : {
    9640       17208 :     TAKE_OPTIONAL_LOCK();
    9641       17208 :     bool isDynamic = false;
    9642       17208 :     d->refreshProjObj();
    9643       17208 :     d->demoteFromBoundCRS();
    9644       17208 :     auto ctxt = d->getPROJContext();
    9645       17208 :     PJ *horiz = nullptr;
    9646       17208 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9647             :     {
    9648          99 :         horiz = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    9649             :     }
    9650       17109 :     else if (d->m_pj_crs)
    9651             :     {
    9652       16915 :         horiz = proj_clone(ctxt, d->m_pj_crs);
    9653             :     }
    9654       17208 :     if (horiz && proj_get_type(horiz) == PJ_TYPE_BOUND_CRS)
    9655             :     {
    9656           6 :         auto baseCRS = proj_get_source_crs(ctxt, horiz);
    9657           6 :         if (baseCRS)
    9658             :         {
    9659           6 :             proj_destroy(horiz);
    9660           6 :             horiz = baseCRS;
    9661             :         }
    9662             :     }
    9663       17208 :     auto datum = horiz ? proj_crs_get_datum(ctxt, horiz) : nullptr;
    9664       17208 :     if (datum)
    9665             :     {
    9666       16979 :         const auto type = proj_get_type(datum);
    9667       16979 :         isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9668             :                     type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9669       16979 :         if (!isDynamic)
    9670             :         {
    9671       16979 :             const char *auth_name = proj_get_id_auth_name(datum, 0);
    9672       16979 :             const char *code = proj_get_id_code(datum, 0);
    9673       16979 :             if (auth_name && code && EQUAL(auth_name, "EPSG") &&
    9674       16447 :                 EQUAL(code, "6326"))
    9675             :             {
    9676       10716 :                 isDynamic = true;
    9677             :             }
    9678             :         }
    9679       16979 :         proj_destroy(datum);
    9680             :     }
    9681             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9682             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9683             :     else
    9684             :     {
    9685             :         auto ensemble =
    9686             :             horiz ? proj_crs_get_datum_ensemble(ctxt, horiz) : nullptr;
    9687             :         if (ensemble)
    9688             :         {
    9689             :             auto member = proj_datum_ensemble_get_member(ctxt, ensemble, 0);
    9690             :             if (member)
    9691             :             {
    9692             :                 const auto type = proj_get_type(member);
    9693             :                 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9694             :                             type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9695             :                 proj_destroy(member);
    9696             :             }
    9697             :             proj_destroy(ensemble);
    9698             :         }
    9699             :     }
    9700             : #endif
    9701       17208 :     proj_destroy(horiz);
    9702       17208 :     d->undoDemoteFromBoundCRS();
    9703       34416 :     return isDynamic;
    9704             : }
    9705             : 
    9706             : /************************************************************************/
    9707             : /*                            OSRIsDynamic()                            */
    9708             : /************************************************************************/
    9709             : /**
    9710             :  * \brief Check if a CRS is a dynamic CRS.
    9711             :  *
    9712             :  * A dynamic CRS relies on a dynamic datum, that is a datum that is not
    9713             :  * plate-fixed.
    9714             :  *
    9715             :  * This function is the same as OGRSpatialReference::IsDynamic().
    9716             :  *
    9717             :  * @since OGR 3.4.0
    9718             :  */
    9719           0 : int OSRIsDynamic(OGRSpatialReferenceH hSRS)
    9720             : 
    9721             : {
    9722           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDynamic", 0);
    9723             : 
    9724           0 :     return ToPointer(hSRS)->IsDynamic();
    9725             : }
    9726             : 
    9727             : /************************************************************************/
    9728             : /*                      HasPointMotionOperation()                       */
    9729             : /************************************************************************/
    9730             : 
    9731             : /**
    9732             :  * \brief Check if a CRS has at least an associated point motion operation.
    9733             :  *
    9734             :  * Some CRS are not formally declared as dynamic, but may behave as such
    9735             :  * in practice due to the presence of point motion operation, to perform
    9736             :  * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
    9737             :  *
    9738             :  * @return true if the CRS has at least an associated point motion operation.
    9739             :  *
    9740             :  * @since OGR 3.8.0 and PROJ 9.4.0
    9741             :  *
    9742             :  * @see IsDynamic()
    9743             :  */
    9744             : 
    9745           5 : bool OGRSpatialReference::HasPointMotionOperation() const
    9746             : 
    9747             : {
    9748             : #if PROJ_VERSION_MAJOR > 9 ||                                                  \
    9749             :     (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 4)
    9750             :     TAKE_OPTIONAL_LOCK();
    9751             :     d->refreshProjObj();
    9752             :     d->demoteFromBoundCRS();
    9753             :     auto ctxt = d->getPROJContext();
    9754             :     auto res =
    9755             :         CPL_TO_BOOL(proj_crs_has_point_motion_operation(ctxt, d->m_pj_crs));
    9756             :     d->undoDemoteFromBoundCRS();
    9757             :     return res;
    9758             : #else
    9759           5 :     return false;
    9760             : #endif
    9761             : }
    9762             : 
    9763             : /************************************************************************/
    9764             : /*                     OSRHasPointMotionOperation()                     */
    9765             : /************************************************************************/
    9766             : 
    9767             : /**
    9768             :  * \brief Check if a CRS has at least an associated point motion operation.
    9769             :  *
    9770             :  * Some CRS are not formally declared as dynamic, but may behave as such
    9771             :  * in practice due to the presence of point motion operation, to perform
    9772             :  * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
    9773             :  *
    9774             :  * This function is the same as OGRSpatialReference::HasPointMotionOperation().
    9775             :  *
    9776             :  * @since OGR 3.8.0 and PROJ 9.4.0
    9777             :  */
    9778           0 : int OSRHasPointMotionOperation(OGRSpatialReferenceH hSRS)
    9779             : 
    9780             : {
    9781           0 :     VALIDATE_POINTER1(hSRS, "OSRHasPointMotionOperation", 0);
    9782             : 
    9783           0 :     return ToPointer(hSRS)->HasPointMotionOperation();
    9784             : }
    9785             : 
    9786             : /************************************************************************/
    9787             : /*                            CloneGeogCS()                             */
    9788             : /************************************************************************/
    9789             : 
    9790             : /**
    9791             :  * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
    9792             :  * object.
    9793             :  *
    9794             :  * @return a new SRS, which becomes the responsibility of the caller.
    9795             :  */
    9796        4648 : OGRSpatialReference *OGRSpatialReference::CloneGeogCS() const
    9797             : 
    9798             : {
    9799        9296 :     TAKE_OPTIONAL_LOCK();
    9800        4648 :     d->refreshProjObj();
    9801        4648 :     if (d->m_pj_crs)
    9802             :     {
    9803        4648 :         if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    9804           0 :             return nullptr;
    9805             : 
    9806             :         auto geodCRS =
    9807        4648 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9808        4648 :         if (geodCRS)
    9809             :         {
    9810        4648 :             OGRSpatialReference *poNewSRS = new OGRSpatialReference();
    9811        4648 :             if (d->m_pjType == PJ_TYPE_BOUND_CRS)
    9812             :             {
    9813             :                 PJ *hub_crs =
    9814          13 :                     proj_get_target_crs(d->getPROJContext(), d->m_pj_crs);
    9815          13 :                 PJ *co = proj_crs_get_coordoperation(d->getPROJContext(),
    9816          13 :                                                      d->m_pj_crs);
    9817          13 :                 auto temp = proj_crs_create_bound_crs(d->getPROJContext(),
    9818             :                                                       geodCRS, hub_crs, co);
    9819          13 :                 proj_destroy(geodCRS);
    9820          13 :                 geodCRS = temp;
    9821          13 :                 proj_destroy(hub_crs);
    9822          13 :                 proj_destroy(co);
    9823             :             }
    9824             : 
    9825             :             /* --------------------------------------------------------------------
    9826             :              */
    9827             :             /*      We have to reconstruct the GEOGCS node for geocentric */
    9828             :             /*      coordinate systems. */
    9829             :             /* --------------------------------------------------------------------
    9830             :              */
    9831        4648 :             if (proj_get_type(geodCRS) == PJ_TYPE_GEOCENTRIC_CRS)
    9832             :             {
    9833           0 :                 auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    9834             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9835             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9836             :                 if (datum == nullptr)
    9837             :                 {
    9838             :                     datum = proj_crs_get_datum_ensemble(d->getPROJContext(),
    9839             :                                                         geodCRS);
    9840             :                 }
    9841             : #endif
    9842           0 :                 if (datum)
    9843             :                 {
    9844           0 :                     auto cs = proj_create_ellipsoidal_2D_cs(
    9845             :                         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE,
    9846             :                         nullptr, 0);
    9847           0 :                     auto temp = proj_create_geographic_crs_from_datum(
    9848             :                         d->getPROJContext(), "unnamed", datum, cs);
    9849           0 :                     proj_destroy(datum);
    9850           0 :                     proj_destroy(cs);
    9851           0 :                     proj_destroy(geodCRS);
    9852           0 :                     geodCRS = temp;
    9853             :                 }
    9854             :             }
    9855             : 
    9856        4648 :             poNewSRS->d->setPjCRS(geodCRS);
    9857        4648 :             if (d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
    9858        3048 :                 poNewSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    9859        4648 :             return poNewSRS;
    9860             :         }
    9861             :     }
    9862           0 :     return nullptr;
    9863             : }
    9864             : 
    9865             : /************************************************************************/
    9866             : /*                           OSRCloneGeogCS()                           */
    9867             : /************************************************************************/
    9868             : /**
    9869             :  * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
    9870             :  * object.
    9871             :  *
    9872             :  * This function is the same as OGRSpatialReference::CloneGeogCS().
    9873             :  */
    9874         127 : OGRSpatialReferenceH CPL_STDCALL OSRCloneGeogCS(OGRSpatialReferenceH hSource)
    9875             : 
    9876             : {
    9877         127 :     VALIDATE_POINTER1(hSource, "OSRCloneGeogCS", nullptr);
    9878             : 
    9879         127 :     return ToHandle(ToPointer(hSource)->CloneGeogCS());
    9880             : }
    9881             : 
    9882             : /************************************************************************/
    9883             : /*                            IsSameGeogCS()                            */
    9884             : /************************************************************************/
    9885             : 
    9886             : /**
    9887             :  * \brief Do the GeogCS'es match?
    9888             :  *
    9889             :  * This method is the same as the C function OSRIsSameGeogCS().
    9890             :  *
    9891             :  * @param poOther the SRS being compared against.
    9892             :  *
    9893             :  * @return TRUE if they are the same or FALSE otherwise.
    9894             :  */
    9895             : 
    9896        8848 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther) const
    9897             : 
    9898             : {
    9899        8848 :     return IsSameGeogCS(poOther, nullptr);
    9900             : }
    9901             : 
    9902             : /**
    9903             :  * \brief Do the GeogCS'es match?
    9904             :  *
    9905             :  * This method is the same as the C function OSRIsSameGeogCS().
    9906             :  *
    9907             :  * @param poOther the SRS being compared against.
    9908             :  * @param papszOptions options. ignored
    9909             :  *
    9910             :  * @return TRUE if they are the same or FALSE otherwise.
    9911             :  */
    9912             : 
    9913        8848 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther,
    9914             :                                       const char *const *papszOptions) const
    9915             : 
    9916             : {
    9917       17696 :     TAKE_OPTIONAL_LOCK();
    9918             : 
    9919        8848 :     CPL_IGNORE_RET_VAL(papszOptions);
    9920             : 
    9921        8848 :     d->refreshProjObj();
    9922        8848 :     poOther->d->refreshProjObj();
    9923        8848 :     if (!d->m_pj_crs || !poOther->d->m_pj_crs)
    9924           0 :         return FALSE;
    9925        8848 :     if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9926        8848 :         d->m_pjType == PJ_TYPE_VERTICAL_CRS ||
    9927       26544 :         poOther->d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9928        8848 :         poOther->d->m_pjType == PJ_TYPE_VERTICAL_CRS)
    9929             :     {
    9930           0 :         return FALSE;
    9931             :     }
    9932             : 
    9933        8848 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9934             :     auto otherGeodCRS =
    9935        8848 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poOther->d->m_pj_crs);
    9936        8848 :     if (!geodCRS || !otherGeodCRS)
    9937             :     {
    9938           0 :         proj_destroy(geodCRS);
    9939           0 :         proj_destroy(otherGeodCRS);
    9940           0 :         return FALSE;
    9941             :     }
    9942             : 
    9943        8848 :     int ret = proj_is_equivalent_to(
    9944             :         geodCRS, otherGeodCRS, PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS);
    9945             : 
    9946        8848 :     proj_destroy(geodCRS);
    9947        8848 :     proj_destroy(otherGeodCRS);
    9948        8848 :     return ret;
    9949             : }
    9950             : 
    9951             : /************************************************************************/
    9952             : /*                          OSRIsSameGeogCS()                           */
    9953             : /************************************************************************/
    9954             : 
    9955             : /**
    9956             :  * \brief Do the GeogCS'es match?
    9957             :  *
    9958             :  * This function is the same as OGRSpatialReference::IsSameGeogCS().
    9959             :  */
    9960           0 : int OSRIsSameGeogCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9961             : 
    9962             : {
    9963           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameGeogCS", 0);
    9964           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameGeogCS", 0);
    9965             : 
    9966           0 :     return ToPointer(hSRS1)->IsSameGeogCS(ToPointer(hSRS2));
    9967             : }
    9968             : 
    9969             : /************************************************************************/
    9970             : /*                            IsSameVertCS()                            */
    9971             : /************************************************************************/
    9972             : 
    9973             : /**
    9974             :  * \brief Do the VertCS'es match?
    9975             :  *
    9976             :  * This method is the same as the C function OSRIsSameVertCS().
    9977             :  *
    9978             :  * @param poOther the SRS being compared against.
    9979             :  *
    9980             :  * @return TRUE if they are the same or FALSE otherwise.
    9981             :  */
    9982             : 
    9983           0 : int OGRSpatialReference::IsSameVertCS(const OGRSpatialReference *poOther) const
    9984             : 
    9985             : {
    9986           0 :     TAKE_OPTIONAL_LOCK();
    9987             : 
    9988             :     /* -------------------------------------------------------------------- */
    9989             :     /*      Does the datum name match?                                      */
    9990             :     /* -------------------------------------------------------------------- */
    9991           0 :     const char *pszThisValue = this->GetAttrValue("VERT_DATUM");
    9992           0 :     const char *pszOtherValue = poOther->GetAttrValue("VERT_DATUM");
    9993             : 
    9994           0 :     if (pszThisValue == nullptr || pszOtherValue == nullptr ||
    9995           0 :         !EQUAL(pszThisValue, pszOtherValue))
    9996           0 :         return FALSE;
    9997             : 
    9998             :     /* -------------------------------------------------------------------- */
    9999             :     /*      Do the units match?                                             */
   10000             :     /* -------------------------------------------------------------------- */
   10001           0 :     pszThisValue = this->GetAttrValue("VERT_CS|UNIT", 1);
   10002           0 :     if (pszThisValue == nullptr)
   10003           0 :         pszThisValue = "1.0";
   10004             : 
   10005           0 :     pszOtherValue = poOther->GetAttrValue("VERT_CS|UNIT", 1);
   10006           0 :     if (pszOtherValue == nullptr)
   10007           0 :         pszOtherValue = "1.0";
   10008             : 
   10009           0 :     if (std::abs(CPLAtof(pszOtherValue) - CPLAtof(pszThisValue)) > 0.00000001)
   10010           0 :         return FALSE;
   10011             : 
   10012           0 :     return TRUE;
   10013             : }
   10014             : 
   10015             : /************************************************************************/
   10016             : /*                          OSRIsSameVertCS()                           */
   10017             : /************************************************************************/
   10018             : 
   10019             : /**
   10020             :  * \brief Do the VertCS'es match?
   10021             :  *
   10022             :  * This function is the same as OGRSpatialReference::IsSameVertCS().
   10023             :  */
   10024           0 : int OSRIsSameVertCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
   10025             : 
   10026             : {
   10027           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameVertCS", 0);
   10028           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameVertCS", 0);
   10029             : 
   10030           0 :     return ToPointer(hSRS1)->IsSameVertCS(ToPointer(hSRS2));
   10031             : }
   10032             : 
   10033             : /************************************************************************/
   10034             : /*                               IsSame()                               */
   10035             : /************************************************************************/
   10036             : 
   10037             : /**
   10038             :  * \brief Do these two spatial references describe the same system ?
   10039             :  *
   10040             :  * @param poOtherSRS the SRS being compared to.
   10041             :  *
   10042             :  * @return TRUE if equivalent or FALSE otherwise.
   10043             :  */
   10044             : 
   10045        8929 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS) const
   10046             : 
   10047             : {
   10048        8929 :     return IsSame(poOtherSRS, nullptr);
   10049             : }
   10050             : 
   10051             : /**
   10052             :  * \brief Do these two spatial references describe the same system ?
   10053             :  *
   10054             :  * This also takes into account the data axis to CRS axis mapping by default
   10055             :  *
   10056             :  * @param poOtherSRS the SRS being compared to.
   10057             :  * @param papszOptions options. NULL or NULL terminated list of options.
   10058             :  * Currently supported options are:
   10059             :  * <ul>
   10060             :  * <li>IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES/NO. Defaults to NO</li>
   10061             :  * <li>CRITERION=STRICT/EQUIVALENT/EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.
   10062             :  *     Defaults to EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.</li>
   10063             :  * <li>IGNORE_COORDINATE_EPOCH=YES/NO. Defaults to NO</li>
   10064             :  * </ul>
   10065             :  *
   10066             :  * @return TRUE if equivalent or FALSE otherwise.
   10067             :  */
   10068             : 
   10069       22850 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS,
   10070             :                                 const char *const *papszOptions) const
   10071             : 
   10072             : {
   10073       45700 :     TAKE_OPTIONAL_LOCK();
   10074             : 
   10075       22850 :     d->refreshProjObj();
   10076       22850 :     poOtherSRS->d->refreshProjObj();
   10077       22850 :     if (!d->m_pj_crs || !poOtherSRS->d->m_pj_crs)
   10078          50 :         return d->m_pj_crs == poOtherSRS->d->m_pj_crs;
   10079       22800 :     if (!CPLTestBool(CSLFetchNameValueDef(
   10080             :             papszOptions, "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING", "NO")))
   10081             :     {
   10082       22042 :         if (d->m_axisMapping != poOtherSRS->d->m_axisMapping)
   10083        3715 :             return false;
   10084             :     }
   10085             : 
   10086       19085 :     if (!CPLTestBool(CSLFetchNameValueDef(papszOptions,
   10087             :                                           "IGNORE_COORDINATE_EPOCH", "NO")))
   10088             :     {
   10089       18593 :         if (d->m_coordinateEpoch != poOtherSRS->d->m_coordinateEpoch)
   10090          27 :             return false;
   10091             :     }
   10092             : 
   10093       19058 :     bool reboundSelf = false;
   10094       19058 :     bool reboundOther = false;
   10095       19110 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS &&
   10096          52 :         poOtherSRS->d->m_pjType != PJ_TYPE_BOUND_CRS)
   10097             :     {
   10098          14 :         d->demoteFromBoundCRS();
   10099          14 :         reboundSelf = true;
   10100             :     }
   10101       38050 :     else if (d->m_pjType != PJ_TYPE_BOUND_CRS &&
   10102       19006 :              poOtherSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
   10103             :     {
   10104          28 :         poOtherSRS->d->demoteFromBoundCRS();
   10105          28 :         reboundOther = true;
   10106             :     }
   10107             : 
   10108       19058 :     PJ_COMPARISON_CRITERION criterion =
   10109             :         PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS;
   10110       19058 :     const char *pszCriterion = CSLFetchNameValueDef(
   10111             :         papszOptions, "CRITERION", "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS");
   10112       19058 :     if (EQUAL(pszCriterion, "STRICT"))
   10113           0 :         criterion = PJ_COMP_STRICT;
   10114       19058 :     else if (EQUAL(pszCriterion, "EQUIVALENT"))
   10115       10376 :         criterion = PJ_COMP_EQUIVALENT;
   10116        8682 :     else if (!EQUAL(pszCriterion, "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS"))
   10117             :     {
   10118           0 :         CPLError(CE_Warning, CPLE_NotSupported,
   10119             :                  "Unsupported value for CRITERION: %s", pszCriterion);
   10120             :     }
   10121             :     int ret =
   10122       19058 :         proj_is_equivalent_to(d->m_pj_crs, poOtherSRS->d->m_pj_crs, criterion);
   10123       19058 :     if (reboundSelf)
   10124          14 :         d->undoDemoteFromBoundCRS();
   10125       19058 :     if (reboundOther)
   10126          28 :         poOtherSRS->d->undoDemoteFromBoundCRS();
   10127             : 
   10128       19058 :     return ret;
   10129             : }
   10130             : 
   10131             : /************************************************************************/
   10132             : /*                             OSRIsSame()                              */
   10133             : /************************************************************************/
   10134             : 
   10135             : /**
   10136             :  * \brief Do these two spatial references describe the same system ?
   10137             :  *
   10138             :  * This function is the same as OGRSpatialReference::IsSame().
   10139             :  */
   10140          41 : int OSRIsSame(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
   10141             : 
   10142             : {
   10143          41 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
   10144          41 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
   10145             : 
   10146          41 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2));
   10147             : }
   10148             : 
   10149             : /************************************************************************/
   10150             : /*                            OSRIsSameEx()                             */
   10151             : /************************************************************************/
   10152             : 
   10153             : /**
   10154             :  * \brief Do these two spatial references describe the same system ?
   10155             :  *
   10156             :  * This function is the same as OGRSpatialReference::IsSame().
   10157             :  */
   10158         672 : int OSRIsSameEx(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2,
   10159             :                 const char *const *papszOptions)
   10160             : {
   10161         672 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
   10162         672 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
   10163             : 
   10164         672 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2), papszOptions);
   10165             : }
   10166             : 
   10167             : /************************************************************************/
   10168             : /*                      convertToOtherProjection()                      */
   10169             : /************************************************************************/
   10170             : 
   10171             : /**
   10172             :  * \brief Convert to another equivalent projection
   10173             :  *
   10174             :  * Currently implemented:
   10175             :  * <ul>
   10176             :  * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
   10177             :  * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
   10178             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
   10179             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
   10180             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
   10181             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
   10182             :  * </ul>
   10183             :  *
   10184             :  * @param pszTargetProjection target projection.
   10185             :  * @param papszOptions lists of options. None supported currently.
   10186             :  * @return a new SRS, or NULL in case of error.
   10187             :  *
   10188             :  */
   10189          91 : OGRSpatialReference *OGRSpatialReference::convertToOtherProjection(
   10190             :     const char *pszTargetProjection,
   10191             :     CPL_UNUSED const char *const *papszOptions) const
   10192             : {
   10193         182 :     TAKE_OPTIONAL_LOCK();
   10194             : 
   10195          91 :     if (pszTargetProjection == nullptr)
   10196           1 :         return nullptr;
   10197             :     int new_code;
   10198          90 :     if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_1SP))
   10199             :     {
   10200           6 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A;
   10201             :     }
   10202          84 :     else if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_2SP))
   10203             :     {
   10204           7 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B;
   10205             :     }
   10206          77 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
   10207             :     {
   10208          65 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP;
   10209             :     }
   10210          12 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
   10211             :     {
   10212          11 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP;
   10213             :     }
   10214             :     else
   10215             :     {
   10216           1 :         return nullptr;
   10217             :     }
   10218             : 
   10219          89 :     d->refreshProjObj();
   10220          89 :     d->demoteFromBoundCRS();
   10221          89 :     OGRSpatialReference *poNewSRS = nullptr;
   10222          89 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
   10223             :     {
   10224             :         auto conv =
   10225          88 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10226          88 :         auto new_conv = proj_convert_conversion_to_other_method(
   10227             :             d->getPROJContext(), conv, new_code, nullptr);
   10228          88 :         proj_destroy(conv);
   10229          88 :         if (new_conv)
   10230             :         {
   10231             :             auto geodCRS =
   10232          74 :                 proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10233          74 :             auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   10234          74 :                                                      d->m_pj_crs);
   10235          74 :             if (geodCRS && cs)
   10236             :             {
   10237          74 :                 auto new_proj_crs = proj_create_projected_crs(
   10238          74 :                     d->getPROJContext(), proj_get_name(d->m_pj_crs), geodCRS,
   10239             :                     new_conv, cs);
   10240          74 :                 proj_destroy(new_conv);
   10241          74 :                 if (new_proj_crs)
   10242             :                 {
   10243          74 :                     poNewSRS = new OGRSpatialReference();
   10244             : 
   10245          74 :                     if (d->m_pj_bound_crs_target && d->m_pj_bound_crs_co)
   10246             :                     {
   10247           9 :                         auto boundCRS = proj_crs_create_bound_crs(
   10248             :                             d->getPROJContext(), new_proj_crs,
   10249           9 :                             d->m_pj_bound_crs_target, d->m_pj_bound_crs_co);
   10250           9 :                         if (boundCRS)
   10251             :                         {
   10252           9 :                             proj_destroy(new_proj_crs);
   10253           9 :                             new_proj_crs = boundCRS;
   10254             :                         }
   10255             :                     }
   10256             : 
   10257          74 :                     poNewSRS->d->setPjCRS(new_proj_crs);
   10258             :                 }
   10259             :             }
   10260          74 :             proj_destroy(geodCRS);
   10261          74 :             proj_destroy(cs);
   10262             :         }
   10263             :     }
   10264          89 :     d->undoDemoteFromBoundCRS();
   10265          89 :     return poNewSRS;
   10266             : }
   10267             : 
   10268             : /************************************************************************/
   10269             : /*                    OSRConvertToOtherProjection()                     */
   10270             : /************************************************************************/
   10271             : 
   10272             : /**
   10273             :  * \brief Convert to another equivalent projection
   10274             :  *
   10275             :  * Currently implemented:
   10276             :  * <ul>
   10277             :  * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
   10278             :  * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
   10279             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
   10280             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
   10281             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
   10282             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
   10283             :  * </ul>
   10284             :  *
   10285             :  * @param hSRS source SRS
   10286             :  * @param pszTargetProjection target projection.
   10287             :  * @param papszOptions lists of options. None supported currently.
   10288             :  * @return a new SRS, or NULL in case of error.
   10289             :  *
   10290             :  */
   10291             : OGRSpatialReferenceH
   10292          28 : OSRConvertToOtherProjection(OGRSpatialReferenceH hSRS,
   10293             :                             const char *pszTargetProjection,
   10294             :                             const char *const *papszOptions)
   10295             : {
   10296          28 :     VALIDATE_POINTER1(hSRS, "OSRConvertToOtherProjection", nullptr);
   10297          28 :     return ToHandle(ToPointer(hSRS)->convertToOtherProjection(
   10298          28 :         pszTargetProjection, papszOptions));
   10299             : }
   10300             : 
   10301             : /************************************************************************/
   10302             : /*                           OSRFindMatches()                           */
   10303             : /************************************************************************/
   10304             : 
   10305             : /**
   10306             :  * \brief Try to identify a match between the passed SRS and a related SRS
   10307             :  * in a catalog.
   10308             :  *
   10309             :  * Matching may be partial, or may fail.
   10310             :  * Returned entries will be sorted by decreasing match confidence (first
   10311             :  * entry has the highest match confidence).
   10312             :  *
   10313             :  * The exact way matching is done may change in future versions. Starting with
   10314             :  * GDAL 3.0, it relies on PROJ' proj_identify() function.
   10315             :  *
   10316             :  * This function is the same as OGRSpatialReference::FindMatches().
   10317             :  *
   10318             :  * @param hSRS SRS to match
   10319             :  * @param papszOptions NULL terminated list of options or NULL
   10320             :  * @param pnEntries Output parameter. Number of values in the returned array.
   10321             :  * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
   10322             :  * will be allocated to an array of *pnEntries whose values between 0 and 100
   10323             :  * indicate the confidence in the match. 100 is the highest confidence level.
   10324             :  * The array must be freed with CPLFree().
   10325             :  *
   10326             :  * @return an array of SRS that match the passed SRS, or NULL. Must be freed
   10327             :  * with OSRFreeSRSArray()
   10328             :  *
   10329             :  */
   10330           8 : OGRSpatialReferenceH *OSRFindMatches(OGRSpatialReferenceH hSRS,
   10331             :                                      CSLConstList papszOptions, int *pnEntries,
   10332             :                                      int **ppanMatchConfidence)
   10333             : {
   10334           8 :     if (pnEntries)
   10335           8 :         *pnEntries = 0;
   10336           8 :     if (ppanMatchConfidence)
   10337           8 :         *ppanMatchConfidence = nullptr;
   10338           8 :     VALIDATE_POINTER1(hSRS, "OSRFindMatches", nullptr);
   10339             : 
   10340           8 :     OGRSpatialReference *poSRS = ToPointer(hSRS);
   10341           8 :     return poSRS->FindMatches(papszOptions, pnEntries, ppanMatchConfidence);
   10342             : }
   10343             : 
   10344             : /************************************************************************/
   10345             : /*                          OSRFreeSRSArray()                           */
   10346             : /************************************************************************/
   10347             : 
   10348             : /**
   10349             :  * \brief Free return of OSRIdentifyMatches()
   10350             :  *
   10351             :  * @param pahSRS array of SRS (must be NULL terminated)
   10352             :  */
   10353         197 : void OSRFreeSRSArray(OGRSpatialReferenceH *pahSRS)
   10354             : {
   10355         197 :     if (pahSRS != nullptr)
   10356             :     {
   10357        1743 :         for (int i = 0; pahSRS[i] != nullptr; ++i)
   10358             :         {
   10359        1564 :             OSRRelease(pahSRS[i]);
   10360             :         }
   10361         179 :         CPLFree(pahSRS);
   10362             :     }
   10363         197 : }
   10364             : 
   10365             : /************************************************************************/
   10366             : /*                           FindBestMatch()                            */
   10367             : /************************************************************************/
   10368             : 
   10369             : /**
   10370             :  * \brief Try to identify the best match between the passed SRS and a related
   10371             :  * SRS in a catalog.
   10372             :  *
   10373             :  * This is a wrapper over OGRSpatialReference::FindMatches() that takes care
   10374             :  * of filtering its output.
   10375             :  * Only matches whose confidence is greater or equal to nMinimumMatchConfidence
   10376             :  * will be considered. If there is a single match, it is returned.
   10377             :  * If there are several matches, only return the one under the
   10378             :  * pszPreferredAuthority, if there is a single one under that authority.
   10379             :  *
   10380             :  * @param nMinimumMatchConfidence Minimum match confidence (value between 0 and
   10381             :  * 100). If set to 0, 90 is used.
   10382             :  * @param pszPreferredAuthority Preferred CRS authority. If set to nullptr,
   10383             :  * "EPSG" is used.
   10384             :  * @param papszOptions NULL terminated list of options or NULL. No option is
   10385             :  * defined at time of writing.
   10386             :  *
   10387             :  * @return a new OGRSpatialReference* object to free with Release(), or nullptr
   10388             :  *
   10389             :  * @since GDAL 3.6
   10390             :  * @see OGRSpatialReference::FindMatches()
   10391             :  */
   10392             : OGRSpatialReference *
   10393        1551 : OGRSpatialReference::FindBestMatch(int nMinimumMatchConfidence,
   10394             :                                    const char *pszPreferredAuthority,
   10395             :                                    CSLConstList papszOptions) const
   10396             : {
   10397        3102 :     TAKE_OPTIONAL_LOCK();
   10398             : 
   10399        1551 :     CPL_IGNORE_RET_VAL(papszOptions);  // ignored for now.
   10400             : 
   10401        1551 :     if (nMinimumMatchConfidence == 0)
   10402           0 :         nMinimumMatchConfidence = 90;
   10403        1551 :     if (pszPreferredAuthority == nullptr)
   10404         202 :         pszPreferredAuthority = "EPSG";
   10405             : 
   10406             :     // Try to identify the CRS with the database
   10407        1551 :     int nEntries = 0;
   10408        1551 :     int *panConfidence = nullptr;
   10409             :     OGRSpatialReferenceH *pahSRS =
   10410        1551 :         FindMatches(nullptr, &nEntries, &panConfidence);
   10411        1551 :     if (nEntries == 1 && panConfidence[0] >= nMinimumMatchConfidence)
   10412             :     {
   10413        2774 :         std::vector<double> adfTOWGS84(7);
   10414        1387 :         if (GetTOWGS84(&adfTOWGS84[0], 7) != OGRERR_NONE)
   10415             :         {
   10416        1386 :             adfTOWGS84.clear();
   10417             :         }
   10418             : 
   10419        1387 :         auto poSRS = OGRSpatialReference::FromHandle(pahSRS[0]);
   10420             : 
   10421             :         auto poBaseGeogCRS =
   10422        1387 :             std::unique_ptr<OGRSpatialReference>(poSRS->CloneGeogCS());
   10423        1387 :         if (poBaseGeogCRS)
   10424             :         {
   10425             :             // If the base geographic SRS of the SRS is EPSG:4326
   10426             :             // with TOWGS84[0,0,0,0,0,0], then just use the official
   10427             :             // SRS code
   10428             :             // Same with EPSG:4258 (ETRS89), since it's the only known
   10429             :             // TOWGS84[] style transformation to WGS 84, and given the
   10430             :             // "fuzzy" nature of both ETRS89 and WGS 84, there's little
   10431             :             // chance that a non-NULL TOWGS84[] will emerge.
   10432        1387 :             const char *pszAuthorityName = nullptr;
   10433        1387 :             const char *pszAuthorityCode = nullptr;
   10434        1387 :             const char *pszBaseAuthorityName = nullptr;
   10435        1387 :             const char *pszBaseAuthorityCode = nullptr;
   10436        1387 :             const char *pszBaseName = poBaseGeogCRS->GetName();
   10437        2774 :             if (adfTOWGS84 == std::vector<double>(7) &&
   10438           1 :                 (pszAuthorityName = poSRS->GetAuthorityName()) != nullptr &&
   10439           1 :                 EQUAL(pszAuthorityName, "EPSG") &&
   10440           1 :                 (pszAuthorityCode = poSRS->GetAuthorityCode()) != nullptr &&
   10441           1 :                 (pszBaseAuthorityName = poBaseGeogCRS->GetAuthorityName()) !=
   10442           1 :                     nullptr &&
   10443           1 :                 EQUAL(pszBaseAuthorityName, "EPSG") &&
   10444           1 :                 (pszBaseAuthorityCode = poBaseGeogCRS->GetAuthorityCode()) !=
   10445        2775 :                     nullptr &&
   10446           1 :                 (EQUAL(pszBaseAuthorityCode, "4326") ||
   10447           1 :                  EQUAL(pszBaseAuthorityCode, "4258") ||
   10448             :                  // For ETRS89-XXX [...] new CRS added in EPSG 12.033+
   10449           0 :                  (pszBaseName && STARTS_WITH(pszBaseName, "ETRS89"))))
   10450             :             {
   10451           1 :                 poSRS->importFromEPSG(atoi(pszAuthorityCode));
   10452             :             }
   10453             :         }
   10454             : 
   10455        1387 :         CPLFree(pahSRS);
   10456        1387 :         CPLFree(panConfidence);
   10457             : 
   10458        1387 :         return poSRS;
   10459             :     }
   10460             :     else
   10461             :     {
   10462             :         // If there are several matches >= nMinimumMatchConfidence, take the
   10463             :         // only one that is under pszPreferredAuthority
   10464         164 :         int iBestEntry = -1;
   10465        1678 :         for (int i = 0; i < nEntries; i++)
   10466             :         {
   10467        1514 :             if (panConfidence[i] >= nMinimumMatchConfidence)
   10468             :             {
   10469             :                 const char *pszAuthName =
   10470           3 :                     OGRSpatialReference::FromHandle(pahSRS[i])
   10471           3 :                         ->GetAuthorityName();
   10472           3 :                 if (pszAuthName != nullptr &&
   10473           3 :                     EQUAL(pszAuthName, pszPreferredAuthority))
   10474             :                 {
   10475           3 :                     if (iBestEntry < 0)
   10476           3 :                         iBestEntry = i;
   10477             :                     else
   10478             :                     {
   10479           0 :                         iBestEntry = -1;
   10480           0 :                         break;
   10481             :                     }
   10482             :                 }
   10483             :             }
   10484             :         }
   10485         164 :         if (iBestEntry >= 0)
   10486             :         {
   10487           3 :             auto poRet = OGRSpatialReference::FromHandle(pahSRS[0])->Clone();
   10488           3 :             OSRFreeSRSArray(pahSRS);
   10489           3 :             CPLFree(panConfidence);
   10490           3 :             return poRet;
   10491             :         }
   10492             :     }
   10493         161 :     OSRFreeSRSArray(pahSRS);
   10494         161 :     CPLFree(panConfidence);
   10495         161 :     return nullptr;
   10496             : }
   10497             : 
   10498             : /************************************************************************/
   10499             : /*                             SetTOWGS84()                             */
   10500             : /************************************************************************/
   10501             : 
   10502             : /**
   10503             :  * \brief Set the Bursa-Wolf conversion to WGS84.
   10504             :  *
   10505             :  * This will create the TOWGS84 node as a child of the DATUM.  It will fail
   10506             :  * if there is no existing DATUM node. It will replace
   10507             :  * an existing TOWGS84 node if there is one.
   10508             :  *
   10509             :  * The parameters have the same meaning as EPSG transformation 9606
   10510             :  * (Position Vector 7-param. transformation).
   10511             :  *
   10512             :  * This method is the same as the C function OSRSetTOWGS84().
   10513             :  *
   10514             :  * @param dfDX X child in meters.
   10515             :  * @param dfDY Y child in meters.
   10516             :  * @param dfDZ Z child in meters.
   10517             :  * @param dfEX X rotation in arc seconds (optional, defaults to zero).
   10518             :  * @param dfEY Y rotation in arc seconds (optional, defaults to zero).
   10519             :  * @param dfEZ Z rotation in arc seconds (optional, defaults to zero).
   10520             :  * @param dfPPM scaling factor (parts per million).
   10521             :  *
   10522             :  * @return OGRERR_NONE on success.
   10523             :  */
   10524             : 
   10525         103 : OGRErr OGRSpatialReference::SetTOWGS84(double dfDX, double dfDY, double dfDZ,
   10526             :                                        double dfEX, double dfEY, double dfEZ,
   10527             :                                        double dfPPM)
   10528             : 
   10529             : {
   10530         206 :     TAKE_OPTIONAL_LOCK();
   10531             : 
   10532         103 :     d->refreshProjObj();
   10533         103 :     if (d->m_pj_crs == nullptr)
   10534             :     {
   10535           0 :         return OGRERR_FAILURE;
   10536             :     }
   10537             : 
   10538             :     // Remove existing BoundCRS
   10539         103 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS)
   10540             :     {
   10541           0 :         auto baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
   10542           0 :         if (!baseCRS)
   10543           0 :             return OGRERR_FAILURE;
   10544           0 :         d->setPjCRS(baseCRS);
   10545             :     }
   10546             : 
   10547             :     PJ_PARAM_DESCRIPTION params[7];
   10548             : 
   10549         103 :     params[0].name = EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION;
   10550         103 :     params[0].auth_name = "EPSG";
   10551         103 :     params[0].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
   10552         103 :     params[0].value = dfDX;
   10553         103 :     params[0].unit_name = "metre";
   10554         103 :     params[0].unit_conv_factor = 1.0;
   10555         103 :     params[0].unit_type = PJ_UT_LINEAR;
   10556             : 
   10557         103 :     params[1].name = EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION;
   10558         103 :     params[1].auth_name = "EPSG";
   10559         103 :     params[1].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
   10560         103 :     params[1].value = dfDY;
   10561         103 :     params[1].unit_name = "metre";
   10562         103 :     params[1].unit_conv_factor = 1.0;
   10563         103 :     params[1].unit_type = PJ_UT_LINEAR;
   10564             : 
   10565         103 :     params[2].name = EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION;
   10566         103 :     params[2].auth_name = "EPSG";
   10567         103 :     params[2].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
   10568         103 :     params[2].value = dfDZ;
   10569         103 :     params[2].unit_name = "metre";
   10570         103 :     params[2].unit_conv_factor = 1.0;
   10571         103 :     params[2].unit_type = PJ_UT_LINEAR;
   10572             : 
   10573         103 :     params[3].name = EPSG_NAME_PARAMETER_X_AXIS_ROTATION;
   10574         103 :     params[3].auth_name = "EPSG";
   10575         103 :     params[3].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_ROTATION);
   10576         103 :     params[3].value = dfEX;
   10577         103 :     params[3].unit_name = "arc-second";
   10578         103 :     params[3].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10579         103 :     params[3].unit_type = PJ_UT_ANGULAR;
   10580             : 
   10581         103 :     params[4].name = EPSG_NAME_PARAMETER_Y_AXIS_ROTATION;
   10582         103 :     params[4].auth_name = "EPSG";
   10583         103 :     params[4].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION);
   10584         103 :     params[4].value = dfEY;
   10585         103 :     params[4].unit_name = "arc-second";
   10586         103 :     params[4].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10587         103 :     params[4].unit_type = PJ_UT_ANGULAR;
   10588             : 
   10589         103 :     params[5].name = EPSG_NAME_PARAMETER_Z_AXIS_ROTATION;
   10590         103 :     params[5].auth_name = "EPSG";
   10591         103 :     params[5].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION);
   10592         103 :     params[5].value = dfEZ;
   10593         103 :     params[5].unit_name = "arc-second";
   10594         103 :     params[5].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10595         103 :     params[5].unit_type = PJ_UT_ANGULAR;
   10596             : 
   10597         103 :     params[6].name = EPSG_NAME_PARAMETER_SCALE_DIFFERENCE;
   10598         103 :     params[6].auth_name = "EPSG";
   10599         103 :     params[6].code = XSTRINGIFY(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE);
   10600         103 :     params[6].value = dfPPM;
   10601         103 :     params[6].unit_name = "parts per million";
   10602         103 :     params[6].unit_conv_factor = 1e-6;
   10603         103 :     params[6].unit_type = PJ_UT_SCALE;
   10604             : 
   10605             :     auto sourceCRS =
   10606         103 :         proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10607         103 :     if (!sourceCRS)
   10608             :     {
   10609           0 :         return OGRERR_FAILURE;
   10610             :     }
   10611             : 
   10612         103 :     const auto sourceType = proj_get_type(sourceCRS);
   10613             : 
   10614         103 :     auto targetCRS = proj_create_from_database(
   10615             :         d->getPROJContext(), "EPSG",
   10616             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS   ? "4326"
   10617             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS ? "4979"
   10618             :                                                   : "4978",
   10619             :         PJ_CATEGORY_CRS, false, nullptr);
   10620         103 :     if (!targetCRS)
   10621             :     {
   10622           0 :         proj_destroy(sourceCRS);
   10623           0 :         return OGRERR_FAILURE;
   10624             :     }
   10625             : 
   10626         206 :     CPLString osMethodCode;
   10627             :     osMethodCode.Printf("%d",
   10628             :                         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10629             :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10630             :                         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10631           0 :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10632         103 :                             : EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC);
   10633             : 
   10634         103 :     auto transf = proj_create_transformation(
   10635             :         d->getPROJContext(), "Transformation to WGS84", nullptr, nullptr,
   10636             :         sourceCRS, targetCRS, nullptr,
   10637             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10638             :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10639             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10640           0 :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10641             :             : EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC,
   10642             :         "EPSG", osMethodCode.c_str(), 7, params, -1);
   10643         103 :     proj_destroy(sourceCRS);
   10644         103 :     if (!transf)
   10645             :     {
   10646           0 :         proj_destroy(targetCRS);
   10647           0 :         return OGRERR_FAILURE;
   10648             :     }
   10649             : 
   10650         103 :     auto newBoundCRS = proj_crs_create_bound_crs(
   10651         103 :         d->getPROJContext(), d->m_pj_crs, targetCRS, transf);
   10652         103 :     proj_destroy(transf);
   10653         103 :     proj_destroy(targetCRS);
   10654         103 :     if (!newBoundCRS)
   10655             :     {
   10656           0 :         return OGRERR_FAILURE;
   10657             :     }
   10658             : 
   10659         103 :     d->setPjCRS(newBoundCRS);
   10660         103 :     return OGRERR_NONE;
   10661             : }
   10662             : 
   10663             : /************************************************************************/
   10664             : /*                           OSRSetTOWGS84()                            */
   10665             : /************************************************************************/
   10666             : 
   10667             : /**
   10668             :  * \brief Set the Bursa-Wolf conversion to WGS84.
   10669             :  *
   10670             :  * This function is the same as OGRSpatialReference::SetTOWGS84().
   10671             :  */
   10672           4 : OGRErr OSRSetTOWGS84(OGRSpatialReferenceH hSRS, double dfDX, double dfDY,
   10673             :                      double dfDZ, double dfEX, double dfEY, double dfEZ,
   10674             :                      double dfPPM)
   10675             : 
   10676             : {
   10677           4 :     VALIDATE_POINTER1(hSRS, "OSRSetTOWGS84", OGRERR_FAILURE);
   10678             : 
   10679           4 :     return ToPointer(hSRS)->SetTOWGS84(dfDX, dfDY, dfDZ, dfEX, dfEY, dfEZ,
   10680           4 :                                        dfPPM);
   10681             : }
   10682             : 
   10683             : /************************************************************************/
   10684             : /*                             GetTOWGS84()                             */
   10685             : /************************************************************************/
   10686             : 
   10687             : /**
   10688             :  * \brief Fetch TOWGS84 parameters, if available.
   10689             :  *
   10690             :  * The parameters have the same meaning as EPSG transformation 9606
   10691             :  * (Position Vector 7-param. transformation).
   10692             :  *
   10693             :  * @param padfCoeff array into which up to 7 coefficients are placed.
   10694             :  * @param nCoeffCount size of padfCoeff - defaults to 7.
   10695             :  *
   10696             :  * @return OGRERR_NONE on success, or OGRERR_FAILURE if there is no
   10697             :  * TOWGS84 node available.
   10698             :  */
   10699             : 
   10700        5040 : OGRErr OGRSpatialReference::GetTOWGS84(double *padfCoeff, int nCoeffCount) const
   10701             : 
   10702             : {
   10703       10080 :     TAKE_OPTIONAL_LOCK();
   10704             : 
   10705        5040 :     d->refreshProjObj();
   10706        5040 :     if (d->m_pjType != PJ_TYPE_BOUND_CRS)
   10707        4992 :         return OGRERR_FAILURE;
   10708             : 
   10709          48 :     memset(padfCoeff, 0, sizeof(double) * nCoeffCount);
   10710             : 
   10711          48 :     auto transf = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10712          48 :     int success = proj_coordoperation_get_towgs84_values(
   10713             :         d->getPROJContext(), transf, padfCoeff, nCoeffCount, false);
   10714          48 :     proj_destroy(transf);
   10715             : 
   10716          48 :     return success ? OGRERR_NONE : OGRERR_FAILURE;
   10717             : }
   10718             : 
   10719             : /************************************************************************/
   10720             : /*                           OSRGetTOWGS84()                            */
   10721             : /************************************************************************/
   10722             : 
   10723             : /**
   10724             :  * \brief Fetch TOWGS84 parameters, if available.
   10725             :  *
   10726             :  * This function is the same as OGRSpatialReference::GetTOWGS84().
   10727             :  */
   10728          10 : OGRErr OSRGetTOWGS84(OGRSpatialReferenceH hSRS, double *padfCoeff,
   10729             :                      int nCoeffCount)
   10730             : 
   10731             : {
   10732          10 :     VALIDATE_POINTER1(hSRS, "OSRGetTOWGS84", OGRERR_FAILURE);
   10733             : 
   10734          10 :     return ToPointer(hSRS)->GetTOWGS84(padfCoeff, nCoeffCount);
   10735             : }
   10736             : 
   10737             : /************************************************************************/
   10738             : /*                         IsAngularParameter()                         */
   10739             : /************************************************************************/
   10740             : 
   10741             : /** Is the passed projection parameter an angular one?
   10742             :  *
   10743             :  * @return TRUE or FALSE
   10744             :  */
   10745             : 
   10746             : /* static */
   10747          10 : int OGRSpatialReference::IsAngularParameter(const char *pszParameterName)
   10748             : 
   10749             : {
   10750          10 :     if (STARTS_WITH_CI(pszParameterName, "long") ||
   10751          10 :         STARTS_WITH_CI(pszParameterName, "lati") ||
   10752           7 :         EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN) ||
   10753           4 :         STARTS_WITH_CI(pszParameterName, "standard_parallel") ||
   10754           2 :         EQUAL(pszParameterName, SRS_PP_AZIMUTH) ||
   10755           2 :         EQUAL(pszParameterName, SRS_PP_RECTIFIED_GRID_ANGLE))
   10756           8 :         return TRUE;
   10757             : 
   10758           2 :     return FALSE;
   10759             : }
   10760             : 
   10761             : /************************************************************************/
   10762             : /*                        IsLongitudeParameter()                        */
   10763             : /************************************************************************/
   10764             : 
   10765             : /** Is the passed projection parameter an angular longitude
   10766             :  * (relative to a prime meridian)?
   10767             :  *
   10768             :  * @return TRUE or FALSE
   10769             :  */
   10770             : 
   10771             : /* static */
   10772           0 : int OGRSpatialReference::IsLongitudeParameter(const char *pszParameterName)
   10773             : 
   10774             : {
   10775           0 :     if (STARTS_WITH_CI(pszParameterName, "long") ||
   10776           0 :         EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN))
   10777           0 :         return TRUE;
   10778             : 
   10779           0 :     return FALSE;
   10780             : }
   10781             : 
   10782             : /************************************************************************/
   10783             : /*                         IsLinearParameter()                          */
   10784             : /************************************************************************/
   10785             : 
   10786             : /** Is the passed projection parameter an linear one measured in meters or
   10787             :  * some similar linear measure.
   10788             :  *
   10789             :  * @return TRUE or FALSE
   10790             :  */
   10791             : 
   10792             : /* static */
   10793          43 : int OGRSpatialReference::IsLinearParameter(const char *pszParameterName)
   10794             : 
   10795             : {
   10796          43 :     if (STARTS_WITH_CI(pszParameterName, "false_") ||
   10797          34 :         EQUAL(pszParameterName, SRS_PP_SATELLITE_HEIGHT))
   10798           9 :         return TRUE;
   10799             : 
   10800          34 :     return FALSE;
   10801             : }
   10802             : 
   10803             : /************************************************************************/
   10804             : /*                            GetNormInfo()                             */
   10805             : /************************************************************************/
   10806             : 
   10807             : /**
   10808             :  * \brief Set the internal information for normalizing linear, and angular
   10809             :  * values.
   10810             :  */
   10811        4148 : void OGRSpatialReference::GetNormInfo() const
   10812             : 
   10813             : {
   10814        4148 :     TAKE_OPTIONAL_LOCK();
   10815             : 
   10816        4148 :     if (d->bNormInfoSet)
   10817        2963 :         return;
   10818             : 
   10819             :     /* -------------------------------------------------------------------- */
   10820             :     /*      Initialize values.                                              */
   10821             :     /* -------------------------------------------------------------------- */
   10822        1185 :     d->bNormInfoSet = TRUE;
   10823             : 
   10824        1185 :     d->dfFromGreenwich = GetPrimeMeridian(nullptr);
   10825        1185 :     d->dfToMeter = GetLinearUnits(nullptr);
   10826        1185 :     d->dfToDegrees = GetAngularUnits(nullptr) / CPLAtof(SRS_UA_DEGREE_CONV);
   10827        1185 :     if (fabs(d->dfToDegrees - 1.0) < 0.000000001)
   10828        1182 :         d->dfToDegrees = 1.0;
   10829             : }
   10830             : 
   10831             : /************************************************************************/
   10832             : /*                            GetExtension()                            */
   10833             : /************************************************************************/
   10834             : 
   10835             : /**
   10836             :  * \brief Fetch extension value.
   10837             :  *
   10838             :  * Fetch the value of the named EXTENSION item for the identified
   10839             :  * target node.
   10840             :  *
   10841             :  * @param pszTargetKey the name or path to the parent node of the EXTENSION.
   10842             :  * @param pszName the name of the extension being fetched.
   10843             :  * @param pszDefault the value to return if the extension is not found.
   10844             :  *
   10845             :  * @return node value if successful or pszDefault on failure.
   10846             :  */
   10847             : 
   10848       13588 : const char *OGRSpatialReference::GetExtension(const char *pszTargetKey,
   10849             :                                               const char *pszName,
   10850             :                                               const char *pszDefault) const
   10851             : 
   10852             : {
   10853       27176 :     TAKE_OPTIONAL_LOCK();
   10854             : 
   10855             :     /* -------------------------------------------------------------------- */
   10856             :     /*      Find the target node.                                           */
   10857             :     /* -------------------------------------------------------------------- */
   10858             :     const OGR_SRSNode *poNode =
   10859       13588 :         pszTargetKey == nullptr ? GetRoot() : GetAttrNode(pszTargetKey);
   10860             : 
   10861       13588 :     if (poNode == nullptr)
   10862        2407 :         return nullptr;
   10863             : 
   10864             :     /* -------------------------------------------------------------------- */
   10865             :     /*      Fetch matching EXTENSION if there is one.                       */
   10866             :     /* -------------------------------------------------------------------- */
   10867       82355 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10868             :     {
   10869       71198 :         const OGR_SRSNode *poChild = poNode->GetChild(i);
   10870             : 
   10871       71224 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10872          26 :             poChild->GetChildCount() >= 2)
   10873             :         {
   10874          26 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10875          24 :                 return poChild->GetChild(1)->GetValue();
   10876             :         }
   10877             :     }
   10878             : 
   10879       11157 :     return pszDefault;
   10880             : }
   10881             : 
   10882             : /************************************************************************/
   10883             : /*                            SetExtension()                            */
   10884             : /************************************************************************/
   10885             : /**
   10886             :  * \brief Set extension value.
   10887             :  *
   10888             :  * Set the value of the named EXTENSION item for the identified
   10889             :  * target node.
   10890             :  *
   10891             :  * @param pszTargetKey the name or path to the parent node of the EXTENSION.
   10892             :  * @param pszName the name of the extension being fetched.
   10893             :  * @param pszValue the value to set
   10894             :  *
   10895             :  * @return OGRERR_NONE on success
   10896             :  */
   10897             : 
   10898          20 : OGRErr OGRSpatialReference::SetExtension(const char *pszTargetKey,
   10899             :                                          const char *pszName,
   10900             :                                          const char *pszValue)
   10901             : 
   10902             : {
   10903          40 :     TAKE_OPTIONAL_LOCK();
   10904             : 
   10905             :     /* -------------------------------------------------------------------- */
   10906             :     /*      Find the target node.                                           */
   10907             :     /* -------------------------------------------------------------------- */
   10908          20 :     OGR_SRSNode *poNode = nullptr;
   10909             : 
   10910          20 :     if (pszTargetKey == nullptr)
   10911           0 :         poNode = GetRoot();
   10912             :     else
   10913          20 :         poNode = GetAttrNode(pszTargetKey);
   10914             : 
   10915          20 :     if (poNode == nullptr)
   10916           0 :         return OGRERR_FAILURE;
   10917             : 
   10918             :     /* -------------------------------------------------------------------- */
   10919             :     /*      Fetch matching EXTENSION if there is one.                       */
   10920             :     /* -------------------------------------------------------------------- */
   10921         151 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10922             :     {
   10923         137 :         OGR_SRSNode *poChild = poNode->GetChild(i);
   10924             : 
   10925         143 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10926           6 :             poChild->GetChildCount() >= 2)
   10927             :         {
   10928           6 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10929             :             {
   10930           6 :                 poChild->GetChild(1)->SetValue(pszValue);
   10931           6 :                 return OGRERR_NONE;
   10932             :             }
   10933             :         }
   10934             :     }
   10935             : 
   10936             :     /* -------------------------------------------------------------------- */
   10937             :     /*      Create a new EXTENSION node.                                    */
   10938             :     /* -------------------------------------------------------------------- */
   10939          14 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("EXTENSION");
   10940          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszName));
   10941          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszValue));
   10942             : 
   10943          14 :     poNode->AddChild(poAuthNode);
   10944             : 
   10945          14 :     return OGRERR_NONE;
   10946             : }
   10947             : 
   10948             : /************************************************************************/
   10949             : /*                             OSRCleanup()                             */
   10950             : /************************************************************************/
   10951             : 
   10952             : static void CleanupSRSWGS84Mutex();
   10953             : 
   10954             : /**
   10955             :  * \brief Cleanup cached SRS related memory.
   10956             :  *
   10957             :  * This function will attempt to cleanup any cache spatial reference
   10958             :  * related information, such as cached tables of coordinate systems.
   10959             :  *
   10960             :  * This function should not be called concurrently with any other GDAL/OGR
   10961             :  * function. It is meant at being called once before process termination
   10962             :  * (typically from the main thread). CPLCleanupTLS() might be used to clean
   10963             :  * thread-specific resources before thread termination.
   10964             :  */
   10965        1308 : void OSRCleanup(void)
   10966             : 
   10967             : {
   10968        1308 :     OGRCTDumpStatistics();
   10969        1308 :     CSVDeaccess(nullptr);
   10970        1308 :     CleanupSRSWGS84Mutex();
   10971        1308 :     OSRCTCleanCache();
   10972        1308 :     OSRCleanupTLSContext();
   10973        1308 : }
   10974             : 
   10975             : /************************************************************************/
   10976             : /*                            GetAxesCount()                            */
   10977             : /************************************************************************/
   10978             : 
   10979             : /**
   10980             :  * \brief Return the number of axis of the coordinate system of the CRS.
   10981             :  *
   10982             :  * @since GDAL 3.0
   10983             :  */
   10984       40363 : int OGRSpatialReference::GetAxesCount() const
   10985             : {
   10986       80726 :     TAKE_OPTIONAL_LOCK();
   10987             : 
   10988       40363 :     int axisCount = 0;
   10989       40363 :     d->refreshProjObj();
   10990       40363 :     if (d->m_pj_crs == nullptr)
   10991             :     {
   10992           0 :         return 0;
   10993             :     }
   10994       40363 :     d->demoteFromBoundCRS();
   10995       40363 :     auto ctxt = d->getPROJContext();
   10996       40363 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   10997             :     {
   10998          96 :         for (int i = 0;; i++)
   10999             :         {
   11000         288 :             auto subCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, i);
   11001         288 :             if (!subCRS)
   11002          96 :                 break;
   11003         192 :             if (proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
   11004             :             {
   11005          18 :                 auto baseCRS = proj_get_source_crs(ctxt, subCRS);
   11006          18 :                 if (baseCRS)
   11007             :                 {
   11008          18 :                     proj_destroy(subCRS);
   11009          18 :                     subCRS = baseCRS;
   11010             :                 }
   11011             :             }
   11012         192 :             auto cs = proj_crs_get_coordinate_system(ctxt, subCRS);
   11013         192 :             if (cs)
   11014             :             {
   11015         192 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
   11016         192 :                 proj_destroy(cs);
   11017             :             }
   11018         192 :             proj_destroy(subCRS);
   11019         192 :         }
   11020             :     }
   11021             :     else
   11022             :     {
   11023       40267 :         auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   11024       40267 :         if (cs)
   11025             :         {
   11026       40267 :             axisCount = proj_cs_get_axis_count(ctxt, cs);
   11027       40267 :             proj_destroy(cs);
   11028             :         }
   11029             :     }
   11030       40363 :     d->undoDemoteFromBoundCRS();
   11031       40363 :     return axisCount;
   11032             : }
   11033             : 
   11034             : /************************************************************************/
   11035             : /*                          OSRGetAxesCount()                           */
   11036             : /************************************************************************/
   11037             : 
   11038             : /**
   11039             :  * \brief Return the number of axis of the coordinate system of the CRS.
   11040             :  *
   11041             :  * This method is the equivalent of the C++ method
   11042             :  * OGRSpatialReference::GetAxesCount()
   11043             :  *
   11044             :  * @since GDAL 3.1
   11045             :  */
   11046           6 : int OSRGetAxesCount(OGRSpatialReferenceH hSRS)
   11047             : 
   11048             : {
   11049           6 :     VALIDATE_POINTER1(hSRS, "OSRGetAxesCount", 0);
   11050             : 
   11051           6 :     return ToPointer(hSRS)->GetAxesCount();
   11052             : }
   11053             : 
   11054             : /************************************************************************/
   11055             : /*                              GetAxis()                               */
   11056             : /************************************************************************/
   11057             : 
   11058             : /**
   11059             :  * \brief Fetch the orientation of one axis.
   11060             :  *
   11061             :  * Fetches the request axis (iAxis - zero based) from the
   11062             :  * indicated portion of the coordinate system (pszTargetKey) which
   11063             :  * should be either "GEOGCS" or "PROJCS".
   11064             :  *
   11065             :  * No CPLError is issued on routine failures (such as not finding the AXIS).
   11066             :  *
   11067             :  * This method is equivalent to the C function OSRGetAxis().
   11068             :  *
   11069             :  * @param pszTargetKey the coordinate system part to query ("PROJCS" or
   11070             :  * "GEOGCS").
   11071             :  * @param iAxis the axis to query (0 for first, 1 for second, 2 for third).
   11072             :  * @param peOrientation location into which to place the fetch orientation, may
   11073             :  * be NULL.
   11074             :  * @param pdfConvUnit (GDAL >= 3.4) Location into which to place axis conversion
   11075             :  * factor. May be NULL. Only set if pszTargetKey == NULL
   11076             :  *
   11077             :  * @return the name of the axis or NULL on failure.
   11078             :  */
   11079             : 
   11080        9024 : const char *OGRSpatialReference::GetAxis(const char *pszTargetKey, int iAxis,
   11081             :                                          OGRAxisOrientation *peOrientation,
   11082             :                                          double *pdfConvUnit) const
   11083             : 
   11084             : {
   11085       18048 :     TAKE_OPTIONAL_LOCK();
   11086             : 
   11087        9024 :     if (peOrientation != nullptr)
   11088        8913 :         *peOrientation = OAO_Other;
   11089        9024 :     if (pdfConvUnit != nullptr)
   11090         103 :         *pdfConvUnit = 0;
   11091             : 
   11092        9024 :     d->refreshProjObj();
   11093        9024 :     if (d->m_pj_crs == nullptr)
   11094             :     {
   11095           3 :         return nullptr;
   11096             :     }
   11097             : 
   11098        9021 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
   11099        9021 :     if (pszTargetKey == nullptr && iAxis <= 2)
   11100             :     {
   11101        9021 :         auto ctxt = d->getPROJContext();
   11102             : 
   11103        9021 :         int iAxisModified = iAxis;
   11104             : 
   11105        9021 :         d->demoteFromBoundCRS();
   11106             : 
   11107        9021 :         PJ *cs = nullptr;
   11108        9021 :         if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   11109             :         {
   11110         138 :             auto horizCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   11111         138 :             if (horizCRS)
   11112             :             {
   11113         138 :                 if (proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
   11114             :                 {
   11115           6 :                     auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
   11116           6 :                     if (baseCRS)
   11117             :                     {
   11118           6 :                         proj_destroy(horizCRS);
   11119           6 :                         horizCRS = baseCRS;
   11120             :                     }
   11121             :                 }
   11122         138 :                 cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
   11123         138 :                 proj_destroy(horizCRS);
   11124         138 :                 if (cs)
   11125             :                 {
   11126         138 :                     if (iAxisModified >= proj_cs_get_axis_count(ctxt, cs))
   11127             :                     {
   11128          45 :                         iAxisModified -= proj_cs_get_axis_count(ctxt, cs);
   11129          45 :                         proj_destroy(cs);
   11130          45 :                         cs = nullptr;
   11131             :                     }
   11132             :                 }
   11133             :             }
   11134             : 
   11135         138 :             if (cs == nullptr)
   11136             :             {
   11137          45 :                 auto vertCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
   11138          45 :                 if (vertCRS)
   11139             :                 {
   11140          45 :                     if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
   11141             :                     {
   11142          30 :                         auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
   11143          30 :                         if (baseCRS)
   11144             :                         {
   11145          30 :                             proj_destroy(vertCRS);
   11146          30 :                             vertCRS = baseCRS;
   11147             :                         }
   11148             :                     }
   11149             : 
   11150          45 :                     cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
   11151          45 :                     proj_destroy(vertCRS);
   11152             :                 }
   11153             :             }
   11154             :         }
   11155             :         else
   11156             :         {
   11157        8883 :             cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   11158             :         }
   11159             : 
   11160        9021 :         if (cs)
   11161             :         {
   11162        9021 :             const char *pszName = nullptr;
   11163        9021 :             const char *pszOrientation = nullptr;
   11164        9021 :             double dfConvFactor = 0.0;
   11165        9021 :             proj_cs_get_axis_info(ctxt, cs, iAxisModified, &pszName, nullptr,
   11166             :                                   &pszOrientation, &dfConvFactor, nullptr,
   11167             :                                   nullptr, nullptr);
   11168             : 
   11169        9021 :             if (pdfConvUnit != nullptr)
   11170             :             {
   11171         103 :                 *pdfConvUnit = dfConvFactor;
   11172             :             }
   11173             : 
   11174        9021 :             if (pszName && pszOrientation)
   11175             :             {
   11176        9021 :                 d->m_osAxisName[iAxis] = pszName;
   11177        9021 :                 if (peOrientation)
   11178             :                 {
   11179        8910 :                     if (EQUAL(pszOrientation, "NORTH"))
   11180        5627 :                         *peOrientation = OAO_North;
   11181        3283 :                     else if (EQUAL(pszOrientation, "EAST"))
   11182        3208 :                         *peOrientation = OAO_East;
   11183          75 :                     else if (EQUAL(pszOrientation, "SOUTH"))
   11184          67 :                         *peOrientation = OAO_South;
   11185           8 :                     else if (EQUAL(pszOrientation, "WEST"))
   11186           0 :                         *peOrientation = OAO_West;
   11187           8 :                     else if (EQUAL(pszOrientation, "UP"))
   11188           1 :                         *peOrientation = OAO_Up;
   11189           7 :                     else if (EQUAL(pszOrientation, "DOWN"))
   11190           0 :                         *peOrientation = OAO_Down;
   11191             :                 }
   11192        9021 :                 proj_destroy(cs);
   11193        9021 :                 d->undoDemoteFromBoundCRS();
   11194        9021 :                 return d->m_osAxisName[iAxis].c_str();
   11195             :             }
   11196           0 :             proj_destroy(cs);
   11197             :         }
   11198           0 :         d->undoDemoteFromBoundCRS();
   11199             :     }
   11200             : 
   11201             :     /* -------------------------------------------------------------------- */
   11202             :     /*      Find the target node.                                           */
   11203             :     /* -------------------------------------------------------------------- */
   11204           0 :     const OGR_SRSNode *poNode = nullptr;
   11205             : 
   11206           0 :     if (pszTargetKey == nullptr)
   11207           0 :         poNode = GetRoot();
   11208             :     else
   11209           0 :         poNode = GetAttrNode(pszTargetKey);
   11210             : 
   11211           0 :     if (poNode == nullptr)
   11212           0 :         return nullptr;
   11213             : 
   11214             :     /* -------------------------------------------------------------------- */
   11215             :     /*      Find desired child AXIS.                                        */
   11216             :     /* -------------------------------------------------------------------- */
   11217           0 :     const OGR_SRSNode *poAxis = nullptr;
   11218           0 :     const int nChildCount = poNode->GetChildCount();
   11219             : 
   11220           0 :     for (int iChild = 0; iChild < nChildCount; iChild++)
   11221             :     {
   11222           0 :         const OGR_SRSNode *poChild = poNode->GetChild(iChild);
   11223             : 
   11224           0 :         if (!EQUAL(poChild->GetValue(), "AXIS"))
   11225           0 :             continue;
   11226             : 
   11227           0 :         if (iAxis == 0)
   11228             :         {
   11229           0 :             poAxis = poChild;
   11230           0 :             break;
   11231             :         }
   11232           0 :         iAxis--;
   11233             :     }
   11234             : 
   11235           0 :     if (poAxis == nullptr)
   11236           0 :         return nullptr;
   11237             : 
   11238           0 :     if (poAxis->GetChildCount() < 2)
   11239           0 :         return nullptr;
   11240             : 
   11241             :     /* -------------------------------------------------------------------- */
   11242             :     /*      Extract name and orientation if possible.                       */
   11243             :     /* -------------------------------------------------------------------- */
   11244           0 :     if (peOrientation != nullptr)
   11245             :     {
   11246           0 :         const char *pszOrientation = poAxis->GetChild(1)->GetValue();
   11247             : 
   11248           0 :         if (EQUAL(pszOrientation, "NORTH"))
   11249           0 :             *peOrientation = OAO_North;
   11250           0 :         else if (EQUAL(pszOrientation, "EAST"))
   11251           0 :             *peOrientation = OAO_East;
   11252           0 :         else if (EQUAL(pszOrientation, "SOUTH"))
   11253           0 :             *peOrientation = OAO_South;
   11254           0 :         else if (EQUAL(pszOrientation, "WEST"))
   11255           0 :             *peOrientation = OAO_West;
   11256           0 :         else if (EQUAL(pszOrientation, "UP"))
   11257           0 :             *peOrientation = OAO_Up;
   11258           0 :         else if (EQUAL(pszOrientation, "DOWN"))
   11259           0 :             *peOrientation = OAO_Down;
   11260           0 :         else if (EQUAL(pszOrientation, "OTHER"))
   11261           0 :             *peOrientation = OAO_Other;
   11262             :         else
   11263             :         {
   11264           0 :             CPLDebug("OSR", "Unrecognized orientation value '%s'.",
   11265             :                      pszOrientation);
   11266             :         }
   11267             :     }
   11268             : 
   11269           0 :     return poAxis->GetChild(0)->GetValue();
   11270             : }
   11271             : 
   11272             : /************************************************************************/
   11273             : /*                             OSRGetAxis()                             */
   11274             : /************************************************************************/
   11275             : 
   11276             : /**
   11277             :  * \brief Fetch the orientation of one axis.
   11278             :  *
   11279             :  * This method is the equivalent of the C++ method OGRSpatialReference::GetAxis
   11280             :  */
   11281          13 : const char *OSRGetAxis(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11282             :                        int iAxis, OGRAxisOrientation *peOrientation)
   11283             : 
   11284             : {
   11285          13 :     VALIDATE_POINTER1(hSRS, "OSRGetAxis", nullptr);
   11286             : 
   11287          13 :     return ToPointer(hSRS)->GetAxis(pszTargetKey, iAxis, peOrientation);
   11288             : }
   11289             : 
   11290             : /************************************************************************/
   11291             : /*                         OSRAxisEnumToName()                          */
   11292             : /************************************************************************/
   11293             : 
   11294             : /**
   11295             :  * \brief Return the string representation for the OGRAxisOrientation
   11296             :  * enumeration.
   11297             :  *
   11298             :  * For example "NORTH" for OAO_North.
   11299             :  *
   11300             :  * @return an internal string
   11301             :  */
   11302         400 : const char *OSRAxisEnumToName(OGRAxisOrientation eOrientation)
   11303             : 
   11304             : {
   11305         400 :     if (eOrientation == OAO_North)
   11306         200 :         return "NORTH";
   11307         200 :     if (eOrientation == OAO_East)
   11308         200 :         return "EAST";
   11309           0 :     if (eOrientation == OAO_South)
   11310           0 :         return "SOUTH";
   11311           0 :     if (eOrientation == OAO_West)
   11312           0 :         return "WEST";
   11313           0 :     if (eOrientation == OAO_Up)
   11314           0 :         return "UP";
   11315           0 :     if (eOrientation == OAO_Down)
   11316           0 :         return "DOWN";
   11317           0 :     if (eOrientation == OAO_Other)
   11318           0 :         return "OTHER";
   11319             : 
   11320           0 :     return "UNKNOWN";
   11321             : }
   11322             : 
   11323             : /************************************************************************/
   11324             : /*                              SetAxes()                               */
   11325             : /************************************************************************/
   11326             : 
   11327             : /**
   11328             :  * \brief Set the axes for a coordinate system.
   11329             :  *
   11330             :  * Set the names, and orientations of the axes for either a projected
   11331             :  * (PROJCS) or geographic (GEOGCS) coordinate system.
   11332             :  *
   11333             :  * This method is equivalent to the C function OSRSetAxes().
   11334             :  *
   11335             :  * @param pszTargetKey either "PROJCS" or "GEOGCS", must already exist in SRS.
   11336             :  * @param pszXAxisName name of first axis, normally "Long" or "Easting".
   11337             :  * @param eXAxisOrientation normally OAO_East.
   11338             :  * @param pszYAxisName name of second axis, normally "Lat" or "Northing".
   11339             :  * @param eYAxisOrientation normally OAO_North.
   11340             :  *
   11341             :  * @return OGRERR_NONE on success or an error code.
   11342             :  */
   11343             : 
   11344         200 : OGRErr OGRSpatialReference::SetAxes(const char *pszTargetKey,
   11345             :                                     const char *pszXAxisName,
   11346             :                                     OGRAxisOrientation eXAxisOrientation,
   11347             :                                     const char *pszYAxisName,
   11348             :                                     OGRAxisOrientation eYAxisOrientation)
   11349             : 
   11350             : {
   11351         400 :     TAKE_OPTIONAL_LOCK();
   11352             : 
   11353             :     /* -------------------------------------------------------------------- */
   11354             :     /*      Find the target node.                                           */
   11355             :     /* -------------------------------------------------------------------- */
   11356         200 :     OGR_SRSNode *poNode = nullptr;
   11357             : 
   11358         200 :     if (pszTargetKey == nullptr)
   11359         200 :         poNode = GetRoot();
   11360             :     else
   11361           0 :         poNode = GetAttrNode(pszTargetKey);
   11362             : 
   11363         200 :     if (poNode == nullptr)
   11364           0 :         return OGRERR_FAILURE;
   11365             : 
   11366             :     /* -------------------------------------------------------------------- */
   11367             :     /*      Strip any existing AXIS children.                               */
   11368             :     /* -------------------------------------------------------------------- */
   11369         600 :     while (poNode->FindChild("AXIS") >= 0)
   11370         400 :         poNode->DestroyChild(poNode->FindChild("AXIS"));
   11371             : 
   11372             :     /* -------------------------------------------------------------------- */
   11373             :     /*      Insert desired axes                                             */
   11374             :     /* -------------------------------------------------------------------- */
   11375         200 :     OGR_SRSNode *poAxis = new OGR_SRSNode("AXIS");
   11376             : 
   11377         200 :     poAxis->AddChild(new OGR_SRSNode(pszXAxisName));
   11378         200 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eXAxisOrientation)));
   11379             : 
   11380         200 :     poNode->AddChild(poAxis);
   11381             : 
   11382         200 :     poAxis = new OGR_SRSNode("AXIS");
   11383             : 
   11384         200 :     poAxis->AddChild(new OGR_SRSNode(pszYAxisName));
   11385         200 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eYAxisOrientation)));
   11386             : 
   11387         200 :     poNode->AddChild(poAxis);
   11388             : 
   11389         200 :     return OGRERR_NONE;
   11390             : }
   11391             : 
   11392             : /************************************************************************/
   11393             : /*                             OSRSetAxes()                             */
   11394             : /************************************************************************/
   11395             : /**
   11396             :  * \brief Set the axes for a coordinate system.
   11397             :  *
   11398             :  * This method is the equivalent of the C++ method OGRSpatialReference::SetAxes
   11399             :  */
   11400           0 : OGRErr OSRSetAxes(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11401             :                   const char *pszXAxisName,
   11402             :                   OGRAxisOrientation eXAxisOrientation,
   11403             :                   const char *pszYAxisName,
   11404             :                   OGRAxisOrientation eYAxisOrientation)
   11405             : {
   11406           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAxes", OGRERR_FAILURE);
   11407             : 
   11408           0 :     return ToPointer(hSRS)->SetAxes(pszTargetKey, pszXAxisName,
   11409             :                                     eXAxisOrientation, pszYAxisName,
   11410           0 :                                     eYAxisOrientation);
   11411             : }
   11412             : 
   11413             : /************************************************************************/
   11414             : /*                       OSRExportToMICoordSys()                        */
   11415             : /************************************************************************/
   11416             : /**
   11417             :  * \brief Export coordinate system in Mapinfo style CoordSys format.
   11418             :  *
   11419             :  * This method is the equivalent of the C++ method
   11420             :  * OGRSpatialReference::exportToMICoordSys
   11421             :  */
   11422           5 : OGRErr OSRExportToMICoordSys(OGRSpatialReferenceH hSRS, char **ppszReturn)
   11423             : 
   11424             : {
   11425           5 :     VALIDATE_POINTER1(hSRS, "OSRExportToMICoordSys", OGRERR_FAILURE);
   11426             : 
   11427           5 :     *ppszReturn = nullptr;
   11428             : 
   11429           5 :     return ToPointer(hSRS)->exportToMICoordSys(ppszReturn);
   11430             : }
   11431             : 
   11432             : /************************************************************************/
   11433             : /*                         exportToMICoordSys()                         */
   11434             : /************************************************************************/
   11435             : 
   11436             : /**
   11437             :  * \brief Export coordinate system in Mapinfo style CoordSys format.
   11438             :  *
   11439             :  * Note that the returned WKT string should be freed with
   11440             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
   11441             :  *
   11442             :  * This method is the same as the C function OSRExportToMICoordSys().
   11443             :  *
   11444             :  * @param ppszResult pointer to which dynamically allocated Mapinfo CoordSys
   11445             :  * definition will be assigned.
   11446             :  *
   11447             :  * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
   11448             :  * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
   11449             :  */
   11450             : 
   11451           7 : OGRErr OGRSpatialReference::exportToMICoordSys(char **ppszResult) const
   11452             : 
   11453             : {
   11454           7 :     *ppszResult = MITABSpatialRef2CoordSys(this);
   11455           7 :     if (*ppszResult != nullptr && strlen(*ppszResult) > 0)
   11456           7 :         return OGRERR_NONE;
   11457             : 
   11458           0 :     return OGRERR_FAILURE;
   11459             : }
   11460             : 
   11461             : /************************************************************************/
   11462             : /*                      OSRImportFromMICoordSys()                       */
   11463             : /************************************************************************/
   11464             : /**
   11465             :  * \brief Import Mapinfo style CoordSys definition.
   11466             :  *
   11467             :  * This method is the equivalent of the C++ method
   11468             :  * OGRSpatialReference::importFromMICoordSys
   11469             :  */
   11470             : 
   11471           3 : OGRErr OSRImportFromMICoordSys(OGRSpatialReferenceH hSRS,
   11472             :                                const char *pszCoordSys)
   11473             : 
   11474             : {
   11475           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromMICoordSys", OGRERR_FAILURE);
   11476             : 
   11477           3 :     return ToPointer(hSRS)->importFromMICoordSys(pszCoordSys);
   11478             : }
   11479             : 
   11480             : /************************************************************************/
   11481             : /*                        importFromMICoordSys()                        */
   11482             : /************************************************************************/
   11483             : 
   11484             : /**
   11485             :  * \brief Import Mapinfo style CoordSys definition.
   11486             :  *
   11487             :  * The OGRSpatialReference is initialized from the passed Mapinfo style CoordSys
   11488             :  * definition string.
   11489             :  *
   11490             :  * This method is the equivalent of the C function OSRImportFromMICoordSys().
   11491             :  *
   11492             :  * @param pszCoordSys Mapinfo style CoordSys definition string.
   11493             :  *
   11494             :  * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
   11495             :  * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
   11496             :  */
   11497             : 
   11498          17 : OGRErr OGRSpatialReference::importFromMICoordSys(const char *pszCoordSys)
   11499             : 
   11500             : {
   11501          17 :     OGRSpatialReference *poResult = MITABCoordSys2SpatialRef(pszCoordSys);
   11502             : 
   11503          17 :     if (poResult == nullptr)
   11504           0 :         return OGRERR_FAILURE;
   11505             : 
   11506          17 :     *this = *poResult;
   11507          17 :     delete poResult;
   11508             : 
   11509          17 :     return OGRERR_NONE;
   11510             : }
   11511             : 
   11512             : /************************************************************************/
   11513             : /*                        OSRCalcInvFlattening()                        */
   11514             : /************************************************************************/
   11515             : 
   11516             : /**
   11517             :  * \brief Compute inverse flattening from semi-major and semi-minor axis
   11518             :  *
   11519             :  * @param dfSemiMajor Semi-major axis length.
   11520             :  * @param dfSemiMinor Semi-minor axis length.
   11521             :  *
   11522             :  * @return inverse flattening, or 0 if both axis are equal.
   11523             :  */
   11524             : 
   11525        8919 : double OSRCalcInvFlattening(double dfSemiMajor, double dfSemiMinor)
   11526             : {
   11527        8919 :     if (fabs(dfSemiMajor - dfSemiMinor) < 1e-1)
   11528          27 :         return 0;
   11529        8892 :     if (dfSemiMajor <= 0 || dfSemiMinor <= 0 || dfSemiMinor > dfSemiMajor)
   11530             :     {
   11531           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11532             :                  "OSRCalcInvFlattening(): Wrong input values");
   11533           0 :         return 0;
   11534             :     }
   11535             : 
   11536        8892 :     return dfSemiMajor / (dfSemiMajor - dfSemiMinor);
   11537             : }
   11538             : 
   11539             : /************************************************************************/
   11540             : /*                        OSRCalcInvFlattening()                        */
   11541             : /************************************************************************/
   11542             : 
   11543             : /**
   11544             :  * \brief Compute semi-minor axis from semi-major axis and inverse flattening.
   11545             :  *
   11546             :  * @param dfSemiMajor Semi-major axis length.
   11547             :  * @param dfInvFlattening Inverse flattening or 0 for sphere.
   11548             :  *
   11549             :  * @return semi-minor axis
   11550             :  */
   11551             : 
   11552         655 : double OSRCalcSemiMinorFromInvFlattening(double dfSemiMajor,
   11553             :                                          double dfInvFlattening)
   11554             : {
   11555         655 :     if (fabs(dfInvFlattening) < 0.000000000001)
   11556         103 :         return dfSemiMajor;
   11557         552 :     if (dfSemiMajor <= 0.0 || dfInvFlattening <= 1.0)
   11558             :     {
   11559           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11560             :                  "OSRCalcSemiMinorFromInvFlattening(): Wrong input values");
   11561           0 :         return dfSemiMajor;
   11562             :     }
   11563             : 
   11564         552 :     return dfSemiMajor * (1.0 - 1.0 / dfInvFlattening);
   11565             : }
   11566             : 
   11567             : /************************************************************************/
   11568             : /*                            GetWGS84SRS()                             */
   11569             : /************************************************************************/
   11570             : 
   11571             : static OGRSpatialReference *poSRSWGS84 = nullptr;
   11572             : static CPLMutex *hMutex = nullptr;
   11573             : 
   11574             : /**
   11575             :  * \brief Returns an instance of a SRS object with WGS84 WKT.
   11576             :  *
   11577             :  * Note: the instance will have
   11578             :  * SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
   11579             :  *
   11580             :  * The reference counter of the returned object is not increased by this
   11581             :  * operation.
   11582             :  *
   11583             :  * @return instance.
   11584             :  */
   11585             : 
   11586        1004 : OGRSpatialReference *OGRSpatialReference::GetWGS84SRS()
   11587             : {
   11588        1004 :     CPLMutexHolderD(&hMutex);
   11589        1004 :     if (poSRSWGS84 == nullptr)
   11590             :     {
   11591           5 :         poSRSWGS84 = new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
   11592           5 :         poSRSWGS84->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
   11593             :     }
   11594        2008 :     return poSRSWGS84;
   11595             : }
   11596             : 
   11597             : /************************************************************************/
   11598             : /*                        CleanupSRSWGS84Mutex()                        */
   11599             : /************************************************************************/
   11600             : 
   11601        1308 : static void CleanupSRSWGS84Mutex()
   11602             : {
   11603        1308 :     if (hMutex != nullptr)
   11604             :     {
   11605           3 :         poSRSWGS84->Release();
   11606           3 :         poSRSWGS84 = nullptr;
   11607           3 :         CPLDestroyMutex(hMutex);
   11608           3 :         hMutex = nullptr;
   11609             :     }
   11610        1308 : }
   11611             : 
   11612             : /************************************************************************/
   11613             : /*                         OSRImportFromProj4()                         */
   11614             : /************************************************************************/
   11615             : /**
   11616             :  * \brief Import PROJ coordinate string.
   11617             :  *
   11618             :  * This function is the same as OGRSpatialReference::importFromProj4().
   11619             :  */
   11620         218 : OGRErr OSRImportFromProj4(OGRSpatialReferenceH hSRS, const char *pszProj4)
   11621             : 
   11622             : {
   11623         218 :     VALIDATE_POINTER1(hSRS, "OSRImportFromProj4", OGRERR_FAILURE);
   11624             : 
   11625         218 :     return OGRSpatialReference::FromHandle(hSRS)->importFromProj4(pszProj4);
   11626             : }
   11627             : 
   11628             : /************************************************************************/
   11629             : /*                          importFromProj4()                           */
   11630             : /************************************************************************/
   11631             : 
   11632             : /**
   11633             :  * \brief Import PROJ coordinate string.
   11634             :  *
   11635             :  * The OGRSpatialReference is initialized from the passed PROJs style
   11636             :  * coordinate system string.
   11637             :  *
   11638             :  * Example:
   11639             :  *   pszProj4 = "+proj=utm +zone=11 +datum=WGS84"
   11640             :  *
   11641             :  * It is also possible to import "+init=epsg:n" style definitions. Those are
   11642             :  * a legacy syntax that should be avoided in the future. In particular they will
   11643             :  * result in CRS objects whose axis order might not correspond to the official
   11644             :  * EPSG axis order.
   11645             :  *
   11646             :  * This method is the equivalent of the C function OSRImportFromProj4().
   11647             :  *
   11648             :  * @param pszProj4 the PROJ style string.
   11649             :  *
   11650             :  * @return OGRERR_NONE on success or OGRERR_CORRUPT_DATA on failure.
   11651             :  */
   11652             : 
   11653         777 : OGRErr OGRSpatialReference::importFromProj4(const char *pszProj4)
   11654             : 
   11655             : {
   11656        1554 :     TAKE_OPTIONAL_LOCK();
   11657             : 
   11658         777 :     if (strlen(pszProj4) >= 10000)
   11659             :     {
   11660           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Too long PROJ string");
   11661           1 :         return OGRERR_CORRUPT_DATA;
   11662             :     }
   11663             : 
   11664             :     /* -------------------------------------------------------------------- */
   11665             :     /*      Clear any existing definition.                                  */
   11666             :     /* -------------------------------------------------------------------- */
   11667         776 :     Clear();
   11668             : 
   11669         776 :     CPLString osProj4(pszProj4);
   11670         776 :     if (osProj4.find("type=crs") == std::string::npos)
   11671             :     {
   11672         764 :         osProj4 += " +type=crs";
   11673             :     }
   11674             : 
   11675         778 :     if (osProj4.find("+init=epsg:") != std::string::npos &&
   11676           2 :         getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr)
   11677             :     {
   11678           2 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11679             :                      "+init=epsg:XXXX syntax is deprecated. It might return "
   11680             :                      "a CRS with a non-EPSG compliant axis order.");
   11681             :     }
   11682         776 :     proj_context_use_proj4_init_rules(d->getPROJContext(), true);
   11683         776 :     d->setPjCRS(proj_create(d->getPROJContext(), osProj4.c_str()));
   11684         776 :     proj_context_use_proj4_init_rules(d->getPROJContext(), false);
   11685         776 :     return d->m_pj_crs ? OGRERR_NONE : OGRERR_CORRUPT_DATA;
   11686             : }
   11687             : 
   11688             : /************************************************************************/
   11689             : /*                          OSRExportToProj4()                          */
   11690             : /************************************************************************/
   11691             : /**
   11692             :  * \brief Export coordinate system in PROJ.4 legacy format.
   11693             :  *
   11694             :  * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
   11695             :  * PROJ &gt;= 6 is significantly different from earlier versions. In particular
   11696             :  * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
   11697             :  * will be missing most of the time. PROJ strings to encode CRS should be
   11698             :  * considered as a legacy solution. Using a AUTHORITY:CODE or WKT representation
   11699             :  * is the recommended way.
   11700             :  *
   11701             :  * This function is the same as OGRSpatialReference::exportToProj4().
   11702             :  */
   11703         324 : OGRErr CPL_STDCALL OSRExportToProj4(OGRSpatialReferenceH hSRS,
   11704             :                                     char **ppszReturn)
   11705             : 
   11706             : {
   11707         324 :     VALIDATE_POINTER1(hSRS, "OSRExportToProj4", OGRERR_FAILURE);
   11708             : 
   11709         324 :     *ppszReturn = nullptr;
   11710             : 
   11711         324 :     return OGRSpatialReference::FromHandle(hSRS)->exportToProj4(ppszReturn);
   11712             : }
   11713             : 
   11714             : /************************************************************************/
   11715             : /*                           exportToProj4()                            */
   11716             : /************************************************************************/
   11717             : 
   11718             : /**
   11719             :  * \brief Export coordinate system in PROJ.4 legacy format.
   11720             :  *
   11721             :  * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
   11722             :  * PROJ &gt;= 6 is significantly different from earlier versions. In particular
   11723             :  * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
   11724             :  * will be missing most of the time. PROJ strings to encode CRS should be
   11725             :  * considered as a a legacy solution. Using a AUTHORITY:CODE or WKT
   11726             :  * representation is the recommended way.
   11727             :  *
   11728             :  * Converts the loaded coordinate reference system into PROJ format
   11729             :  * to the extent possible.  The string returned in ppszProj4 should be
   11730             :  * deallocated by the caller with CPLFree() when no longer needed.
   11731             :  *
   11732             :  * LOCAL_CS coordinate systems are not translatable.  An empty string
   11733             :  * will be returned along with OGRERR_NONE.
   11734             :  *
   11735             :  * Special processing for Transverse Mercator:
   11736             :  * Starting with GDAL 3.0, if the OSR_USE_APPROX_TMERC configuration option is
   11737             :  * set to YES, the PROJ definition built from the SRS will use the +approx flag
   11738             :  * for the tmerc and utm projection methods, rather than the more accurate
   11739             :  * method.
   11740             :  *
   11741             :  * Starting with GDAL 3.0.3, this method will try to add a +towgs84 parameter,
   11742             :  * if there's none attached yet to the SRS and if the SRS has a EPSG code.
   11743             :  * See the AddGuessedTOWGS84() method for how this +towgs84 parameter may be
   11744             :  * added. This automatic addition may be disabled by setting the
   11745             :  * OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4 configuration option to NO.
   11746             :  *
   11747             :  * This method is the equivalent of the C function OSRExportToProj4().
   11748             :  *
   11749             :  * @param ppszProj4 pointer to which dynamically allocated PROJ definition
   11750             :  * will be assigned.
   11751             :  *
   11752             :  * @return OGRERR_NONE on success or an error code on failure.
   11753             :  */
   11754             : 
   11755        1469 : OGRErr OGRSpatialReference::exportToProj4(char **ppszProj4) const
   11756             : 
   11757             : {
   11758             :     // In the past calling this method was thread-safe, even if we never
   11759             :     // guaranteed it. Now proj_as_proj_string() will cache the result
   11760             :     // internally, so this is no longer thread-safe.
   11761        2938 :     std::lock_guard oLock(d->m_mutex);
   11762             : 
   11763        1469 :     d->refreshProjObj();
   11764        1469 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
   11765             :     {
   11766           4 :         *ppszProj4 = CPLStrdup("");
   11767           4 :         return OGRERR_FAILURE;
   11768             :     }
   11769             : 
   11770             :     // OSR_USE_ETMERC is here just for legacy
   11771        1465 :     bool bForceApproxTMerc = false;
   11772        1465 :     const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr);
   11773        1465 :     if (pszUseETMERC && pszUseETMERC[0])
   11774             :     {
   11775           0 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11776             :                      "OSR_USE_ETMERC is a legacy configuration option, which "
   11777             :                      "now has only effect when set to NO (YES is the default). "
   11778             :                      "Use OSR_USE_APPROX_TMERC=YES instead");
   11779           0 :         bForceApproxTMerc = !CPLTestBool(pszUseETMERC);
   11780             :     }
   11781             :     else
   11782             :     {
   11783             :         const char *pszUseApproxTMERC =
   11784        1465 :             CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr);
   11785        1465 :         if (pszUseApproxTMERC && pszUseApproxTMERC[0])
   11786             :         {
   11787           2 :             bForceApproxTMerc = CPLTestBool(pszUseApproxTMERC);
   11788             :         }
   11789             :     }
   11790        1465 :     const char *options[] = {
   11791        1465 :         bForceApproxTMerc ? "USE_APPROX_TMERC=YES" : nullptr, nullptr};
   11792             : 
   11793        1465 :     const char *projString = proj_as_proj_string(
   11794        1465 :         d->getPROJContext(), d->m_pj_crs, PJ_PROJ_4, options);
   11795             : 
   11796        1465 :     PJ *boundCRS = nullptr;
   11797        2926 :     if (projString &&
   11798        1461 :         (strstr(projString, "+datum=") == nullptr ||
   11799        2936 :          d->m_pjType == PJ_TYPE_COMPOUND_CRS) &&
   11800         544 :         CPLTestBool(
   11801             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4", "YES")))
   11802             :     {
   11803         544 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   11804         544 :             d->getPROJContext(), d->m_pj_crs, true,
   11805         544 :             strstr(projString, "+datum=") == nullptr);
   11806         544 :         if (boundCRS)
   11807             :         {
   11808         174 :             projString = proj_as_proj_string(d->getPROJContext(), boundCRS,
   11809             :                                              PJ_PROJ_4, options);
   11810             :         }
   11811             :     }
   11812             : 
   11813        1465 :     if (projString == nullptr)
   11814             :     {
   11815           4 :         *ppszProj4 = CPLStrdup("");
   11816           4 :         proj_destroy(boundCRS);
   11817           4 :         return OGRERR_FAILURE;
   11818             :     }
   11819        1461 :     *ppszProj4 = CPLStrdup(projString);
   11820        1461 :     proj_destroy(boundCRS);
   11821        1461 :     char *pszTypeCrs = strstr(*ppszProj4, " +type=crs");
   11822        1461 :     if (pszTypeCrs)
   11823        1461 :         *pszTypeCrs = '\0';
   11824        1461 :     return OGRERR_NONE;
   11825             : }
   11826             : 
   11827             : /************************************************************************/
   11828             : /*                            morphToESRI()                             */
   11829             : /************************************************************************/
   11830             : /**
   11831             :  * \brief Convert in place to ESRI WKT format.
   11832             :  *
   11833             :  * The value nodes of this coordinate system are modified in various manners
   11834             :  * more closely map onto the ESRI concept of WKT format.  This includes
   11835             :  * renaming a variety of projections and arguments, and stripping out
   11836             :  * nodes note recognised by ESRI (like AUTHORITY and AXIS).
   11837             :  *
   11838             :  * \note Since GDAL 3.0, this function has only user-visible effects at
   11839             :  * exportToWkt() time. It is recommended to use instead exportToWkt(char**,
   11840             :  * const char* const char*) const with options having FORMAT=WKT1_ESRI.
   11841             :  *
   11842             :  * This does the same as the C function OSRMorphToESRI().
   11843             :  *
   11844             :  * @return OGRERR_NONE unless something goes badly wrong.
   11845             :  * @deprecated
   11846             :  */
   11847             : 
   11848         236 : OGRErr OGRSpatialReference::morphToESRI()
   11849             : 
   11850             : {
   11851         236 :     TAKE_OPTIONAL_LOCK();
   11852             : 
   11853         236 :     d->refreshProjObj();
   11854         236 :     d->setMorphToESRI(true);
   11855             : 
   11856         472 :     return OGRERR_NONE;
   11857             : }
   11858             : 
   11859             : /************************************************************************/
   11860             : /*                           OSRMorphToESRI()                           */
   11861             : /************************************************************************/
   11862             : 
   11863             : /**
   11864             :  * \brief Convert in place to ESRI WKT format.
   11865             :  *
   11866             :  * This function is the same as the C++ method
   11867             :  * OGRSpatialReference::morphToESRI().
   11868             :  */
   11869          71 : OGRErr OSRMorphToESRI(OGRSpatialReferenceH hSRS)
   11870             : 
   11871             : {
   11872          71 :     VALIDATE_POINTER1(hSRS, "OSRMorphToESRI", OGRERR_FAILURE);
   11873             : 
   11874          71 :     return OGRSpatialReference::FromHandle(hSRS)->morphToESRI();
   11875             : }
   11876             : 
   11877             : /************************************************************************/
   11878             : /*                           morphFromESRI()                            */
   11879             : /************************************************************************/
   11880             : 
   11881             : /**
   11882             :  * \brief Convert in place from ESRI WKT format.
   11883             :  *
   11884             :  * The value notes of this coordinate system are modified in various manners
   11885             :  * to adhere more closely to the WKT standard.  This mostly involves
   11886             :  * translating a variety of ESRI names for projections, arguments and
   11887             :  * datums to "standard" names, as defined by Adam Gawne-Cain's reference
   11888             :  * translation of EPSG to WKT for the CT specification.
   11889             :  *
   11890             :  * \note Since GDAL 3.0, this function is essentially a no-operation, since
   11891             :  * morphing from ESRI is automatically done by importFromWkt(). Its only
   11892             :  * effect is to undo the effect of a potential prior call to morphToESRI().
   11893             :  *
   11894             :  * This does the same as the C function OSRMorphFromESRI().
   11895             :  *
   11896             :  * @return OGRERR_NONE unless something goes badly wrong.
   11897             :  * @deprecated
   11898             :  */
   11899             : 
   11900          21 : OGRErr OGRSpatialReference::morphFromESRI()
   11901             : 
   11902             : {
   11903          21 :     TAKE_OPTIONAL_LOCK();
   11904             : 
   11905          21 :     d->refreshProjObj();
   11906          21 :     d->setMorphToESRI(false);
   11907             : 
   11908          42 :     return OGRERR_NONE;
   11909             : }
   11910             : 
   11911             : /************************************************************************/
   11912             : /*                          OSRMorphFromESRI()                          */
   11913             : /************************************************************************/
   11914             : 
   11915             : /**
   11916             :  * \brief Convert in place from ESRI WKT format.
   11917             :  *
   11918             :  * This function is the same as the C++ method
   11919             :  * OGRSpatialReference::morphFromESRI().
   11920             :  */
   11921          20 : OGRErr OSRMorphFromESRI(OGRSpatialReferenceH hSRS)
   11922             : 
   11923             : {
   11924          20 :     VALIDATE_POINTER1(hSRS, "OSRMorphFromESRI", OGRERR_FAILURE);
   11925             : 
   11926          20 :     return OGRSpatialReference::FromHandle(hSRS)->morphFromESRI();
   11927             : }
   11928             : 
   11929             : /************************************************************************/
   11930             : /*                            FindMatches()                             */
   11931             : /************************************************************************/
   11932             : 
   11933             : /**
   11934             :  * \brief Try to identify a match between the passed SRS and a related SRS
   11935             :  * in a catalog.
   11936             :  *
   11937             :  * Matching may be partial, or may fail.
   11938             :  * Returned entries will be sorted by decreasing match confidence (first
   11939             :  * entry has the highest match confidence).
   11940             :  *
   11941             :  * The exact way matching is done may change in future versions. Starting with
   11942             :  * GDAL 3.0, it relies on PROJ' proj_identify() function.
   11943             :  *
   11944             :  * This method is the same as OSRFindMatches().
   11945             :  *
   11946             :  * @param papszOptions NULL terminated list of options or NULL
   11947             :  * @param pnEntries Output parameter. Number of values in the returned array.
   11948             :  * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
   11949             :  * will be allocated to an array of *pnEntries whose values between 0 and 100
   11950             :  * indicate the confidence in the match. 100 is the highest confidence level.
   11951             :  * The array must be freed with CPLFree().
   11952             :  *
   11953             :  * @return an array of SRS that match the passed SRS, or NULL. Must be freed
   11954             :  * with OSRFreeSRSArray()
   11955             :  *
   11956             :  *
   11957             :  * @see OGRSpatialReference::FindBestMatch()
   11958             :  */
   11959             : OGRSpatialReferenceH *
   11960        1564 : OGRSpatialReference::FindMatches(CSLConstList papszOptions, int *pnEntries,
   11961             :                                  int **ppanMatchConfidence) const
   11962             : {
   11963        3128 :     TAKE_OPTIONAL_LOCK();
   11964             : 
   11965        1564 :     CPL_IGNORE_RET_VAL(papszOptions);
   11966             : 
   11967        1564 :     if (pnEntries)
   11968        1564 :         *pnEntries = 0;
   11969        1564 :     if (ppanMatchConfidence)
   11970        1564 :         *ppanMatchConfidence = nullptr;
   11971             : 
   11972        1564 :     d->refreshProjObj();
   11973        1564 :     if (!d->m_pj_crs)
   11974           0 :         return nullptr;
   11975             : 
   11976        1564 :     int *panConfidence = nullptr;
   11977        1564 :     auto ctxt = d->getPROJContext();
   11978             :     auto list =
   11979        1564 :         proj_identify(ctxt, d->m_pj_crs, nullptr, nullptr, &panConfidence);
   11980        1564 :     if (!list)
   11981           0 :         return nullptr;
   11982             : 
   11983        1564 :     const int nMatches = proj_list_get_count(list);
   11984             : 
   11985        1564 :     if (pnEntries)
   11986        1564 :         *pnEntries = static_cast<int>(nMatches);
   11987             :     OGRSpatialReferenceH *pahRet = static_cast<OGRSpatialReferenceH *>(
   11988        1564 :         CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   11989        1564 :     if (ppanMatchConfidence)
   11990             :     {
   11991        1564 :         *ppanMatchConfidence =
   11992        1564 :             static_cast<int *>(CPLMalloc(sizeof(int) * (nMatches + 1)));
   11993             :     }
   11994             : 
   11995        1564 :     bool bSortAgain = false;
   11996             : 
   11997        4510 :     for (int i = 0; i < nMatches; i++)
   11998             :     {
   11999        2946 :         PJ *obj = proj_list_get(ctxt, list, i);
   12000        2946 :         CPLAssert(obj);
   12001        2946 :         OGRSpatialReference *poSRS = new OGRSpatialReference();
   12002        2946 :         poSRS->d->setPjCRS(obj);
   12003        2946 :         pahRet[i] = ToHandle(poSRS);
   12004             : 
   12005             :         // Identify matches that only differ by axis order
   12006           9 :         if (panConfidence[i] == 50 && GetAxesCount() == 2 &&
   12007        2964 :             poSRS->GetAxesCount() == 2 &&
   12008        2955 :             GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2})
   12009             :         {
   12010           9 :             OGRAxisOrientation eThisAxis0 = OAO_Other;
   12011           9 :             OGRAxisOrientation eThisAxis1 = OAO_Other;
   12012           9 :             OGRAxisOrientation eSRSAxis0 = OAO_Other;
   12013           9 :             OGRAxisOrientation eSRSAxis1 = OAO_Other;
   12014           9 :             GetAxis(nullptr, 0, &eThisAxis0);
   12015           9 :             GetAxis(nullptr, 1, &eThisAxis1);
   12016           9 :             poSRS->GetAxis(nullptr, 0, &eSRSAxis0);
   12017           9 :             poSRS->GetAxis(nullptr, 1, &eSRSAxis1);
   12018           9 :             if (eThisAxis0 == OAO_East && eThisAxis1 == OAO_North &&
   12019           9 :                 eSRSAxis0 == OAO_North && eSRSAxis1 == OAO_East)
   12020             :             {
   12021             :                 auto pj_crs_normalized =
   12022           9 :                     proj_normalize_for_visualization(ctxt, poSRS->d->m_pj_crs);
   12023           9 :                 if (pj_crs_normalized)
   12024             :                 {
   12025           9 :                     if (proj_is_equivalent_to(d->m_pj_crs, pj_crs_normalized,
   12026           9 :                                               PJ_COMP_EQUIVALENT))
   12027             :                     {
   12028           3 :                         bSortAgain = true;
   12029           3 :                         panConfidence[i] = 90;
   12030           3 :                         poSRS->SetDataAxisToSRSAxisMapping({2, 1});
   12031             :                     }
   12032           9 :                     proj_destroy(pj_crs_normalized);
   12033             :                 }
   12034             :             }
   12035             :         }
   12036             : 
   12037        2946 :         if (ppanMatchConfidence)
   12038        2946 :             (*ppanMatchConfidence)[i] = panConfidence[i];
   12039             :     }
   12040             : 
   12041        1564 :     if (bSortAgain)
   12042             :     {
   12043           3 :         std::vector<int> anIndices;
   12044          12 :         for (int i = 0; i < nMatches; ++i)
   12045           9 :             anIndices.push_back(i);
   12046             : 
   12047           3 :         std::stable_sort(anIndices.begin(), anIndices.end(),
   12048           9 :                          [&panConfidence](int i, int j)
   12049           9 :                          { return panConfidence[i] > panConfidence[j]; });
   12050             : 
   12051             :         OGRSpatialReferenceH *pahRetSorted =
   12052             :             static_cast<OGRSpatialReferenceH *>(
   12053           3 :                 CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   12054          12 :         for (int i = 0; i < nMatches; ++i)
   12055             :         {
   12056           9 :             pahRetSorted[i] = pahRet[anIndices[i]];
   12057           9 :             if (ppanMatchConfidence)
   12058           9 :                 (*ppanMatchConfidence)[i] = panConfidence[anIndices[i]];
   12059             :         }
   12060           3 :         CPLFree(pahRet);
   12061           3 :         pahRet = pahRetSorted;
   12062             :     }
   12063             : 
   12064        1564 :     pahRet[nMatches] = nullptr;
   12065        1564 :     proj_list_destroy(list);
   12066        1564 :     proj_int_list_destroy(panConfidence);
   12067             : 
   12068        1564 :     return pahRet;
   12069             : }
   12070             : 
   12071             : /************************************************************************/
   12072             : /*                          importFromEPSGA()                           */
   12073             : /************************************************************************/
   12074             : 
   12075             : /**
   12076             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12077             :  * code.
   12078             :  *
   12079             :  * This method will initialize the spatial reference based on the
   12080             :  * passed in EPSG CRS code found in the PROJ database.
   12081             :  *
   12082             :  * Since GDAL 3.0, this method is identical to importFromEPSG().
   12083             :  *
   12084             :  * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
   12085             :  * 7-parameter Helmert transformation to WGS84 when there is one and only one
   12086             :  * such method available for the CRS. This behavior might not always be
   12087             :  * desirable, so starting with GDAL 3.0.3, this is no longer done unless
   12088             :  * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
   12089             :  * The AddGuessedTOWGS84() method can also be used for that purpose.
   12090             :  *
   12091             :  * The method will also by default substitute a deprecated EPSG code by its
   12092             :  * non-deprecated replacement. If this behavior is not desired, the
   12093             :  * OSR_USE_NON_DEPRECATED configuration option can be set to NO.
   12094             :  *
   12095             :  * This method is the same as the C function OSRImportFromEPSGA().
   12096             :  *
   12097             :  * @param nCode a CRS code.
   12098             :  *
   12099             :  * @return OGRERR_NONE on success, or an error code on failure.
   12100             :  */
   12101             : 
   12102       49866 : OGRErr OGRSpatialReference::importFromEPSGA(int nCode)
   12103             : 
   12104             : {
   12105       99732 :     TAKE_OPTIONAL_LOCK();
   12106             : 
   12107       49866 :     Clear();
   12108             : 
   12109             :     const char *pszUseNonDeprecated =
   12110       49866 :         CPLGetConfigOption("OSR_USE_NON_DEPRECATED", nullptr);
   12111             :     const bool bUseNonDeprecated =
   12112       49866 :         CPLTestBool(pszUseNonDeprecated ? pszUseNonDeprecated : "YES");
   12113       49866 :     const bool bAddTOWGS84 = CPLTestBool(
   12114             :         CPLGetConfigOption("OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG", "NO"));
   12115       49866 :     auto tlsCache = OSRGetProjTLSCache();
   12116       49866 :     if (tlsCache)
   12117             :     {
   12118             :         auto cachedObj =
   12119       49866 :             tlsCache->GetPJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84);
   12120       49866 :         if (cachedObj)
   12121             :         {
   12122       39459 :             d->setPjCRS(cachedObj);
   12123       39459 :             return OGRERR_NONE;
   12124             :         }
   12125             :     }
   12126             : 
   12127       20814 :     CPLString osCode;
   12128       10407 :     osCode.Printf("%d", nCode);
   12129             :     PJ *obj;
   12130       10407 :     constexpr int FIRST_NON_DEPRECATED_ESRI_CODE = 53001;
   12131       10407 :     if (nCode < FIRST_NON_DEPRECATED_ESRI_CODE)
   12132             :     {
   12133       10402 :         obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   12134             :                                         osCode.c_str(), PJ_CATEGORY_CRS, true,
   12135             :                                         nullptr);
   12136       10402 :         if (!obj)
   12137             :         {
   12138          25 :             return OGRERR_FAILURE;
   12139             :         }
   12140             :     }
   12141             :     else
   12142             :     {
   12143             :         // Likely to be an ESRI CRS...
   12144           5 :         CPLErr eLastErrorType = CE_None;
   12145           5 :         CPLErrorNum eLastErrorNum = CPLE_None;
   12146           5 :         std::string osLastErrorMsg;
   12147           5 :         bool bIsESRI = false;
   12148             :         {
   12149          10 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
   12150           5 :             CPLErrorReset();
   12151           5 :             obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   12152             :                                             osCode.c_str(), PJ_CATEGORY_CRS,
   12153             :                                             true, nullptr);
   12154           5 :             if (!obj)
   12155             :             {
   12156           2 :                 eLastErrorType = CPLGetLastErrorType();
   12157           2 :                 eLastErrorNum = CPLGetLastErrorNo();
   12158           2 :                 osLastErrorMsg = CPLGetLastErrorMsg();
   12159           2 :                 obj = proj_create_from_database(d->getPROJContext(), "ESRI",
   12160             :                                                 osCode.c_str(), PJ_CATEGORY_CRS,
   12161             :                                                 true, nullptr);
   12162           2 :                 if (obj)
   12163           1 :                     bIsESRI = true;
   12164             :             }
   12165             :         }
   12166           5 :         if (!obj)
   12167             :         {
   12168           1 :             if (eLastErrorType != CE_None)
   12169           1 :                 CPLError(eLastErrorType, eLastErrorNum, "%s",
   12170             :                          osLastErrorMsg.c_str());
   12171           1 :             return OGRERR_FAILURE;
   12172             :         }
   12173           4 :         if (bIsESRI)
   12174             :         {
   12175           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12176             :                      "EPSG:%d is not a valid CRS code, but ESRI:%d is. "
   12177             :                      "Assuming ESRI:%d was meant",
   12178             :                      nCode, nCode, nCode);
   12179             :         }
   12180             :     }
   12181             : 
   12182       10381 :     if (bUseNonDeprecated && proj_is_deprecated(obj))
   12183             :     {
   12184         410 :         auto list = proj_get_non_deprecated(d->getPROJContext(), obj);
   12185         410 :         if (list)
   12186             :         {
   12187         410 :             const auto count = proj_list_get_count(list);
   12188         410 :             if (count == 1)
   12189             :             {
   12190             :                 auto nonDeprecated =
   12191         359 :                     proj_list_get(d->getPROJContext(), list, 0);
   12192         359 :                 if (nonDeprecated)
   12193             :                 {
   12194         359 :                     if (pszUseNonDeprecated == nullptr)
   12195             :                     {
   12196             :                         const char *pszNewAuth =
   12197         359 :                             proj_get_id_auth_name(nonDeprecated, 0);
   12198             :                         const char *pszNewCode =
   12199         359 :                             proj_get_id_code(nonDeprecated, 0);
   12200         359 :                         CPLError(CE_Warning, CPLE_AppDefined,
   12201             :                                  "CRS EPSG:%d is deprecated. "
   12202             :                                  "Its non-deprecated replacement %s:%s "
   12203             :                                  "will be used instead. "
   12204             :                                  "To use the original CRS, set the "
   12205             :                                  "OSR_USE_NON_DEPRECATED "
   12206             :                                  "configuration option to NO.",
   12207             :                                  nCode, pszNewAuth ? pszNewAuth : "(null)",
   12208             :                                  pszNewCode ? pszNewCode : "(null)");
   12209             :                     }
   12210         359 :                     proj_destroy(obj);
   12211         359 :                     obj = nonDeprecated;
   12212             :                 }
   12213             :             }
   12214             :         }
   12215         410 :         proj_list_destroy(list);
   12216             :     }
   12217             : 
   12218       10381 :     if (bAddTOWGS84)
   12219             :     {
   12220           1 :         auto boundCRS = proj_crs_create_bound_crs_to_WGS84(d->getPROJContext(),
   12221             :                                                            obj, nullptr);
   12222           1 :         if (boundCRS)
   12223             :         {
   12224           1 :             proj_destroy(obj);
   12225           1 :             obj = boundCRS;
   12226             :         }
   12227             :     }
   12228             : 
   12229       10381 :     d->setPjCRS(obj);
   12230             : 
   12231       10381 :     if (tlsCache)
   12232             :     {
   12233       10381 :         tlsCache->CachePJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84,
   12234             :                                      obj);
   12235             :     }
   12236             : 
   12237       10381 :     return OGRERR_NONE;
   12238             : }
   12239             : 
   12240             : /************************************************************************/
   12241             : /*                         AddGuessedTOWGS84()                          */
   12242             : /************************************************************************/
   12243             : 
   12244             : /**
   12245             :  * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
   12246             :  * to WGS84.
   12247             :  *
   12248             :  * This method try to attach a 3-parameter or 7-parameter Helmert transformation
   12249             :  * to WGS84 when there is one and only one such method available for the CRS.
   12250             :  * Note: this is more restrictive to how GDAL < 3 worked.
   12251             :  *
   12252             :  * This method is the same as the C function OSRAddGuessedTOWGS84().
   12253             :  *
   12254             :  * @return OGRERR_NONE on success, or an error code on failure (the CRS has
   12255             :  * already a transformation to WGS84 or none matching could be found).
   12256             :  *
   12257             :  * @since GDAL 3.0.3
   12258             :  */
   12259          18 : OGRErr OGRSpatialReference::AddGuessedTOWGS84()
   12260             : {
   12261          36 :     TAKE_OPTIONAL_LOCK();
   12262             : 
   12263          18 :     d->refreshProjObj();
   12264          18 :     if (!d->m_pj_crs)
   12265           0 :         return OGRERR_FAILURE;
   12266          18 :     auto boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   12267          18 :         d->getPROJContext(), d->m_pj_crs, false, true);
   12268          18 :     if (!boundCRS)
   12269             :     {
   12270           0 :         return OGRERR_FAILURE;
   12271             :     }
   12272          18 :     d->setPjCRS(boundCRS);
   12273          18 :     return OGRERR_NONE;
   12274             : }
   12275             : 
   12276             : /************************************************************************/
   12277             : /*                         OSRImportFromEPSGA()                         */
   12278             : /************************************************************************/
   12279             : 
   12280             : /**
   12281             :  * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
   12282             :  * to WGS84.
   12283             :  *
   12284             :  * This function is the same as OGRSpatialReference::AddGuessedTOWGS84().
   12285             :  *
   12286             :  * @since GDAL 3.0.3
   12287             :  */
   12288             : 
   12289           2 : OGRErr OSRAddGuessedTOWGS84(OGRSpatialReferenceH hSRS)
   12290             : 
   12291             : {
   12292           2 :     VALIDATE_POINTER1(hSRS, "OSRAddGuessedTOWGS84", OGRERR_FAILURE);
   12293             : 
   12294           2 :     return OGRSpatialReference::FromHandle(hSRS)->AddGuessedTOWGS84();
   12295             : }
   12296             : 
   12297             : /************************************************************************/
   12298             : /*                         OSRImportFromEPSGA()                         */
   12299             : /************************************************************************/
   12300             : 
   12301             : /**
   12302             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12303             :  * code.
   12304             :  *
   12305             :  * This function is the same as OGRSpatialReference::importFromEPSGA().
   12306             :  */
   12307             : 
   12308           3 : OGRErr CPL_STDCALL OSRImportFromEPSGA(OGRSpatialReferenceH hSRS, int nCode)
   12309             : 
   12310             : {
   12311           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSGA", OGRERR_FAILURE);
   12312             : 
   12313           3 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSGA(nCode);
   12314             : }
   12315             : 
   12316             : /************************************************************************/
   12317             : /*                           importFromEPSG()                           */
   12318             : /************************************************************************/
   12319             : 
   12320             : /**
   12321             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12322             :  * code.
   12323             :  *
   12324             :  * This method will initialize the spatial reference based on the
   12325             :  * passed in EPSG CRS code found in the PROJ database.
   12326             :  *
   12327             :  * This method is the same as the C function OSRImportFromEPSG().
   12328             :  *
   12329             :  * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
   12330             :  * 7-parameter Helmert transformation to WGS84 when there is one and only one
   12331             :  * such method available for the CRS. This behavior might not always be
   12332             :  * desirable, so starting with GDAL 3.0.3, this is no longer done unless
   12333             :  * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
   12334             :  *
   12335             :  * @param nCode a GCS or PCS code from the horizontal coordinate system table.
   12336             :  *
   12337             :  * @return OGRERR_NONE on success, or an error code on failure.
   12338             :  */
   12339             : 
   12340       43558 : OGRErr OGRSpatialReference::importFromEPSG(int nCode)
   12341             : 
   12342             : {
   12343       43558 :     return importFromEPSGA(nCode);
   12344             : }
   12345             : 
   12346             : /************************************************************************/
   12347             : /*                         OSRImportFromEPSG()                          */
   12348             : /************************************************************************/
   12349             : 
   12350             : /**
   12351             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12352             :  * code.
   12353             :  *
   12354             :  * This function is the same as OGRSpatialReference::importFromEPSG().
   12355             :  */
   12356             : 
   12357        1514 : OGRErr CPL_STDCALL OSRImportFromEPSG(OGRSpatialReferenceH hSRS, int nCode)
   12358             : 
   12359             : {
   12360        1514 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSG", OGRERR_FAILURE);
   12361             : 
   12362        1514 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSG(nCode);
   12363             : }
   12364             : 
   12365             : /************************************************************************/
   12366             : /*                        EPSGTreatsAsLatLong()                         */
   12367             : /************************************************************************/
   12368             : 
   12369             : /**
   12370             :  * \brief This method returns TRUE if this geographic coordinate
   12371             :  * system should be treated as having lat/long coordinate ordering.
   12372             :  *
   12373             :  * Currently this returns TRUE for all geographic coordinate systems
   12374             :  * with axes set defining it as lat, long (prior to GDAL 3.10, it
   12375             :  * also checked that the CRS had belonged to EPSG authority, but this check
   12376             :  * has now been removed).
   12377             :  *
   12378             :  * \note Important change of behavior since GDAL 3.0. In previous versions,
   12379             :  * geographic CRS imported with importFromEPSG() would cause this method to
   12380             :  * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
   12381             :  * is now equivalent to importFromEPSGA().
   12382             :  *
   12383             :  * FALSE will be returned for all coordinate systems that are not geographic,
   12384             :  * or whose axes ordering is not latitude, longitude.
   12385             :  *
   12386             :  * This method is the same as the C function OSREPSGTreatsAsLatLong().
   12387             :  *
   12388             :  * @return TRUE or FALSE.
   12389             :  */
   12390             : 
   12391        1295 : int OGRSpatialReference::EPSGTreatsAsLatLong() const
   12392             : 
   12393             : {
   12394        2590 :     TAKE_OPTIONAL_LOCK();
   12395             : 
   12396        1295 :     if (!IsGeographic())
   12397         863 :         return FALSE;
   12398             : 
   12399         432 :     d->demoteFromBoundCRS();
   12400             : 
   12401         432 :     bool ret = false;
   12402         432 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12403             :     {
   12404             :         auto horizCRS =
   12405          17 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
   12406          17 :         if (horizCRS)
   12407             :         {
   12408             :             auto cs =
   12409          17 :                 proj_crs_get_coordinate_system(d->getPROJContext(), horizCRS);
   12410          17 :             if (cs)
   12411             :             {
   12412          17 :                 const char *pszDirection = nullptr;
   12413          17 :                 if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12414             :                                           nullptr, &pszDirection, nullptr,
   12415          17 :                                           nullptr, nullptr, nullptr))
   12416             :                 {
   12417          17 :                     if (EQUAL(pszDirection, "north"))
   12418             :                     {
   12419          17 :                         ret = true;
   12420             :                     }
   12421             :                 }
   12422             : 
   12423          17 :                 proj_destroy(cs);
   12424             :             }
   12425             : 
   12426          17 :             proj_destroy(horizCRS);
   12427             :         }
   12428             :     }
   12429             :     else
   12430             :     {
   12431             :         auto cs =
   12432         415 :             proj_crs_get_coordinate_system(d->getPROJContext(), d->m_pj_crs);
   12433         415 :         if (cs)
   12434             :         {
   12435         415 :             const char *pszDirection = nullptr;
   12436         415 :             if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12437             :                                       nullptr, &pszDirection, nullptr, nullptr,
   12438         415 :                                       nullptr, nullptr))
   12439             :             {
   12440         415 :                 if (EQUAL(pszDirection, "north"))
   12441             :                 {
   12442         368 :                     ret = true;
   12443             :                 }
   12444             :             }
   12445             : 
   12446         415 :             proj_destroy(cs);
   12447             :         }
   12448             :     }
   12449         432 :     d->undoDemoteFromBoundCRS();
   12450             : 
   12451         432 :     return ret;
   12452             : }
   12453             : 
   12454             : /************************************************************************/
   12455             : /*                       OSREPSGTreatsAsLatLong()                       */
   12456             : /************************************************************************/
   12457             : 
   12458             : /**
   12459             :  * \brief This function returns TRUE if this geographic coordinate
   12460             :  * system should be treated as having lat/long coordinate ordering.
   12461             :  *
   12462             :  * This function is the same as OGRSpatialReference::OSREPSGTreatsAsLatLong().
   12463             :  */
   12464             : 
   12465         208 : int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH hSRS)
   12466             : 
   12467             : {
   12468         208 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsLatLong", OGRERR_FAILURE);
   12469             : 
   12470         208 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsLatLong();
   12471             : }
   12472             : 
   12473             : /************************************************************************/
   12474             : /*                    EPSGTreatsAsNorthingEasting()                     */
   12475             : /************************************************************************/
   12476             : 
   12477             : /**
   12478             :  * \brief This method returns TRUE if this projected coordinate
   12479             :  * system should be treated as having northing/easting coordinate ordering.
   12480             :  *
   12481             :  * Currently this returns TRUE for all projected coordinate systems
   12482             :  * with axes set defining it as northing, easting (prior to GDAL 3.10, it
   12483             :  * also checked that the CRS had belonged to EPSG authority, but this check
   12484             :  * has now been removed).
   12485             :  *
   12486             :  * \note Important change of behavior since GDAL 3.0. In previous versions,
   12487             :  * projected CRS with northing, easting axis order imported with
   12488             :  * importFromEPSG() would cause this method to
   12489             :  * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
   12490             :  * is now equivalent to importFromEPSGA().
   12491             :  *
   12492             :  * FALSE will be returned for all coordinate systems that are not projected,
   12493             :  * or whose axes ordering is not northing, easting.
   12494             :  *
   12495             :  * This method is the same as the C function EPSGTreatsAsNorthingEasting().
   12496             :  *
   12497             :  * @return TRUE or FALSE.
   12498             :  *
   12499             :  */
   12500             : 
   12501         950 : int OGRSpatialReference::EPSGTreatsAsNorthingEasting() const
   12502             : 
   12503             : {
   12504        1900 :     TAKE_OPTIONAL_LOCK();
   12505             : 
   12506         950 :     if (!IsProjected())
   12507          49 :         return FALSE;
   12508             : 
   12509         901 :     d->demoteFromBoundCRS();
   12510             :     PJ *projCRS;
   12511         901 :     const auto ctxt = d->getPROJContext();
   12512         901 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12513             :     {
   12514           4 :         projCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   12515           4 :         if (!projCRS || proj_get_type(projCRS) != PJ_TYPE_PROJECTED_CRS)
   12516             :         {
   12517           0 :             d->undoDemoteFromBoundCRS();
   12518           0 :             proj_destroy(projCRS);
   12519           0 :             return FALSE;
   12520             :         }
   12521             :     }
   12522             :     else
   12523             :     {
   12524         897 :         projCRS = proj_clone(ctxt, d->m_pj_crs);
   12525             :     }
   12526             : 
   12527         901 :     bool ret = false;
   12528         901 :     auto cs = proj_crs_get_coordinate_system(ctxt, projCRS);
   12529         901 :     proj_destroy(projCRS);
   12530         901 :     d->undoDemoteFromBoundCRS();
   12531             : 
   12532         901 :     if (cs)
   12533             :     {
   12534         901 :         ret = isNorthEastAxisOrder(ctxt, cs);
   12535         901 :         proj_destroy(cs);
   12536             :     }
   12537             : 
   12538         901 :     return ret;
   12539             : }
   12540             : 
   12541             : /************************************************************************/
   12542             : /*                   OSREPSGTreatsAsNorthingEasting()                   */
   12543             : /************************************************************************/
   12544             : 
   12545             : /**
   12546             :  * \brief This function returns TRUE if this projected coordinate
   12547             :  * system should be treated as having northing/easting coordinate ordering.
   12548             :  *
   12549             :  * This function is the same as
   12550             :  * OGRSpatialReference::EPSGTreatsAsNorthingEasting().
   12551             :  *
   12552             :  */
   12553             : 
   12554         215 : int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH hSRS)
   12555             : 
   12556             : {
   12557         215 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsNorthingEasting", OGRERR_FAILURE);
   12558             : 
   12559         215 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsNorthingEasting();
   12560             : }
   12561             : 
   12562             : /************************************************************************/
   12563             : /*                     ImportFromESRIWisconsinWKT()                     */
   12564             : /*                                                                      */
   12565             : /*      Search a ESRI State Plane WKT and import it.                    */
   12566             : /************************************************************************/
   12567             : 
   12568             : // This is only used by the HFA driver and somewhat dubious we really need that
   12569             : // Coming from an old ESRI merge
   12570             : 
   12571           1 : OGRErr OGRSpatialReference::ImportFromESRIWisconsinWKT(const char *prjName,
   12572             :                                                        double centralMeridian,
   12573             :                                                        double latOfOrigin,
   12574             :                                                        const char *unitsName,
   12575             :                                                        const char *crsName)
   12576             : {
   12577           2 :     TAKE_OPTIONAL_LOCK();
   12578             : 
   12579           1 :     if (centralMeridian < -93 || centralMeridian > -87)
   12580           0 :         return OGRERR_FAILURE;
   12581           1 :     if (latOfOrigin < 40 || latOfOrigin > 47)
   12582           0 :         return OGRERR_FAILURE;
   12583             : 
   12584             :     // If the CS name is known.
   12585           1 :     if (!prjName && !unitsName && crsName)
   12586             :     {
   12587           0 :         const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12588           0 :         PJ_OBJ_LIST *list = proj_create_from_name(
   12589             :             d->getPROJContext(), "ESRI", crsName, &type, 1, false, 1, nullptr);
   12590           0 :         if (list)
   12591             :         {
   12592           0 :             if (proj_list_get_count(list) == 1)
   12593             :             {
   12594           0 :                 auto crs = proj_list_get(d->getPROJContext(), list, 0);
   12595           0 :                 if (crs)
   12596             :                 {
   12597           0 :                     Clear();
   12598           0 :                     d->setPjCRS(crs);
   12599           0 :                     proj_list_destroy(list);
   12600           0 :                     return OGRERR_NONE;
   12601             :                 }
   12602             :             }
   12603           0 :             proj_list_destroy(list);
   12604             :         }
   12605           0 :         return OGRERR_FAILURE;
   12606             :     }
   12607             : 
   12608           1 :     if (prjName == nullptr || unitsName == nullptr)
   12609             :     {
   12610           0 :         return OGRERR_FAILURE;
   12611             :     }
   12612             : 
   12613           1 :     const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12614           1 :     PJ_OBJ_LIST *list = proj_create_from_name(d->getPROJContext(), "ESRI",
   12615             :                                               "NAD_1983_HARN_WISCRS_", &type, 1,
   12616             :                                               true, 0, nullptr);
   12617           1 :     if (list)
   12618             :     {
   12619           1 :         const auto listSize = proj_list_get_count(list);
   12620           8 :         for (int i = 0; i < listSize; i++)
   12621             :         {
   12622           8 :             auto crs = proj_list_get(d->getPROJContext(), list, i);
   12623           8 :             if (!crs)
   12624             :             {
   12625           7 :                 continue;
   12626             :             }
   12627             : 
   12628           8 :             auto conv = proj_crs_get_coordoperation(d->getPROJContext(), crs);
   12629           8 :             if (!conv)
   12630             :             {
   12631           0 :                 proj_destroy(crs);
   12632           0 :                 continue;
   12633             :             }
   12634           8 :             const char *pszMethodCode = nullptr;
   12635           8 :             proj_coordoperation_get_method_info(
   12636             :                 d->getPROJContext(), conv, nullptr, nullptr, &pszMethodCode);
   12637           8 :             const int nMethodCode = atoi(pszMethodCode ? pszMethodCode : "0");
   12638           8 :             if (!((EQUAL(prjName, SRS_PT_TRANSVERSE_MERCATOR) &&
   12639             :                    nMethodCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) ||
   12640           3 :                   (EQUAL(prjName, "Lambert_Conformal_Conic") &&
   12641             :                    nMethodCode ==
   12642             :                        EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)))
   12643             :             {
   12644           3 :                 proj_destroy(crs);
   12645           3 :                 proj_destroy(conv);
   12646           3 :                 continue;
   12647             :             }
   12648             : 
   12649             :             auto coordSys =
   12650           5 :                 proj_crs_get_coordinate_system(d->getPROJContext(), crs);
   12651           5 :             if (!coordSys)
   12652             :             {
   12653           0 :                 proj_destroy(crs);
   12654           0 :                 proj_destroy(conv);
   12655           0 :                 continue;
   12656             :             }
   12657             : 
   12658           5 :             double dfConvFactor = 0.0;
   12659           5 :             proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
   12660             :                                   nullptr, nullptr, &dfConvFactor, nullptr,
   12661             :                                   nullptr, nullptr);
   12662           5 :             proj_destroy(coordSys);
   12663             : 
   12664           6 :             if ((EQUAL(unitsName, "meters") && dfConvFactor != 1.0) ||
   12665           1 :                 (!EQUAL(unitsName, "meters") &&
   12666           0 :                  std::fabs(dfConvFactor - CPLAtof(SRS_UL_US_FOOT_CONV)) >
   12667             :                      1e-10))
   12668             :             {
   12669           4 :                 proj_destroy(crs);
   12670           4 :                 proj_destroy(conv);
   12671           4 :                 continue;
   12672             :             }
   12673             : 
   12674           1 :             int idx_lat = proj_coordoperation_get_param_index(
   12675             :                 d->getPROJContext(), conv,
   12676             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
   12677           1 :             double valueLat = -1000;
   12678           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lat,
   12679             :                                           nullptr, nullptr, nullptr, &valueLat,
   12680             :                                           nullptr, nullptr, nullptr, nullptr,
   12681             :                                           nullptr, nullptr);
   12682           1 :             int idx_lon = proj_coordoperation_get_param_index(
   12683             :                 d->getPROJContext(), conv,
   12684             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN);
   12685           1 :             double valueLong = -1000;
   12686           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lon,
   12687             :                                           nullptr, nullptr, nullptr, &valueLong,
   12688             :                                           nullptr, nullptr, nullptr, nullptr,
   12689             :                                           nullptr, nullptr);
   12690           1 :             if (std::fabs(centralMeridian - valueLong) <= 1e-10 &&
   12691           1 :                 std::fabs(latOfOrigin - valueLat) <= 1e-10)
   12692             :             {
   12693           1 :                 Clear();
   12694           1 :                 d->setPjCRS(crs);
   12695           1 :                 proj_list_destroy(list);
   12696           1 :                 proj_destroy(conv);
   12697           1 :                 return OGRERR_NONE;
   12698             :             }
   12699             : 
   12700           0 :             proj_destroy(crs);
   12701           0 :             proj_destroy(conv);
   12702             :         }
   12703           0 :         proj_list_destroy(list);
   12704             :     }
   12705             : 
   12706           0 :     return OGRERR_FAILURE;
   12707             : }
   12708             : 
   12709             : /************************************************************************/
   12710             : /*                       GetAxisMappingStrategy()                       */
   12711             : /************************************************************************/
   12712             : 
   12713             : /** \brief Return the data axis to CRS axis mapping strategy.
   12714             :  *
   12715             :  * <ul>
   12716             :  * <li>OAMS_TRADITIONAL_GIS_ORDER means that for geographic CRS with
   12717             :  *     lat/long order, the data will still be long/lat ordered. Similarly for
   12718             :  *     a projected CRS with northing/easting order, the data will still be
   12719             :  *     easting/northing ordered.
   12720             :  * <li>OAMS_AUTHORITY_COMPLIANT means that the data axis will be identical to
   12721             :  *     the CRS axis.
   12722             :  * <li>OAMS_CUSTOM means that the data axis are customly defined with
   12723             :  *     SetDataAxisToSRSAxisMapping()
   12724             :  * </ul>
   12725             :  * @return the data axis to CRS axis mapping strategy.
   12726             :  * @since GDAL 3.0
   12727             :  */
   12728          95 : OSRAxisMappingStrategy OGRSpatialReference::GetAxisMappingStrategy() const
   12729             : {
   12730          95 :     TAKE_OPTIONAL_LOCK();
   12731             : 
   12732         190 :     return d->m_axisMappingStrategy;
   12733             : }
   12734             : 
   12735             : /************************************************************************/
   12736             : /*                     OSRGetAxisMappingStrategy()                      */
   12737             : /************************************************************************/
   12738             : 
   12739             : /** \brief Return the data axis to CRS axis mapping strategy.
   12740             :  *
   12741             :  * See OGRSpatialReference::GetAxisMappingStrategy()
   12742             :  * @since GDAL 3.0
   12743             :  */
   12744          37 : OSRAxisMappingStrategy OSRGetAxisMappingStrategy(OGRSpatialReferenceH hSRS)
   12745             : {
   12746          37 :     VALIDATE_POINTER1(hSRS, "OSRGetAxisMappingStrategy", OAMS_CUSTOM);
   12747             : 
   12748          37 :     return OGRSpatialReference::FromHandle(hSRS)->GetAxisMappingStrategy();
   12749             : }
   12750             : 
   12751             : /************************************************************************/
   12752             : /*                       SetAxisMappingStrategy()                       */
   12753             : /************************************************************************/
   12754             : 
   12755             : /** \brief Set the data axis to CRS axis mapping strategy.
   12756             :  *
   12757             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
   12758             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
   12759             :  * later being the default value when the option is not set) to control the
   12760             :  * value of the data axis to CRS axis mapping strategy when a
   12761             :  * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
   12762             :  * override this default value.
   12763             :  *
   12764             :  * See OGRSpatialReference::GetAxisMappingStrategy()
   12765             :  * @since GDAL 3.0
   12766             :  */
   12767       95690 : void OGRSpatialReference::SetAxisMappingStrategy(
   12768             :     OSRAxisMappingStrategy strategy)
   12769             : {
   12770      191380 :     TAKE_OPTIONAL_LOCK();
   12771             : 
   12772       95690 :     d->m_axisMappingStrategy = strategy;
   12773       95690 :     d->refreshAxisMapping();
   12774       95690 : }
   12775             : 
   12776             : /************************************************************************/
   12777             : /*                     OSRSetAxisMappingStrategy()                      */
   12778             : /************************************************************************/
   12779             : 
   12780             : /** \brief Set the data axis to CRS axis mapping strategy.
   12781             :  *
   12782             :  * See OGRSpatialReference::SetAxisMappingStrategy()
   12783             :  * @since GDAL 3.0
   12784             :  */
   12785         814 : void OSRSetAxisMappingStrategy(OGRSpatialReferenceH hSRS,
   12786             :                                OSRAxisMappingStrategy strategy)
   12787             : {
   12788         814 :     VALIDATE_POINTER0(hSRS, "OSRSetAxisMappingStrategy");
   12789             : 
   12790         814 :     OGRSpatialReference::FromHandle(hSRS)->SetAxisMappingStrategy(strategy);
   12791             : }
   12792             : 
   12793             : /************************************************************************/
   12794             : /*                    GetDataAxisToSRSAxisMapping()                     */
   12795             : /************************************************************************/
   12796             : 
   12797             : /** \brief Return the data axis to SRS axis mapping.
   12798             :  *
   12799             :  * The number of elements of the vector will be the number of axis of the CRS.
   12800             :  * Values start at 1.
   12801             :  *
   12802             :  * If m = GetDataAxisToSRSAxisMapping(), then m[0] is the data axis number
   12803             :  * for the first axis of the CRS.
   12804             :  *
   12805             :  * @since GDAL 3.0
   12806             :  */
   12807    17398600 : const std::vector<int> &OGRSpatialReference::GetDataAxisToSRSAxisMapping() const
   12808             : {
   12809    17398600 :     TAKE_OPTIONAL_LOCK();
   12810             : 
   12811    34797200 :     return d->m_axisMapping;
   12812             : }
   12813             : 
   12814             : /************************************************************************/
   12815             : /*                   OSRGetDataAxisToSRSAxisMapping()                   */
   12816             : /************************************************************************/
   12817             : 
   12818             : /** \brief Return the data axis to SRS axis mapping.
   12819             :  *
   12820             :  * See OGRSpatialReference::GetDataAxisToSRSAxisMapping()
   12821             :  *
   12822             :  * @since GDAL 3.0
   12823             :  */
   12824         105 : const int *OSRGetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12825             :                                           int *pnCount)
   12826             : {
   12827         105 :     VALIDATE_POINTER1(hSRS, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12828         105 :     VALIDATE_POINTER1(pnCount, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12829             : 
   12830             :     const auto &v =
   12831         105 :         OGRSpatialReference::FromHandle(hSRS)->GetDataAxisToSRSAxisMapping();
   12832         105 :     *pnCount = static_cast<int>(v.size());
   12833         105 :     return v.data();
   12834             : }
   12835             : 
   12836             : /************************************************************************/
   12837             : /*                    SetDataAxisToSRSAxisMapping()                     */
   12838             : /************************************************************************/
   12839             : 
   12840             : /** \brief Set a custom data axis to CRS axis mapping.
   12841             :  *
   12842             :  * The number of elements of the mapping vector should be the number of axis
   12843             :  * of the CRS (as returned by GetAxesCount()) (although this method does not
   12844             :  * check that, beyond checking there are at least 2 elements, so that this
   12845             :  * method and setting the CRS can be done in any order).
   12846             :  * This is taken into account by OGRCoordinateTransformation to transform the
   12847             :  * order of coordinates to the order expected by the CRS before
   12848             :  * transformation, and back to the data order after transformation.
   12849             :  *
   12850             :  * The mapping[i] value (one based) represents the data axis number for the i(th)
   12851             :  * axis of the CRS. A negative value can also be used to ask for a sign
   12852             :  * reversal during coordinate transformation (to deal with northing vs southing,
   12853             :  * easting vs westing, heights vs depths).
   12854             :  *
   12855             :  * When used with OGRCoordinateTransformation,
   12856             :  * - the only valid values for mapping[0] (data axis number for the first axis
   12857             :  *   of the CRS) are 1, 2, -1, -2.
   12858             :  * - the only valid values for mapping[1] (data axis number for the second axis
   12859             :  *   of the CRS) are 1, 2, -1, -2.
   12860             :  *  - the only valid values mapping[2] are 3 or -3.
   12861             :  * Note: this method does not validate the values of mapping[].
   12862             :  *
   12863             :  * mapping=[2,1] typically expresses the inversion of axis between the data
   12864             :  * axis and the CRS axis for a 2D CRS.
   12865             :  *
   12866             :  * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
   12867             :  *
   12868             :  * This is the same as the C function OSRSetDataAxisToSRSAxisMapping().
   12869             :  *
   12870             :  * @param mapping The new data axis to CRS axis mapping.
   12871             :  *
   12872             :  * @since GDAL 3.0
   12873             :  * @see OGRSpatialReference::GetDataAxisToSRSAxisMapping()
   12874             :  */
   12875       10500 : OGRErr OGRSpatialReference::SetDataAxisToSRSAxisMapping(
   12876             :     const std::vector<int> &mapping)
   12877             : {
   12878       21000 :     TAKE_OPTIONAL_LOCK();
   12879             : 
   12880       10500 :     if (mapping.size() < 2)
   12881           0 :         return OGRERR_FAILURE;
   12882       10500 :     d->m_axisMappingStrategy = OAMS_CUSTOM;
   12883       10500 :     d->m_axisMapping = mapping;
   12884       10500 :     return OGRERR_NONE;
   12885             : }
   12886             : 
   12887             : /************************************************************************/
   12888             : /*                   OSRSetDataAxisToSRSAxisMapping()                   */
   12889             : /************************************************************************/
   12890             : 
   12891             : /** \brief Set a custom data axis to CRS axis mapping.
   12892             :  *
   12893             :  * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
   12894             :  *
   12895             :  * This is the same as the C++ method
   12896             :  * OGRSpatialReference::SetDataAxisToSRSAxisMapping()
   12897             :  *
   12898             :  * @since GDAL 3.1
   12899             :  */
   12900          15 : OGRErr OSRSetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12901             :                                       int nMappingSize, const int *panMapping)
   12902             : {
   12903          15 :     VALIDATE_POINTER1(hSRS, "OSRSetDataAxisToSRSAxisMapping", OGRERR_FAILURE);
   12904          15 :     VALIDATE_POINTER1(panMapping, "OSRSetDataAxisToSRSAxisMapping",
   12905             :                       OGRERR_FAILURE);
   12906             : 
   12907          15 :     if (nMappingSize < 0)
   12908           0 :         return OGRERR_FAILURE;
   12909             : 
   12910          30 :     std::vector<int> mapping(nMappingSize);
   12911          15 :     if (nMappingSize)
   12912          15 :         memcpy(&mapping[0], panMapping, nMappingSize * sizeof(int));
   12913          15 :     return OGRSpatialReference::FromHandle(hSRS)->SetDataAxisToSRSAxisMapping(
   12914          15 :         mapping);
   12915             : }
   12916             : 
   12917             : /************************************************************************/
   12918             : /*                            GetAreaOfUse()                            */
   12919             : /************************************************************************/
   12920             : 
   12921             : /** \brief Return the area of use of the CRS.
   12922             :  *
   12923             :  * This method is the same as the OSRGetAreaOfUse() function.
   12924             :  *
   12925             :  * @param pdfWestLongitudeDeg Pointer to a double to receive the western-most
   12926             :  * longitude, expressed in degree. Might be NULL. If the returned value is
   12927             :  * -1000, the bounding box is unknown.
   12928             :  * @param pdfSouthLatitudeDeg Pointer to a double to receive the southern-most
   12929             :  * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
   12930             :  * the bounding box is unknown.
   12931             :  * @param pdfEastLongitudeDeg Pointer to a double to receive the eastern-most
   12932             :  * longitude, expressed in degree. Might be NULL. If the returned value is
   12933             :  * -1000, the bounding box is unknown.
   12934             :  * @param pdfNorthLatitudeDeg Pointer to a double to receive the northern-most
   12935             :  * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
   12936             :  * the bounding box is unknown.
   12937             :  * @param ppszAreaName Pointer to a string to receive the name of the area of
   12938             :  * use. Might be NULL. Note that *ppszAreaName is short-lived and might be
   12939             :  * invalidated by further calls.
   12940             :  * @return true in case of success
   12941             :  * @since GDAL 3.0
   12942             :  */
   12943          82 : bool OGRSpatialReference::GetAreaOfUse(double *pdfWestLongitudeDeg,
   12944             :                                        double *pdfSouthLatitudeDeg,
   12945             :                                        double *pdfEastLongitudeDeg,
   12946             :                                        double *pdfNorthLatitudeDeg,
   12947             :                                        const char **ppszAreaName) const
   12948             : {
   12949         164 :     TAKE_OPTIONAL_LOCK();
   12950             : 
   12951          82 :     d->refreshProjObj();
   12952          82 :     if (!d->m_pj_crs)
   12953             :     {
   12954           0 :         return false;
   12955             :     }
   12956          82 :     d->demoteFromBoundCRS();
   12957          82 :     const char *pszAreaName = nullptr;
   12958          82 :     int bSuccess = proj_get_area_of_use(
   12959          82 :         d->getPROJContext(), d->m_pj_crs, pdfWestLongitudeDeg,
   12960             :         pdfSouthLatitudeDeg, pdfEastLongitudeDeg, pdfNorthLatitudeDeg,
   12961             :         &pszAreaName);
   12962          82 :     d->undoDemoteFromBoundCRS();
   12963          82 :     d->m_osAreaName = pszAreaName ? pszAreaName : "";
   12964          82 :     if (ppszAreaName)
   12965          28 :         *ppszAreaName = d->m_osAreaName.c_str();
   12966          82 :     return CPL_TO_BOOL(bSuccess);
   12967             : }
   12968             : 
   12969             : /************************************************************************/
   12970             : /*                            GetAreaOfUse()                            */
   12971             : /************************************************************************/
   12972             : 
   12973             : /** \brief Return the area of use of the CRS.
   12974             :  *
   12975             :  * This function is the same as the OGRSpatialReference::GetAreaOfUse() method.
   12976             :  *
   12977             :  * @since GDAL 3.0
   12978             :  */
   12979           1 : int OSRGetAreaOfUse(OGRSpatialReferenceH hSRS, double *pdfWestLongitudeDeg,
   12980             :                     double *pdfSouthLatitudeDeg, double *pdfEastLongitudeDeg,
   12981             :                     double *pdfNorthLatitudeDeg, const char **ppszAreaName)
   12982             : {
   12983           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAreaOfUse", FALSE);
   12984             : 
   12985           1 :     return OGRSpatialReference::FromHandle(hSRS)->GetAreaOfUse(
   12986             :         pdfWestLongitudeDeg, pdfSouthLatitudeDeg, pdfEastLongitudeDeg,
   12987           1 :         pdfNorthLatitudeDeg, ppszAreaName);
   12988             : }
   12989             : 
   12990             : /************************************************************************/
   12991             : /*                   OSRGetCRSInfoListFromDatabase()                    */
   12992             : /************************************************************************/
   12993             : 
   12994             : /** \brief Enumerate CRS objects from the database.
   12995             :  *
   12996             :  * The returned object is an array of OSRCRSInfo* pointers, whose last
   12997             :  * entry is NULL. This array should be freed with OSRDestroyCRSInfoList()
   12998             :  *
   12999             :  * @param pszAuthName Authority name, used to restrict the search.
   13000             :  * Or NULL for all authorities.
   13001             :  * @param params Additional criteria. Must be set to NULL for now.
   13002             :  * @param pnOutResultCount Output parameter pointing to an integer to receive
   13003             :  * the size of the result list. Might be NULL
   13004             :  * @return an array of OSRCRSInfo* pointers to be freed with
   13005             :  * OSRDestroyCRSInfoList(), or NULL in case of error.
   13006             :  *
   13007             :  * @since GDAL 3.0
   13008             :  */
   13009             : OSRCRSInfo **
   13010          28 : OSRGetCRSInfoListFromDatabase(const char *pszAuthName,
   13011             :                               CPL_UNUSED const OSRCRSListParameters *params,
   13012             :                               int *pnOutResultCount)
   13013             : {
   13014          28 :     int nResultCount = 0;
   13015          28 :     auto projList = proj_get_crs_info_list_from_database(
   13016             :         OSRGetProjTLSContext(), pszAuthName, nullptr, &nResultCount);
   13017          28 :     if (pnOutResultCount)
   13018          28 :         *pnOutResultCount = nResultCount;
   13019          28 :     if (!projList)
   13020             :     {
   13021           0 :         return nullptr;
   13022             :     }
   13023          28 :     auto res = new OSRCRSInfo *[nResultCount + 1];
   13024       92468 :     for (int i = 0; i < nResultCount; i++)
   13025             :     {
   13026       92440 :         res[i] = new OSRCRSInfo;
   13027      184880 :         res[i]->pszAuthName = projList[i]->auth_name
   13028       92440 :                                   ? CPLStrdup(projList[i]->auth_name)
   13029             :                                   : nullptr;
   13030       92440 :         res[i]->pszCode =
   13031       92440 :             projList[i]->code ? CPLStrdup(projList[i]->code) : nullptr;
   13032       92440 :         res[i]->pszName =
   13033       92440 :             projList[i]->name ? CPLStrdup(projList[i]->name) : nullptr;
   13034       92440 :         res[i]->eType = OSR_CRS_TYPE_OTHER;
   13035       92440 :         switch (projList[i]->type)
   13036             :         {
   13037        9272 :             case PJ_TYPE_GEOGRAPHIC_2D_CRS:
   13038        9272 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_2D;
   13039        9272 :                 break;
   13040        2864 :             case PJ_TYPE_GEOGRAPHIC_3D_CRS:
   13041        2864 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_3D;
   13042        2864 :                 break;
   13043        3200 :             case PJ_TYPE_GEOCENTRIC_CRS:
   13044        3200 :                 res[i]->eType = OSR_CRS_TYPE_GEOCENTRIC;
   13045        3200 :                 break;
   13046       70012 :             case PJ_TYPE_PROJECTED_CRS:
   13047       70012 :                 res[i]->eType = OSR_CRS_TYPE_PROJECTED;
   13048       70012 :                 break;
   13049        2860 :             case PJ_TYPE_VERTICAL_CRS:
   13050        2860 :                 res[i]->eType = OSR_CRS_TYPE_VERTICAL;
   13051        2860 :                 break;
   13052        4232 :             case PJ_TYPE_COMPOUND_CRS:
   13053        4232 :                 res[i]->eType = OSR_CRS_TYPE_COMPOUND;
   13054        4232 :                 break;
   13055           0 :             default:
   13056           0 :                 break;
   13057             :         }
   13058       92440 :         res[i]->bDeprecated = projList[i]->deprecated;
   13059       92440 :         res[i]->bBboxValid = projList[i]->bbox_valid;
   13060       92440 :         res[i]->dfWestLongitudeDeg = projList[i]->west_lon_degree;
   13061       92440 :         res[i]->dfSouthLatitudeDeg = projList[i]->south_lat_degree;
   13062       92440 :         res[i]->dfEastLongitudeDeg = projList[i]->east_lon_degree;
   13063       92440 :         res[i]->dfNorthLatitudeDeg = projList[i]->north_lat_degree;
   13064      184880 :         res[i]->pszAreaName = projList[i]->area_name
   13065       92440 :                                   ? CPLStrdup(projList[i]->area_name)
   13066             :                                   : nullptr;
   13067       92440 :         res[i]->pszProjectionMethod =
   13068       92440 :             projList[i]->projection_method_name
   13069       92440 :                 ? CPLStrdup(projList[i]->projection_method_name)
   13070             :                 : nullptr;
   13071             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
   13072             :         res[i]->pszCelestialBodyName =
   13073             :             projList[i]->celestial_body_name
   13074             :                 ? CPLStrdup(projList[i]->celestial_body_name)
   13075             :                 : nullptr;
   13076             : #else
   13077       92440 :         res[i]->pszCelestialBodyName =
   13078       92440 :             res[i]->pszAuthName && EQUAL(res[i]->pszAuthName, "EPSG")
   13079      184880 :                 ? CPLStrdup("Earth")
   13080             :                 : nullptr;
   13081             : #endif
   13082             :     }
   13083          28 :     res[nResultCount] = nullptr;
   13084          28 :     proj_crs_info_list_destroy(projList);
   13085          28 :     return res;
   13086             : }
   13087             : 
   13088             : /************************************************************************/
   13089             : /*                       OSRDestroyCRSInfoList()                        */
   13090             : /************************************************************************/
   13091             : 
   13092             : /** \brief Destroy the result returned by
   13093             :  * OSRGetCRSInfoListFromDatabase().
   13094             :  *
   13095             :  * @since GDAL 3.0
   13096             :  */
   13097          28 : void OSRDestroyCRSInfoList(OSRCRSInfo **list)
   13098             : {
   13099          28 :     if (list)
   13100             :     {
   13101       92468 :         for (int i = 0; list[i] != nullptr; i++)
   13102             :         {
   13103       92440 :             CPLFree(list[i]->pszAuthName);
   13104       92440 :             CPLFree(list[i]->pszCode);
   13105       92440 :             CPLFree(list[i]->pszName);
   13106       92440 :             CPLFree(list[i]->pszAreaName);
   13107       92440 :             CPLFree(list[i]->pszProjectionMethod);
   13108       92440 :             CPLFree(list[i]->pszCelestialBodyName);
   13109       92440 :             delete list[i];
   13110             :         }
   13111          28 :         delete[] list;
   13112             :     }
   13113          28 : }
   13114             : 
   13115             : /************************************************************************/
   13116             : /*                  OSRGetAuthorityListFromDatabase()                   */
   13117             : /************************************************************************/
   13118             : 
   13119             : /** \brief Return the list of CRS authorities used in the PROJ database.
   13120             :  *
   13121             :  * Such as "EPSG", "ESRI", "PROJ", "IGNF", "IAU_2015", etc.
   13122             :  *
   13123             :  * This is a direct mapping of https://proj.org/en/latest/development/reference/functions.html#c.proj_get_authorities_from_database
   13124             :  *
   13125             :  * @return nullptr in case of error, or a NULL terminated list of strings to
   13126             :  * free with CSLDestroy()
   13127             :  * @since GDAL 3.10
   13128             :  */
   13129           5 : char **OSRGetAuthorityListFromDatabase()
   13130             : {
   13131             :     PROJ_STRING_LIST list =
   13132           5 :         proj_get_authorities_from_database(OSRGetProjTLSContext());
   13133           5 :     if (!list)
   13134             :     {
   13135           0 :         return nullptr;
   13136             :     }
   13137           5 :     int count = 0;
   13138          30 :     while (list[count])
   13139          25 :         ++count;
   13140           5 :     char **res = static_cast<char **>(CPLCalloc(count + 1, sizeof(char *)));
   13141          30 :     for (int i = 0; i < count; ++i)
   13142          25 :         res[i] = CPLStrdup(list[i]);
   13143           5 :     proj_string_list_destroy(list);
   13144           5 :     return res;
   13145             : }
   13146             : 
   13147             : /************************************************************************/
   13148             : /*                 UpdateCoordinateSystemFromGeogCRS()                  */
   13149             : /************************************************************************/
   13150             : 
   13151             : /*! @cond Doxygen_Suppress */
   13152             : /** \brief Used by gt_wkt_srs.cpp to create projected 3D CRS. Internal use only
   13153             :  *
   13154             :  * @since GDAL 3.1
   13155             :  */
   13156           1 : void OGRSpatialReference::UpdateCoordinateSystemFromGeogCRS()
   13157             : {
   13158           1 :     TAKE_OPTIONAL_LOCK();
   13159             : 
   13160           1 :     d->refreshProjObj();
   13161           1 :     if (!d->m_pj_crs)
   13162           0 :         return;
   13163           1 :     if (d->m_pjType != PJ_TYPE_PROJECTED_CRS)
   13164           0 :         return;
   13165           1 :     if (GetAxesCount() == 3)
   13166           0 :         return;
   13167           1 :     auto ctxt = d->getPROJContext();
   13168           1 :     auto baseCRS = proj_crs_get_geodetic_crs(ctxt, d->m_pj_crs);
   13169           1 :     if (!baseCRS)
   13170           0 :         return;
   13171           1 :     auto baseCRSCS = proj_crs_get_coordinate_system(ctxt, baseCRS);
   13172           1 :     if (!baseCRSCS)
   13173             :     {
   13174           0 :         proj_destroy(baseCRS);
   13175           0 :         return;
   13176             :     }
   13177           1 :     if (proj_cs_get_axis_count(ctxt, baseCRSCS) != 3)
   13178             :     {
   13179           0 :         proj_destroy(baseCRSCS);
   13180           0 :         proj_destroy(baseCRS);
   13181           0 :         return;
   13182             :     }
   13183           1 :     auto projCS = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   13184           1 :     if (!projCS || proj_cs_get_axis_count(ctxt, projCS) != 2)
   13185             :     {
   13186           0 :         proj_destroy(baseCRSCS);
   13187           0 :         proj_destroy(baseCRS);
   13188           0 :         proj_destroy(projCS);
   13189           0 :         return;
   13190             :     }
   13191             : 
   13192             :     PJ_AXIS_DESCRIPTION axis[3];
   13193           4 :     for (int i = 0; i < 3; i++)
   13194             :     {
   13195           3 :         const char *name = nullptr;
   13196           3 :         const char *abbreviation = nullptr;
   13197           3 :         const char *direction = nullptr;
   13198           3 :         double unit_conv_factor = 0;
   13199           3 :         const char *unit_name = nullptr;
   13200           3 :         proj_cs_get_axis_info(ctxt, i < 2 ? projCS : baseCRSCS, i, &name,
   13201             :                               &abbreviation, &direction, &unit_conv_factor,
   13202             :                               &unit_name, nullptr, nullptr);
   13203           3 :         axis[i].name = CPLStrdup(name);
   13204           3 :         axis[i].abbreviation = CPLStrdup(abbreviation);
   13205           3 :         axis[i].direction = CPLStrdup(direction);
   13206           3 :         axis[i].unit_name = CPLStrdup(unit_name);
   13207           3 :         axis[i].unit_conv_factor = unit_conv_factor;
   13208           3 :         axis[i].unit_type = PJ_UT_LINEAR;
   13209             :     }
   13210           1 :     proj_destroy(baseCRSCS);
   13211           1 :     proj_destroy(projCS);
   13212           1 :     auto cs = proj_create_cs(ctxt, PJ_CS_TYPE_CARTESIAN, 3, axis);
   13213           4 :     for (int i = 0; i < 3; i++)
   13214             :     {
   13215           3 :         CPLFree(axis[i].name);
   13216           3 :         CPLFree(axis[i].abbreviation);
   13217           3 :         CPLFree(axis[i].direction);
   13218           3 :         CPLFree(axis[i].unit_name);
   13219             :     }
   13220           1 :     if (!cs)
   13221             :     {
   13222           0 :         proj_destroy(baseCRS);
   13223           0 :         return;
   13224             :     }
   13225           1 :     auto conversion = proj_crs_get_coordoperation(ctxt, d->m_pj_crs);
   13226           1 :     auto crs = proj_create_projected_crs(ctxt, d->getProjCRSName(), baseCRS,
   13227             :                                          conversion, cs);
   13228           1 :     proj_destroy(baseCRS);
   13229           1 :     proj_destroy(conversion);
   13230           1 :     proj_destroy(cs);
   13231           1 :     d->setPjCRS(crs);
   13232             : }
   13233             : 
   13234             : /*! @endcond */
   13235             : 
   13236             : /************************************************************************/
   13237             : /*                            PromoteTo3D()                             */
   13238             : /************************************************************************/
   13239             : 
   13240             : /** \brief "Promotes" a 2D CRS to a 3D CRS one.
   13241             :  *
   13242             :  * The new axis will be ellipsoidal height, oriented upwards, and with metre
   13243             :  * units.
   13244             :  *
   13245             :  * @param pszName New name for the CRS. If set to NULL, the previous name will
   13246             :  * be used.
   13247             :  * @return OGRERR_NONE if no error occurred.
   13248             :  * @since GDAL 3.1 and PROJ 6.3
   13249             :  */
   13250          44 : OGRErr OGRSpatialReference::PromoteTo3D(const char *pszName)
   13251             : {
   13252          88 :     TAKE_OPTIONAL_LOCK();
   13253             : 
   13254          44 :     d->refreshProjObj();
   13255          44 :     if (!d->m_pj_crs)
   13256           0 :         return OGRERR_FAILURE;
   13257             :     auto newPj =
   13258          44 :         proj_crs_promote_to_3D(d->getPROJContext(), pszName, d->m_pj_crs);
   13259          44 :     if (!newPj)
   13260           0 :         return OGRERR_FAILURE;
   13261          44 :     d->setPjCRS(newPj);
   13262          44 :     return OGRERR_NONE;
   13263             : }
   13264             : 
   13265             : /************************************************************************/
   13266             : /*                           OSRPromoteTo3D()                           */
   13267             : /************************************************************************/
   13268             : 
   13269             : /** \brief "Promotes" a 2D CRS to a 3D CRS one.
   13270             :  *
   13271             :  * See OGRSpatialReference::PromoteTo3D()
   13272             :  *
   13273             :  * @since GDAL 3.1 and PROJ 6.3
   13274             :  */
   13275           3 : OGRErr OSRPromoteTo3D(OGRSpatialReferenceH hSRS, const char *pszName)
   13276             : {
   13277           3 :     VALIDATE_POINTER1(hSRS, "OSRPromoteTo3D", OGRERR_FAILURE);
   13278             : 
   13279           3 :     return OGRSpatialReference::FromHandle(hSRS)->PromoteTo3D(pszName);
   13280             : }
   13281             : 
   13282             : /************************************************************************/
   13283             : /*                             DemoteTo2D()                             */
   13284             : /************************************************************************/
   13285             : 
   13286             : /** \brief "Demote" a 3D CRS to a 2D CRS one.
   13287             :  *
   13288             :  * @param pszName New name for the CRS. If set to NULL, the previous name will
   13289             :  * be used.
   13290             :  * @return OGRERR_NONE if no error occurred.
   13291             :  * @since GDAL 3.2 and PROJ 6.3
   13292             :  */
   13293          49 : OGRErr OGRSpatialReference::DemoteTo2D(const char *pszName)
   13294             : {
   13295          98 :     TAKE_OPTIONAL_LOCK();
   13296             : 
   13297          49 :     d->refreshProjObj();
   13298          49 :     if (!d->m_pj_crs)
   13299           0 :         return OGRERR_FAILURE;
   13300             :     auto newPj =
   13301          49 :         proj_crs_demote_to_2D(d->getPROJContext(), pszName, d->m_pj_crs);
   13302          49 :     if (!newPj)
   13303           0 :         return OGRERR_FAILURE;
   13304          49 :     d->setPjCRS(newPj);
   13305          49 :     return OGRERR_NONE;
   13306             : }
   13307             : 
   13308             : /************************************************************************/
   13309             : /*                           OSRDemoteTo2D()                            */
   13310             : /************************************************************************/
   13311             : 
   13312             : /** \brief "Demote" a 3D CRS to a 2D CRS one.
   13313             :  *
   13314             :  * See OGRSpatialReference::DemoteTo2D()
   13315             :  *
   13316             :  * @since GDAL 3.2 and PROJ 6.3
   13317             :  */
   13318           1 : OGRErr OSRDemoteTo2D(OGRSpatialReferenceH hSRS, const char *pszName)
   13319             : {
   13320           1 :     VALIDATE_POINTER1(hSRS, "OSRDemoteTo2D", OGRERR_FAILURE);
   13321             : 
   13322           1 :     return OGRSpatialReference::FromHandle(hSRS)->DemoteTo2D(pszName);
   13323             : }
   13324             : 
   13325             : /************************************************************************/
   13326             : /*                           GetEPSGGeogCS()                            */
   13327             : /************************************************************************/
   13328             : 
   13329             : /** Try to establish what the EPSG code for this coordinate systems
   13330             :  * GEOGCS might be.  Returns -1 if no reasonable guess can be made.
   13331             :  *
   13332             :  * @return EPSG code
   13333             :  */
   13334             : 
   13335         346 : int OGRSpatialReference::GetEPSGGeogCS() const
   13336             : 
   13337             : {
   13338         692 :     TAKE_OPTIONAL_LOCK();
   13339             : 
   13340             :     /* -------------------------------------------------------------------- */
   13341             :     /*      Check axis order.                                               */
   13342             :     /* -------------------------------------------------------------------- */
   13343         692 :     auto poGeogCRS = std::unique_ptr<OGRSpatialReference>(CloneGeogCS());
   13344         346 :     if (!poGeogCRS)
   13345           0 :         return -1;
   13346             : 
   13347         346 :     bool ret = false;
   13348         346 :     poGeogCRS->d->demoteFromBoundCRS();
   13349         346 :     auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   13350         346 :                                              poGeogCRS->d->m_pj_crs);
   13351         346 :     poGeogCRS->d->undoDemoteFromBoundCRS();
   13352         346 :     if (cs)
   13353             :     {
   13354         346 :         const char *pszDirection = nullptr;
   13355         346 :         if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr, nullptr,
   13356             :                                   &pszDirection, nullptr, nullptr, nullptr,
   13357         346 :                                   nullptr))
   13358             :         {
   13359         346 :             if (EQUAL(pszDirection, "north"))
   13360             :             {
   13361         144 :                 ret = true;
   13362             :             }
   13363             :         }
   13364             : 
   13365         346 :         proj_destroy(cs);
   13366             :     }
   13367         346 :     if (!ret)
   13368         202 :         return -1;
   13369             : 
   13370             :     /* -------------------------------------------------------------------- */
   13371             :     /*      Do we already have it?                                          */
   13372             :     /* -------------------------------------------------------------------- */
   13373         144 :     const char *pszAuthName = GetAuthorityName("GEOGCS");
   13374         144 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg"))
   13375          67 :         return atoi(GetAuthorityCode("GEOGCS"));
   13376             : 
   13377             :     /* -------------------------------------------------------------------- */
   13378             :     /*      Get the datum and geogcs names.                                 */
   13379             :     /* -------------------------------------------------------------------- */
   13380             : 
   13381          77 :     const char *pszGEOGCS = GetAttrValue("GEOGCS");
   13382          77 :     const char *pszDatum = GetAttrValue("DATUM");
   13383             : 
   13384             :     // We can only operate on coordinate systems with a geogcs.
   13385         154 :     OGRSpatialReference oSRSTmp;
   13386          77 :     if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13387             :     {
   13388             :         // Calling GetAttrValue("GEOGCS") will fail on a CRS that can't be
   13389             :         // export to WKT1, so try to extract the geographic CRS through PROJ
   13390             :         // API with CopyGeogCSFrom() and get the nodes' values from it.
   13391           1 :         oSRSTmp.CopyGeogCSFrom(this);
   13392           1 :         pszGEOGCS = oSRSTmp.GetAttrValue("GEOGCS");
   13393           1 :         pszDatum = oSRSTmp.GetAttrValue("DATUM");
   13394           1 :         if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13395             :         {
   13396           0 :             return -1;
   13397             :         }
   13398             :     }
   13399             : 
   13400             :     // Lookup geographic CRS name
   13401          77 :     const PJ_TYPE type = PJ_TYPE_GEOGRAPHIC_2D_CRS;
   13402          77 :     PJ_OBJ_LIST *list = proj_create_from_name(
   13403             :         d->getPROJContext(), nullptr, pszGEOGCS, &type, 1, false, 1, nullptr);
   13404          77 :     if (list)
   13405             :     {
   13406          77 :         const auto listSize = proj_list_get_count(list);
   13407          77 :         if (listSize == 1)
   13408             :         {
   13409          49 :             auto crs = proj_list_get(d->getPROJContext(), list, 0);
   13410          49 :             if (crs)
   13411             :             {
   13412          49 :                 pszAuthName = proj_get_id_auth_name(crs, 0);
   13413          49 :                 const char *pszCode = proj_get_id_code(crs, 0);
   13414          49 :                 if (pszAuthName && pszCode && EQUAL(pszAuthName, "EPSG"))
   13415             :                 {
   13416          47 :                     const int nCode = atoi(pszCode);
   13417          47 :                     proj_destroy(crs);
   13418          47 :                     proj_list_destroy(list);
   13419          47 :                     return nCode;
   13420             :                 }
   13421           2 :                 proj_destroy(crs);
   13422             :             }
   13423             :         }
   13424          30 :         proj_list_destroy(list);
   13425             :     }
   13426             : 
   13427             :     /* -------------------------------------------------------------------- */
   13428             :     /*      Is this a "well known" geographic coordinate system?            */
   13429             :     /* -------------------------------------------------------------------- */
   13430          90 :     const bool bWGS = strstr(pszGEOGCS, "WGS") != nullptr ||
   13431          30 :                       strstr(pszDatum, "WGS") ||
   13432          30 :                       strstr(pszGEOGCS, "World Geodetic System") ||
   13433          30 :                       strstr(pszGEOGCS, "World_Geodetic_System") ||
   13434          90 :                       strstr(pszDatum, "World Geodetic System") ||
   13435          30 :                       strstr(pszDatum, "World_Geodetic_System");
   13436             : 
   13437          90 :     const bool bNAD = strstr(pszGEOGCS, "NAD") != nullptr ||
   13438          30 :                       strstr(pszDatum, "NAD") ||
   13439          30 :                       strstr(pszGEOGCS, "North American") ||
   13440          30 :                       strstr(pszGEOGCS, "North_American") ||
   13441          90 :                       strstr(pszDatum, "North American") ||
   13442          30 :                       strstr(pszDatum, "North_American");
   13443             : 
   13444          30 :     if (bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")))
   13445           0 :         return 4326;
   13446             : 
   13447          30 :     if (bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")))
   13448           0 :         return 4322;
   13449             : 
   13450             :     // This is questionable as there are several 'flavors' of NAD83 that
   13451             :     // are not the same as 4269
   13452          30 :     if (bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")))
   13453           0 :         return 4269;
   13454             : 
   13455          30 :     if (bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")))
   13456           0 :         return 4267;
   13457             : 
   13458             :     /* -------------------------------------------------------------------- */
   13459             :     /*      If we know the datum, associate the most likely GCS with        */
   13460             :     /*      it.                                                             */
   13461             :     /* -------------------------------------------------------------------- */
   13462          30 :     const OGRSpatialReference &oActiveObj = oSRSTmp.IsEmpty() ? *this : oSRSTmp;
   13463          30 :     pszAuthName = oActiveObj.GetAuthorityName("GEOGCS|DATUM");
   13464          30 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") &&
   13465           0 :         GetPrimeMeridian() == 0.0)
   13466             :     {
   13467           0 :         const int nDatum = atoi(oActiveObj.GetAuthorityCode("GEOGCS|DATUM"));
   13468             : 
   13469           0 :         if (nDatum >= 6000 && nDatum <= 6999)
   13470           0 :             return nDatum - 2000;
   13471             :     }
   13472             : 
   13473          30 :     return -1;
   13474             : }
   13475             : 
   13476             : /************************************************************************/
   13477             : /*                         SetCoordinateEpoch()                         */
   13478             : /************************************************************************/
   13479             : 
   13480             : /** Set the coordinate epoch, as decimal year.
   13481             :  *
   13482             :  * In a dynamic CRS, coordinates of a point on the surface of the Earth may
   13483             :  * change with time. To be unambiguous the coordinates must always be qualified
   13484             :  * with the epoch at which they are valid. The coordinate epoch is not
   13485             :  * necessarily the epoch at which the observation was collected.
   13486             :  *
   13487             :  * Pedantically the coordinate epoch of an observation belongs to the
   13488             :  * observation, and not to the CRS, however it is often more practical to
   13489             :  * bind it to the CRS. The coordinate epoch should be specified for dynamic
   13490             :  * CRS (see IsDynamic())
   13491             :  *
   13492             :  * This method is the same as the OSRSetCoordinateEpoch() function.
   13493             :  *
   13494             :  * @param dfCoordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3)
   13495             :  * @since OGR 3.4
   13496             :  */
   13497             : 
   13498         958 : void OGRSpatialReference::SetCoordinateEpoch(double dfCoordinateEpoch)
   13499             : {
   13500         958 :     d->m_coordinateEpoch = dfCoordinateEpoch;
   13501         958 : }
   13502             : 
   13503             : /************************************************************************/
   13504             : /*                       OSRSetCoordinateEpoch()                        */
   13505             : /************************************************************************/
   13506             : 
   13507             : /** \brief Set the coordinate epoch, as decimal year.
   13508             :  *
   13509             :  * See OGRSpatialReference::SetCoordinateEpoch()
   13510             :  *
   13511             :  * @since OGR 3.4
   13512             :  */
   13513          31 : void OSRSetCoordinateEpoch(OGRSpatialReferenceH hSRS, double dfCoordinateEpoch)
   13514             : {
   13515          31 :     VALIDATE_POINTER0(hSRS, "OSRSetCoordinateEpoch");
   13516             : 
   13517          31 :     return OGRSpatialReference::FromHandle(hSRS)->SetCoordinateEpoch(
   13518          31 :         dfCoordinateEpoch);
   13519             : }
   13520             : 
   13521             : /************************************************************************/
   13522             : /*                         GetCoordinateEpoch()                         */
   13523             : /************************************************************************/
   13524             : 
   13525             : /** Return the coordinate epoch, as decimal year.
   13526             :  *
   13527             :  * In a dynamic CRS, coordinates of a point on the surface of the Earth may
   13528             :  * change with time. To be unambiguous the coordinates must always be qualified
   13529             :  * with the epoch at which they are valid. The coordinate epoch is not
   13530             :  * necessarily the epoch at which the observation was collected.
   13531             :  *
   13532             :  * Pedantically the coordinate epoch of an observation belongs to the
   13533             :  * observation, and not to the CRS, however it is often more practical to
   13534             :  * bind it to the CRS. The coordinate epoch should be specified for dynamic
   13535             :  * CRS (see IsDynamic())
   13536             :  *
   13537             :  * This method is the same as the OSRGetCoordinateEpoch() function.
   13538             :  *
   13539             :  * @return coordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3), or 0
   13540             :  *                         if not set, or relevant.
   13541             :  * @since OGR 3.4
   13542             :  */
   13543             : 
   13544       15126 : double OGRSpatialReference::GetCoordinateEpoch() const
   13545             : {
   13546       15126 :     return d->m_coordinateEpoch;
   13547             : }
   13548             : 
   13549             : /************************************************************************/
   13550             : /*                       OSRGetCoordinateEpoch()                        */
   13551             : /************************************************************************/
   13552             : 
   13553             : /** \brief Get the coordinate epoch, as decimal year.
   13554             :  *
   13555             :  * See OGRSpatialReference::GetCoordinateEpoch()
   13556             :  *
   13557             :  * @since OGR 3.4
   13558             :  */
   13559         609 : double OSRGetCoordinateEpoch(OGRSpatialReferenceH hSRS)
   13560             : {
   13561         609 :     VALIDATE_POINTER1(hSRS, "OSRGetCoordinateEpoch", 0);
   13562             : 
   13563         609 :     return OGRSpatialReference::FromHandle(hSRS)->GetCoordinateEpoch();
   13564             : }

Generated by: LCOV version 1.14