LCOV - code coverage report
Current view: top level - ogr - ogrspatialreference.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 3461 4234 81.7 %
Date: 2025-09-10 17:48:50 Functions: 299 375 79.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  The OGRSpatialReference class.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 1999,  Les Technologies SoftMap Inc.
       9             :  * Copyright (c) 2008-2018, Even Rouault <even.rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "ogr_spatialref.h"
      16             : 
      17             : #include <cmath>
      18             : #include <cstddef>
      19             : #include <cstdio>
      20             : #include <cstdlib>
      21             : #include <cstring>
      22             : #include <limits>
      23             : #include <string>
      24             : #include <mutex>
      25             : #include <set>
      26             : #include <vector>
      27             : 
      28             : #include "cpl_atomic_ops.h"
      29             : #include "cpl_conv.h"
      30             : #include "cpl_csv.h"
      31             : #include "cpl_error.h"
      32             : #include "cpl_error_internal.h"
      33             : #include "cpl_http.h"
      34             : #include "cpl_json.h"
      35             : #include "cpl_multiproc.h"
      36             : #include "cpl_string.h"
      37             : #include "cpl_vsi.h"
      38             : #include "ogr_core.h"
      39             : #include "ogr_p.h"
      40             : #include "ogr_proj_p.h"
      41             : #include "ogr_srs_api.h"
      42             : #include "ogrmitabspatialref.h"
      43             : 
      44             : #include "proj.h"
      45             : #include "proj_experimental.h"
      46             : #include "proj_constants.h"
      47             : 
      48             : bool GDALThreadLocalDatasetCacheIsInDestruction();
      49             : 
      50             : // Exists since 8.0.1
      51             : #ifndef PROJ_AT_LEAST_VERSION
      52             : #define PROJ_COMPUTE_VERSION(maj, min, patch)                                  \
      53             :     ((maj)*10000 + (min)*100 + (patch))
      54             : #define PROJ_VERSION_NUMBER                                                    \
      55             :     PROJ_COMPUTE_VERSION(PROJ_VERSION_MAJOR, PROJ_VERSION_MINOR,               \
      56             :                          PROJ_VERSION_PATCH)
      57             : #define PROJ_AT_LEAST_VERSION(maj, min, patch)                                 \
      58             :     (PROJ_VERSION_NUMBER >= PROJ_COMPUTE_VERSION(maj, min, patch))
      59             : #endif
      60             : 
      61             : #define STRINGIFY(s) #s
      62             : #define XSTRINGIFY(s) STRINGIFY(s)
      63             : 
      64             : struct OGRSpatialReference::Private
      65             : {
      66             :     struct Listener final : public OGR_SRSNode::Listener
      67             :     {
      68             :         OGRSpatialReference::Private *m_poObj = nullptr;
      69             : 
      70      227965 :         explicit Listener(OGRSpatialReference::Private *poObj) : m_poObj(poObj)
      71             :         {
      72      227974 :         }
      73             : 
      74             :         Listener(const Listener &) = delete;
      75             :         Listener &operator=(const Listener &) = delete;
      76             : 
      77             :         void notifyChange(OGR_SRSNode *) override;
      78             :     };
      79             : 
      80             :     OGRSpatialReference *m_poSelf = nullptr;
      81             :     PJ *m_pj_crs = nullptr;
      82             : 
      83             :     // Temporary state used for object construction
      84             :     PJ_TYPE m_pjType = PJ_TYPE_UNKNOWN;
      85             :     CPLString m_osPrimeMeridianName{};
      86             :     CPLString m_osAngularUnits{};
      87             :     CPLString m_osLinearUnits{};
      88             :     CPLString m_osAxisName[3]{};
      89             : 
      90             :     std::vector<std::string> m_wktImportWarnings{};
      91             :     std::vector<std::string> m_wktImportErrors{};
      92             :     CPLString m_osAreaName{};
      93             :     CPLString m_celestialBodyName{};
      94             : 
      95             :     bool m_bIsThreadSafe = false;
      96             :     bool m_bNodesChanged = false;
      97             :     bool m_bNodesWKT2 = false;
      98             :     OGR_SRSNode *m_poRoot = nullptr;
      99             : 
     100             :     double dfFromGreenwich = 0.0;
     101             :     double dfToMeter = 0.0;
     102             :     double dfToDegrees = 0.0;
     103             :     double m_dfAngularUnitToRadian = 0.0;
     104             : 
     105             :     int nRefCount = 1;
     106             :     int bNormInfoSet = FALSE;
     107             : 
     108             :     PJ *m_pj_geod_base_crs_temp = nullptr;
     109             :     PJ *m_pj_proj_crs_cs_temp = nullptr;
     110             : 
     111             :     bool m_pj_crs_modified_during_demote = false;
     112             :     PJ *m_pj_bound_crs_target = nullptr;
     113             :     PJ *m_pj_bound_crs_co = nullptr;
     114             :     PJ *m_pj_crs_backup = nullptr;
     115             :     OGR_SRSNode *m_poRootBackup = nullptr;
     116             : 
     117             :     bool m_bMorphToESRI = false;
     118             :     bool m_bHasCenterLong = false;
     119             : 
     120             :     std::shared_ptr<Listener> m_poListener{};
     121             : 
     122             :     std::recursive_mutex m_mutex{};
     123             : 
     124             :     OSRAxisMappingStrategy m_axisMappingStrategy = OAMS_AUTHORITY_COMPLIANT;
     125             :     std::vector<int> m_axisMapping{1, 2, 3};
     126             : 
     127             :     double m_coordinateEpoch = 0;  // as decimal year
     128             : 
     129             :     explicit Private(OGRSpatialReference *poSelf);
     130             :     ~Private();
     131             :     Private(const Private &) = delete;
     132             :     Private &operator=(const Private &) = delete;
     133             : 
     134           2 :     void SetThreadSafe()
     135             :     {
     136           2 :         m_bIsThreadSafe = true;
     137           2 :     }
     138             : 
     139             :     void clear();
     140             :     void setPjCRS(PJ *pj_crsIn, bool doRefreshAxisMapping = true);
     141             :     void setRoot(OGR_SRSNode *poRoot);
     142             :     void refreshProjObj();
     143             :     void nodesChanged();
     144             :     void refreshRootFromProjObj();
     145             :     void invalidateNodes();
     146             : 
     147             :     void setMorphToESRI(bool b);
     148             : 
     149             :     PJ *getGeodBaseCRS();
     150             :     PJ *getProjCRSCoordSys();
     151             : 
     152             :     const char *getProjCRSName();
     153             :     OGRErr replaceConversionAndUnref(PJ *conv);
     154             : 
     155             :     void demoteFromBoundCRS();
     156             :     void undoDemoteFromBoundCRS();
     157             : 
     158     1139880 :     PJ_CONTEXT *getPROJContext()
     159             :     {
     160     1139880 :         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     5156890 :         explicit OptionalLockGuard(Private *p) : m_private(*p)
     180             :         {
     181     5156890 :             if (m_private.m_bIsThreadSafe)
     182        3798 :                 m_private.m_mutex.lock();
     183     5156890 :         }
     184             : 
     185     5156980 :         ~OptionalLockGuard()
     186     5156980 :         {
     187     5156980 :             if (m_private.m_bIsThreadSafe)
     188        3798 :                 m_private.m_mutex.unlock();
     189     5156980 :         }
     190             :     };
     191             : 
     192     5156950 :     inline OptionalLockGuard GetOptionalLockGuard()
     193             :     {
     194     5156950 :         return OptionalLockGuard(this);
     195             :     }
     196             : };
     197             : 
     198     2108770 : void OGRSpatialReference::Private::Listener::notifyChange(OGR_SRSNode *)
     199             : {
     200     2108770 :     m_poObj->nodesChanged();
     201     2108770 : }
     202             : 
     203             : #define TAKE_OPTIONAL_LOCK()                                                   \
     204             :     auto lock = d->GetOptionalLockGuard();                                     \
     205             :     CPL_IGNORE_RET_VAL(lock)
     206             : 
     207      227775 : static OSRAxisMappingStrategy GetDefaultAxisMappingStrategy()
     208             : {
     209             :     const char *pszDefaultAMS =
     210      227775 :         CPLGetConfigOption("OSR_DEFAULT_AXIS_MAPPING_STRATEGY", nullptr);
     211      228051 :     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      228050 :     return OAMS_AUTHORITY_COMPLIANT;
     225             : }
     226             : 
     227      228008 : OGRSpatialReference::Private::Private(OGRSpatialReference *poSelf)
     228             :     : m_poSelf(poSelf),
     229      228008 :       m_poListener(std::shared_ptr<Listener>(new Listener(this)))
     230             : {
     231             :     // Get the default value for m_axisMappingStrategy from the
     232             :     // OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration option, if set.
     233      227788 :     m_axisMappingStrategy = GetDefaultAxisMappingStrategy();
     234      228051 : }
     235             : 
     236      905813 : OGRSpatialReference::Private::~Private()
     237             : {
     238             :     // In case we destroy the object not in the thread that created it,
     239             :     // we need to reassign the PROJ context. Having the context bundled inside
     240             :     // PJ* deeply sucks...
     241      226512 :     PJ_CONTEXT *pj_context_to_destroy = nullptr;
     242             :     PJ_CONTEXT *ctxt;
     243      226512 :     if (GDALThreadLocalDatasetCacheIsInDestruction())
     244             :     {
     245         184 :         pj_context_to_destroy = proj_context_create();
     246         184 :         ctxt = pj_context_to_destroy;
     247             :     }
     248             :     else
     249             :     {
     250      226306 :         ctxt = getPROJContext();
     251             :     }
     252             : 
     253      226514 :     proj_assign_context(m_pj_crs, ctxt);
     254      226514 :     proj_destroy(m_pj_crs);
     255             : 
     256      226513 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     257      226513 :     proj_destroy(m_pj_geod_base_crs_temp);
     258             : 
     259      226509 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     260      226511 :     proj_destroy(m_pj_proj_crs_cs_temp);
     261             : 
     262      226510 :     proj_assign_context(m_pj_bound_crs_target, ctxt);
     263      226510 :     proj_destroy(m_pj_bound_crs_target);
     264             : 
     265      226511 :     proj_assign_context(m_pj_bound_crs_co, ctxt);
     266      226512 :     proj_destroy(m_pj_bound_crs_co);
     267             : 
     268      226511 :     proj_assign_context(m_pj_crs_backup, ctxt);
     269      226511 :     proj_destroy(m_pj_crs_backup);
     270             : 
     271      226506 :     delete m_poRootBackup;
     272      226513 :     delete m_poRoot;
     273      226513 :     proj_context_destroy(pj_context_to_destroy);
     274      226449 : }
     275             : 
     276      119003 : void OGRSpatialReference::Private::clear()
     277             : {
     278      119003 :     proj_assign_context(m_pj_crs, getPROJContext());
     279      119003 :     proj_destroy(m_pj_crs);
     280      119003 :     m_pj_crs = nullptr;
     281             : 
     282      119003 :     delete m_poRoot;
     283      119003 :     m_poRoot = nullptr;
     284      119003 :     m_bNodesChanged = false;
     285             : 
     286      119003 :     m_wktImportWarnings.clear();
     287      119003 :     m_wktImportErrors.clear();
     288             : 
     289      119002 :     m_pj_crs_modified_during_demote = false;
     290      119002 :     m_pjType = PJ_TYPE_UNKNOWN;
     291      119002 :     m_osPrimeMeridianName.clear();
     292      119003 :     m_osAngularUnits.clear();
     293      119002 :     m_osLinearUnits.clear();
     294             : 
     295      119002 :     bNormInfoSet = FALSE;
     296      119002 :     dfFromGreenwich = 1.0;
     297      119002 :     dfToMeter = 1.0;
     298      119002 :     dfToDegrees = 1.0;
     299      119002 :     m_dfAngularUnitToRadian = 0.0;
     300             : 
     301      119002 :     m_bMorphToESRI = false;
     302      119002 :     m_bHasCenterLong = false;
     303             : 
     304      119002 :     m_coordinateEpoch = 0.0;
     305      119002 : }
     306             : 
     307       26480 : void OGRSpatialReference::Private::setRoot(OGR_SRSNode *poRoot)
     308             : {
     309       26480 :     m_poRoot = poRoot;
     310       26480 :     if (m_poRoot)
     311             :     {
     312       26480 :         m_poRoot->RegisterListener(m_poListener);
     313             :     }
     314       26480 :     nodesChanged();
     315       26480 : }
     316             : 
     317      179968 : void OGRSpatialReference::Private::setPjCRS(PJ *pj_crsIn,
     318             :                                             bool doRefreshAxisMapping)
     319             : {
     320      179968 :     auto ctxt = getPROJContext();
     321             : 
     322             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
     323             :     if (proj_get_type(pj_crsIn) == PJ_TYPE_COORDINATE_METADATA)
     324             :     {
     325             :         const double dfEpoch =
     326             :             proj_coordinate_metadata_get_epoch(ctxt, pj_crsIn);
     327             :         if (!std::isnan(dfEpoch))
     328             :         {
     329             :             m_poSelf->SetCoordinateEpoch(dfEpoch);
     330             :         }
     331             :         auto crs = proj_get_source_crs(ctxt, pj_crsIn);
     332             :         proj_destroy(pj_crsIn);
     333             :         pj_crsIn = crs;
     334             :     }
     335             : #endif
     336             : 
     337      179968 :     proj_assign_context(m_pj_crs, ctxt);
     338      179968 :     proj_destroy(m_pj_crs);
     339      179968 :     m_pj_crs = pj_crsIn;
     340      179968 :     if (m_pj_crs)
     341             :     {
     342      179917 :         m_pjType = proj_get_type(m_pj_crs);
     343             :     }
     344      179968 :     if (m_pj_crs_backup)
     345             :     {
     346          21 :         m_pj_crs_modified_during_demote = true;
     347             :     }
     348      179968 :     invalidateNodes();
     349      179967 :     if (doRefreshAxisMapping)
     350             :     {
     351      179948 :         refreshAxisMapping();
     352             :     }
     353      179966 : }
     354             : 
     355      726493 : void OGRSpatialReference::Private::refreshProjObj()
     356             : {
     357      726493 :     if (m_bNodesChanged && m_poRoot)
     358             :     {
     359        8233 :         char *pszWKT = nullptr;
     360        8233 :         m_poRoot->exportToWkt(&pszWKT);
     361        8233 :         auto poRootBackup = m_poRoot;
     362        8233 :         m_poRoot = nullptr;
     363        8233 :         const double dfCoordinateEpochBackup = m_coordinateEpoch;
     364        8233 :         clear();
     365        8233 :         m_coordinateEpoch = dfCoordinateEpochBackup;
     366        8233 :         m_bHasCenterLong = strstr(pszWKT, "CENTER_LONG") != nullptr;
     367             : 
     368        8233 :         const char *const options[] = {
     369             :             "STRICT=NO",
     370             : #if PROJ_AT_LEAST_VERSION(9, 1, 0)
     371             :             "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=NO",
     372             : #endif
     373             :             nullptr
     374             :         };
     375        8233 :         PROJ_STRING_LIST warnings = nullptr;
     376        8233 :         PROJ_STRING_LIST errors = nullptr;
     377        8233 :         setPjCRS(proj_create_from_wkt(getPROJContext(), pszWKT, options,
     378             :                                       &warnings, &errors));
     379       16071 :         for (auto iter = warnings; iter && *iter; ++iter)
     380             :         {
     381        7838 :             m_wktImportWarnings.push_back(*iter);
     382             :         }
     383        8448 :         for (auto iter = errors; iter && *iter; ++iter)
     384             :         {
     385         215 :             m_wktImportErrors.push_back(*iter);
     386             :         }
     387        8233 :         proj_string_list_destroy(warnings);
     388        8233 :         proj_string_list_destroy(errors);
     389             : 
     390        8233 :         CPLFree(pszWKT);
     391             : 
     392        8233 :         m_poRoot = poRootBackup;
     393        8233 :         m_bNodesChanged = false;
     394             :     }
     395      726493 : }
     396             : 
     397       28602 : void OGRSpatialReference::Private::refreshRootFromProjObj()
     398             : {
     399       28602 :     CPLAssert(m_poRoot == nullptr);
     400             : 
     401       28602 :     if (m_pj_crs)
     402             :     {
     403       52872 :         CPLStringList aosOptions;
     404       26436 :         if (!m_bMorphToESRI)
     405             :         {
     406       26432 :             aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
     407       26432 :             aosOptions.SetNameValue("MULTILINE", "NO");
     408             :         }
     409       26436 :         aosOptions.SetNameValue("STRICT", "NO");
     410             : 
     411             :         const char *pszWKT;
     412             :         {
     413       26436 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     414       26436 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs,
     415       26436 :                                  m_bMorphToESRI ? PJ_WKT1_ESRI : PJ_WKT1_GDAL,
     416       26436 :                                  aosOptions.List());
     417       26436 :             m_bNodesWKT2 = false;
     418             :         }
     419       26436 :         if (!m_bMorphToESRI && pszWKT == nullptr)
     420             :         {
     421          70 :             pszWKT = proj_as_wkt(getPROJContext(), m_pj_crs, PJ_WKT2_2018,
     422          70 :                                  aosOptions.List());
     423          70 :             m_bNodesWKT2 = true;
     424             :         }
     425       26436 :         if (pszWKT)
     426             :         {
     427       26436 :             auto root = new OGR_SRSNode();
     428       26436 :             setRoot(root);
     429       26436 :             root->importFromWkt(&pszWKT);
     430       26436 :             m_bNodesChanged = false;
     431             :         }
     432             :     }
     433       28602 : }
     434             : 
     435      208123 : static bool isNorthEastAxisOrder(PJ_CONTEXT *ctx, PJ *cs)
     436             : {
     437      208123 :     const char *pszName1 = nullptr;
     438      208123 :     const char *pszDirection1 = nullptr;
     439      208123 :     proj_cs_get_axis_info(ctx, cs, 0, &pszName1, nullptr, &pszDirection1,
     440             :                           nullptr, nullptr, nullptr, nullptr);
     441      208123 :     const char *pszName2 = nullptr;
     442      208123 :     const char *pszDirection2 = nullptr;
     443      208123 :     proj_cs_get_axis_info(ctx, cs, 1, &pszName2, nullptr, &pszDirection2,
     444             :                           nullptr, nullptr, nullptr, nullptr);
     445      208122 :     if (pszDirection1 && EQUAL(pszDirection1, "north") && pszDirection2 &&
     446       91717 :         EQUAL(pszDirection2, "east"))
     447             :     {
     448       90310 :         return true;
     449             :     }
     450      117812 :     if (pszDirection1 && pszDirection2 &&
     451      117813 :         ((EQUAL(pszDirection1, "north") && EQUAL(pszDirection2, "north")) ||
     452      116423 :          (EQUAL(pszDirection1, "south") && EQUAL(pszDirection2, "south"))) &&
     453        2777 :         pszName1 && STARTS_WITH_CI(pszName1, "northing") && pszName2 &&
     454        1083 :         STARTS_WITH_CI(pszName2, "easting"))
     455             :     {
     456        1083 :         return true;
     457             :     }
     458      116729 :     return false;
     459             : }
     460             : 
     461      265623 : void OGRSpatialReference::Private::refreshAxisMapping()
     462             : {
     463      265623 :     if (!m_pj_crs || m_axisMappingStrategy == OAMS_CUSTOM)
     464       57978 :         return;
     465             : 
     466      207645 :     bool doUndoDemote = false;
     467      207645 :     if (m_pj_crs_backup == nullptr)
     468             :     {
     469      207623 :         doUndoDemote = true;
     470      207623 :         demoteFromBoundCRS();
     471             :     }
     472      207645 :     const auto ctxt = getPROJContext();
     473      207646 :     PJ *horizCRS = nullptr;
     474      207646 :     int axisCount = 0;
     475      207646 :     if (m_pjType == PJ_TYPE_VERTICAL_CRS)
     476             :     {
     477         218 :         axisCount = 1;
     478             :     }
     479      207428 :     else if (m_pjType == PJ_TYPE_COMPOUND_CRS)
     480             :     {
     481        1100 :         horizCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 0);
     482        1100 :         if (horizCRS && proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
     483             :         {
     484         222 :             auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
     485         222 :             if (baseCRS)
     486             :             {
     487         222 :                 proj_destroy(horizCRS);
     488         222 :                 horizCRS = baseCRS;
     489             :             }
     490             :         }
     491             : 
     492        1100 :         auto vertCRS = proj_crs_get_sub_crs(ctxt, m_pj_crs, 1);
     493        1100 :         if (vertCRS)
     494             :         {
     495        1097 :             if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
     496             :             {
     497         391 :                 auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
     498         391 :                 if (baseCRS)
     499             :                 {
     500         391 :                     proj_destroy(vertCRS);
     501         391 :                     vertCRS = baseCRS;
     502             :                 }
     503             :             }
     504             : 
     505        1097 :             auto cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
     506        1097 :             if (cs)
     507             :             {
     508        1097 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
     509        1097 :                 proj_destroy(cs);
     510             :             }
     511        1097 :             proj_destroy(vertCRS);
     512             :         }
     513             :     }
     514             :     else
     515             :     {
     516      206328 :         horizCRS = m_pj_crs;
     517             :     }
     518             : 
     519      207646 :     bool bSwitchForGisFriendlyOrder = false;
     520      207646 :     if (horizCRS)
     521             :     {
     522      207425 :         auto cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
     523      207425 :         if (cs)
     524             :         {
     525      207425 :             int nHorizCSAxisCount = proj_cs_get_axis_count(ctxt, cs);
     526      207425 :             axisCount += nHorizCSAxisCount;
     527      207425 :             if (nHorizCSAxisCount >= 2)
     528             :             {
     529      207415 :                 bSwitchForGisFriendlyOrder = isNorthEastAxisOrder(ctxt, cs);
     530             :             }
     531      207425 :             proj_destroy(cs);
     532             :         }
     533             :     }
     534      207634 :     if (horizCRS != m_pj_crs)
     535             :     {
     536        1318 :         proj_destroy(horizCRS);
     537             :     }
     538      207634 :     if (doUndoDemote)
     539             :     {
     540      207625 :         undoDemoteFromBoundCRS();
     541             :     }
     542             : 
     543      207634 :     m_axisMapping.resize(axisCount);
     544      207644 :     if (m_axisMappingStrategy == OAMS_AUTHORITY_COMPLIANT ||
     545       61383 :         !bSwitchForGisFriendlyOrder)
     546             :     {
     547      541027 :         for (int i = 0; i < axisCount; i++)
     548             :         {
     549      361151 :             m_axisMapping[i] = i + 1;
     550      179876 :         }
     551             :     }
     552             :     else
     553             :     {
     554       27768 :         m_axisMapping[0] = 2;
     555       27768 :         m_axisMapping[1] = 1;
     556       27768 :         if (axisCount == 3)
     557             :         {
     558         338 :             m_axisMapping[2] = 3;
     559             :         }
     560             :     }
     561             : }
     562             : 
     563     2135250 : void OGRSpatialReference::Private::nodesChanged()
     564             : {
     565     2135250 :     m_bNodesChanged = true;
     566     2135250 : }
     567             : 
     568      180224 : void OGRSpatialReference::Private::invalidateNodes()
     569             : {
     570      180224 :     delete m_poRoot;
     571      180224 :     m_poRoot = nullptr;
     572      180224 :     m_bNodesChanged = false;
     573      180224 : }
     574             : 
     575         256 : void OGRSpatialReference::Private::setMorphToESRI(bool b)
     576             : {
     577         256 :     invalidateNodes();
     578         256 :     m_bMorphToESRI = b;
     579         256 : }
     580             : 
     581      611413 : void OGRSpatialReference::Private::demoteFromBoundCRS()
     582             : {
     583      611413 :     CPLAssert(m_pj_bound_crs_target == nullptr);
     584      611413 :     CPLAssert(m_pj_bound_crs_co == nullptr);
     585      611413 :     CPLAssert(m_poRootBackup == nullptr);
     586      611413 :     CPLAssert(m_pj_crs_backup == nullptr);
     587             : 
     588      611413 :     m_pj_crs_modified_during_demote = false;
     589             : 
     590      611413 :     if (m_pjType == PJ_TYPE_BOUND_CRS)
     591             :     {
     592        2759 :         auto baseCRS = proj_get_source_crs(getPROJContext(), m_pj_crs);
     593        2759 :         m_pj_bound_crs_target = proj_get_target_crs(getPROJContext(), m_pj_crs);
     594        2759 :         m_pj_bound_crs_co =
     595        2759 :             proj_crs_get_coordoperation(getPROJContext(), m_pj_crs);
     596             : 
     597        2759 :         m_poRootBackup = m_poRoot;
     598        2759 :         m_poRoot = nullptr;
     599        2759 :         m_pj_crs_backup = m_pj_crs;
     600        2759 :         m_pj_crs = baseCRS;
     601        2759 :         m_pjType = proj_get_type(m_pj_crs);
     602             :     }
     603      611413 : }
     604             : 
     605      611414 : void OGRSpatialReference::Private::undoDemoteFromBoundCRS()
     606             : {
     607      611414 :     if (m_pj_bound_crs_target)
     608             :     {
     609        2759 :         CPLAssert(m_poRoot == nullptr);
     610        2759 :         CPLAssert(m_pj_crs);
     611        2759 :         if (!m_pj_crs_modified_during_demote)
     612             :         {
     613        2739 :             proj_destroy(m_pj_crs);
     614        2739 :             m_pj_crs = m_pj_crs_backup;
     615        2739 :             m_pjType = proj_get_type(m_pj_crs);
     616        2739 :             m_poRoot = m_poRootBackup;
     617             :         }
     618             :         else
     619             :         {
     620          20 :             delete m_poRootBackup;
     621          20 :             m_poRootBackup = nullptr;
     622          20 :             proj_destroy(m_pj_crs_backup);
     623          20 :             m_pj_crs_backup = nullptr;
     624          20 :             setPjCRS(proj_crs_create_bound_crs(getPROJContext(), m_pj_crs,
     625          20 :                                                m_pj_bound_crs_target,
     626          20 :                                                m_pj_bound_crs_co),
     627             :                      false);
     628             :         }
     629             :     }
     630             : 
     631      611414 :     m_poRootBackup = nullptr;
     632      611414 :     m_pj_crs_backup = nullptr;
     633      611414 :     proj_destroy(m_pj_bound_crs_target);
     634      611414 :     m_pj_bound_crs_target = nullptr;
     635      611414 :     proj_destroy(m_pj_bound_crs_co);
     636      611413 :     m_pj_bound_crs_co = nullptr;
     637      611413 :     m_pj_crs_modified_during_demote = false;
     638      611413 : }
     639             : 
     640      151958 : const char *OGRSpatialReference::Private::nullifyTargetKeyIfPossible(
     641             :     const char *pszTargetKey)
     642             : {
     643      151958 :     if (pszTargetKey)
     644             :     {
     645       57693 :         demoteFromBoundCRS();
     646       57693 :         if ((m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     647       29762 :              m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS) &&
     648       28009 :             EQUAL(pszTargetKey, "GEOGCS"))
     649             :         {
     650        6952 :             pszTargetKey = nullptr;
     651             :         }
     652       50741 :         else if (m_pjType == PJ_TYPE_GEOCENTRIC_CRS &&
     653          20 :                  EQUAL(pszTargetKey, "GEOCCS"))
     654             :         {
     655           0 :             pszTargetKey = nullptr;
     656             :         }
     657       50741 :         else if (m_pjType == PJ_TYPE_PROJECTED_CRS &&
     658       28377 :                  EQUAL(pszTargetKey, "PROJCS"))
     659             :         {
     660        3988 :             pszTargetKey = nullptr;
     661             :         }
     662       46753 :         else if (m_pjType == PJ_TYPE_VERTICAL_CRS &&
     663           4 :                  EQUAL(pszTargetKey, "VERT_CS"))
     664             :         {
     665           2 :             pszTargetKey = nullptr;
     666             :         }
     667       57693 :         undoDemoteFromBoundCRS();
     668             :     }
     669      151958 :     return pszTargetKey;
     670             : }
     671             : 
     672        9257 : PJ *OGRSpatialReference::Private::getGeodBaseCRS()
     673             : {
     674        9257 :     if (m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
     675        9204 :         m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
     676             :     {
     677          53 :         return m_pj_crs;
     678             :     }
     679             : 
     680        9204 :     auto ctxt = getPROJContext();
     681        9204 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     682             :     {
     683        4221 :         proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     684        4221 :         proj_destroy(m_pj_geod_base_crs_temp);
     685        4221 :         m_pj_geod_base_crs_temp = proj_crs_get_geodetic_crs(ctxt, m_pj_crs);
     686        4221 :         return m_pj_geod_base_crs_temp;
     687             :     }
     688             : 
     689        4983 :     proj_assign_context(m_pj_geod_base_crs_temp, ctxt);
     690        4983 :     proj_destroy(m_pj_geod_base_crs_temp);
     691        4983 :     auto cs = proj_create_ellipsoidal_2D_cs(ctxt, PJ_ELLPS2D_LATITUDE_LONGITUDE,
     692             :                                             nullptr, 0);
     693        4983 :     m_pj_geod_base_crs_temp = proj_create_geographic_crs(
     694             :         ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84",
     695             :         SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING, SRS_PM_GREENWICH, 0.0,
     696             :         SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV), cs);
     697        4983 :     proj_destroy(cs);
     698             : 
     699        4983 :     return m_pj_geod_base_crs_temp;
     700             : }
     701             : 
     702        5184 : PJ *OGRSpatialReference::Private::getProjCRSCoordSys()
     703             : {
     704        5184 :     auto ctxt = getPROJContext();
     705        5184 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     706             :     {
     707        4207 :         proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     708        4207 :         proj_destroy(m_pj_proj_crs_cs_temp);
     709        4207 :         m_pj_proj_crs_cs_temp =
     710        4207 :             proj_crs_get_coordinate_system(getPROJContext(), m_pj_crs);
     711        4207 :         return m_pj_proj_crs_cs_temp;
     712             :     }
     713             : 
     714         977 :     proj_assign_context(m_pj_proj_crs_cs_temp, ctxt);
     715         977 :     proj_destroy(m_pj_proj_crs_cs_temp);
     716         977 :     m_pj_proj_crs_cs_temp = proj_create_cartesian_2D_cs(
     717             :         ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0);
     718         977 :     return m_pj_proj_crs_cs_temp;
     719             : }
     720             : 
     721        5235 : const char *OGRSpatialReference::Private::getProjCRSName()
     722             : {
     723        5235 :     if (m_pjType == PJ_TYPE_PROJECTED_CRS)
     724             :     {
     725        4222 :         return proj_get_name(m_pj_crs);
     726             :     }
     727             : 
     728        1013 :     return "unnamed";
     729             : }
     730             : 
     731        1384 : OGRErr OGRSpatialReference::Private::replaceConversionAndUnref(PJ *conv)
     732             : {
     733        1384 :     refreshProjObj();
     734             : 
     735        1384 :     demoteFromBoundCRS();
     736             : 
     737             :     auto projCRS =
     738        1384 :         proj_create_projected_crs(getPROJContext(), getProjCRSName(),
     739        1384 :                                   getGeodBaseCRS(), conv, getProjCRSCoordSys());
     740        1384 :     proj_destroy(conv);
     741             : 
     742        1384 :     setPjCRS(projCRS);
     743             : 
     744        1384 :     undoDemoteFromBoundCRS();
     745        1384 :     return OGRERR_NONE;
     746             : }
     747             : 
     748             : /************************************************************************/
     749             : /*                           ToPointer()                                */
     750             : /************************************************************************/
     751             : 
     752       25169 : static inline OGRSpatialReference *ToPointer(OGRSpatialReferenceH hSRS)
     753             : {
     754       25169 :     return OGRSpatialReference::FromHandle(hSRS);
     755             : }
     756             : 
     757             : /************************************************************************/
     758             : /*                           ToHandle()                                 */
     759             : /************************************************************************/
     760             : 
     761        4207 : static inline OGRSpatialReferenceH ToHandle(OGRSpatialReference *poSRS)
     762             : {
     763        4207 :     return OGRSpatialReference::ToHandle(poSRS);
     764             : }
     765             : 
     766             : /************************************************************************/
     767             : /*                           OGRsnPrintDouble()                         */
     768             : /************************************************************************/
     769             : 
     770             : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue);
     771             : 
     772         126 : void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue)
     773             : 
     774             : {
     775         126 :     CPLsnprintf(pszStrBuf, size, "%.16g", dfValue);
     776             : 
     777         126 :     const size_t nLen = strlen(pszStrBuf);
     778             : 
     779             :     // The following hack is intended to truncate some "precision" in cases
     780             :     // that appear to be roundoff error.
     781         126 :     if (nLen > 15 && (strcmp(pszStrBuf + nLen - 6, "999999") == 0 ||
     782           8 :                       strcmp(pszStrBuf + nLen - 6, "000001") == 0))
     783             :     {
     784           0 :         CPLsnprintf(pszStrBuf, size, "%.15g", dfValue);
     785             :     }
     786             : 
     787             :     // Force to user periods regardless of locale.
     788         126 :     if (strchr(pszStrBuf, ',') != nullptr)
     789             :     {
     790           0 :         char *const pszDelim = strchr(pszStrBuf, ',');
     791           0 :         *pszDelim = '.';
     792             :     }
     793         126 : }
     794             : 
     795             : /************************************************************************/
     796             : /*                        OGRSpatialReference()                         */
     797             : /************************************************************************/
     798             : 
     799             : /**
     800             :  * \brief Constructor.
     801             :  *
     802             :  * This constructor takes an optional string argument which if passed
     803             :  * should be a WKT representation of an SRS.  Passing this is equivalent
     804             :  * to not passing it, and then calling importFromWkt() with the WKT string.
     805             :  *
     806             :  * Note that newly created objects are given a reference count of one.
     807             :  *
     808             :  * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
     809             :  * object are assumed to be in the order of the axis of the CRS definition
     810             :  (which
     811             :  * for example means latitude first, longitude second for geographic CRS
     812             :  belonging
     813             :  * to the EPSG authority). It is possible to define a data axis to CRS axis
     814             :  * mapping strategy with the SetAxisMappingStrategy() method.
     815             :  *
     816             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
     817             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
     818             :  later
     819             :  * being the default value when the option is not set) to control the value of
     820             :  the
     821             :  * data axis to CRS axis mapping strategy when a OSRSpatialReference object is
     822             :  * created. Calling SetAxisMappingStrategy() will override this default value.
     823             : 
     824             :  * The C function OSRNewSpatialReference() does the same thing as this
     825             :  * constructor.
     826             :  *
     827             :  * @param pszWKT well known text definition to which the object should
     828             :  * be initialized, or NULL (the default).
     829             :  */
     830             : 
     831      225547 : OGRSpatialReference::OGRSpatialReference(const char *pszWKT)
     832      225547 :     : d(new Private(this))
     833             : {
     834      225384 :     if (pszWKT != nullptr)
     835         280 :         importFromWkt(pszWKT);
     836      225384 : }
     837             : 
     838             : /************************************************************************/
     839             : /*                       OSRNewSpatialReference()                       */
     840             : /************************************************************************/
     841             : 
     842             : /**
     843             :  * \brief Constructor.
     844             :  *
     845             :  * Starting with GDAL 3.0, coordinates associated with a OGRSpatialReference
     846             :  * object are assumed to be in the order of the axis of the CRS definition
     847             :  * (which for example means latitude first, longitude second for geographic CRS
     848             :  * belonging to the EPSG authority). It is possible to define a data axis to CRS
     849             :  * axis mapping strategy with the SetAxisMappingStrategy() method.
     850             :  *
     851             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
     852             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
     853             :  * later being the default value when the option is not set) to control the
     854             :  * value of the data axis to CRS axis mapping strategy when a
     855             :  * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
     856             :  * override this default value.
     857             :  *
     858             :  * This function is the same as OGRSpatialReference::OGRSpatialReference()
     859             :  */
     860        2957 : OGRSpatialReferenceH CPL_STDCALL OSRNewSpatialReference(const char *pszWKT)
     861             : 
     862             : {
     863        2957 :     OGRSpatialReference *poSRS = new OGRSpatialReference();
     864             : 
     865        2957 :     if (pszWKT != nullptr && strlen(pszWKT) > 0)
     866             :     {
     867          65 :         if (poSRS->importFromWkt(pszWKT) != OGRERR_NONE)
     868             :         {
     869           0 :             delete poSRS;
     870           0 :             poSRS = nullptr;
     871             :         }
     872             :     }
     873             : 
     874        2957 :     return ToHandle(poSRS);
     875             : }
     876             : 
     877             : /************************************************************************/
     878             : /*                        OGRSpatialReference()                         */
     879             : /************************************************************************/
     880             : 
     881             : /** Copy constructor. See also Clone().
     882             :  * @param oOther other spatial reference
     883             :  */
     884        2465 : OGRSpatialReference::OGRSpatialReference(const OGRSpatialReference &oOther)
     885        2465 :     : d(new Private(this))
     886             : {
     887        2465 :     *this = oOther;
     888        2465 : }
     889             : 
     890             : /************************************************************************/
     891             : /*                        OGRSpatialReference()                         */
     892             : /************************************************************************/
     893             : 
     894             : /** Move constructor.
     895             :  * @param oOther other spatial reference
     896             :  */
     897          29 : OGRSpatialReference::OGRSpatialReference(OGRSpatialReference &&oOther)
     898          29 :     : d(std::move(oOther.d))
     899             : {
     900          29 : }
     901             : 
     902             : /************************************************************************/
     903             : /*                        ~OGRSpatialReference()                        */
     904             : /************************************************************************/
     905             : 
     906             : /**
     907             :  * \brief OGRSpatialReference destructor.
     908             :  *
     909             :  * The C function OSRDestroySpatialReference() does the same thing as this
     910             :  * method. Preferred C++ method : OGRSpatialReference::DestroySpatialReference()
     911             :  *
     912             :  * @deprecated
     913             :  */
     914             : 
     915      282309 : OGRSpatialReference::~OGRSpatialReference()
     916             : 
     917             : {
     918      282246 : }
     919             : 
     920             : /************************************************************************/
     921             : /*                      DestroySpatialReference()                       */
     922             : /************************************************************************/
     923             : 
     924             : /**
     925             :  * \brief OGRSpatialReference destructor.
     926             :  *
     927             :  * This static method will destroy a OGRSpatialReference.  It is
     928             :  * equivalent to calling delete on the object, but it ensures that the
     929             :  * deallocation is properly executed within the OGR libraries heap on
     930             :  * platforms where this can matter (win32).
     931             :  *
     932             :  * This function is the same as OSRDestroySpatialReference()
     933             :  *
     934             :  * @param poSRS the object to delete
     935             :  *
     936             :  * @since GDAL 1.7.0
     937             :  */
     938             : 
     939           0 : void OGRSpatialReference::DestroySpatialReference(OGRSpatialReference *poSRS)
     940             : {
     941           0 :     delete poSRS;
     942           0 : }
     943             : 
     944             : /************************************************************************/
     945             : /*                     OSRDestroySpatialReference()                     */
     946             : /************************************************************************/
     947             : 
     948             : /**
     949             :  * \brief OGRSpatialReference destructor.
     950             :  *
     951             :  * This function is the same as OGRSpatialReference::~OGRSpatialReference()
     952             :  * and OGRSpatialReference::DestroySpatialReference()
     953             :  *
     954             :  * @param hSRS the object to delete
     955             :  */
     956        9100 : void CPL_STDCALL OSRDestroySpatialReference(OGRSpatialReferenceH hSRS)
     957             : 
     958             : {
     959        9100 :     delete ToPointer(hSRS);
     960        9100 : }
     961             : 
     962             : /************************************************************************/
     963             : /*                               Clear()                                */
     964             : /************************************************************************/
     965             : 
     966             : /**
     967             :  * \brief Wipe current definition.
     968             :  *
     969             :  * Returns OGRSpatialReference to a state with no definition, as it
     970             :  * exists when first created.  It does not affect reference counts.
     971             :  */
     972             : 
     973      110769 : void OGRSpatialReference::Clear()
     974             : 
     975             : {
     976      110769 :     d->clear();
     977      110769 : }
     978             : 
     979             : /************************************************************************/
     980             : /*                             operator=()                              */
     981             : /************************************************************************/
     982             : 
     983             : /** Assignment operator.
     984             :  * @param oSource SRS to assign to *this
     985             :  * @return *this
     986             :  */
     987             : OGRSpatialReference &
     988       25929 : OGRSpatialReference::operator=(const OGRSpatialReference &oSource)
     989             : 
     990             : {
     991       25929 :     if (&oSource != this)
     992             :     {
     993       25928 :         Clear();
     994             : #ifdef CPPCHECK
     995             :         // Otherwise cppcheck would protest that nRefCount isn't modified
     996             :         d->nRefCount = (d->nRefCount + 1) - 1;
     997             : #endif
     998             : 
     999       25929 :         oSource.d->refreshProjObj();
    1000       25928 :         if (oSource.d->m_pj_crs)
    1001       25639 :             d->setPjCRS(proj_clone(d->getPROJContext(), oSource.d->m_pj_crs));
    1002       25930 :         if (oSource.d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
    1003       11931 :             SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1004       13997 :         else if (oSource.d->m_axisMappingStrategy == OAMS_CUSTOM)
    1005         124 :             SetDataAxisToSRSAxisMapping(oSource.d->m_axisMapping);
    1006             : 
    1007       25929 :         d->m_coordinateEpoch = oSource.d->m_coordinateEpoch;
    1008             :     }
    1009             : 
    1010       25929 :     return *this;
    1011             : }
    1012             : 
    1013             : /************************************************************************/
    1014             : /*                             operator=()                              */
    1015             : /************************************************************************/
    1016             : 
    1017             : /** Move assignment operator.
    1018             :  * @param oSource SRS to assign to *this
    1019             :  * @return *this
    1020             :  */
    1021             : OGRSpatialReference &
    1022        4528 : OGRSpatialReference::operator=(OGRSpatialReference &&oSource)
    1023             : 
    1024             : {
    1025        4528 :     if (&oSource != this)
    1026             :     {
    1027        4527 :         d = std::move(oSource.d);
    1028             :     }
    1029             : 
    1030        4528 :     return *this;
    1031             : }
    1032             : 
    1033             : /************************************************************************/
    1034             : /*                      AssignAndSetThreadSafe()                        */
    1035             : /************************************************************************/
    1036             : 
    1037             : /** Assignment method, with thread-safety.
    1038             :  *
    1039             :  * Same as an assignment operator, but asking also that the *this instance
    1040             :  * becomes thread-safe.
    1041             :  *
    1042             :  * @param oSource SRS to assign to *this
    1043             :  * @return *this
    1044             :  * @since 3.10
    1045             :  */
    1046             : 
    1047             : OGRSpatialReference &
    1048           2 : OGRSpatialReference::AssignAndSetThreadSafe(const OGRSpatialReference &oSource)
    1049             : {
    1050           2 :     *this = oSource;
    1051           2 :     d->SetThreadSafe();
    1052           2 :     return *this;
    1053             : }
    1054             : 
    1055             : /************************************************************************/
    1056             : /*                             Reference()                              */
    1057             : /************************************************************************/
    1058             : 
    1059             : /**
    1060             :  * \brief Increments the reference count by one.
    1061             :  *
    1062             :  * The reference count is used keep track of the number of OGRGeometry objects
    1063             :  * referencing this SRS.
    1064             :  *
    1065             :  * The method does the same thing as the C function OSRReference().
    1066             :  *
    1067             :  * @return the updated reference count.
    1068             :  */
    1069             : 
    1070     4121470 : int OGRSpatialReference::Reference()
    1071             : 
    1072             : {
    1073     4121470 :     return CPLAtomicInc(&d->nRefCount);
    1074             : }
    1075             : 
    1076             : /************************************************************************/
    1077             : /*                            OSRReference()                            */
    1078             : /************************************************************************/
    1079             : 
    1080             : /**
    1081             :  * \brief Increments the reference count by one.
    1082             :  *
    1083             :  * This function is the same as OGRSpatialReference::Reference()
    1084             :  */
    1085         977 : int OSRReference(OGRSpatialReferenceH hSRS)
    1086             : 
    1087             : {
    1088         977 :     VALIDATE_POINTER1(hSRS, "OSRReference", 0);
    1089             : 
    1090         977 :     return ToPointer(hSRS)->Reference();
    1091             : }
    1092             : 
    1093             : /************************************************************************/
    1094             : /*                            Dereference()                             */
    1095             : /************************************************************************/
    1096             : 
    1097             : /**
    1098             :  * \brief Decrements the reference count by one.
    1099             :  *
    1100             :  * The method does the same thing as the C function OSRDereference().
    1101             :  *
    1102             :  * @return the updated reference count.
    1103             :  */
    1104             : 
    1105     4161320 : int OGRSpatialReference::Dereference()
    1106             : 
    1107             : {
    1108     4161320 :     if (d->nRefCount <= 0)
    1109           0 :         CPLDebug("OSR",
    1110             :                  "Dereference() called on an object with refcount %d,"
    1111             :                  "likely already destroyed!",
    1112           0 :                  d->nRefCount);
    1113     4161320 :     return CPLAtomicDec(&d->nRefCount);
    1114             : }
    1115             : 
    1116             : /************************************************************************/
    1117             : /*                           OSRDereference()                           */
    1118             : /************************************************************************/
    1119             : 
    1120             : /**
    1121             :  * \brief Decrements the reference count by one.
    1122             :  *
    1123             :  * This function is the same as OGRSpatialReference::Dereference()
    1124             :  */
    1125           0 : int OSRDereference(OGRSpatialReferenceH hSRS)
    1126             : 
    1127             : {
    1128           0 :     VALIDATE_POINTER1(hSRS, "OSRDereference", 0);
    1129             : 
    1130           0 :     return ToPointer(hSRS)->Dereference();
    1131             : }
    1132             : 
    1133             : /************************************************************************/
    1134             : /*                         GetReferenceCount()                          */
    1135             : /************************************************************************/
    1136             : 
    1137             : /**
    1138             :  * \brief Fetch current reference count.
    1139             :  *
    1140             :  * @return the current reference count.
    1141             :  */
    1142         180 : int OGRSpatialReference::GetReferenceCount() const
    1143             : {
    1144         180 :     return d->nRefCount;
    1145             : }
    1146             : 
    1147             : /************************************************************************/
    1148             : /*                              Release()                               */
    1149             : /************************************************************************/
    1150             : 
    1151             : /**
    1152             :  * \brief Decrements the reference count by one, and destroy if zero.
    1153             :  *
    1154             :  * The method does the same thing as the C function OSRRelease().
    1155             :  */
    1156             : 
    1157     4158540 : void OGRSpatialReference::Release()
    1158             : 
    1159             : {
    1160     4158540 :     if (Dereference() <= 0)
    1161       40030 :         delete this;
    1162     4158540 : }
    1163             : 
    1164             : /************************************************************************/
    1165             : /*                             OSRRelease()                             */
    1166             : /************************************************************************/
    1167             : 
    1168             : /**
    1169             :  * \brief Decrements the reference count by one, and destroy if zero.
    1170             :  *
    1171             :  * This function is the same as OGRSpatialReference::Release()
    1172             :  */
    1173        6191 : void OSRRelease(OGRSpatialReferenceH hSRS)
    1174             : 
    1175             : {
    1176        6191 :     VALIDATE_POINTER0(hSRS, "OSRRelease");
    1177             : 
    1178        6191 :     ToPointer(hSRS)->Release();
    1179             : }
    1180             : 
    1181       87256 : OGR_SRSNode *OGRSpatialReference::GetRoot()
    1182             : {
    1183       87256 :     TAKE_OPTIONAL_LOCK();
    1184             : 
    1185       87256 :     if (!d->m_poRoot)
    1186             :     {
    1187       25672 :         d->refreshRootFromProjObj();
    1188             :     }
    1189      174512 :     return d->m_poRoot;
    1190             : }
    1191             : 
    1192        7856 : const OGR_SRSNode *OGRSpatialReference::GetRoot() const
    1193             : {
    1194        7856 :     TAKE_OPTIONAL_LOCK();
    1195             : 
    1196        7856 :     if (!d->m_poRoot)
    1197             :     {
    1198        2930 :         d->refreshRootFromProjObj();
    1199             :     }
    1200       15712 :     return d->m_poRoot;
    1201             : }
    1202             : 
    1203             : /************************************************************************/
    1204             : /*                              SetRoot()                               */
    1205             : /************************************************************************/
    1206             : 
    1207             : /**
    1208             :  * \brief Set the root SRS node.
    1209             :  *
    1210             :  * If the object has an existing tree of OGR_SRSNodes, they are destroyed
    1211             :  * as part of assigning the new root.  Ownership of the passed OGR_SRSNode is
    1212             :  * is assumed by the OGRSpatialReference.
    1213             :  *
    1214             :  * @param poNewRoot object to assign as root.
    1215             :  */
    1216             : 
    1217          44 : void OGRSpatialReference::SetRoot(OGR_SRSNode *poNewRoot)
    1218             : 
    1219             : {
    1220          44 :     if (d->m_poRoot != poNewRoot)
    1221             :     {
    1222          44 :         delete d->m_poRoot;
    1223          44 :         d->setRoot(poNewRoot);
    1224             :     }
    1225          44 : }
    1226             : 
    1227             : /************************************************************************/
    1228             : /*                            GetAttrNode()                             */
    1229             : /************************************************************************/
    1230             : 
    1231             : /**
    1232             :  * \brief Find named node in tree.
    1233             :  *
    1234             :  * This method does a pre-order traversal of the node tree searching for
    1235             :  * a node with this exact value (case insensitive), and returns it.  Leaf
    1236             :  * nodes are not considered, under the assumption that they are just
    1237             :  * attribute value nodes.
    1238             :  *
    1239             :  * If a node appears more than once in the tree (such as UNIT for instance),
    1240             :  * the first encountered will be returned.  Use GetNode() on a subtree to be
    1241             :  * more specific.
    1242             :  *
    1243             :  * @param pszNodePath the name of the node to search for.  May contain multiple
    1244             :  * components such as "GEOGCS|UNIT".
    1245             :  *
    1246             :  * @return a pointer to the node found, or NULL if none.
    1247             :  */
    1248             : 
    1249       84124 : OGR_SRSNode *OGRSpatialReference::GetAttrNode(const char *pszNodePath)
    1250             : 
    1251             : {
    1252       84124 :     if (strchr(pszNodePath, '|') == nullptr)
    1253             :     {
    1254             :         // Fast path
    1255       46093 :         OGR_SRSNode *poNode = GetRoot();
    1256       46093 :         if (poNode)
    1257       44891 :             poNode = poNode->GetNode(pszNodePath);
    1258       46093 :         return poNode;
    1259             :     }
    1260             : 
    1261             :     char **papszPathTokens =
    1262       38031 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    1263             : 
    1264       38031 :     if (CSLCount(papszPathTokens) < 1)
    1265             :     {
    1266           0 :         CSLDestroy(papszPathTokens);
    1267           0 :         return nullptr;
    1268             :     }
    1269             : 
    1270       38031 :     OGR_SRSNode *poNode = GetRoot();
    1271      115354 :     for (int i = 0; poNode != nullptr && papszPathTokens[i] != nullptr; i++)
    1272             :     {
    1273       77323 :         poNode = poNode->GetNode(papszPathTokens[i]);
    1274             :     }
    1275             : 
    1276       38031 :     CSLDestroy(papszPathTokens);
    1277             : 
    1278       38031 :     return poNode;
    1279             : }
    1280             : 
    1281             : /**
    1282             :  * \brief Find named node in tree.
    1283             :  *
    1284             :  * This method does a pre-order traversal of the node tree searching for
    1285             :  * a node with this exact value (case insensitive), and returns it.  Leaf
    1286             :  * nodes are not considered, under the assumption that they are just
    1287             :  * attribute value nodes.
    1288             :  *
    1289             :  * If a node appears more than once in the tree (such as UNIT for instance),
    1290             :  * the first encountered will be returned.  Use GetNode() on a subtree to be
    1291             :  * more specific.
    1292             :  *
    1293             :  * @param pszNodePath the name of the node to search for.  May contain multiple
    1294             :  * components such as "GEOGCS|UNIT".
    1295             :  *
    1296             :  * @return a pointer to the node found, or NULL if none.
    1297             :  */
    1298             : 
    1299             : const OGR_SRSNode *
    1300       76150 : OGRSpatialReference::GetAttrNode(const char *pszNodePath) const
    1301             : 
    1302             : {
    1303             :     OGR_SRSNode *poNode =
    1304       76150 :         const_cast<OGRSpatialReference *>(this)->GetAttrNode(pszNodePath);
    1305             : 
    1306       76150 :     return poNode;
    1307             : }
    1308             : 
    1309             : /************************************************************************/
    1310             : /*                            GetAttrValue()                            */
    1311             : /************************************************************************/
    1312             : 
    1313             : /**
    1314             :  * \brief Fetch indicated attribute of named node.
    1315             :  *
    1316             :  * This method uses GetAttrNode() to find the named node, and then extracts
    1317             :  * the value of the indicated child.  Thus a call to GetAttrValue("UNIT",1)
    1318             :  * would return the second child of the UNIT node, which is normally the
    1319             :  * length of the linear unit in meters.
    1320             :  *
    1321             :  * This method does the same thing as the C function OSRGetAttrValue().
    1322             :  *
    1323             :  * @param pszNodeName the tree node to look for (case insensitive).
    1324             :  * @param iAttr the child of the node to fetch (zero based).
    1325             :  *
    1326             :  * @return the requested value, or NULL if it fails for any reason.
    1327             :  */
    1328             : 
    1329       23168 : const char *OGRSpatialReference::GetAttrValue(const char *pszNodeName,
    1330             :                                               int iAttr) const
    1331             : 
    1332             : {
    1333       23168 :     const OGR_SRSNode *poNode = GetAttrNode(pszNodeName);
    1334       23168 :     if (poNode == nullptr)
    1335             :     {
    1336       10026 :         if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJECTION"))
    1337             :         {
    1338          14 :             return GetAttrValue("METHOD", iAttr);
    1339             :         }
    1340       10012 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS|PROJECTION"))
    1341             :         {
    1342           0 :             return GetAttrValue("PROJCRS|METHOD", iAttr);
    1343             :         }
    1344       10012 :         else if (d->m_bNodesWKT2 && EQUAL(pszNodeName, "PROJCS"))
    1345             :         {
    1346           1 :             return GetAttrValue("PROJCRS", iAttr);
    1347             :         }
    1348       10011 :         return nullptr;
    1349             :     }
    1350             : 
    1351       13142 :     if (iAttr < 0 || iAttr >= poNode->GetChildCount())
    1352           0 :         return nullptr;
    1353             : 
    1354       13142 :     return poNode->GetChild(iAttr)->GetValue();
    1355             : }
    1356             : 
    1357             : /************************************************************************/
    1358             : /*                          OSRGetAttrValue()                           */
    1359             : /************************************************************************/
    1360             : 
    1361             : /**
    1362             :  * \brief Fetch indicated attribute of named node.
    1363             :  *
    1364             :  * This function is the same as OGRSpatialReference::GetAttrValue()
    1365             :  */
    1366          34 : const char *CPL_STDCALL OSRGetAttrValue(OGRSpatialReferenceH hSRS,
    1367             :                                         const char *pszKey, int iChild)
    1368             : 
    1369             : {
    1370          34 :     VALIDATE_POINTER1(hSRS, "OSRGetAttrValue", nullptr);
    1371             : 
    1372          34 :     return ToPointer(hSRS)->GetAttrValue(pszKey, iChild);
    1373             : }
    1374             : 
    1375             : /************************************************************************/
    1376             : /*                             GetName()                                */
    1377             : /************************************************************************/
    1378             : 
    1379             : /**
    1380             :  * \brief Return the CRS name.
    1381             :  *
    1382             :  * The returned value is only short lived and should not be used after other
    1383             :  * calls to methods on this object.
    1384             :  *
    1385             :  * @since GDAL 3.0
    1386             :  */
    1387             : 
    1388        5683 : const char *OGRSpatialReference::GetName() const
    1389             : {
    1390       11366 :     TAKE_OPTIONAL_LOCK();
    1391             : 
    1392        5683 :     d->refreshProjObj();
    1393        5683 :     if (!d->m_pj_crs)
    1394         113 :         return nullptr;
    1395        5570 :     const char *pszName = proj_get_name(d->m_pj_crs);
    1396             : #if PROJ_VERSION_NUMBER == PROJ_COMPUTE_VERSION(8, 2, 0)
    1397             :     if (d->m_pjType == PJ_TYPE_BOUND_CRS && EQUAL(pszName, "SOURCECRS"))
    1398             :     {
    1399             :         // Work around a bug of PROJ 8.2.0 (fixed in 8.2.1)
    1400             :         PJ *baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
    1401             :         if (baseCRS)
    1402             :         {
    1403             :             pszName = proj_get_name(baseCRS);
    1404             :             // pszName still remains valid after proj_destroy(), since
    1405             :             // d->m_pj_crs keeps a reference to the base CRS C++ object.
    1406             :             proj_destroy(baseCRS);
    1407             :         }
    1408             :     }
    1409             : #endif
    1410        5570 :     return pszName;
    1411             : }
    1412             : 
    1413             : /************************************************************************/
    1414             : /*                           OSRGetName()                               */
    1415             : /************************************************************************/
    1416             : 
    1417             : /**
    1418             :  * \brief Return the CRS name.
    1419             :  *
    1420             :  * The returned value is only short lived and should not be used after other
    1421             :  * calls to methods on this object.
    1422             :  *
    1423             :  * @since GDAL 3.0
    1424             :  */
    1425          44 : const char *OSRGetName(OGRSpatialReferenceH hSRS)
    1426             : 
    1427             : {
    1428          44 :     VALIDATE_POINTER1(hSRS, "OSRGetName", nullptr);
    1429             : 
    1430          44 :     return ToPointer(hSRS)->GetName();
    1431             : }
    1432             : 
    1433             : /************************************************************************/
    1434             : /*                       GetCelestialBodyName()                         */
    1435             : /************************************************************************/
    1436             : 
    1437             : /**
    1438             :  * \brief Return the name of the celestial body of this CRS.
    1439             :  *
    1440             :  * e.g. "Earth" for an Earth CRS
    1441             :  *
    1442             :  * The returned value is only short lived and should not be used after other
    1443             :  * calls to methods on this object.
    1444             :  *
    1445             :  * @since GDAL 3.12 and PROJ 8.1
    1446             :  */
    1447             : 
    1448           3 : const char *OGRSpatialReference::GetCelestialBodyName() const
    1449             : {
    1450             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    1451             : 
    1452             :     TAKE_OPTIONAL_LOCK();
    1453             : 
    1454             :     d->refreshProjObj();
    1455             :     if (!d->m_pj_crs)
    1456             :         return nullptr;
    1457             :     d->demoteFromBoundCRS();
    1458             :     const char *name =
    1459             :         proj_get_celestial_body_name(d->getPROJContext(), d->m_pj_crs);
    1460             :     if (name)
    1461             :     {
    1462             :         d->m_celestialBodyName = name;
    1463             :     }
    1464             :     d->undoDemoteFromBoundCRS();
    1465             :     return d->m_celestialBodyName.c_str();
    1466             : #else
    1467           3 :     if (std::fabs(GetSemiMajor(nullptr) - SRS_WGS84_SEMIMAJOR) <=
    1468             :         0.05 * SRS_WGS84_SEMIMAJOR)
    1469           3 :         return "Earth";
    1470           0 :     const char *pszAuthName = GetAuthorityName(nullptr);
    1471           0 :     if (pszAuthName && EQUAL(pszAuthName, "EPSG"))
    1472           0 :         return "Earth";
    1473           0 :     return nullptr;
    1474             : #endif
    1475             : }
    1476             : 
    1477             : /************************************************************************/
    1478             : /*                       OSRGetCelestialBodyName()                      */
    1479             : /************************************************************************/
    1480             : 
    1481             : /**
    1482             :  * \brief Return the name of the celestial body of this CRS.
    1483             :  *
    1484             :  * e.g. "Earth" for an Earth CRS
    1485             :  *
    1486             :  * The returned value is only short lived and should not be used after other
    1487             :  * calls to methods on this object.
    1488             :  *
    1489             :  * @since GDAL 3.12 and PROJ 8.1
    1490             :  */
    1491             : 
    1492           1 : const char *OSRGetCelestialBodyName(OGRSpatialReferenceH hSRS)
    1493             : 
    1494             : {
    1495           1 :     VALIDATE_POINTER1(hSRS, "GetCelestialBodyName", nullptr);
    1496             : 
    1497           1 :     return ToPointer(hSRS)->GetCelestialBodyName();
    1498             : }
    1499             : 
    1500             : /************************************************************************/
    1501             : /*                               Clone()                                */
    1502             : /************************************************************************/
    1503             : 
    1504             : /**
    1505             :  * \brief Make a duplicate of this OGRSpatialReference.
    1506             :  *
    1507             :  * This method is the same as the C function OSRClone().
    1508             :  *
    1509             :  * @return a new SRS, which becomes the responsibility of the caller.
    1510             :  */
    1511             : 
    1512       31649 : OGRSpatialReference *OGRSpatialReference::Clone() const
    1513             : 
    1514             : {
    1515       31649 :     OGRSpatialReference *poNewRef = new OGRSpatialReference();
    1516             : 
    1517       31648 :     TAKE_OPTIONAL_LOCK();
    1518             : 
    1519       31649 :     d->refreshProjObj();
    1520       31649 :     if (d->m_pj_crs != nullptr)
    1521       31594 :         poNewRef->d->setPjCRS(proj_clone(d->getPROJContext(), d->m_pj_crs));
    1522       31649 :     if (d->m_bHasCenterLong && d->m_poRoot)
    1523             :     {
    1524           0 :         poNewRef->d->setRoot(d->m_poRoot->Clone());
    1525             :     }
    1526       31648 :     poNewRef->d->m_axisMapping = d->m_axisMapping;
    1527       31649 :     poNewRef->d->m_axisMappingStrategy = d->m_axisMappingStrategy;
    1528       31649 :     poNewRef->d->m_coordinateEpoch = d->m_coordinateEpoch;
    1529       63298 :     return poNewRef;
    1530             : }
    1531             : 
    1532             : /************************************************************************/
    1533             : /*                              OSRClone()                              */
    1534             : /************************************************************************/
    1535             : 
    1536             : /**
    1537             :  * \brief Make a duplicate of this OGRSpatialReference.
    1538             :  *
    1539             :  * This function is the same as OGRSpatialReference::Clone()
    1540             :  */
    1541        1099 : OGRSpatialReferenceH CPL_STDCALL OSRClone(OGRSpatialReferenceH hSRS)
    1542             : 
    1543             : {
    1544        1099 :     VALIDATE_POINTER1(hSRS, "OSRClone", nullptr);
    1545             : 
    1546        1099 :     return ToHandle(ToPointer(hSRS)->Clone());
    1547             : }
    1548             : 
    1549             : /************************************************************************/
    1550             : /*                            dumpReadable()                            */
    1551             : /************************************************************************/
    1552             : 
    1553             : /** Dump pretty wkt to stdout, mostly for debugging.
    1554             :  */
    1555           0 : void OGRSpatialReference::dumpReadable()
    1556             : 
    1557             : {
    1558           0 :     char *pszPrettyWkt = nullptr;
    1559             : 
    1560           0 :     const char *const apszOptions[] = {"FORMAT=WKT2", "MULTILINE=YES", nullptr};
    1561           0 :     exportToWkt(&pszPrettyWkt, apszOptions);
    1562           0 :     printf("%s\n", pszPrettyWkt); /*ok*/
    1563           0 :     CPLFree(pszPrettyWkt);
    1564           0 : }
    1565             : 
    1566             : /************************************************************************/
    1567             : /*                         exportToPrettyWkt()                          */
    1568             : /************************************************************************/
    1569             : 
    1570             : /**
    1571             :  * Convert this SRS into a nicely formatted WKT 1 string for display to a
    1572             :  * person.
    1573             :  *
    1574             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1575             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1576             :  *
    1577             :  * Note that the returned WKT string should be freed with
    1578             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1579             :  *
    1580             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1581             :  * option. Valid values are the one of the FORMAT option of
    1582             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1583             :  *
    1584             :  * This method is the same as the C function OSRExportToPrettyWkt().
    1585             :  *
    1586             :  * @param ppszResult the resulting string is returned in this pointer.
    1587             :  * @param bSimplify TRUE if the AXIS, AUTHORITY and EXTENSION nodes should be
    1588             :  *   stripped off.
    1589             :  *
    1590             :  * @return OGRERR_NONE if successful.
    1591             :  */
    1592             : 
    1593          58 : OGRErr OGRSpatialReference::exportToPrettyWkt(char **ppszResult,
    1594             :                                               int bSimplify) const
    1595             : 
    1596             : {
    1597         116 :     CPLStringList aosOptions;
    1598          58 :     aosOptions.SetNameValue("MULTILINE", "YES");
    1599          58 :     if (bSimplify)
    1600             :     {
    1601           0 :         aosOptions.SetNameValue("FORMAT", "WKT1_SIMPLE");
    1602             :     }
    1603         116 :     return exportToWkt(ppszResult, aosOptions.List());
    1604             : }
    1605             : 
    1606             : /************************************************************************/
    1607             : /*                        OSRExportToPrettyWkt()                        */
    1608             : /************************************************************************/
    1609             : 
    1610             : /**
    1611             :  * \brief Convert this SRS into a nicely formatted WKT 1 string for display to a
    1612             :  * person.
    1613             :  *
    1614             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1615             :  * option. Valid values are the one of the FORMAT option of
    1616             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1617             :  *
    1618             :  * This function is the same as OGRSpatialReference::exportToPrettyWkt().
    1619             :  */
    1620             : 
    1621          56 : OGRErr CPL_STDCALL OSRExportToPrettyWkt(OGRSpatialReferenceH hSRS,
    1622             :                                         char **ppszReturn, int bSimplify)
    1623             : 
    1624             : {
    1625          56 :     VALIDATE_POINTER1(hSRS, "OSRExportToPrettyWkt", OGRERR_FAILURE);
    1626             : 
    1627          56 :     *ppszReturn = nullptr;
    1628             : 
    1629          56 :     return ToPointer(hSRS)->exportToPrettyWkt(ppszReturn, bSimplify);
    1630             : }
    1631             : 
    1632             : /************************************************************************/
    1633             : /*                            exportToWkt()                             */
    1634             : /************************************************************************/
    1635             : 
    1636             : /**
    1637             :  * \brief Convert this SRS into WKT 1 format.
    1638             :  *
    1639             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1640             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1641             :  *
    1642             :  * Note that the returned WKT string should be freed with
    1643             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1644             :  *
    1645             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    1646             :  * option. Valid values are the one of the FORMAT option of
    1647             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    1648             :  *
    1649             :  * This method is the same as the C function OSRExportToWkt().
    1650             :  *
    1651             :  * @param ppszResult the resulting string is returned in this pointer.
    1652             :  *
    1653             :  * @return OGRERR_NONE if successful.
    1654             :  */
    1655             : 
    1656       12369 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult) const
    1657             : 
    1658             : {
    1659       12369 :     return exportToWkt(ppszResult, nullptr);
    1660             : }
    1661             : 
    1662             : /************************************************************************/
    1663             : /*                GDAL_proj_crs_create_bound_crs_to_WGS84()             */
    1664             : /************************************************************************/
    1665             : 
    1666         573 : static PJ *GDAL_proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, PJ *pj,
    1667             :                                                    bool onlyIfEPSGCode,
    1668             :                                                    bool canModifyHorizPart)
    1669             : {
    1670         573 :     PJ *ret = nullptr;
    1671         573 :     if (proj_get_type(pj) == PJ_TYPE_COMPOUND_CRS)
    1672             :     {
    1673          13 :         auto horizCRS = proj_crs_get_sub_crs(ctx, pj, 0);
    1674          13 :         auto vertCRS = proj_crs_get_sub_crs(ctx, pj, 1);
    1675          13 :         if (horizCRS && proj_get_type(horizCRS) != PJ_TYPE_BOUND_CRS &&
    1676          26 :             vertCRS &&
    1677          10 :             (!onlyIfEPSGCode || proj_get_id_auth_name(horizCRS, 0) != nullptr))
    1678             :         {
    1679             :             auto boundHoriz =
    1680             :                 canModifyHorizPart
    1681           3 :                     ? proj_crs_create_bound_crs_to_WGS84(ctx, horizCRS, nullptr)
    1682           3 :                     : proj_clone(ctx, horizCRS);
    1683             :             auto boundVert =
    1684           3 :                 proj_crs_create_bound_crs_to_WGS84(ctx, vertCRS, nullptr);
    1685           3 :             if (boundHoriz && boundVert)
    1686             :             {
    1687           3 :                 ret = proj_create_compound_crs(ctx, proj_get_name(pj),
    1688             :                                                boundHoriz, boundVert);
    1689             :             }
    1690           3 :             proj_destroy(boundHoriz);
    1691           3 :             proj_destroy(boundVert);
    1692             :         }
    1693          13 :         proj_destroy(horizCRS);
    1694          13 :         proj_destroy(vertCRS);
    1695             :     }
    1696        1080 :     else if (proj_get_type(pj) != PJ_TYPE_BOUND_CRS &&
    1697         520 :              (!onlyIfEPSGCode || proj_get_id_auth_name(pj, 0) != nullptr))
    1698             :     {
    1699         242 :         ret = proj_crs_create_bound_crs_to_WGS84(ctx, pj, nullptr);
    1700             :     }
    1701         573 :     return ret;
    1702             : }
    1703             : 
    1704             : /************************************************************************/
    1705             : /*                            exportToWkt()                             */
    1706             : /************************************************************************/
    1707             : 
    1708             : /**
    1709             :  * Convert this SRS into a WKT string.
    1710             :  *
    1711             :  * Note that the returned WKT string should be freed with
    1712             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    1713             :  *
    1714             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1715             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1716             :  *
    1717             :  * @param ppszResult the resulting string is returned in this pointer.
    1718             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    1719             :  * supported options are
    1720             :  * <ul>
    1721             :  * <li>MULTILINE=YES/NO. Defaults to NO.</li>
    1722             :  * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
    1723             :  *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
    1724             :  *     node is returned.
    1725             :  *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
    1726             :  *     node is returned.
    1727             :  *     WKT1 is an alias of WKT1_GDAL.
    1728             :  *     WKT2 will default to the latest revision implemented (currently
    1729             :  *     WKT2_2018) WKT2_2019 can be used as an alias of WKT2_2018 since GDAL 3.2
    1730             :  * </li>
    1731             :  * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
    1732             :  * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
    1733             :  * be exported as a compound CRS whose vertical part represents an ellipsoidal
    1734             :  * height (for example for use with LAS 1.4 WKT1).
    1735             :  * Requires PROJ 7.2.1 and GDAL 3.2.1.</li>
    1736             :  * </ul>
    1737             :  *
    1738             :  * Starting with GDAL 3.0.3, if the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
    1739             :  * configuration option is set to YES, when exporting to WKT1_GDAL, this method
    1740             :  * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
    1741             :  * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
    1742             :  * TOWGS84[] node may be added.
    1743             :  *
    1744             :  * @return OGRERR_NONE if successful.
    1745             :  * @since GDAL 3.0
    1746             :  */
    1747             : 
    1748       16910 : OGRErr OGRSpatialReference::exportToWkt(char **ppszResult,
    1749             :                                         const char *const *papszOptions) const
    1750             : {
    1751             :     // In the past calling this method was thread-safe, even if we never
    1752             :     // guaranteed it. Now proj_as_wkt() will cache the result internally,
    1753             :     // so this is no longer thread-safe.
    1754       33820 :     std::lock_guard oLock(d->m_mutex);
    1755             : 
    1756       16910 :     d->refreshProjObj();
    1757       16910 :     if (!d->m_pj_crs)
    1758             :     {
    1759          21 :         *ppszResult = CPLStrdup("");
    1760          21 :         return OGRERR_FAILURE;
    1761             :     }
    1762             : 
    1763       16889 :     if (d->m_bHasCenterLong && d->m_poRoot && !d->m_bMorphToESRI)
    1764             :     {
    1765           0 :         return d->m_poRoot->exportToWkt(ppszResult);
    1766             :     }
    1767             : 
    1768       16889 :     auto ctxt = d->getPROJContext();
    1769       16889 :     auto wktFormat = PJ_WKT1_GDAL;
    1770             :     const char *pszFormat =
    1771       16889 :         CSLFetchNameValueDef(papszOptions, "FORMAT",
    1772             :                              CPLGetConfigOption("OSR_WKT_FORMAT", "DEFAULT"));
    1773       16889 :     if (EQUAL(pszFormat, "DEFAULT"))
    1774       14283 :         pszFormat = "";
    1775             : 
    1776       16889 :     if (EQUAL(pszFormat, "WKT1_ESRI") || d->m_bMorphToESRI)
    1777             :     {
    1778         646 :         wktFormat = PJ_WKT1_ESRI;
    1779             :     }
    1780       16243 :     else if (EQUAL(pszFormat, "WKT1") || EQUAL(pszFormat, "WKT1_GDAL") ||
    1781       15529 :              EQUAL(pszFormat, "WKT1_SIMPLE") || EQUAL(pszFormat, "SFSQL"))
    1782             :     {
    1783         719 :         wktFormat = PJ_WKT1_GDAL;
    1784             :     }
    1785       15524 :     else if (EQUAL(pszFormat, "WKT2_2015"))
    1786             :     {
    1787         293 :         wktFormat = PJ_WKT2_2015;
    1788             :     }
    1789       15231 :     else if (EQUAL(pszFormat, "WKT2") || EQUAL(pszFormat, "WKT2_2018") ||
    1790       14882 :              EQUAL(pszFormat, "WKT2_2019"))
    1791             :     {
    1792        1177 :         wktFormat = PJ_WKT2_2018;
    1793             :     }
    1794       14054 :     else if (pszFormat[0] == '\0')
    1795             :     {
    1796             :         // cppcheck-suppress knownConditionTrueFalse
    1797       14054 :         if (IsDerivedGeographic())
    1798             :         {
    1799           2 :             wktFormat = PJ_WKT2_2018;
    1800             :         }
    1801       27474 :         else if ((IsGeographic() || IsProjected()) && !IsCompound() &&
    1802       13422 :                  GetAxesCount() == 3)
    1803             :         {
    1804          56 :             wktFormat = PJ_WKT2_2018;
    1805             :         }
    1806             :     }
    1807             :     else
    1808             :     {
    1809           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unsupported value for FORMAT");
    1810           0 :         *ppszResult = CPLStrdup("");
    1811           0 :         return OGRERR_FAILURE;
    1812             :     }
    1813             : 
    1814       33778 :     CPLStringList aosOptions;
    1815       16889 :     if (wktFormat != PJ_WKT1_ESRI)
    1816             :     {
    1817       16243 :         aosOptions.SetNameValue("OUTPUT_AXIS", "YES");
    1818             :     }
    1819             :     aosOptions.SetNameValue(
    1820       16889 :         "MULTILINE", CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO"));
    1821             : 
    1822       16889 :     const char *pszAllowEllpsHeightAsVertCS = CSLFetchNameValue(
    1823             :         papszOptions, "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS");
    1824       16889 :     if (pszAllowEllpsHeightAsVertCS)
    1825             :     {
    1826             :         aosOptions.SetNameValue("ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS",
    1827           0 :                                 pszAllowEllpsHeightAsVertCS);
    1828             :     }
    1829             : 
    1830       16889 :     PJ *boundCRS = nullptr;
    1831       31604 :     if (wktFormat == PJ_WKT1_GDAL &&
    1832       14715 :         CPLTestBool(CSLFetchNameValueDef(
    1833             :             papszOptions, "ADD_TOWGS84_ON_EXPORT_TO_WKT1",
    1834             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1", "NO"))))
    1835             :     {
    1836           0 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
    1837           0 :             d->getPROJContext(), d->m_pj_crs, true, true);
    1838             :     }
    1839             : 
    1840       33778 :     CPLErrorAccumulator oErrorAccumulator;
    1841             :     const char *pszWKT;
    1842             :     {
    1843       16889 :         auto oAccumulator = oErrorAccumulator.InstallForCurrentScope();
    1844       16889 :         CPL_IGNORE_RET_VAL(oAccumulator);
    1845       16889 :         pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs, wktFormat,
    1846       16889 :                              aosOptions.List());
    1847             :     }
    1848       16891 :     for (const auto &oError : oErrorAccumulator.GetErrors())
    1849             :     {
    1850          32 :         if (pszFormat[0] == '\0' &&
    1851          14 :             (oError.msg.find("Unsupported conversion method") !=
    1852           2 :                  std::string::npos ||
    1853           2 :              oError.msg.find("can only be exported to WKT2") !=
    1854           0 :                  std::string::npos ||
    1855           0 :              oError.msg.find("can only be exported since WKT2:2019") !=
    1856             :                  std::string::npos))
    1857             :         {
    1858          14 :             CPLErrorReset();
    1859             :             // If we cannot export in the default mode (WKT1), retry with WKT2
    1860          14 :             pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs,
    1861          14 :                                  PJ_WKT2_2018, aosOptions.List());
    1862          14 :             break;
    1863             :         }
    1864           2 :         CPLError(oError.type, oError.no, "%s", oError.msg.c_str());
    1865             :     }
    1866             : 
    1867       16889 :     if (!pszWKT)
    1868             :     {
    1869           2 :         *ppszResult = CPLStrdup("");
    1870           2 :         proj_destroy(boundCRS);
    1871           2 :         return OGRERR_FAILURE;
    1872             :     }
    1873             : 
    1874       16887 :     if (EQUAL(pszFormat, "SFSQL") || EQUAL(pszFormat, "WKT1_SIMPLE"))
    1875             :     {
    1876           5 :         OGR_SRSNode oRoot;
    1877           5 :         oRoot.importFromWkt(&pszWKT);
    1878           5 :         oRoot.StripNodes("AXIS");
    1879           5 :         if (EQUAL(pszFormat, "SFSQL"))
    1880             :         {
    1881           3 :             oRoot.StripNodes("TOWGS84");
    1882             :         }
    1883           5 :         oRoot.StripNodes("AUTHORITY");
    1884           5 :         oRoot.StripNodes("EXTENSION");
    1885             :         OGRErr eErr;
    1886           5 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "MULTILINE", "NO")))
    1887           2 :             eErr = oRoot.exportToPrettyWkt(ppszResult, 1);
    1888             :         else
    1889           3 :             eErr = oRoot.exportToWkt(ppszResult);
    1890           5 :         proj_destroy(boundCRS);
    1891           5 :         return eErr;
    1892             :     }
    1893             : 
    1894       16882 :     *ppszResult = CPLStrdup(pszWKT);
    1895             : 
    1896             : #if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
    1897       16882 :     if (wktFormat == PJ_WKT2_2018)
    1898             :     {
    1899             :         // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
    1900             :         // related to a wrong EPSG code assigned to UTM South conversions
    1901        1235 :         char *pszPtr = strstr(*ppszResult, "CONVERSION[\"UTM zone ");
    1902        1235 :         if (pszPtr)
    1903             :         {
    1904         297 :             pszPtr += strlen("CONVERSION[\"UTM zone ");
    1905         297 :             const int nZone = atoi(pszPtr);
    1906         890 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    1907         593 :                 ++pszPtr;
    1908         297 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' &&
    1909           1 :                 pszPtr[1] == '"' && pszPtr[2] == ',')
    1910             :             {
    1911           1 :                 pszPtr += 3;
    1912           1 :                 int nLevel = 0;
    1913           1 :                 bool bInString = false;
    1914             :                 // Find the ID node corresponding to this CONVERSION node
    1915         480 :                 while (*pszPtr)
    1916             :                 {
    1917         480 :                     if (bInString)
    1918             :                     {
    1919         197 :                         if (*pszPtr == '"' && pszPtr[1] == '"')
    1920             :                         {
    1921           0 :                             ++pszPtr;
    1922             :                         }
    1923         197 :                         else if (*pszPtr == '"')
    1924             :                         {
    1925          17 :                             bInString = false;
    1926             :                         }
    1927             :                     }
    1928         283 :                     else if (nLevel == 0 && STARTS_WITH_CI(pszPtr, "ID["))
    1929             :                     {
    1930           1 :                         if (STARTS_WITH_CI(pszPtr, CPLSPrintf("ID[\"EPSG\",%d]",
    1931             :                                                               17000 + nZone)))
    1932             :                         {
    1933           1 :                             CPLAssert(pszPtr[11] == '7');
    1934           1 :                             CPLAssert(pszPtr[12] == '0');
    1935           1 :                             pszPtr[11] = '6';
    1936           1 :                             pszPtr[12] = '1';
    1937             :                         }
    1938           1 :                         break;
    1939             :                     }
    1940         282 :                     else if (*pszPtr == '"')
    1941             :                     {
    1942          17 :                         bInString = true;
    1943             :                     }
    1944         265 :                     else if (*pszPtr == '[')
    1945             :                     {
    1946          17 :                         ++nLevel;
    1947             :                     }
    1948         248 :                     else if (*pszPtr == ']')
    1949             :                     {
    1950          17 :                         --nLevel;
    1951             :                     }
    1952             : 
    1953         479 :                     ++pszPtr;
    1954             :                 }
    1955             :             }
    1956             :         }
    1957             :     }
    1958             : #endif
    1959             : 
    1960       16882 :     proj_destroy(boundCRS);
    1961       16882 :     return OGRERR_NONE;
    1962             : }
    1963             : 
    1964             : /************************************************************************/
    1965             : /*                            exportToWkt()                             */
    1966             : /************************************************************************/
    1967             : 
    1968             : /**
    1969             :  * Convert this SRS into a WKT string.
    1970             :  *
    1971             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    1972             :  * Issues</a> page for implementation details of WKT 1 in OGR.
    1973             :  *
    1974             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    1975             :  * supported options are
    1976             :  * <ul>
    1977             :  * <li>MULTILINE=YES/NO. Defaults to NO.</li>
    1978             :  * <li>FORMAT=SFSQL/WKT1_SIMPLE/WKT1/WKT1_GDAL/WKT1_ESRI/WKT2_2015/WKT2_2018/WKT2/DEFAULT.
    1979             :  *     If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or EXTENSION
    1980             :  *     node is returned.
    1981             :  *     If WKT1_SIMPLE, a WKT1 string without AXIS, AUTHORITY or EXTENSION
    1982             :  *     node is returned.
    1983             :  *     WKT1 is an alias of WKT1_GDAL.
    1984             :  *     WKT2 will default to the latest revision implemented (currently
    1985             :  *     WKT2_2019)
    1986             :  * </li>
    1987             :  * <li>ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set
    1988             :  * to YES and FORMAT=WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS will
    1989             :  * be exported as a compound CRS whose vertical part represents an ellipsoidal
    1990             :  * height (for example for use with LAS 1.4 WKT1).
    1991             :  * Requires PROJ 7.2.1.</li>
    1992             :  * </ul>
    1993             :  *
    1994             :  * If the OSR_ADD_TOWGS84_ON_EXPORT_TO_WKT1
    1995             :  * configuration option is set to YES, when exporting to WKT1_GDAL, this method
    1996             :  * will try to add a TOWGS84[] node, if there's none attached yet to the SRS and
    1997             :  * if the SRS has a EPSG code. See the AddGuessedTOWGS84() method for how this
    1998             :  * TOWGS84[] node may be added.
    1999             :  *
    2000             :  * @return a non-empty string if successful.
    2001             :  * @since GDAL 3.9
    2002             :  */
    2003             : 
    2004             : std::string
    2005         287 : OGRSpatialReference::exportToWkt(const char *const *papszOptions) const
    2006             : {
    2007         287 :     std::string osWKT;
    2008         287 :     char *pszWKT = nullptr;
    2009         287 :     if (exportToWkt(&pszWKT, papszOptions) == OGRERR_NONE)
    2010         287 :         osWKT = pszWKT;
    2011         287 :     CPLFree(pszWKT);
    2012         574 :     return osWKT;
    2013             : }
    2014             : 
    2015             : /************************************************************************/
    2016             : /*                           OSRExportToWkt()                           */
    2017             : /************************************************************************/
    2018             : 
    2019             : /**
    2020             :  * \brief Convert this SRS into WKT 1 format.
    2021             :  *
    2022             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2023             :  * Issues</a> page for implementation details of WKT in OGR.
    2024             :  *
    2025             :  * The WKT version can be overridden by using the OSR_WKT_FORMAT configuration
    2026             :  * option. Valid values are the one of the FORMAT option of
    2027             :  * exportToWkt( char ** ppszResult, const char* const* papszOptions ) const
    2028             :  *
    2029             :  * This function is the same as OGRSpatialReference::exportToWkt().
    2030             :  */
    2031             : 
    2032         903 : OGRErr CPL_STDCALL OSRExportToWkt(OGRSpatialReferenceH hSRS, char **ppszReturn)
    2033             : 
    2034             : {
    2035         903 :     VALIDATE_POINTER1(hSRS, "OSRExportToWkt", OGRERR_FAILURE);
    2036             : 
    2037         903 :     *ppszReturn = nullptr;
    2038             : 
    2039         903 :     return ToPointer(hSRS)->exportToWkt(ppszReturn);
    2040             : }
    2041             : 
    2042             : /************************************************************************/
    2043             : /*                          OSRExportToWktEx()                          */
    2044             : /************************************************************************/
    2045             : 
    2046             : /**
    2047             :  * \brief Convert this SRS into WKT format.
    2048             :  *
    2049             :  * This function is the same as OGRSpatialReference::exportToWkt(char **
    2050             :  * ppszResult,const char* const* papszOptions ) const
    2051             :  *
    2052             :  * @since GDAL 3.0
    2053             :  */
    2054             : 
    2055        1334 : OGRErr OSRExportToWktEx(OGRSpatialReferenceH hSRS, char **ppszReturn,
    2056             :                         const char *const *papszOptions)
    2057             : {
    2058        1334 :     VALIDATE_POINTER1(hSRS, "OSRExportToWktEx", OGRERR_FAILURE);
    2059             : 
    2060        1334 :     *ppszReturn = nullptr;
    2061             : 
    2062        1334 :     return ToPointer(hSRS)->exportToWkt(ppszReturn, papszOptions);
    2063             : }
    2064             : 
    2065             : /************************************************************************/
    2066             : /*                       exportToPROJJSON()                             */
    2067             : /************************************************************************/
    2068             : 
    2069             : /**
    2070             :  * Convert this SRS into a PROJJSON string.
    2071             :  *
    2072             :  * Note that the returned JSON string should be freed with
    2073             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
    2074             :  *
    2075             :  * @param ppszResult the resulting string is returned in this pointer.
    2076             :  * @param papszOptions NULL terminated list of options, or NULL. Currently
    2077             :  * supported options are
    2078             :  * <ul>
    2079             :  * <li>MULTILINE=YES/NO. Defaults to YES</li>
    2080             :  * <li>INDENTATION_WIDTH=number. Defaults to 2 (when multiline output is
    2081             :  * on).</li>
    2082             :  * <li>SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to
    2083             :  * disable it.</li>
    2084             :  * </ul>
    2085             :  *
    2086             :  * @return OGRERR_NONE if successful.
    2087             :  * @since GDAL 3.1 and PROJ 6.2
    2088             :  */
    2089             : 
    2090        2481 : OGRErr OGRSpatialReference::exportToPROJJSON(
    2091             :     char **ppszResult, CPL_UNUSED const char *const *papszOptions) const
    2092             : {
    2093        4962 :     TAKE_OPTIONAL_LOCK();
    2094             : 
    2095        2481 :     d->refreshProjObj();
    2096        2481 :     if (!d->m_pj_crs)
    2097             :     {
    2098           1 :         *ppszResult = nullptr;
    2099           1 :         return OGRERR_FAILURE;
    2100             :     }
    2101             : 
    2102             :     const char *pszPROJJSON =
    2103        2480 :         proj_as_projjson(d->getPROJContext(), d->m_pj_crs, papszOptions);
    2104             : 
    2105        2480 :     if (!pszPROJJSON)
    2106             :     {
    2107           0 :         *ppszResult = CPLStrdup("");
    2108           0 :         return OGRERR_FAILURE;
    2109             :     }
    2110             : 
    2111        2480 :     *ppszResult = CPLStrdup(pszPROJJSON);
    2112             : 
    2113             : #if !(PROJ_AT_LEAST_VERSION(9, 5, 0))
    2114             :     {
    2115             :         // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166
    2116             :         // related to a wrong EPSG code assigned to UTM South conversions
    2117        2480 :         char *pszPtr = strstr(*ppszResult, "\"name\": \"UTM zone ");
    2118        2480 :         if (pszPtr)
    2119             :         {
    2120         237 :             pszPtr += strlen("\"name\": \"UTM zone ");
    2121         237 :             const int nZone = atoi(pszPtr);
    2122         710 :             while (*pszPtr >= '0' && *pszPtr <= '9')
    2123         473 :                 ++pszPtr;
    2124         237 :             if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' && pszPtr[1] == '"')
    2125             :             {
    2126           4 :                 pszPtr += 2;
    2127           4 :                 int nLevel = 0;
    2128           4 :                 bool bInString = false;
    2129             :                 // Find the id node corresponding to this conversion node
    2130        5299 :                 while (*pszPtr)
    2131             :                 {
    2132        5299 :                     if (bInString)
    2133             :                     {
    2134        1950 :                         if (*pszPtr == '\\')
    2135             :                         {
    2136           0 :                             ++pszPtr;
    2137             :                         }
    2138        1950 :                         else if (*pszPtr == '"')
    2139             :                         {
    2140         244 :                             bInString = false;
    2141             :                         }
    2142             :                     }
    2143        3349 :                     else if (nLevel == 0 && STARTS_WITH(pszPtr, "\"id\": {"))
    2144             :                     {
    2145           4 :                         const char *pszNextEndCurl = strchr(pszPtr, '}');
    2146             :                         const char *pszAuthEPSG =
    2147           4 :                             strstr(pszPtr, "\"authority\": \"EPSG\"");
    2148           4 :                         char *pszCode = strstr(
    2149             :                             pszPtr, CPLSPrintf("\"code\": %d", 17000 + nZone));
    2150           4 :                         if (pszAuthEPSG && pszCode && pszNextEndCurl &&
    2151           4 :                             pszNextEndCurl - pszAuthEPSG > 0 &&
    2152           4 :                             pszNextEndCurl - pszCode > 0)
    2153             :                         {
    2154           4 :                             CPLAssert(pszCode[9] == '7');
    2155           4 :                             CPLAssert(pszCode[10] == '0');
    2156           4 :                             pszCode[9] = '6';
    2157           4 :                             pszCode[10] = '1';
    2158             :                         }
    2159           4 :                         break;
    2160             :                     }
    2161        3345 :                     else if (*pszPtr == '"')
    2162             :                     {
    2163         244 :                         bInString = true;
    2164             :                     }
    2165        3101 :                     else if (*pszPtr == '{' || *pszPtr == '[')
    2166             :                     {
    2167          60 :                         ++nLevel;
    2168             :                     }
    2169        3041 :                     else if (*pszPtr == '}' || *pszPtr == ']')
    2170             :                     {
    2171          60 :                         --nLevel;
    2172             :                     }
    2173             : 
    2174        5295 :                     ++pszPtr;
    2175             :                 }
    2176             :             }
    2177             :         }
    2178             :     }
    2179             : #endif
    2180             : 
    2181        2480 :     return OGRERR_NONE;
    2182             : }
    2183             : 
    2184             : /************************************************************************/
    2185             : /*                          OSRExportToPROJJSON()                       */
    2186             : /************************************************************************/
    2187             : 
    2188             : /**
    2189             :  * \brief Convert this SRS into PROJJSON format.
    2190             :  *
    2191             :  * This function is the same as OGRSpatialReference::exportToPROJJSON() const
    2192             :  *
    2193             :  * @since GDAL 3.1 and PROJ 6.2
    2194             :  */
    2195             : 
    2196          72 : OGRErr OSRExportToPROJJSON(OGRSpatialReferenceH hSRS, char **ppszReturn,
    2197             :                            const char *const *papszOptions)
    2198             : {
    2199          72 :     VALIDATE_POINTER1(hSRS, "OSRExportToPROJJSON", OGRERR_FAILURE);
    2200             : 
    2201          72 :     *ppszReturn = nullptr;
    2202             : 
    2203          72 :     return ToPointer(hSRS)->exportToPROJJSON(ppszReturn, papszOptions);
    2204             : }
    2205             : 
    2206             : /************************************************************************/
    2207             : /*                           importFromWkt()                            */
    2208             : /************************************************************************/
    2209             : 
    2210             : /**
    2211             :  * \brief Import from WKT string.
    2212             :  *
    2213             :  * This method will wipe the existing SRS definition, and
    2214             :  * reassign it based on the contents of the passed WKT string.  Only as
    2215             :  * much of the input string as needed to construct this SRS is consumed from
    2216             :  * the input string, and the input string pointer
    2217             :  * is then updated to point to the remaining (unused) input.
    2218             :  *
    2219             :  * Starting with PROJ 9.2, if invoked on a COORDINATEMETADATA[] construct,
    2220             :  * the CRS contained in it will be used to fill the OGRSpatialReference object,
    2221             :  * and the coordinate epoch potentially present used as the coordinate epoch
    2222             :  * property of the OGRSpatialReference object.
    2223             :  *
    2224             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2225             :  * Issues</a> page for implementation details of WKT in OGR.
    2226             :  *
    2227             :  * This method is the same as the C function OSRImportFromWkt().
    2228             :  *
    2229             :  * @param ppszInput Pointer to pointer to input.  The pointer is updated to
    2230             :  * point to remaining unused input text.
    2231             :  *
    2232             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2233             :  * fails for any reason.
    2234             :  * @since GDAL 2.3
    2235             :  */
    2236             : 
    2237       17090 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput)
    2238             : 
    2239             : {
    2240       17090 :     return importFromWkt(ppszInput, nullptr);
    2241             : }
    2242             : 
    2243             : /************************************************************************/
    2244             : /*                           importFromWkt()                            */
    2245             : /************************************************************************/
    2246             : 
    2247             : /*! @cond Doxygen_Suppress */
    2248             : 
    2249          21 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput,
    2250             :                                           CSLConstList papszOptions)
    2251             : 
    2252             : {
    2253          21 :     return importFromWkt(&pszInput, papszOptions);
    2254             : }
    2255             : 
    2256       17111 : OGRErr OGRSpatialReference::importFromWkt(const char **ppszInput,
    2257             :                                           CSLConstList papszOptions)
    2258             : 
    2259             : {
    2260       34222 :     TAKE_OPTIONAL_LOCK();
    2261             : 
    2262       17111 :     if (!ppszInput || !*ppszInput)
    2263           0 :         return OGRERR_FAILURE;
    2264             : 
    2265       17111 :     if (strlen(*ppszInput) > 100 * 1000 &&
    2266           0 :         CPLTestBool(CPLGetConfigOption("OSR_IMPORT_FROM_WKT_LIMIT", "YES")))
    2267             :     {
    2268           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2269             :                  "Suspiciously large input for importFromWkt(). Rejecting it. "
    2270             :                  "You can remove this limitation by definition the "
    2271             :                  "OSR_IMPORT_FROM_WKT_LIMIT configuration option to NO.");
    2272           0 :         return OGRERR_FAILURE;
    2273             :     }
    2274             : 
    2275       17111 :     Clear();
    2276             : 
    2277       17111 :     bool canCache = false;
    2278       17111 :     auto tlsCache = OSRGetProjTLSCache();
    2279       34222 :     std::string osWkt;
    2280       17111 :     if (**ppszInput)
    2281             :     {
    2282       16555 :         osWkt = *ppszInput;
    2283       16555 :         auto cachedObj = tlsCache->GetPJForWKT(osWkt);
    2284       16555 :         if (cachedObj)
    2285             :         {
    2286       14754 :             d->setPjCRS(cachedObj);
    2287             :         }
    2288             :         else
    2289             :         {
    2290        3602 :             CPLStringList aosOptions(papszOptions);
    2291        1801 :             if (aosOptions.FetchNameValue("STRICT") == nullptr)
    2292        1801 :                 aosOptions.SetNameValue("STRICT", "NO");
    2293        1801 :             PROJ_STRING_LIST warnings = nullptr;
    2294        1801 :             PROJ_STRING_LIST errors = nullptr;
    2295        1801 :             auto ctxt = d->getPROJContext();
    2296        1801 :             auto pj = proj_create_from_wkt(ctxt, *ppszInput, aosOptions.List(),
    2297             :                                            &warnings, &errors);
    2298        1801 :             d->setPjCRS(pj);
    2299             : 
    2300        1850 :             for (auto iter = warnings; iter && *iter; ++iter)
    2301             :             {
    2302          49 :                 d->m_wktImportWarnings.push_back(*iter);
    2303             :             }
    2304        2037 :             for (auto iter = errors; iter && *iter; ++iter)
    2305             :             {
    2306         236 :                 d->m_wktImportErrors.push_back(*iter);
    2307         236 :                 if (!d->m_pj_crs)
    2308             :                 {
    2309          34 :                     CPLError(CE_Failure, CPLE_AppDefined, "%s", *iter);
    2310             :                 }
    2311             :             }
    2312        1801 :             if (warnings == nullptr && errors == nullptr)
    2313             :             {
    2314        1523 :                 canCache = true;
    2315             :             }
    2316        1801 :             proj_string_list_destroy(warnings);
    2317        1801 :             proj_string_list_destroy(errors);
    2318             :         }
    2319             :     }
    2320       17111 :     if (!d->m_pj_crs)
    2321         590 :         return OGRERR_CORRUPT_DATA;
    2322             : 
    2323             :     // Only accept CRS objects
    2324       16521 :     if (!proj_is_crs(d->m_pj_crs))
    2325             :     {
    2326           0 :         Clear();
    2327           0 :         return OGRERR_CORRUPT_DATA;
    2328             :     }
    2329             : 
    2330       16521 :     if (canCache)
    2331             :     {
    2332        1523 :         tlsCache->CachePJForWKT(osWkt, d->m_pj_crs);
    2333             :     }
    2334             : 
    2335       16521 :     if (strstr(*ppszInput, "CENTER_LONG"))
    2336             :     {
    2337           0 :         auto poRoot = new OGR_SRSNode();
    2338           0 :         d->setRoot(poRoot);
    2339           0 :         const char *pszTmp = *ppszInput;
    2340           0 :         poRoot->importFromWkt(&pszTmp);
    2341           0 :         d->m_bHasCenterLong = true;
    2342             :     }
    2343             : 
    2344             :     // TODO? we don't really update correctly since we assume that the
    2345             :     // passed string is only WKT.
    2346       16521 :     *ppszInput += strlen(*ppszInput);
    2347       16521 :     return OGRERR_NONE;
    2348             : 
    2349             : #if no_longer_implemented_for_now
    2350             :     /* -------------------------------------------------------------------- */
    2351             :     /*      The following seems to try and detect and unconsumed            */
    2352             :     /*      VERTCS[] coordinate system definition (ESRI style) and to       */
    2353             :     /*      import and attach it to the existing root.  Likely we will      */
    2354             :     /*      need to extend this somewhat to bring it into an acceptable     */
    2355             :     /*      OGRSpatialReference organization at some point.                 */
    2356             :     /* -------------------------------------------------------------------- */
    2357             :     if (strlen(*ppszInput) > 0 && strstr(*ppszInput, "VERTCS"))
    2358             :     {
    2359             :         if (((*ppszInput)[0]) == ',')
    2360             :             (*ppszInput)++;
    2361             :         OGR_SRSNode *poNewChild = new OGR_SRSNode();
    2362             :         poRoot->AddChild(poNewChild);
    2363             :         return poNewChild->importFromWkt(ppszInput);
    2364             :     }
    2365             : #endif
    2366             : }
    2367             : 
    2368             : /*! @endcond */
    2369             : 
    2370             : /**
    2371             :  * \brief Import from WKT string.
    2372             :  *
    2373             :  * This method will wipe the existing SRS definition, and
    2374             :  * reassign it based on the contents of the passed WKT string.  Only as
    2375             :  * much of the input string as needed to construct this SRS is consumed from
    2376             :  * the input string, and the input string pointer
    2377             :  * is then updated to point to the remaining (unused) input.
    2378             :  *
    2379             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2380             :  * Issues</a> page for implementation details of WKT in OGR.
    2381             :  *
    2382             :  * This method is the same as the C function OSRImportFromWkt().
    2383             :  *
    2384             :  * @param ppszInput Pointer to pointer to input.  The pointer is updated to
    2385             :  * point to remaining unused input text.
    2386             :  *
    2387             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2388             :  * fails for any reason.
    2389             :  * @deprecated GDAL 2.3. Use importFromWkt(const char**) or importFromWkt(const
    2390             :  * char*)
    2391             :  */
    2392             : 
    2393           0 : OGRErr OGRSpatialReference::importFromWkt(char **ppszInput)
    2394             : 
    2395             : {
    2396           0 :     return importFromWkt(const_cast<const char **>(ppszInput));
    2397             : }
    2398             : 
    2399             : /**
    2400             :  * \brief Import from WKT string.
    2401             :  *
    2402             :  * This method will wipe the existing SRS definition, and
    2403             :  * reassign it based on the contents of the passed WKT string.  Only as
    2404             :  * much of the input string as needed to construct this SRS is consumed from
    2405             :  * the input string, and the input string pointer
    2406             :  * is then updated to point to the remaining (unused) input.
    2407             :  *
    2408             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2409             :  * Issues</a> page for implementation details of WKT in OGR.
    2410             :  *
    2411             :  * @param pszInput Input WKT
    2412             :  *
    2413             :  * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
    2414             :  * fails for any reason.
    2415             :  * @since GDAL 2.3
    2416             :  */
    2417             : 
    2418       16800 : OGRErr OGRSpatialReference::importFromWkt(const char *pszInput)
    2419             : {
    2420       16800 :     return importFromWkt(&pszInput);
    2421             : }
    2422             : 
    2423             : /************************************************************************/
    2424             : /*                              Validate()                              */
    2425             : /************************************************************************/
    2426             : 
    2427             : /**
    2428             :  * \brief Validate CRS imported with importFromWkt() or with modified with
    2429             :  * direct node manipulations. Otherwise the CRS should be always valid.
    2430             :  *
    2431             :  * This method attempts to verify that the spatial reference system is
    2432             :  * well formed, and consists of known tokens.  The validation is not
    2433             :  * comprehensive.
    2434             :  *
    2435             :  * This method is the same as the C function OSRValidate().
    2436             :  *
    2437             :  * @return OGRERR_NONE if all is fine, OGRERR_CORRUPT_DATA if the SRS is
    2438             :  * not well formed, and OGRERR_UNSUPPORTED_SRS if the SRS is well formed,
    2439             :  * but contains non-standard PROJECTION[] values.
    2440             :  */
    2441             : 
    2442         116 : OGRErr OGRSpatialReference::Validate() const
    2443             : 
    2444             : {
    2445         232 :     TAKE_OPTIONAL_LOCK();
    2446             : 
    2447         154 :     for (const auto &str : d->m_wktImportErrors)
    2448             :     {
    2449          38 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2450             :     }
    2451         116 :     for (const auto &str : d->m_wktImportWarnings)
    2452             :     {
    2453           0 :         CPLDebug("OGRSpatialReference::Validate", "%s", str.c_str());
    2454             :     }
    2455         116 :     if (!d->m_pj_crs || !d->m_wktImportErrors.empty())
    2456             :     {
    2457          37 :         return OGRERR_CORRUPT_DATA;
    2458             :     }
    2459          79 :     if (!d->m_wktImportWarnings.empty())
    2460             :     {
    2461           0 :         return OGRERR_UNSUPPORTED_SRS;
    2462             :     }
    2463          79 :     return OGRERR_NONE;
    2464             : }
    2465             : 
    2466             : /************************************************************************/
    2467             : /*                            OSRValidate()                             */
    2468             : /************************************************************************/
    2469             : /**
    2470             :  * \brief Validate SRS tokens.
    2471             :  *
    2472             :  * This function is the same as the C++ method OGRSpatialReference::Validate().
    2473             :  */
    2474         114 : OGRErr OSRValidate(OGRSpatialReferenceH hSRS)
    2475             : 
    2476             : {
    2477         114 :     VALIDATE_POINTER1(hSRS, "OSRValidate", OGRERR_FAILURE);
    2478             : 
    2479         114 :     return OGRSpatialReference::FromHandle(hSRS)->Validate();
    2480             : }
    2481             : 
    2482             : /************************************************************************/
    2483             : /*                          OSRImportFromWkt()                          */
    2484             : /************************************************************************/
    2485             : 
    2486             : /**
    2487             :  * \brief Import from WKT string.
    2488             :  *
    2489             :  * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System
    2490             :  * Issues</a> page for implementation details of WKT in OGR.
    2491             :  *
    2492             :  * This function is the same as OGRSpatialReference::importFromWkt().
    2493             :  */
    2494             : 
    2495         290 : OGRErr OSRImportFromWkt(OGRSpatialReferenceH hSRS, char **ppszInput)
    2496             : 
    2497             : {
    2498         290 :     VALIDATE_POINTER1(hSRS, "OSRImportFromWkt", OGRERR_FAILURE);
    2499             : 
    2500         290 :     return ToPointer(hSRS)->importFromWkt(const_cast<const char **>(ppszInput));
    2501             : }
    2502             : 
    2503             : /************************************************************************/
    2504             : /*                              SetNode()                               */
    2505             : /************************************************************************/
    2506             : 
    2507             : /**
    2508             :  * \brief Set attribute value in spatial reference.
    2509             :  *
    2510             :  * Missing intermediate nodes in the path will be created if not already
    2511             :  * in existence.  If the attribute has no children one will be created and
    2512             :  * assigned the value otherwise the zeroth child will be assigned the value.
    2513             :  *
    2514             :  * This method does the same as the C function OSRSetAttrValue().
    2515             :  *
    2516             :  * @param pszNodePath full path to attribute to be set.  For instance
    2517             :  * "PROJCS|GEOGCS|UNIT".
    2518             :  *
    2519             :  * @param pszNewNodeValue value to be assigned to node, such as "meter".
    2520             :  * This may be NULL if you just want to force creation of the intermediate
    2521             :  * path.
    2522             :  *
    2523             :  * @return OGRERR_NONE on success.
    2524             :  */
    2525             : 
    2526         583 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath,
    2527             :                                     const char *pszNewNodeValue)
    2528             : 
    2529             : {
    2530        1166 :     TAKE_OPTIONAL_LOCK();
    2531             : 
    2532             :     char **papszPathTokens =
    2533         583 :         CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);
    2534             : 
    2535         583 :     if (CSLCount(papszPathTokens) < 1)
    2536             :     {
    2537           0 :         CSLDestroy(papszPathTokens);
    2538           0 :         return OGRERR_FAILURE;
    2539             :     }
    2540             : 
    2541        1018 :     if (GetRoot() == nullptr ||
    2542         435 :         !EQUAL(papszPathTokens[0], GetRoot()->GetValue()))
    2543             :     {
    2544         268 :         if (EQUAL(papszPathTokens[0], "PROJCS") &&
    2545         116 :             CSLCount(papszPathTokens) == 1)
    2546             :         {
    2547         116 :             CSLDestroy(papszPathTokens);
    2548         116 :             return SetProjCS(pszNewNodeValue);
    2549             :         }
    2550             :         else
    2551             :         {
    2552          36 :             SetRoot(new OGR_SRSNode(papszPathTokens[0]));
    2553             :         }
    2554             :     }
    2555             : 
    2556         467 :     OGR_SRSNode *poNode = GetRoot();
    2557         725 :     for (int i = 1; papszPathTokens[i] != nullptr; i++)
    2558             :     {
    2559         258 :         int j = 0;  // Used after for.
    2560             : 
    2561         645 :         for (; j < poNode->GetChildCount(); j++)
    2562             :         {
    2563         585 :             if (EQUAL(poNode->GetChild(j)->GetValue(), papszPathTokens[i]))
    2564             :             {
    2565         198 :                 poNode = poNode->GetChild(j);
    2566         198 :                 j = -1;
    2567         198 :                 break;
    2568             :             }
    2569             :         }
    2570             : 
    2571         258 :         if (j != -1)
    2572             :         {
    2573          60 :             OGR_SRSNode *poNewNode = new OGR_SRSNode(papszPathTokens[i]);
    2574          60 :             poNode->AddChild(poNewNode);
    2575          60 :             poNode = poNewNode;
    2576             :         }
    2577             :     }
    2578             : 
    2579         467 :     CSLDestroy(papszPathTokens);
    2580             : 
    2581         467 :     if (pszNewNodeValue != nullptr)
    2582             :     {
    2583         467 :         if (poNode->GetChildCount() > 0)
    2584         371 :             poNode->GetChild(0)->SetValue(pszNewNodeValue);
    2585             :         else
    2586          96 :             poNode->AddChild(new OGR_SRSNode(pszNewNodeValue));
    2587             :     };
    2588         467 :     return OGRERR_NONE;
    2589             : }
    2590             : 
    2591             : /************************************************************************/
    2592             : /*                          OSRSetAttrValue()                           */
    2593             : /************************************************************************/
    2594             : 
    2595             : /**
    2596             :  * \brief Set attribute value in spatial reference.
    2597             :  *
    2598             :  * This function is the same as OGRSpatialReference::SetNode()
    2599             :  */
    2600           1 : OGRErr CPL_STDCALL OSRSetAttrValue(OGRSpatialReferenceH hSRS,
    2601             :                                    const char *pszPath, const char *pszValue)
    2602             : 
    2603             : {
    2604           1 :     VALIDATE_POINTER1(hSRS, "OSRSetAttrValue", OGRERR_FAILURE);
    2605             : 
    2606           1 :     return ToPointer(hSRS)->SetNode(pszPath, pszValue);
    2607             : }
    2608             : 
    2609             : /************************************************************************/
    2610             : /*                              SetNode()                               */
    2611             : /************************************************************************/
    2612             : 
    2613             : /**
    2614             :  * \brief Set attribute value in spatial reference.
    2615             :  *
    2616             :  * Missing intermediate nodes in the path will be created if not already
    2617             :  * in existence.  If the attribute has no children one will be created and
    2618             :  * assigned the value otherwise the zeroth child will be assigned the value.
    2619             :  *
    2620             :  * This method does the same as the C function OSRSetAttrValue().
    2621             :  *
    2622             :  * @param pszNodePath full path to attribute to be set.  For instance
    2623             :  * "PROJCS|GEOGCS|UNIT".
    2624             :  *
    2625             :  * @param dfValue value to be assigned to node.
    2626             :  *
    2627             :  * @return OGRERR_NONE on success.
    2628             :  */
    2629             : 
    2630           0 : OGRErr OGRSpatialReference::SetNode(const char *pszNodePath, double dfValue)
    2631             : 
    2632             : {
    2633           0 :     char szValue[64] = {'\0'};
    2634             : 
    2635           0 :     if (std::abs(dfValue - static_cast<int>(dfValue)) == 0.0)
    2636           0 :         snprintf(szValue, sizeof(szValue), "%d", static_cast<int>(dfValue));
    2637             :     else
    2638           0 :         OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    2639             : 
    2640           0 :     return SetNode(pszNodePath, szValue);
    2641             : }
    2642             : 
    2643             : /************************************************************************/
    2644             : /*                          SetAngularUnits()                           */
    2645             : /************************************************************************/
    2646             : 
    2647             : /**
    2648             :  * \brief Set the angular units for the geographic coordinate system.
    2649             :  *
    2650             :  * This method creates a UNIT subnode with the specified values as a
    2651             :  * child of the GEOGCS node.
    2652             :  *
    2653             :  * This method does the same as the C function OSRSetAngularUnits().
    2654             :  *
    2655             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2656             :  * names can be found in ogr_srs_api.h such as SRS_UA_DEGREE.
    2657             :  *
    2658             :  * @param dfInRadians the value to multiple by an angle in the indicated
    2659             :  * units to transform to radians.  Some standard conversion factors can
    2660             :  * be found in ogr_srs_api.h.
    2661             :  *
    2662             :  * @return OGRERR_NONE on success.
    2663             :  */
    2664             : 
    2665        1346 : OGRErr OGRSpatialReference::SetAngularUnits(const char *pszUnitsName,
    2666             :                                             double dfInRadians)
    2667             : 
    2668             : {
    2669        2692 :     TAKE_OPTIONAL_LOCK();
    2670             : 
    2671        1346 :     d->bNormInfoSet = FALSE;
    2672             : 
    2673        1346 :     d->refreshProjObj();
    2674        1346 :     if (!d->m_pj_crs)
    2675           0 :         return OGRERR_FAILURE;
    2676        1346 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2677        1346 :     if (!geodCRS)
    2678           0 :         return OGRERR_FAILURE;
    2679        1346 :     proj_destroy(geodCRS);
    2680        1346 :     d->demoteFromBoundCRS();
    2681        1346 :     d->setPjCRS(proj_crs_alter_cs_angular_unit(d->getPROJContext(), d->m_pj_crs,
    2682             :                                                pszUnitsName, dfInRadians,
    2683             :                                                nullptr, nullptr));
    2684        1346 :     d->undoDemoteFromBoundCRS();
    2685             : 
    2686        1346 :     d->m_osAngularUnits = pszUnitsName;
    2687        1346 :     d->m_dfAngularUnitToRadian = dfInRadians;
    2688             : 
    2689        1346 :     return OGRERR_NONE;
    2690             : }
    2691             : 
    2692             : /************************************************************************/
    2693             : /*                         OSRSetAngularUnits()                         */
    2694             : /************************************************************************/
    2695             : 
    2696             : /**
    2697             :  * \brief Set the angular units for the geographic coordinate system.
    2698             :  *
    2699             :  * This function is the same as OGRSpatialReference::SetAngularUnits()
    2700             :  */
    2701          45 : OGRErr OSRSetAngularUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2702             :                           double dfInRadians)
    2703             : 
    2704             : {
    2705          45 :     VALIDATE_POINTER1(hSRS, "OSRSetAngularUnits", OGRERR_FAILURE);
    2706             : 
    2707          45 :     return ToPointer(hSRS)->SetAngularUnits(pszUnits, dfInRadians);
    2708             : }
    2709             : 
    2710             : /************************************************************************/
    2711             : /*                          GetAngularUnits()                           */
    2712             : /************************************************************************/
    2713             : 
    2714             : /**
    2715             :  * \brief Fetch angular geographic coordinate system units.
    2716             :  *
    2717             :  * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
    2718             :  * will be assumed.  This method only checks directly under the GEOGCS node
    2719             :  * for units.
    2720             :  *
    2721             :  * This method does the same thing as the C function OSRGetAngularUnits().
    2722             :  *
    2723             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    2724             :  * The returned value remains internal to the OGRSpatialReference and should
    2725             :  * not be freed, or modified.  It may be invalidated on the next
    2726             :  * OGRSpatialReference call.
    2727             :  *
    2728             :  * @return the value to multiply by angular distances to transform them to
    2729             :  * radians.
    2730             :  * @since GDAL 2.3.0
    2731             :  */
    2732             : 
    2733        7584 : double OGRSpatialReference::GetAngularUnits(const char **ppszName) const
    2734             : 
    2735             : {
    2736       15168 :     TAKE_OPTIONAL_LOCK();
    2737             : 
    2738        7584 :     d->refreshProjObj();
    2739             : 
    2740        7584 :     if (!d->m_osAngularUnits.empty())
    2741             :     {
    2742        2134 :         if (ppszName != nullptr)
    2743         148 :             *ppszName = d->m_osAngularUnits.c_str();
    2744        2134 :         return d->m_dfAngularUnitToRadian;
    2745             :     }
    2746             : 
    2747             :     do
    2748             :     {
    2749        5450 :         if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    2750             :         {
    2751         113 :             break;
    2752             :         }
    2753             : 
    2754             :         auto geodCRS =
    2755        5339 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    2756        5339 :         if (!geodCRS)
    2757             :         {
    2758           0 :             break;
    2759             :         }
    2760             :         auto coordSys =
    2761        5339 :             proj_crs_get_coordinate_system(d->getPROJContext(), geodCRS);
    2762        5339 :         proj_destroy(geodCRS);
    2763        5339 :         if (!coordSys)
    2764             :         {
    2765           0 :             break;
    2766             :         }
    2767        5339 :         if (proj_cs_get_type(d->getPROJContext(), coordSys) !=
    2768             :             PJ_CS_TYPE_ELLIPSOIDAL)
    2769             :         {
    2770           2 :             proj_destroy(coordSys);
    2771           2 :             break;
    2772             :         }
    2773             : 
    2774        5337 :         double dfConvFactor = 0.0;
    2775        5337 :         const char *pszUnitName = nullptr;
    2776        5337 :         if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
    2777             :                                    nullptr, nullptr, &dfConvFactor,
    2778             :                                    &pszUnitName, nullptr, nullptr))
    2779             :         {
    2780           0 :             proj_destroy(coordSys);
    2781           0 :             break;
    2782             :         }
    2783             : 
    2784        5337 :         d->m_osAngularUnits = pszUnitName;
    2785             : 
    2786        5337 :         proj_destroy(coordSys);
    2787        5337 :         d->m_dfAngularUnitToRadian = dfConvFactor;
    2788             :     } while (false);
    2789             : 
    2790        5450 :     if (d->m_osAngularUnits.empty())
    2791             :     {
    2792         113 :         d->m_osAngularUnits = "degree";
    2793         113 :         d->m_dfAngularUnitToRadian = CPLAtof(SRS_UA_DEGREE_CONV);
    2794             :     }
    2795             : 
    2796        5450 :     if (ppszName != nullptr)
    2797        3059 :         *ppszName = d->m_osAngularUnits.c_str();
    2798        5450 :     return d->m_dfAngularUnitToRadian;
    2799             : }
    2800             : 
    2801             : /**
    2802             :  * \brief Fetch angular geographic coordinate system units.
    2803             :  *
    2804             :  * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
    2805             :  * will be assumed.  This method only checks directly under the GEOGCS node
    2806             :  * for units.
    2807             :  *
    2808             :  * This method does the same thing as the C function OSRGetAngularUnits().
    2809             :  *
    2810             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    2811             :  * The returned value remains internal to the OGRSpatialReference and should
    2812             :  * not be freed, or modified.  It may be invalidated on the next
    2813             :  * OGRSpatialReference call.
    2814             :  *
    2815             :  * @return the value to multiply by angular distances to transform them to
    2816             :  * radians.
    2817             :  * @deprecated GDAL 2.3.0. Use GetAngularUnits(const char**) const.
    2818             :  */
    2819             : 
    2820           0 : double OGRSpatialReference::GetAngularUnits(char **ppszName) const
    2821             : 
    2822             : {
    2823           0 :     return GetAngularUnits(const_cast<const char **>(ppszName));
    2824             : }
    2825             : 
    2826             : /************************************************************************/
    2827             : /*                         OSRGetAngularUnits()                         */
    2828             : /************************************************************************/
    2829             : 
    2830             : /**
    2831             :  * \brief Fetch angular geographic coordinate system units.
    2832             :  *
    2833             :  * This function is the same as OGRSpatialReference::GetAngularUnits()
    2834             :  */
    2835           1 : double OSRGetAngularUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    2836             : 
    2837             : {
    2838           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAngularUnits", 0);
    2839             : 
    2840           1 :     return ToPointer(hSRS)->GetAngularUnits(
    2841           1 :         const_cast<const char **>(ppszName));
    2842             : }
    2843             : 
    2844             : /************************************************************************/
    2845             : /*                 SetLinearUnitsAndUpdateParameters()                  */
    2846             : /************************************************************************/
    2847             : 
    2848             : /**
    2849             :  * \brief Set the linear units for the projection.
    2850             :  *
    2851             :  * This method creates a UNIT subnode with the specified values as a
    2852             :  * child of the PROJCS or LOCAL_CS node.   It works the same as the
    2853             :  * SetLinearUnits() method, but it also updates all existing linear
    2854             :  * projection parameter values from the old units to the new units.
    2855             :  *
    2856             :  * @param pszName the units name to be used.  Some preferred units
    2857             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2858             :  * and SRS_UL_US_FOOT.
    2859             :  *
    2860             :  * @param dfInMeters the value to multiple by a length in the indicated
    2861             :  * units to transform to meters.  Some standard conversion factors can
    2862             :  * be found in ogr_srs_api.h.
    2863             :  *
    2864             :  * @param pszUnitAuthority Unit authority name. Or nullptr
    2865             :  *
    2866             :  * @param pszUnitCode Unit code. Or nullptr
    2867             :  *
    2868             :  * @return OGRERR_NONE on success.
    2869             :  */
    2870             : 
    2871          39 : OGRErr OGRSpatialReference::SetLinearUnitsAndUpdateParameters(
    2872             :     const char *pszName, double dfInMeters, const char *pszUnitAuthority,
    2873             :     const char *pszUnitCode)
    2874             : 
    2875             : {
    2876          78 :     TAKE_OPTIONAL_LOCK();
    2877             : 
    2878          39 :     if (dfInMeters <= 0.0)
    2879           0 :         return OGRERR_FAILURE;
    2880             : 
    2881          39 :     d->refreshProjObj();
    2882          39 :     if (!d->m_pj_crs)
    2883           0 :         return OGRERR_FAILURE;
    2884             : 
    2885          39 :     d->demoteFromBoundCRS();
    2886          39 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    2887             :     {
    2888          78 :         d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    2889          39 :             d->getPROJContext(), d->m_pj_crs, pszName, dfInMeters,
    2890             :             pszUnitAuthority, pszUnitCode, true));
    2891             :     }
    2892          39 :     d->setPjCRS(proj_crs_alter_cs_linear_unit(d->getPROJContext(), d->m_pj_crs,
    2893             :                                               pszName, dfInMeters,
    2894             :                                               pszUnitAuthority, pszUnitCode));
    2895          39 :     d->undoDemoteFromBoundCRS();
    2896             : 
    2897          39 :     d->m_osLinearUnits = pszName;
    2898          39 :     d->dfToMeter = dfInMeters;
    2899             : 
    2900          39 :     return OGRERR_NONE;
    2901             : }
    2902             : 
    2903             : /************************************************************************/
    2904             : /*                OSRSetLinearUnitsAndUpdateParameters()                */
    2905             : /************************************************************************/
    2906             : 
    2907             : /**
    2908             :  * \brief Set the linear units for the projection.
    2909             :  *
    2910             :  * This function is the same as
    2911             :  *   OGRSpatialReference::SetLinearUnitsAndUpdateParameters()
    2912             :  */
    2913           1 : OGRErr OSRSetLinearUnitsAndUpdateParameters(OGRSpatialReferenceH hSRS,
    2914             :                                             const char *pszUnits,
    2915             :                                             double dfInMeters)
    2916             : 
    2917             : {
    2918           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnitsAndUpdateParameters",
    2919             :                       OGRERR_FAILURE);
    2920             : 
    2921           1 :     return ToPointer(hSRS)->SetLinearUnitsAndUpdateParameters(pszUnits,
    2922           1 :                                                               dfInMeters);
    2923             : }
    2924             : 
    2925             : /************************************************************************/
    2926             : /*                           SetLinearUnits()                           */
    2927             : /************************************************************************/
    2928             : 
    2929             : /**
    2930             :  * \brief Set the linear units for the projection.
    2931             :  *
    2932             :  * This method creates a UNIT subnode with the specified values as a
    2933             :  * child of the PROJCS, GEOCCS, GEOGCS or LOCAL_CS node. When called on a
    2934             :  * Geographic 3D CRS the vertical axis units will be set.
    2935             :  *
    2936             :  * This method does the same as the C function OSRSetLinearUnits().
    2937             :  *
    2938             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2939             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2940             :  * and SRS_UL_US_FOOT.
    2941             :  *
    2942             :  * @param dfInMeters the value to multiple by a length in the indicated
    2943             :  * units to transform to meters.  Some standard conversion factors can
    2944             :  * be found in ogr_srs_api.h.
    2945             :  *
    2946             :  * @return OGRERR_NONE on success.
    2947             :  */
    2948             : 
    2949        7087 : OGRErr OGRSpatialReference::SetLinearUnits(const char *pszUnitsName,
    2950             :                                            double dfInMeters)
    2951             : 
    2952             : {
    2953        7087 :     return SetTargetLinearUnits(nullptr, pszUnitsName, dfInMeters);
    2954             : }
    2955             : 
    2956             : /************************************************************************/
    2957             : /*                         OSRSetLinearUnits()                          */
    2958             : /************************************************************************/
    2959             : 
    2960             : /**
    2961             :  * \brief Set the linear units for the projection.
    2962             :  *
    2963             :  * This function is the same as OGRSpatialReference::SetLinearUnits()
    2964             :  */
    2965           7 : OGRErr OSRSetLinearUnits(OGRSpatialReferenceH hSRS, const char *pszUnits,
    2966             :                          double dfInMeters)
    2967             : 
    2968             : {
    2969           7 :     VALIDATE_POINTER1(hSRS, "OSRSetLinearUnits", OGRERR_FAILURE);
    2970             : 
    2971           7 :     return ToPointer(hSRS)->SetLinearUnits(pszUnits, dfInMeters);
    2972             : }
    2973             : 
    2974             : /************************************************************************/
    2975             : /*                        SetTargetLinearUnits()                        */
    2976             : /************************************************************************/
    2977             : 
    2978             : /**
    2979             :  * \brief Set the linear units for the projection.
    2980             :  *
    2981             :  * This method creates a UNIT subnode with the specified values as a
    2982             :  * child of the target node.
    2983             :  *
    2984             :  * This method does the same as the C function OSRSetTargetLinearUnits().
    2985             :  *
    2986             :  * @param pszTargetKey the keyword to set the linear units for.
    2987             :  * i.e. "PROJCS" or "VERT_CS"
    2988             :  *
    2989             :  * @param pszUnitsName the units name to be used.  Some preferred units
    2990             :  * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
    2991             :  * and SRS_UL_US_FOOT.
    2992             :  *
    2993             :  * @param dfInMeters the value to multiple by a length in the indicated
    2994             :  * units to transform to meters.  Some standard conversion factors can
    2995             :  * be found in ogr_srs_api.h.
    2996             :  *
    2997             :  * @param pszUnitAuthority Unit authority name. Or nullptr
    2998             :  *
    2999             :  * @param pszUnitCode Unit code. Or nullptr
    3000             :  *
    3001             :  * @return OGRERR_NONE on success.
    3002             :  *
    3003             :  * @since OGR 1.9.0
    3004             :  */
    3005             : 
    3006       11230 : OGRErr OGRSpatialReference::SetTargetLinearUnits(const char *pszTargetKey,
    3007             :                                                  const char *pszUnitsName,
    3008             :                                                  double dfInMeters,
    3009             :                                                  const char *pszUnitAuthority,
    3010             :                                                  const char *pszUnitCode)
    3011             : 
    3012             : {
    3013       22460 :     TAKE_OPTIONAL_LOCK();
    3014             : 
    3015       11230 :     if (dfInMeters <= 0.0)
    3016           0 :         return OGRERR_FAILURE;
    3017             : 
    3018       11230 :     d->refreshProjObj();
    3019       11230 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    3020       11230 :     if (pszTargetKey == nullptr)
    3021             :     {
    3022       11230 :         if (!d->m_pj_crs)
    3023           0 :             return OGRERR_FAILURE;
    3024             : 
    3025       11230 :         d->demoteFromBoundCRS();
    3026       11230 :         if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3027             :         {
    3028       16674 :             d->setPjCRS(proj_crs_alter_parameters_linear_unit(
    3029        8337 :                 d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    3030             :                 pszUnitAuthority, pszUnitCode, false));
    3031             :         }
    3032       22460 :         d->setPjCRS(proj_crs_alter_cs_linear_unit(
    3033       11230 :             d->getPROJContext(), d->m_pj_crs, pszUnitsName, dfInMeters,
    3034             :             pszUnitAuthority, pszUnitCode));
    3035       11230 :         d->undoDemoteFromBoundCRS();
    3036             : 
    3037       11230 :         d->m_osLinearUnits = pszUnitsName;
    3038       11230 :         d->dfToMeter = dfInMeters;
    3039             : 
    3040       11230 :         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             :  * @since OGR 1.9.0
    3089             :  */
    3090           1 : OGRErr OSRSetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3091             :                                const char *pszTargetKey, const char *pszUnits,
    3092             :                                double dfInMeters)
    3093             : 
    3094             : {
    3095           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTargetLinearUnits", OGRERR_FAILURE);
    3096             : 
    3097           1 :     return ToPointer(hSRS)->SetTargetLinearUnits(pszTargetKey, pszUnits,
    3098           1 :                                                  dfInMeters);
    3099             : }
    3100             : 
    3101             : /************************************************************************/
    3102             : /*                           GetLinearUnits()                           */
    3103             : /************************************************************************/
    3104             : 
    3105             : /**
    3106             :  * \brief Fetch linear projection units.
    3107             :  *
    3108             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3109             :  * This method only checks directly under the PROJCS, GEOCCS, GEOGCS or
    3110             :  * LOCAL_CS node for units. When called on a Geographic 3D CRS the vertical
    3111             :  * axis units will be returned.
    3112             :  *
    3113             :  * This method does the same thing as the C function OSRGetLinearUnits()
    3114             :  *
    3115             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3116             :  * The returned value remains internal to the OGRSpatialReference and should
    3117             :  * not be freed, or modified.  It may be invalidated on the next
    3118             :  * OGRSpatialReference call.
    3119             :  *
    3120             :  * @return the value to multiply by linear distances to transform them to
    3121             :  * meters.
    3122             :  * @deprecated GDAL 2.3.0. Use GetLinearUnits(const char**) const.
    3123             :  */
    3124             : 
    3125           0 : double OGRSpatialReference::GetLinearUnits(char **ppszName) const
    3126             : 
    3127             : {
    3128           0 :     return GetTargetLinearUnits(nullptr, const_cast<const char **>(ppszName));
    3129             : }
    3130             : 
    3131             : /**
    3132             :  * \brief Fetch linear projection units.
    3133             :  *
    3134             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3135             :  * This method only checks directly under the PROJCS, GEOCCS or LOCAL_CS node
    3136             :  * for units.
    3137             :  *
    3138             :  * This method does the same thing as the C function OSRGetLinearUnits()
    3139             :  *
    3140             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3141             :  * The returned value remains internal to the OGRSpatialReference and should
    3142             :  * not be freed, or modified.  It may be invalidated on the next
    3143             :  * OGRSpatialReference call.
    3144             :  *
    3145             :  * @return the value to multiply by linear distances to transform them to
    3146             :  * meters.
    3147             :  * @since GDAL 2.3.0
    3148             :  */
    3149             : 
    3150       19610 : double OGRSpatialReference::GetLinearUnits(const char **ppszName) const
    3151             : 
    3152             : {
    3153       19610 :     return GetTargetLinearUnits(nullptr, ppszName);
    3154             : }
    3155             : 
    3156             : /************************************************************************/
    3157             : /*                         OSRGetLinearUnits()                          */
    3158             : /************************************************************************/
    3159             : 
    3160             : /**
    3161             :  * \brief Fetch linear projection units.
    3162             :  *
    3163             :  * This function is the same as OGRSpatialReference::GetLinearUnits()
    3164             :  */
    3165         227 : double OSRGetLinearUnits(OGRSpatialReferenceH hSRS, char **ppszName)
    3166             : 
    3167             : {
    3168         227 :     VALIDATE_POINTER1(hSRS, "OSRGetLinearUnits", 0);
    3169             : 
    3170         227 :     return ToPointer(hSRS)->GetLinearUnits(const_cast<const char **>(ppszName));
    3171             : }
    3172             : 
    3173             : /************************************************************************/
    3174             : /*                        GetTargetLinearUnits()                        */
    3175             : /************************************************************************/
    3176             : 
    3177             : /**
    3178             :  * \brief Fetch linear units for target.
    3179             :  *
    3180             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3181             :  *
    3182             :  * This method does the same thing as the C function OSRGetTargetLinearUnits()
    3183             :  *
    3184             :  * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
    3185             :  * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
    3186             :  * GEOCCS, GEOGCS and VERT_CS are looked up)
    3187             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3188             :  * The returned value remains internal to the OGRSpatialReference and should not
    3189             :  * be freed, or modified.  It may be invalidated on the next
    3190             :  * OGRSpatialReference call. ppszName can be set to NULL.
    3191             :  *
    3192             :  * @return the value to multiply by linear distances to transform them to
    3193             :  * meters.
    3194             :  *
    3195             :  * @since OGR 1.9.0
    3196             :  * @deprecated GDAL 2.3.0. Use GetTargetLinearUnits(const char*, const char**)
    3197             :  * const.
    3198             :  */
    3199             : 
    3200       19761 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3201             :                                                  const char **ppszName) const
    3202             : 
    3203             : {
    3204       39522 :     TAKE_OPTIONAL_LOCK();
    3205             : 
    3206       19761 :     d->refreshProjObj();
    3207             : 
    3208       19761 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    3209       19761 :     if (pszTargetKey == nullptr)
    3210             :     {
    3211             :         // Use cached result if available
    3212       19670 :         if (!d->m_osLinearUnits.empty())
    3213             :         {
    3214        8367 :             if (ppszName)
    3215        7600 :                 *ppszName = d->m_osLinearUnits.c_str();
    3216        8367 :             return d->dfToMeter;
    3217             :         }
    3218             : 
    3219             :         while (true)
    3220             :         {
    3221       11303 :             if (d->m_pj_crs == nullptr)
    3222             :             {
    3223         242 :                 break;
    3224             :             }
    3225             : 
    3226       11061 :             d->demoteFromBoundCRS();
    3227       11061 :             PJ *coordSys = nullptr;
    3228       11061 :             if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    3229             :             {
    3230          30 :                 for (int iComponent = 0; iComponent < 2; iComponent++)
    3231             :                 {
    3232          30 :                     auto subCRS = proj_crs_get_sub_crs(d->getPROJContext(),
    3233          30 :                                                        d->m_pj_crs, iComponent);
    3234          30 :                     if (subCRS && proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
    3235             :                     {
    3236             :                         auto temp =
    3237           0 :                             proj_get_source_crs(d->getPROJContext(), subCRS);
    3238           0 :                         proj_destroy(subCRS);
    3239           0 :                         subCRS = temp;
    3240             :                     }
    3241          60 :                     if (subCRS &&
    3242          30 :                         (proj_get_type(subCRS) == PJ_TYPE_PROJECTED_CRS ||
    3243          16 :                          proj_get_type(subCRS) == PJ_TYPE_ENGINEERING_CRS ||
    3244          12 :                          proj_get_type(subCRS) == PJ_TYPE_VERTICAL_CRS))
    3245             :                     {
    3246          24 :                         coordSys = proj_crs_get_coordinate_system(
    3247             :                             d->getPROJContext(), subCRS);
    3248          24 :                         proj_destroy(subCRS);
    3249          24 :                         break;
    3250             :                     }
    3251           6 :                     else if (subCRS)
    3252             :                     {
    3253           6 :                         proj_destroy(subCRS);
    3254             :                     }
    3255             :                 }
    3256          24 :                 if (coordSys == nullptr)
    3257             :                 {
    3258           0 :                     d->undoDemoteFromBoundCRS();
    3259           0 :                     break;
    3260             :                 }
    3261             :             }
    3262             :             else
    3263             :             {
    3264       11037 :                 coordSys = proj_crs_get_coordinate_system(d->getPROJContext(),
    3265       11037 :                                                           d->m_pj_crs);
    3266             :             }
    3267             : 
    3268       11061 :             d->undoDemoteFromBoundCRS();
    3269       11061 :             if (!coordSys)
    3270             :             {
    3271           0 :                 break;
    3272             :             }
    3273       11061 :             auto csType = proj_cs_get_type(d->getPROJContext(), coordSys);
    3274             : 
    3275       11061 :             if (csType != PJ_CS_TYPE_CARTESIAN &&
    3276        2173 :                 csType != PJ_CS_TYPE_VERTICAL &&
    3277           0 :                 csType != PJ_CS_TYPE_ELLIPSOIDAL &&
    3278             :                 csType != PJ_CS_TYPE_SPHERICAL)
    3279             :             {
    3280           0 :                 proj_destroy(coordSys);
    3281           0 :                 break;
    3282             :             }
    3283             : 
    3284       11061 :             int axis = 0;
    3285             : 
    3286       11061 :             if (csType == PJ_CS_TYPE_ELLIPSOIDAL ||
    3287             :                 csType == PJ_CS_TYPE_SPHERICAL)
    3288             :             {
    3289             :                 const int axisCount =
    3290        2173 :                     proj_cs_get_axis_count(d->getPROJContext(), coordSys);
    3291             : 
    3292        2173 :                 if (axisCount == 3)
    3293             :                 {
    3294           4 :                     axis = 2;
    3295             :                 }
    3296             :                 else
    3297             :                 {
    3298        2169 :                     proj_destroy(coordSys);
    3299        2169 :                     break;
    3300             :                 }
    3301             :             }
    3302             : 
    3303        8892 :             double dfConvFactor = 0.0;
    3304        8892 :             const char *pszUnitName = nullptr;
    3305        8892 :             if (!proj_cs_get_axis_info(d->getPROJContext(), coordSys, axis,
    3306             :                                        nullptr, nullptr, nullptr, &dfConvFactor,
    3307             :                                        &pszUnitName, nullptr, nullptr))
    3308             :             {
    3309           0 :                 proj_destroy(coordSys);
    3310           0 :                 break;
    3311             :             }
    3312             : 
    3313        8892 :             d->m_osLinearUnits = pszUnitName;
    3314        8892 :             d->dfToMeter = dfConvFactor;
    3315        8892 :             if (ppszName)
    3316        1162 :                 *ppszName = d->m_osLinearUnits.c_str();
    3317             : 
    3318        8892 :             proj_destroy(coordSys);
    3319        8892 :             return dfConvFactor;
    3320             :         }
    3321             : 
    3322        2411 :         d->m_osLinearUnits = "unknown";
    3323        2411 :         d->dfToMeter = 1.0;
    3324             : 
    3325        2411 :         if (ppszName != nullptr)
    3326        2225 :             *ppszName = d->m_osLinearUnits.c_str();
    3327        2411 :         return 1.0;
    3328             :     }
    3329             : 
    3330          91 :     const OGR_SRSNode *poCS = GetAttrNode(pszTargetKey);
    3331             : 
    3332          91 :     if (ppszName != nullptr)
    3333          37 :         *ppszName = "unknown";
    3334             : 
    3335          91 :     if (poCS == nullptr)
    3336          53 :         return 1.0;
    3337             : 
    3338         114 :     for (int iChild = 0; iChild < poCS->GetChildCount(); iChild++)
    3339             :     {
    3340         114 :         const OGR_SRSNode *poChild = poCS->GetChild(iChild);
    3341             : 
    3342         114 :         if (EQUAL(poChild->GetValue(), "UNIT") && poChild->GetChildCount() >= 2)
    3343             :         {
    3344          38 :             if (ppszName != nullptr)
    3345          37 :                 *ppszName = poChild->GetChild(0)->GetValue();
    3346             : 
    3347          38 :             return CPLAtof(poChild->GetChild(1)->GetValue());
    3348             :         }
    3349             :     }
    3350             : 
    3351           0 :     return 1.0;
    3352             : }
    3353             : 
    3354             : /**
    3355             :  * \brief Fetch linear units for target.
    3356             :  *
    3357             :  * If no units are available, a value of "Meters" and 1.0 will be assumed.
    3358             :  *
    3359             :  * This method does the same thing as the C function OSRGetTargetLinearUnits()
    3360             :  *
    3361             :  * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
    3362             :  * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
    3363             :  * GEOCCS and VERT_CS are looked up)
    3364             :  * @param ppszName a pointer to be updated with the pointer to the units name.
    3365             :  * The returned value remains internal to the OGRSpatialReference and should not
    3366             :  * be freed, or modified.  It may be invalidated on the next
    3367             :  * OGRSpatialReference call. ppszName can be set to NULL.
    3368             :  *
    3369             :  * @return the value to multiply by linear distances to transform them to
    3370             :  * meters.
    3371             :  *
    3372             :  * @since GDAL 2.3.0
    3373             :  */
    3374             : 
    3375           0 : double OGRSpatialReference::GetTargetLinearUnits(const char *pszTargetKey,
    3376             :                                                  char **ppszName) const
    3377             : 
    3378             : {
    3379           0 :     return GetTargetLinearUnits(pszTargetKey,
    3380           0 :                                 const_cast<const char **>(ppszName));
    3381             : }
    3382             : 
    3383             : /************************************************************************/
    3384             : /*                      OSRGetTargetLinearUnits()                       */
    3385             : /************************************************************************/
    3386             : 
    3387             : /**
    3388             :  * \brief Fetch linear projection units.
    3389             :  *
    3390             :  * This function is the same as OGRSpatialReference::GetTargetLinearUnits()
    3391             :  *
    3392             :  * @since OGR 1.9.0
    3393             :  */
    3394           4 : double OSRGetTargetLinearUnits(OGRSpatialReferenceH hSRS,
    3395             :                                const char *pszTargetKey, char **ppszName)
    3396             : 
    3397             : {
    3398           4 :     VALIDATE_POINTER1(hSRS, "OSRGetTargetLinearUnits", 0);
    3399             : 
    3400           4 :     return ToPointer(hSRS)->GetTargetLinearUnits(
    3401           4 :         pszTargetKey, const_cast<const char **>(ppszName));
    3402             : }
    3403             : 
    3404             : /************************************************************************/
    3405             : /*                          GetPrimeMeridian()                          */
    3406             : /************************************************************************/
    3407             : 
    3408             : /**
    3409             :  * \brief Fetch prime meridian info.
    3410             :  *
    3411             :  * Returns the offset of the prime meridian from greenwich in degrees,
    3412             :  * and the prime meridian name (if requested).   If no PRIMEM value exists
    3413             :  * in the coordinate system definition a value of "Greenwich" and an
    3414             :  * offset of 0.0 is assumed.
    3415             :  *
    3416             :  * If the prime meridian name is returned, the pointer is to an internal
    3417             :  * copy of the name. It should not be freed, altered or depended on after
    3418             :  * the next OGR call.
    3419             :  *
    3420             :  * This method is the same as the C function OSRGetPrimeMeridian().
    3421             :  *
    3422             :  * @param ppszName return location for prime meridian name.  If NULL, name
    3423             :  * is not returned.
    3424             :  *
    3425             :  * @return the offset to the GEOGCS prime meridian from greenwich in decimal
    3426             :  * degrees.
    3427             :  * @deprecated GDAL 2.3.0. Use GetPrimeMeridian(const char**) const.
    3428             :  */
    3429             : 
    3430        1434 : double OGRSpatialReference::GetPrimeMeridian(const char **ppszName) const
    3431             : 
    3432             : {
    3433        2868 :     TAKE_OPTIONAL_LOCK();
    3434             : 
    3435        1434 :     d->refreshProjObj();
    3436             : 
    3437        1434 :     if (!d->m_osPrimeMeridianName.empty())
    3438             :     {
    3439          60 :         if (ppszName != nullptr)
    3440           1 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3441          60 :         return d->dfFromGreenwich;
    3442             :     }
    3443             : 
    3444             :     while (true)
    3445             :     {
    3446        1374 :         if (!d->m_pj_crs)
    3447           0 :             break;
    3448             : 
    3449        1374 :         auto pm = proj_get_prime_meridian(d->getPROJContext(), d->m_pj_crs);
    3450        1374 :         if (!pm)
    3451           0 :             break;
    3452             : 
    3453        1374 :         d->m_osPrimeMeridianName = proj_get_name(pm);
    3454        1374 :         if (ppszName)
    3455          30 :             *ppszName = d->m_osPrimeMeridianName.c_str();
    3456        1374 :         double dfLongitude = 0.0;
    3457        1374 :         double dfConvFactor = 0.0;
    3458        1374 :         proj_prime_meridian_get_parameters(
    3459             :             d->getPROJContext(), pm, &dfLongitude, &dfConvFactor, nullptr);
    3460        1374 :         proj_destroy(pm);
    3461        2748 :         d->dfFromGreenwich =
    3462        1374 :             dfLongitude * dfConvFactor / CPLAtof(SRS_UA_DEGREE_CONV);
    3463        1374 :         return d->dfFromGreenwich;
    3464             :     }
    3465             : 
    3466           0 :     d->m_osPrimeMeridianName = SRS_PM_GREENWICH;
    3467           0 :     d->dfFromGreenwich = 0.0;
    3468           0 :     if (ppszName != nullptr)
    3469           0 :         *ppszName = d->m_osPrimeMeridianName.c_str();
    3470           0 :     return d->dfFromGreenwich;
    3471             : }
    3472             : 
    3473             : /**
    3474             :  * \brief Fetch prime meridian info.
    3475             :  *
    3476             :  * Returns the offset of the prime meridian from greenwich in degrees,
    3477             :  * and the prime meridian name (if requested).   If no PRIMEM value exists
    3478             :  * in the coordinate system definition a value of "Greenwich" and an
    3479             :  * offset of 0.0 is assumed.
    3480             :  *
    3481             :  * If the prime meridian name is returned, the pointer is to an internal
    3482             :  * copy of the name. It should not be freed, altered or depended on after
    3483             :  * the next OGR call.
    3484             :  *
    3485             :  * This method is the same as the C function OSRGetPrimeMeridian().
    3486             :  *
    3487             :  * @param ppszName return location for prime meridian name.  If NULL, name
    3488             :  * is not returned.
    3489             :  *
    3490             :  * @return the offset to the GEOGCS prime meridian from greenwich in decimal
    3491             :  * degrees.
    3492             :  * @since GDAL 2.3.0
    3493             :  */
    3494             : 
    3495           0 : double OGRSpatialReference::GetPrimeMeridian(char **ppszName) const
    3496             : 
    3497             : {
    3498           0 :     return GetPrimeMeridian(const_cast<const char **>(ppszName));
    3499             : }
    3500             : 
    3501             : /************************************************************************/
    3502             : /*                        OSRGetPrimeMeridian()                         */
    3503             : /************************************************************************/
    3504             : 
    3505             : /**
    3506             :  * \brief Fetch prime meridian info.
    3507             :  *
    3508             :  * This function is the same as OGRSpatialReference::GetPrimeMeridian()
    3509             :  */
    3510           0 : double OSRGetPrimeMeridian(OGRSpatialReferenceH hSRS, char **ppszName)
    3511             : 
    3512             : {
    3513           0 :     VALIDATE_POINTER1(hSRS, "OSRGetPrimeMeridian", 0);
    3514             : 
    3515           0 :     return ToPointer(hSRS)->GetPrimeMeridian(
    3516           0 :         const_cast<const char **>(ppszName));
    3517             : }
    3518             : 
    3519             : /************************************************************************/
    3520             : /*                             SetGeogCS()                              */
    3521             : /************************************************************************/
    3522             : 
    3523             : /**
    3524             :  * \brief Set geographic coordinate system.
    3525             :  *
    3526             :  * This method is used to set the datum, ellipsoid, prime meridian and
    3527             :  * angular units for a geographic coordinate system.  It can be used on its
    3528             :  * own to establish a geographic spatial reference, or applied to a
    3529             :  * projected coordinate system to establish the underlying geographic
    3530             :  * coordinate system.
    3531             :  *
    3532             :  * This method does the same as the C function OSRSetGeogCS().
    3533             :  *
    3534             :  * @param pszGeogName user visible name for the geographic coordinate system
    3535             :  * (not to serve as a key).
    3536             :  *
    3537             :  * @param pszDatumName key name for this datum.  The OpenGIS specification
    3538             :  * lists some known values, and otherwise EPSG datum names with a standard
    3539             :  * transformation are considered legal keys.
    3540             :  *
    3541             :  * @param pszSpheroidName user visible spheroid name (not to serve as a key)
    3542             :  *
    3543             :  * @param dfSemiMajor the semi major axis of the spheroid.
    3544             :  *
    3545             :  * @param dfInvFlattening the inverse flattening for the spheroid.
    3546             :  * This can be computed from the semi minor axis as
    3547             :  * 1/f = 1.0 / (1.0 - semiminor/semimajor).
    3548             :  *
    3549             :  * @param pszPMName the name of the prime meridian (not to serve as a key)
    3550             :  * If this is NULL a default value of "Greenwich" will be used.
    3551             :  *
    3552             :  * @param dfPMOffset the longitude of Greenwich relative to this prime
    3553             :  * meridian. Always in Degrees
    3554             :  *
    3555             :  * @param pszAngularUnits the angular units name (see ogr_srs_api.h for some
    3556             :  * standard names).  If NULL a value of "degrees" will be assumed.
    3557             :  *
    3558             :  * @param dfConvertToRadians value to multiply angular units by to transform
    3559             :  * them to radians.  A value of SRS_UA_DEGREE_CONV will be used if
    3560             :  * pszAngularUnits is NULL.
    3561             :  *
    3562             :  * @return OGRERR_NONE on success.
    3563             :  */
    3564             : 
    3565        9216 : OGRErr OGRSpatialReference::SetGeogCS(
    3566             :     const char *pszGeogName, const char *pszDatumName,
    3567             :     const char *pszSpheroidName, double dfSemiMajor, double dfInvFlattening,
    3568             :     const char *pszPMName, double dfPMOffset, const char *pszAngularUnits,
    3569             :     double dfConvertToRadians)
    3570             : 
    3571             : {
    3572       18432 :     TAKE_OPTIONAL_LOCK();
    3573             : 
    3574        9216 :     d->bNormInfoSet = FALSE;
    3575        9216 :     d->m_osAngularUnits.clear();
    3576        9216 :     d->m_dfAngularUnitToRadian = 0.0;
    3577        9216 :     d->m_osPrimeMeridianName.clear();
    3578        9216 :     d->dfFromGreenwich = 0.0;
    3579             : 
    3580             :     /* -------------------------------------------------------------------- */
    3581             :     /*      For a geocentric coordinate system we want to set the datum     */
    3582             :     /*      and ellipsoid based on the GEOGCS.  Create the GEOGCS in a      */
    3583             :     /*      temporary srs and use the copy method which has special         */
    3584             :     /*      handling for GEOCCS.                                            */
    3585             :     /* -------------------------------------------------------------------- */
    3586        9216 :     if (IsGeocentric())
    3587             :     {
    3588           4 :         OGRSpatialReference oGCS;
    3589             : 
    3590           2 :         oGCS.SetGeogCS(pszGeogName, pszDatumName, pszSpheroidName, dfSemiMajor,
    3591             :                        dfInvFlattening, pszPMName, dfPMOffset, pszAngularUnits,
    3592             :                        dfConvertToRadians);
    3593           2 :         return CopyGeogCSFrom(&oGCS);
    3594             :     }
    3595             : 
    3596        9214 :     auto cs = proj_create_ellipsoidal_2D_cs(
    3597             :         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE, pszAngularUnits,
    3598             :         dfConvertToRadians);
    3599             :     // Prime meridian expressed in Degree
    3600        9214 :     auto obj = proj_create_geographic_crs(
    3601             :         d->getPROJContext(), pszGeogName, pszDatumName, pszSpheroidName,
    3602             :         dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset, nullptr, 0.0, cs);
    3603        9214 :     proj_destroy(cs);
    3604             : 
    3605       13897 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    3606        4683 :         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    3607             :     {
    3608        4531 :         d->setPjCRS(obj);
    3609             :     }
    3610        4683 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3611             :     {
    3612        9366 :         d->setPjCRS(
    3613        4683 :             proj_crs_alter_geodetic_crs(d->getPROJContext(), d->m_pj_crs, obj));
    3614        4683 :         proj_destroy(obj);
    3615             :     }
    3616             :     else
    3617             :     {
    3618           0 :         proj_destroy(obj);
    3619             :     }
    3620             : 
    3621        9214 :     return OGRERR_NONE;
    3622             : }
    3623             : 
    3624             : /************************************************************************/
    3625             : /*                            OSRSetGeogCS()                            */
    3626             : /************************************************************************/
    3627             : 
    3628             : /**
    3629             :  * \brief Set geographic coordinate system.
    3630             :  *
    3631             :  * This function is the same as OGRSpatialReference::SetGeogCS()
    3632             :  */
    3633          18 : OGRErr OSRSetGeogCS(OGRSpatialReferenceH hSRS, const char *pszGeogName,
    3634             :                     const char *pszDatumName, const char *pszSpheroidName,
    3635             :                     double dfSemiMajor, double dfInvFlattening,
    3636             :                     const char *pszPMName, double dfPMOffset,
    3637             :                     const char *pszAngularUnits, double dfConvertToRadians)
    3638             : 
    3639             : {
    3640          18 :     VALIDATE_POINTER1(hSRS, "OSRSetGeogCS", OGRERR_FAILURE);
    3641             : 
    3642          18 :     return ToPointer(hSRS)->SetGeogCS(pszGeogName, pszDatumName,
    3643             :                                       pszSpheroidName, dfSemiMajor,
    3644             :                                       dfInvFlattening, pszPMName, dfPMOffset,
    3645          18 :                                       pszAngularUnits, dfConvertToRadians);
    3646             : }
    3647             : 
    3648             : /************************************************************************/
    3649             : /*                         SetWellKnownGeogCS()                         */
    3650             : /************************************************************************/
    3651             : 
    3652             : /**
    3653             :  * \brief Set a GeogCS based on well known name.
    3654             :  *
    3655             :  * This may be called on an empty OGRSpatialReference to make a geographic
    3656             :  * coordinate system, or on something with an existing PROJCS node to
    3657             :  * set the underlying geographic coordinate system of a projected coordinate
    3658             :  * system.
    3659             :  *
    3660             :  * The following well known text values are currently supported,
    3661             :  * Except for "EPSG:n", the others are without dependency on EPSG data files:
    3662             :  * <ul>
    3663             :  * <li> "EPSG:n": where n is the code a Geographic coordinate reference system.
    3664             :  * <li> "WGS84": same as "EPSG:4326" (axis order lat/long).
    3665             :  * <li> "WGS72": same as "EPSG:4322" (axis order lat/long).
    3666             :  * <li> "NAD83": same as "EPSG:4269" (axis order lat/long).
    3667             :  * <li> "NAD27": same as "EPSG:4267" (axis order lat/long).
    3668             :  * <li> "CRS84", "CRS:84": same as "WGS84" but with axis order long/lat.
    3669             :  * <li> "CRS72", "CRS:72": same as "WGS72" but with axis order long/lat.
    3670             :  * <li> "CRS27", "CRS:27": same as "NAD27" but with axis order long/lat.
    3671             :  * </ul>
    3672             :  *
    3673             :  * @param pszName name of well known geographic coordinate system.
    3674             :  * @return OGRERR_NONE on success, or OGRERR_FAILURE if the name isn't
    3675             :  * recognised, the target object is already initialized, or an EPSG value
    3676             :  * can't be successfully looked up.
    3677             :  */
    3678             : 
    3679        2925 : OGRErr OGRSpatialReference::SetWellKnownGeogCS(const char *pszName)
    3680             : 
    3681             : {
    3682        5850 :     TAKE_OPTIONAL_LOCK();
    3683             : 
    3684             :     /* -------------------------------------------------------------------- */
    3685             :     /*      Check for EPSG authority numbers.                               */
    3686             :     /* -------------------------------------------------------------------- */
    3687        2925 :     if (STARTS_WITH_CI(pszName, "EPSG:") || STARTS_WITH_CI(pszName, "EPSGA:"))
    3688             :     {
    3689          84 :         OGRSpatialReference oSRS2;
    3690          42 :         const OGRErr eErr = oSRS2.importFromEPSG(atoi(pszName + 5));
    3691          42 :         if (eErr != OGRERR_NONE)
    3692           0 :             return eErr;
    3693             : 
    3694          42 :         if (!oSRS2.IsGeographic())
    3695           0 :             return OGRERR_FAILURE;
    3696             : 
    3697          42 :         return CopyGeogCSFrom(&oSRS2);
    3698             :     }
    3699             : 
    3700             :     /* -------------------------------------------------------------------- */
    3701             :     /*      Check for simple names.                                         */
    3702             :     /* -------------------------------------------------------------------- */
    3703        2883 :     const char *pszWKT = nullptr;
    3704             : 
    3705        2883 :     if (EQUAL(pszName, "WGS84"))
    3706             :     {
    3707        2098 :         pszWKT = SRS_WKT_WGS84_LAT_LONG;
    3708             :     }
    3709         785 :     else if (EQUAL(pszName, "CRS84") || EQUAL(pszName, "CRS:84"))
    3710             :     {
    3711         604 :         pszWKT =
    3712             :             "GEOGCRS[\"WGS 84 (CRS84)\",DATUM[\"World Geodetic System 1984\","
    3713             :             "ELLIPSOID[\"WGS "
    3714             :             "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],"
    3715             :             "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3716             :             "CS[ellipsoidal,2],AXIS[\"geodetic longitude (Lon)\",east,ORDER[1],"
    3717             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3718             :             "AXIS[\"geodetic latitude (Lat)\",north,ORDER[2],"
    3719             :             "ANGLEUNIT[\"degree\",0.0174532925199433]],"
    3720             :             "USAGE[SCOPE[\"unknown\"],AREA[\"World\"],BBOX[-90,-180,90,180]],"
    3721             :             "ID[\"OGC\",\"CRS84\"]]";
    3722             :     }
    3723         181 :     else if (EQUAL(pszName, "WGS72"))
    3724          19 :         pszWKT =
    3725             :             "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
    3726             :             "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",\"7043\"]],"
    3727             :             "AUTHORITY[\"EPSG\",\"6322\"]],"
    3728             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3729             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3730             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3731             :             "AUTHORITY[\"EPSG\",\"4322\"]]";
    3732             : 
    3733         162 :     else if (EQUAL(pszName, "NAD27"))
    3734         134 :         pszWKT =
    3735             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3736             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3737             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3738             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3739             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3740             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],"
    3741             :             "AUTHORITY[\"EPSG\",\"4267\"]]";
    3742             : 
    3743          28 :     else if (EQUAL(pszName, "CRS27") || EQUAL(pszName, "CRS:27"))
    3744           0 :         pszWKT =
    3745             :             "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
    3746             :             "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
    3747             :             "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
    3748             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3749             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3750             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3751             : 
    3752          28 :     else if (EQUAL(pszName, "NAD83"))
    3753          24 :         pszWKT =
    3754             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3755             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3756             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3757             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3758             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3759             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3760             :             "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],AUTHORITY["
    3761             :             "\"EPSG\",\"4269\"]]";
    3762             : 
    3763           4 :     else if (EQUAL(pszName, "CRS83") || EQUAL(pszName, "CRS:83"))
    3764           0 :         pszWKT =
    3765             :             "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
    3766             :             "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
    3767             :             "AUTHORITY[\"EPSG\",\"7019\"]],"
    3768             :             "AUTHORITY[\"EPSG\",\"6269\"]],"
    3769             :             "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
    3770             :             "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
    3771             :             "AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]]";
    3772             : 
    3773             :     else
    3774           4 :         return OGRERR_FAILURE;
    3775             : 
    3776             :     /* -------------------------------------------------------------------- */
    3777             :     /*      Import the WKT                                                  */
    3778             :     /* -------------------------------------------------------------------- */
    3779        5758 :     OGRSpatialReference oSRS2;
    3780        2879 :     const OGRErr eErr = oSRS2.importFromWkt(pszWKT);
    3781        2879 :     if (eErr != OGRERR_NONE)
    3782           0 :         return eErr;
    3783             : 
    3784             :     /* -------------------------------------------------------------------- */
    3785             :     /*      Copy over.                                                      */
    3786             :     /* -------------------------------------------------------------------- */
    3787        2879 :     return CopyGeogCSFrom(&oSRS2);
    3788             : }
    3789             : 
    3790             : /************************************************************************/
    3791             : /*                       OSRSetWellKnownGeogCS()                        */
    3792             : /************************************************************************/
    3793             : 
    3794             : /**
    3795             :  * \brief Set a GeogCS based on well known name.
    3796             :  *
    3797             :  * This function is the same as OGRSpatialReference::SetWellKnownGeogCS()
    3798             :  */
    3799         134 : OGRErr OSRSetWellKnownGeogCS(OGRSpatialReferenceH hSRS, const char *pszName)
    3800             : 
    3801             : {
    3802         134 :     VALIDATE_POINTER1(hSRS, "OSRSetWellKnownGeogCS", OGRERR_FAILURE);
    3803             : 
    3804         134 :     return ToPointer(hSRS)->SetWellKnownGeogCS(pszName);
    3805             : }
    3806             : 
    3807             : /************************************************************************/
    3808             : /*                           CopyGeogCSFrom()                           */
    3809             : /************************************************************************/
    3810             : 
    3811             : /**
    3812             :  * \brief Copy GEOGCS from another OGRSpatialReference.
    3813             :  *
    3814             :  * The GEOGCS information is copied into this OGRSpatialReference from another.
    3815             :  * If this object has a PROJCS root already, the GEOGCS is installed within
    3816             :  * it, otherwise it is installed as the root.
    3817             :  *
    3818             :  * @param poSrcSRS the spatial reference to copy the GEOGCS information from.
    3819             :  *
    3820             :  * @return OGRERR_NONE on success or an error code.
    3821             :  */
    3822             : 
    3823        3515 : OGRErr OGRSpatialReference::CopyGeogCSFrom(const OGRSpatialReference *poSrcSRS)
    3824             : 
    3825             : {
    3826        7030 :     TAKE_OPTIONAL_LOCK();
    3827             : 
    3828        3515 :     d->bNormInfoSet = FALSE;
    3829        3515 :     d->m_osAngularUnits.clear();
    3830        3515 :     d->m_dfAngularUnitToRadian = 0.0;
    3831        3515 :     d->m_osPrimeMeridianName.clear();
    3832        3515 :     d->dfFromGreenwich = 0.0;
    3833             : 
    3834        3515 :     d->refreshProjObj();
    3835        3515 :     poSrcSRS->d->refreshProjObj();
    3836        3515 :     if (!poSrcSRS->d->m_pj_crs)
    3837             :     {
    3838           1 :         return OGRERR_FAILURE;
    3839             :     }
    3840             :     auto geodCRS =
    3841        3514 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3842        3514 :     if (!geodCRS)
    3843             :     {
    3844           0 :         return OGRERR_FAILURE;
    3845             :     }
    3846             : 
    3847             :     /* -------------------------------------------------------------------- */
    3848             :     /*      Handle geocentric coordinate systems specially.  We just        */
    3849             :     /*      want to copy the DATUM.                                         */
    3850             :     /* -------------------------------------------------------------------- */
    3851        3514 :     if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    3852             :     {
    3853           3 :         auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    3854             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    3855             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    3856             :         if (datum == nullptr)
    3857             :         {
    3858             :             datum = proj_crs_get_datum_ensemble(d->getPROJContext(), geodCRS);
    3859             :         }
    3860             : #endif
    3861           3 :         if (datum == nullptr)
    3862             :         {
    3863           0 :             proj_destroy(geodCRS);
    3864           0 :             return OGRERR_FAILURE;
    3865             :         }
    3866             : 
    3867           3 :         const char *pszUnitName = nullptr;
    3868           3 :         double unitConvFactor = GetLinearUnits(&pszUnitName);
    3869             : 
    3870           3 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    3871           3 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, pszUnitName,
    3872             :             unitConvFactor);
    3873           3 :         proj_destroy(datum);
    3874             : 
    3875           3 :         d->setPjCRS(pj_crs);
    3876             :     }
    3877             : 
    3878        3511 :     else if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    3879             :     {
    3880         324 :         auto pj_crs = proj_crs_alter_geodetic_crs(d->getPROJContext(),
    3881         324 :                                                   d->m_pj_crs, geodCRS);
    3882         324 :         d->setPjCRS(pj_crs);
    3883             :     }
    3884             : 
    3885             :     else
    3886             :     {
    3887        3187 :         d->setPjCRS(proj_clone(d->getPROJContext(), geodCRS));
    3888             :     }
    3889             : 
    3890             :     // Apply TOWGS84 of source CRS
    3891        3514 :     if (poSrcSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
    3892             :     {
    3893             :         auto target =
    3894           1 :             proj_get_target_crs(d->getPROJContext(), poSrcSRS->d->m_pj_crs);
    3895           1 :         auto co = proj_crs_get_coordoperation(d->getPROJContext(),
    3896           1 :                                               poSrcSRS->d->m_pj_crs);
    3897           1 :         d->setPjCRS(proj_crs_create_bound_crs(d->getPROJContext(), d->m_pj_crs,
    3898             :                                               target, co));
    3899           1 :         proj_destroy(target);
    3900           1 :         proj_destroy(co);
    3901             :     }
    3902             : 
    3903        3514 :     proj_destroy(geodCRS);
    3904             : 
    3905        3514 :     return OGRERR_NONE;
    3906             : }
    3907             : 
    3908             : /************************************************************************/
    3909             : /*                         OSRCopyGeogCSFrom()                          */
    3910             : /************************************************************************/
    3911             : 
    3912             : /**
    3913             :  * \brief Copy GEOGCS from another OGRSpatialReference.
    3914             :  *
    3915             :  * This function is the same as OGRSpatialReference::CopyGeogCSFrom()
    3916             :  */
    3917           1 : OGRErr OSRCopyGeogCSFrom(OGRSpatialReferenceH hSRS,
    3918             :                          const OGRSpatialReferenceH hSrcSRS)
    3919             : 
    3920             : {
    3921           1 :     VALIDATE_POINTER1(hSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3922           1 :     VALIDATE_POINTER1(hSrcSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE);
    3923             : 
    3924           1 :     return ToPointer(hSRS)->CopyGeogCSFrom(ToPointer(hSrcSRS));
    3925             : }
    3926             : 
    3927             : /************************************************************************/
    3928             : /*                   SET_FROM_USER_INPUT_LIMITATIONS_get()              */
    3929             : /************************************************************************/
    3930             : 
    3931             : /** Limitations for OGRSpatialReference::SetFromUserInput().
    3932             :  *
    3933             :  * Currently ALLOW_NETWORK_ACCESS=NO and ALLOW_FILE_ACCESS=NO.
    3934             :  */
    3935             : const char *const OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS[] = {
    3936             :     "ALLOW_NETWORK_ACCESS=NO", "ALLOW_FILE_ACCESS=NO", nullptr};
    3937             : 
    3938             : /**
    3939             :  * \brief Return OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS
    3940             :  */
    3941        2687 : CSLConstList OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()
    3942             : {
    3943        2687 :     return SET_FROM_USER_INPUT_LIMITATIONS;
    3944             : }
    3945             : 
    3946             : /************************************************************************/
    3947             : /*                      RemoveIDFromMemberOfEnsembles()                 */
    3948             : /************************************************************************/
    3949             : 
    3950             : // cppcheck-suppress constParameterReference
    3951         198 : static void RemoveIDFromMemberOfEnsembles(CPLJSONObject &obj)
    3952             : {
    3953             :     // Remove "id" from members of datum ensembles for compatibility with
    3954             :     // older PROJ versions
    3955             :     // Cf https://github.com/opengeospatial/geoparquet/discussions/110
    3956             :     // and https://github.com/OSGeo/PROJ/pull/3221
    3957         198 :     if (obj.GetType() == CPLJSONObject::Type::Object)
    3958             :     {
    3959         243 :         for (auto &subObj : obj.GetChildren())
    3960             :         {
    3961         191 :             RemoveIDFromMemberOfEnsembles(subObj);
    3962             :         }
    3963             :     }
    3964         162 :     else if (obj.GetType() == CPLJSONObject::Type::Array &&
    3965         162 :              obj.GetName() == "members")
    3966             :     {
    3967          51 :         for (auto &subObj : obj.ToArray())
    3968             :         {
    3969          44 :             if (subObj.GetType() == CPLJSONObject::Type::Object)
    3970             :             {
    3971          43 :                 subObj.Delete("id");
    3972             :             }
    3973             :         }
    3974             :     }
    3975         198 : }
    3976             : 
    3977             : /************************************************************************/
    3978             : /*                          SetFromUserInput()                          */
    3979             : /************************************************************************/
    3980             : 
    3981             : /**
    3982             :  * \brief Set spatial reference from various text formats.
    3983             :  *
    3984             :  * This method will examine the provided input, and try to deduce the
    3985             :  * format, and then use it to initialize the spatial reference system.  It
    3986             :  * may take the following forms:
    3987             :  *
    3988             :  * <ol>
    3989             :  * <li> Well Known Text definition - passed on to importFromWkt().
    3990             :  * <li> "EPSG:n" - number passed on to importFromEPSG().
    3991             :  * <li> "EPSGA:n" - number passed on to importFromEPSGA().
    3992             :  * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
    3993             :  * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
    3994             :  * <li> PROJ.4 definitions - passed on to importFromProj4().
    3995             :  * <li> filename - file read for WKT, XML or PROJ.4 definition.
    3996             :  * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
    3997             :  * WGS84 or WGS72.
    3998             :  * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
    3999             :  * <li> PROJJSON (PROJ &gt;= 6.2)
    4000             :  * </ol>
    4001             :  *
    4002             :  * It is expected that this method will be extended in the future to support
    4003             :  * XML and perhaps a simplified "minilanguage" for indicating common UTM and
    4004             :  * State Plane definitions.
    4005             :  *
    4006             :  * This method is intended to be flexible, but by its nature it is
    4007             :  * imprecise as it must guess information about the format intended.  When
    4008             :  * possible applications should call the specific method appropriate if the
    4009             :  * input is known to be in a particular format.
    4010             :  *
    4011             :  * This method does the same thing as the OSRSetFromUserInput() function.
    4012             :  *
    4013             :  * @param pszDefinition text definition to try to deduce SRS from.
    4014             :  *
    4015             :  * @return OGRERR_NONE on success, or an error code if the name isn't
    4016             :  * recognised, the definition is corrupt, or an EPSG value can't be
    4017             :  * successfully looked up.
    4018             :  */
    4019             : 
    4020       18295 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition)
    4021             : {
    4022       18295 :     return SetFromUserInput(pszDefinition, nullptr);
    4023             : }
    4024             : 
    4025             : /**
    4026             :  * \brief Set spatial reference from various text formats.
    4027             :  *
    4028             :  * This method will examine the provided input, and try to deduce the
    4029             :  * format, and then use it to initialize the spatial reference system.  It
    4030             :  * may take the following forms:
    4031             :  *
    4032             :  * <ol>
    4033             :  * <li> Well Known Text definition - passed on to importFromWkt().
    4034             :  * <li> "EPSG:n" - number passed on to importFromEPSG().
    4035             :  * <li> "EPSGA:n" - number passed on to importFromEPSGA().
    4036             :  * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
    4037             :  * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
    4038             :  * <li> PROJ.4 definitions - passed on to importFromProj4().
    4039             :  * <li> filename - file read for WKT, XML or PROJ.4 definition.
    4040             :  * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
    4041             :  * WGS84 or WGS72.
    4042             :  * <li> "IGNF:xxxx", "ESRI:xxxx", etc. from definitions from the PROJ database;
    4043             :  * <li> PROJJSON (PROJ &gt;= 6.2)
    4044             :  * </ol>
    4045             :  *
    4046             :  * It is expected that this method will be extended in the future to support
    4047             :  * XML and perhaps a simplified "minilanguage" for indicating common UTM and
    4048             :  * State Plane definitions.
    4049             :  *
    4050             :  * This method is intended to be flexible, but by its nature it is
    4051             :  * imprecise as it must guess information about the format intended.  When
    4052             :  * possible applications should call the specific method appropriate if the
    4053             :  * input is known to be in a particular format.
    4054             :  *
    4055             :  * This method does the same thing as the OSRSetFromUserInput() and
    4056             :  * OSRSetFromUserInputEx() functions.
    4057             :  *
    4058             :  * @param pszDefinition text definition to try to deduce SRS from.
    4059             :  *
    4060             :  * @param papszOptions NULL terminated list of options, or NULL.
    4061             :  * <ol>
    4062             :  * <li> ALLOW_NETWORK_ACCESS=YES/NO.
    4063             :  *      Whether http:// or https:// access is allowed. Defaults to YES.
    4064             :  * <li> ALLOW_FILE_ACCESS=YES/NO.
    4065             :  *      Whether reading a file using the Virtual File System layer is allowed
    4066             :  *      (can also involve network access). Defaults to YES.
    4067             :  * </ol>
    4068             :  *
    4069             :  * @return OGRERR_NONE on success, or an error code if the name isn't
    4070             :  * recognised, the definition is corrupt, or an EPSG value can't be
    4071             :  * successfully looked up.
    4072             :  */
    4073             : 
    4074       25153 : OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition,
    4075             :                                              CSLConstList papszOptions)
    4076             : {
    4077       50306 :     TAKE_OPTIONAL_LOCK();
    4078             : 
    4079             :     // Skip leading white space
    4080       25155 :     while (isspace(static_cast<unsigned char>(*pszDefinition)))
    4081           2 :         pszDefinition++;
    4082             : 
    4083       25153 :     if (STARTS_WITH_CI(pszDefinition, "ESRI::"))
    4084             :     {
    4085           1 :         pszDefinition += 6;
    4086             :     }
    4087             : 
    4088             :     /* -------------------------------------------------------------------- */
    4089             :     /*      Is it a recognised syntax?                                      */
    4090             :     /* -------------------------------------------------------------------- */
    4091       25153 :     const char *const wktKeywords[] = {
    4092             :         // WKT1
    4093             :         "GEOGCS", "GEOCCS", "PROJCS", "VERT_CS", "COMPD_CS", "LOCAL_CS",
    4094             :         // WKT2"
    4095             :         "GEODCRS", "GEOGCRS", "GEODETICCRS", "GEOGRAPHICCRS", "PROJCRS",
    4096             :         "PROJECTEDCRS", "VERTCRS", "VERTICALCRS", "COMPOUNDCRS", "ENGCRS",
    4097             :         "ENGINEERINGCRS", "BOUNDCRS", "DERIVEDPROJCRS", "COORDINATEMETADATA"};
    4098      368414 :     for (const char *keyword : wktKeywords)
    4099             :     {
    4100      351807 :         if (STARTS_WITH_CI(pszDefinition, keyword))
    4101             :         {
    4102        8546 :             return importFromWkt(pszDefinition);
    4103             :         }
    4104             :     }
    4105             : 
    4106       16607 :     const bool bStartsWithEPSG = STARTS_WITH_CI(pszDefinition, "EPSG:");
    4107       16607 :     if (bStartsWithEPSG || STARTS_WITH_CI(pszDefinition, "EPSGA:"))
    4108             :     {
    4109        9505 :         OGRErr eStatus = OGRERR_NONE;
    4110             : 
    4111        9505 :         if (strchr(pszDefinition, '+') || strchr(pszDefinition, '@'))
    4112             :         {
    4113             :             // Use proj_create() as it allows things like EPSG:3157+4617
    4114             :             // that are not normally supported by the below code that
    4115             :             // builds manually a compound CRS
    4116          62 :             PJ *pj = proj_create(d->getPROJContext(), pszDefinition);
    4117          62 :             if (!pj)
    4118             :             {
    4119           1 :                 return OGRERR_FAILURE;
    4120             :             }
    4121          61 :             Clear();
    4122          61 :             d->setPjCRS(pj);
    4123          61 :             return OGRERR_NONE;
    4124             :         }
    4125             :         else
    4126             :         {
    4127             :             eStatus =
    4128        9443 :                 importFromEPSG(atoi(pszDefinition + (bStartsWithEPSG ? 5 : 6)));
    4129             :         }
    4130             : 
    4131        9443 :         return eStatus;
    4132             :     }
    4133             : 
    4134        7102 :     if (STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs:") ||
    4135        6403 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs,crs:") ||
    4136        6402 :         STARTS_WITH_CI(pszDefinition, "urn:x-ogc:def:crs:") ||
    4137        6344 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:crs:") ||
    4138        6344 :         STARTS_WITH_CI(pszDefinition, "urn:opengis:def:crs:") ||
    4139        6344 :         STARTS_WITH_CI(pszDefinition, "urn:ogc:def:coordinateMetadata:"))
    4140         758 :         return importFromURN(pszDefinition);
    4141             : 
    4142        6344 :     if (STARTS_WITH_CI(pszDefinition, "http://opengis.net/def/crs") ||
    4143        6342 :         STARTS_WITH_CI(pszDefinition, "https://opengis.net/def/crs") ||
    4144        6341 :         STARTS_WITH_CI(pszDefinition, "http://www.opengis.net/def/crs") ||
    4145        1152 :         STARTS_WITH_CI(pszDefinition, "https://www.opengis.net/def/crs") ||
    4146        1151 :         STARTS_WITH_CI(pszDefinition, "www.opengis.net/def/crs"))
    4147        5193 :         return importFromCRSURL(pszDefinition);
    4148             : 
    4149        1151 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    4150           1 :         return importFromWMSAUTO(pszDefinition);
    4151             : 
    4152             :     // WMS/WCS OGC codes like OGC:CRS84.
    4153        1150 :     if (EQUAL(pszDefinition, "OGC:CRS84"))
    4154          76 :         return SetWellKnownGeogCS(pszDefinition + 4);
    4155             : 
    4156        1074 :     if (STARTS_WITH_CI(pszDefinition, "CRS:"))
    4157           1 :         return SetWellKnownGeogCS(pszDefinition);
    4158             : 
    4159        1073 :     if (STARTS_WITH_CI(pszDefinition, "DICT:") && strstr(pszDefinition, ","))
    4160             :     {
    4161           0 :         char *pszFile = CPLStrdup(pszDefinition + 5);
    4162           0 :         char *pszCode = strstr(pszFile, ",") + 1;
    4163             : 
    4164           0 :         pszCode[-1] = '\0';
    4165             : 
    4166           0 :         OGRErr err = importFromDict(pszFile, pszCode);
    4167           0 :         CPLFree(pszFile);
    4168             : 
    4169           0 :         return err;
    4170             :     }
    4171             : 
    4172        1073 :     if (EQUAL(pszDefinition, "NAD27") || EQUAL(pszDefinition, "NAD83") ||
    4173        1069 :         EQUAL(pszDefinition, "WGS84") || EQUAL(pszDefinition, "WGS72"))
    4174             :     {
    4175         303 :         Clear();
    4176         303 :         return SetWellKnownGeogCS(pszDefinition);
    4177             :     }
    4178             : 
    4179             :     // PROJJSON
    4180         770 :     if (pszDefinition[0] == '{' && strstr(pszDefinition, "\"type\"") &&
    4181          56 :         (strstr(pszDefinition, "GeodeticCRS") ||
    4182          56 :          strstr(pszDefinition, "GeographicCRS") ||
    4183          24 :          strstr(pszDefinition, "ProjectedCRS") ||
    4184           0 :          strstr(pszDefinition, "VerticalCRS") ||
    4185           0 :          strstr(pszDefinition, "BoundCRS") ||
    4186           0 :          strstr(pszDefinition, "CompoundCRS") ||
    4187           0 :          strstr(pszDefinition, "DerivedGeodeticCRS") ||
    4188           0 :          strstr(pszDefinition, "DerivedGeographicCRS") ||
    4189           0 :          strstr(pszDefinition, "DerivedProjectedCRS") ||
    4190           0 :          strstr(pszDefinition, "DerivedVerticalCRS") ||
    4191           0 :          strstr(pszDefinition, "EngineeringCRS") ||
    4192           0 :          strstr(pszDefinition, "DerivedEngineeringCRS") ||
    4193           0 :          strstr(pszDefinition, "ParametricCRS") ||
    4194           0 :          strstr(pszDefinition, "DerivedParametricCRS") ||
    4195           0 :          strstr(pszDefinition, "TemporalCRS") ||
    4196           0 :          strstr(pszDefinition, "DerivedTemporalCRS")))
    4197             :     {
    4198             :         PJ *pj;
    4199          56 :         if (strstr(pszDefinition, "datum_ensemble") != nullptr)
    4200             :         {
    4201             :             // PROJ < 9.0.1 doesn't like a datum_ensemble whose member have
    4202             :             // a unknown id.
    4203           7 :             CPLJSONDocument oCRSDoc;
    4204           7 :             if (!oCRSDoc.LoadMemory(pszDefinition))
    4205           0 :                 return OGRERR_CORRUPT_DATA;
    4206           7 :             CPLJSONObject oCRSRoot = oCRSDoc.GetRoot();
    4207           7 :             RemoveIDFromMemberOfEnsembles(oCRSRoot);
    4208           7 :             pj = proj_create(d->getPROJContext(), oCRSRoot.ToString().c_str());
    4209             :         }
    4210             :         else
    4211             :         {
    4212          49 :             pj = proj_create(d->getPROJContext(), pszDefinition);
    4213             :         }
    4214          56 :         if (!pj)
    4215             :         {
    4216           2 :             return OGRERR_FAILURE;
    4217             :         }
    4218          54 :         Clear();
    4219          54 :         d->setPjCRS(pj);
    4220          54 :         return OGRERR_NONE;
    4221             :     }
    4222             : 
    4223         714 :     if (strstr(pszDefinition, "+proj") != nullptr ||
    4224         278 :         strstr(pszDefinition, "+init") != nullptr)
    4225         436 :         return importFromProj4(pszDefinition);
    4226             : 
    4227         278 :     if (STARTS_WITH_CI(pszDefinition, "http://") ||
    4228         278 :         STARTS_WITH_CI(pszDefinition, "https://"))
    4229             :     {
    4230           1 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
    4231             :                                              "ALLOW_NETWORK_ACCESS", "YES")))
    4232           0 :             return importFromUrl(pszDefinition);
    4233             : 
    4234           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4235             :                  "Cannot import %s due to ALLOW_NETWORK_ACCESS=NO",
    4236             :                  pszDefinition);
    4237           1 :         return OGRERR_FAILURE;
    4238             :     }
    4239             : 
    4240         277 :     if (EQUAL(pszDefinition, "osgb:BNG"))
    4241             :     {
    4242          13 :         return importFromEPSG(27700);
    4243             :     }
    4244             : 
    4245             :     // Used by German CityGML files
    4246         264 :     if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH"))
    4247             :     {
    4248             :         // "ETRS89 / UTM Zone 32N + DHHN92 height"
    4249           0 :         return SetFromUserInput("EPSG:25832+5783");
    4250             :     }
    4251         264 :     else if (EQUAL(pszDefinition, "urn:adv:crs:ETRS89_UTM32*DE_DHHN2016_NH"))
    4252             :     {
    4253             :         // "ETRS89 / UTM Zone 32N + DHHN2016 height"
    4254           4 :         return SetFromUserInput("EPSG:25832+7837");
    4255             :     }
    4256             : 
    4257             :     // Used by  Japan's Fundamental Geospatial Data (FGD) GML
    4258         260 :     if (EQUAL(pszDefinition, "fguuid:jgd2001.bl"))
    4259           0 :         return importFromEPSG(4612);  // JGD2000 (slight difference in years)
    4260         260 :     else if (EQUAL(pszDefinition, "fguuid:jgd2011.bl"))
    4261          10 :         return importFromEPSG(6668);  // JGD2011
    4262         250 :     else if (EQUAL(pszDefinition, "fguuid:jgd2024.bl"))
    4263             :     {
    4264             :         // FIXME when EPSG attributes a CRS code
    4265           4 :         return importFromWkt(
    4266             :             "GEOGCRS[\"JGD2024\",\n"
    4267             :             "    DATUM[\"Japanese Geodetic Datum 2024\",\n"
    4268             :             "        ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
    4269             :             "            LENGTHUNIT[\"metre\",1]]],\n"
    4270             :             "    PRIMEM[\"Greenwich\",0,\n"
    4271             :             "        ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4272             :             "    CS[ellipsoidal,2],\n"
    4273             :             "        AXIS[\"geodetic latitude (Lat)\",north,\n"
    4274             :             "            ORDER[1],\n"
    4275             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4276             :             "        AXIS[\"geodetic longitude (Lon)\",east,\n"
    4277             :             "            ORDER[2],\n"
    4278             :             "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    4279             :             "    USAGE[\n"
    4280             :             "        SCOPE[\"Horizontal component of 3D system.\"],\n"
    4281             :             "        AREA[\"Japan - onshore and offshore.\"],\n"
    4282           4 :             "        BBOX[17.09,122.38,46.05,157.65]]]");
    4283             :     }
    4284             : 
    4285             :     // Deal with IGNF:xxx, ESRI:xxx, etc from the PROJ database
    4286         246 :     const char *pszDot = strrchr(pszDefinition, ':');
    4287         246 :     if (pszDot)
    4288             :     {
    4289         111 :         CPLString osPrefix(pszDefinition, pszDot - pszDefinition);
    4290             :         auto authorities =
    4291         111 :             proj_get_authorities_from_database(d->getPROJContext());
    4292         111 :         if (authorities)
    4293             :         {
    4294         111 :             std::set<std::string> aosCandidateAuthorities;
    4295         232 :             for (auto iter = authorities; *iter; ++iter)
    4296             :             {
    4297         231 :                 if (*iter == osPrefix)
    4298             :                 {
    4299         110 :                     aosCandidateAuthorities.clear();
    4300         110 :                     aosCandidateAuthorities.insert(*iter);
    4301         110 :                     break;
    4302             :                 }
    4303             :                 // Deal with "IAU_2015" as authority in the list and input
    4304             :                 // "IAU:code"
    4305         121 :                 else if (strncmp(*iter, osPrefix.c_str(), osPrefix.size()) ==
    4306         121 :                              0 &&
    4307           0 :                          (*iter)[osPrefix.size()] == '_')
    4308             :                 {
    4309           0 :                     aosCandidateAuthorities.insert(*iter);
    4310             :                 }
    4311             :                 // Deal with "IAU_2015" as authority in the list and input
    4312             :                 // "IAU:2015:code"
    4313         242 :                 else if (osPrefix.find(':') != std::string::npos &&
    4314         121 :                          osPrefix.size() == strlen(*iter) &&
    4315         121 :                          CPLString(osPrefix).replaceAll(':', '_') == *iter)
    4316             :                 {
    4317           0 :                     aosCandidateAuthorities.clear();
    4318           0 :                     aosCandidateAuthorities.insert(*iter);
    4319           0 :                     break;
    4320             :                 }
    4321             :             }
    4322             : 
    4323         111 :             proj_string_list_destroy(authorities);
    4324             : 
    4325         111 :             if (!aosCandidateAuthorities.empty())
    4326             :             {
    4327         110 :                 auto obj = proj_create_from_database(
    4328             :                     d->getPROJContext(),
    4329         110 :                     aosCandidateAuthorities.rbegin()->c_str(), pszDot + 1,
    4330             :                     PJ_CATEGORY_CRS, false, nullptr);
    4331         110 :                 if (!obj)
    4332             :                 {
    4333           3 :                     return OGRERR_FAILURE;
    4334             :                 }
    4335         107 :                 Clear();
    4336         107 :                 d->setPjCRS(obj);
    4337         107 :                 return OGRERR_NONE;
    4338             :             }
    4339             :         }
    4340             :     }
    4341             : 
    4342             :     /* -------------------------------------------------------------------- */
    4343             :     /*      Try to open it as a file.                                       */
    4344             :     /* -------------------------------------------------------------------- */
    4345         136 :     if (!CPLTestBool(
    4346             :             CSLFetchNameValueDef(papszOptions, "ALLOW_FILE_ACCESS", "YES")))
    4347             :     {
    4348             :         VSIStatBufL sStat;
    4349          20 :         if (STARTS_WITH(pszDefinition, "/vsi") ||
    4350          10 :             VSIStatExL(pszDefinition, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
    4351             :         {
    4352           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4353             :                      "Cannot import %s due to ALLOW_FILE_ACCESS=NO",
    4354             :                      pszDefinition);
    4355           0 :             return OGRERR_FAILURE;
    4356             :         }
    4357             :         // We used to silently return an error without a CE_Failure message
    4358             :         // Cf https://github.com/Toblerity/Fiona/issues/1063
    4359          10 :         return OGRERR_CORRUPT_DATA;
    4360             :     }
    4361             : 
    4362         252 :     CPLConfigOptionSetter oSetter("CPL_ALLOW_VSISTDIN", "NO", true);
    4363         126 :     VSILFILE *const fp = VSIFOpenL(pszDefinition, "rt");
    4364         126 :     if (fp == nullptr)
    4365         123 :         return OGRERR_CORRUPT_DATA;
    4366             : 
    4367           3 :     const size_t nBufMax = 100000;
    4368           3 :     char *const pszBuffer = static_cast<char *>(CPLMalloc(nBufMax));
    4369           3 :     const size_t nBytes = VSIFReadL(pszBuffer, 1, nBufMax - 1, fp);
    4370           3 :     VSIFCloseL(fp);
    4371             : 
    4372           3 :     if (nBytes == nBufMax - 1)
    4373             :     {
    4374           0 :         CPLDebug("OGR",
    4375             :                  "OGRSpatialReference::SetFromUserInput(%s), opened file "
    4376             :                  "but it is to large for our generous buffer.  Is it really "
    4377             :                  "just a WKT definition?",
    4378             :                  pszDefinition);
    4379           0 :         CPLFree(pszBuffer);
    4380           0 :         return OGRERR_FAILURE;
    4381             :     }
    4382             : 
    4383           3 :     pszBuffer[nBytes] = '\0';
    4384             : 
    4385           3 :     char *pszBufPtr = pszBuffer;
    4386           3 :     while (pszBufPtr[0] == ' ' || pszBufPtr[0] == '\n')
    4387           0 :         pszBufPtr++;
    4388             : 
    4389           3 :     OGRErr err = OGRERR_NONE;
    4390           3 :     if (pszBufPtr[0] == '<')
    4391           0 :         err = importFromXML(pszBufPtr);
    4392           3 :     else if ((strstr(pszBuffer, "+proj") != nullptr ||
    4393           3 :               strstr(pszBuffer, "+init") != nullptr) &&
    4394           0 :              (strstr(pszBuffer, "EXTENSION") == nullptr &&
    4395           0 :               strstr(pszBuffer, "extension") == nullptr))
    4396           0 :         err = importFromProj4(pszBufPtr);
    4397             :     else
    4398             :     {
    4399           3 :         if (STARTS_WITH_CI(pszBufPtr, "ESRI::"))
    4400             :         {
    4401           0 :             pszBufPtr += 6;
    4402             :         }
    4403             : 
    4404             :         // coverity[tainted_data]
    4405           3 :         err = importFromWkt(pszBufPtr);
    4406             :     }
    4407             : 
    4408           3 :     CPLFree(pszBuffer);
    4409             : 
    4410           3 :     return err;
    4411             : }
    4412             : 
    4413             : /************************************************************************/
    4414             : /*                        OSRSetFromUserInput()                         */
    4415             : /************************************************************************/
    4416             : 
    4417             : /**
    4418             :  * \brief Set spatial reference from various text formats.
    4419             :  *
    4420             :  * This function is the same as OGRSpatialReference::SetFromUserInput()
    4421             :  *
    4422             :  * \see OSRSetFromUserInputEx() for a variant allowing to pass options.
    4423             :  */
    4424         299 : OGRErr CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS,
    4425             :                                        const char *pszDef)
    4426             : 
    4427             : {
    4428         299 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInput", OGRERR_FAILURE);
    4429             : 
    4430         299 :     return ToPointer(hSRS)->SetFromUserInput(pszDef);
    4431             : }
    4432             : 
    4433             : /************************************************************************/
    4434             : /*                       OSRSetFromUserInputEx()                        */
    4435             : /************************************************************************/
    4436             : 
    4437             : /**
    4438             :  * \brief Set spatial reference from various text formats.
    4439             :  *
    4440             :  * This function is the same as OGRSpatialReference::SetFromUserInput().
    4441             :  *
    4442             :  * @since GDAL 3.9
    4443             :  */
    4444         975 : OGRErr OSRSetFromUserInputEx(OGRSpatialReferenceH hSRS, const char *pszDef,
    4445             :                              CSLConstList papszOptions)
    4446             : 
    4447             : {
    4448         975 :     VALIDATE_POINTER1(hSRS, "OSRSetFromUserInputEx", OGRERR_FAILURE);
    4449             : 
    4450         975 :     return ToPointer(hSRS)->SetFromUserInput(pszDef, papszOptions);
    4451             : }
    4452             : 
    4453             : /************************************************************************/
    4454             : /*                          ImportFromUrl()                             */
    4455             : /************************************************************************/
    4456             : 
    4457             : /**
    4458             :  * \brief Set spatial reference from a URL.
    4459             :  *
    4460             :  * This method will download the spatial reference at a given URL and
    4461             :  * feed it into SetFromUserInput for you.
    4462             :  *
    4463             :  * This method does the same thing as the OSRImportFromUrl() function.
    4464             :  *
    4465             :  * @param pszUrl text definition to try to deduce SRS from.
    4466             :  *
    4467             :  * @return OGRERR_NONE on success, or an error code with the curl
    4468             :  * error message if it is unable to download data.
    4469             :  */
    4470             : 
    4471           5 : OGRErr OGRSpatialReference::importFromUrl(const char *pszUrl)
    4472             : 
    4473             : {
    4474          10 :     TAKE_OPTIONAL_LOCK();
    4475             : 
    4476           5 :     if (!STARTS_WITH_CI(pszUrl, "http://") &&
    4477           3 :         !STARTS_WITH_CI(pszUrl, "https://"))
    4478             :     {
    4479           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    4480             :                  "The given string is not recognized as a URL"
    4481             :                  "starting with 'http://' -- %s",
    4482             :                  pszUrl);
    4483           2 :         return OGRERR_FAILURE;
    4484             :     }
    4485             : 
    4486             :     /* -------------------------------------------------------------------- */
    4487             :     /*      Fetch the result.                                               */
    4488             :     /* -------------------------------------------------------------------- */
    4489           3 :     CPLErrorReset();
    4490             : 
    4491           6 :     std::string osUrl(pszUrl);
    4492             :     // We have historically supported "http://spatialreference.org/ref/AUTHNAME/CODE/"
    4493             :     // as a valid URL since we used a "Accept: application/x-ogcwkt" header
    4494             :     // to query WKT. To allow a static server to be used, rather append a
    4495             :     // "ogcwkt/" suffix.
    4496           2 :     for (const char *pszPrefix : {"https://spatialreference.org/ref/",
    4497           5 :                                   "http://spatialreference.org/ref/"})
    4498             :     {
    4499           5 :         if (STARTS_WITH(pszUrl, pszPrefix))
    4500             :         {
    4501             :             const CPLStringList aosTokens(
    4502           6 :                 CSLTokenizeString2(pszUrl + strlen(pszPrefix), "/", 0));
    4503           3 :             if (aosTokens.size() == 2)
    4504             :             {
    4505           2 :                 osUrl = "https://spatialreference.org/ref/";
    4506           2 :                 osUrl += aosTokens[0];  // authority
    4507           2 :                 osUrl += '/';
    4508           2 :                 osUrl += aosTokens[1];  // code
    4509           2 :                 osUrl += "/ogcwkt/";
    4510             :             }
    4511           3 :             break;
    4512             :         }
    4513             :     }
    4514             : 
    4515           3 :     const char *pszTimeout = "TIMEOUT=10";
    4516           3 :     char *apszOptions[] = {const_cast<char *>(pszTimeout), nullptr};
    4517             : 
    4518           3 :     CPLHTTPResult *psResult = CPLHTTPFetch(osUrl.c_str(), apszOptions);
    4519             : 
    4520             :     /* -------------------------------------------------------------------- */
    4521             :     /*      Try to handle errors.                                           */
    4522             :     /* -------------------------------------------------------------------- */
    4523             : 
    4524           3 :     if (psResult == nullptr)
    4525           0 :         return OGRERR_FAILURE;
    4526           6 :     if (psResult->nDataLen == 0 || CPLGetLastErrorNo() != 0 ||
    4527           3 :         psResult->pabyData == nullptr)
    4528             :     {
    4529           0 :         if (CPLGetLastErrorNo() == 0)
    4530             :         {
    4531           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4532             :                      "No data was returned from the given URL");
    4533             :         }
    4534           0 :         CPLHTTPDestroyResult(psResult);
    4535           0 :         return OGRERR_FAILURE;
    4536             :     }
    4537             : 
    4538           3 :     if (psResult->nStatus != 0)
    4539             :     {
    4540           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
    4541             :                  psResult->nStatus, psResult->pszErrBuf);
    4542           0 :         CPLHTTPDestroyResult(psResult);
    4543           0 :         return OGRERR_FAILURE;
    4544             :     }
    4545             : 
    4546           3 :     const char *pszData = reinterpret_cast<const char *>(psResult->pabyData);
    4547           3 :     if (STARTS_WITH_CI(pszData, "http://") ||
    4548           3 :         STARTS_WITH_CI(pszData, "https://"))
    4549             :     {
    4550           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4551             :                  "The data that was downloaded also starts with 'http://' "
    4552             :                  "and cannot be passed into SetFromUserInput.  Is this "
    4553             :                  "really a spatial reference definition? ");
    4554           0 :         CPLHTTPDestroyResult(psResult);
    4555           0 :         return OGRERR_FAILURE;
    4556             :     }
    4557           3 :     if (OGRERR_NONE != SetFromUserInput(pszData))
    4558             :     {
    4559           0 :         CPLHTTPDestroyResult(psResult);
    4560           0 :         return OGRERR_FAILURE;
    4561             :     }
    4562             : 
    4563           3 :     CPLHTTPDestroyResult(psResult);
    4564           3 :     return OGRERR_NONE;
    4565             : }
    4566             : 
    4567             : /************************************************************************/
    4568             : /*                        OSRimportFromUrl()                            */
    4569             : /************************************************************************/
    4570             : 
    4571             : /**
    4572             :  * \brief Set spatial reference from a URL.
    4573             :  *
    4574             :  * This function is the same as OGRSpatialReference::importFromUrl()
    4575             :  */
    4576           3 : OGRErr OSRImportFromUrl(OGRSpatialReferenceH hSRS, const char *pszUrl)
    4577             : 
    4578             : {
    4579           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromUrl", OGRERR_FAILURE);
    4580             : 
    4581           3 :     return ToPointer(hSRS)->importFromUrl(pszUrl);
    4582             : }
    4583             : 
    4584             : /************************************************************************/
    4585             : /*                         importFromURNPart()                          */
    4586             : /************************************************************************/
    4587        5384 : OGRErr OGRSpatialReference::importFromURNPart(const char *pszAuthority,
    4588             :                                               const char *pszCode,
    4589             :                                               const char *pszURN)
    4590             : {
    4591             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4592             :     (void)this;
    4593             :     (void)pszAuthority;
    4594             :     (void)pszCode;
    4595             :     (void)pszURN;
    4596             :     return OGRERR_FAILURE;
    4597             : #else
    4598             :     /* -------------------------------------------------------------------- */
    4599             :     /*      Is this an EPSG code? Note that we import it with EPSG          */
    4600             :     /*      preferred axis ordering for geographic coordinate systems.      */
    4601             :     /* -------------------------------------------------------------------- */
    4602        5384 :     if (STARTS_WITH_CI(pszAuthority, "EPSG"))
    4603        4854 :         return importFromEPSGA(atoi(pszCode));
    4604             : 
    4605             :     /* -------------------------------------------------------------------- */
    4606             :     /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
    4607             :     /* -------------------------------------------------------------------- */
    4608         530 :     if (STARTS_WITH_CI(pszAuthority, "IAU"))
    4609           0 :         return importFromDict("IAU2000.wkt", pszCode);
    4610             : 
    4611             :     /* -------------------------------------------------------------------- */
    4612             :     /*      Is this an OGC code?                                            */
    4613             :     /* -------------------------------------------------------------------- */
    4614         530 :     if (!STARTS_WITH_CI(pszAuthority, "OGC"))
    4615             :     {
    4616           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4617             :                  "URN %s has unrecognized authority.", pszURN);
    4618           1 :         return OGRERR_FAILURE;
    4619             :     }
    4620             : 
    4621         529 :     if (STARTS_WITH_CI(pszCode, "CRS84"))
    4622         517 :         return SetWellKnownGeogCS(pszCode);
    4623          12 :     else if (STARTS_WITH_CI(pszCode, "CRS83"))
    4624           0 :         return SetWellKnownGeogCS(pszCode);
    4625          12 :     else if (STARTS_WITH_CI(pszCode, "CRS27"))
    4626           0 :         return SetWellKnownGeogCS(pszCode);
    4627          12 :     else if (STARTS_WITH_CI(pszCode, "84"))  // urn:ogc:def:crs:OGC:2:84
    4628          10 :         return SetWellKnownGeogCS("CRS84");
    4629             : 
    4630             :     /* -------------------------------------------------------------------- */
    4631             :     /*      Handle auto codes.  We need to convert from format              */
    4632             :     /*      AUTO42001:99:8888 to format AUTO:42001,99,8888.                 */
    4633             :     /* -------------------------------------------------------------------- */
    4634           2 :     else if (STARTS_WITH_CI(pszCode, "AUTO"))
    4635             :     {
    4636           2 :         char szWMSAuto[100] = {'\0'};
    4637             : 
    4638           2 :         if (strlen(pszCode) > sizeof(szWMSAuto) - 2)
    4639           0 :             return OGRERR_FAILURE;
    4640             : 
    4641           2 :         snprintf(szWMSAuto, sizeof(szWMSAuto), "AUTO:%s", pszCode + 4);
    4642          28 :         for (int i = 5; szWMSAuto[i] != '\0'; i++)
    4643             :         {
    4644          26 :             if (szWMSAuto[i] == ':')
    4645           4 :                 szWMSAuto[i] = ',';
    4646             :         }
    4647             : 
    4648           2 :         return importFromWMSAUTO(szWMSAuto);
    4649             :     }
    4650             : 
    4651             :     /* -------------------------------------------------------------------- */
    4652             :     /*      Not a recognise OGC item.                                       */
    4653             :     /* -------------------------------------------------------------------- */
    4654           0 :     CPLError(CE_Failure, CPLE_AppDefined, "URN %s value not supported.",
    4655             :              pszURN);
    4656             : 
    4657           0 :     return OGRERR_FAILURE;
    4658             : #endif
    4659             : }
    4660             : 
    4661             : /************************************************************************/
    4662             : /*                           importFromURN()                            */
    4663             : /*                                                                      */
    4664             : /*      See OGC recommendation paper 06-023r1 or later for details.     */
    4665             : /************************************************************************/
    4666             : 
    4667             : /**
    4668             :  * \brief Initialize from OGC URN.
    4669             :  *
    4670             :  * Initializes this spatial reference from a coordinate system defined
    4671             :  * by an OGC URN prefixed with "urn:ogc:def:crs:" per recommendation
    4672             :  * paper 06-023r1.  Currently EPSG and OGC authority values are supported,
    4673             :  * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
    4674             :  *
    4675             :  * This method is also support through SetFromUserInput() which can
    4676             :  * normally be used for URNs.
    4677             :  *
    4678             :  * @param pszURN the urn string.
    4679             :  *
    4680             :  * @return OGRERR_NONE on success or an error code.
    4681             :  */
    4682             : 
    4683         819 : OGRErr OGRSpatialReference::importFromURN(const char *pszURN)
    4684             : 
    4685             : {
    4686         819 :     constexpr const char *EPSG_URN_CRS_PREFIX = "urn:ogc:def:crs:EPSG::";
    4687        1550 :     if (STARTS_WITH(pszURN, EPSG_URN_CRS_PREFIX) &&
    4688         731 :         CPLGetValueType(pszURN + strlen(EPSG_URN_CRS_PREFIX)) ==
    4689             :             CPL_VALUE_INTEGER)
    4690             :     {
    4691         729 :         return importFromEPSG(atoi(pszURN + strlen(EPSG_URN_CRS_PREFIX)));
    4692             :     }
    4693             : 
    4694         180 :     TAKE_OPTIONAL_LOCK();
    4695             : 
    4696             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4697             : 
    4698             :     // PROJ 8.2.0 has support for IAU codes now.
    4699             : #if !PROJ_AT_LEAST_VERSION(8, 2, 0)
    4700             :     /* -------------------------------------------------------------------- */
    4701             :     /*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
    4702             :     /* -------------------------------------------------------------------- */
    4703             :     const char *pszIAU = strstr(pszURN, "IAU");
    4704             :     if (pszIAU)
    4705             :     {
    4706             :         const char *pszCode = strchr(pszIAU, ':');
    4707             :         if (pszCode)
    4708             :         {
    4709             :             ++pszCode;
    4710             :             if (*pszCode == ':')
    4711             :                 ++pszCode;
    4712             :             return importFromDict("IAU2000.wkt", pszCode);
    4713             :         }
    4714             :     }
    4715             : #endif
    4716             : 
    4717             :     if (strlen(pszURN) >= 1000)
    4718             :     {
    4719             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4720             :         return OGRERR_CORRUPT_DATA;
    4721             :     }
    4722             :     auto obj = proj_create(d->getPROJContext(), pszURN);
    4723             :     if (!obj)
    4724             :     {
    4725             :         return OGRERR_FAILURE;
    4726             :     }
    4727             :     Clear();
    4728             :     d->setPjCRS(obj);
    4729             :     return OGRERR_NONE;
    4730             : #else
    4731          90 :     const char *pszCur = nullptr;
    4732             : 
    4733          90 :     if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs:"))
    4734          25 :         pszCur = pszURN + 16;
    4735          65 :     else if (STARTS_WITH_CI(pszURN, "urn:ogc:def:crs,crs:"))
    4736           1 :         pszCur = pszURN + 20;
    4737          64 :     else if (STARTS_WITH_CI(pszURN, "urn:x-ogc:def:crs:"))
    4738          62 :         pszCur = pszURN + 18;
    4739           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:crs:"))
    4740           0 :         pszCur = pszURN + 16;
    4741           2 :     else if (STARTS_WITH_CI(pszURN, "urn:opengis:def:crs:"))
    4742           0 :         pszCur = pszURN + 20;
    4743             :     else
    4744             :     {
    4745           2 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4746             :                  pszURN);
    4747           2 :         return OGRERR_FAILURE;
    4748             :     }
    4749             : 
    4750             :     /* -------------------------------------------------------------------- */
    4751             :     /*      Clear any existing definition.                                  */
    4752             :     /* -------------------------------------------------------------------- */
    4753          88 :     Clear();
    4754             : 
    4755             :     /* -------------------------------------------------------------------- */
    4756             :     /*      Find code (ignoring version) out of string like:                */
    4757             :     /*                                                                      */
    4758             :     /*      authority:[version]:code                                        */
    4759             :     /* -------------------------------------------------------------------- */
    4760          88 :     const char *pszAuthority = pszCur;
    4761             : 
    4762             :     // skip authority
    4763         421 :     while (*pszCur != ':' && *pszCur)
    4764         333 :         pszCur++;
    4765          88 :     if (*pszCur == ':')
    4766          88 :         pszCur++;
    4767             : 
    4768             :     // skip version
    4769          88 :     const char *pszBeforeVersion = pszCur;
    4770         398 :     while (*pszCur != ':' && *pszCur)
    4771         310 :         pszCur++;
    4772          88 :     if (*pszCur == ':')
    4773          60 :         pszCur++;
    4774             :     else
    4775             :         // We come here in the case, the content to parse is authority:code
    4776             :         // (instead of authority::code) which is probably illegal according to
    4777             :         // http://www.opengeospatial.org/ogcUrnPolicy but such content is found
    4778             :         // for example in what is returned by GeoServer.
    4779          28 :         pszCur = pszBeforeVersion;
    4780             : 
    4781          88 :     const char *pszCode = pszCur;
    4782             : 
    4783          88 :     const char *pszComma = strchr(pszCur, ',');
    4784          88 :     if (pszComma == nullptr)
    4785          87 :         return importFromURNPart(pszAuthority, pszCode, pszURN);
    4786             : 
    4787             :     // There's a second part with the vertical SRS.
    4788           1 :     pszCur = pszComma + 1;
    4789           1 :     if (!STARTS_WITH(pszCur, "crs:"))
    4790             :     {
    4791           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URN %s not a supported format.",
    4792             :                  pszURN);
    4793           0 :         return OGRERR_FAILURE;
    4794             :     }
    4795             : 
    4796           1 :     pszCur += 4;
    4797             : 
    4798           1 :     char *pszFirstCode = CPLStrdup(pszCode);
    4799           1 :     pszFirstCode[pszComma - pszCode] = '\0';
    4800           1 :     OGRErr eStatus = importFromURNPart(pszAuthority, pszFirstCode, pszURN);
    4801           1 :     CPLFree(pszFirstCode);
    4802             : 
    4803             :     // Do we want to turn this into a compound definition
    4804             :     // with a vertical datum?
    4805           1 :     if (eStatus != OGRERR_NONE)
    4806           0 :         return eStatus;
    4807             : 
    4808             :     /* -------------------------------------------------------------------- */
    4809             :     /*      Find code (ignoring version) out of string like:                */
    4810             :     /*                                                                      */
    4811             :     /*      authority:[version]:code                                        */
    4812             :     /* -------------------------------------------------------------------- */
    4813           1 :     pszAuthority = pszCur;
    4814             : 
    4815             :     // skip authority
    4816           5 :     while (*pszCur != ':' && *pszCur)
    4817           4 :         pszCur++;
    4818           1 :     if (*pszCur == ':')
    4819           1 :         pszCur++;
    4820             : 
    4821             :     // skip version
    4822           1 :     pszBeforeVersion = pszCur;
    4823           1 :     while (*pszCur != ':' && *pszCur)
    4824           0 :         pszCur++;
    4825           1 :     if (*pszCur == ':')
    4826           1 :         pszCur++;
    4827             :     else
    4828           0 :         pszCur = pszBeforeVersion;
    4829             : 
    4830           1 :     pszCode = pszCur;
    4831             : 
    4832           2 :     OGRSpatialReference oVertSRS;
    4833           1 :     eStatus = oVertSRS.importFromURNPart(pszAuthority, pszCode, pszURN);
    4834           1 :     if (eStatus == OGRERR_NONE)
    4835             :     {
    4836           1 :         OGRSpatialReference oHorizSRS(*this);
    4837             : 
    4838           1 :         Clear();
    4839             : 
    4840           1 :         oHorizSRS.d->refreshProjObj();
    4841           1 :         oVertSRS.d->refreshProjObj();
    4842           1 :         if (!oHorizSRS.d->m_pj_crs || !oVertSRS.d->m_pj_crs)
    4843           0 :             return OGRERR_FAILURE;
    4844             : 
    4845           1 :         const char *pszHorizName = proj_get_name(oHorizSRS.d->m_pj_crs);
    4846           1 :         const char *pszVertName = proj_get_name(oVertSRS.d->m_pj_crs);
    4847             : 
    4848           2 :         CPLString osName = pszHorizName ? pszHorizName : "";
    4849           1 :         osName += " + ";
    4850           1 :         osName += pszVertName ? pszVertName : "";
    4851             : 
    4852           1 :         SetCompoundCS(osName, &oHorizSRS, &oVertSRS);
    4853             :     }
    4854             : 
    4855           1 :     return eStatus;
    4856             : #endif
    4857             : }
    4858             : 
    4859             : /************************************************************************/
    4860             : /*                           importFromCRSURL()                         */
    4861             : /*                                                                      */
    4862             : /*      See OGC Best Practice document 11-135 for details.              */
    4863             : /************************************************************************/
    4864             : 
    4865             : /**
    4866             :  * \brief Initialize from OGC URL.
    4867             :  *
    4868             :  * Initializes this spatial reference from a coordinate system defined
    4869             :  * by an OGC URL prefixed with "http://opengis.net/def/crs" per best practice
    4870             :  * paper 11-135.  Currently EPSG and OGC authority values are supported,
    4871             :  * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
    4872             :  *
    4873             :  * This method is also supported through SetFromUserInput() which can
    4874             :  * normally be used for URLs.
    4875             :  *
    4876             :  * @param pszURL the URL string.
    4877             :  *
    4878             :  * @return OGRERR_NONE on success or an error code.
    4879             :  */
    4880             : 
    4881        5296 : OGRErr OGRSpatialReference::importFromCRSURL(const char *pszURL)
    4882             : 
    4883             : {
    4884       10592 :     TAKE_OPTIONAL_LOCK();
    4885             : 
    4886             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    4887             :     if (strlen(pszURL) >= 10000)
    4888             :     {
    4889             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    4890             :         return OGRERR_CORRUPT_DATA;
    4891             :     }
    4892             : 
    4893             :     PJ *obj;
    4894             : #if !PROJ_AT_LEAST_VERSION(9, 2, 0)
    4895             :     if (STARTS_WITH(pszURL, "http://www.opengis.net/def/crs/IAU/0/"))
    4896             :     {
    4897             :         obj = proj_create(
    4898             :             d->getPROJContext(),
    4899             :             CPLSPrintf("IAU:%s",
    4900             :                        pszURL +
    4901             :                            strlen("http://www.opengis.net/def/crs/IAU/0/")));
    4902             :     }
    4903             :     else
    4904             : #endif
    4905             :     {
    4906             :         obj = proj_create(d->getPROJContext(), pszURL);
    4907             :     }
    4908             :     if (!obj)
    4909             :     {
    4910             :         return OGRERR_FAILURE;
    4911             :     }
    4912             :     Clear();
    4913             :     d->setPjCRS(obj);
    4914             :     return OGRERR_NONE;
    4915             : #else
    4916        5296 :     const char *pszCur = nullptr;
    4917             : 
    4918        5296 :     if (STARTS_WITH_CI(pszURL, "http://opengis.net/def/crs"))
    4919           2 :         pszCur = pszURL + 26;
    4920        5294 :     else if (STARTS_WITH_CI(pszURL, "https://opengis.net/def/crs"))
    4921           1 :         pszCur = pszURL + 27;
    4922        5293 :     else if (STARTS_WITH_CI(pszURL, "http://www.opengis.net/def/crs"))
    4923        5292 :         pszCur = pszURL + 30;
    4924           1 :     else if (STARTS_WITH_CI(pszURL, "https://www.opengis.net/def/crs"))
    4925           1 :         pszCur = pszURL + 31;
    4926           0 :     else if (STARTS_WITH_CI(pszURL, "www.opengis.net/def/crs"))
    4927           0 :         pszCur = pszURL + 23;
    4928             :     else
    4929             :     {
    4930           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s not a supported format.",
    4931             :                  pszURL);
    4932           0 :         return OGRERR_FAILURE;
    4933             :     }
    4934             : 
    4935        5296 :     if (*pszCur == '\0')
    4936             :     {
    4937           0 :         CPLError(CE_Failure, CPLE_AppDefined, "URL %s malformed.", pszURL);
    4938           0 :         return OGRERR_FAILURE;
    4939             :     }
    4940             : 
    4941             :     /* -------------------------------------------------------------------- */
    4942             :     /*      Clear any existing definition.                                  */
    4943             :     /* -------------------------------------------------------------------- */
    4944        5296 :     Clear();
    4945             : 
    4946        5296 :     if (STARTS_WITH_CI(pszCur, "-compound?1="))
    4947             :     {
    4948             :         /* --------------------------------------------------------------------
    4949             :          */
    4950             :         /*      It's a compound CRS, of the form: */
    4951             :         /*                                                                      */
    4952             :         /*      http://opengis.net/def/crs-compound?1=URL1&2=URL2&3=URL3&.. */
    4953             :         /* --------------------------------------------------------------------
    4954             :          */
    4955           1 :         pszCur += 12;
    4956             : 
    4957             :         // Extract each component CRS URL.
    4958           1 :         int iComponentUrl = 2;
    4959             : 
    4960           2 :         CPLString osName = "";
    4961           1 :         Clear();
    4962             : 
    4963           3 :         while (iComponentUrl != -1)
    4964             :         {
    4965           2 :             char searchStr[15] = {};
    4966           2 :             snprintf(searchStr, sizeof(searchStr), "&%d=", iComponentUrl);
    4967             : 
    4968           2 :             const char *pszUrlEnd = strstr(pszCur, searchStr);
    4969             : 
    4970             :             // Figure out the next component URL.
    4971           2 :             char *pszComponentUrl = nullptr;
    4972             : 
    4973           2 :             if (pszUrlEnd)
    4974             :             {
    4975           1 :                 size_t nLen = pszUrlEnd - pszCur;
    4976           1 :                 pszComponentUrl = static_cast<char *>(CPLMalloc(nLen + 1));
    4977           1 :                 strncpy(pszComponentUrl, pszCur, nLen);
    4978           1 :                 pszComponentUrl[nLen] = '\0';
    4979             : 
    4980           1 :                 ++iComponentUrl;
    4981           1 :                 pszCur += nLen + strlen(searchStr);
    4982             :             }
    4983             :             else
    4984             :             {
    4985           1 :                 if (iComponentUrl == 2)
    4986             :                 {
    4987           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    4988             :                              "Compound CRS URLs must have at least two "
    4989             :                              "component CRSs.");
    4990           0 :                     return OGRERR_FAILURE;
    4991             :                 }
    4992             :                 else
    4993             :                 {
    4994           1 :                     pszComponentUrl = CPLStrdup(pszCur);
    4995             :                     // no more components
    4996           1 :                     iComponentUrl = -1;
    4997             :                 }
    4998             :             }
    4999             : 
    5000           2 :             OGRSpatialReference oComponentSRS;
    5001           2 :             OGRErr eStatus = oComponentSRS.importFromCRSURL(pszComponentUrl);
    5002             : 
    5003           2 :             CPLFree(pszComponentUrl);
    5004           2 :             pszComponentUrl = nullptr;
    5005             : 
    5006           2 :             if (eStatus == OGRERR_NONE)
    5007             :             {
    5008           2 :                 if (osName.length() != 0)
    5009             :                 {
    5010           1 :                     osName += " + ";
    5011             :                 }
    5012           2 :                 osName += oComponentSRS.GetRoot()->GetValue();
    5013           2 :                 SetNode("COMPD_CS", osName);
    5014           2 :                 GetRoot()->AddChild(oComponentSRS.GetRoot()->Clone());
    5015             :             }
    5016             :             else
    5017           0 :                 return eStatus;
    5018             :         }
    5019             : 
    5020           1 :         return OGRERR_NONE;
    5021             :     }
    5022             : 
    5023             :     /* -------------------------------------------------------------------- */
    5024             :     /*      It's a normal CRS URL, of the form:                             */
    5025             :     /*                                                                      */
    5026             :     /*      http://opengis.net/def/crs/AUTHORITY/VERSION/CODE               */
    5027             :     /* -------------------------------------------------------------------- */
    5028        5295 :     ++pszCur;
    5029        5295 :     const char *pszAuthority = pszCur;
    5030             : 
    5031             :     // skip authority
    5032      125961 :     while (*pszCur != '/' && *pszCur)
    5033      120666 :         pszCur++;
    5034        5295 :     if (*pszCur == '/')
    5035        5294 :         pszCur++;
    5036             : 
    5037             :     // skip version
    5038       11609 :     while (*pszCur != '/' && *pszCur)
    5039        6314 :         pszCur++;
    5040        5295 :     if (*pszCur == '/')
    5041        5294 :         pszCur++;
    5042             : 
    5043        5295 :     const char *pszCode = pszCur;
    5044             : 
    5045        5295 :     return importFromURNPart(pszAuthority, pszCode, pszURL);
    5046             : #endif
    5047             : }
    5048             : 
    5049             : /************************************************************************/
    5050             : /*                         importFromWMSAUTO()                          */
    5051             : /************************************************************************/
    5052             : 
    5053             : /**
    5054             :  * \brief Initialize from WMSAUTO string.
    5055             :  *
    5056             :  * Note that the WMS 1.3 specification does not include the
    5057             :  * units code, while apparently earlier specs do.  We try to
    5058             :  * guess around this.
    5059             :  *
    5060             :  * @param pszDefinition the WMSAUTO string
    5061             :  *
    5062             :  * @return OGRERR_NONE on success or an error code.
    5063             :  */
    5064           3 : OGRErr OGRSpatialReference::importFromWMSAUTO(const char *pszDefinition)
    5065             : 
    5066             : {
    5067           6 :     TAKE_OPTIONAL_LOCK();
    5068             : 
    5069             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
    5070             :     if (strlen(pszDefinition) >= 10000)
    5071             :     {
    5072             :         CPLError(CE_Failure, CPLE_AppDefined, "Too long input string");
    5073             :         return OGRERR_CORRUPT_DATA;
    5074             :     }
    5075             : 
    5076             :     auto obj = proj_create(d->getPROJContext(), pszDefinition);
    5077             :     if (!obj)
    5078             :     {
    5079             :         return OGRERR_FAILURE;
    5080             :     }
    5081             :     Clear();
    5082             :     d->setPjCRS(obj);
    5083             :     return OGRERR_NONE;
    5084             : #else
    5085             :     int nProjId, nUnitsId;
    5086           3 :     double dfRefLong, dfRefLat = 0.0;
    5087             : 
    5088             :     /* -------------------------------------------------------------------- */
    5089             :     /*      Tokenize                                                        */
    5090             :     /* -------------------------------------------------------------------- */
    5091           3 :     if (STARTS_WITH_CI(pszDefinition, "AUTO:"))
    5092           3 :         pszDefinition += 5;
    5093             : 
    5094             :     char **papszTokens =
    5095           3 :         CSLTokenizeStringComplex(pszDefinition, ",", FALSE, TRUE);
    5096             : 
    5097           3 :     if (CSLCount(papszTokens) == 4)
    5098             :     {
    5099           0 :         nProjId = atoi(papszTokens[0]);
    5100           0 :         nUnitsId = atoi(papszTokens[1]);
    5101           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    5102           0 :         dfRefLat = CPLAtof(papszTokens[3]);
    5103             :     }
    5104           3 :     else if (CSLCount(papszTokens) == 3 && atoi(papszTokens[0]) == 42005)
    5105             :     {
    5106           0 :         nProjId = atoi(papszTokens[0]);
    5107           0 :         nUnitsId = atoi(papszTokens[1]);
    5108           0 :         dfRefLong = CPLAtof(papszTokens[2]);
    5109           0 :         dfRefLat = 0.0;
    5110             :     }
    5111           3 :     else if (CSLCount(papszTokens) == 3)
    5112             :     {
    5113           2 :         nProjId = atoi(papszTokens[0]);
    5114           2 :         nUnitsId = 9001;
    5115           2 :         dfRefLong = CPLAtof(papszTokens[1]);
    5116           2 :         dfRefLat = CPLAtof(papszTokens[2]);
    5117             :     }
    5118           1 :     else if (CSLCount(papszTokens) == 2 && atoi(papszTokens[0]) == 42005)
    5119             :     {
    5120           0 :         nProjId = atoi(papszTokens[0]);
    5121           0 :         nUnitsId = 9001;
    5122           0 :         dfRefLong = CPLAtof(papszTokens[1]);
    5123             :     }
    5124             :     else
    5125             :     {
    5126           1 :         CSLDestroy(papszTokens);
    5127           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    5128             :                  "AUTO projection has wrong number of arguments, expected\n"
    5129             :                  "AUTO:proj_id,units_id,ref_long,ref_lat or"
    5130             :                  "AUTO:proj_id,ref_long,ref_lat");
    5131           1 :         return OGRERR_FAILURE;
    5132             :     }
    5133             : 
    5134           2 :     CSLDestroy(papszTokens);
    5135           2 :     papszTokens = nullptr;
    5136             : 
    5137             :     /* -------------------------------------------------------------------- */
    5138             :     /*      Build coordsys.                                                 */
    5139             :     /* -------------------------------------------------------------------- */
    5140           2 :     Clear();
    5141             : 
    5142             :     /* -------------------------------------------------------------------- */
    5143             :     /*      Set WGS84.                                                      */
    5144             :     /* -------------------------------------------------------------------- */
    5145           2 :     SetWellKnownGeogCS("WGS84");
    5146             : 
    5147           2 :     switch (nProjId)
    5148             :     {
    5149           2 :         case 42001:  // Auto UTM
    5150           2 :             SetUTM(static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
    5151             :                    dfRefLat >= 0.0);
    5152           2 :             break;
    5153             : 
    5154           0 :         case 42002:  // Auto TM (strangely very UTM-like).
    5155           0 :             SetTM(0, dfRefLong, 0.9996, 500000.0,
    5156             :                   (dfRefLat >= 0.0) ? 0.0 : 10000000.0);
    5157           0 :             break;
    5158             : 
    5159           0 :         case 42003:  // Auto Orthographic.
    5160           0 :             SetOrthographic(dfRefLat, dfRefLong, 0.0, 0.0);
    5161           0 :             break;
    5162             : 
    5163           0 :         case 42004:  // Auto Equirectangular
    5164           0 :             SetEquirectangular(dfRefLat, dfRefLong, 0.0, 0.0);
    5165           0 :             break;
    5166             : 
    5167           0 :         case 42005:
    5168           0 :             SetMollweide(dfRefLong, 0.0, 0.0);
    5169           0 :             break;
    5170             : 
    5171           0 :         default:
    5172           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5173             :                      "Unsupported projection id in importFromWMSAUTO(): %d",
    5174             :                      nProjId);
    5175           0 :             return OGRERR_FAILURE;
    5176             :     }
    5177             : 
    5178             :     /* -------------------------------------------------------------------- */
    5179             :     /*      Set units.                                                      */
    5180             :     /* -------------------------------------------------------------------- */
    5181             : 
    5182           2 :     switch (nUnitsId)
    5183             :     {
    5184           2 :         case 9001:
    5185           2 :             SetTargetLinearUnits(nullptr, SRS_UL_METER, 1.0, "EPSG", "9001");
    5186           2 :             break;
    5187             : 
    5188           0 :         case 9002:
    5189           0 :             SetTargetLinearUnits(nullptr, "Foot", 0.3048, "EPSG", "9002");
    5190           0 :             break;
    5191             : 
    5192           0 :         case 9003:
    5193           0 :             SetTargetLinearUnits(nullptr, "US survey foot",
    5194             :                                  CPLAtof(SRS_UL_US_FOOT_CONV), "EPSG", "9003");
    5195           0 :             break;
    5196             : 
    5197           0 :         default:
    5198           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5199             :                      "Unsupported units code (%d).", nUnitsId);
    5200           0 :             return OGRERR_FAILURE;
    5201             :             break;
    5202             :     }
    5203             : 
    5204           2 :     return OGRERR_NONE;
    5205             : #endif
    5206             : }
    5207             : 
    5208             : /************************************************************************/
    5209             : /*                            GetSemiMajor()                            */
    5210             : /************************************************************************/
    5211             : 
    5212             : /**
    5213             :  * \brief Get spheroid semi major axis (in metres starting with GDAL 3.0)
    5214             :  *
    5215             :  * This method does the same thing as the C function OSRGetSemiMajor().
    5216             :  *
    5217             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if semi major axis
    5218             :  * can be found.
    5219             :  *
    5220             :  * @return semi-major axis, or SRS_WGS84_SEMIMAJOR if it can't be found.
    5221             :  */
    5222             : 
    5223        6227 : double OGRSpatialReference::GetSemiMajor(OGRErr *pnErr) const
    5224             : 
    5225             : {
    5226       12454 :     TAKE_OPTIONAL_LOCK();
    5227             : 
    5228        6227 :     if (pnErr != nullptr)
    5229        3345 :         *pnErr = OGRERR_FAILURE;
    5230             : 
    5231        6227 :     d->refreshProjObj();
    5232        6227 :     if (!d->m_pj_crs)
    5233         111 :         return SRS_WGS84_SEMIMAJOR;
    5234             : 
    5235        6116 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5236        6116 :     if (!ellps)
    5237           5 :         return SRS_WGS84_SEMIMAJOR;
    5238             : 
    5239        6111 :     double dfSemiMajor = 0.0;
    5240        6111 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, &dfSemiMajor,
    5241             :                                   nullptr, nullptr, nullptr);
    5242        6111 :     proj_destroy(ellps);
    5243             : 
    5244        6111 :     if (dfSemiMajor > 0)
    5245             :     {
    5246        6111 :         if (pnErr != nullptr)
    5247        3231 :             *pnErr = OGRERR_NONE;
    5248        6111 :         return dfSemiMajor;
    5249             :     }
    5250             : 
    5251           0 :     return SRS_WGS84_SEMIMAJOR;
    5252             : }
    5253             : 
    5254             : /************************************************************************/
    5255             : /*                          OSRGetSemiMajor()                           */
    5256             : /************************************************************************/
    5257             : 
    5258             : /**
    5259             :  * \brief Get spheroid semi major axis.
    5260             :  *
    5261             :  * This function is the same as OGRSpatialReference::GetSemiMajor()
    5262             :  */
    5263          77 : double OSRGetSemiMajor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5264             : 
    5265             : {
    5266          77 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMajor", 0);
    5267             : 
    5268          77 :     return ToPointer(hSRS)->GetSemiMajor(pnErr);
    5269             : }
    5270             : 
    5271             : /************************************************************************/
    5272             : /*                          GetInvFlattening()                          */
    5273             : /************************************************************************/
    5274             : 
    5275             : /**
    5276             :  * \brief Get spheroid inverse flattening.
    5277             :  *
    5278             :  * This method does the same thing as the C function OSRGetInvFlattening().
    5279             :  *
    5280             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if no inverse flattening
    5281             :  * can be found.
    5282             :  *
    5283             :  * @return inverse flattening, or SRS_WGS84_INVFLATTENING if it can't be found.
    5284             :  */
    5285             : 
    5286        4317 : double OGRSpatialReference::GetInvFlattening(OGRErr *pnErr) const
    5287             : 
    5288             : {
    5289        8634 :     TAKE_OPTIONAL_LOCK();
    5290             : 
    5291        4317 :     if (pnErr != nullptr)
    5292        3249 :         *pnErr = OGRERR_FAILURE;
    5293             : 
    5294        4317 :     d->refreshProjObj();
    5295        4317 :     if (!d->m_pj_crs)
    5296         111 :         return SRS_WGS84_INVFLATTENING;
    5297             : 
    5298        4206 :     auto ellps = proj_get_ellipsoid(d->getPROJContext(), d->m_pj_crs);
    5299        4206 :     if (!ellps)
    5300           2 :         return SRS_WGS84_INVFLATTENING;
    5301             : 
    5302        4204 :     double dfInvFlattening = -1.0;
    5303        4204 :     proj_ellipsoid_get_parameters(d->getPROJContext(), ellps, nullptr, nullptr,
    5304             :                                   nullptr, &dfInvFlattening);
    5305        4204 :     proj_destroy(ellps);
    5306             : 
    5307        4204 :     if (dfInvFlattening >= 0.0)
    5308             :     {
    5309        4204 :         if (pnErr != nullptr)
    5310        3138 :             *pnErr = OGRERR_NONE;
    5311        4204 :         return dfInvFlattening;
    5312             :     }
    5313             : 
    5314           0 :     return SRS_WGS84_INVFLATTENING;
    5315             : }
    5316             : 
    5317             : /************************************************************************/
    5318             : /*                        OSRGetInvFlattening()                         */
    5319             : /************************************************************************/
    5320             : 
    5321             : /**
    5322             :  * \brief Get spheroid inverse flattening.
    5323             :  *
    5324             :  * This function is the same as OGRSpatialReference::GetInvFlattening()
    5325             :  */
    5326          10 : double OSRGetInvFlattening(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5327             : 
    5328             : {
    5329          10 :     VALIDATE_POINTER1(hSRS, "OSRGetInvFlattening", 0);
    5330             : 
    5331          10 :     return ToPointer(hSRS)->GetInvFlattening(pnErr);
    5332             : }
    5333             : 
    5334             : /************************************************************************/
    5335             : /*                           GetEccentricity()                          */
    5336             : /************************************************************************/
    5337             : 
    5338             : /**
    5339             :  * \brief Get spheroid eccentricity
    5340             :  *
    5341             :  * @return eccentricity (or -1 in case of error)
    5342             :  * @since GDAL 2.3
    5343             :  */
    5344             : 
    5345           0 : double OGRSpatialReference::GetEccentricity() const
    5346             : 
    5347             : {
    5348           0 :     OGRErr eErr = OGRERR_NONE;
    5349           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5350           0 :     if (eErr != OGRERR_NONE)
    5351             :     {
    5352           0 :         return -1.0;
    5353             :     }
    5354           0 :     if (dfInvFlattening == 0.0)
    5355           0 :         return 0.0;
    5356           0 :     if (dfInvFlattening < 0.5)
    5357           0 :         return -1.0;
    5358           0 :     return sqrt(2.0 / dfInvFlattening -
    5359           0 :                 1.0 / (dfInvFlattening * dfInvFlattening));
    5360             : }
    5361             : 
    5362             : /************************************************************************/
    5363             : /*                      GetSquaredEccentricity()                        */
    5364             : /************************************************************************/
    5365             : 
    5366             : /**
    5367             :  * \brief Get spheroid squared eccentricity
    5368             :  *
    5369             :  * @return squared eccentricity (or -1 in case of error)
    5370             :  * @since GDAL 2.3
    5371             :  */
    5372             : 
    5373           0 : double OGRSpatialReference::GetSquaredEccentricity() const
    5374             : 
    5375             : {
    5376           0 :     OGRErr eErr = OGRERR_NONE;
    5377           0 :     const double dfInvFlattening = GetInvFlattening(&eErr);
    5378           0 :     if (eErr != OGRERR_NONE)
    5379             :     {
    5380           0 :         return -1.0;
    5381             :     }
    5382           0 :     if (dfInvFlattening == 0.0)
    5383           0 :         return 0.0;
    5384           0 :     if (dfInvFlattening < 0.5)
    5385           0 :         return -1.0;
    5386           0 :     return 2.0 / dfInvFlattening - 1.0 / (dfInvFlattening * dfInvFlattening);
    5387             : }
    5388             : 
    5389             : /************************************************************************/
    5390             : /*                            GetSemiMinor()                            */
    5391             : /************************************************************************/
    5392             : 
    5393             : /**
    5394             :  * \brief Get spheroid semi minor axis.
    5395             :  *
    5396             :  * This method does the same thing as the C function OSRGetSemiMinor().
    5397             :  *
    5398             :  * @param pnErr if non-NULL set to OGRERR_FAILURE if semi minor axis
    5399             :  * can be found.
    5400             :  *
    5401             :  * @return semi-minor axis, or WGS84 semi minor if it can't be found.
    5402             :  */
    5403             : 
    5404         651 : double OGRSpatialReference::GetSemiMinor(OGRErr *pnErr) const
    5405             : 
    5406             : {
    5407         651 :     const double dfSemiMajor = GetSemiMajor(pnErr);
    5408         651 :     const double dfInvFlattening = GetInvFlattening(pnErr);
    5409             : 
    5410         651 :     return OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
    5411             : }
    5412             : 
    5413             : /************************************************************************/
    5414             : /*                          OSRGetSemiMinor()                           */
    5415             : /************************************************************************/
    5416             : 
    5417             : /**
    5418             :  * \brief Get spheroid semi minor axis.
    5419             :  *
    5420             :  * This function is the same as OGRSpatialReference::GetSemiMinor()
    5421             :  */
    5422           4 : double OSRGetSemiMinor(OGRSpatialReferenceH hSRS, OGRErr *pnErr)
    5423             : 
    5424             : {
    5425           4 :     VALIDATE_POINTER1(hSRS, "OSRGetSemiMinor", 0);
    5426             : 
    5427           4 :     return ToPointer(hSRS)->GetSemiMinor(pnErr);
    5428             : }
    5429             : 
    5430             : /************************************************************************/
    5431             : /*                             SetLocalCS()                             */
    5432             : /************************************************************************/
    5433             : 
    5434             : /**
    5435             :  * \brief Set the user visible LOCAL_CS name.
    5436             :  *
    5437             :  * This method is the same as the C function OSRSetLocalCS().
    5438             :  *
    5439             :  * This method will ensure a LOCAL_CS node is created as the root,
    5440             :  * and set the provided name on it.  It must be used before SetLinearUnits().
    5441             :  *
    5442             :  * @param pszName the user visible name to assign.  Not used as a key.
    5443             :  *
    5444             :  * @return OGRERR_NONE on success.
    5445             :  */
    5446             : 
    5447        2892 : OGRErr OGRSpatialReference::SetLocalCS(const char *pszName)
    5448             : 
    5449             : {
    5450        5784 :     TAKE_OPTIONAL_LOCK();
    5451             : 
    5452        2892 :     if (d->m_pjType == PJ_TYPE_UNKNOWN ||
    5453           0 :         d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    5454             :     {
    5455        2892 :         d->setPjCRS(proj_create_engineering_crs(d->getPROJContext(), pszName));
    5456             :     }
    5457             :     else
    5458             :     {
    5459           0 :         CPLDebug("OGR",
    5460             :                  "OGRSpatialReference::SetLocalCS(%s) failed.  "
    5461             :                  "It appears an incompatible object already exists.",
    5462             :                  pszName);
    5463           0 :         return OGRERR_FAILURE;
    5464             :     }
    5465             : 
    5466        2892 :     return OGRERR_NONE;
    5467             : }
    5468             : 
    5469             : /************************************************************************/
    5470             : /*                           OSRSetLocalCS()                            */
    5471             : /************************************************************************/
    5472             : 
    5473             : /**
    5474             :  * \brief Set the user visible LOCAL_CS name.
    5475             :  *
    5476             :  * This function is the same as OGRSpatialReference::SetLocalCS()
    5477             :  */
    5478           1 : OGRErr OSRSetLocalCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5479             : 
    5480             : {
    5481           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLocalCS", OGRERR_FAILURE);
    5482             : 
    5483           1 :     return ToPointer(hSRS)->SetLocalCS(pszName);
    5484             : }
    5485             : 
    5486             : /************************************************************************/
    5487             : /*                             SetGeocCS()                              */
    5488             : /************************************************************************/
    5489             : 
    5490             : /**
    5491             :  * \brief Set the user visible GEOCCS name.
    5492             :  *
    5493             :  * This method is the same as the C function OSRSetGeocCS().
    5494             : 
    5495             :  * This method will ensure a GEOCCS node is created as the root,
    5496             :  * and set the provided name on it.  If used on a GEOGCS coordinate system,
    5497             :  * the DATUM and PRIMEM nodes from the GEOGCS will be transferred over to
    5498             :  * the GEOGCS.
    5499             :  *
    5500             :  * @param pszName the user visible name to assign.  Not used as a key.
    5501             :  *
    5502             :  * @return OGRERR_NONE on success.
    5503             :  *
    5504             :  * @since OGR 1.9.0
    5505             :  */
    5506             : 
    5507           6 : OGRErr OGRSpatialReference::SetGeocCS(const char *pszName)
    5508             : 
    5509             : {
    5510          12 :     TAKE_OPTIONAL_LOCK();
    5511             : 
    5512           6 :     OGRErr eErr = OGRERR_NONE;
    5513           6 :     d->refreshProjObj();
    5514           6 :     d->demoteFromBoundCRS();
    5515           6 :     if (d->m_pjType == PJ_TYPE_UNKNOWN)
    5516             :     {
    5517           3 :         d->setPjCRS(proj_create_geocentric_crs(
    5518             :             d->getPROJContext(), pszName, "World Geodetic System 1984",
    5519             :             "WGS 84", SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING,
    5520             :             SRS_PM_GREENWICH, 0.0, SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV),
    5521             :             "Metre", 1.0));
    5522             :     }
    5523           3 :     else if (d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS)
    5524             :     {
    5525           1 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5526             :     }
    5527           3 :     else if (d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    5528           1 :              d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS)
    5529             :     {
    5530           1 :         auto datum = proj_crs_get_datum(d->getPROJContext(), d->m_pj_crs);
    5531             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    5532             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    5533             :         if (datum == nullptr)
    5534             :         {
    5535             :             datum =
    5536             :                 proj_crs_get_datum_ensemble(d->getPROJContext(), d->m_pj_crs);
    5537             :         }
    5538             : #endif
    5539           1 :         if (datum == nullptr)
    5540             :         {
    5541           0 :             d->undoDemoteFromBoundCRS();
    5542           0 :             return OGRERR_FAILURE;
    5543             :         }
    5544             : 
    5545           1 :         auto pj_crs = proj_create_geocentric_crs_from_datum(
    5546           1 :             d->getPROJContext(), proj_get_name(d->m_pj_crs), datum, nullptr,
    5547             :             0.0);
    5548           1 :         d->setPjCRS(pj_crs);
    5549             : 
    5550           1 :         proj_destroy(datum);
    5551             :     }
    5552             :     else
    5553             :     {
    5554           1 :         CPLDebug("OGR",
    5555             :                  "OGRSpatialReference::SetGeocCS(%s) failed.  "
    5556             :                  "It appears an incompatible object already exists.",
    5557             :                  pszName);
    5558           1 :         eErr = OGRERR_FAILURE;
    5559             :     }
    5560           6 :     d->undoDemoteFromBoundCRS();
    5561             : 
    5562           6 :     return eErr;
    5563             : }
    5564             : 
    5565             : /************************************************************************/
    5566             : /*                            OSRSetGeocCS()                            */
    5567             : /************************************************************************/
    5568             : 
    5569             : /**
    5570             :  * \brief Set the user visible PROJCS name.
    5571             :  *
    5572             :  * This function is the same as OGRSpatialReference::SetGeocCS()
    5573             :  *
    5574             :  * @since OGR 1.9.0
    5575             :  */
    5576           4 : OGRErr OSRSetGeocCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5577             : 
    5578             : {
    5579           4 :     VALIDATE_POINTER1(hSRS, "OSRSetGeocCS", OGRERR_FAILURE);
    5580             : 
    5581           4 :     return ToPointer(hSRS)->SetGeocCS(pszName);
    5582             : }
    5583             : 
    5584             : /************************************************************************/
    5585             : /*                             SetVertCS()                              */
    5586             : /************************************************************************/
    5587             : 
    5588             : /**
    5589             :  * \brief Set the user visible VERT_CS name.
    5590             :  *
    5591             :  * This method is the same as the C function OSRSetVertCS().
    5592             : 
    5593             :  * This method will ensure a VERT_CS node is created if needed.  If the
    5594             :  * existing coordinate system is GEOGCS or PROJCS rooted, then it will be
    5595             :  * turned into a COMPD_CS.
    5596             :  *
    5597             :  * @param pszVertCSName the user visible name of the vertical coordinate
    5598             :  * system. Not used as a key.
    5599             :  *
    5600             :  * @param pszVertDatumName the user visible name of the vertical datum.  It
    5601             :  * is helpful if this matches the EPSG name.
    5602             :  *
    5603             :  * @param nVertDatumType the OGC vertical datum type. Ignored
    5604             :  *
    5605             :  * @return OGRERR_NONE on success.
    5606             :  *
    5607             :  * @since OGR 1.9.0
    5608             :  */
    5609             : 
    5610           1 : OGRErr OGRSpatialReference::SetVertCS(const char *pszVertCSName,
    5611             :                                       const char *pszVertDatumName,
    5612             :                                       int nVertDatumType)
    5613             : 
    5614             : {
    5615           1 :     TAKE_OPTIONAL_LOCK();
    5616             : 
    5617           1 :     CPL_IGNORE_RET_VAL(nVertDatumType);
    5618             : 
    5619           1 :     d->refreshProjObj();
    5620             : 
    5621           1 :     auto vertCRS = proj_create_vertical_crs(d->getPROJContext(), pszVertCSName,
    5622             :                                             pszVertDatumName, nullptr, 0.0);
    5623             : 
    5624             :     /* -------------------------------------------------------------------- */
    5625             :     /*      Handle the case where we want to make a compound coordinate     */
    5626             :     /*      system.                                                         */
    5627             :     /* -------------------------------------------------------------------- */
    5628           1 :     if (IsProjected() || IsGeographic())
    5629             :     {
    5630           1 :         auto compoundCRS = proj_create_compound_crs(
    5631           1 :             d->getPROJContext(), nullptr, d->m_pj_crs, vertCRS);
    5632           1 :         proj_destroy(vertCRS);
    5633           1 :         d->setPjCRS(compoundCRS);
    5634             :     }
    5635             :     else
    5636             :     {
    5637           0 :         d->setPjCRS(vertCRS);
    5638             :     }
    5639           2 :     return OGRERR_NONE;
    5640             : }
    5641             : 
    5642             : /************************************************************************/
    5643             : /*                            OSRSetVertCS()                            */
    5644             : /************************************************************************/
    5645             : 
    5646             : /**
    5647             :  * \brief Setup the vertical coordinate system.
    5648             :  *
    5649             :  * This function is the same as OGRSpatialReference::SetVertCS()
    5650             :  *
    5651             :  * @since OGR 1.9.0
    5652             :  */
    5653           0 : OGRErr OSRSetVertCS(OGRSpatialReferenceH hSRS, const char *pszVertCSName,
    5654             :                     const char *pszVertDatumName, int nVertDatumType)
    5655             : 
    5656             : {
    5657           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVertCS", OGRERR_FAILURE);
    5658             : 
    5659           0 :     return ToPointer(hSRS)->SetVertCS(pszVertCSName, pszVertDatumName,
    5660           0 :                                       nVertDatumType);
    5661             : }
    5662             : 
    5663             : /************************************************************************/
    5664             : /*                           SetCompoundCS()                            */
    5665             : /************************************************************************/
    5666             : 
    5667             : /**
    5668             :  * \brief Setup a compound coordinate system.
    5669             :  *
    5670             :  * This method is the same as the C function OSRSetCompoundCS().
    5671             : 
    5672             :  * This method is replace the current SRS with a COMPD_CS coordinate system
    5673             :  * consisting of the passed in horizontal and vertical coordinate systems.
    5674             :  *
    5675             :  * @param pszName the name of the compound coordinate system.
    5676             :  *
    5677             :  * @param poHorizSRS the horizontal SRS (PROJCS or GEOGCS).
    5678             :  *
    5679             :  * @param poVertSRS the vertical SRS (VERT_CS).
    5680             :  *
    5681             :  * @return OGRERR_NONE on success.
    5682             :  */
    5683             : 
    5684          92 : OGRErr OGRSpatialReference::SetCompoundCS(const char *pszName,
    5685             :                                           const OGRSpatialReference *poHorizSRS,
    5686             :                                           const OGRSpatialReference *poVertSRS)
    5687             : 
    5688             : {
    5689         184 :     TAKE_OPTIONAL_LOCK();
    5690             : 
    5691             :     /* -------------------------------------------------------------------- */
    5692             :     /*      Verify these are legal horizontal and vertical coordinate       */
    5693             :     /*      systems.                                                        */
    5694             :     /* -------------------------------------------------------------------- */
    5695          92 :     if (!poVertSRS->IsVertical())
    5696             :     {
    5697           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5698             :                  "SetCompoundCS() fails, vertical component is not VERT_CS.");
    5699           0 :         return OGRERR_FAILURE;
    5700             :     }
    5701          92 :     if (!poHorizSRS->IsProjected() && !poHorizSRS->IsGeographic())
    5702             :     {
    5703           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5704             :                  "SetCompoundCS() fails, horizontal component is not PROJCS or "
    5705             :                  "GEOGCS.");
    5706           0 :         return OGRERR_FAILURE;
    5707             :     }
    5708             : 
    5709             :     /* -------------------------------------------------------------------- */
    5710             :     /*      Replace with compound srs.                                      */
    5711             :     /* -------------------------------------------------------------------- */
    5712          92 :     Clear();
    5713             : 
    5714          92 :     auto compoundCRS = proj_create_compound_crs(d->getPROJContext(), pszName,
    5715          92 :                                                 poHorizSRS->d->m_pj_crs,
    5716          92 :                                                 poVertSRS->d->m_pj_crs);
    5717          92 :     d->setPjCRS(compoundCRS);
    5718             : 
    5719          92 :     return OGRERR_NONE;
    5720             : }
    5721             : 
    5722             : /************************************************************************/
    5723             : /*                          OSRSetCompoundCS()                          */
    5724             : /************************************************************************/
    5725             : 
    5726             : /**
    5727             :  * \brief Setup a compound coordinate system.
    5728             :  *
    5729             :  * This function is the same as OGRSpatialReference::SetCompoundCS()
    5730             :  */
    5731           8 : OGRErr OSRSetCompoundCS(OGRSpatialReferenceH hSRS, const char *pszName,
    5732             :                         OGRSpatialReferenceH hHorizSRS,
    5733             :                         OGRSpatialReferenceH hVertSRS)
    5734             : 
    5735             : {
    5736           8 :     VALIDATE_POINTER1(hSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5737           8 :     VALIDATE_POINTER1(hHorizSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5738           8 :     VALIDATE_POINTER1(hVertSRS, "OSRSetCompoundCS", OGRERR_FAILURE);
    5739             : 
    5740          16 :     return ToPointer(hSRS)->SetCompoundCS(pszName, ToPointer(hHorizSRS),
    5741          16 :                                           ToPointer(hVertSRS));
    5742             : }
    5743             : 
    5744             : /************************************************************************/
    5745             : /*                             SetProjCS()                              */
    5746             : /************************************************************************/
    5747             : 
    5748             : /**
    5749             :  * \brief Set the user visible PROJCS name.
    5750             :  *
    5751             :  * This method is the same as the C function OSRSetProjCS().
    5752             :  *
    5753             :  * This method will ensure a PROJCS node is created as the root,
    5754             :  * and set the provided name on it.  If used on a GEOGCS coordinate system,
    5755             :  * the GEOGCS node will be demoted to be a child of the new PROJCS root.
    5756             :  *
    5757             :  * @param pszName the user visible name to assign.  Not used as a key.
    5758             :  *
    5759             :  * @return OGRERR_NONE on success.
    5760             :  */
    5761             : 
    5762        4510 : OGRErr OGRSpatialReference::SetProjCS(const char *pszName)
    5763             : 
    5764             : {
    5765        4510 :     TAKE_OPTIONAL_LOCK();
    5766             : 
    5767        4510 :     d->refreshProjObj();
    5768        4510 :     d->demoteFromBoundCRS();
    5769        4510 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    5770             :     {
    5771         487 :         d->setPjCRS(proj_alter_name(d->getPROJContext(), d->m_pj_crs, pszName));
    5772             :     }
    5773             :     else
    5774             :     {
    5775        4023 :         auto dummyConv = proj_create_conversion(d->getPROJContext(), nullptr,
    5776             :                                                 nullptr, nullptr, nullptr,
    5777             :                                                 nullptr, nullptr, 0, nullptr);
    5778        4023 :         auto cs = proj_create_cartesian_2D_cs(
    5779             :             d->getPROJContext(), PJ_CART2D_EASTING_NORTHING, nullptr, 0);
    5780             : 
    5781        4023 :         auto projCRS = proj_create_projected_crs(
    5782        4023 :             d->getPROJContext(), pszName, d->getGeodBaseCRS(), dummyConv, cs);
    5783        4023 :         proj_destroy(dummyConv);
    5784        4023 :         proj_destroy(cs);
    5785             : 
    5786        4023 :         d->setPjCRS(projCRS);
    5787             :     }
    5788        4510 :     d->undoDemoteFromBoundCRS();
    5789        9020 :     return OGRERR_NONE;
    5790             : }
    5791             : 
    5792             : /************************************************************************/
    5793             : /*                            OSRSetProjCS()                            */
    5794             : /************************************************************************/
    5795             : 
    5796             : /**
    5797             :  * \brief Set the user visible PROJCS name.
    5798             :  *
    5799             :  * This function is the same as OGRSpatialReference::SetProjCS()
    5800             :  */
    5801           1 : OGRErr OSRSetProjCS(OGRSpatialReferenceH hSRS, const char *pszName)
    5802             : 
    5803             : {
    5804           1 :     VALIDATE_POINTER1(hSRS, "OSRSetProjCS", OGRERR_FAILURE);
    5805             : 
    5806           1 :     return ToPointer(hSRS)->SetProjCS(pszName);
    5807             : }
    5808             : 
    5809             : /************************************************************************/
    5810             : /*                           SetProjection()                            */
    5811             : /************************************************************************/
    5812             : 
    5813             : /**
    5814             :  * \brief Set a projection name.
    5815             :  *
    5816             :  * This method is the same as the C function OSRSetProjection().
    5817             :  *
    5818             :  * @param pszProjection the projection name, which should be selected from
    5819             :  * the macros in ogr_srs_api.h, such as SRS_PT_TRANSVERSE_MERCATOR.
    5820             :  *
    5821             :  * @return OGRERR_NONE on success.
    5822             :  */
    5823             : 
    5824          23 : OGRErr OGRSpatialReference::SetProjection(const char *pszProjection)
    5825             : 
    5826             : {
    5827          46 :     TAKE_OPTIONAL_LOCK();
    5828             : 
    5829          23 :     OGR_SRSNode *poGeogCS = nullptr;
    5830             : 
    5831          23 :     if (GetRoot() != nullptr && EQUAL(d->m_poRoot->GetValue(), "GEOGCS"))
    5832             :     {
    5833           4 :         poGeogCS = d->m_poRoot;
    5834           4 :         d->m_poRoot = nullptr;
    5835             :     }
    5836             : 
    5837          23 :     if (!GetAttrNode("PROJCS"))
    5838             :     {
    5839          11 :         SetNode("PROJCS", "unnamed");
    5840             :     }
    5841             : 
    5842          23 :     const OGRErr eErr = SetNode("PROJCS|PROJECTION", pszProjection);
    5843          23 :     if (eErr != OGRERR_NONE)
    5844           0 :         return eErr;
    5845             : 
    5846          23 :     if (poGeogCS != nullptr)
    5847           4 :         d->m_poRoot->InsertChild(poGeogCS, 1);
    5848             : 
    5849          23 :     return OGRERR_NONE;
    5850             : }
    5851             : 
    5852             : /************************************************************************/
    5853             : /*                            OSRSetProjection()                        */
    5854             : /************************************************************************/
    5855             : 
    5856             : /**
    5857             :  * \brief Set a projection name.
    5858             :  *
    5859             :  * This function is the same as OGRSpatialReference::SetProjection()
    5860             :  */
    5861           0 : OGRErr OSRSetProjection(OGRSpatialReferenceH hSRS, const char *pszProjection)
    5862             : 
    5863             : {
    5864           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjection", OGRERR_FAILURE);
    5865             : 
    5866           0 :     return ToPointer(hSRS)->SetProjection(pszProjection);
    5867             : }
    5868             : 
    5869             : /************************************************************************/
    5870             : /*                      GetWKT2ProjectionMethod()                       */
    5871             : /************************************************************************/
    5872             : 
    5873             : /**
    5874             :  * \brief Returns info on the projection method, based on WKT2 naming
    5875             :  * conventions.
    5876             :  *
    5877             :  * The returned strings are short lived and should be considered to be
    5878             :  * invalidated by any further call to the GDAL API.
    5879             :  *
    5880             :  * @param[out] ppszMethodName Pointer to a string that will receive the
    5881             :  * projection method name.
    5882             :  * @param[out] ppszMethodAuthName null pointer, or pointer to a string that will
    5883             :  * receive the name of the authority that defines the projection method.
    5884             :  * *ppszMethodAuthName may be nullptr if the projection method is not linked to
    5885             :  * an authority.
    5886             :  * @param[out] ppszMethodCode null pointer, or pointer to a string that will
    5887             :  * receive the code that defines the projection method.
    5888             :  * *ppszMethodCode may be nullptr if the projection method is not linked to
    5889             :  * an authority.
    5890             :  *
    5891             :  * @return OGRERR_NONE on success.
    5892             :  */
    5893             : OGRErr
    5894           1 : OGRSpatialReference::GetWKT2ProjectionMethod(const char **ppszMethodName,
    5895             :                                              const char **ppszMethodAuthName,
    5896             :                                              const char **ppszMethodCode) const
    5897             : {
    5898           2 :     TAKE_OPTIONAL_LOCK();
    5899             : 
    5900           1 :     auto conv = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    5901           1 :     if (!conv)
    5902           0 :         return OGRERR_FAILURE;
    5903           1 :     const char *pszTmpMethodName = "";
    5904           1 :     const char *pszTmpMethodAuthName = "";
    5905           1 :     const char *pszTmpMethodCode = "";
    5906           1 :     int ret = proj_coordoperation_get_method_info(
    5907             :         d->getPROJContext(), conv, &pszTmpMethodName, &pszTmpMethodAuthName,
    5908             :         &pszTmpMethodCode);
    5909             :     // "Internalize" temporary strings returned by PROJ
    5910           1 :     CPLAssert(pszTmpMethodName);
    5911           1 :     if (ppszMethodName)
    5912           1 :         *ppszMethodName = CPLSPrintf("%s", pszTmpMethodName);
    5913           1 :     if (ppszMethodAuthName)
    5914           0 :         *ppszMethodAuthName = pszTmpMethodAuthName
    5915           0 :                                   ? CPLSPrintf("%s", pszTmpMethodAuthName)
    5916           0 :                                   : nullptr;
    5917           1 :     if (ppszMethodCode)
    5918           0 :         *ppszMethodCode =
    5919           0 :             pszTmpMethodCode ? CPLSPrintf("%s", pszTmpMethodCode) : nullptr;
    5920           1 :     proj_destroy(conv);
    5921           1 :     return ret ? OGRERR_NONE : OGRERR_FAILURE;
    5922             : }
    5923             : 
    5924             : /************************************************************************/
    5925             : /*                            SetProjParm()                             */
    5926             : /************************************************************************/
    5927             : 
    5928             : /**
    5929             :  * \brief Set a projection parameter value.
    5930             :  *
    5931             :  * Adds a new PARAMETER under the PROJCS with the indicated name and value.
    5932             :  *
    5933             :  * This method is the same as the C function OSRSetProjParm().
    5934             :  *
    5935             :  * Please check https://gdal.org/proj_list pages for
    5936             :  * legal parameter names for specific projections.
    5937             :  *
    5938             :  *
    5939             :  * @param pszParamName the parameter name, which should be selected from
    5940             :  * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
    5941             :  *
    5942             :  * @param dfValue value to assign.
    5943             :  *
    5944             :  * @return OGRERR_NONE on success.
    5945             :  */
    5946             : 
    5947         129 : OGRErr OGRSpatialReference::SetProjParm(const char *pszParamName,
    5948             :                                         double dfValue)
    5949             : 
    5950             : {
    5951         258 :     TAKE_OPTIONAL_LOCK();
    5952             : 
    5953         129 :     OGR_SRSNode *poPROJCS = GetAttrNode("PROJCS");
    5954             : 
    5955         129 :     if (poPROJCS == nullptr)
    5956           3 :         return OGRERR_FAILURE;
    5957             : 
    5958         126 :     char szValue[64] = {'\0'};
    5959         126 :     OGRsnPrintDouble(szValue, sizeof(szValue), dfValue);
    5960             : 
    5961             :     /* -------------------------------------------------------------------- */
    5962             :     /*      Try to find existing parameter with this name.                  */
    5963             :     /* -------------------------------------------------------------------- */
    5964        1030 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    5965             :     {
    5966         943 :         OGR_SRSNode *poParam = poPROJCS->GetChild(iChild);
    5967             : 
    5968        1242 :         if (EQUAL(poParam->GetValue(), "PARAMETER") &&
    5969        1242 :             poParam->GetChildCount() == 2 &&
    5970         299 :             EQUAL(poParam->GetChild(0)->GetValue(), pszParamName))
    5971             :         {
    5972          39 :             poParam->GetChild(1)->SetValue(szValue);
    5973          39 :             return OGRERR_NONE;
    5974             :         }
    5975             :     }
    5976             : 
    5977             :     /* -------------------------------------------------------------------- */
    5978             :     /*      Otherwise create a new parameter and append.                    */
    5979             :     /* -------------------------------------------------------------------- */
    5980          87 :     OGR_SRSNode *poParam = new OGR_SRSNode("PARAMETER");
    5981          87 :     poParam->AddChild(new OGR_SRSNode(pszParamName));
    5982          87 :     poParam->AddChild(new OGR_SRSNode(szValue));
    5983             : 
    5984          87 :     poPROJCS->AddChild(poParam);
    5985             : 
    5986          87 :     return OGRERR_NONE;
    5987             : }
    5988             : 
    5989             : /************************************************************************/
    5990             : /*                           OSRSetProjParm()                           */
    5991             : /************************************************************************/
    5992             : 
    5993             : /**
    5994             :  * \brief Set a projection parameter value.
    5995             :  *
    5996             :  * This function is the same as OGRSpatialReference::SetProjParm()
    5997             :  */
    5998           0 : OGRErr OSRSetProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    5999             :                       double dfValue)
    6000             : 
    6001             : {
    6002           0 :     VALIDATE_POINTER1(hSRS, "OSRSetProjParm", OGRERR_FAILURE);
    6003             : 
    6004           0 :     return ToPointer(hSRS)->SetProjParm(pszParamName, dfValue);
    6005             : }
    6006             : 
    6007             : /************************************************************************/
    6008             : /*                            FindProjParm()                            */
    6009             : /************************************************************************/
    6010             : 
    6011             : /**
    6012             :  * \brief Return the child index of the named projection parameter on
    6013             :  * its parent PROJCS node.
    6014             :  *
    6015             :  * @param pszParameter projection parameter to look for
    6016             :  * @param poPROJCS projection CS node to look in. If NULL is passed,
    6017             :  *        the PROJCS node of the SpatialReference object will be searched.
    6018             :  *
    6019             :  * @return the child index of the named projection parameter. -1 on failure
    6020             :  */
    6021        4792 : int OGRSpatialReference::FindProjParm(const char *pszParameter,
    6022             :                                       const OGR_SRSNode *poPROJCS) const
    6023             : 
    6024             : {
    6025        9584 :     TAKE_OPTIONAL_LOCK();
    6026             : 
    6027        4792 :     if (poPROJCS == nullptr)
    6028           0 :         poPROJCS = GetAttrNode("PROJCS");
    6029             : 
    6030        4792 :     if (poPROJCS == nullptr)
    6031           0 :         return -1;
    6032             : 
    6033             :     /* -------------------------------------------------------------------- */
    6034             :     /*      Search for requested parameter.                                 */
    6035             :     /* -------------------------------------------------------------------- */
    6036        4792 :     bool bIsWKT2 = false;
    6037       31514 :     for (int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++)
    6038             :     {
    6039       30897 :         const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    6040             : 
    6041       30897 :         if (poParameter->GetChildCount() >= 2)
    6042             :         {
    6043       21354 :             const char *pszValue = poParameter->GetValue();
    6044       35297 :             if (EQUAL(pszValue, "PARAMETER") &&
    6045       13943 :                 EQUAL(poPROJCS->GetChild(iChild)->GetChild(0)->GetValue(),
    6046             :                       pszParameter))
    6047             :             {
    6048        4175 :                 return iChild;
    6049             :             }
    6050       17179 :             else if (EQUAL(pszValue, "METHOD"))
    6051             :             {
    6052          41 :                 bIsWKT2 = true;
    6053             :             }
    6054             :         }
    6055             :     }
    6056             : 
    6057             :     /* -------------------------------------------------------------------- */
    6058             :     /*      Try similar names, for selected parameters.                     */
    6059             :     /* -------------------------------------------------------------------- */
    6060         617 :     if (EQUAL(pszParameter, SRS_PP_LATITUDE_OF_ORIGIN))
    6061             :     {
    6062         287 :         if (bIsWKT2)
    6063             :         {
    6064           8 :             int iChild = FindProjParm(
    6065             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    6066           8 :             if (iChild == -1)
    6067           3 :                 iChild = FindProjParm(
    6068             :                     EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, poPROJCS);
    6069           8 :             return iChild;
    6070             :         }
    6071         279 :         return FindProjParm(SRS_PP_LATITUDE_OF_CENTER, poPROJCS);
    6072             :     }
    6073             : 
    6074         330 :     if (EQUAL(pszParameter, SRS_PP_CENTRAL_MERIDIAN))
    6075             :     {
    6076          34 :         if (bIsWKT2)
    6077             :         {
    6078           9 :             int iChild = FindProjParm(
    6079             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, poPROJCS);
    6080           9 :             if (iChild == -1)
    6081           0 :                 iChild = FindProjParm(
    6082             :                     EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, poPROJCS);
    6083           9 :             return iChild;
    6084             :         }
    6085          25 :         int iChild = FindProjParm(SRS_PP_LONGITUDE_OF_CENTER, poPROJCS);
    6086          25 :         if (iChild == -1)
    6087           0 :             iChild = FindProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, poPROJCS);
    6088          25 :         return iChild;
    6089             :     }
    6090             : 
    6091         296 :     return -1;
    6092             : }
    6093             : 
    6094             : /************************************************************************/
    6095             : /*                            GetProjParm()                             */
    6096             : /************************************************************************/
    6097             : 
    6098             : /**
    6099             :  * \brief Fetch a projection parameter value.
    6100             :  *
    6101             :  * NOTE: This code should be modified to translate non degree angles into
    6102             :  * degrees based on the GEOGCS unit.  This has not yet been done.
    6103             :  *
    6104             :  * This method is the same as the C function OSRGetProjParm().
    6105             :  *
    6106             :  * @param pszName the name of the parameter to fetch, from the set of
    6107             :  * SRS_PP codes in ogr_srs_api.h.
    6108             :  *
    6109             :  * @param dfDefaultValue the value to return if this parameter doesn't exist.
    6110             :  *
    6111             :  * @param pnErr place to put error code on failure.  Ignored if NULL.
    6112             :  *
    6113             :  * @return value of parameter.
    6114             :  */
    6115             : 
    6116        4726 : double OGRSpatialReference::GetProjParm(const char *pszName,
    6117             :                                         double dfDefaultValue,
    6118             :                                         OGRErr *pnErr) const
    6119             : 
    6120             : {
    6121        9452 :     TAKE_OPTIONAL_LOCK();
    6122             : 
    6123        4726 :     d->refreshProjObj();
    6124        4726 :     GetRoot();  // force update of d->m_bNodesWKT2
    6125             : 
    6126        4726 :     if (pnErr != nullptr)
    6127        3749 :         *pnErr = OGRERR_NONE;
    6128             : 
    6129             :     /* -------------------------------------------------------------------- */
    6130             :     /*      Find the desired parameter.                                     */
    6131             :     /* -------------------------------------------------------------------- */
    6132             :     const OGR_SRSNode *poPROJCS =
    6133        4726 :         GetAttrNode(d->m_bNodesWKT2 ? "CONVERSION" : "PROJCS");
    6134        4726 :     if (poPROJCS == nullptr)
    6135             :     {
    6136         258 :         if (pnErr != nullptr)
    6137         258 :             *pnErr = OGRERR_FAILURE;
    6138         258 :         return dfDefaultValue;
    6139             :     }
    6140             : 
    6141        4468 :     const int iChild = FindProjParm(pszName, poPROJCS);
    6142        4468 :     if (iChild == -1)
    6143             :     {
    6144         293 :         if (IsProjected() && GetAxesCount() == 3)
    6145             :         {
    6146           3 :             OGRSpatialReference *poSRSTmp = Clone();
    6147           3 :             poSRSTmp->DemoteTo2D(nullptr);
    6148             :             const double dfRet =
    6149           3 :                 poSRSTmp->GetProjParm(pszName, dfDefaultValue, pnErr);
    6150           3 :             delete poSRSTmp;
    6151           3 :             return dfRet;
    6152             :         }
    6153             : 
    6154         290 :         if (pnErr != nullptr)
    6155         268 :             *pnErr = OGRERR_FAILURE;
    6156         290 :         return dfDefaultValue;
    6157             :     }
    6158             : 
    6159        4175 :     const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    6160        4175 :     return CPLAtof(poParameter->GetChild(1)->GetValue());
    6161             : }
    6162             : 
    6163             : /************************************************************************/
    6164             : /*                           OSRGetProjParm()                           */
    6165             : /************************************************************************/
    6166             : 
    6167             : /**
    6168             :  * \brief Fetch a projection parameter value.
    6169             :  *
    6170             :  * This function is the same as OGRSpatialReference::GetProjParm()
    6171             :  */
    6172          90 : double OSRGetProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6173             :                       double dfDefaultValue, OGRErr *pnErr)
    6174             : 
    6175             : {
    6176          90 :     VALIDATE_POINTER1(hSRS, "OSRGetProjParm", 0);
    6177             : 
    6178          90 :     return ToPointer(hSRS)->GetProjParm(pszName, dfDefaultValue, pnErr);
    6179             : }
    6180             : 
    6181             : /************************************************************************/
    6182             : /*                          GetNormProjParm()                           */
    6183             : /************************************************************************/
    6184             : 
    6185             : /**
    6186             :  * \brief Fetch a normalized projection parameter value.
    6187             :  *
    6188             :  * This method is the same as GetProjParm() except that the value of
    6189             :  * the parameter is "normalized" into degrees or meters depending on
    6190             :  * whether it is linear or angular.
    6191             :  *
    6192             :  * This method is the same as the C function OSRGetNormProjParm().
    6193             :  *
    6194             :  * @param pszName the name of the parameter to fetch, from the set of
    6195             :  * SRS_PP codes in ogr_srs_api.h.
    6196             :  *
    6197             :  * @param dfDefaultValue the value to return if this parameter doesn't exist.
    6198             :  *
    6199             :  * @param pnErr place to put error code on failure.  Ignored if NULL.
    6200             :  *
    6201             :  * @return value of parameter.
    6202             :  */
    6203             : 
    6204        3724 : double OGRSpatialReference::GetNormProjParm(const char *pszName,
    6205             :                                             double dfDefaultValue,
    6206             :                                             OGRErr *pnErr) const
    6207             : 
    6208             : {
    6209        7448 :     TAKE_OPTIONAL_LOCK();
    6210             : 
    6211        3724 :     GetNormInfo();
    6212             : 
    6213        3724 :     OGRErr nError = OGRERR_NONE;
    6214        3724 :     double dfRawResult = GetProjParm(pszName, dfDefaultValue, &nError);
    6215        3724 :     if (pnErr != nullptr)
    6216           0 :         *pnErr = nError;
    6217             : 
    6218             :     // If we got the default just return it unadjusted.
    6219        3724 :     if (nError != OGRERR_NONE)
    6220         526 :         return dfRawResult;
    6221             : 
    6222        3198 :     if (d->dfToDegrees != 1.0 && IsAngularParameter(pszName))
    6223           8 :         dfRawResult *= d->dfToDegrees;
    6224             : 
    6225        3198 :     if (d->dfToMeter != 1.0 && IsLinearParameter(pszName))
    6226           5 :         return dfRawResult * d->dfToMeter;
    6227             : 
    6228        3193 :     return dfRawResult;
    6229             : }
    6230             : 
    6231             : /************************************************************************/
    6232             : /*                         OSRGetNormProjParm()                         */
    6233             : /************************************************************************/
    6234             : 
    6235             : /**
    6236             :  * \brief This function is the same as OGRSpatialReference::
    6237             :  *
    6238             :  * This function is the same as OGRSpatialReference::GetNormProjParm()
    6239             :  */
    6240           1 : double OSRGetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszName,
    6241             :                           double dfDefaultValue, OGRErr *pnErr)
    6242             : 
    6243             : {
    6244           1 :     VALIDATE_POINTER1(hSRS, "OSRGetNormProjParm", 0);
    6245             : 
    6246           1 :     return ToPointer(hSRS)->GetNormProjParm(pszName, dfDefaultValue, pnErr);
    6247             : }
    6248             : 
    6249             : /************************************************************************/
    6250             : /*                          SetNormProjParm()                           */
    6251             : /************************************************************************/
    6252             : 
    6253             : /**
    6254             :  * \brief Set a projection parameter with a normalized value.
    6255             :  *
    6256             :  * This method is the same as SetProjParm() except that the value of
    6257             :  * the parameter passed in is assumed to be in "normalized" form (decimal
    6258             :  * degrees for angular values, meters for linear values.  The values are
    6259             :  * converted in a form suitable for the GEOGCS and linear units in effect.
    6260             :  *
    6261             :  * This method is the same as the C function OSRSetNormProjParm().
    6262             :  *
    6263             :  * @param pszName the parameter name, which should be selected from
    6264             :  * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
    6265             :  *
    6266             :  * @param dfValue value to assign.
    6267             :  *
    6268             :  * @return OGRERR_NONE on success.
    6269             :  */
    6270             : 
    6271          91 : OGRErr OGRSpatialReference::SetNormProjParm(const char *pszName, double dfValue)
    6272             : 
    6273             : {
    6274         182 :     TAKE_OPTIONAL_LOCK();
    6275             : 
    6276          91 :     GetNormInfo();
    6277             : 
    6278          91 :     if (d->dfToDegrees != 0.0 &&
    6279          91 :         (d->dfToDegrees != 1.0 || d->dfFromGreenwich != 0.0) &&
    6280           0 :         IsAngularParameter(pszName))
    6281             :     {
    6282           0 :         dfValue /= d->dfToDegrees;
    6283             :     }
    6284          95 :     else if (d->dfToMeter != 1.0 && d->dfToMeter != 0.0 &&
    6285           4 :              IsLinearParameter(pszName))
    6286           4 :         dfValue /= d->dfToMeter;
    6287             : 
    6288         182 :     return SetProjParm(pszName, dfValue);
    6289             : }
    6290             : 
    6291             : /************************************************************************/
    6292             : /*                         OSRSetNormProjParm()                         */
    6293             : /************************************************************************/
    6294             : 
    6295             : /**
    6296             :  * \brief Set a projection parameter with a normalized value.
    6297             :  *
    6298             :  * This function is the same as OGRSpatialReference::SetNormProjParm()
    6299             :  */
    6300           0 : OGRErr OSRSetNormProjParm(OGRSpatialReferenceH hSRS, const char *pszParamName,
    6301             :                           double dfValue)
    6302             : 
    6303             : {
    6304           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNormProjParm", OGRERR_FAILURE);
    6305             : 
    6306           0 :     return ToPointer(hSRS)->SetNormProjParm(pszParamName, dfValue);
    6307             : }
    6308             : 
    6309             : /************************************************************************/
    6310             : /*                               SetTM()                                */
    6311             : /************************************************************************/
    6312             : 
    6313         439 : OGRErr OGRSpatialReference::SetTM(double dfCenterLat, double dfCenterLong,
    6314             :                                   double dfScale, double dfFalseEasting,
    6315             :                                   double dfFalseNorthing)
    6316             : 
    6317             : {
    6318         878 :     TAKE_OPTIONAL_LOCK();
    6319             : 
    6320         439 :     return d->replaceConversionAndUnref(
    6321             :         proj_create_conversion_transverse_mercator(
    6322             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    6323         878 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6324             : }
    6325             : 
    6326             : /************************************************************************/
    6327             : /*                              OSRSetTM()                              */
    6328             : /************************************************************************/
    6329             : 
    6330           1 : OGRErr OSRSetTM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6331             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    6332             :                 double dfFalseNorthing)
    6333             : 
    6334             : {
    6335           1 :     VALIDATE_POINTER1(hSRS, "OSRSetTM", OGRERR_FAILURE);
    6336             : 
    6337           1 :     return ToPointer(hSRS)->SetTM(dfCenterLat, dfCenterLong, dfScale,
    6338           1 :                                   dfFalseEasting, dfFalseNorthing);
    6339             : }
    6340             : 
    6341             : /************************************************************************/
    6342             : /*                            SetTMVariant()                            */
    6343             : /************************************************************************/
    6344             : 
    6345           0 : OGRErr OGRSpatialReference::SetTMVariant(const char *pszVariantName,
    6346             :                                          double dfCenterLat,
    6347             :                                          double dfCenterLong, double dfScale,
    6348             :                                          double dfFalseEasting,
    6349             :                                          double dfFalseNorthing)
    6350             : 
    6351             : {
    6352           0 :     TAKE_OPTIONAL_LOCK();
    6353             : 
    6354           0 :     SetProjection(pszVariantName);
    6355           0 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6356           0 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6357           0 :     SetNormProjParm(SRS_PP_SCALE_FACTOR, dfScale);
    6358           0 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6359           0 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6360             : 
    6361           0 :     return OGRERR_NONE;
    6362             : }
    6363             : 
    6364             : /************************************************************************/
    6365             : /*                          OSRSetTMVariant()                           */
    6366             : /************************************************************************/
    6367             : 
    6368           0 : OGRErr OSRSetTMVariant(OGRSpatialReferenceH hSRS, const char *pszVariantName,
    6369             :                        double dfCenterLat, double dfCenterLong, double dfScale,
    6370             :                        double dfFalseEasting, double dfFalseNorthing)
    6371             : 
    6372             : {
    6373           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMVariant", OGRERR_FAILURE);
    6374             : 
    6375           0 :     return ToPointer(hSRS)->SetTMVariant(pszVariantName, dfCenterLat,
    6376             :                                          dfCenterLong, dfScale, dfFalseEasting,
    6377           0 :                                          dfFalseNorthing);
    6378             : }
    6379             : 
    6380             : /************************************************************************/
    6381             : /*                              SetTMSO()                               */
    6382             : /************************************************************************/
    6383             : 
    6384           3 : OGRErr OGRSpatialReference::SetTMSO(double dfCenterLat, double dfCenterLong,
    6385             :                                     double dfScale, double dfFalseEasting,
    6386             :                                     double dfFalseNorthing)
    6387             : 
    6388             : {
    6389           6 :     TAKE_OPTIONAL_LOCK();
    6390             : 
    6391           3 :     auto conv = proj_create_conversion_transverse_mercator_south_oriented(
    6392             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale, dfFalseEasting,
    6393             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6394             : 
    6395           3 :     const char *pszName = nullptr;
    6396           3 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    6397           3 :     CPLString osName = pszName ? pszName : "";
    6398             : 
    6399           3 :     d->refreshProjObj();
    6400             : 
    6401           3 :     d->demoteFromBoundCRS();
    6402             : 
    6403           3 :     auto cs = proj_create_cartesian_2D_cs(
    6404             :         d->getPROJContext(), PJ_CART2D_WESTING_SOUTHING,
    6405           3 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    6406             :     auto projCRS =
    6407           3 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    6408           3 :                                   d->getGeodBaseCRS(), conv, cs);
    6409           3 :     proj_destroy(conv);
    6410           3 :     proj_destroy(cs);
    6411             : 
    6412           3 :     d->setPjCRS(projCRS);
    6413             : 
    6414           3 :     d->undoDemoteFromBoundCRS();
    6415             : 
    6416           6 :     return OGRERR_NONE;
    6417             : }
    6418             : 
    6419             : /************************************************************************/
    6420             : /*                             OSRSetTMSO()                             */
    6421             : /************************************************************************/
    6422             : 
    6423           0 : OGRErr OSRSetTMSO(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6424             :                   double dfCenterLong, double dfScale, double dfFalseEasting,
    6425             :                   double dfFalseNorthing)
    6426             : 
    6427             : {
    6428           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMSO", OGRERR_FAILURE);
    6429             : 
    6430           0 :     return ToPointer(hSRS)->SetTMSO(dfCenterLat, dfCenterLong, dfScale,
    6431           0 :                                     dfFalseEasting, dfFalseNorthing);
    6432             : }
    6433             : 
    6434             : /************************************************************************/
    6435             : /*                              SetTPED()                               */
    6436             : /************************************************************************/
    6437             : 
    6438           1 : OGRErr OGRSpatialReference::SetTPED(double dfLat1, double dfLong1,
    6439             :                                     double dfLat2, double dfLong2,
    6440             :                                     double dfFalseEasting,
    6441             :                                     double dfFalseNorthing)
    6442             : 
    6443             : {
    6444           2 :     TAKE_OPTIONAL_LOCK();
    6445             : 
    6446           1 :     return d->replaceConversionAndUnref(
    6447             :         proj_create_conversion_two_point_equidistant(
    6448             :             d->getPROJContext(), dfLat1, dfLong1, dfLat2, dfLong2,
    6449           2 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6450             : }
    6451             : 
    6452             : /************************************************************************/
    6453             : /*                             OSRSetTPED()                             */
    6454             : /************************************************************************/
    6455             : 
    6456           0 : OGRErr OSRSetTPED(OGRSpatialReferenceH hSRS, double dfLat1, double dfLong1,
    6457             :                   double dfLat2, double dfLong2, double dfFalseEasting,
    6458             :                   double dfFalseNorthing)
    6459             : 
    6460             : {
    6461           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTPED", OGRERR_FAILURE);
    6462             : 
    6463           0 :     return ToPointer(hSRS)->SetTPED(dfLat1, dfLong1, dfLat2, dfLong2,
    6464           0 :                                     dfFalseEasting, dfFalseNorthing);
    6465             : }
    6466             : 
    6467             : /************************************************************************/
    6468             : /*                               SetTMG()                               */
    6469             : /************************************************************************/
    6470             : 
    6471           0 : OGRErr OGRSpatialReference::SetTMG(double dfCenterLat, double dfCenterLong,
    6472             :                                    double dfFalseEasting,
    6473             :                                    double dfFalseNorthing)
    6474             : 
    6475             : {
    6476           0 :     TAKE_OPTIONAL_LOCK();
    6477             : 
    6478           0 :     return d->replaceConversionAndUnref(
    6479             :         proj_create_conversion_tunisia_mapping_grid(
    6480             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6481           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6482             : }
    6483             : 
    6484             : /************************************************************************/
    6485             : /*                             OSRSetTMG()                              */
    6486             : /************************************************************************/
    6487             : 
    6488           0 : OGRErr OSRSetTMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6489             :                  double dfCenterLong, double dfFalseEasting,
    6490             :                  double dfFalseNorthing)
    6491             : 
    6492             : {
    6493           0 :     VALIDATE_POINTER1(hSRS, "OSRSetTMG", OGRERR_FAILURE);
    6494             : 
    6495           0 :     return ToPointer(hSRS)->SetTMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    6496           0 :                                    dfFalseNorthing);
    6497             : }
    6498             : 
    6499             : /************************************************************************/
    6500             : /*                              SetACEA()                               */
    6501             : /************************************************************************/
    6502             : 
    6503          40 : OGRErr OGRSpatialReference::SetACEA(double dfStdP1, double dfStdP2,
    6504             :                                     double dfCenterLat, double dfCenterLong,
    6505             :                                     double dfFalseEasting,
    6506             :                                     double dfFalseNorthing)
    6507             : 
    6508             : {
    6509          80 :     TAKE_OPTIONAL_LOCK();
    6510             : 
    6511             :     // Note different order of parameters. The one in PROJ is conformant with
    6512             :     // EPSG
    6513          40 :     return d->replaceConversionAndUnref(
    6514             :         proj_create_conversion_albers_equal_area(
    6515             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6516          80 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6517             : }
    6518             : 
    6519             : /************************************************************************/
    6520             : /*                             OSRSetACEA()                             */
    6521             : /************************************************************************/
    6522             : 
    6523           0 : OGRErr OSRSetACEA(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6524             :                   double dfCenterLat, double dfCenterLong,
    6525             :                   double dfFalseEasting, double dfFalseNorthing)
    6526             : 
    6527             : {
    6528           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6529             : 
    6530           0 :     return ToPointer(hSRS)->SetACEA(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6531           0 :                                     dfFalseEasting, dfFalseNorthing);
    6532             : }
    6533             : 
    6534             : /************************************************************************/
    6535             : /*                               SetAE()                                */
    6536             : /************************************************************************/
    6537             : 
    6538          21 : OGRErr OGRSpatialReference::SetAE(double dfCenterLat, double dfCenterLong,
    6539             :                                   double dfFalseEasting, double dfFalseNorthing)
    6540             : 
    6541             : {
    6542          42 :     TAKE_OPTIONAL_LOCK();
    6543             : 
    6544          21 :     return d->replaceConversionAndUnref(
    6545             :         proj_create_conversion_azimuthal_equidistant(
    6546             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6547          42 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6548             : }
    6549             : 
    6550             : /************************************************************************/
    6551             : /*                              OSRSetAE()                              */
    6552             : /************************************************************************/
    6553             : 
    6554           0 : OGRErr OSRSetAE(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6555             :                 double dfCenterLong, double dfFalseEasting,
    6556             :                 double dfFalseNorthing)
    6557             : 
    6558             : {
    6559           0 :     VALIDATE_POINTER1(hSRS, "OSRSetACEA", OGRERR_FAILURE);
    6560             : 
    6561           0 :     return ToPointer(hSRS)->SetAE(dfCenterLat, dfCenterLong, dfFalseEasting,
    6562           0 :                                   dfFalseNorthing);
    6563             : }
    6564             : 
    6565             : /************************************************************************/
    6566             : /*                              SetBonne()                              */
    6567             : /************************************************************************/
    6568             : 
    6569           1 : OGRErr OGRSpatialReference::SetBonne(double dfStdP1, double dfCentralMeridian,
    6570             :                                      double dfFalseEasting,
    6571             :                                      double dfFalseNorthing)
    6572             : 
    6573             : {
    6574           2 :     TAKE_OPTIONAL_LOCK();
    6575             : 
    6576           1 :     return d->replaceConversionAndUnref(proj_create_conversion_bonne(
    6577             :         d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6578           2 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6579             : }
    6580             : 
    6581             : /************************************************************************/
    6582             : /*                            OSRSetBonne()                             */
    6583             : /************************************************************************/
    6584             : 
    6585           0 : OGRErr OSRSetBonne(OGRSpatialReferenceH hSRS, double dfStdP1,
    6586             :                    double dfCentralMeridian, double dfFalseEasting,
    6587             :                    double dfFalseNorthing)
    6588             : 
    6589             : {
    6590           0 :     VALIDATE_POINTER1(hSRS, "OSRSetBonne", OGRERR_FAILURE);
    6591             : 
    6592           0 :     return ToPointer(hSRS)->SetBonne(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6593           0 :                                      dfFalseNorthing);
    6594             : }
    6595             : 
    6596             : /************************************************************************/
    6597             : /*                               SetCEA()                               */
    6598             : /************************************************************************/
    6599             : 
    6600           4 : OGRErr OGRSpatialReference::SetCEA(double dfStdP1, double dfCentralMeridian,
    6601             :                                    double dfFalseEasting,
    6602             :                                    double dfFalseNorthing)
    6603             : 
    6604             : {
    6605           8 :     TAKE_OPTIONAL_LOCK();
    6606             : 
    6607           4 :     return d->replaceConversionAndUnref(
    6608             :         proj_create_conversion_lambert_cylindrical_equal_area(
    6609             :             d->getPROJContext(), dfStdP1, dfCentralMeridian, dfFalseEasting,
    6610           8 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6611             : }
    6612             : 
    6613             : /************************************************************************/
    6614             : /*                             OSRSetCEA()                              */
    6615             : /************************************************************************/
    6616             : 
    6617           0 : OGRErr OSRSetCEA(OGRSpatialReferenceH hSRS, double dfStdP1,
    6618             :                  double dfCentralMeridian, double dfFalseEasting,
    6619             :                  double dfFalseNorthing)
    6620             : 
    6621             : {
    6622           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCEA", OGRERR_FAILURE);
    6623             : 
    6624           0 :     return ToPointer(hSRS)->SetCEA(dfStdP1, dfCentralMeridian, dfFalseEasting,
    6625           0 :                                    dfFalseNorthing);
    6626             : }
    6627             : 
    6628             : /************************************************************************/
    6629             : /*                               SetCS()                                */
    6630             : /************************************************************************/
    6631             : 
    6632           5 : OGRErr OGRSpatialReference::SetCS(double dfCenterLat, double dfCenterLong,
    6633             :                                   double dfFalseEasting, double dfFalseNorthing)
    6634             : 
    6635             : {
    6636          10 :     TAKE_OPTIONAL_LOCK();
    6637             : 
    6638           5 :     return d->replaceConversionAndUnref(proj_create_conversion_cassini_soldner(
    6639             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    6640          10 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6641             : }
    6642             : 
    6643             : /************************************************************************/
    6644             : /*                              OSRSetCS()                              */
    6645             : /************************************************************************/
    6646             : 
    6647           0 : OGRErr OSRSetCS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6648             :                 double dfCenterLong, double dfFalseEasting,
    6649             :                 double dfFalseNorthing)
    6650             : 
    6651             : {
    6652           0 :     VALIDATE_POINTER1(hSRS, "OSRSetCS", OGRERR_FAILURE);
    6653             : 
    6654           0 :     return ToPointer(hSRS)->SetCS(dfCenterLat, dfCenterLong, dfFalseEasting,
    6655           0 :                                   dfFalseNorthing);
    6656             : }
    6657             : 
    6658             : /************************************************************************/
    6659             : /*                               SetEC()                                */
    6660             : /************************************************************************/
    6661             : 
    6662           7 : OGRErr OGRSpatialReference::SetEC(double dfStdP1, double dfStdP2,
    6663             :                                   double dfCenterLat, double dfCenterLong,
    6664             :                                   double dfFalseEasting, double dfFalseNorthing)
    6665             : 
    6666             : {
    6667          14 :     TAKE_OPTIONAL_LOCK();
    6668             : 
    6669             :     // Note: different order of arguments
    6670           7 :     return d->replaceConversionAndUnref(
    6671             :         proj_create_conversion_equidistant_conic(
    6672             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    6673          14 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6674             : }
    6675             : 
    6676             : /************************************************************************/
    6677             : /*                              OSRSetEC()                              */
    6678             : /************************************************************************/
    6679             : 
    6680           0 : OGRErr OSRSetEC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    6681             :                 double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    6682             :                 double dfFalseNorthing)
    6683             : 
    6684             : {
    6685           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEC", OGRERR_FAILURE);
    6686             : 
    6687           0 :     return ToPointer(hSRS)->SetEC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    6688           0 :                                   dfFalseEasting, dfFalseNorthing);
    6689             : }
    6690             : 
    6691             : /************************************************************************/
    6692             : /*                             SetEckert()                              */
    6693             : /************************************************************************/
    6694             : 
    6695          10 : OGRErr OGRSpatialReference::SetEckert(int nVariation,  // 1-6.
    6696             :                                       double dfCentralMeridian,
    6697             :                                       double dfFalseEasting,
    6698             :                                       double dfFalseNorthing)
    6699             : 
    6700             : {
    6701          20 :     TAKE_OPTIONAL_LOCK();
    6702             : 
    6703             :     PJ *conv;
    6704          10 :     if (nVariation == 1)
    6705             :     {
    6706           1 :         conv = proj_create_conversion_eckert_i(
    6707             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6708             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6709             :     }
    6710           9 :     else if (nVariation == 2)
    6711             :     {
    6712           1 :         conv = proj_create_conversion_eckert_ii(
    6713             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6714             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6715             :     }
    6716           8 :     else if (nVariation == 3)
    6717             :     {
    6718           1 :         conv = proj_create_conversion_eckert_iii(
    6719             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6720             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6721             :     }
    6722           7 :     else if (nVariation == 4)
    6723             :     {
    6724           3 :         conv = proj_create_conversion_eckert_iv(
    6725             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6726             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6727             :     }
    6728           4 :     else if (nVariation == 5)
    6729             :     {
    6730           1 :         conv = proj_create_conversion_eckert_v(
    6731             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6732             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6733             :     }
    6734           3 :     else if (nVariation == 6)
    6735             :     {
    6736           3 :         conv = proj_create_conversion_eckert_vi(
    6737             :             d->getPROJContext(), dfCentralMeridian, dfFalseEasting,
    6738             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    6739             :     }
    6740             :     else
    6741             :     {
    6742           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    6743             :                  "Unsupported Eckert variation (%d).", nVariation);
    6744           0 :         return OGRERR_UNSUPPORTED_SRS;
    6745             :     }
    6746             : 
    6747          10 :     return d->replaceConversionAndUnref(conv);
    6748             : }
    6749             : 
    6750             : /************************************************************************/
    6751             : /*                            OSRSetEckert()                            */
    6752             : /************************************************************************/
    6753             : 
    6754           0 : OGRErr OSRSetEckert(OGRSpatialReferenceH hSRS, int nVariation,
    6755             :                     double dfCentralMeridian, double dfFalseEasting,
    6756             :                     double dfFalseNorthing)
    6757             : 
    6758             : {
    6759           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckert", OGRERR_FAILURE);
    6760             : 
    6761           0 :     return ToPointer(hSRS)->SetEckert(nVariation, dfCentralMeridian,
    6762           0 :                                       dfFalseEasting, dfFalseNorthing);
    6763             : }
    6764             : 
    6765             : /************************************************************************/
    6766             : /*                            SetEckertIV()                             */
    6767             : /*                                                                      */
    6768             : /*      Deprecated                                                      */
    6769             : /************************************************************************/
    6770             : 
    6771           2 : OGRErr OGRSpatialReference::SetEckertIV(double dfCentralMeridian,
    6772             :                                         double dfFalseEasting,
    6773             :                                         double dfFalseNorthing)
    6774             : 
    6775             : {
    6776           2 :     return SetEckert(4, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6777             : }
    6778             : 
    6779             : /************************************************************************/
    6780             : /*                           OSRSetEckertIV()                           */
    6781             : /************************************************************************/
    6782             : 
    6783           0 : OGRErr OSRSetEckertIV(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6784             :                       double dfFalseEasting, double dfFalseNorthing)
    6785             : 
    6786             : {
    6787           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertIV", OGRERR_FAILURE);
    6788             : 
    6789           0 :     return ToPointer(hSRS)->SetEckertIV(dfCentralMeridian, dfFalseEasting,
    6790           0 :                                         dfFalseNorthing);
    6791             : }
    6792             : 
    6793             : /************************************************************************/
    6794             : /*                            SetEckertVI()                             */
    6795             : /*                                                                      */
    6796             : /*      Deprecated                                                      */
    6797             : /************************************************************************/
    6798             : 
    6799           2 : OGRErr OGRSpatialReference::SetEckertVI(double dfCentralMeridian,
    6800             :                                         double dfFalseEasting,
    6801             :                                         double dfFalseNorthing)
    6802             : 
    6803             : {
    6804           2 :     return SetEckert(6, dfCentralMeridian, dfFalseEasting, dfFalseNorthing);
    6805             : }
    6806             : 
    6807             : /************************************************************************/
    6808             : /*                           OSRSetEckertVI()                           */
    6809             : /************************************************************************/
    6810             : 
    6811           0 : OGRErr OSRSetEckertVI(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6812             :                       double dfFalseEasting, double dfFalseNorthing)
    6813             : 
    6814             : {
    6815           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEckertVI", OGRERR_FAILURE);
    6816             : 
    6817           0 :     return ToPointer(hSRS)->SetEckertVI(dfCentralMeridian, dfFalseEasting,
    6818           0 :                                         dfFalseNorthing);
    6819             : }
    6820             : 
    6821             : /************************************************************************/
    6822             : /*                         SetEquirectangular()                         */
    6823             : /************************************************************************/
    6824             : 
    6825           2 : OGRErr OGRSpatialReference::SetEquirectangular(double dfCenterLat,
    6826             :                                                double dfCenterLong,
    6827             :                                                double dfFalseEasting,
    6828             :                                                double dfFalseNorthing)
    6829             : 
    6830             : {
    6831           4 :     TAKE_OPTIONAL_LOCK();
    6832             : 
    6833           2 :     if (dfCenterLat == 0.0)
    6834             :     {
    6835           0 :         return d->replaceConversionAndUnref(
    6836             :             proj_create_conversion_equidistant_cylindrical(
    6837             :                 d->getPROJContext(), 0.0, dfCenterLong, dfFalseEasting,
    6838           0 :                 dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6839             :     }
    6840             : 
    6841             :     // Non-standard extension with non-zero latitude of origin
    6842           2 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6843           2 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6844           2 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6845           2 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6846           2 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6847             : 
    6848           2 :     return OGRERR_NONE;
    6849             : }
    6850             : 
    6851             : /************************************************************************/
    6852             : /*                       OSRSetEquirectangular()                        */
    6853             : /************************************************************************/
    6854             : 
    6855           0 : OGRErr OSRSetEquirectangular(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6856             :                              double dfCenterLong, double dfFalseEasting,
    6857             :                              double dfFalseNorthing)
    6858             : 
    6859             : {
    6860           0 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular", OGRERR_FAILURE);
    6861             : 
    6862           0 :     return ToPointer(hSRS)->SetEquirectangular(dfCenterLat, dfCenterLong,
    6863           0 :                                                dfFalseEasting, dfFalseNorthing);
    6864             : }
    6865             : 
    6866             : /************************************************************************/
    6867             : /*                         SetEquirectangular2()                        */
    6868             : /* Generalized form                                                     */
    6869             : /************************************************************************/
    6870             : 
    6871         179 : OGRErr OGRSpatialReference::SetEquirectangular2(double dfCenterLat,
    6872             :                                                 double dfCenterLong,
    6873             :                                                 double dfStdParallel1,
    6874             :                                                 double dfFalseEasting,
    6875             :                                                 double dfFalseNorthing)
    6876             : 
    6877             : {
    6878         358 :     TAKE_OPTIONAL_LOCK();
    6879             : 
    6880         179 :     if (dfCenterLat == 0.0)
    6881             :     {
    6882         174 :         return d->replaceConversionAndUnref(
    6883             :             proj_create_conversion_equidistant_cylindrical(
    6884             :                 d->getPROJContext(), dfStdParallel1, dfCenterLong,
    6885         174 :                 dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    6886             :     }
    6887             : 
    6888             :     // Non-standard extension with non-zero latitude of origin
    6889           5 :     SetProjection(SRS_PT_EQUIRECTANGULAR);
    6890           5 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    6891           5 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    6892           5 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdParallel1);
    6893           5 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    6894           5 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    6895             : 
    6896           5 :     return OGRERR_NONE;
    6897             : }
    6898             : 
    6899             : /************************************************************************/
    6900             : /*                       OSRSetEquirectangular2()                       */
    6901             : /************************************************************************/
    6902             : 
    6903           3 : OGRErr OSRSetEquirectangular2(OGRSpatialReferenceH hSRS, double dfCenterLat,
    6904             :                               double dfCenterLong, double dfStdParallel1,
    6905             :                               double dfFalseEasting, double dfFalseNorthing)
    6906             : 
    6907             : {
    6908           3 :     VALIDATE_POINTER1(hSRS, "OSRSetEquirectangular2", OGRERR_FAILURE);
    6909             : 
    6910           3 :     return ToPointer(hSRS)->SetEquirectangular2(dfCenterLat, dfCenterLong,
    6911             :                                                 dfStdParallel1, dfFalseEasting,
    6912           3 :                                                 dfFalseNorthing);
    6913             : }
    6914             : 
    6915             : /************************************************************************/
    6916             : /*                               SetGS()                                */
    6917             : /************************************************************************/
    6918             : 
    6919           5 : OGRErr OGRSpatialReference::SetGS(double dfCentralMeridian,
    6920             :                                   double dfFalseEasting, double dfFalseNorthing)
    6921             : 
    6922             : {
    6923           5 :     return d->replaceConversionAndUnref(proj_create_conversion_gall(
    6924             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    6925           5 :         nullptr, 0.0, nullptr, 0.0));
    6926             : }
    6927             : 
    6928             : /************************************************************************/
    6929             : /*                              OSRSetGS()                              */
    6930             : /************************************************************************/
    6931             : 
    6932           2 : OGRErr OSRSetGS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6933             :                 double dfFalseEasting, double dfFalseNorthing)
    6934             : 
    6935             : {
    6936           2 :     VALIDATE_POINTER1(hSRS, "OSRSetGS", OGRERR_FAILURE);
    6937             : 
    6938           2 :     return ToPointer(hSRS)->SetGS(dfCentralMeridian, dfFalseEasting,
    6939           2 :                                   dfFalseNorthing);
    6940             : }
    6941             : 
    6942             : /************************************************************************/
    6943             : /*                               SetGH()                                */
    6944             : /************************************************************************/
    6945             : 
    6946           0 : OGRErr OGRSpatialReference::SetGH(double dfCentralMeridian,
    6947             :                                   double dfFalseEasting, double dfFalseNorthing)
    6948             : 
    6949             : {
    6950           0 :     TAKE_OPTIONAL_LOCK();
    6951             : 
    6952           0 :     return d->replaceConversionAndUnref(proj_create_conversion_goode_homolosine(
    6953             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    6954           0 :         nullptr, 0.0, nullptr, 0.0));
    6955             : }
    6956             : 
    6957             : /************************************************************************/
    6958             : /*                              OSRSetGH()                              */
    6959             : /************************************************************************/
    6960             : 
    6961           0 : OGRErr OSRSetGH(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    6962             :                 double dfFalseEasting, double dfFalseNorthing)
    6963             : 
    6964             : {
    6965           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGH", OGRERR_FAILURE);
    6966             : 
    6967           0 :     return ToPointer(hSRS)->SetGH(dfCentralMeridian, dfFalseEasting,
    6968           0 :                                   dfFalseNorthing);
    6969             : }
    6970             : 
    6971             : /************************************************************************/
    6972             : /*                              SetIGH()                                */
    6973             : /************************************************************************/
    6974             : 
    6975           0 : OGRErr OGRSpatialReference::SetIGH()
    6976             : 
    6977             : {
    6978           0 :     TAKE_OPTIONAL_LOCK();
    6979             : 
    6980           0 :     return d->replaceConversionAndUnref(
    6981             :         proj_create_conversion_interrupted_goode_homolosine(
    6982           0 :             d->getPROJContext(), 0.0, 0.0, 0.0, nullptr, 0.0, nullptr, 0.0));
    6983             : }
    6984             : 
    6985             : /************************************************************************/
    6986             : /*                              OSRSetIGH()                             */
    6987             : /************************************************************************/
    6988             : 
    6989           0 : OGRErr OSRSetIGH(OGRSpatialReferenceH hSRS)
    6990             : 
    6991             : {
    6992           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIGH", OGRERR_FAILURE);
    6993             : 
    6994           0 :     return ToPointer(hSRS)->SetIGH();
    6995             : }
    6996             : 
    6997             : /************************************************************************/
    6998             : /*                              SetGEOS()                               */
    6999             : /************************************************************************/
    7000             : 
    7001           3 : OGRErr OGRSpatialReference::SetGEOS(double dfCentralMeridian,
    7002             :                                     double dfSatelliteHeight,
    7003             :                                     double dfFalseEasting,
    7004             :                                     double dfFalseNorthing)
    7005             : 
    7006             : {
    7007           6 :     TAKE_OPTIONAL_LOCK();
    7008             : 
    7009           3 :     return d->replaceConversionAndUnref(
    7010             :         proj_create_conversion_geostationary_satellite_sweep_y(
    7011             :             d->getPROJContext(), dfCentralMeridian, dfSatelliteHeight,
    7012           6 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7013             : }
    7014             : 
    7015             : /************************************************************************/
    7016             : /*                              OSRSetGEOS()                             */
    7017             : /************************************************************************/
    7018             : 
    7019           0 : OGRErr OSRSetGEOS(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7020             :                   double dfSatelliteHeight, double dfFalseEasting,
    7021             :                   double dfFalseNorthing)
    7022             : 
    7023             : {
    7024           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGEOS", OGRERR_FAILURE);
    7025             : 
    7026           0 :     return ToPointer(hSRS)->SetGEOS(dfCentralMeridian, dfSatelliteHeight,
    7027           0 :                                     dfFalseEasting, dfFalseNorthing);
    7028             : }
    7029             : 
    7030             : /************************************************************************/
    7031             : /*                       SetGaussSchreiberTMercator()                   */
    7032             : /************************************************************************/
    7033             : 
    7034           0 : OGRErr OGRSpatialReference::SetGaussSchreiberTMercator(double dfCenterLat,
    7035             :                                                        double dfCenterLong,
    7036             :                                                        double dfScale,
    7037             :                                                        double dfFalseEasting,
    7038             :                                                        double dfFalseNorthing)
    7039             : 
    7040             : {
    7041           0 :     TAKE_OPTIONAL_LOCK();
    7042             : 
    7043           0 :     return d->replaceConversionAndUnref(
    7044             :         proj_create_conversion_gauss_schreiber_transverse_mercator(
    7045             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7046           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7047             : }
    7048             : 
    7049             : /************************************************************************/
    7050             : /*                     OSRSetGaussSchreiberTMercator()                  */
    7051             : /************************************************************************/
    7052             : 
    7053           0 : OGRErr OSRSetGaussSchreiberTMercator(OGRSpatialReferenceH hSRS,
    7054             :                                      double dfCenterLat, double dfCenterLong,
    7055             :                                      double dfScale, double dfFalseEasting,
    7056             :                                      double dfFalseNorthing)
    7057             : 
    7058             : {
    7059           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGaussSchreiberTMercator", OGRERR_FAILURE);
    7060             : 
    7061           0 :     return ToPointer(hSRS)->SetGaussSchreiberTMercator(
    7062           0 :         dfCenterLat, dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing);
    7063             : }
    7064             : 
    7065             : /************************************************************************/
    7066             : /*                            SetGnomonic()                             */
    7067             : /************************************************************************/
    7068             : 
    7069           2 : OGRErr OGRSpatialReference::SetGnomonic(double dfCenterLat, double dfCenterLong,
    7070             :                                         double dfFalseEasting,
    7071             :                                         double dfFalseNorthing)
    7072             : 
    7073             : {
    7074           4 :     TAKE_OPTIONAL_LOCK();
    7075             : 
    7076           2 :     return d->replaceConversionAndUnref(proj_create_conversion_gnomonic(
    7077             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7078           4 :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7079             : }
    7080             : 
    7081             : /************************************************************************/
    7082             : /*                           OSRSetGnomonic()                           */
    7083             : /************************************************************************/
    7084             : 
    7085           0 : OGRErr OSRSetGnomonic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7086             :                       double dfCenterLong, double dfFalseEasting,
    7087             :                       double dfFalseNorthing)
    7088             : 
    7089             : {
    7090           0 :     VALIDATE_POINTER1(hSRS, "OSRSetGnomonic", OGRERR_FAILURE);
    7091             : 
    7092           0 :     return ToPointer(hSRS)->SetGnomonic(dfCenterLat, dfCenterLong,
    7093           0 :                                         dfFalseEasting, dfFalseNorthing);
    7094             : }
    7095             : 
    7096             : /************************************************************************/
    7097             : /*                              SetHOMAC()                              */
    7098             : /************************************************************************/
    7099             : 
    7100             : /**
    7101             :  * \brief Set an Hotine Oblique Mercator Azimuth Center projection using
    7102             :  * azimuth angle.
    7103             :  *
    7104             :  * This projection corresponds to EPSG projection method 9815, also
    7105             :  * sometimes known as hotine oblique mercator (variant B).
    7106             :  *
    7107             :  * This method does the same thing as the C function OSRSetHOMAC().
    7108             :  *
    7109             :  * @param dfCenterLat Latitude of the projection origin.
    7110             :  * @param dfCenterLong Longitude of the projection origin.
    7111             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7112             :  * centerline.
    7113             :  * @param dfRectToSkew Angle from Rectified to Skew Grid
    7114             :  * @param dfScale Scale factor applies to the projection origin.
    7115             :  * @param dfFalseEasting False easting.
    7116             :  * @param dfFalseNorthing False northing.
    7117             :  *
    7118             :  * @return OGRERR_NONE on success.
    7119             :  */
    7120             : 
    7121           4 : OGRErr OGRSpatialReference::SetHOMAC(double dfCenterLat, double dfCenterLong,
    7122             :                                      double dfAzimuth, double dfRectToSkew,
    7123             :                                      double dfScale, double dfFalseEasting,
    7124             :                                      double dfFalseNorthing)
    7125             : 
    7126             : {
    7127           8 :     TAKE_OPTIONAL_LOCK();
    7128             : 
    7129           4 :     return d->replaceConversionAndUnref(
    7130             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    7131             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7132             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7133           8 :             0.0, nullptr, 0.0));
    7134             : }
    7135             : 
    7136             : /************************************************************************/
    7137             : /*                            OSRSetHOMAC()                             */
    7138             : /************************************************************************/
    7139             : 
    7140             : /**
    7141             :  * \brief Set an Oblique Mercator projection using azimuth angle.
    7142             :  *
    7143             :  * This is the same as the C++ method OGRSpatialReference::SetHOMAC()
    7144             :  */
    7145           0 : OGRErr OSRSetHOMAC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7146             :                    double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7147             :                    double dfScale, double dfFalseEasting,
    7148             :                    double dfFalseNorthing)
    7149             : 
    7150             : {
    7151           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOMAC", OGRERR_FAILURE);
    7152             : 
    7153           0 :     return ToPointer(hSRS)->SetHOMAC(dfCenterLat, dfCenterLong, dfAzimuth,
    7154             :                                      dfRectToSkew, dfScale, dfFalseEasting,
    7155           0 :                                      dfFalseNorthing);
    7156             : }
    7157             : 
    7158             : /************************************************************************/
    7159             : /*                               SetHOM()                               */
    7160             : /************************************************************************/
    7161             : 
    7162             : /**
    7163             :  * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
    7164             :  *
    7165             :  * This projection corresponds to EPSG projection method 9812, also
    7166             :  * sometimes known as hotine oblique mercator (variant A)..
    7167             :  *
    7168             :  * This method does the same thing as the C function OSRSetHOM().
    7169             :  *
    7170             :  * @param dfCenterLat Latitude of the projection origin.
    7171             :  * @param dfCenterLong Longitude of the projection origin.
    7172             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7173             :  * centerline.
    7174             :  * @param dfRectToSkew Angle from Rectified to Skew Grid
    7175             :  * @param dfScale Scale factor applies to the projection origin.
    7176             :  * @param dfFalseEasting False easting.
    7177             :  * @param dfFalseNorthing False northing.
    7178             :  *
    7179             :  * @return OGRERR_NONE on success.
    7180             :  */
    7181             : 
    7182          13 : OGRErr OGRSpatialReference::SetHOM(double dfCenterLat, double dfCenterLong,
    7183             :                                    double dfAzimuth, double dfRectToSkew,
    7184             :                                    double dfScale, double dfFalseEasting,
    7185             :                                    double dfFalseNorthing)
    7186             : 
    7187             : {
    7188          26 :     TAKE_OPTIONAL_LOCK();
    7189             : 
    7190          13 :     return d->replaceConversionAndUnref(
    7191             :         proj_create_conversion_hotine_oblique_mercator_variant_a(
    7192             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7193             :             dfRectToSkew, dfScale, dfFalseEasting, dfFalseNorthing, nullptr,
    7194          26 :             0.0, nullptr, 0.0));
    7195             : }
    7196             : 
    7197             : /************************************************************************/
    7198             : /*                             OSRSetHOM()                              */
    7199             : /************************************************************************/
    7200             : /**
    7201             :  * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
    7202             :  *
    7203             :  * This is the same as the C++ method OGRSpatialReference::SetHOM()
    7204             :  */
    7205           0 : OGRErr OSRSetHOM(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7206             :                  double dfCenterLong, double dfAzimuth, double dfRectToSkew,
    7207             :                  double dfScale, double dfFalseEasting, double dfFalseNorthing)
    7208             : 
    7209             : {
    7210           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM", OGRERR_FAILURE);
    7211             : 
    7212           0 :     return ToPointer(hSRS)->SetHOM(dfCenterLat, dfCenterLong, dfAzimuth,
    7213             :                                    dfRectToSkew, dfScale, dfFalseEasting,
    7214           0 :                                    dfFalseNorthing);
    7215             : }
    7216             : 
    7217             : /************************************************************************/
    7218             : /*                             SetHOM2PNO()                             */
    7219             : /************************************************************************/
    7220             : 
    7221             : /**
    7222             :  * \brief Set a Hotine Oblique Mercator projection using two points on
    7223             :  * projection centerline.
    7224             :  *
    7225             :  * This method does the same thing as the C function OSRSetHOM2PNO().
    7226             :  *
    7227             :  * @param dfCenterLat Latitude of the projection origin.
    7228             :  * @param dfLat1 Latitude of the first point on center line.
    7229             :  * @param dfLong1 Longitude of the first point on center line.
    7230             :  * @param dfLat2 Latitude of the second point on center line.
    7231             :  * @param dfLong2 Longitude of the second point on center line.
    7232             :  * @param dfScale Scale factor applies to the projection origin.
    7233             :  * @param dfFalseEasting False easting.
    7234             :  * @param dfFalseNorthing False northing.
    7235             :  *
    7236             :  * @return OGRERR_NONE on success.
    7237             :  */
    7238             : 
    7239           3 : OGRErr OGRSpatialReference::SetHOM2PNO(double dfCenterLat, double dfLat1,
    7240             :                                        double dfLong1, double dfLat2,
    7241             :                                        double dfLong2, double dfScale,
    7242             :                                        double dfFalseEasting,
    7243             :                                        double dfFalseNorthing)
    7244             : 
    7245             : {
    7246           6 :     TAKE_OPTIONAL_LOCK();
    7247             : 
    7248           3 :     return d->replaceConversionAndUnref(
    7249             :         proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin(
    7250             :             d->getPROJContext(), dfCenterLat, dfLat1, dfLong1, dfLat2, dfLong2,
    7251             :             dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    7252           6 :             0.0));
    7253             : }
    7254             : 
    7255             : /************************************************************************/
    7256             : /*                           OSRSetHOM2PNO()                            */
    7257             : /************************************************************************/
    7258             : /**
    7259             :  * \brief Set a Hotine Oblique Mercator projection using two points on
    7260             :  *  projection centerline.
    7261             :  *
    7262             :  * This is the same as the C++ method OGRSpatialReference::SetHOM2PNO()
    7263             :  */
    7264           0 : OGRErr OSRSetHOM2PNO(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7265             :                      double dfLat1, double dfLong1, double dfLat2,
    7266             :                      double dfLong2, double dfScale, double dfFalseEasting,
    7267             :                      double dfFalseNorthing)
    7268             : 
    7269             : {
    7270           0 :     VALIDATE_POINTER1(hSRS, "OSRSetHOM2PNO", OGRERR_FAILURE);
    7271             : 
    7272           0 :     return ToPointer(hSRS)->SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
    7273             :                                        dfLong2, dfScale, dfFalseEasting,
    7274           0 :                                        dfFalseNorthing);
    7275             : }
    7276             : 
    7277             : /************************************************************************/
    7278             : /*                               SetLOM()                               */
    7279             : /************************************************************************/
    7280             : 
    7281             : /**
    7282             :  * \brief Set a Laborde Oblique Mercator projection.
    7283             :  *
    7284             :  * @param dfCenterLat Latitude of the projection origin.
    7285             :  * @param dfCenterLong Longitude of the projection origin.
    7286             :  * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
    7287             :  * centerline.
    7288             :  * @param dfScale Scale factor on the initiali line
    7289             :  * @param dfFalseEasting False easting.
    7290             :  * @param dfFalseNorthing False northing.
    7291             :  *
    7292             :  * @return OGRERR_NONE on success.
    7293             :  */
    7294             : 
    7295           0 : OGRErr OGRSpatialReference::SetLOM(double dfCenterLat, double dfCenterLong,
    7296             :                                    double dfAzimuth, double dfScale,
    7297             :                                    double dfFalseEasting,
    7298             :                                    double dfFalseNorthing)
    7299             : 
    7300             : {
    7301           0 :     TAKE_OPTIONAL_LOCK();
    7302             : 
    7303           0 :     return d->replaceConversionAndUnref(
    7304             :         proj_create_conversion_laborde_oblique_mercator(
    7305             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth, dfScale,
    7306           0 :             dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7307             : }
    7308             : 
    7309             : /************************************************************************/
    7310             : /*                            SetIWMPolyconic()                         */
    7311             : /************************************************************************/
    7312             : 
    7313           0 : OGRErr OGRSpatialReference::SetIWMPolyconic(double dfLat1, double dfLat2,
    7314             :                                             double dfCenterLong,
    7315             :                                             double dfFalseEasting,
    7316             :                                             double dfFalseNorthing)
    7317             : 
    7318             : {
    7319           0 :     TAKE_OPTIONAL_LOCK();
    7320             : 
    7321           0 :     return d->replaceConversionAndUnref(
    7322             :         proj_create_conversion_international_map_world_polyconic(
    7323             :             d->getPROJContext(), dfCenterLong, dfLat1, dfLat2, dfFalseEasting,
    7324           0 :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0));
    7325             : }
    7326             : 
    7327             : /************************************************************************/
    7328             : /*                          OSRSetIWMPolyconic()                        */
    7329             : /************************************************************************/
    7330             : 
    7331           0 : OGRErr OSRSetIWMPolyconic(OGRSpatialReferenceH hSRS, double dfLat1,
    7332             :                           double dfLat2, double dfCenterLong,
    7333             :                           double dfFalseEasting, double dfFalseNorthing)
    7334             : 
    7335             : {
    7336           0 :     VALIDATE_POINTER1(hSRS, "OSRSetIWMPolyconic", OGRERR_FAILURE);
    7337             : 
    7338           0 :     return ToPointer(hSRS)->SetIWMPolyconic(dfLat1, dfLat2, dfCenterLong,
    7339           0 :                                             dfFalseEasting, dfFalseNorthing);
    7340             : }
    7341             : 
    7342             : /************************************************************************/
    7343             : /*                             SetKrovak()                              */
    7344             : /************************************************************************/
    7345             : 
    7346             : /** Krovak east-north projection.
    7347             :  *
    7348             :  * Note that dfAzimuth and dfPseudoStdParallel1 are ignored when exporting
    7349             :  * to PROJ and should be respectively set to 30.28813972222222 and 78.5
    7350             :  */
    7351           3 : OGRErr OGRSpatialReference::SetKrovak(double dfCenterLat, double dfCenterLong,
    7352             :                                       double dfAzimuth,
    7353             :                                       double dfPseudoStdParallel1,
    7354             :                                       double dfScale, double dfFalseEasting,
    7355             :                                       double dfFalseNorthing)
    7356             : 
    7357             : {
    7358           6 :     TAKE_OPTIONAL_LOCK();
    7359             : 
    7360           3 :     return d->replaceConversionAndUnref(
    7361             :         proj_create_conversion_krovak_north_oriented(
    7362             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfAzimuth,
    7363             :             dfPseudoStdParallel1, dfScale, dfFalseEasting, dfFalseNorthing,
    7364           6 :             nullptr, 0.0, nullptr, 0.0));
    7365             : }
    7366             : 
    7367             : /************************************************************************/
    7368             : /*                            OSRSetKrovak()                            */
    7369             : /************************************************************************/
    7370             : 
    7371           0 : OGRErr OSRSetKrovak(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7372             :                     double dfCenterLong, double dfAzimuth,
    7373             :                     double dfPseudoStdParallel1, double dfScale,
    7374             :                     double dfFalseEasting, double dfFalseNorthing)
    7375             : 
    7376             : {
    7377           0 :     VALIDATE_POINTER1(hSRS, "OSRSetKrovak", OGRERR_FAILURE);
    7378             : 
    7379           0 :     return ToPointer(hSRS)->SetKrovak(dfCenterLat, dfCenterLong, dfAzimuth,
    7380             :                                       dfPseudoStdParallel1, dfScale,
    7381           0 :                                       dfFalseEasting, dfFalseNorthing);
    7382             : }
    7383             : 
    7384             : /************************************************************************/
    7385             : /*                              SetLAEA()                               */
    7386             : /************************************************************************/
    7387             : 
    7388          17 : OGRErr OGRSpatialReference::SetLAEA(double dfCenterLat, double dfCenterLong,
    7389             :                                     double dfFalseEasting,
    7390             :                                     double dfFalseNorthing)
    7391             : 
    7392             : {
    7393          34 :     TAKE_OPTIONAL_LOCK();
    7394             : 
    7395          17 :     auto conv = proj_create_conversion_lambert_azimuthal_equal_area(
    7396             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7397             :         dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    7398             : 
    7399          17 :     const char *pszName = nullptr;
    7400          17 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7401          17 :     CPLString osName = pszName ? pszName : "";
    7402             : 
    7403          17 :     d->refreshProjObj();
    7404             : 
    7405          17 :     d->demoteFromBoundCRS();
    7406             : 
    7407          17 :     auto cs = proj_create_cartesian_2D_cs(
    7408             :         d->getPROJContext(),
    7409          17 :         std::fabs(dfCenterLat - 90) < 1e-10 && dfCenterLong == 0
    7410             :             ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7411           0 :         : std::fabs(dfCenterLat - -90) < 1e-10 && dfCenterLong == 0
    7412          14 :             ? PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH
    7413             :             : PJ_CART2D_EASTING_NORTHING,
    7414          17 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7415             :     auto projCRS =
    7416          17 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7417          17 :                                   d->getGeodBaseCRS(), conv, cs);
    7418          17 :     proj_destroy(conv);
    7419          17 :     proj_destroy(cs);
    7420             : 
    7421          17 :     d->setPjCRS(projCRS);
    7422             : 
    7423          17 :     d->undoDemoteFromBoundCRS();
    7424             : 
    7425          34 :     return OGRERR_NONE;
    7426             : }
    7427             : 
    7428             : /************************************************************************/
    7429             : /*                             OSRSetLAEA()                             */
    7430             : /************************************************************************/
    7431             : 
    7432           0 : OGRErr OSRSetLAEA(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7433             :                   double dfCenterLong, double dfFalseEasting,
    7434             :                   double dfFalseNorthing)
    7435             : 
    7436             : {
    7437           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLAEA", OGRERR_FAILURE);
    7438             : 
    7439           0 :     return ToPointer(hSRS)->SetLAEA(dfCenterLat, dfCenterLong, dfFalseEasting,
    7440           0 :                                     dfFalseNorthing);
    7441             : }
    7442             : 
    7443             : /************************************************************************/
    7444             : /*                               SetLCC()                               */
    7445             : /************************************************************************/
    7446             : 
    7447         148 : OGRErr OGRSpatialReference::SetLCC(double dfStdP1, double dfStdP2,
    7448             :                                    double dfCenterLat, double dfCenterLong,
    7449             :                                    double dfFalseEasting,
    7450             :                                    double dfFalseNorthing)
    7451             : 
    7452             : {
    7453         296 :     TAKE_OPTIONAL_LOCK();
    7454             : 
    7455         148 :     return d->replaceConversionAndUnref(
    7456             :         proj_create_conversion_lambert_conic_conformal_2sp(
    7457             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7458         296 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7459             : }
    7460             : 
    7461             : /************************************************************************/
    7462             : /*                             OSRSetLCC()                              */
    7463             : /************************************************************************/
    7464             : 
    7465           1 : OGRErr OSRSetLCC(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7466             :                  double dfCenterLat, double dfCenterLong, double dfFalseEasting,
    7467             :                  double dfFalseNorthing)
    7468             : 
    7469             : {
    7470           1 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC", OGRERR_FAILURE);
    7471             : 
    7472           1 :     return ToPointer(hSRS)->SetLCC(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7473           1 :                                    dfFalseEasting, dfFalseNorthing);
    7474             : }
    7475             : 
    7476             : /************************************************************************/
    7477             : /*                             SetLCC1SP()                              */
    7478             : /************************************************************************/
    7479             : 
    7480          10 : OGRErr OGRSpatialReference::SetLCC1SP(double dfCenterLat, double dfCenterLong,
    7481             :                                       double dfScale, double dfFalseEasting,
    7482             :                                       double dfFalseNorthing)
    7483             : 
    7484             : {
    7485          20 :     TAKE_OPTIONAL_LOCK();
    7486             : 
    7487          10 :     return d->replaceConversionAndUnref(
    7488             :         proj_create_conversion_lambert_conic_conformal_1sp(
    7489             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7490          20 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7491             : }
    7492             : 
    7493             : /************************************************************************/
    7494             : /*                            OSRSetLCC1SP()                            */
    7495             : /************************************************************************/
    7496             : 
    7497           0 : OGRErr OSRSetLCC1SP(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7498             :                     double dfCenterLong, double dfScale, double dfFalseEasting,
    7499             :                     double dfFalseNorthing)
    7500             : 
    7501             : {
    7502           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCC1SP", OGRERR_FAILURE);
    7503             : 
    7504           0 :     return ToPointer(hSRS)->SetLCC1SP(dfCenterLat, dfCenterLong, dfScale,
    7505           0 :                                       dfFalseEasting, dfFalseNorthing);
    7506             : }
    7507             : 
    7508             : /************************************************************************/
    7509             : /*                              SetLCCB()                               */
    7510             : /************************************************************************/
    7511             : 
    7512           2 : OGRErr OGRSpatialReference::SetLCCB(double dfStdP1, double dfStdP2,
    7513             :                                     double dfCenterLat, double dfCenterLong,
    7514             :                                     double dfFalseEasting,
    7515             :                                     double dfFalseNorthing)
    7516             : 
    7517             : {
    7518           4 :     TAKE_OPTIONAL_LOCK();
    7519             : 
    7520           2 :     return d->replaceConversionAndUnref(
    7521             :         proj_create_conversion_lambert_conic_conformal_2sp_belgium(
    7522             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfStdP1, dfStdP2,
    7523           4 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7524             : }
    7525             : 
    7526             : /************************************************************************/
    7527             : /*                             OSRSetLCCB()                             */
    7528             : /************************************************************************/
    7529             : 
    7530           0 : OGRErr OSRSetLCCB(OGRSpatialReferenceH hSRS, double dfStdP1, double dfStdP2,
    7531             :                   double dfCenterLat, double dfCenterLong,
    7532             :                   double dfFalseEasting, double dfFalseNorthing)
    7533             : 
    7534             : {
    7535           0 :     VALIDATE_POINTER1(hSRS, "OSRSetLCCB", OGRERR_FAILURE);
    7536             : 
    7537           0 :     return ToPointer(hSRS)->SetLCCB(dfStdP1, dfStdP2, dfCenterLat, dfCenterLong,
    7538           0 :                                     dfFalseEasting, dfFalseNorthing);
    7539             : }
    7540             : 
    7541             : /************************************************************************/
    7542             : /*                               SetMC()                                */
    7543             : /************************************************************************/
    7544             : 
    7545           4 : OGRErr OGRSpatialReference::SetMC(double dfCenterLat, double dfCenterLong,
    7546             :                                   double dfFalseEasting, double dfFalseNorthing)
    7547             : 
    7548             : {
    7549           8 :     TAKE_OPTIONAL_LOCK();
    7550             : 
    7551             :     (void)dfCenterLat;  // ignored
    7552             : 
    7553           4 :     return d->replaceConversionAndUnref(
    7554             :         proj_create_conversion_miller_cylindrical(
    7555             :             d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7556           8 :             nullptr, 0, nullptr, 0));
    7557             : }
    7558             : 
    7559             : /************************************************************************/
    7560             : /*                              OSRSetMC()                              */
    7561             : /************************************************************************/
    7562             : 
    7563           0 : OGRErr OSRSetMC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7564             :                 double dfCenterLong, double dfFalseEasting,
    7565             :                 double dfFalseNorthing)
    7566             : 
    7567             : {
    7568           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMC", OGRERR_FAILURE);
    7569             : 
    7570           0 :     return ToPointer(hSRS)->SetMC(dfCenterLat, dfCenterLong, dfFalseEasting,
    7571           0 :                                   dfFalseNorthing);
    7572             : }
    7573             : 
    7574             : /************************************************************************/
    7575             : /*                            SetMercator()                             */
    7576             : /************************************************************************/
    7577             : 
    7578          59 : OGRErr OGRSpatialReference::SetMercator(double dfCenterLat, double dfCenterLong,
    7579             :                                         double dfScale, double dfFalseEasting,
    7580             :                                         double dfFalseNorthing)
    7581             : 
    7582             : {
    7583         118 :     TAKE_OPTIONAL_LOCK();
    7584             : 
    7585          59 :     if (dfCenterLat != 0.0 && dfScale == 1.0)
    7586             :     {
    7587             :         // Not sure this is correct, but this is how it has been used
    7588             :         // historically
    7589           0 :         return SetMercator2SP(dfCenterLat, 0.0, dfCenterLong, dfFalseEasting,
    7590           0 :                               dfFalseNorthing);
    7591             :     }
    7592          59 :     return d->replaceConversionAndUnref(
    7593             :         proj_create_conversion_mercator_variant_a(
    7594             :             d->getPROJContext(),
    7595             :             dfCenterLat,  // should be zero
    7596             :             dfCenterLong, dfScale, dfFalseEasting, dfFalseNorthing, nullptr, 0,
    7597          59 :             nullptr, 0));
    7598             : }
    7599             : 
    7600             : /************************************************************************/
    7601             : /*                           OSRSetMercator()                           */
    7602             : /************************************************************************/
    7603             : 
    7604           2 : OGRErr OSRSetMercator(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7605             :                       double dfCenterLong, double dfScale,
    7606             :                       double dfFalseEasting, double dfFalseNorthing)
    7607             : 
    7608             : {
    7609           2 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator", OGRERR_FAILURE);
    7610             : 
    7611           2 :     return ToPointer(hSRS)->SetMercator(dfCenterLat, dfCenterLong, dfScale,
    7612           2 :                                         dfFalseEasting, dfFalseNorthing);
    7613             : }
    7614             : 
    7615             : /************************************************************************/
    7616             : /*                           SetMercator2SP()                           */
    7617             : /************************************************************************/
    7618             : 
    7619          31 : OGRErr OGRSpatialReference::SetMercator2SP(double dfStdP1, double dfCenterLat,
    7620             :                                            double dfCenterLong,
    7621             :                                            double dfFalseEasting,
    7622             :                                            double dfFalseNorthing)
    7623             : 
    7624             : {
    7625          31 :     if (dfCenterLat == 0.0)
    7626             :     {
    7627          30 :         return d->replaceConversionAndUnref(
    7628             :             proj_create_conversion_mercator_variant_b(
    7629             :                 d->getPROJContext(), dfStdP1, dfCenterLong, dfFalseEasting,
    7630          30 :                 dfFalseNorthing, nullptr, 0, nullptr, 0));
    7631             :     }
    7632             : 
    7633           1 :     TAKE_OPTIONAL_LOCK();
    7634             : 
    7635           1 :     SetProjection(SRS_PT_MERCATOR_2SP);
    7636             : 
    7637           1 :     SetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, dfStdP1);
    7638           1 :     SetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat);
    7639           1 :     SetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, dfCenterLong);
    7640           1 :     SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
    7641           1 :     SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
    7642             : 
    7643           1 :     return OGRERR_NONE;
    7644             : }
    7645             : 
    7646             : /************************************************************************/
    7647             : /*                         OSRSetMercator2SP()                          */
    7648             : /************************************************************************/
    7649             : 
    7650           1 : OGRErr OSRSetMercator2SP(OGRSpatialReferenceH hSRS, double dfStdP1,
    7651             :                          double dfCenterLat, double dfCenterLong,
    7652             :                          double dfFalseEasting, double dfFalseNorthing)
    7653             : 
    7654             : {
    7655           1 :     VALIDATE_POINTER1(hSRS, "OSRSetMercator2SP", OGRERR_FAILURE);
    7656             : 
    7657           1 :     return ToPointer(hSRS)->SetMercator2SP(dfStdP1, dfCenterLat, dfCenterLong,
    7658           1 :                                            dfFalseEasting, dfFalseNorthing);
    7659             : }
    7660             : 
    7661             : /************************************************************************/
    7662             : /*                            SetMollweide()                            */
    7663             : /************************************************************************/
    7664             : 
    7665           3 : OGRErr OGRSpatialReference::SetMollweide(double dfCentralMeridian,
    7666             :                                          double dfFalseEasting,
    7667             :                                          double dfFalseNorthing)
    7668             : 
    7669             : {
    7670           6 :     TAKE_OPTIONAL_LOCK();
    7671             : 
    7672           3 :     return d->replaceConversionAndUnref(proj_create_conversion_mollweide(
    7673             :         d->getPROJContext(), dfCentralMeridian, dfFalseEasting, dfFalseNorthing,
    7674           6 :         nullptr, 0, nullptr, 0));
    7675             : }
    7676             : 
    7677             : /************************************************************************/
    7678             : /*                          OSRSetMollweide()                           */
    7679             : /************************************************************************/
    7680             : 
    7681           0 : OGRErr OSRSetMollweide(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    7682             :                        double dfFalseEasting, double dfFalseNorthing)
    7683             : 
    7684             : {
    7685           0 :     VALIDATE_POINTER1(hSRS, "OSRSetMollweide", OGRERR_FAILURE);
    7686             : 
    7687           0 :     return ToPointer(hSRS)->SetMollweide(dfCentralMeridian, dfFalseEasting,
    7688           0 :                                          dfFalseNorthing);
    7689             : }
    7690             : 
    7691             : /************************************************************************/
    7692             : /*                              SetNZMG()                               */
    7693             : /************************************************************************/
    7694             : 
    7695           7 : OGRErr OGRSpatialReference::SetNZMG(double dfCenterLat, double dfCenterLong,
    7696             :                                     double dfFalseEasting,
    7697             :                                     double dfFalseNorthing)
    7698             : 
    7699             : {
    7700          14 :     TAKE_OPTIONAL_LOCK();
    7701             : 
    7702           7 :     return d->replaceConversionAndUnref(
    7703             :         proj_create_conversion_new_zealand_mapping_grid(
    7704             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7705          14 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7706             : }
    7707             : 
    7708             : /************************************************************************/
    7709             : /*                             OSRSetNZMG()                             */
    7710             : /************************************************************************/
    7711             : 
    7712           0 : OGRErr OSRSetNZMG(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7713             :                   double dfCenterLong, double dfFalseEasting,
    7714             :                   double dfFalseNorthing)
    7715             : 
    7716             : {
    7717           0 :     VALIDATE_POINTER1(hSRS, "OSRSetNZMG", OGRERR_FAILURE);
    7718             : 
    7719           0 :     return ToPointer(hSRS)->SetNZMG(dfCenterLat, dfCenterLong, dfFalseEasting,
    7720           0 :                                     dfFalseNorthing);
    7721             : }
    7722             : 
    7723             : /************************************************************************/
    7724             : /*                               SetOS()                                */
    7725             : /************************************************************************/
    7726             : 
    7727           6 : OGRErr OGRSpatialReference::SetOS(double dfOriginLat, double dfCMeridian,
    7728             :                                   double dfScale, double dfFalseEasting,
    7729             :                                   double dfFalseNorthing)
    7730             : 
    7731             : {
    7732          12 :     TAKE_OPTIONAL_LOCK();
    7733             : 
    7734           6 :     return d->replaceConversionAndUnref(
    7735             :         proj_create_conversion_oblique_stereographic(
    7736             :             d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale,
    7737          12 :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0));
    7738             : }
    7739             : 
    7740             : /************************************************************************/
    7741             : /*                              OSRSetOS()                              */
    7742             : /************************************************************************/
    7743             : 
    7744           0 : OGRErr OSRSetOS(OGRSpatialReferenceH hSRS, double dfOriginLat,
    7745             :                 double dfCMeridian, double dfScale, double dfFalseEasting,
    7746             :                 double dfFalseNorthing)
    7747             : 
    7748             : {
    7749           0 :     VALIDATE_POINTER1(hSRS, "OSRSetOS", OGRERR_FAILURE);
    7750             : 
    7751           0 :     return ToPointer(hSRS)->SetOS(dfOriginLat, dfCMeridian, dfScale,
    7752           0 :                                   dfFalseEasting, dfFalseNorthing);
    7753             : }
    7754             : 
    7755             : /************************************************************************/
    7756             : /*                          SetOrthographic()                           */
    7757             : /************************************************************************/
    7758             : 
    7759           7 : OGRErr OGRSpatialReference::SetOrthographic(double dfCenterLat,
    7760             :                                             double dfCenterLong,
    7761             :                                             double dfFalseEasting,
    7762             :                                             double dfFalseNorthing)
    7763             : 
    7764             : {
    7765          14 :     TAKE_OPTIONAL_LOCK();
    7766             : 
    7767           7 :     return d->replaceConversionAndUnref(proj_create_conversion_orthographic(
    7768             :         d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7769          14 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    7770             : }
    7771             : 
    7772             : /************************************************************************/
    7773             : /*                         OSRSetOrthographic()                         */
    7774             : /************************************************************************/
    7775             : 
    7776           1 : OGRErr OSRSetOrthographic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7777             :                           double dfCenterLong, double dfFalseEasting,
    7778             :                           double dfFalseNorthing)
    7779             : 
    7780             : {
    7781           1 :     VALIDATE_POINTER1(hSRS, "OSRSetOrthographic", OGRERR_FAILURE);
    7782             : 
    7783           1 :     return ToPointer(hSRS)->SetOrthographic(dfCenterLat, dfCenterLong,
    7784           1 :                                             dfFalseEasting, dfFalseNorthing);
    7785             : }
    7786             : 
    7787             : /************************************************************************/
    7788             : /*                            SetPolyconic()                            */
    7789             : /************************************************************************/
    7790             : 
    7791           7 : OGRErr OGRSpatialReference::SetPolyconic(double dfCenterLat,
    7792             :                                          double dfCenterLong,
    7793             :                                          double dfFalseEasting,
    7794             :                                          double dfFalseNorthing)
    7795             : 
    7796             : {
    7797          14 :     TAKE_OPTIONAL_LOCK();
    7798             : 
    7799             :     // note: it seems that by some definitions this should include a
    7800             :     //       scale_factor parameter.
    7801           7 :     return d->replaceConversionAndUnref(
    7802             :         proj_create_conversion_american_polyconic(
    7803             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7804          14 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    7805             : }
    7806             : 
    7807             : /************************************************************************/
    7808             : /*                          OSRSetPolyconic()                           */
    7809             : /************************************************************************/
    7810             : 
    7811           0 : OGRErr OSRSetPolyconic(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7812             :                        double dfCenterLong, double dfFalseEasting,
    7813             :                        double dfFalseNorthing)
    7814             : 
    7815             : {
    7816           0 :     VALIDATE_POINTER1(hSRS, "OSRSetPolyconic", OGRERR_FAILURE);
    7817             : 
    7818           0 :     return ToPointer(hSRS)->SetPolyconic(dfCenterLat, dfCenterLong,
    7819           0 :                                          dfFalseEasting, dfFalseNorthing);
    7820             : }
    7821             : 
    7822             : /************************************************************************/
    7823             : /*                               SetPS()                                */
    7824             : /************************************************************************/
    7825             : 
    7826             : /** Sets a Polar Stereographic projection.
    7827             :  *
    7828             :  * Two variants are possible:
    7829             :  * - Polar Stereographic Variant A: dfCenterLat must be +/- 90° and is
    7830             :  *   interpreted as the latitude of origin, combined with the scale factor
    7831             :  * - Polar Stereographic Variant B: dfCenterLat is different from +/- 90° and
    7832             :  *   is interpreted as the latitude of true scale. In that situation, dfScale
    7833             :  *   must be set to 1 (it is ignored in the projection parameters)
    7834             :  */
    7835          30 : OGRErr OGRSpatialReference::SetPS(double dfCenterLat, double dfCenterLong,
    7836             :                                   double dfScale, double dfFalseEasting,
    7837             :                                   double dfFalseNorthing)
    7838             : 
    7839             : {
    7840          60 :     TAKE_OPTIONAL_LOCK();
    7841             : 
    7842             :     PJ *conv;
    7843          30 :     if (dfScale == 1.0 && std::abs(std::abs(dfCenterLat) - 90) > 1e-8)
    7844             :     {
    7845          20 :         conv = proj_create_conversion_polar_stereographic_variant_b(
    7846             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfFalseEasting,
    7847             :             dfFalseNorthing, nullptr, 0, nullptr, 0);
    7848             :     }
    7849             :     else
    7850             :     {
    7851          10 :         conv = proj_create_conversion_polar_stereographic_variant_a(
    7852             :             d->getPROJContext(), dfCenterLat, dfCenterLong, dfScale,
    7853             :             dfFalseEasting, dfFalseNorthing, nullptr, 0, nullptr, 0);
    7854             :     }
    7855             : 
    7856          30 :     const char *pszName = nullptr;
    7857          30 :     double dfConvFactor = GetTargetLinearUnits(nullptr, &pszName);
    7858          30 :     CPLString osName = pszName ? pszName : "";
    7859             : 
    7860          30 :     d->refreshProjObj();
    7861             : 
    7862          30 :     d->demoteFromBoundCRS();
    7863             : 
    7864          30 :     auto cs = proj_create_cartesian_2D_cs(
    7865             :         d->getPROJContext(),
    7866             :         dfCenterLat > 0 ? PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH
    7867             :                         : PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH,
    7868          30 :         !osName.empty() ? osName.c_str() : nullptr, dfConvFactor);
    7869             :     auto projCRS =
    7870          30 :         proj_create_projected_crs(d->getPROJContext(), d->getProjCRSName(),
    7871          30 :                                   d->getGeodBaseCRS(), conv, cs);
    7872          30 :     proj_destroy(conv);
    7873          30 :     proj_destroy(cs);
    7874             : 
    7875          30 :     d->setPjCRS(projCRS);
    7876             : 
    7877          30 :     d->undoDemoteFromBoundCRS();
    7878             : 
    7879          60 :     return OGRERR_NONE;
    7880             : }
    7881             : 
    7882             : /************************************************************************/
    7883             : /*                              OSRSetPS()                              */
    7884             : /************************************************************************/
    7885             : 
    7886           1 : OGRErr OSRSetPS(OGRSpatialReferenceH hSRS, double dfCenterLat,
    7887             :                 double dfCenterLong, double dfScale, double dfFalseEasting,
    7888             :                 double dfFalseNorthing)
    7889             : 
    7890             : {
    7891           1 :     VALIDATE_POINTER1(hSRS, "OSRSetPS", OGRERR_FAILURE);
    7892             : 
    7893           1 :     return ToPointer(hSRS)->SetPS(dfCenterLat, dfCenterLong, dfScale,
    7894           1 :                                   dfFalseEasting, dfFalseNorthing);
    7895             : }
    7896             : 
    7897             : /************************************************************************/
    7898             : /*                            SetRobinson()                             */
    7899             : /************************************************************************/
    7900             : 
    7901           4 : OGRErr OGRSpatialReference::SetRobinson(double dfCenterLong,
    7902             :                                         double dfFalseEasting,
    7903             :                                         double dfFalseNorthing)
    7904             : 
    7905             : {
    7906           8 :     TAKE_OPTIONAL_LOCK();
    7907             : 
    7908           4 :     return d->replaceConversionAndUnref(proj_create_conversion_robinson(
    7909             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7910           8 :         nullptr, 0, nullptr, 0));
    7911             : }
    7912             : 
    7913             : /************************************************************************/
    7914             : /*                           OSRSetRobinson()                           */
    7915             : /************************************************************************/
    7916             : 
    7917           0 : OGRErr OSRSetRobinson(OGRSpatialReferenceH hSRS, double dfCenterLong,
    7918             :                       double dfFalseEasting, double dfFalseNorthing)
    7919             : 
    7920             : {
    7921           0 :     VALIDATE_POINTER1(hSRS, "OSRSetRobinson", OGRERR_FAILURE);
    7922             : 
    7923           0 :     return ToPointer(hSRS)->SetRobinson(dfCenterLong, dfFalseEasting,
    7924           0 :                                         dfFalseNorthing);
    7925             : }
    7926             : 
    7927             : /************************************************************************/
    7928             : /*                           SetSinusoidal()                            */
    7929             : /************************************************************************/
    7930             : 
    7931          35 : OGRErr OGRSpatialReference::SetSinusoidal(double dfCenterLong,
    7932             :                                           double dfFalseEasting,
    7933             :                                           double dfFalseNorthing)
    7934             : 
    7935             : {
    7936          70 :     TAKE_OPTIONAL_LOCK();
    7937             : 
    7938          35 :     return d->replaceConversionAndUnref(proj_create_conversion_sinusoidal(
    7939             :         d->getPROJContext(), dfCenterLong, dfFalseEasting, dfFalseNorthing,
    7940          70 :         nullptr, 0, nullptr, 0));
    7941             : }
    7942             : 
    7943             : /************************************************************************/
    7944             : /*                          OSRSetSinusoidal()                          */
    7945             : /************************************************************************/
    7946             : 
    7947           1 : OGRErr OSRSetSinusoidal(OGRSpatialReferenceH hSRS, double dfCenterLong,
    7948             :                         double dfFalseEasting, double dfFalseNorthing)
    7949             : 
    7950             : {
    7951           1 :     VALIDATE_POINTER1(hSRS, "OSRSetSinusoidal", OGRERR_FAILURE);
    7952             : 
    7953           1 :     return ToPointer(hSRS)->SetSinusoidal(dfCenterLong, dfFalseEasting,
    7954           1 :                                           dfFalseNorthing);
    7955             : }
    7956             : 
    7957             : /************************************************************************/
    7958             : /*                          SetStereographic()                          */
    7959             : /************************************************************************/
    7960             : 
    7961           2 : OGRErr OGRSpatialReference::SetStereographic(double dfOriginLat,
    7962             :                                              double dfCMeridian, double dfScale,
    7963             :                                              double dfFalseEasting,
    7964             :                                              double dfFalseNorthing)
    7965             : 
    7966             : {
    7967           4 :     TAKE_OPTIONAL_LOCK();
    7968             : 
    7969           2 :     return d->replaceConversionAndUnref(proj_create_conversion_stereographic(
    7970             :         d->getPROJContext(), dfOriginLat, dfCMeridian, dfScale, dfFalseEasting,
    7971           4 :         dfFalseNorthing, nullptr, 0, nullptr, 0));
    7972             : }
    7973             : 
    7974             : /************************************************************************/
    7975             : /*                        OSRSetStereographic()                         */
    7976             : /************************************************************************/
    7977             : 
    7978           0 : OGRErr OSRSetStereographic(OGRSpatialReferenceH hSRS, double dfOriginLat,
    7979             :                            double dfCMeridian, double dfScale,
    7980             :                            double dfFalseEasting, double dfFalseNorthing)
    7981             : 
    7982             : {
    7983           0 :     VALIDATE_POINTER1(hSRS, "OSRSetStereographic", OGRERR_FAILURE);
    7984             : 
    7985           0 :     return ToPointer(hSRS)->SetStereographic(dfOriginLat, dfCMeridian, dfScale,
    7986           0 :                                              dfFalseEasting, dfFalseNorthing);
    7987             : }
    7988             : 
    7989             : /************************************************************************/
    7990             : /*                               SetSOC()                               */
    7991             : /*                                                                      */
    7992             : /*      NOTE: This definition isn't really used in practice any more    */
    7993             : /*      and should be considered deprecated.  It seems that swiss       */
    7994             : /*      oblique mercator is now define as Hotine_Oblique_Mercator       */
    7995             : /*      with an azimuth of 90 and a rectified_grid_angle of 90.  See    */
    7996             : /*      EPSG:2056 and Bug 423.                                          */
    7997             : /************************************************************************/
    7998             : 
    7999           2 : OGRErr OGRSpatialReference::SetSOC(double dfLatitudeOfOrigin,
    8000             :                                    double dfCentralMeridian,
    8001             :                                    double dfFalseEasting,
    8002             :                                    double dfFalseNorthing)
    8003             : 
    8004             : {
    8005           4 :     TAKE_OPTIONAL_LOCK();
    8006             : 
    8007           2 :     return d->replaceConversionAndUnref(
    8008             :         proj_create_conversion_hotine_oblique_mercator_variant_b(
    8009             :             d->getPROJContext(), dfLatitudeOfOrigin, dfCentralMeridian, 90.0,
    8010             :             90.0, 1.0, dfFalseEasting, dfFalseNorthing, nullptr, 0.0, nullptr,
    8011           4 :             0.0));
    8012             : #if 0
    8013             :     SetProjection( SRS_PT_SWISS_OBLIQUE_CYLINDRICAL );
    8014             :     SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfLatitudeOfOrigin );
    8015             :     SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    8016             :     SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    8017             :     SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );
    8018             : 
    8019             :     return OGRERR_NONE;
    8020             : #endif
    8021             : }
    8022             : 
    8023             : /************************************************************************/
    8024             : /*                             OSRSetSOC()                              */
    8025             : /************************************************************************/
    8026             : 
    8027           0 : OGRErr OSRSetSOC(OGRSpatialReferenceH hSRS, double dfLatitudeOfOrigin,
    8028             :                  double dfCentralMeridian, double dfFalseEasting,
    8029             :                  double dfFalseNorthing)
    8030             : 
    8031             : {
    8032           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSOC", OGRERR_FAILURE);
    8033             : 
    8034           0 :     return ToPointer(hSRS)->SetSOC(dfLatitudeOfOrigin, dfCentralMeridian,
    8035           0 :                                    dfFalseEasting, dfFalseNorthing);
    8036             : }
    8037             : 
    8038             : /************************************************************************/
    8039             : /*                               SetVDG()                               */
    8040             : /************************************************************************/
    8041             : 
    8042           2 : OGRErr OGRSpatialReference::SetVDG(double dfCMeridian, double dfFalseEasting,
    8043             :                                    double dfFalseNorthing)
    8044             : 
    8045             : {
    8046           4 :     TAKE_OPTIONAL_LOCK();
    8047             : 
    8048           2 :     return d->replaceConversionAndUnref(proj_create_conversion_van_der_grinten(
    8049             :         d->getPROJContext(), dfCMeridian, dfFalseEasting, dfFalseNorthing,
    8050           4 :         nullptr, 0, nullptr, 0));
    8051             : }
    8052             : 
    8053             : /************************************************************************/
    8054             : /*                             OSRSetVDG()                              */
    8055             : /************************************************************************/
    8056             : 
    8057           0 : OGRErr OSRSetVDG(OGRSpatialReferenceH hSRS, double dfCentralMeridian,
    8058             :                  double dfFalseEasting, double dfFalseNorthing)
    8059             : 
    8060             : {
    8061           0 :     VALIDATE_POINTER1(hSRS, "OSRSetVDG", OGRERR_FAILURE);
    8062             : 
    8063           0 :     return ToPointer(hSRS)->SetVDG(dfCentralMeridian, dfFalseEasting,
    8064           0 :                                    dfFalseNorthing);
    8065             : }
    8066             : 
    8067             : /************************************************************************/
    8068             : /*                               SetUTM()                               */
    8069             : /************************************************************************/
    8070             : 
    8071             : /**
    8072             :  * \brief Set UTM projection definition.
    8073             :  *
    8074             :  * This will generate a projection definition with the full set of
    8075             :  * transverse mercator projection parameters for the given UTM zone.
    8076             :  * If no PROJCS[] description is set yet, one will be set to look
    8077             :  * like "UTM Zone %d, {Northern, Southern} Hemisphere".
    8078             :  *
    8079             :  * This method is the same as the C function OSRSetUTM().
    8080             :  *
    8081             :  * @param nZone UTM zone.
    8082             :  *
    8083             :  * @param bNorth TRUE for northern hemisphere, or FALSE for southern
    8084             :  * hemisphere.
    8085             :  *
    8086             :  * @return OGRERR_NONE on success.
    8087             :  */
    8088             : 
    8089         318 : OGRErr OGRSpatialReference::SetUTM(int nZone, int bNorth)
    8090             : 
    8091             : {
    8092         636 :     TAKE_OPTIONAL_LOCK();
    8093             : 
    8094         318 :     if (nZone < 0 || nZone > 60)
    8095             :     {
    8096           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid zone: %d", nZone);
    8097           0 :         return OGRERR_FAILURE;
    8098             :     }
    8099             : 
    8100         318 :     return d->replaceConversionAndUnref(
    8101         318 :         proj_create_conversion_utm(d->getPROJContext(), nZone, bNorth));
    8102             : }
    8103             : 
    8104             : /************************************************************************/
    8105             : /*                             OSRSetUTM()                              */
    8106             : /************************************************************************/
    8107             : 
    8108             : /**
    8109             :  * \brief Set UTM projection definition.
    8110             :  *
    8111             :  * This is the same as the C++ method OGRSpatialReference::SetUTM()
    8112             :  */
    8113          19 : OGRErr OSRSetUTM(OGRSpatialReferenceH hSRS, int nZone, int bNorth)
    8114             : 
    8115             : {
    8116          19 :     VALIDATE_POINTER1(hSRS, "OSRSetUTM", OGRERR_FAILURE);
    8117             : 
    8118          19 :     return ToPointer(hSRS)->SetUTM(nZone, bNorth);
    8119             : }
    8120             : 
    8121             : /************************************************************************/
    8122             : /*                             GetUTMZone()                             */
    8123             : /*                                                                      */
    8124             : /*      Returns zero if it isn't UTM.                                   */
    8125             : /************************************************************************/
    8126             : 
    8127             : /**
    8128             :  * \brief Get utm zone information.
    8129             :  *
    8130             :  * This is the same as the C function OSRGetUTMZone().
    8131             :  *
    8132             :  * In SWIG bindings (Python, Java, etc) the GetUTMZone() method returns a
    8133             :  * zone which is negative in the southern hemisphere instead of having the
    8134             :  * pbNorth flag used in the C and C++ interface.
    8135             :  *
    8136             :  * @param pbNorth pointer to in to set to TRUE if northern hemisphere, or
    8137             :  * FALSE if southern.
    8138             :  *
    8139             :  * @return UTM zone number or zero if this isn't a UTM definition.
    8140             :  */
    8141             : 
    8142         604 : int OGRSpatialReference::GetUTMZone(int *pbNorth) const
    8143             : 
    8144             : {
    8145        1208 :     TAKE_OPTIONAL_LOCK();
    8146             : 
    8147         604 :     if (IsProjected() && GetAxesCount() == 3)
    8148             :     {
    8149           1 :         OGRSpatialReference *poSRSTmp = Clone();
    8150           1 :         poSRSTmp->DemoteTo2D(nullptr);
    8151           1 :         const int nZone = poSRSTmp->GetUTMZone(pbNorth);
    8152           1 :         delete poSRSTmp;
    8153           1 :         return nZone;
    8154             :     }
    8155             : 
    8156         603 :     const char *pszProjection = GetAttrValue("PROJECTION");
    8157             : 
    8158         603 :     if (pszProjection == nullptr ||
    8159         523 :         !EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    8160         274 :         return 0;
    8161             : 
    8162         329 :     if (GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) != 0.0)
    8163           5 :         return 0;
    8164             : 
    8165         324 :     if (GetProjParm(SRS_PP_SCALE_FACTOR, 1.0) != 0.9996)
    8166          15 :         return 0;
    8167             : 
    8168         309 :     if (fabs(GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0) - 500000.0) > 0.001)
    8169           6 :         return 0;
    8170             : 
    8171         303 :     const double dfFalseNorthing = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    8172             : 
    8173         303 :     if (dfFalseNorthing != 0.0 && fabs(dfFalseNorthing - 10000000.0) > 0.001)
    8174           0 :         return 0;
    8175             : 
    8176         303 :     if (pbNorth != nullptr)
    8177         238 :         *pbNorth = (dfFalseNorthing == 0);
    8178             : 
    8179             :     const double dfCentralMeridian =
    8180         303 :         GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    8181         303 :     const double dfZone = (dfCentralMeridian + 186.0) / 6.0;
    8182             : 
    8183         606 :     if (dfCentralMeridian < -177.00001 || dfCentralMeridian > 177.000001 ||
    8184         909 :         std::isnan(dfZone) ||
    8185         303 :         std::abs(dfZone - static_cast<int>(dfZone) - 0.5) > 0.00001)
    8186           0 :         return 0;
    8187             : 
    8188         303 :     return static_cast<int>(dfZone);
    8189             : }
    8190             : 
    8191             : /************************************************************************/
    8192             : /*                           OSRGetUTMZone()                            */
    8193             : /************************************************************************/
    8194             : 
    8195             : /**
    8196             :  * \brief Get utm zone information.
    8197             :  *
    8198             :  * This is the same as the C++ method OGRSpatialReference::GetUTMZone()
    8199             :  */
    8200           6 : int OSRGetUTMZone(OGRSpatialReferenceH hSRS, int *pbNorth)
    8201             : 
    8202             : {
    8203           6 :     VALIDATE_POINTER1(hSRS, "OSRGetUTMZone", 0);
    8204             : 
    8205           6 :     return ToPointer(hSRS)->GetUTMZone(pbNorth);
    8206             : }
    8207             : 
    8208             : /************************************************************************/
    8209             : /*                             SetWagner()                              */
    8210             : /************************************************************************/
    8211             : 
    8212           0 : OGRErr OGRSpatialReference::SetWagner(int nVariation,  // 1--7.
    8213             :                                       double dfCenterLat, double dfFalseEasting,
    8214             :                                       double dfFalseNorthing)
    8215             : 
    8216             : {
    8217           0 :     TAKE_OPTIONAL_LOCK();
    8218             : 
    8219             :     PJ *conv;
    8220           0 :     if (nVariation == 1)
    8221             :     {
    8222           0 :         conv = proj_create_conversion_wagner_i(d->getPROJContext(), 0.0,
    8223             :                                                dfFalseEasting, dfFalseNorthing,
    8224             :                                                nullptr, 0.0, nullptr, 0.0);
    8225             :     }
    8226           0 :     else if (nVariation == 2)
    8227             :     {
    8228           0 :         conv = proj_create_conversion_wagner_ii(d->getPROJContext(), 0.0,
    8229             :                                                 dfFalseEasting, dfFalseNorthing,
    8230             :                                                 nullptr, 0.0, nullptr, 0.0);
    8231             :     }
    8232           0 :     else if (nVariation == 3)
    8233             :     {
    8234           0 :         conv = proj_create_conversion_wagner_iii(
    8235             :             d->getPROJContext(), dfCenterLat, 0.0, dfFalseEasting,
    8236             :             dfFalseNorthing, nullptr, 0.0, nullptr, 0.0);
    8237             :     }
    8238           0 :     else if (nVariation == 4)
    8239             :     {
    8240           0 :         conv = proj_create_conversion_wagner_iv(d->getPROJContext(), 0.0,
    8241             :                                                 dfFalseEasting, dfFalseNorthing,
    8242             :                                                 nullptr, 0.0, nullptr, 0.0);
    8243             :     }
    8244           0 :     else if (nVariation == 5)
    8245             :     {
    8246           0 :         conv = proj_create_conversion_wagner_v(d->getPROJContext(), 0.0,
    8247             :                                                dfFalseEasting, dfFalseNorthing,
    8248             :                                                nullptr, 0.0, nullptr, 0.0);
    8249             :     }
    8250           0 :     else if (nVariation == 6)
    8251             :     {
    8252           0 :         conv = proj_create_conversion_wagner_vi(d->getPROJContext(), 0.0,
    8253             :                                                 dfFalseEasting, dfFalseNorthing,
    8254             :                                                 nullptr, 0.0, nullptr, 0.0);
    8255             :     }
    8256           0 :     else if (nVariation == 7)
    8257             :     {
    8258           0 :         conv = proj_create_conversion_wagner_vii(
    8259             :             d->getPROJContext(), 0.0, dfFalseEasting, dfFalseNorthing, nullptr,
    8260             :             0.0, nullptr, 0.0);
    8261             :     }
    8262             :     else
    8263             :     {
    8264           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8265             :                  "Unsupported Wagner variation (%d).", nVariation);
    8266           0 :         return OGRERR_UNSUPPORTED_SRS;
    8267             :     }
    8268             : 
    8269           0 :     return d->replaceConversionAndUnref(conv);
    8270             : }
    8271             : 
    8272             : /************************************************************************/
    8273             : /*                            OSRSetWagner()                            */
    8274             : /************************************************************************/
    8275             : 
    8276           0 : OGRErr OSRSetWagner(OGRSpatialReferenceH hSRS, int nVariation,
    8277             :                     double dfCenterLat, double dfFalseEasting,
    8278             :                     double dfFalseNorthing)
    8279             : 
    8280             : {
    8281           0 :     VALIDATE_POINTER1(hSRS, "OSRSetWagner", OGRERR_FAILURE);
    8282             : 
    8283           0 :     return ToPointer(hSRS)->SetWagner(nVariation, dfCenterLat, dfFalseEasting,
    8284           0 :                                       dfFalseNorthing);
    8285             : }
    8286             : 
    8287             : /************************************************************************/
    8288             : /*                            SetQSC()                     */
    8289             : /************************************************************************/
    8290             : 
    8291           0 : OGRErr OGRSpatialReference::SetQSC(double dfCenterLat, double dfCenterLong)
    8292             : {
    8293           0 :     TAKE_OPTIONAL_LOCK();
    8294             : 
    8295           0 :     return d->replaceConversionAndUnref(
    8296             :         proj_create_conversion_quadrilateralized_spherical_cube(
    8297             :             d->getPROJContext(), dfCenterLat, dfCenterLong, 0.0, 0.0, nullptr,
    8298           0 :             0, nullptr, 0));
    8299             : }
    8300             : 
    8301             : /************************************************************************/
    8302             : /*                           OSRSetQSC()                   */
    8303             : /************************************************************************/
    8304             : 
    8305           0 : OGRErr OSRSetQSC(OGRSpatialReferenceH hSRS, double dfCenterLat,
    8306             :                  double dfCenterLong)
    8307             : 
    8308             : {
    8309           0 :     VALIDATE_POINTER1(hSRS, "OSRSetQSC", OGRERR_FAILURE);
    8310             : 
    8311           0 :     return ToPointer(hSRS)->SetQSC(dfCenterLat, dfCenterLong);
    8312             : }
    8313             : 
    8314             : /************************************************************************/
    8315             : /*                            SetSCH()                     */
    8316             : /************************************************************************/
    8317             : 
    8318           0 : OGRErr OGRSpatialReference::SetSCH(double dfPegLat, double dfPegLong,
    8319             :                                    double dfPegHeading, double dfPegHgt)
    8320             : 
    8321             : {
    8322           0 :     TAKE_OPTIONAL_LOCK();
    8323             : 
    8324           0 :     return d->replaceConversionAndUnref(
    8325             :         proj_create_conversion_spherical_cross_track_height(
    8326             :             d->getPROJContext(), dfPegLat, dfPegLong, dfPegHeading, dfPegHgt,
    8327           0 :             nullptr, 0, nullptr, 0));
    8328             : }
    8329             : 
    8330             : /************************************************************************/
    8331             : /*                           OSRSetSCH()                   */
    8332             : /************************************************************************/
    8333             : 
    8334           0 : OGRErr OSRSetSCH(OGRSpatialReferenceH hSRS, double dfPegLat, double dfPegLong,
    8335             :                  double dfPegHeading, double dfPegHgt)
    8336             : 
    8337             : {
    8338           0 :     VALIDATE_POINTER1(hSRS, "OSRSetSCH", OGRERR_FAILURE);
    8339             : 
    8340           0 :     return ToPointer(hSRS)->SetSCH(dfPegLat, dfPegLong, dfPegHeading, dfPegHgt);
    8341             : }
    8342             : 
    8343             : /************************************************************************/
    8344             : /*                         SetVerticalPerspective()                     */
    8345             : /************************************************************************/
    8346             : 
    8347           3 : OGRErr OGRSpatialReference::SetVerticalPerspective(
    8348             :     double dfTopoOriginLat, double dfTopoOriginLon, double dfTopoOriginHeight,
    8349             :     double dfViewPointHeight, double dfFalseEasting, double dfFalseNorthing)
    8350             : {
    8351           6 :     TAKE_OPTIONAL_LOCK();
    8352             : 
    8353           3 :     return d->replaceConversionAndUnref(
    8354             :         proj_create_conversion_vertical_perspective(
    8355             :             d->getPROJContext(), dfTopoOriginLat, dfTopoOriginLon,
    8356             :             dfTopoOriginHeight, dfViewPointHeight, dfFalseEasting,
    8357           6 :             dfFalseNorthing, nullptr, 0, nullptr, 0));
    8358             : }
    8359             : 
    8360             : /************************************************************************/
    8361             : /*                       OSRSetVerticalPerspective()                    */
    8362             : /************************************************************************/
    8363             : 
    8364           1 : OGRErr OSRSetVerticalPerspective(OGRSpatialReferenceH hSRS,
    8365             :                                  double dfTopoOriginLat, double dfTopoOriginLon,
    8366             :                                  double dfTopoOriginHeight,
    8367             :                                  double dfViewPointHeight,
    8368             :                                  double dfFalseEasting, double dfFalseNorthing)
    8369             : 
    8370             : {
    8371           1 :     VALIDATE_POINTER1(hSRS, "OSRSetVerticalPerspective", OGRERR_FAILURE);
    8372             : 
    8373           1 :     return ToPointer(hSRS)->SetVerticalPerspective(
    8374             :         dfTopoOriginLat, dfTopoOriginLon, dfTopoOriginHeight, dfViewPointHeight,
    8375           1 :         dfFalseEasting, dfFalseNorthing);
    8376             : }
    8377             : 
    8378             : /************************************************************************/
    8379             : /*             SetDerivedGeogCRSWithPoleRotationGRIBConvention()        */
    8380             : /************************************************************************/
    8381             : 
    8382           2 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationGRIBConvention(
    8383             :     const char *pszCRSName, double dfSouthPoleLat, double dfSouthPoleLon,
    8384             :     double dfAxisRotation)
    8385             : {
    8386           4 :     TAKE_OPTIONAL_LOCK();
    8387             : 
    8388           2 :     d->refreshProjObj();
    8389           2 :     if (!d->m_pj_crs)
    8390           0 :         return OGRERR_FAILURE;
    8391           2 :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8392           0 :         return OGRERR_FAILURE;
    8393           2 :     auto ctxt = d->getPROJContext();
    8394           2 :     auto conv = proj_create_conversion_pole_rotation_grib_convention(
    8395             :         ctxt, dfSouthPoleLat, dfSouthPoleLon, dfAxisRotation, nullptr, 0);
    8396           2 :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8397           4 :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8398           2 :                                                    d->m_pj_crs, conv, cs));
    8399           2 :     proj_destroy(conv);
    8400           2 :     proj_destroy(cs);
    8401           2 :     return OGRERR_NONE;
    8402             : }
    8403             : 
    8404             : /************************************************************************/
    8405             : /*         SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention()        */
    8406             : /************************************************************************/
    8407             : 
    8408           3 : OGRErr OGRSpatialReference::SetDerivedGeogCRSWithPoleRotationNetCDFCFConvention(
    8409             :     const char *pszCRSName, double dfGridNorthPoleLat,
    8410             :     double dfGridNorthPoleLon, double dfNorthPoleGridLon)
    8411             : {
    8412           3 :     TAKE_OPTIONAL_LOCK();
    8413             : 
    8414             : #if PROJ_VERSION_MAJOR > 8 ||                                                  \
    8415             :     (PROJ_VERSION_MAJOR == 8 && PROJ_VERSION_MINOR >= 2)
    8416             :     d->refreshProjObj();
    8417             :     if (!d->m_pj_crs)
    8418             :         return OGRERR_FAILURE;
    8419             :     if (d->m_pjType != PJ_TYPE_GEOGRAPHIC_2D_CRS)
    8420             :         return OGRERR_FAILURE;
    8421             :     auto ctxt = d->getPROJContext();
    8422             :     auto conv = proj_create_conversion_pole_rotation_netcdf_cf_convention(
    8423             :         ctxt, dfGridNorthPoleLat, dfGridNorthPoleLon, dfNorthPoleGridLon,
    8424             :         nullptr, 0);
    8425             :     auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8426             :     d->setPjCRS(proj_create_derived_geographic_crs(ctxt, pszCRSName,
    8427             :                                                    d->m_pj_crs, conv, cs));
    8428             :     proj_destroy(conv);
    8429             :     proj_destroy(cs);
    8430             :     return OGRERR_NONE;
    8431             : #else
    8432             :     (void)pszCRSName;
    8433           3 :     SetProjection("Rotated_pole");
    8434           3 :     SetExtension(
    8435             :         "PROJCS", "PROJ4",
    8436             :         CPLSPrintf("+proj=ob_tran +o_proj=longlat +lon_0=%.17g +o_lon_p=%.17g "
    8437             :                    "+o_lat_p=%.17g +a=%.17g +b=%.17g "
    8438             :                    "+to_meter=0.0174532925199433 "
    8439             :                    "+wktext",
    8440             :                    180.0 + dfGridNorthPoleLon, dfNorthPoleGridLon,
    8441             :                    dfGridNorthPoleLat, GetSemiMajor(nullptr),
    8442             :                    GetSemiMinor(nullptr)));
    8443           6 :     return OGRERR_NONE;
    8444             : #endif
    8445             : }
    8446             : 
    8447             : /************************************************************************/
    8448             : /*                            SetAuthority()                            */
    8449             : /************************************************************************/
    8450             : 
    8451             : /**
    8452             :  * \brief Set the authority for a node.
    8453             :  *
    8454             :  * This method is the same as the C function OSRSetAuthority().
    8455             :  *
    8456             :  * @param pszTargetKey the partial or complete path to the node to
    8457             :  * set an authority on.  i.e. "PROJCS", "GEOGCS" or "GEOGCS|UNIT".
    8458             :  *
    8459             :  * @param pszAuthority authority name, such as "EPSG".
    8460             :  *
    8461             :  * @param nCode code for value with this authority.
    8462             :  *
    8463             :  * @return OGRERR_NONE on success.
    8464             :  */
    8465             : 
    8466       11760 : OGRErr OGRSpatialReference::SetAuthority(const char *pszTargetKey,
    8467             :                                          const char *pszAuthority, int nCode)
    8468             : 
    8469             : {
    8470       23520 :     TAKE_OPTIONAL_LOCK();
    8471             : 
    8472       11760 :     d->refreshProjObj();
    8473       11760 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8474             : 
    8475       11760 :     if (pszTargetKey == nullptr)
    8476             :     {
    8477         263 :         if (!d->m_pj_crs)
    8478           0 :             return OGRERR_FAILURE;
    8479         263 :         CPLString osCode;
    8480         263 :         osCode.Printf("%d", nCode);
    8481         263 :         d->demoteFromBoundCRS();
    8482         263 :         d->setPjCRS(proj_alter_id(d->getPROJContext(), d->m_pj_crs,
    8483             :                                   pszAuthority, osCode.c_str()));
    8484         263 :         d->undoDemoteFromBoundCRS();
    8485         263 :         return OGRERR_NONE;
    8486             :     }
    8487             : 
    8488       11497 :     d->demoteFromBoundCRS();
    8489       11497 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS && EQUAL(pszTargetKey, "GEOGCS"))
    8490             :     {
    8491        3800 :         CPLString osCode;
    8492        3800 :         osCode.Printf("%d", nCode);
    8493             :         auto newGeogCRS =
    8494        3800 :             proj_alter_id(d->getPROJContext(), d->getGeodBaseCRS(),
    8495             :                           pszAuthority, osCode.c_str());
    8496             : 
    8497             :         auto conv =
    8498        3800 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
    8499             : 
    8500        3800 :         auto projCRS = proj_create_projected_crs(
    8501             :             d->getPROJContext(), d->getProjCRSName(), newGeogCRS, conv,
    8502        3800 :             d->getProjCRSCoordSys());
    8503             : 
    8504             :         // Preserve existing id on the PROJCRS
    8505        3800 :         const char *pszProjCRSAuthName = proj_get_id_auth_name(d->m_pj_crs, 0);
    8506        3800 :         const char *pszProjCRSCode = proj_get_id_code(d->m_pj_crs, 0);
    8507        3800 :         if (pszProjCRSAuthName && pszProjCRSCode)
    8508             :         {
    8509             :             auto projCRSWithId =
    8510           0 :                 proj_alter_id(d->getPROJContext(), projCRS, pszProjCRSAuthName,
    8511             :                               pszProjCRSCode);
    8512           0 :             proj_destroy(projCRS);
    8513           0 :             projCRS = projCRSWithId;
    8514             :         }
    8515             : 
    8516        3800 :         proj_destroy(newGeogCRS);
    8517        3800 :         proj_destroy(conv);
    8518             : 
    8519        3800 :         d->setPjCRS(projCRS);
    8520        3800 :         d->undoDemoteFromBoundCRS();
    8521        3800 :         return OGRERR_NONE;
    8522             :     }
    8523        7697 :     d->undoDemoteFromBoundCRS();
    8524             : 
    8525             :     /* -------------------------------------------------------------------- */
    8526             :     /*      Find the node below which the authority should be put.          */
    8527             :     /* -------------------------------------------------------------------- */
    8528        7697 :     OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8529             : 
    8530        7697 :     if (poNode == nullptr)
    8531           0 :         return OGRERR_FAILURE;
    8532             : 
    8533             :     /* -------------------------------------------------------------------- */
    8534             :     /*      If there is an existing AUTHORITY child blow it away before     */
    8535             :     /*      trying to set a new one.                                        */
    8536             :     /* -------------------------------------------------------------------- */
    8537        7697 :     int iOldChild = poNode->FindChild("AUTHORITY");
    8538        7697 :     if (iOldChild != -1)
    8539           5 :         poNode->DestroyChild(iOldChild);
    8540             : 
    8541             :     /* -------------------------------------------------------------------- */
    8542             :     /*      Create a new authority node.                                    */
    8543             :     /* -------------------------------------------------------------------- */
    8544        7697 :     char szCode[32] = {};
    8545             : 
    8546        7697 :     snprintf(szCode, sizeof(szCode), "%d", nCode);
    8547             : 
    8548        7697 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("AUTHORITY");
    8549        7697 :     poAuthNode->AddChild(new OGR_SRSNode(pszAuthority));
    8550        7697 :     poAuthNode->AddChild(new OGR_SRSNode(szCode));
    8551             : 
    8552        7697 :     poNode->AddChild(poAuthNode);
    8553             : 
    8554        7697 :     return OGRERR_NONE;
    8555             : }
    8556             : 
    8557             : /************************************************************************/
    8558             : /*                          OSRSetAuthority()                           */
    8559             : /************************************************************************/
    8560             : 
    8561             : /**
    8562             :  * \brief Set the authority for a node.
    8563             :  *
    8564             :  * This function is the same as OGRSpatialReference::SetAuthority().
    8565             :  */
    8566           0 : OGRErr OSRSetAuthority(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
    8567             :                        const char *pszAuthority, int nCode)
    8568             : 
    8569             : {
    8570           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAuthority", OGRERR_FAILURE);
    8571             : 
    8572           0 :     return ToPointer(hSRS)->SetAuthority(pszTargetKey, pszAuthority, nCode);
    8573             : }
    8574             : 
    8575             : /************************************************************************/
    8576             : /*                          GetAuthorityCode()                          */
    8577             : /************************************************************************/
    8578             : 
    8579             : /**
    8580             :  * \brief Get the authority code for a node.
    8581             :  *
    8582             :  * This method is used to query an AUTHORITY[] node from within the
    8583             :  * WKT tree, and fetch the code value.
    8584             :  *
    8585             :  * While in theory values may be non-numeric, for the EPSG authority all
    8586             :  * code values should be integral.
    8587             :  *
    8588             :  * This method is the same as the C function OSRGetAuthorityCode().
    8589             :  *
    8590             :  * @param pszTargetKey the partial or complete path to the node to
    8591             :  * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
    8592             :  * search for an authority node on the root element.
    8593             :  *
    8594             :  * @return value code from authority node, or NULL on failure.  The value
    8595             :  * returned is internal and should not be freed or modified.
    8596             :  */
    8597             : 
    8598             : const char *
    8599       44595 : OGRSpatialReference::GetAuthorityCode(const char *pszTargetKey) const
    8600             : 
    8601             : {
    8602       89190 :     TAKE_OPTIONAL_LOCK();
    8603             : 
    8604       44595 :     d->refreshProjObj();
    8605       44595 :     const char *pszInputTargetKey = pszTargetKey;
    8606       44595 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8607       44595 :     if (pszTargetKey == nullptr)
    8608             :     {
    8609       36761 :         if (!d->m_pj_crs)
    8610             :         {
    8611          17 :             return nullptr;
    8612             :         }
    8613       36744 :         d->demoteFromBoundCRS();
    8614       36744 :         auto ret = proj_get_id_code(d->m_pj_crs, 0);
    8615       36744 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8616             :         {
    8617        1192 :             auto ctxt = d->getPROJContext();
    8618        1192 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8619        1192 :             if (cs)
    8620             :             {
    8621        1192 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8622        1192 :                 proj_destroy(cs);
    8623        1192 :                 if (axisCount == 3)
    8624             :                 {
    8625             :                     // This might come from a COMPD_CS with a VERT_DATUM type =
    8626             :                     // 2002 in which case, using the WKT1 representation will
    8627             :                     // enable us to recover the EPSG code.
    8628          14 :                     pszTargetKey = pszInputTargetKey;
    8629             :                 }
    8630             :             }
    8631             :         }
    8632       36744 :         d->undoDemoteFromBoundCRS();
    8633       36744 :         if (ret != nullptr || pszTargetKey == nullptr)
    8634             :         {
    8635       36744 :             return ret;
    8636             :         }
    8637             :     }
    8638             : 
    8639             :     // Special key for that context
    8640        7838 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8641           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8642             :     {
    8643           4 :         auto ctxt = d->getPROJContext();
    8644           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8645           4 :         if (crs)
    8646             :         {
    8647           4 :             const char *ret = proj_get_id_code(crs, 0);
    8648           4 :             if (ret)
    8649           4 :                 ret = CPLSPrintf("%s", ret);
    8650           4 :             proj_destroy(crs);
    8651           4 :             return ret;
    8652             :         }
    8653             :     }
    8654        7834 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8655           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8656             :     {
    8657           4 :         auto ctxt = d->getPROJContext();
    8658           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8659           4 :         if (crs)
    8660             :         {
    8661           4 :             const char *ret = proj_get_id_code(crs, 0);
    8662           4 :             if (ret)
    8663           4 :                 ret = CPLSPrintf("%s", ret);
    8664           4 :             proj_destroy(crs);
    8665           4 :             return ret;
    8666             :         }
    8667             :     }
    8668             : 
    8669             :     /* -------------------------------------------------------------------- */
    8670             :     /*      Find the node below which the authority should be put.          */
    8671             :     /* -------------------------------------------------------------------- */
    8672        7826 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8673             : 
    8674        7826 :     if (poNode == nullptr)
    8675         103 :         return nullptr;
    8676             : 
    8677             :     /* -------------------------------------------------------------------- */
    8678             :     /*      Fetch AUTHORITY child if there is one.                          */
    8679             :     /* -------------------------------------------------------------------- */
    8680        7723 :     if (poNode->FindChild("AUTHORITY") == -1)
    8681         183 :         return nullptr;
    8682             : 
    8683        7540 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8684             : 
    8685             :     /* -------------------------------------------------------------------- */
    8686             :     /*      Create a new authority node.                                    */
    8687             :     /* -------------------------------------------------------------------- */
    8688        7540 :     if (poNode->GetChildCount() < 2)
    8689           0 :         return nullptr;
    8690             : 
    8691        7540 :     return poNode->GetChild(1)->GetValue();
    8692             : }
    8693             : 
    8694             : /************************************************************************/
    8695             : /*                          OSRGetAuthorityCode()                       */
    8696             : /************************************************************************/
    8697             : 
    8698             : /**
    8699             :  * \brief Get the authority code for a node.
    8700             :  *
    8701             :  * This function is the same as OGRSpatialReference::GetAuthorityCode().
    8702             :  */
    8703         712 : const char *OSRGetAuthorityCode(OGRSpatialReferenceH hSRS,
    8704             :                                 const char *pszTargetKey)
    8705             : 
    8706             : {
    8707         712 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityCode", nullptr);
    8708             : 
    8709         712 :     return ToPointer(hSRS)->GetAuthorityCode(pszTargetKey);
    8710             : }
    8711             : 
    8712             : /************************************************************************/
    8713             : /*                          GetAuthorityName()                          */
    8714             : /************************************************************************/
    8715             : 
    8716             : /**
    8717             :  * \brief Get the authority name for a node.
    8718             :  *
    8719             :  * This method is used to query an AUTHORITY[] node from within the
    8720             :  * WKT tree, and fetch the authority name value.
    8721             :  *
    8722             :  * The most common authority is "EPSG".
    8723             :  *
    8724             :  * This method is the same as the C function OSRGetAuthorityName().
    8725             :  *
    8726             :  * @param pszTargetKey the partial or complete path to the node to
    8727             :  * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
    8728             :  * search for an authority node on the root element.
    8729             :  *
    8730             :  * @return value code from authority node, or NULL on failure. The value
    8731             :  * returned is internal and should not be freed or modified.
    8732             :  */
    8733             : 
    8734             : const char *
    8735       57745 : OGRSpatialReference::GetAuthorityName(const char *pszTargetKey) const
    8736             : 
    8737             : {
    8738      115490 :     TAKE_OPTIONAL_LOCK();
    8739             : 
    8740       57745 :     d->refreshProjObj();
    8741       57745 :     const char *pszInputTargetKey = pszTargetKey;
    8742       57745 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
    8743       57745 :     if (pszTargetKey == nullptr)
    8744             :     {
    8745       30416 :         if (!d->m_pj_crs)
    8746             :         {
    8747          18 :             return nullptr;
    8748             :         }
    8749       30398 :         d->demoteFromBoundCRS();
    8750       30398 :         auto ret = proj_get_id_auth_name(d->m_pj_crs, 0);
    8751       30398 :         if (ret == nullptr && d->m_pjType == PJ_TYPE_PROJECTED_CRS)
    8752             :         {
    8753         814 :             auto ctxt = d->getPROJContext();
    8754         814 :             auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
    8755         814 :             if (cs)
    8756             :             {
    8757         814 :                 const int axisCount = proj_cs_get_axis_count(ctxt, cs);
    8758         814 :                 proj_destroy(cs);
    8759         814 :                 if (axisCount == 3)
    8760             :                 {
    8761             :                     // This might come from a COMPD_CS with a VERT_DATUM type =
    8762             :                     // 2002 in which case, using the WKT1 representation will
    8763             :                     // enable us to recover the EPSG code.
    8764          14 :                     pszTargetKey = pszInputTargetKey;
    8765             :                 }
    8766             :             }
    8767             :         }
    8768       30398 :         d->undoDemoteFromBoundCRS();
    8769       30398 :         if (ret != nullptr || pszTargetKey == nullptr)
    8770             :         {
    8771       30398 :             return ret;
    8772             :         }
    8773             :     }
    8774             : 
    8775             :     // Special key for that context
    8776       27333 :     else if (EQUAL(pszTargetKey, "HORIZCRS") &&
    8777           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8778             :     {
    8779           4 :         auto ctxt = d->getPROJContext();
    8780           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    8781           4 :         if (crs)
    8782             :         {
    8783           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8784           4 :             if (ret)
    8785           4 :                 ret = CPLSPrintf("%s", ret);
    8786           4 :             proj_destroy(crs);
    8787           4 :             return ret;
    8788             :         }
    8789             :     }
    8790       27329 :     else if (EQUAL(pszTargetKey, "VERTCRS") &&
    8791           4 :              d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    8792             :     {
    8793           4 :         auto ctxt = d->getPROJContext();
    8794           4 :         auto crs = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
    8795           4 :         if (crs)
    8796             :         {
    8797           4 :             const char *ret = proj_get_id_auth_name(crs, 0);
    8798           4 :             if (ret)
    8799           4 :                 ret = CPLSPrintf("%s", ret);
    8800           4 :             proj_destroy(crs);
    8801           4 :             return ret;
    8802             :         }
    8803             :     }
    8804             : 
    8805             :     /* -------------------------------------------------------------------- */
    8806             :     /*      Find the node below which the authority should be put.          */
    8807             :     /* -------------------------------------------------------------------- */
    8808       27321 :     const OGR_SRSNode *poNode = GetAttrNode(pszTargetKey);
    8809             : 
    8810       27321 :     if (poNode == nullptr)
    8811       11358 :         return nullptr;
    8812             : 
    8813             :     /* -------------------------------------------------------------------- */
    8814             :     /*      Fetch AUTHORITY child if there is one.                          */
    8815             :     /* -------------------------------------------------------------------- */
    8816       15963 :     if (poNode->FindChild("AUTHORITY") == -1)
    8817        1448 :         return nullptr;
    8818             : 
    8819       14515 :     poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));
    8820             : 
    8821             :     /* -------------------------------------------------------------------- */
    8822             :     /*      Create a new authority node.                                    */
    8823             :     /* -------------------------------------------------------------------- */
    8824       14515 :     if (poNode->GetChildCount() < 2)
    8825           0 :         return nullptr;
    8826             : 
    8827       14515 :     return poNode->GetChild(0)->GetValue();
    8828             : }
    8829             : 
    8830             : /************************************************************************/
    8831             : /*                        OSRGetAuthorityName()                         */
    8832             : /************************************************************************/
    8833             : 
    8834             : /**
    8835             :  * \brief Get the authority name for a node.
    8836             :  *
    8837             :  * This function is the same as OGRSpatialReference::GetAuthorityName().
    8838             :  */
    8839         180 : const char *OSRGetAuthorityName(OGRSpatialReferenceH hSRS,
    8840             :                                 const char *pszTargetKey)
    8841             : 
    8842             : {
    8843         180 :     VALIDATE_POINTER1(hSRS, "OSRGetAuthorityName", nullptr);
    8844             : 
    8845         180 :     return ToPointer(hSRS)->GetAuthorityName(pszTargetKey);
    8846             : }
    8847             : 
    8848             : /************************************************************************/
    8849             : /*                          GetOGCURN()                                 */
    8850             : /************************************************************************/
    8851             : 
    8852             : /**
    8853             :  * \brief Get a OGC URN string describing the CRS, when possible
    8854             :  *
    8855             :  * This method assumes that the CRS has a top-level identifier, or is
    8856             :  * a compound CRS whose horizontal and vertical parts have a top-level
    8857             :  * identifier.
    8858             :  *
    8859             :  * @return a string to free with CPLFree(), or nullptr when no result can be
    8860             :  * generated
    8861             :  *
    8862             :  * @since GDAL 3.5
    8863             :  */
    8864             : 
    8865          61 : char *OGRSpatialReference::GetOGCURN() const
    8866             : 
    8867             : {
    8868         122 :     TAKE_OPTIONAL_LOCK();
    8869             : 
    8870          61 :     const char *pszAuthName = GetAuthorityName(nullptr);
    8871          61 :     const char *pszAuthCode = GetAuthorityCode(nullptr);
    8872          61 :     if (pszAuthName && pszAuthCode)
    8873          58 :         return CPLStrdup(
    8874          58 :             CPLSPrintf("urn:ogc:def:crs:%s::%s", pszAuthName, pszAuthCode));
    8875           3 :     if (d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    8876           2 :         return nullptr;
    8877           1 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    8878           1 :     auto vertCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    8879           1 :     char *pszRet = nullptr;
    8880           1 :     if (horizCRS && vertCRS)
    8881             :     {
    8882           1 :         auto horizAuthName = proj_get_id_auth_name(horizCRS, 0);
    8883           1 :         auto horizAuthCode = proj_get_id_code(horizCRS, 0);
    8884           1 :         auto vertAuthName = proj_get_id_auth_name(vertCRS, 0);
    8885           1 :         auto vertAuthCode = proj_get_id_code(vertCRS, 0);
    8886           1 :         if (horizAuthName && horizAuthCode && vertAuthName && vertAuthCode)
    8887             :         {
    8888           1 :             pszRet = CPLStrdup(CPLSPrintf(
    8889             :                 "urn:ogc:def:crs,crs:%s::%s,crs:%s::%s", horizAuthName,
    8890             :                 horizAuthCode, vertAuthName, vertAuthCode));
    8891             :         }
    8892             :     }
    8893           1 :     proj_destroy(horizCRS);
    8894           1 :     proj_destroy(vertCRS);
    8895           1 :     return pszRet;
    8896             : }
    8897             : 
    8898             : /************************************************************************/
    8899             : /*                           StripVertical()                            */
    8900             : /************************************************************************/
    8901             : 
    8902             : /**
    8903             :  * \brief Convert a compound cs into a horizontal CS.
    8904             :  *
    8905             :  * If this SRS is of type COMPD_CS[] then the vertical CS and the root COMPD_CS
    8906             :  * nodes are stripped resulting and only the horizontal coordinate system
    8907             :  * portion remains (normally PROJCS, GEOGCS or LOCAL_CS).
    8908             :  *
    8909             :  * If this is not a compound coordinate system then nothing is changed.
    8910             :  *
    8911             :  * This method is the same as the C function OSRStripVertical().
    8912             :  *
    8913             :  * @since OGR 1.8.0
    8914             :  */
    8915             : 
    8916          44 : OGRErr OGRSpatialReference::StripVertical()
    8917             : 
    8918             : {
    8919          88 :     TAKE_OPTIONAL_LOCK();
    8920             : 
    8921          44 :     d->refreshProjObj();
    8922          44 :     d->demoteFromBoundCRS();
    8923          44 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_COMPOUND_CRS)
    8924             :     {
    8925           0 :         d->undoDemoteFromBoundCRS();
    8926           0 :         return OGRERR_NONE;
    8927             :     }
    8928          44 :     auto horizCRS = proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    8929          44 :     if (!horizCRS)
    8930             :     {
    8931           0 :         d->undoDemoteFromBoundCRS();
    8932           0 :         return OGRERR_FAILURE;
    8933             :     }
    8934             : 
    8935          44 :     bool reuseExistingBoundCRS = false;
    8936          44 :     if (d->m_pj_bound_crs_target)
    8937             :     {
    8938           4 :         auto type = proj_get_type(d->m_pj_bound_crs_target);
    8939           8 :         reuseExistingBoundCRS = type == PJ_TYPE_GEOCENTRIC_CRS ||
    8940           8 :                                 type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    8941             :                                 type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    8942             :     }
    8943             : 
    8944          44 :     if (reuseExistingBoundCRS)
    8945             :     {
    8946           4 :         auto newBoundCRS = proj_crs_create_bound_crs(
    8947           4 :             d->getPROJContext(), horizCRS, d->m_pj_bound_crs_target,
    8948           4 :             d->m_pj_bound_crs_co);
    8949           4 :         proj_destroy(horizCRS);
    8950           4 :         d->undoDemoteFromBoundCRS();
    8951           4 :         d->setPjCRS(newBoundCRS);
    8952             :     }
    8953             :     else
    8954             :     {
    8955          40 :         d->undoDemoteFromBoundCRS();
    8956          40 :         d->setPjCRS(horizCRS);
    8957             :     }
    8958             : 
    8959          44 :     return OGRERR_NONE;
    8960             : }
    8961             : 
    8962             : /************************************************************************/
    8963             : /*                            OSRStripVertical()                             */
    8964             : /************************************************************************/
    8965             : /**
    8966             :  * \brief Convert a compound cs into a horizontal CS.
    8967             :  *
    8968             :  * This function is the same as the C++ method
    8969             :  * OGRSpatialReference::StripVertical().
    8970             :  */
    8971           1 : OGRErr OSRStripVertical(OGRSpatialReferenceH hSRS)
    8972             : 
    8973             : {
    8974           1 :     VALIDATE_POINTER1(hSRS, "OSRStripVertical", OGRERR_FAILURE);
    8975             : 
    8976           1 :     return OGRSpatialReference::FromHandle(hSRS)->StripVertical();
    8977             : }
    8978             : 
    8979             : /************************************************************************/
    8980             : /*                   StripTOWGS84IfKnownDatumAndAllowed()               */
    8981             : /************************************************************************/
    8982             : 
    8983             : /**
    8984             :  * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
    8985             :  *        and this is allowed by the user.
    8986             :  *
    8987             :  * The default behavior is to remove TOWGS84 information if the CRS has a
    8988             :  * known horizontal datum. This can be disabled by setting the
    8989             :  * OSR_STRIP_TOWGS84 configuration option to NO.
    8990             :  *
    8991             :  * @return true if TOWGS84 has been removed.
    8992             :  * @since OGR 3.1.0
    8993             :  */
    8994             : 
    8995        8919 : bool OGRSpatialReference::StripTOWGS84IfKnownDatumAndAllowed()
    8996             : {
    8997        8919 :     if (CPLTestBool(CPLGetConfigOption("OSR_STRIP_TOWGS84", "YES")))
    8998             :     {
    8999        8916 :         if (StripTOWGS84IfKnownDatum())
    9000             :         {
    9001          72 :             CPLDebug("OSR", "TOWGS84 information has been removed. "
    9002             :                             "It can be kept by setting the OSR_STRIP_TOWGS84 "
    9003             :                             "configuration option to NO");
    9004          72 :             return true;
    9005             :         }
    9006             :     }
    9007        8847 :     return false;
    9008             : }
    9009             : 
    9010             : /************************************************************************/
    9011             : /*                      StripTOWGS84IfKnownDatum()                      */
    9012             : /************************************************************************/
    9013             : 
    9014             : /**
    9015             :  * \brief Remove TOWGS84 information if the CRS has a known horizontal datum
    9016             :  *
    9017             :  * @return true if TOWGS84 has been removed.
    9018             :  * @since OGR 3.1.0
    9019             :  */
    9020             : 
    9021        8922 : bool OGRSpatialReference::StripTOWGS84IfKnownDatum()
    9022             : 
    9023             : {
    9024       17844 :     TAKE_OPTIONAL_LOCK();
    9025             : 
    9026        8922 :     d->refreshProjObj();
    9027        8922 :     if (!d->m_pj_crs || d->m_pjType != PJ_TYPE_BOUND_CRS)
    9028             :     {
    9029        8830 :         return false;
    9030             :     }
    9031          92 :     auto ctxt = d->getPROJContext();
    9032          92 :     auto baseCRS = proj_get_source_crs(ctxt, d->m_pj_crs);
    9033          92 :     if (proj_get_type(baseCRS) == PJ_TYPE_COMPOUND_CRS)
    9034             :     {
    9035           3 :         proj_destroy(baseCRS);
    9036           3 :         return false;
    9037             :     }
    9038             : 
    9039             :     // Known base CRS code ? Return base CRS
    9040          89 :     const char *pszCode = proj_get_id_code(baseCRS, 0);
    9041          89 :     if (pszCode)
    9042             :     {
    9043           2 :         d->setPjCRS(baseCRS);
    9044           2 :         return true;
    9045             :     }
    9046             : 
    9047          87 :     auto datum = proj_crs_get_datum(ctxt, baseCRS);
    9048             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9049             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9050             :     if (datum == nullptr)
    9051             :     {
    9052             :         datum = proj_crs_get_datum_ensemble(ctxt, baseCRS);
    9053             :     }
    9054             : #endif
    9055          87 :     if (!datum)
    9056             :     {
    9057           0 :         proj_destroy(baseCRS);
    9058           0 :         return false;
    9059             :     }
    9060             : 
    9061             :     // Known datum code ? Return base CRS
    9062          87 :     pszCode = proj_get_id_code(datum, 0);
    9063          87 :     if (pszCode)
    9064             :     {
    9065           3 :         proj_destroy(datum);
    9066           3 :         d->setPjCRS(baseCRS);
    9067           3 :         return true;
    9068             :     }
    9069             : 
    9070          84 :     const char *name = proj_get_name(datum);
    9071          84 :     if (EQUAL(name, "unknown"))
    9072             :     {
    9073           1 :         proj_destroy(datum);
    9074           1 :         proj_destroy(baseCRS);
    9075           1 :         return false;
    9076             :     }
    9077          83 :     const PJ_TYPE type = PJ_TYPE_GEODETIC_REFERENCE_FRAME;
    9078             :     PJ_OBJ_LIST *list =
    9079          83 :         proj_create_from_name(ctxt, nullptr, name, &type, 1, false, 1, nullptr);
    9080             : 
    9081          83 :     bool knownDatumName = false;
    9082          83 :     if (list)
    9083             :     {
    9084          83 :         if (proj_list_get_count(list) == 1)
    9085             :         {
    9086          70 :             knownDatumName = true;
    9087             :         }
    9088          83 :         proj_list_destroy(list);
    9089             :     }
    9090             : 
    9091          83 :     proj_destroy(datum);
    9092          83 :     if (knownDatumName)
    9093             :     {
    9094          70 :         d->setPjCRS(baseCRS);
    9095          70 :         return true;
    9096             :     }
    9097          13 :     proj_destroy(baseCRS);
    9098          13 :     return false;
    9099             : }
    9100             : 
    9101             : /************************************************************************/
    9102             : /*                             IsCompound()                             */
    9103             : /************************************************************************/
    9104             : 
    9105             : /**
    9106             :  * \brief Check if coordinate system is compound.
    9107             :  *
    9108             :  * This method is the same as the C function OSRIsCompound().
    9109             :  *
    9110             :  * @return TRUE if this is rooted with a COMPD_CS node.
    9111             :  */
    9112             : 
    9113       39848 : int OGRSpatialReference::IsCompound() const
    9114             : 
    9115             : {
    9116       39848 :     TAKE_OPTIONAL_LOCK();
    9117             : 
    9118       39848 :     d->refreshProjObj();
    9119       39848 :     d->demoteFromBoundCRS();
    9120       39848 :     bool isCompound = d->m_pjType == PJ_TYPE_COMPOUND_CRS;
    9121       39848 :     d->undoDemoteFromBoundCRS();
    9122       79696 :     return isCompound;
    9123             : }
    9124             : 
    9125             : /************************************************************************/
    9126             : /*                           OSRIsCompound()                            */
    9127             : /************************************************************************/
    9128             : 
    9129             : /**
    9130             :  * \brief Check if the coordinate system is compound.
    9131             :  *
    9132             :  * This function is the same as OGRSpatialReference::IsCompound().
    9133             :  */
    9134           5 : int OSRIsCompound(OGRSpatialReferenceH hSRS)
    9135             : 
    9136             : {
    9137           5 :     VALIDATE_POINTER1(hSRS, "OSRIsCompound", 0);
    9138             : 
    9139           5 :     return ToPointer(hSRS)->IsCompound();
    9140             : }
    9141             : 
    9142             : /************************************************************************/
    9143             : /*                            IsProjected()                             */
    9144             : /************************************************************************/
    9145             : 
    9146             : /**
    9147             :  * \brief Check if projected coordinate system.
    9148             :  *
    9149             :  * This method is the same as the C function OSRIsProjected().
    9150             :  *
    9151             :  * @return TRUE if this contains a PROJCS node indicating a it is a
    9152             :  * projected coordinate system. Also if it is a CompoundCRS made of a
    9153             :  * ProjectedCRS
    9154             :  */
    9155             : 
    9156       41422 : int OGRSpatialReference::IsProjected() const
    9157             : 
    9158             : {
    9159       41422 :     TAKE_OPTIONAL_LOCK();
    9160             : 
    9161       41422 :     d->refreshProjObj();
    9162       41422 :     d->demoteFromBoundCRS();
    9163       41422 :     bool isProjected = d->m_pjType == PJ_TYPE_PROJECTED_CRS;
    9164       41422 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9165             :     {
    9166             :         auto horizCRS =
    9167         142 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9168         142 :         if (horizCRS)
    9169             :         {
    9170         142 :             auto horizCRSType = proj_get_type(horizCRS);
    9171         142 :             isProjected = horizCRSType == PJ_TYPE_PROJECTED_CRS;
    9172         142 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9173             :             {
    9174           6 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9175           6 :                 if (base)
    9176             :                 {
    9177           6 :                     isProjected = proj_get_type(base) == PJ_TYPE_PROJECTED_CRS;
    9178           6 :                     proj_destroy(base);
    9179             :                 }
    9180             :             }
    9181         142 :             proj_destroy(horizCRS);
    9182             :         }
    9183             :     }
    9184       41422 :     d->undoDemoteFromBoundCRS();
    9185       82844 :     return isProjected;
    9186             : }
    9187             : 
    9188             : /************************************************************************/
    9189             : /*                           OSRIsProjected()                           */
    9190             : /************************************************************************/
    9191             : /**
    9192             :  * \brief Check if projected coordinate system.
    9193             :  *
    9194             :  * This function is the same as OGRSpatialReference::IsProjected().
    9195             :  */
    9196         413 : int OSRIsProjected(OGRSpatialReferenceH hSRS)
    9197             : 
    9198             : {
    9199         413 :     VALIDATE_POINTER1(hSRS, "OSRIsProjected", 0);
    9200             : 
    9201         413 :     return ToPointer(hSRS)->IsProjected();
    9202             : }
    9203             : 
    9204             : /************************************************************************/
    9205             : /*                            IsGeocentric()                            */
    9206             : /************************************************************************/
    9207             : 
    9208             : /**
    9209             :  * \brief Check if geocentric coordinate system.
    9210             :  *
    9211             :  * This method is the same as the C function OSRIsGeocentric().
    9212             :  *
    9213             :  * @return TRUE if this contains a GEOCCS node indicating a it is a
    9214             :  * geocentric coordinate system.
    9215             :  *
    9216             :  * @since OGR 1.9.0
    9217             :  */
    9218             : 
    9219       16821 : int OGRSpatialReference::IsGeocentric() const
    9220             : 
    9221             : {
    9222       16821 :     TAKE_OPTIONAL_LOCK();
    9223             : 
    9224       16821 :     d->refreshProjObj();
    9225       16821 :     d->demoteFromBoundCRS();
    9226       16821 :     bool isGeocentric = d->m_pjType == PJ_TYPE_GEOCENTRIC_CRS;
    9227       16821 :     d->undoDemoteFromBoundCRS();
    9228       33642 :     return isGeocentric;
    9229             : }
    9230             : 
    9231             : /************************************************************************/
    9232             : /*                           OSRIsGeocentric()                          */
    9233             : /************************************************************************/
    9234             : /**
    9235             :  * \brief Check if geocentric coordinate system.
    9236             :  *
    9237             :  * This function is the same as OGRSpatialReference::IsGeocentric().
    9238             :  *
    9239             :  * @since OGR 1.9.0
    9240             :  */
    9241           2 : int OSRIsGeocentric(OGRSpatialReferenceH hSRS)
    9242             : 
    9243             : {
    9244           2 :     VALIDATE_POINTER1(hSRS, "OSRIsGeocentric", 0);
    9245             : 
    9246           2 :     return ToPointer(hSRS)->IsGeocentric();
    9247             : }
    9248             : 
    9249             : /************************************************************************/
    9250             : /*                            IsEmpty()                                 */
    9251             : /************************************************************************/
    9252             : 
    9253             : /**
    9254             :  * \brief Return if the SRS is not set.
    9255             :  */
    9256             : 
    9257      111760 : bool OGRSpatialReference::IsEmpty() const
    9258             : {
    9259      111760 :     TAKE_OPTIONAL_LOCK();
    9260             : 
    9261      111745 :     d->refreshProjObj();
    9262      223497 :     return d->m_pj_crs == nullptr;
    9263             : }
    9264             : 
    9265             : /************************************************************************/
    9266             : /*                            IsGeographic()                            */
    9267             : /************************************************************************/
    9268             : 
    9269             : /**
    9270             :  * \brief Check if geographic coordinate system.
    9271             :  *
    9272             :  * This method is the same as the C function OSRIsGeographic().
    9273             :  *
    9274             :  * @return TRUE if this spatial reference is geographic ... that is the
    9275             :  * root is a GEOGCS node. Also if it is a CompoundCRS made of a
    9276             :  * GeographicCRS
    9277             :  */
    9278             : 
    9279       57091 : int OGRSpatialReference::IsGeographic() const
    9280             : 
    9281             : {
    9282       57091 :     TAKE_OPTIONAL_LOCK();
    9283             : 
    9284       57091 :     d->refreshProjObj();
    9285       57091 :     d->demoteFromBoundCRS();
    9286       80318 :     bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9287       23227 :                   d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9288       57091 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9289             :     {
    9290             :         auto horizCRS =
    9291         291 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
    9292         291 :         if (horizCRS)
    9293             :         {
    9294         291 :             auto horizCRSType = proj_get_type(horizCRS);
    9295         291 :             isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9296             :                      horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9297         291 :             if (horizCRSType == PJ_TYPE_BOUND_CRS)
    9298             :             {
    9299          13 :                 auto base = proj_get_source_crs(d->getPROJContext(), horizCRS);
    9300          13 :                 if (base)
    9301             :                 {
    9302          13 :                     horizCRSType = proj_get_type(base);
    9303          13 :                     isGeog = horizCRSType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9304             :                              horizCRSType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9305          13 :                     proj_destroy(base);
    9306             :                 }
    9307             :             }
    9308         291 :             proj_destroy(horizCRS);
    9309             :         }
    9310             :     }
    9311       57091 :     d->undoDemoteFromBoundCRS();
    9312      114182 :     return isGeog;
    9313             : }
    9314             : 
    9315             : /************************************************************************/
    9316             : /*                          OSRIsGeographic()                           */
    9317             : /************************************************************************/
    9318             : /**
    9319             :  * \brief Check if geographic coordinate system.
    9320             :  *
    9321             :  * This function is the same as OGRSpatialReference::IsGeographic().
    9322             :  */
    9323         304 : int OSRIsGeographic(OGRSpatialReferenceH hSRS)
    9324             : 
    9325             : {
    9326         304 :     VALIDATE_POINTER1(hSRS, "OSRIsGeographic", 0);
    9327             : 
    9328         304 :     return ToPointer(hSRS)->IsGeographic();
    9329             : }
    9330             : 
    9331             : /************************************************************************/
    9332             : /*                      IsDerivedGeographic()                           */
    9333             : /************************************************************************/
    9334             : 
    9335             : /**
    9336             :  * \brief Check if the CRS is a derived geographic coordinate system.
    9337             :  * (for example a rotated long/lat grid)
    9338             :  *
    9339             :  * This method is the same as the C function OSRIsDerivedGeographic().
    9340             :  *
    9341             :  * @since GDAL 3.1.0 and PROJ 6.3.0
    9342             :  */
    9343             : 
    9344       14774 : int OGRSpatialReference::IsDerivedGeographic() const
    9345             : 
    9346             : {
    9347       14774 :     TAKE_OPTIONAL_LOCK();
    9348             : 
    9349       14774 :     d->refreshProjObj();
    9350       14774 :     d->demoteFromBoundCRS();
    9351       24128 :     const bool isGeog = d->m_pjType == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
    9352        9354 :                         d->m_pjType == PJ_TYPE_GEOGRAPHIC_3D_CRS;
    9353             :     const bool isDerivedGeographic =
    9354       14774 :         isGeog && proj_is_derived_crs(d->getPROJContext(), d->m_pj_crs);
    9355       14774 :     d->undoDemoteFromBoundCRS();
    9356       29548 :     return isDerivedGeographic ? TRUE : FALSE;
    9357             : }
    9358             : 
    9359             : /************************************************************************/
    9360             : /*                      OSRIsDerivedGeographic()                        */
    9361             : /************************************************************************/
    9362             : /**
    9363             :  * \brief Check if the CRS is a derived geographic coordinate system.
    9364             :  * (for example a rotated long/lat grid)
    9365             :  *
    9366             :  * This function is the same as OGRSpatialReference::IsDerivedGeographic().
    9367             :  */
    9368           1 : int OSRIsDerivedGeographic(OGRSpatialReferenceH hSRS)
    9369             : 
    9370             : {
    9371           1 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedGeographic", 0);
    9372             : 
    9373           1 :     return ToPointer(hSRS)->IsDerivedGeographic();
    9374             : }
    9375             : 
    9376             : /************************************************************************/
    9377             : /*                      IsDerivedProjected()                            */
    9378             : /************************************************************************/
    9379             : 
    9380             : /**
    9381             :  * \brief Check if the CRS is a derived projected coordinate system.
    9382             :  *
    9383             :  * This method is the same as the C function OSRIsDerivedGeographic().
    9384             :  *
    9385             :  * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
    9386             :  */
    9387             : 
    9388           0 : int OGRSpatialReference::IsDerivedProjected() const
    9389             : 
    9390             : {
    9391             : #if PROJ_AT_LEAST_VERSION(9, 2, 0)
    9392             :     TAKE_OPTIONAL_LOCK();
    9393             :     d->refreshProjObj();
    9394             :     d->demoteFromBoundCRS();
    9395             :     const bool isDerivedProjected =
    9396             :         d->m_pjType == PJ_TYPE_DERIVED_PROJECTED_CRS;
    9397             :     d->undoDemoteFromBoundCRS();
    9398             :     return isDerivedProjected ? TRUE : FALSE;
    9399             : #else
    9400           0 :     return FALSE;
    9401             : #endif
    9402             : }
    9403             : 
    9404             : /************************************************************************/
    9405             : /*                      OSRIsDerivedProjected()                         */
    9406             : /************************************************************************/
    9407             : /**
    9408             :  * \brief Check if the CRS is a derived projected coordinate system.
    9409             :  *
    9410             :  * This function is the same as OGRSpatialReference::IsDerivedProjected().
    9411             :  *
    9412             :  * @since GDAL 3.9.0 (and may only return non-zero starting with PROJ 9.2.0)
    9413             :  */
    9414           0 : int OSRIsDerivedProjected(OGRSpatialReferenceH hSRS)
    9415             : 
    9416             : {
    9417           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDerivedProjected", 0);
    9418             : 
    9419           0 :     return ToPointer(hSRS)->IsDerivedProjected();
    9420             : }
    9421             : 
    9422             : /************************************************************************/
    9423             : /*                              IsLocal()                               */
    9424             : /************************************************************************/
    9425             : 
    9426             : /**
    9427             :  * \brief Check if local coordinate system.
    9428             :  *
    9429             :  * This method is the same as the C function OSRIsLocal().
    9430             :  *
    9431             :  * @return TRUE if this spatial reference is local ... that is the
    9432             :  * root is a LOCAL_CS node.
    9433             :  */
    9434             : 
    9435        7619 : int OGRSpatialReference::IsLocal() const
    9436             : 
    9437             : {
    9438        7619 :     TAKE_OPTIONAL_LOCK();
    9439        7619 :     d->refreshProjObj();
    9440       15238 :     return d->m_pjType == PJ_TYPE_ENGINEERING_CRS;
    9441             : }
    9442             : 
    9443             : /************************************************************************/
    9444             : /*                          OSRIsLocal()                                */
    9445             : /************************************************************************/
    9446             : /**
    9447             :  * \brief Check if local coordinate system.
    9448             :  *
    9449             :  * This function is the same as OGRSpatialReference::IsLocal().
    9450             :  */
    9451           8 : int OSRIsLocal(OGRSpatialReferenceH hSRS)
    9452             : 
    9453             : {
    9454           8 :     VALIDATE_POINTER1(hSRS, "OSRIsLocal", 0);
    9455             : 
    9456           8 :     return ToPointer(hSRS)->IsLocal();
    9457             : }
    9458             : 
    9459             : /************************************************************************/
    9460             : /*                            IsVertical()                              */
    9461             : /************************************************************************/
    9462             : 
    9463             : /**
    9464             :  * \brief Check if vertical coordinate system.
    9465             :  *
    9466             :  * This method is the same as the C function OSRIsVertical().
    9467             :  *
    9468             :  * @return TRUE if this contains a VERT_CS node indicating a it is a
    9469             :  * vertical coordinate system. Also if it is a CompoundCRS made of a
    9470             :  * VerticalCRS
    9471             :  *
    9472             :  * @since OGR 1.8.0
    9473             :  */
    9474             : 
    9475        8492 : int OGRSpatialReference::IsVertical() const
    9476             : 
    9477             : {
    9478        8492 :     TAKE_OPTIONAL_LOCK();
    9479        8492 :     d->refreshProjObj();
    9480        8492 :     d->demoteFromBoundCRS();
    9481        8492 :     bool isVertical = d->m_pjType == PJ_TYPE_VERTICAL_CRS;
    9482        8492 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9483             :     {
    9484             :         auto vertCRS =
    9485          32 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 1);
    9486          32 :         if (vertCRS)
    9487             :         {
    9488          32 :             const auto vertCRSType = proj_get_type(vertCRS);
    9489          32 :             isVertical = vertCRSType == PJ_TYPE_VERTICAL_CRS;
    9490          32 :             if (vertCRSType == PJ_TYPE_BOUND_CRS)
    9491             :             {
    9492           0 :                 auto base = proj_get_source_crs(d->getPROJContext(), vertCRS);
    9493           0 :                 if (base)
    9494             :                 {
    9495           0 :                     isVertical = proj_get_type(base) == PJ_TYPE_VERTICAL_CRS;
    9496           0 :                     proj_destroy(base);
    9497             :                 }
    9498             :             }
    9499          32 :             proj_destroy(vertCRS);
    9500             :         }
    9501             :     }
    9502        8492 :     d->undoDemoteFromBoundCRS();
    9503       16984 :     return isVertical;
    9504             : }
    9505             : 
    9506             : /************************************************************************/
    9507             : /*                           OSRIsVertical()                            */
    9508             : /************************************************************************/
    9509             : /**
    9510             :  * \brief Check if vertical coordinate system.
    9511             :  *
    9512             :  * This function is the same as OGRSpatialReference::IsVertical().
    9513             :  *
    9514             :  * @since OGR 1.8.0
    9515             :  */
    9516           0 : int OSRIsVertical(OGRSpatialReferenceH hSRS)
    9517             : 
    9518             : {
    9519           0 :     VALIDATE_POINTER1(hSRS, "OSRIsVertical", 0);
    9520             : 
    9521           0 :     return ToPointer(hSRS)->IsVertical();
    9522             : }
    9523             : 
    9524             : /************************************************************************/
    9525             : /*                            IsDynamic()                               */
    9526             : /************************************************************************/
    9527             : 
    9528             : /**
    9529             :  * \brief Check if a CRS is a dynamic CRS.
    9530             :  *
    9531             :  * A dynamic CRS relies on a dynamic datum, that is a datum that is not
    9532             :  * plate-fixed.
    9533             :  *
    9534             :  * This method is the same as the C function OSRIsDynamic().
    9535             :  *
    9536             :  * @return true if the CRS is dynamic
    9537             :  *
    9538             :  * @since OGR 3.4.0
    9539             :  *
    9540             :  * @see HasPointMotionOperation()
    9541             :  */
    9542             : 
    9543       14394 : bool OGRSpatialReference::IsDynamic() const
    9544             : 
    9545             : {
    9546       14394 :     TAKE_OPTIONAL_LOCK();
    9547       14394 :     bool isDynamic = false;
    9548       14394 :     d->refreshProjObj();
    9549       14394 :     d->demoteFromBoundCRS();
    9550       14394 :     auto ctxt = d->getPROJContext();
    9551       14394 :     PJ *horiz = nullptr;
    9552       14394 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
    9553             :     {
    9554          96 :         horiz = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
    9555             :     }
    9556       14298 :     else if (d->m_pj_crs)
    9557             :     {
    9558       14192 :         horiz = proj_clone(ctxt, d->m_pj_crs);
    9559             :     }
    9560       14394 :     if (horiz && proj_get_type(horiz) == PJ_TYPE_BOUND_CRS)
    9561             :     {
    9562           6 :         auto baseCRS = proj_get_source_crs(ctxt, horiz);
    9563           6 :         if (baseCRS)
    9564             :         {
    9565           6 :             proj_destroy(horiz);
    9566           6 :             horiz = baseCRS;
    9567             :         }
    9568             :     }
    9569       14394 :     auto datum = horiz ? proj_crs_get_datum(ctxt, horiz) : nullptr;
    9570       14394 :     if (datum)
    9571             :     {
    9572       14266 :         const auto type = proj_get_type(datum);
    9573       14266 :         isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9574             :                     type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9575       14266 :         if (!isDynamic)
    9576             :         {
    9577       14266 :             const char *auth_name = proj_get_id_auth_name(datum, 0);
    9578       14266 :             const char *code = proj_get_id_code(datum, 0);
    9579       14266 :             if (auth_name && code && EQUAL(auth_name, "EPSG") &&
    9580       13822 :                 EQUAL(code, "6326"))
    9581             :             {
    9582        8909 :                 isDynamic = true;
    9583             :             }
    9584             :         }
    9585       14266 :         proj_destroy(datum);
    9586             :     }
    9587             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9588             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9589             :     else
    9590             :     {
    9591             :         auto ensemble =
    9592             :             horiz ? proj_crs_get_datum_ensemble(ctxt, horiz) : nullptr;
    9593             :         if (ensemble)
    9594             :         {
    9595             :             auto member = proj_datum_ensemble_get_member(ctxt, ensemble, 0);
    9596             :             if (member)
    9597             :             {
    9598             :                 const auto type = proj_get_type(member);
    9599             :                 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
    9600             :                             type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
    9601             :                 proj_destroy(member);
    9602             :             }
    9603             :             proj_destroy(ensemble);
    9604             :         }
    9605             :     }
    9606             : #endif
    9607       14394 :     proj_destroy(horiz);
    9608       14394 :     d->undoDemoteFromBoundCRS();
    9609       28788 :     return isDynamic;
    9610             : }
    9611             : 
    9612             : /************************************************************************/
    9613             : /*                           OSRIsDynamic()                             */
    9614             : /************************************************************************/
    9615             : /**
    9616             :  * \brief Check if a CRS is a dynamic CRS.
    9617             :  *
    9618             :  * A dynamic CRS relies on a dynamic datum, that is a datum that is not
    9619             :  * plate-fixed.
    9620             :  *
    9621             :  * This function is the same as OGRSpatialReference::IsDynamic().
    9622             :  *
    9623             :  * @since OGR 3.4.0
    9624             :  */
    9625           0 : int OSRIsDynamic(OGRSpatialReferenceH hSRS)
    9626             : 
    9627             : {
    9628           0 :     VALIDATE_POINTER1(hSRS, "OSRIsDynamic", 0);
    9629             : 
    9630           0 :     return ToPointer(hSRS)->IsDynamic();
    9631             : }
    9632             : 
    9633             : /************************************************************************/
    9634             : /*                         HasPointMotionOperation()                    */
    9635             : /************************************************************************/
    9636             : 
    9637             : /**
    9638             :  * \brief Check if a CRS has at least an associated point motion operation.
    9639             :  *
    9640             :  * Some CRS are not formally declared as dynamic, but may behave as such
    9641             :  * in practice due to the presence of point motion operation, to perform
    9642             :  * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
    9643             :  *
    9644             :  * @return true if the CRS has at least an associated point motion operation.
    9645             :  *
    9646             :  * @since OGR 3.8.0 and PROJ 9.4.0
    9647             :  *
    9648             :  * @see IsDynamic()
    9649             :  */
    9650             : 
    9651           5 : bool OGRSpatialReference::HasPointMotionOperation() const
    9652             : 
    9653             : {
    9654             : #if PROJ_VERSION_MAJOR > 9 ||                                                  \
    9655             :     (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 4)
    9656             :     TAKE_OPTIONAL_LOCK();
    9657             :     d->refreshProjObj();
    9658             :     d->demoteFromBoundCRS();
    9659             :     auto ctxt = d->getPROJContext();
    9660             :     auto res =
    9661             :         CPL_TO_BOOL(proj_crs_has_point_motion_operation(ctxt, d->m_pj_crs));
    9662             :     d->undoDemoteFromBoundCRS();
    9663             :     return res;
    9664             : #else
    9665           5 :     return false;
    9666             : #endif
    9667             : }
    9668             : 
    9669             : /************************************************************************/
    9670             : /*                      OSRHasPointMotionOperation()                    */
    9671             : /************************************************************************/
    9672             : 
    9673             : /**
    9674             :  * \brief Check if a CRS has at least an associated point motion operation.
    9675             :  *
    9676             :  * Some CRS are not formally declared as dynamic, but may behave as such
    9677             :  * in practice due to the presence of point motion operation, to perform
    9678             :  * coordinate epoch changes within the CRS. Typically NAD83(CSRS)v7
    9679             :  *
    9680             :  * This function is the same as OGRSpatialReference::HasPointMotionOperation().
    9681             :  *
    9682             :  * @since OGR 3.8.0 and PROJ 9.4.0
    9683             :  */
    9684           0 : int OSRHasPointMotionOperation(OGRSpatialReferenceH hSRS)
    9685             : 
    9686             : {
    9687           0 :     VALIDATE_POINTER1(hSRS, "OSRHasPointMotionOperation", 0);
    9688             : 
    9689           0 :     return ToPointer(hSRS)->HasPointMotionOperation();
    9690             : }
    9691             : 
    9692             : /************************************************************************/
    9693             : /*                            CloneGeogCS()                             */
    9694             : /************************************************************************/
    9695             : 
    9696             : /**
    9697             :  * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
    9698             :  * object.
    9699             :  *
    9700             :  * @return a new SRS, which becomes the responsibility of the caller.
    9701             :  */
    9702        4098 : OGRSpatialReference *OGRSpatialReference::CloneGeogCS() const
    9703             : 
    9704             : {
    9705        8196 :     TAKE_OPTIONAL_LOCK();
    9706        4098 :     d->refreshProjObj();
    9707        4098 :     if (d->m_pj_crs)
    9708             :     {
    9709        4098 :         if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
    9710           0 :             return nullptr;
    9711             : 
    9712             :         auto geodCRS =
    9713        4098 :             proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9714        4098 :         if (geodCRS)
    9715             :         {
    9716        4098 :             OGRSpatialReference *poNewSRS = new OGRSpatialReference();
    9717        4098 :             if (d->m_pjType == PJ_TYPE_BOUND_CRS)
    9718             :             {
    9719             :                 PJ *hub_crs =
    9720          13 :                     proj_get_target_crs(d->getPROJContext(), d->m_pj_crs);
    9721          13 :                 PJ *co = proj_crs_get_coordoperation(d->getPROJContext(),
    9722          13 :                                                      d->m_pj_crs);
    9723          13 :                 auto temp = proj_crs_create_bound_crs(d->getPROJContext(),
    9724             :                                                       geodCRS, hub_crs, co);
    9725          13 :                 proj_destroy(geodCRS);
    9726          13 :                 geodCRS = temp;
    9727          13 :                 proj_destroy(hub_crs);
    9728          13 :                 proj_destroy(co);
    9729             :             }
    9730             : 
    9731             :             /* --------------------------------------------------------------------
    9732             :              */
    9733             :             /*      We have to reconstruct the GEOGCS node for geocentric */
    9734             :             /*      coordinate systems. */
    9735             :             /* --------------------------------------------------------------------
    9736             :              */
    9737        4098 :             if (proj_get_type(geodCRS) == PJ_TYPE_GEOCENTRIC_CRS)
    9738             :             {
    9739           0 :                 auto datum = proj_crs_get_datum(d->getPROJContext(), geodCRS);
    9740             : #if PROJ_VERSION_MAJOR > 7 ||                                                  \
    9741             :     (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
    9742             :                 if (datum == nullptr)
    9743             :                 {
    9744             :                     datum = proj_crs_get_datum_ensemble(d->getPROJContext(),
    9745             :                                                         geodCRS);
    9746             :                 }
    9747             : #endif
    9748           0 :                 if (datum)
    9749             :                 {
    9750           0 :                     auto cs = proj_create_ellipsoidal_2D_cs(
    9751             :                         d->getPROJContext(), PJ_ELLPS2D_LATITUDE_LONGITUDE,
    9752             :                         nullptr, 0);
    9753           0 :                     auto temp = proj_create_geographic_crs_from_datum(
    9754             :                         d->getPROJContext(), "unnamed", datum, cs);
    9755           0 :                     proj_destroy(datum);
    9756           0 :                     proj_destroy(cs);
    9757           0 :                     proj_destroy(geodCRS);
    9758           0 :                     geodCRS = temp;
    9759             :                 }
    9760             :             }
    9761             : 
    9762        4098 :             poNewSRS->d->setPjCRS(geodCRS);
    9763        4098 :             if (d->m_axisMappingStrategy == OAMS_TRADITIONAL_GIS_ORDER)
    9764        2677 :                 poNewSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    9765        4098 :             return poNewSRS;
    9766             :         }
    9767             :     }
    9768           0 :     return nullptr;
    9769             : }
    9770             : 
    9771             : /************************************************************************/
    9772             : /*                           OSRCloneGeogCS()                           */
    9773             : /************************************************************************/
    9774             : /**
    9775             :  * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference
    9776             :  * object.
    9777             :  *
    9778             :  * This function is the same as OGRSpatialReference::CloneGeogCS().
    9779             :  */
    9780         123 : OGRSpatialReferenceH CPL_STDCALL OSRCloneGeogCS(OGRSpatialReferenceH hSource)
    9781             : 
    9782             : {
    9783         123 :     VALIDATE_POINTER1(hSource, "OSRCloneGeogCS", nullptr);
    9784             : 
    9785         123 :     return ToHandle(ToPointer(hSource)->CloneGeogCS());
    9786             : }
    9787             : 
    9788             : /************************************************************************/
    9789             : /*                            IsSameGeogCS()                            */
    9790             : /************************************************************************/
    9791             : 
    9792             : /**
    9793             :  * \brief Do the GeogCS'es match?
    9794             :  *
    9795             :  * This method is the same as the C function OSRIsSameGeogCS().
    9796             :  *
    9797             :  * @param poOther the SRS being compared against.
    9798             :  *
    9799             :  * @return TRUE if they are the same or FALSE otherwise.
    9800             :  */
    9801             : 
    9802        8220 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther) const
    9803             : 
    9804             : {
    9805        8220 :     return IsSameGeogCS(poOther, nullptr);
    9806             : }
    9807             : 
    9808             : /**
    9809             :  * \brief Do the GeogCS'es match?
    9810             :  *
    9811             :  * This method is the same as the C function OSRIsSameGeogCS().
    9812             :  *
    9813             :  * @param poOther the SRS being compared against.
    9814             :  * @param papszOptions options. ignored
    9815             :  *
    9816             :  * @return TRUE if they are the same or FALSE otherwise.
    9817             :  */
    9818             : 
    9819        8220 : int OGRSpatialReference::IsSameGeogCS(const OGRSpatialReference *poOther,
    9820             :                                       const char *const *papszOptions) const
    9821             : 
    9822             : {
    9823       16440 :     TAKE_OPTIONAL_LOCK();
    9824             : 
    9825        8220 :     CPL_IGNORE_RET_VAL(papszOptions);
    9826             : 
    9827        8220 :     d->refreshProjObj();
    9828        8220 :     poOther->d->refreshProjObj();
    9829        8220 :     if (!d->m_pj_crs || !poOther->d->m_pj_crs)
    9830           0 :         return FALSE;
    9831        8220 :     if (d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9832        8220 :         d->m_pjType == PJ_TYPE_VERTICAL_CRS ||
    9833       24660 :         poOther->d->m_pjType == PJ_TYPE_ENGINEERING_CRS ||
    9834        8220 :         poOther->d->m_pjType == PJ_TYPE_VERTICAL_CRS)
    9835             :     {
    9836           0 :         return FALSE;
    9837             :     }
    9838             : 
    9839        8220 :     auto geodCRS = proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
    9840             :     auto otherGeodCRS =
    9841        8220 :         proj_crs_get_geodetic_crs(d->getPROJContext(), poOther->d->m_pj_crs);
    9842        8220 :     if (!geodCRS || !otherGeodCRS)
    9843             :     {
    9844           0 :         proj_destroy(geodCRS);
    9845           0 :         proj_destroy(otherGeodCRS);
    9846           0 :         return FALSE;
    9847             :     }
    9848             : 
    9849        8220 :     int ret = proj_is_equivalent_to(
    9850             :         geodCRS, otherGeodCRS, PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS);
    9851             : 
    9852        8220 :     proj_destroy(geodCRS);
    9853        8220 :     proj_destroy(otherGeodCRS);
    9854        8220 :     return ret;
    9855             : }
    9856             : 
    9857             : /************************************************************************/
    9858             : /*                          OSRIsSameGeogCS()                           */
    9859             : /************************************************************************/
    9860             : 
    9861             : /**
    9862             :  * \brief Do the GeogCS'es match?
    9863             :  *
    9864             :  * This function is the same as OGRSpatialReference::IsSameGeogCS().
    9865             :  */
    9866           0 : int OSRIsSameGeogCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9867             : 
    9868             : {
    9869           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameGeogCS", 0);
    9870           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameGeogCS", 0);
    9871             : 
    9872           0 :     return ToPointer(hSRS1)->IsSameGeogCS(ToPointer(hSRS2));
    9873             : }
    9874             : 
    9875             : /************************************************************************/
    9876             : /*                            IsSameVertCS()                            */
    9877             : /************************************************************************/
    9878             : 
    9879             : /**
    9880             :  * \brief Do the VertCS'es match?
    9881             :  *
    9882             :  * This method is the same as the C function OSRIsSameVertCS().
    9883             :  *
    9884             :  * @param poOther the SRS being compared against.
    9885             :  *
    9886             :  * @return TRUE if they are the same or FALSE otherwise.
    9887             :  */
    9888             : 
    9889           0 : int OGRSpatialReference::IsSameVertCS(const OGRSpatialReference *poOther) const
    9890             : 
    9891             : {
    9892           0 :     TAKE_OPTIONAL_LOCK();
    9893             : 
    9894             :     /* -------------------------------------------------------------------- */
    9895             :     /*      Does the datum name match?                                      */
    9896             :     /* -------------------------------------------------------------------- */
    9897           0 :     const char *pszThisValue = this->GetAttrValue("VERT_DATUM");
    9898           0 :     const char *pszOtherValue = poOther->GetAttrValue("VERT_DATUM");
    9899             : 
    9900           0 :     if (pszThisValue == nullptr || pszOtherValue == nullptr ||
    9901           0 :         !EQUAL(pszThisValue, pszOtherValue))
    9902           0 :         return FALSE;
    9903             : 
    9904             :     /* -------------------------------------------------------------------- */
    9905             :     /*      Do the units match?                                             */
    9906             :     /* -------------------------------------------------------------------- */
    9907           0 :     pszThisValue = this->GetAttrValue("VERT_CS|UNIT", 1);
    9908           0 :     if (pszThisValue == nullptr)
    9909           0 :         pszThisValue = "1.0";
    9910             : 
    9911           0 :     pszOtherValue = poOther->GetAttrValue("VERT_CS|UNIT", 1);
    9912           0 :     if (pszOtherValue == nullptr)
    9913           0 :         pszOtherValue = "1.0";
    9914             : 
    9915           0 :     if (std::abs(CPLAtof(pszOtherValue) - CPLAtof(pszThisValue)) > 0.00000001)
    9916           0 :         return FALSE;
    9917             : 
    9918           0 :     return TRUE;
    9919             : }
    9920             : 
    9921             : /************************************************************************/
    9922             : /*                          OSRIsSameVertCS()                           */
    9923             : /************************************************************************/
    9924             : 
    9925             : /**
    9926             :  * \brief Do the VertCS'es match?
    9927             :  *
    9928             :  * This function is the same as OGRSpatialReference::IsSameVertCS().
    9929             :  */
    9930           0 : int OSRIsSameVertCS(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
    9931             : 
    9932             : {
    9933           0 :     VALIDATE_POINTER1(hSRS1, "OSRIsSameVertCS", 0);
    9934           0 :     VALIDATE_POINTER1(hSRS2, "OSRIsSameVertCS", 0);
    9935             : 
    9936           0 :     return ToPointer(hSRS1)->IsSameVertCS(ToPointer(hSRS2));
    9937             : }
    9938             : 
    9939             : /************************************************************************/
    9940             : /*                               IsSame()                               */
    9941             : /************************************************************************/
    9942             : 
    9943             : /**
    9944             :  * \brief Do these two spatial references describe the same system ?
    9945             :  *
    9946             :  * @param poOtherSRS the SRS being compared to.
    9947             :  *
    9948             :  * @return TRUE if equivalent or FALSE otherwise.
    9949             :  */
    9950             : 
    9951       22831 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS) const
    9952             : 
    9953             : {
    9954       22831 :     return IsSame(poOtherSRS, nullptr);
    9955             : }
    9956             : 
    9957             : /**
    9958             :  * \brief Do these two spatial references describe the same system ?
    9959             :  *
    9960             :  * This also takes into account the data axis to CRS axis mapping by default
    9961             :  *
    9962             :  * @param poOtherSRS the SRS being compared to.
    9963             :  * @param papszOptions options. NULL or NULL terminated list of options.
    9964             :  * Currently supported options are:
    9965             :  * <ul>
    9966             :  * <li>IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES/NO. Defaults to NO</li>
    9967             :  * <li>CRITERION=STRICT/EQUIVALENT/EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.
    9968             :  *     Defaults to EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS.</li>
    9969             :  * <li>IGNORE_COORDINATE_EPOCH=YES/NO. Defaults to NO</li>
    9970             :  * </ul>
    9971             :  *
    9972             :  * @return TRUE if equivalent or FALSE otherwise.
    9973             :  */
    9974             : 
    9975       33514 : int OGRSpatialReference::IsSame(const OGRSpatialReference *poOtherSRS,
    9976             :                                 const char *const *papszOptions) const
    9977             : 
    9978             : {
    9979       67028 :     TAKE_OPTIONAL_LOCK();
    9980             : 
    9981       33514 :     d->refreshProjObj();
    9982       33514 :     poOtherSRS->d->refreshProjObj();
    9983       33514 :     if (!d->m_pj_crs || !poOtherSRS->d->m_pj_crs)
    9984          51 :         return d->m_pj_crs == poOtherSRS->d->m_pj_crs;
    9985       33463 :     if (!CPLTestBool(CSLFetchNameValueDef(
    9986             :             papszOptions, "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING", "NO")))
    9987             :     {
    9988       32862 :         if (d->m_axisMapping != poOtherSRS->d->m_axisMapping)
    9989        2791 :             return false;
    9990             :     }
    9991             : 
    9992       30672 :     if (!CPLTestBool(CSLFetchNameValueDef(papszOptions,
    9993             :                                           "IGNORE_COORDINATE_EPOCH", "NO")))
    9994             :     {
    9995       30308 :         if (d->m_coordinateEpoch != poOtherSRS->d->m_coordinateEpoch)
    9996          27 :             return false;
    9997             :     }
    9998             : 
    9999       30645 :     bool reboundSelf = false;
   10000       30645 :     bool reboundOther = false;
   10001       30696 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS &&
   10002          51 :         poOtherSRS->d->m_pjType != PJ_TYPE_BOUND_CRS)
   10003             :     {
   10004          14 :         d->demoteFromBoundCRS();
   10005          14 :         reboundSelf = true;
   10006             :     }
   10007       61225 :     else if (d->m_pjType != PJ_TYPE_BOUND_CRS &&
   10008       30594 :              poOtherSRS->d->m_pjType == PJ_TYPE_BOUND_CRS)
   10009             :     {
   10010          28 :         poOtherSRS->d->demoteFromBoundCRS();
   10011          28 :         reboundOther = true;
   10012             :     }
   10013             : 
   10014       30645 :     PJ_COMPARISON_CRITERION criterion =
   10015             :         PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS;
   10016       30645 :     const char *pszCriterion = CSLFetchNameValueDef(
   10017             :         papszOptions, "CRITERION", "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS");
   10018       30645 :     if (EQUAL(pszCriterion, "STRICT"))
   10019           0 :         criterion = PJ_COMP_STRICT;
   10020       30645 :     else if (EQUAL(pszCriterion, "EQUIVALENT"))
   10021        7907 :         criterion = PJ_COMP_EQUIVALENT;
   10022       22738 :     else if (!EQUAL(pszCriterion, "EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS"))
   10023             :     {
   10024           0 :         CPLError(CE_Warning, CPLE_NotSupported,
   10025             :                  "Unsupported value for CRITERION: %s", pszCriterion);
   10026             :     }
   10027             :     int ret =
   10028       30645 :         proj_is_equivalent_to(d->m_pj_crs, poOtherSRS->d->m_pj_crs, criterion);
   10029       30645 :     if (reboundSelf)
   10030          14 :         d->undoDemoteFromBoundCRS();
   10031       30645 :     if (reboundOther)
   10032          28 :         poOtherSRS->d->undoDemoteFromBoundCRS();
   10033             : 
   10034       30645 :     return ret;
   10035             : }
   10036             : 
   10037             : /************************************************************************/
   10038             : /*                             OSRIsSame()                              */
   10039             : /************************************************************************/
   10040             : 
   10041             : /**
   10042             :  * \brief Do these two spatial references describe the same system ?
   10043             :  *
   10044             :  * This function is the same as OGRSpatialReference::IsSame().
   10045             :  */
   10046          27 : int OSRIsSame(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2)
   10047             : 
   10048             : {
   10049          27 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
   10050          27 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
   10051             : 
   10052          27 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2));
   10053             : }
   10054             : 
   10055             : /************************************************************************/
   10056             : /*                             OSRIsSameEx()                            */
   10057             : /************************************************************************/
   10058             : 
   10059             : /**
   10060             :  * \brief Do these two spatial references describe the same system ?
   10061             :  *
   10062             :  * This function is the same as OGRSpatialReference::IsSame().
   10063             :  */
   10064         609 : int OSRIsSameEx(OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2,
   10065             :                 const char *const *papszOptions)
   10066             : {
   10067         609 :     VALIDATE_POINTER1(hSRS1, "OSRIsSame", 0);
   10068         609 :     VALIDATE_POINTER1(hSRS2, "OSRIsSame", 0);
   10069             : 
   10070         609 :     return ToPointer(hSRS1)->IsSame(ToPointer(hSRS2), papszOptions);
   10071             : }
   10072             : 
   10073             : /************************************************************************/
   10074             : /*                    convertToOtherProjection()                        */
   10075             : /************************************************************************/
   10076             : 
   10077             : /**
   10078             :  * \brief Convert to another equivalent projection
   10079             :  *
   10080             :  * Currently implemented:
   10081             :  * <ul>
   10082             :  * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
   10083             :  * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
   10084             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
   10085             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
   10086             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
   10087             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
   10088             :  * </ul>
   10089             :  *
   10090             :  * @param pszTargetProjection target projection.
   10091             :  * @param papszOptions lists of options. None supported currently.
   10092             :  * @return a new SRS, or NULL in case of error.
   10093             :  *
   10094             :  * @since GDAL 2.3
   10095             :  */
   10096          89 : OGRSpatialReference *OGRSpatialReference::convertToOtherProjection(
   10097             :     const char *pszTargetProjection,
   10098             :     CPL_UNUSED const char *const *papszOptions) const
   10099             : {
   10100         178 :     TAKE_OPTIONAL_LOCK();
   10101             : 
   10102          89 :     if (pszTargetProjection == nullptr)
   10103           1 :         return nullptr;
   10104             :     int new_code;
   10105          88 :     if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_1SP))
   10106             :     {
   10107           6 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A;
   10108             :     }
   10109          82 :     else if (EQUAL(pszTargetProjection, SRS_PT_MERCATOR_2SP))
   10110             :     {
   10111           5 :         new_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B;
   10112             :     }
   10113          77 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
   10114             :     {
   10115          65 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP;
   10116             :     }
   10117          12 :     else if (EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
   10118             :     {
   10119          11 :         new_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP;
   10120             :     }
   10121             :     else
   10122             :     {
   10123           1 :         return nullptr;
   10124             :     }
   10125             : 
   10126          87 :     d->refreshProjObj();
   10127          87 :     d->demoteFromBoundCRS();
   10128          87 :     OGRSpatialReference *poNewSRS = nullptr;
   10129          87 :     if (d->m_pjType == PJ_TYPE_PROJECTED_CRS)
   10130             :     {
   10131             :         auto conv =
   10132          86 :             proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10133          86 :         auto new_conv = proj_convert_conversion_to_other_method(
   10134             :             d->getPROJContext(), conv, new_code, nullptr);
   10135          86 :         proj_destroy(conv);
   10136          86 :         if (new_conv)
   10137             :         {
   10138             :             auto geodCRS =
   10139          72 :                 proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10140          72 :             auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   10141          72 :                                                      d->m_pj_crs);
   10142          72 :             if (geodCRS && cs)
   10143             :             {
   10144          72 :                 auto new_proj_crs = proj_create_projected_crs(
   10145          72 :                     d->getPROJContext(), proj_get_name(d->m_pj_crs), geodCRS,
   10146             :                     new_conv, cs);
   10147          72 :                 proj_destroy(new_conv);
   10148          72 :                 if (new_proj_crs)
   10149             :                 {
   10150          72 :                     poNewSRS = new OGRSpatialReference();
   10151             : 
   10152          72 :                     if (d->m_pj_bound_crs_target && d->m_pj_bound_crs_co)
   10153             :                     {
   10154           9 :                         auto boundCRS = proj_crs_create_bound_crs(
   10155             :                             d->getPROJContext(), new_proj_crs,
   10156           9 :                             d->m_pj_bound_crs_target, d->m_pj_bound_crs_co);
   10157           9 :                         if (boundCRS)
   10158             :                         {
   10159           9 :                             proj_destroy(new_proj_crs);
   10160           9 :                             new_proj_crs = boundCRS;
   10161             :                         }
   10162             :                     }
   10163             : 
   10164          72 :                     poNewSRS->d->setPjCRS(new_proj_crs);
   10165             :                 }
   10166             :             }
   10167          72 :             proj_destroy(geodCRS);
   10168          72 :             proj_destroy(cs);
   10169             :         }
   10170             :     }
   10171          87 :     d->undoDemoteFromBoundCRS();
   10172          87 :     return poNewSRS;
   10173             : }
   10174             : 
   10175             : /************************************************************************/
   10176             : /*                    OSRConvertToOtherProjection()                     */
   10177             : /************************************************************************/
   10178             : 
   10179             : /**
   10180             :  * \brief Convert to another equivalent projection
   10181             :  *
   10182             :  * Currently implemented:
   10183             :  * <ul>
   10184             :  * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
   10185             :  * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
   10186             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to
   10187             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
   10188             :  * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to
   10189             :  * SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
   10190             :  * </ul>
   10191             :  *
   10192             :  * @param hSRS source SRS
   10193             :  * @param pszTargetProjection target projection.
   10194             :  * @param papszOptions lists of options. None supported currently.
   10195             :  * @return a new SRS, or NULL in case of error.
   10196             :  *
   10197             :  * @since GDAL 2.3
   10198             :  */
   10199             : OGRSpatialReferenceH
   10200          28 : OSRConvertToOtherProjection(OGRSpatialReferenceH hSRS,
   10201             :                             const char *pszTargetProjection,
   10202             :                             const char *const *papszOptions)
   10203             : {
   10204          28 :     VALIDATE_POINTER1(hSRS, "OSRConvertToOtherProjection", nullptr);
   10205          28 :     return ToHandle(ToPointer(hSRS)->convertToOtherProjection(
   10206          28 :         pszTargetProjection, papszOptions));
   10207             : }
   10208             : 
   10209             : /************************************************************************/
   10210             : /*                           OSRFindMatches()                           */
   10211             : /************************************************************************/
   10212             : 
   10213             : /**
   10214             :  * \brief Try to identify a match between the passed SRS and a related SRS
   10215             :  * in a catalog.
   10216             :  *
   10217             :  * Matching may be partial, or may fail.
   10218             :  * Returned entries will be sorted by decreasing match confidence (first
   10219             :  * entry has the highest match confidence).
   10220             :  *
   10221             :  * The exact way matching is done may change in future versions. Starting with
   10222             :  * GDAL 3.0, it relies on PROJ' proj_identify() function.
   10223             :  *
   10224             :  * This function is the same as OGRSpatialReference::FindMatches().
   10225             :  *
   10226             :  * @param hSRS SRS to match
   10227             :  * @param papszOptions NULL terminated list of options or NULL
   10228             :  * @param pnEntries Output parameter. Number of values in the returned array.
   10229             :  * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
   10230             :  * will be allocated to an array of *pnEntries whose values between 0 and 100
   10231             :  * indicate the confidence in the match. 100 is the highest confidence level.
   10232             :  * The array must be freed with CPLFree().
   10233             :  *
   10234             :  * @return an array of SRS that match the passed SRS, or NULL. Must be freed
   10235             :  * with OSRFreeSRSArray()
   10236             :  *
   10237             :  * @since GDAL 2.3
   10238             :  */
   10239          10 : OGRSpatialReferenceH *OSRFindMatches(OGRSpatialReferenceH hSRS,
   10240             :                                      char **papszOptions, int *pnEntries,
   10241             :                                      int **ppanMatchConfidence)
   10242             : {
   10243          10 :     if (pnEntries)
   10244          10 :         *pnEntries = 0;
   10245          10 :     if (ppanMatchConfidence)
   10246          10 :         *ppanMatchConfidence = nullptr;
   10247          10 :     VALIDATE_POINTER1(hSRS, "OSRFindMatches", nullptr);
   10248             : 
   10249          10 :     OGRSpatialReference *poSRS = ToPointer(hSRS);
   10250          10 :     return poSRS->FindMatches(papszOptions, pnEntries, ppanMatchConfidence);
   10251             : }
   10252             : 
   10253             : /************************************************************************/
   10254             : /*                           OSRFreeSRSArray()                          */
   10255             : /************************************************************************/
   10256             : 
   10257             : /**
   10258             :  * \brief Free return of OSRIdentifyMatches()
   10259             :  *
   10260             :  * @param pahSRS array of SRS (must be NULL terminated)
   10261             :  * @since GDAL 2.3
   10262             :  */
   10263         192 : void OSRFreeSRSArray(OGRSpatialReferenceH *pahSRS)
   10264             : {
   10265         192 :     if (pahSRS != nullptr)
   10266             :     {
   10267        1739 :         for (int i = 0; pahSRS[i] != nullptr; ++i)
   10268             :         {
   10269        1565 :             OSRRelease(pahSRS[i]);
   10270             :         }
   10271         174 :         CPLFree(pahSRS);
   10272             :     }
   10273         192 : }
   10274             : 
   10275             : /************************************************************************/
   10276             : /*                         FindBestMatch()                              */
   10277             : /************************************************************************/
   10278             : 
   10279             : /**
   10280             :  * \brief Try to identify the best match between the passed SRS and a related
   10281             :  * SRS in a catalog.
   10282             :  *
   10283             :  * This is a wrapper over OGRSpatialReference::FindMatches() that takes care
   10284             :  * of filtering its output.
   10285             :  * Only matches whose confidence is greater or equal to nMinimumMatchConfidence
   10286             :  * will be considered. If there is a single match, it is returned.
   10287             :  * If there are several matches, only return the one under the
   10288             :  * pszPreferredAuthority, if there is a single one under that authority.
   10289             :  *
   10290             :  * @param nMinimumMatchConfidence Minimum match confidence (value between 0 and
   10291             :  * 100). If set to 0, 90 is used.
   10292             :  * @param pszPreferredAuthority Preferred CRS authority. If set to nullptr,
   10293             :  * "EPSG" is used.
   10294             :  * @param papszOptions NULL terminated list of options or NULL. No option is
   10295             :  * defined at time of writing.
   10296             :  *
   10297             :  * @return a new OGRSpatialReference* object to free with Release(), or nullptr
   10298             :  *
   10299             :  * @since GDAL 3.6
   10300             :  * @see OGRSpatialReference::FindMatches()
   10301             :  */
   10302             : OGRSpatialReference *
   10303        1412 : OGRSpatialReference::FindBestMatch(int nMinimumMatchConfidence,
   10304             :                                    const char *pszPreferredAuthority,
   10305             :                                    CSLConstList papszOptions) const
   10306             : {
   10307        2824 :     TAKE_OPTIONAL_LOCK();
   10308             : 
   10309        1412 :     CPL_IGNORE_RET_VAL(papszOptions);  // ignored for now.
   10310             : 
   10311        1412 :     if (nMinimumMatchConfidence == 0)
   10312           0 :         nMinimumMatchConfidence = 90;
   10313        1412 :     if (pszPreferredAuthority == nullptr)
   10314         200 :         pszPreferredAuthority = "EPSG";
   10315             : 
   10316             :     // Try to identify the CRS with the database
   10317        1412 :     int nEntries = 0;
   10318        1412 :     int *panConfidence = nullptr;
   10319             :     OGRSpatialReferenceH *pahSRS =
   10320        1412 :         FindMatches(nullptr, &nEntries, &panConfidence);
   10321        1412 :     if (nEntries == 1 && panConfidence[0] >= nMinimumMatchConfidence)
   10322             :     {
   10323        2506 :         std::vector<double> adfTOWGS84(7);
   10324        1253 :         if (GetTOWGS84(&adfTOWGS84[0], 7) != OGRERR_NONE)
   10325             :         {
   10326        1252 :             adfTOWGS84.clear();
   10327             :         }
   10328             : 
   10329        1253 :         auto poSRS = OGRSpatialReference::FromHandle(pahSRS[0]);
   10330             : 
   10331             :         auto poBaseGeogCRS =
   10332        1253 :             std::unique_ptr<OGRSpatialReference>(poSRS->CloneGeogCS());
   10333             : 
   10334             :         // If the base geographic SRS of the SRS is EPSG:4326
   10335             :         // with TOWGS84[0,0,0,0,0,0], then just use the official
   10336             :         // SRS code
   10337             :         // Same with EPSG:4258 (ETRS89), since it's the only known
   10338             :         // TOWGS84[] style transformation to WGS 84, and given the
   10339             :         // "fuzzy" nature of both ETRS89 and WGS 84, there's little
   10340             :         // chance that a non-NULL TOWGS84[] will emerge.
   10341        1253 :         const char *pszAuthorityName = nullptr;
   10342        1253 :         const char *pszAuthorityCode = nullptr;
   10343        1253 :         const char *pszBaseAuthorityName = nullptr;
   10344        1253 :         const char *pszBaseAuthorityCode = nullptr;
   10345        2506 :         if (adfTOWGS84 == std::vector<double>(7) &&
   10346           1 :             (pszAuthorityName = poSRS->GetAuthorityName(nullptr)) != nullptr &&
   10347           1 :             EQUAL(pszAuthorityName, "EPSG") &&
   10348           1 :             (pszAuthorityCode = poSRS->GetAuthorityCode(nullptr)) != nullptr &&
   10349           1 :             (pszBaseAuthorityName = poBaseGeogCRS->GetAuthorityName(nullptr)) !=
   10350           1 :                 nullptr &&
   10351           1 :             EQUAL(pszBaseAuthorityName, "EPSG") &&
   10352           1 :             (pszBaseAuthorityCode = poBaseGeogCRS->GetAuthorityCode(nullptr)) !=
   10353        2507 :                 nullptr &&
   10354           1 :             (EQUAL(pszBaseAuthorityCode, "4326") ||
   10355           1 :              EQUAL(pszBaseAuthorityCode, "4258")))
   10356             :         {
   10357           1 :             poSRS->importFromEPSG(atoi(pszAuthorityCode));
   10358             :         }
   10359             : 
   10360        1253 :         CPLFree(pahSRS);
   10361        1253 :         CPLFree(panConfidence);
   10362             : 
   10363        1253 :         return poSRS;
   10364             :     }
   10365             :     else
   10366             :     {
   10367             :         // If there are several matches >= nMinimumMatchConfidence, take the
   10368             :         // only one that is under pszPreferredAuthority
   10369         159 :         int iBestEntry = -1;
   10370        1674 :         for (int i = 0; i < nEntries; i++)
   10371             :         {
   10372        1515 :             if (panConfidence[i] >= nMinimumMatchConfidence)
   10373             :             {
   10374             :                 const char *pszAuthName =
   10375           3 :                     OGRSpatialReference::FromHandle(pahSRS[i])
   10376           3 :                         ->GetAuthorityName(nullptr);
   10377           3 :                 if (pszAuthName != nullptr &&
   10378           3 :                     EQUAL(pszAuthName, pszPreferredAuthority))
   10379             :                 {
   10380           3 :                     if (iBestEntry < 0)
   10381           3 :                         iBestEntry = i;
   10382             :                     else
   10383             :                     {
   10384           0 :                         iBestEntry = -1;
   10385           0 :                         break;
   10386             :                     }
   10387             :                 }
   10388             :             }
   10389             :         }
   10390         159 :         if (iBestEntry >= 0)
   10391             :         {
   10392           3 :             auto poRet = OGRSpatialReference::FromHandle(pahSRS[0])->Clone();
   10393           3 :             OSRFreeSRSArray(pahSRS);
   10394           3 :             CPLFree(panConfidence);
   10395           3 :             return poRet;
   10396             :         }
   10397             :     }
   10398         156 :     OSRFreeSRSArray(pahSRS);
   10399         156 :     CPLFree(panConfidence);
   10400         156 :     return nullptr;
   10401             : }
   10402             : 
   10403             : /************************************************************************/
   10404             : /*                             SetTOWGS84()                             */
   10405             : /************************************************************************/
   10406             : 
   10407             : /**
   10408             :  * \brief Set the Bursa-Wolf conversion to WGS84.
   10409             :  *
   10410             :  * This will create the TOWGS84 node as a child of the DATUM.  It will fail
   10411             :  * if there is no existing DATUM node. It will replace
   10412             :  * an existing TOWGS84 node if there is one.
   10413             :  *
   10414             :  * The parameters have the same meaning as EPSG transformation 9606
   10415             :  * (Position Vector 7-param. transformation).
   10416             :  *
   10417             :  * This method is the same as the C function OSRSetTOWGS84().
   10418             :  *
   10419             :  * @param dfDX X child in meters.
   10420             :  * @param dfDY Y child in meters.
   10421             :  * @param dfDZ Z child in meters.
   10422             :  * @param dfEX X rotation in arc seconds (optional, defaults to zero).
   10423             :  * @param dfEY Y rotation in arc seconds (optional, defaults to zero).
   10424             :  * @param dfEZ Z rotation in arc seconds (optional, defaults to zero).
   10425             :  * @param dfPPM scaling factor (parts per million).
   10426             :  *
   10427             :  * @return OGRERR_NONE on success.
   10428             :  */
   10429             : 
   10430         104 : OGRErr OGRSpatialReference::SetTOWGS84(double dfDX, double dfDY, double dfDZ,
   10431             :                                        double dfEX, double dfEY, double dfEZ,
   10432             :                                        double dfPPM)
   10433             : 
   10434             : {
   10435         208 :     TAKE_OPTIONAL_LOCK();
   10436             : 
   10437         104 :     d->refreshProjObj();
   10438         104 :     if (d->m_pj_crs == nullptr)
   10439             :     {
   10440           0 :         return OGRERR_FAILURE;
   10441             :     }
   10442             : 
   10443             :     // Remove existing BoundCRS
   10444         104 :     if (d->m_pjType == PJ_TYPE_BOUND_CRS)
   10445             :     {
   10446           0 :         auto baseCRS = proj_get_source_crs(d->getPROJContext(), d->m_pj_crs);
   10447           0 :         if (!baseCRS)
   10448           0 :             return OGRERR_FAILURE;
   10449           0 :         d->setPjCRS(baseCRS);
   10450             :     }
   10451             : 
   10452             :     PJ_PARAM_DESCRIPTION params[7];
   10453             : 
   10454         104 :     params[0].name = EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION;
   10455         104 :     params[0].auth_name = "EPSG";
   10456         104 :     params[0].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
   10457         104 :     params[0].value = dfDX;
   10458         104 :     params[0].unit_name = "metre";
   10459         104 :     params[0].unit_conv_factor = 1.0;
   10460         104 :     params[0].unit_type = PJ_UT_LINEAR;
   10461             : 
   10462         104 :     params[1].name = EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION;
   10463         104 :     params[1].auth_name = "EPSG";
   10464         104 :     params[1].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
   10465         104 :     params[1].value = dfDY;
   10466         104 :     params[1].unit_name = "metre";
   10467         104 :     params[1].unit_conv_factor = 1.0;
   10468         104 :     params[1].unit_type = PJ_UT_LINEAR;
   10469             : 
   10470         104 :     params[2].name = EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION;
   10471         104 :     params[2].auth_name = "EPSG";
   10472         104 :     params[2].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
   10473         104 :     params[2].value = dfDZ;
   10474         104 :     params[2].unit_name = "metre";
   10475         104 :     params[2].unit_conv_factor = 1.0;
   10476         104 :     params[2].unit_type = PJ_UT_LINEAR;
   10477             : 
   10478         104 :     params[3].name = EPSG_NAME_PARAMETER_X_AXIS_ROTATION;
   10479         104 :     params[3].auth_name = "EPSG";
   10480         104 :     params[3].code = XSTRINGIFY(EPSG_CODE_PARAMETER_X_AXIS_ROTATION);
   10481         104 :     params[3].value = dfEX;
   10482         104 :     params[3].unit_name = "arc-second";
   10483         104 :     params[3].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10484         104 :     params[3].unit_type = PJ_UT_ANGULAR;
   10485             : 
   10486         104 :     params[4].name = EPSG_NAME_PARAMETER_Y_AXIS_ROTATION;
   10487         104 :     params[4].auth_name = "EPSG";
   10488         104 :     params[4].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION);
   10489         104 :     params[4].value = dfEY;
   10490         104 :     params[4].unit_name = "arc-second";
   10491         104 :     params[4].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10492         104 :     params[4].unit_type = PJ_UT_ANGULAR;
   10493             : 
   10494         104 :     params[5].name = EPSG_NAME_PARAMETER_Z_AXIS_ROTATION;
   10495         104 :     params[5].auth_name = "EPSG";
   10496         104 :     params[5].code = XSTRINGIFY(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION);
   10497         104 :     params[5].value = dfEZ;
   10498         104 :     params[5].unit_name = "arc-second";
   10499         104 :     params[5].unit_conv_factor = 1. / 3600 * M_PI / 180;
   10500         104 :     params[5].unit_type = PJ_UT_ANGULAR;
   10501             : 
   10502         104 :     params[6].name = EPSG_NAME_PARAMETER_SCALE_DIFFERENCE;
   10503         104 :     params[6].auth_name = "EPSG";
   10504         104 :     params[6].code = XSTRINGIFY(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE);
   10505         104 :     params[6].value = dfPPM;
   10506         104 :     params[6].unit_name = "parts per million";
   10507         104 :     params[6].unit_conv_factor = 1e-6;
   10508         104 :     params[6].unit_type = PJ_UT_SCALE;
   10509             : 
   10510             :     auto sourceCRS =
   10511         104 :         proj_crs_get_geodetic_crs(d->getPROJContext(), d->m_pj_crs);
   10512         104 :     if (!sourceCRS)
   10513             :     {
   10514           0 :         return OGRERR_FAILURE;
   10515             :     }
   10516             : 
   10517         104 :     const auto sourceType = proj_get_type(sourceCRS);
   10518             : 
   10519         104 :     auto targetCRS = proj_create_from_database(
   10520             :         d->getPROJContext(), "EPSG",
   10521             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS   ? "4326"
   10522             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS ? "4979"
   10523             :                                                   : "4978",
   10524             :         PJ_CATEGORY_CRS, false, nullptr);
   10525         104 :     if (!targetCRS)
   10526             :     {
   10527           0 :         proj_destroy(sourceCRS);
   10528           0 :         return OGRERR_FAILURE;
   10529             :     }
   10530             : 
   10531         208 :     CPLString osMethodCode;
   10532             :     osMethodCode.Printf("%d",
   10533             :                         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10534             :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10535             :                         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10536           0 :                             ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10537         104 :                             : EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC);
   10538             : 
   10539         104 :     auto transf = proj_create_transformation(
   10540             :         d->getPROJContext(), "Transformation to WGS84", nullptr, nullptr,
   10541             :         sourceCRS, targetCRS, nullptr,
   10542             :         sourceType == PJ_TYPE_GEOGRAPHIC_2D_CRS
   10543             :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
   10544             :         : sourceType == PJ_TYPE_GEOGRAPHIC_3D_CRS
   10545           0 :             ? EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D
   10546             :             : EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC,
   10547             :         "EPSG", osMethodCode.c_str(), 7, params, -1);
   10548         104 :     proj_destroy(sourceCRS);
   10549         104 :     if (!transf)
   10550             :     {
   10551           0 :         proj_destroy(targetCRS);
   10552           0 :         return OGRERR_FAILURE;
   10553             :     }
   10554             : 
   10555         104 :     auto newBoundCRS = proj_crs_create_bound_crs(
   10556         104 :         d->getPROJContext(), d->m_pj_crs, targetCRS, transf);
   10557         104 :     proj_destroy(transf);
   10558         104 :     proj_destroy(targetCRS);
   10559         104 :     if (!newBoundCRS)
   10560             :     {
   10561           0 :         return OGRERR_FAILURE;
   10562             :     }
   10563             : 
   10564         104 :     d->setPjCRS(newBoundCRS);
   10565         104 :     return OGRERR_NONE;
   10566             : }
   10567             : 
   10568             : /************************************************************************/
   10569             : /*                           OSRSetTOWGS84()                            */
   10570             : /************************************************************************/
   10571             : 
   10572             : /**
   10573             :  * \brief Set the Bursa-Wolf conversion to WGS84.
   10574             :  *
   10575             :  * This function is the same as OGRSpatialReference::SetTOWGS84().
   10576             :  */
   10577           4 : OGRErr OSRSetTOWGS84(OGRSpatialReferenceH hSRS, double dfDX, double dfDY,
   10578             :                      double dfDZ, double dfEX, double dfEY, double dfEZ,
   10579             :                      double dfPPM)
   10580             : 
   10581             : {
   10582           4 :     VALIDATE_POINTER1(hSRS, "OSRSetTOWGS84", OGRERR_FAILURE);
   10583             : 
   10584           4 :     return ToPointer(hSRS)->SetTOWGS84(dfDX, dfDY, dfDZ, dfEX, dfEY, dfEZ,
   10585           4 :                                        dfPPM);
   10586             : }
   10587             : 
   10588             : /************************************************************************/
   10589             : /*                             GetTOWGS84()                             */
   10590             : /************************************************************************/
   10591             : 
   10592             : /**
   10593             :  * \brief Fetch TOWGS84 parameters, if available.
   10594             :  *
   10595             :  * The parameters have the same meaning as EPSG transformation 9606
   10596             :  * (Position Vector 7-param. transformation).
   10597             :  *
   10598             :  * @param padfCoeff array into which up to 7 coefficients are placed.
   10599             :  * @param nCoeffCount size of padfCoeff - defaults to 7.
   10600             :  *
   10601             :  * @return OGRERR_NONE on success, or OGRERR_FAILURE if there is no
   10602             :  * TOWGS84 node available.
   10603             :  */
   10604             : 
   10605        4636 : OGRErr OGRSpatialReference::GetTOWGS84(double *padfCoeff, int nCoeffCount) const
   10606             : 
   10607             : {
   10608        9272 :     TAKE_OPTIONAL_LOCK();
   10609             : 
   10610        4636 :     d->refreshProjObj();
   10611        4636 :     if (d->m_pjType != PJ_TYPE_BOUND_CRS)
   10612        4588 :         return OGRERR_FAILURE;
   10613             : 
   10614          48 :     memset(padfCoeff, 0, sizeof(double) * nCoeffCount);
   10615             : 
   10616          48 :     auto transf = proj_crs_get_coordoperation(d->getPROJContext(), d->m_pj_crs);
   10617          48 :     int success = proj_coordoperation_get_towgs84_values(
   10618             :         d->getPROJContext(), transf, padfCoeff, nCoeffCount, false);
   10619          48 :     proj_destroy(transf);
   10620             : 
   10621          48 :     return success ? OGRERR_NONE : OGRERR_FAILURE;
   10622             : }
   10623             : 
   10624             : /************************************************************************/
   10625             : /*                           OSRGetTOWGS84()                            */
   10626             : /************************************************************************/
   10627             : 
   10628             : /**
   10629             :  * \brief Fetch TOWGS84 parameters, if available.
   10630             :  *
   10631             :  * This function is the same as OGRSpatialReference::GetTOWGS84().
   10632             :  */
   10633          10 : OGRErr OSRGetTOWGS84(OGRSpatialReferenceH hSRS, double *padfCoeff,
   10634             :                      int nCoeffCount)
   10635             : 
   10636             : {
   10637          10 :     VALIDATE_POINTER1(hSRS, "OSRGetTOWGS84", OGRERR_FAILURE);
   10638             : 
   10639          10 :     return ToPointer(hSRS)->GetTOWGS84(padfCoeff, nCoeffCount);
   10640             : }
   10641             : 
   10642             : /************************************************************************/
   10643             : /*                         IsAngularParameter()                         */
   10644             : /************************************************************************/
   10645             : 
   10646             : /** Is the passed projection parameter an angular one?
   10647             :  *
   10648             :  * @return TRUE or FALSE
   10649             :  */
   10650             : 
   10651             : /* static */
   10652          10 : int OGRSpatialReference::IsAngularParameter(const char *pszParameterName)
   10653             : 
   10654             : {
   10655          10 :     if (STARTS_WITH_CI(pszParameterName, "long") ||
   10656          10 :         STARTS_WITH_CI(pszParameterName, "lati") ||
   10657           7 :         EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN) ||
   10658           4 :         STARTS_WITH_CI(pszParameterName, "standard_parallel") ||
   10659           2 :         EQUAL(pszParameterName, SRS_PP_AZIMUTH) ||
   10660           2 :         EQUAL(pszParameterName, SRS_PP_RECTIFIED_GRID_ANGLE))
   10661           8 :         return TRUE;
   10662             : 
   10663           2 :     return FALSE;
   10664             : }
   10665             : 
   10666             : /************************************************************************/
   10667             : /*                        IsLongitudeParameter()                        */
   10668             : /************************************************************************/
   10669             : 
   10670             : /** Is the passed projection parameter an angular longitude
   10671             :  * (relative to a prime meridian)?
   10672             :  *
   10673             :  * @return TRUE or FALSE
   10674             :  */
   10675             : 
   10676             : /* static */
   10677           0 : int OGRSpatialReference::IsLongitudeParameter(const char *pszParameterName)
   10678             : 
   10679             : {
   10680           0 :     if (STARTS_WITH_CI(pszParameterName, "long") ||
   10681           0 :         EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN))
   10682           0 :         return TRUE;
   10683             : 
   10684           0 :     return FALSE;
   10685             : }
   10686             : 
   10687             : /************************************************************************/
   10688             : /*                         IsLinearParameter()                          */
   10689             : /************************************************************************/
   10690             : 
   10691             : /** Is the passed projection parameter an linear one measured in meters or
   10692             :  * some similar linear measure.
   10693             :  *
   10694             :  * @return TRUE or FALSE
   10695             :  */
   10696             : 
   10697             : /* static */
   10698          43 : int OGRSpatialReference::IsLinearParameter(const char *pszParameterName)
   10699             : 
   10700             : {
   10701          43 :     if (STARTS_WITH_CI(pszParameterName, "false_") ||
   10702          34 :         EQUAL(pszParameterName, SRS_PP_SATELLITE_HEIGHT))
   10703           9 :         return TRUE;
   10704             : 
   10705          34 :     return FALSE;
   10706             : }
   10707             : 
   10708             : /************************************************************************/
   10709             : /*                            GetNormInfo()                             */
   10710             : /************************************************************************/
   10711             : 
   10712             : /**
   10713             :  * \brief Set the internal information for normalizing linear, and angular
   10714             :  * values.
   10715             :  */
   10716        3815 : void OGRSpatialReference::GetNormInfo() const
   10717             : 
   10718             : {
   10719        3815 :     TAKE_OPTIONAL_LOCK();
   10720             : 
   10721        3815 :     if (d->bNormInfoSet)
   10722        2696 :         return;
   10723             : 
   10724             :     /* -------------------------------------------------------------------- */
   10725             :     /*      Initialize values.                                              */
   10726             :     /* -------------------------------------------------------------------- */
   10727        1119 :     d->bNormInfoSet = TRUE;
   10728             : 
   10729        1119 :     d->dfFromGreenwich = GetPrimeMeridian(nullptr);
   10730        1119 :     d->dfToMeter = GetLinearUnits(nullptr);
   10731        1119 :     d->dfToDegrees = GetAngularUnits(nullptr) / CPLAtof(SRS_UA_DEGREE_CONV);
   10732        1119 :     if (fabs(d->dfToDegrees - 1.0) < 0.000000001)
   10733        1116 :         d->dfToDegrees = 1.0;
   10734             : }
   10735             : 
   10736             : /************************************************************************/
   10737             : /*                            GetExtension()                            */
   10738             : /************************************************************************/
   10739             : 
   10740             : /**
   10741             :  * \brief Fetch extension value.
   10742             :  *
   10743             :  * Fetch the value of the named EXTENSION item for the identified
   10744             :  * target node.
   10745             :  *
   10746             :  * @param pszTargetKey the name or path to the parent node of the EXTENSION.
   10747             :  * @param pszName the name of the extension being fetched.
   10748             :  * @param pszDefault the value to return if the extension is not found.
   10749             :  *
   10750             :  * @return node value if successful or pszDefault on failure.
   10751             :  */
   10752             : 
   10753       10807 : const char *OGRSpatialReference::GetExtension(const char *pszTargetKey,
   10754             :                                               const char *pszName,
   10755             :                                               const char *pszDefault) const
   10756             : 
   10757             : {
   10758       21614 :     TAKE_OPTIONAL_LOCK();
   10759             : 
   10760             :     /* -------------------------------------------------------------------- */
   10761             :     /*      Find the target node.                                           */
   10762             :     /* -------------------------------------------------------------------- */
   10763             :     const OGR_SRSNode *poNode =
   10764       10807 :         pszTargetKey == nullptr ? GetRoot() : GetAttrNode(pszTargetKey);
   10765             : 
   10766       10807 :     if (poNode == nullptr)
   10767        2241 :         return nullptr;
   10768             : 
   10769             :     /* -------------------------------------------------------------------- */
   10770             :     /*      Fetch matching EXTENSION if there is one.                       */
   10771             :     /* -------------------------------------------------------------------- */
   10772       63015 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10773             :     {
   10774       54471 :         const OGR_SRSNode *poChild = poNode->GetChild(i);
   10775             : 
   10776       54495 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10777          24 :             poChild->GetChildCount() >= 2)
   10778             :         {
   10779          24 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10780          22 :                 return poChild->GetChild(1)->GetValue();
   10781             :         }
   10782             :     }
   10783             : 
   10784        8544 :     return pszDefault;
   10785             : }
   10786             : 
   10787             : /************************************************************************/
   10788             : /*                            SetExtension()                            */
   10789             : /************************************************************************/
   10790             : /**
   10791             :  * \brief Set extension value.
   10792             :  *
   10793             :  * Set the value of the named EXTENSION item for the identified
   10794             :  * target node.
   10795             :  *
   10796             :  * @param pszTargetKey the name or path to the parent node of the EXTENSION.
   10797             :  * @param pszName the name of the extension being fetched.
   10798             :  * @param pszValue the value to set
   10799             :  *
   10800             :  * @return OGRERR_NONE on success
   10801             :  */
   10802             : 
   10803          20 : OGRErr OGRSpatialReference::SetExtension(const char *pszTargetKey,
   10804             :                                          const char *pszName,
   10805             :                                          const char *pszValue)
   10806             : 
   10807             : {
   10808          40 :     TAKE_OPTIONAL_LOCK();
   10809             : 
   10810             :     /* -------------------------------------------------------------------- */
   10811             :     /*      Find the target node.                                           */
   10812             :     /* -------------------------------------------------------------------- */
   10813          20 :     OGR_SRSNode *poNode = nullptr;
   10814             : 
   10815          20 :     if (pszTargetKey == nullptr)
   10816           0 :         poNode = GetRoot();
   10817             :     else
   10818          20 :         poNode = GetAttrNode(pszTargetKey);
   10819             : 
   10820          20 :     if (poNode == nullptr)
   10821           0 :         return OGRERR_FAILURE;
   10822             : 
   10823             :     /* -------------------------------------------------------------------- */
   10824             :     /*      Fetch matching EXTENSION if there is one.                       */
   10825             :     /* -------------------------------------------------------------------- */
   10826         151 :     for (int i = poNode->GetChildCount() - 1; i >= 0; i--)
   10827             :     {
   10828         137 :         OGR_SRSNode *poChild = poNode->GetChild(i);
   10829             : 
   10830         143 :         if (EQUAL(poChild->GetValue(), "EXTENSION") &&
   10831           6 :             poChild->GetChildCount() >= 2)
   10832             :         {
   10833           6 :             if (EQUAL(poChild->GetChild(0)->GetValue(), pszName))
   10834             :             {
   10835           6 :                 poChild->GetChild(1)->SetValue(pszValue);
   10836           6 :                 return OGRERR_NONE;
   10837             :             }
   10838             :         }
   10839             :     }
   10840             : 
   10841             :     /* -------------------------------------------------------------------- */
   10842             :     /*      Create a new EXTENSION node.                                    */
   10843             :     /* -------------------------------------------------------------------- */
   10844          14 :     OGR_SRSNode *poAuthNode = new OGR_SRSNode("EXTENSION");
   10845          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszName));
   10846          14 :     poAuthNode->AddChild(new OGR_SRSNode(pszValue));
   10847             : 
   10848          14 :     poNode->AddChild(poAuthNode);
   10849             : 
   10850          14 :     return OGRERR_NONE;
   10851             : }
   10852             : 
   10853             : /************************************************************************/
   10854             : /*                             OSRCleanup()                             */
   10855             : /************************************************************************/
   10856             : 
   10857             : static void CleanupSRSWGS84Mutex();
   10858             : 
   10859             : /**
   10860             :  * \brief Cleanup cached SRS related memory.
   10861             :  *
   10862             :  * This function will attempt to cleanup any cache spatial reference
   10863             :  * related information, such as cached tables of coordinate systems.
   10864             :  *
   10865             :  * This function should not be called concurrently with any other GDAL/OGR
   10866             :  * function. It is meant at being called once before process termination
   10867             :  * (typically from the main thread). CPLCleanupTLS() might be used to clean
   10868             :  * thread-specific resources before thread termination.
   10869             :  */
   10870        1126 : void OSRCleanup(void)
   10871             : 
   10872             : {
   10873        1126 :     OGRCTDumpStatistics();
   10874        1126 :     CSVDeaccess(nullptr);
   10875        1126 :     CleanupSRSWGS84Mutex();
   10876        1126 :     OSRCTCleanCache();
   10877        1126 :     OSRCleanupTLSContext();
   10878        1126 : }
   10879             : 
   10880             : /************************************************************************/
   10881             : /*                              GetAxesCount()                          */
   10882             : /************************************************************************/
   10883             : 
   10884             : /**
   10885             :  * \brief Return the number of axis of the coordinate system of the CRS.
   10886             :  *
   10887             :  * @since GDAL 3.0
   10888             :  */
   10889       36433 : int OGRSpatialReference::GetAxesCount() const
   10890             : {
   10891       72866 :     TAKE_OPTIONAL_LOCK();
   10892             : 
   10893       36433 :     int axisCount = 0;
   10894       36433 :     d->refreshProjObj();
   10895       36433 :     if (d->m_pj_crs == nullptr)
   10896             :     {
   10897           0 :         return 0;
   10898             :     }
   10899       36433 :     d->demoteFromBoundCRS();
   10900       36433 :     auto ctxt = d->getPROJContext();
   10901       36433 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   10902             :     {
   10903          29 :         for (int i = 0;; i++)
   10904             :         {
   10905          87 :             auto subCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, i);
   10906          87 :             if (!subCRS)
   10907          29 :                 break;
   10908          58 :             if (proj_get_type(subCRS) == PJ_TYPE_BOUND_CRS)
   10909             :             {
   10910          17 :                 auto baseCRS = proj_get_source_crs(ctxt, subCRS);
   10911          17 :                 if (baseCRS)
   10912             :                 {
   10913          17 :                     proj_destroy(subCRS);
   10914          17 :                     subCRS = baseCRS;
   10915             :                 }
   10916             :             }
   10917          58 :             auto cs = proj_crs_get_coordinate_system(ctxt, subCRS);
   10918          58 :             if (cs)
   10919             :             {
   10920          58 :                 axisCount += proj_cs_get_axis_count(ctxt, cs);
   10921          58 :                 proj_destroy(cs);
   10922             :             }
   10923          58 :             proj_destroy(subCRS);
   10924          58 :         }
   10925             :     }
   10926             :     else
   10927             :     {
   10928       36404 :         auto cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   10929       36404 :         if (cs)
   10930             :         {
   10931       36404 :             axisCount = proj_cs_get_axis_count(ctxt, cs);
   10932       36404 :             proj_destroy(cs);
   10933             :         }
   10934             :     }
   10935       36433 :     d->undoDemoteFromBoundCRS();
   10936       36433 :     return axisCount;
   10937             : }
   10938             : 
   10939             : /************************************************************************/
   10940             : /*                           OSRGetAxesCount()                          */
   10941             : /************************************************************************/
   10942             : 
   10943             : /**
   10944             :  * \brief Return the number of axis of the coordinate system of the CRS.
   10945             :  *
   10946             :  * This method is the equivalent of the C++ method
   10947             :  * OGRSpatialReference::GetAxesCount()
   10948             :  *
   10949             :  * @since GDAL 3.1
   10950             :  */
   10951           6 : int OSRGetAxesCount(OGRSpatialReferenceH hSRS)
   10952             : 
   10953             : {
   10954           6 :     VALIDATE_POINTER1(hSRS, "OSRGetAxesCount", 0);
   10955             : 
   10956           6 :     return ToPointer(hSRS)->GetAxesCount();
   10957             : }
   10958             : 
   10959             : /************************************************************************/
   10960             : /*                              GetAxis()                               */
   10961             : /************************************************************************/
   10962             : 
   10963             : /**
   10964             :  * \brief Fetch the orientation of one axis.
   10965             :  *
   10966             :  * Fetches the request axis (iAxis - zero based) from the
   10967             :  * indicated portion of the coordinate system (pszTargetKey) which
   10968             :  * should be either "GEOGCS" or "PROJCS".
   10969             :  *
   10970             :  * No CPLError is issued on routine failures (such as not finding the AXIS).
   10971             :  *
   10972             :  * This method is equivalent to the C function OSRGetAxis().
   10973             :  *
   10974             :  * @param pszTargetKey the coordinate system part to query ("PROJCS" or
   10975             :  * "GEOGCS").
   10976             :  * @param iAxis the axis to query (0 for first, 1 for second, 2 for third).
   10977             :  * @param peOrientation location into which to place the fetch orientation, may
   10978             :  * be NULL.
   10979             :  * @param pdfConvUnit (GDAL >= 3.4) Location into which to place axis conversion
   10980             :  * factor. May be NULL. Only set if pszTargetKey == NULL
   10981             :  *
   10982             :  * @return the name of the axis or NULL on failure.
   10983             :  */
   10984             : 
   10985        6870 : const char *OGRSpatialReference::GetAxis(const char *pszTargetKey, int iAxis,
   10986             :                                          OGRAxisOrientation *peOrientation,
   10987             :                                          double *pdfConvUnit) const
   10988             : 
   10989             : {
   10990       13740 :     TAKE_OPTIONAL_LOCK();
   10991             : 
   10992        6870 :     if (peOrientation != nullptr)
   10993        6777 :         *peOrientation = OAO_Other;
   10994        6870 :     if (pdfConvUnit != nullptr)
   10995          85 :         *pdfConvUnit = 0;
   10996             : 
   10997        6870 :     d->refreshProjObj();
   10998        6870 :     if (d->m_pj_crs == nullptr)
   10999             :     {
   11000           3 :         return nullptr;
   11001             :     }
   11002             : 
   11003        6867 :     pszTargetKey = d->nullifyTargetKeyIfPossible(pszTargetKey);
   11004        6867 :     if (pszTargetKey == nullptr && iAxis <= 2)
   11005             :     {
   11006        6867 :         auto ctxt = d->getPROJContext();
   11007             : 
   11008        6867 :         int iAxisModified = iAxis;
   11009             : 
   11010        6867 :         d->demoteFromBoundCRS();
   11011             : 
   11012        6867 :         PJ *cs = nullptr;
   11013        6867 :         if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   11014             :         {
   11015         134 :             auto horizCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   11016         134 :             if (horizCRS)
   11017             :             {
   11018         134 :                 if (proj_get_type(horizCRS) == PJ_TYPE_BOUND_CRS)
   11019             :                 {
   11020           6 :                     auto baseCRS = proj_get_source_crs(ctxt, horizCRS);
   11021           6 :                     if (baseCRS)
   11022             :                     {
   11023           6 :                         proj_destroy(horizCRS);
   11024           6 :                         horizCRS = baseCRS;
   11025             :                     }
   11026             :                 }
   11027         134 :                 cs = proj_crs_get_coordinate_system(ctxt, horizCRS);
   11028         134 :                 proj_destroy(horizCRS);
   11029         134 :                 if (cs)
   11030             :                 {
   11031         134 :                     if (iAxisModified >= proj_cs_get_axis_count(ctxt, cs))
   11032             :                     {
   11033          44 :                         iAxisModified -= proj_cs_get_axis_count(ctxt, cs);
   11034          44 :                         proj_destroy(cs);
   11035          44 :                         cs = nullptr;
   11036             :                     }
   11037             :                 }
   11038             :             }
   11039             : 
   11040         134 :             if (cs == nullptr)
   11041             :             {
   11042          44 :                 auto vertCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 1);
   11043          44 :                 if (vertCRS)
   11044             :                 {
   11045          44 :                     if (proj_get_type(vertCRS) == PJ_TYPE_BOUND_CRS)
   11046             :                     {
   11047          30 :                         auto baseCRS = proj_get_source_crs(ctxt, vertCRS);
   11048          30 :                         if (baseCRS)
   11049             :                         {
   11050          30 :                             proj_destroy(vertCRS);
   11051          30 :                             vertCRS = baseCRS;
   11052             :                         }
   11053             :                     }
   11054             : 
   11055          44 :                     cs = proj_crs_get_coordinate_system(ctxt, vertCRS);
   11056          44 :                     proj_destroy(vertCRS);
   11057             :                 }
   11058             :             }
   11059             :         }
   11060             :         else
   11061             :         {
   11062        6733 :             cs = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   11063             :         }
   11064             : 
   11065        6867 :         if (cs)
   11066             :         {
   11067        6867 :             const char *pszName = nullptr;
   11068        6867 :             const char *pszOrientation = nullptr;
   11069        6867 :             double dfConvFactor = 0.0;
   11070        6867 :             proj_cs_get_axis_info(ctxt, cs, iAxisModified, &pszName, nullptr,
   11071             :                                   &pszOrientation, &dfConvFactor, nullptr,
   11072             :                                   nullptr, nullptr);
   11073             : 
   11074        6867 :             if (pdfConvUnit != nullptr)
   11075             :             {
   11076          85 :                 *pdfConvUnit = dfConvFactor;
   11077             :             }
   11078             : 
   11079        6867 :             if (pszName && pszOrientation)
   11080             :             {
   11081        6867 :                 d->m_osAxisName[iAxis] = pszName;
   11082        6867 :                 if (peOrientation)
   11083             :                 {
   11084        6774 :                     if (EQUAL(pszOrientation, "NORTH"))
   11085        4278 :                         *peOrientation = OAO_North;
   11086        2496 :                     else if (EQUAL(pszOrientation, "EAST"))
   11087        2453 :                         *peOrientation = OAO_East;
   11088          43 :                     else if (EQUAL(pszOrientation, "SOUTH"))
   11089          32 :                         *peOrientation = OAO_South;
   11090          11 :                     else if (EQUAL(pszOrientation, "WEST"))
   11091           0 :                         *peOrientation = OAO_West;
   11092          11 :                     else if (EQUAL(pszOrientation, "UP"))
   11093           1 :                         *peOrientation = OAO_Up;
   11094          10 :                     else if (EQUAL(pszOrientation, "DOWN"))
   11095           0 :                         *peOrientation = OAO_Down;
   11096             :                 }
   11097        6867 :                 proj_destroy(cs);
   11098        6867 :                 d->undoDemoteFromBoundCRS();
   11099        6867 :                 return d->m_osAxisName[iAxis].c_str();
   11100             :             }
   11101           0 :             proj_destroy(cs);
   11102             :         }
   11103           0 :         d->undoDemoteFromBoundCRS();
   11104             :     }
   11105             : 
   11106             :     /* -------------------------------------------------------------------- */
   11107             :     /*      Find the target node.                                           */
   11108             :     /* -------------------------------------------------------------------- */
   11109           0 :     const OGR_SRSNode *poNode = nullptr;
   11110             : 
   11111           0 :     if (pszTargetKey == nullptr)
   11112           0 :         poNode = GetRoot();
   11113             :     else
   11114           0 :         poNode = GetAttrNode(pszTargetKey);
   11115             : 
   11116           0 :     if (poNode == nullptr)
   11117           0 :         return nullptr;
   11118             : 
   11119             :     /* -------------------------------------------------------------------- */
   11120             :     /*      Find desired child AXIS.                                        */
   11121             :     /* -------------------------------------------------------------------- */
   11122           0 :     const OGR_SRSNode *poAxis = nullptr;
   11123           0 :     const int nChildCount = poNode->GetChildCount();
   11124             : 
   11125           0 :     for (int iChild = 0; iChild < nChildCount; iChild++)
   11126             :     {
   11127           0 :         const OGR_SRSNode *poChild = poNode->GetChild(iChild);
   11128             : 
   11129           0 :         if (!EQUAL(poChild->GetValue(), "AXIS"))
   11130           0 :             continue;
   11131             : 
   11132           0 :         if (iAxis == 0)
   11133             :         {
   11134           0 :             poAxis = poChild;
   11135           0 :             break;
   11136             :         }
   11137           0 :         iAxis--;
   11138             :     }
   11139             : 
   11140           0 :     if (poAxis == nullptr)
   11141           0 :         return nullptr;
   11142             : 
   11143           0 :     if (poAxis->GetChildCount() < 2)
   11144           0 :         return nullptr;
   11145             : 
   11146             :     /* -------------------------------------------------------------------- */
   11147             :     /*      Extract name and orientation if possible.                       */
   11148             :     /* -------------------------------------------------------------------- */
   11149           0 :     if (peOrientation != nullptr)
   11150             :     {
   11151           0 :         const char *pszOrientation = poAxis->GetChild(1)->GetValue();
   11152             : 
   11153           0 :         if (EQUAL(pszOrientation, "NORTH"))
   11154           0 :             *peOrientation = OAO_North;
   11155           0 :         else if (EQUAL(pszOrientation, "EAST"))
   11156           0 :             *peOrientation = OAO_East;
   11157           0 :         else if (EQUAL(pszOrientation, "SOUTH"))
   11158           0 :             *peOrientation = OAO_South;
   11159           0 :         else if (EQUAL(pszOrientation, "WEST"))
   11160           0 :             *peOrientation = OAO_West;
   11161           0 :         else if (EQUAL(pszOrientation, "UP"))
   11162           0 :             *peOrientation = OAO_Up;
   11163           0 :         else if (EQUAL(pszOrientation, "DOWN"))
   11164           0 :             *peOrientation = OAO_Down;
   11165           0 :         else if (EQUAL(pszOrientation, "OTHER"))
   11166           0 :             *peOrientation = OAO_Other;
   11167             :         else
   11168             :         {
   11169           0 :             CPLDebug("OSR", "Unrecognized orientation value '%s'.",
   11170             :                      pszOrientation);
   11171             :         }
   11172             :     }
   11173             : 
   11174           0 :     return poAxis->GetChild(0)->GetValue();
   11175             : }
   11176             : 
   11177             : /************************************************************************/
   11178             : /*                             OSRGetAxis()                             */
   11179             : /************************************************************************/
   11180             : 
   11181             : /**
   11182             :  * \brief Fetch the orientation of one axis.
   11183             :  *
   11184             :  * This method is the equivalent of the C++ method OGRSpatialReference::GetAxis
   11185             :  */
   11186          13 : const char *OSRGetAxis(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11187             :                        int iAxis, OGRAxisOrientation *peOrientation)
   11188             : 
   11189             : {
   11190          13 :     VALIDATE_POINTER1(hSRS, "OSRGetAxis", nullptr);
   11191             : 
   11192          13 :     return ToPointer(hSRS)->GetAxis(pszTargetKey, iAxis, peOrientation);
   11193             : }
   11194             : 
   11195             : /************************************************************************/
   11196             : /*                         OSRAxisEnumToName()                          */
   11197             : /************************************************************************/
   11198             : 
   11199             : /**
   11200             :  * \brief Return the string representation for the OGRAxisOrientation
   11201             :  * enumeration.
   11202             :  *
   11203             :  * For example "NORTH" for OAO_North.
   11204             :  *
   11205             :  * @return an internal string
   11206             :  */
   11207         396 : const char *OSRAxisEnumToName(OGRAxisOrientation eOrientation)
   11208             : 
   11209             : {
   11210         396 :     if (eOrientation == OAO_North)
   11211         198 :         return "NORTH";
   11212         198 :     if (eOrientation == OAO_East)
   11213         198 :         return "EAST";
   11214           0 :     if (eOrientation == OAO_South)
   11215           0 :         return "SOUTH";
   11216           0 :     if (eOrientation == OAO_West)
   11217           0 :         return "WEST";
   11218           0 :     if (eOrientation == OAO_Up)
   11219           0 :         return "UP";
   11220           0 :     if (eOrientation == OAO_Down)
   11221           0 :         return "DOWN";
   11222           0 :     if (eOrientation == OAO_Other)
   11223           0 :         return "OTHER";
   11224             : 
   11225           0 :     return "UNKNOWN";
   11226             : }
   11227             : 
   11228             : /************************************************************************/
   11229             : /*                              SetAxes()                               */
   11230             : /************************************************************************/
   11231             : 
   11232             : /**
   11233             :  * \brief Set the axes for a coordinate system.
   11234             :  *
   11235             :  * Set the names, and orientations of the axes for either a projected
   11236             :  * (PROJCS) or geographic (GEOGCS) coordinate system.
   11237             :  *
   11238             :  * This method is equivalent to the C function OSRSetAxes().
   11239             :  *
   11240             :  * @param pszTargetKey either "PROJCS" or "GEOGCS", must already exist in SRS.
   11241             :  * @param pszXAxisName name of first axis, normally "Long" or "Easting".
   11242             :  * @param eXAxisOrientation normally OAO_East.
   11243             :  * @param pszYAxisName name of second axis, normally "Lat" or "Northing".
   11244             :  * @param eYAxisOrientation normally OAO_North.
   11245             :  *
   11246             :  * @return OGRERR_NONE on success or an error code.
   11247             :  */
   11248             : 
   11249         198 : OGRErr OGRSpatialReference::SetAxes(const char *pszTargetKey,
   11250             :                                     const char *pszXAxisName,
   11251             :                                     OGRAxisOrientation eXAxisOrientation,
   11252             :                                     const char *pszYAxisName,
   11253             :                                     OGRAxisOrientation eYAxisOrientation)
   11254             : 
   11255             : {
   11256         396 :     TAKE_OPTIONAL_LOCK();
   11257             : 
   11258             :     /* -------------------------------------------------------------------- */
   11259             :     /*      Find the target node.                                           */
   11260             :     /* -------------------------------------------------------------------- */
   11261         198 :     OGR_SRSNode *poNode = nullptr;
   11262             : 
   11263         198 :     if (pszTargetKey == nullptr)
   11264         198 :         poNode = GetRoot();
   11265             :     else
   11266           0 :         poNode = GetAttrNode(pszTargetKey);
   11267             : 
   11268         198 :     if (poNode == nullptr)
   11269           0 :         return OGRERR_FAILURE;
   11270             : 
   11271             :     /* -------------------------------------------------------------------- */
   11272             :     /*      Strip any existing AXIS children.                               */
   11273             :     /* -------------------------------------------------------------------- */
   11274         594 :     while (poNode->FindChild("AXIS") >= 0)
   11275         396 :         poNode->DestroyChild(poNode->FindChild("AXIS"));
   11276             : 
   11277             :     /* -------------------------------------------------------------------- */
   11278             :     /*      Insert desired axes                                             */
   11279             :     /* -------------------------------------------------------------------- */
   11280         198 :     OGR_SRSNode *poAxis = new OGR_SRSNode("AXIS");
   11281             : 
   11282         198 :     poAxis->AddChild(new OGR_SRSNode(pszXAxisName));
   11283         198 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eXAxisOrientation)));
   11284             : 
   11285         198 :     poNode->AddChild(poAxis);
   11286             : 
   11287         198 :     poAxis = new OGR_SRSNode("AXIS");
   11288             : 
   11289         198 :     poAxis->AddChild(new OGR_SRSNode(pszYAxisName));
   11290         198 :     poAxis->AddChild(new OGR_SRSNode(OSRAxisEnumToName(eYAxisOrientation)));
   11291             : 
   11292         198 :     poNode->AddChild(poAxis);
   11293             : 
   11294         198 :     return OGRERR_NONE;
   11295             : }
   11296             : 
   11297             : /************************************************************************/
   11298             : /*                             OSRSetAxes()                             */
   11299             : /************************************************************************/
   11300             : /**
   11301             :  * \brief Set the axes for a coordinate system.
   11302             :  *
   11303             :  * This method is the equivalent of the C++ method OGRSpatialReference::SetAxes
   11304             :  */
   11305           0 : OGRErr OSRSetAxes(OGRSpatialReferenceH hSRS, const char *pszTargetKey,
   11306             :                   const char *pszXAxisName,
   11307             :                   OGRAxisOrientation eXAxisOrientation,
   11308             :                   const char *pszYAxisName,
   11309             :                   OGRAxisOrientation eYAxisOrientation)
   11310             : {
   11311           0 :     VALIDATE_POINTER1(hSRS, "OSRSetAxes", OGRERR_FAILURE);
   11312             : 
   11313           0 :     return ToPointer(hSRS)->SetAxes(pszTargetKey, pszXAxisName,
   11314             :                                     eXAxisOrientation, pszYAxisName,
   11315           0 :                                     eYAxisOrientation);
   11316             : }
   11317             : 
   11318             : /************************************************************************/
   11319             : /*                       OSRExportToMICoordSys()                        */
   11320             : /************************************************************************/
   11321             : /**
   11322             :  * \brief Export coordinate system in Mapinfo style CoordSys format.
   11323             :  *
   11324             :  * This method is the equivalent of the C++ method
   11325             :  * OGRSpatialReference::exportToMICoordSys
   11326             :  */
   11327           5 : OGRErr OSRExportToMICoordSys(OGRSpatialReferenceH hSRS, char **ppszReturn)
   11328             : 
   11329             : {
   11330           5 :     VALIDATE_POINTER1(hSRS, "OSRExportToMICoordSys", OGRERR_FAILURE);
   11331             : 
   11332           5 :     *ppszReturn = nullptr;
   11333             : 
   11334           5 :     return ToPointer(hSRS)->exportToMICoordSys(ppszReturn);
   11335             : }
   11336             : 
   11337             : /************************************************************************/
   11338             : /*                         exportToMICoordSys()                         */
   11339             : /************************************************************************/
   11340             : 
   11341             : /**
   11342             :  * \brief Export coordinate system in Mapinfo style CoordSys format.
   11343             :  *
   11344             :  * Note that the returned WKT string should be freed with
   11345             :  * CPLFree() when no longer needed.  It is the responsibility of the caller.
   11346             :  *
   11347             :  * This method is the same as the C function OSRExportToMICoordSys().
   11348             :  *
   11349             :  * @param ppszResult pointer to which dynamically allocated Mapinfo CoordSys
   11350             :  * definition will be assigned.
   11351             :  *
   11352             :  * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
   11353             :  * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
   11354             :  */
   11355             : 
   11356           7 : OGRErr OGRSpatialReference::exportToMICoordSys(char **ppszResult) const
   11357             : 
   11358             : {
   11359           7 :     *ppszResult = MITABSpatialRef2CoordSys(this);
   11360           7 :     if (*ppszResult != nullptr && strlen(*ppszResult) > 0)
   11361           7 :         return OGRERR_NONE;
   11362             : 
   11363           0 :     return OGRERR_FAILURE;
   11364             : }
   11365             : 
   11366             : /************************************************************************/
   11367             : /*                       OSRImportFromMICoordSys()                      */
   11368             : /************************************************************************/
   11369             : /**
   11370             :  * \brief Import Mapinfo style CoordSys definition.
   11371             :  *
   11372             :  * This method is the equivalent of the C++ method
   11373             :  * OGRSpatialReference::importFromMICoordSys
   11374             :  */
   11375             : 
   11376           3 : OGRErr OSRImportFromMICoordSys(OGRSpatialReferenceH hSRS,
   11377             :                                const char *pszCoordSys)
   11378             : 
   11379             : {
   11380           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromMICoordSys", OGRERR_FAILURE);
   11381             : 
   11382           3 :     return ToPointer(hSRS)->importFromMICoordSys(pszCoordSys);
   11383             : }
   11384             : 
   11385             : /************************************************************************/
   11386             : /*                        importFromMICoordSys()                        */
   11387             : /************************************************************************/
   11388             : 
   11389             : /**
   11390             :  * \brief Import Mapinfo style CoordSys definition.
   11391             :  *
   11392             :  * The OGRSpatialReference is initialized from the passed Mapinfo style CoordSys
   11393             :  * definition string.
   11394             :  *
   11395             :  * This method is the equivalent of the C function OSRImportFromMICoordSys().
   11396             :  *
   11397             :  * @param pszCoordSys Mapinfo style CoordSys definition string.
   11398             :  *
   11399             :  * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
   11400             :  * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
   11401             :  */
   11402             : 
   11403          17 : OGRErr OGRSpatialReference::importFromMICoordSys(const char *pszCoordSys)
   11404             : 
   11405             : {
   11406          17 :     OGRSpatialReference *poResult = MITABCoordSys2SpatialRef(pszCoordSys);
   11407             : 
   11408          17 :     if (poResult == nullptr)
   11409           0 :         return OGRERR_FAILURE;
   11410             : 
   11411          17 :     *this = *poResult;
   11412          17 :     delete poResult;
   11413             : 
   11414          17 :     return OGRERR_NONE;
   11415             : }
   11416             : 
   11417             : /************************************************************************/
   11418             : /*                        OSRCalcInvFlattening()                        */
   11419             : /************************************************************************/
   11420             : 
   11421             : /**
   11422             :  * \brief Compute inverse flattening from semi-major and semi-minor axis
   11423             :  *
   11424             :  * @param dfSemiMajor Semi-major axis length.
   11425             :  * @param dfSemiMinor Semi-minor axis length.
   11426             :  *
   11427             :  * @return inverse flattening, or 0 if both axis are equal.
   11428             :  * @since GDAL 2.0
   11429             :  */
   11430             : 
   11431        8319 : double OSRCalcInvFlattening(double dfSemiMajor, double dfSemiMinor)
   11432             : {
   11433        8319 :     if (fabs(dfSemiMajor - dfSemiMinor) < 1e-1)
   11434          27 :         return 0;
   11435        8292 :     if (dfSemiMajor <= 0 || dfSemiMinor <= 0 || dfSemiMinor > dfSemiMajor)
   11436             :     {
   11437           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11438             :                  "OSRCalcInvFlattening(): Wrong input values");
   11439           0 :         return 0;
   11440             :     }
   11441             : 
   11442        8292 :     return dfSemiMajor / (dfSemiMajor - dfSemiMinor);
   11443             : }
   11444             : 
   11445             : /************************************************************************/
   11446             : /*                        OSRCalcInvFlattening()                        */
   11447             : /************************************************************************/
   11448             : 
   11449             : /**
   11450             :  * \brief Compute semi-minor axis from semi-major axis and inverse flattening.
   11451             :  *
   11452             :  * @param dfSemiMajor Semi-major axis length.
   11453             :  * @param dfInvFlattening Inverse flattening or 0 for sphere.
   11454             :  *
   11455             :  * @return semi-minor axis
   11456             :  * @since GDAL 2.0
   11457             :  */
   11458             : 
   11459         655 : double OSRCalcSemiMinorFromInvFlattening(double dfSemiMajor,
   11460             :                                          double dfInvFlattening)
   11461             : {
   11462         655 :     if (fabs(dfInvFlattening) < 0.000000000001)
   11463         103 :         return dfSemiMajor;
   11464         552 :     if (dfSemiMajor <= 0.0 || dfInvFlattening <= 1.0)
   11465             :     {
   11466           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11467             :                  "OSRCalcSemiMinorFromInvFlattening(): Wrong input values");
   11468           0 :         return dfSemiMajor;
   11469             :     }
   11470             : 
   11471         552 :     return dfSemiMajor * (1.0 - 1.0 / dfInvFlattening);
   11472             : }
   11473             : 
   11474             : /************************************************************************/
   11475             : /*                        GetWGS84SRS()                                 */
   11476             : /************************************************************************/
   11477             : 
   11478             : static OGRSpatialReference *poSRSWGS84 = nullptr;
   11479             : static CPLMutex *hMutex = nullptr;
   11480             : 
   11481             : /**
   11482             :  * \brief Returns an instance of a SRS object with WGS84 WKT.
   11483             :  *
   11484             :  * Note: the instance will have
   11485             :  * SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
   11486             :  *
   11487             :  * The reference counter of the returned object is not increased by this
   11488             :  * operation.
   11489             :  *
   11490             :  * @return instance.
   11491             :  * @since GDAL 2.0
   11492             :  */
   11493             : 
   11494         843 : OGRSpatialReference *OGRSpatialReference::GetWGS84SRS()
   11495             : {
   11496         843 :     CPLMutexHolderD(&hMutex);
   11497         843 :     if (poSRSWGS84 == nullptr)
   11498             :     {
   11499           4 :         poSRSWGS84 = new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
   11500           4 :         poSRSWGS84->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
   11501             :     }
   11502        1686 :     return poSRSWGS84;
   11503             : }
   11504             : 
   11505             : /************************************************************************/
   11506             : /*                        CleanupSRSWGS84Mutex()                       */
   11507             : /************************************************************************/
   11508             : 
   11509        1126 : static void CleanupSRSWGS84Mutex()
   11510             : {
   11511        1126 :     if (hMutex != nullptr)
   11512             :     {
   11513           2 :         poSRSWGS84->Release();
   11514           2 :         poSRSWGS84 = nullptr;
   11515           2 :         CPLDestroyMutex(hMutex);
   11516           2 :         hMutex = nullptr;
   11517             :     }
   11518        1126 : }
   11519             : 
   11520             : /************************************************************************/
   11521             : /*                         OSRImportFromProj4()                         */
   11522             : /************************************************************************/
   11523             : /**
   11524             :  * \brief Import PROJ coordinate string.
   11525             :  *
   11526             :  * This function is the same as OGRSpatialReference::importFromProj4().
   11527             :  */
   11528         186 : OGRErr OSRImportFromProj4(OGRSpatialReferenceH hSRS, const char *pszProj4)
   11529             : 
   11530             : {
   11531         186 :     VALIDATE_POINTER1(hSRS, "OSRImportFromProj4", OGRERR_FAILURE);
   11532             : 
   11533         186 :     return OGRSpatialReference::FromHandle(hSRS)->importFromProj4(pszProj4);
   11534             : }
   11535             : 
   11536             : /************************************************************************/
   11537             : /*                          importFromProj4()                           */
   11538             : /************************************************************************/
   11539             : 
   11540             : /**
   11541             :  * \brief Import PROJ coordinate string.
   11542             :  *
   11543             :  * The OGRSpatialReference is initialized from the passed PROJs style
   11544             :  * coordinate system string.
   11545             :  *
   11546             :  * Example:
   11547             :  *   pszProj4 = "+proj=utm +zone=11 +datum=WGS84"
   11548             :  *
   11549             :  * It is also possible to import "+init=epsg:n" style definitions. Those are
   11550             :  * a legacy syntax that should be avoided in the future. In particular they will
   11551             :  * result in CRS objects whose axis order might not correspond to the official
   11552             :  * EPSG axis order.
   11553             :  *
   11554             :  * This method is the equivalent of the C function OSRImportFromProj4().
   11555             :  *
   11556             :  * @param pszProj4 the PROJ style string.
   11557             :  *
   11558             :  * @return OGRERR_NONE on success or OGRERR_CORRUPT_DATA on failure.
   11559             :  */
   11560             : 
   11561         716 : OGRErr OGRSpatialReference::importFromProj4(const char *pszProj4)
   11562             : 
   11563             : {
   11564        1432 :     TAKE_OPTIONAL_LOCK();
   11565             : 
   11566         716 :     if (strlen(pszProj4) >= 10000)
   11567             :     {
   11568           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Too long PROJ string");
   11569           1 :         return OGRERR_CORRUPT_DATA;
   11570             :     }
   11571             : 
   11572             :     /* -------------------------------------------------------------------- */
   11573             :     /*      Clear any existing definition.                                  */
   11574             :     /* -------------------------------------------------------------------- */
   11575         715 :     Clear();
   11576             : 
   11577         715 :     CPLString osProj4(pszProj4);
   11578         715 :     if (osProj4.find("type=crs") == std::string::npos)
   11579             :     {
   11580         706 :         osProj4 += " +type=crs";
   11581             :     }
   11582             : 
   11583         717 :     if (osProj4.find("+init=epsg:") != std::string::npos &&
   11584           2 :         getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr)
   11585             :     {
   11586           2 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11587             :                      "+init=epsg:XXXX syntax is deprecated. It might return "
   11588             :                      "a CRS with a non-EPSG compliant axis order.");
   11589             :     }
   11590         715 :     proj_context_use_proj4_init_rules(d->getPROJContext(), true);
   11591         715 :     d->setPjCRS(proj_create(d->getPROJContext(), osProj4.c_str()));
   11592         715 :     proj_context_use_proj4_init_rules(d->getPROJContext(), false);
   11593         715 :     return d->m_pj_crs ? OGRERR_NONE : OGRERR_CORRUPT_DATA;
   11594             : }
   11595             : 
   11596             : /************************************************************************/
   11597             : /*                          OSRExportToProj4()                          */
   11598             : /************************************************************************/
   11599             : /**
   11600             :  * \brief Export coordinate system in PROJ.4 legacy format.
   11601             :  *
   11602             :  * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
   11603             :  * PROJ &gt;= 6 is significantly different from earlier versions. In particular
   11604             :  * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
   11605             :  * will be missing most of the time. PROJ strings to encode CRS should be
   11606             :  * considered as a legacy solution. Using a AUTHORITY:CODE or WKT representation
   11607             :  * is the recommended way.
   11608             :  *
   11609             :  * This function is the same as OGRSpatialReference::exportToProj4().
   11610             :  */
   11611         427 : OGRErr CPL_STDCALL OSRExportToProj4(OGRSpatialReferenceH hSRS,
   11612             :                                     char **ppszReturn)
   11613             : 
   11614             : {
   11615         427 :     VALIDATE_POINTER1(hSRS, "OSRExportToProj4", OGRERR_FAILURE);
   11616             : 
   11617         427 :     *ppszReturn = nullptr;
   11618             : 
   11619         427 :     return OGRSpatialReference::FromHandle(hSRS)->exportToProj4(ppszReturn);
   11620             : }
   11621             : 
   11622             : /************************************************************************/
   11623             : /*                           exportToProj4()                            */
   11624             : /************************************************************************/
   11625             : 
   11626             : /**
   11627             :  * \brief Export coordinate system in PROJ.4 legacy format.
   11628             :  *
   11629             :  * \warning Use of this function is discouraged. Its behavior in GDAL &gt;= 3 /
   11630             :  * PROJ &gt;= 6 is significantly different from earlier versions. In particular
   11631             :  * +datum will only encode WGS84, NAD27 and NAD83, and +towgs84/+nadgrids terms
   11632             :  * will be missing most of the time. PROJ strings to encode CRS should be
   11633             :  * considered as a a legacy solution. Using a AUTHORITY:CODE or WKT
   11634             :  * representation is the recommended way.
   11635             :  *
   11636             :  * Converts the loaded coordinate reference system into PROJ format
   11637             :  * to the extent possible.  The string returned in ppszProj4 should be
   11638             :  * deallocated by the caller with CPLFree() when no longer needed.
   11639             :  *
   11640             :  * LOCAL_CS coordinate systems are not translatable.  An empty string
   11641             :  * will be returned along with OGRERR_NONE.
   11642             :  *
   11643             :  * Special processing for Transverse Mercator:
   11644             :  * Starting with GDAL 3.0, if the OSR_USE_APPROX_TMERC configuration option is
   11645             :  * set to YES, the PROJ definition built from the SRS will use the +approx flag
   11646             :  * for the tmerc and utm projection methods, rather than the more accurate
   11647             :  * method.
   11648             :  *
   11649             :  * Starting with GDAL 3.0.3, this method will try to add a +towgs84 parameter,
   11650             :  * if there's none attached yet to the SRS and if the SRS has a EPSG code.
   11651             :  * See the AddGuessedTOWGS84() method for how this +towgs84 parameter may be
   11652             :  * added. This automatic addition may be disabled by setting the
   11653             :  * OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4 configuration option to NO.
   11654             :  *
   11655             :  * This method is the equivalent of the C function OSRExportToProj4().
   11656             :  *
   11657             :  * @param ppszProj4 pointer to which dynamically allocated PROJ definition
   11658             :  * will be assigned.
   11659             :  *
   11660             :  * @return OGRERR_NONE on success or an error code on failure.
   11661             :  */
   11662             : 
   11663        1393 : OGRErr OGRSpatialReference::exportToProj4(char **ppszProj4) const
   11664             : 
   11665             : {
   11666             :     // In the past calling this method was thread-safe, even if we never
   11667             :     // guaranteed it. Now proj_as_proj_string() will cache the result
   11668             :     // internally, so this is no longer thread-safe.
   11669        2786 :     std::lock_guard oLock(d->m_mutex);
   11670             : 
   11671        1393 :     d->refreshProjObj();
   11672        1393 :     if (d->m_pj_crs == nullptr || d->m_pjType == PJ_TYPE_ENGINEERING_CRS)
   11673             :     {
   11674           4 :         *ppszProj4 = CPLStrdup("");
   11675           4 :         return OGRERR_FAILURE;
   11676             :     }
   11677             : 
   11678             :     // OSR_USE_ETMERC is here just for legacy
   11679        1389 :     bool bForceApproxTMerc = false;
   11680        1389 :     const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr);
   11681        1389 :     if (pszUseETMERC && pszUseETMERC[0])
   11682             :     {
   11683           0 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
   11684             :                      "OSR_USE_ETMERC is a legacy configuration option, which "
   11685             :                      "now has only effect when set to NO (YES is the default). "
   11686             :                      "Use OSR_USE_APPROX_TMERC=YES instead");
   11687           0 :         bForceApproxTMerc = !CPLTestBool(pszUseETMERC);
   11688             :     }
   11689             :     else
   11690             :     {
   11691             :         const char *pszUseApproxTMERC =
   11692        1389 :             CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr);
   11693        1389 :         if (pszUseApproxTMERC && pszUseApproxTMERC[0])
   11694             :         {
   11695           2 :             bForceApproxTMerc = CPLTestBool(pszUseApproxTMERC);
   11696             :         }
   11697             :     }
   11698        1389 :     const char *options[] = {
   11699        1389 :         bForceApproxTMerc ? "USE_APPROX_TMERC=YES" : nullptr, nullptr};
   11700             : 
   11701        1389 :     const char *projString = proj_as_proj_string(
   11702        1389 :         d->getPROJContext(), d->m_pj_crs, PJ_PROJ_4, options);
   11703             : 
   11704        1389 :     PJ *boundCRS = nullptr;
   11705        2774 :     if (projString &&
   11706        1385 :         (strstr(projString, "+datum=") == nullptr ||
   11707        2784 :          d->m_pjType == PJ_TYPE_COMPOUND_CRS) &&
   11708         555 :         CPLTestBool(
   11709             :             CPLGetConfigOption("OSR_ADD_TOWGS84_ON_EXPORT_TO_PROJ4", "YES")))
   11710             :     {
   11711         555 :         boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   11712         555 :             d->getPROJContext(), d->m_pj_crs, true,
   11713         555 :             strstr(projString, "+datum=") == nullptr);
   11714         555 :         if (boundCRS)
   11715             :         {
   11716         227 :             projString = proj_as_proj_string(d->getPROJContext(), boundCRS,
   11717             :                                              PJ_PROJ_4, options);
   11718             :         }
   11719             :     }
   11720             : 
   11721        1389 :     if (projString == nullptr)
   11722             :     {
   11723           4 :         *ppszProj4 = CPLStrdup("");
   11724           4 :         proj_destroy(boundCRS);
   11725           4 :         return OGRERR_FAILURE;
   11726             :     }
   11727        1385 :     *ppszProj4 = CPLStrdup(projString);
   11728        1385 :     proj_destroy(boundCRS);
   11729        1385 :     char *pszTypeCrs = strstr(*ppszProj4, " +type=crs");
   11730        1385 :     if (pszTypeCrs)
   11731        1385 :         *pszTypeCrs = '\0';
   11732        1385 :     return OGRERR_NONE;
   11733             : }
   11734             : 
   11735             : /************************************************************************/
   11736             : /*                            morphToESRI()                             */
   11737             : /************************************************************************/
   11738             : /**
   11739             :  * \brief Convert in place to ESRI WKT format.
   11740             :  *
   11741             :  * The value nodes of this coordinate system are modified in various manners
   11742             :  * more closely map onto the ESRI concept of WKT format.  This includes
   11743             :  * renaming a variety of projections and arguments, and stripping out
   11744             :  * nodes note recognised by ESRI (like AUTHORITY and AXIS).
   11745             :  *
   11746             :  * \note Since GDAL 3.0, this function has only user-visible effects at
   11747             :  * exportToWkt() time. It is recommended to use instead exportToWkt(char**,
   11748             :  * const char* const char*) const with options having FORMAT=WKT1_ESRI.
   11749             :  *
   11750             :  * This does the same as the C function OSRMorphToESRI().
   11751             :  *
   11752             :  * @return OGRERR_NONE unless something goes badly wrong.
   11753             :  * @deprecated
   11754             :  */
   11755             : 
   11756         235 : OGRErr OGRSpatialReference::morphToESRI()
   11757             : 
   11758             : {
   11759         235 :     TAKE_OPTIONAL_LOCK();
   11760             : 
   11761         235 :     d->refreshProjObj();
   11762         235 :     d->setMorphToESRI(true);
   11763             : 
   11764         470 :     return OGRERR_NONE;
   11765             : }
   11766             : 
   11767             : /************************************************************************/
   11768             : /*                           OSRMorphToESRI()                           */
   11769             : /************************************************************************/
   11770             : 
   11771             : /**
   11772             :  * \brief Convert in place to ESRI WKT format.
   11773             :  *
   11774             :  * This function is the same as the C++ method
   11775             :  * OGRSpatialReference::morphToESRI().
   11776             :  */
   11777          71 : OGRErr OSRMorphToESRI(OGRSpatialReferenceH hSRS)
   11778             : 
   11779             : {
   11780          71 :     VALIDATE_POINTER1(hSRS, "OSRMorphToESRI", OGRERR_FAILURE);
   11781             : 
   11782          71 :     return OGRSpatialReference::FromHandle(hSRS)->morphToESRI();
   11783             : }
   11784             : 
   11785             : /************************************************************************/
   11786             : /*                           morphFromESRI()                            */
   11787             : /************************************************************************/
   11788             : 
   11789             : /**
   11790             :  * \brief Convert in place from ESRI WKT format.
   11791             :  *
   11792             :  * The value notes of this coordinate system are modified in various manners
   11793             :  * to adhere more closely to the WKT standard.  This mostly involves
   11794             :  * translating a variety of ESRI names for projections, arguments and
   11795             :  * datums to "standard" names, as defined by Adam Gawne-Cain's reference
   11796             :  * translation of EPSG to WKT for the CT specification.
   11797             :  *
   11798             :  * \note Since GDAL 3.0, this function is essentially a no-operation, since
   11799             :  * morphing from ESRI is automatically done by importFromWkt(). Its only
   11800             :  * effect is to undo the effect of a potential prior call to morphToESRI().
   11801             :  *
   11802             :  * This does the same as the C function OSRMorphFromESRI().
   11803             :  *
   11804             :  * @return OGRERR_NONE unless something goes badly wrong.
   11805             :  * @deprecated
   11806             :  */
   11807             : 
   11808          21 : OGRErr OGRSpatialReference::morphFromESRI()
   11809             : 
   11810             : {
   11811          21 :     TAKE_OPTIONAL_LOCK();
   11812             : 
   11813          21 :     d->refreshProjObj();
   11814          21 :     d->setMorphToESRI(false);
   11815             : 
   11816          42 :     return OGRERR_NONE;
   11817             : }
   11818             : 
   11819             : /************************************************************************/
   11820             : /*                          OSRMorphFromESRI()                          */
   11821             : /************************************************************************/
   11822             : 
   11823             : /**
   11824             :  * \brief Convert in place from ESRI WKT format.
   11825             :  *
   11826             :  * This function is the same as the C++ method
   11827             :  * OGRSpatialReference::morphFromESRI().
   11828             :  */
   11829          20 : OGRErr OSRMorphFromESRI(OGRSpatialReferenceH hSRS)
   11830             : 
   11831             : {
   11832          20 :     VALIDATE_POINTER1(hSRS, "OSRMorphFromESRI", OGRERR_FAILURE);
   11833             : 
   11834          20 :     return OGRSpatialReference::FromHandle(hSRS)->morphFromESRI();
   11835             : }
   11836             : 
   11837             : /************************************************************************/
   11838             : /*                            FindMatches()                             */
   11839             : /************************************************************************/
   11840             : 
   11841             : /**
   11842             :  * \brief Try to identify a match between the passed SRS and a related SRS
   11843             :  * in a catalog.
   11844             :  *
   11845             :  * Matching may be partial, or may fail.
   11846             :  * Returned entries will be sorted by decreasing match confidence (first
   11847             :  * entry has the highest match confidence).
   11848             :  *
   11849             :  * The exact way matching is done may change in future versions. Starting with
   11850             :  * GDAL 3.0, it relies on PROJ' proj_identify() function.
   11851             :  *
   11852             :  * This method is the same as OSRFindMatches().
   11853             :  *
   11854             :  * @param papszOptions NULL terminated list of options or NULL
   11855             :  * @param pnEntries Output parameter. Number of values in the returned array.
   11856             :  * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
   11857             :  * will be allocated to an array of *pnEntries whose values between 0 and 100
   11858             :  * indicate the confidence in the match. 100 is the highest confidence level.
   11859             :  * The array must be freed with CPLFree().
   11860             :  *
   11861             :  * @return an array of SRS that match the passed SRS, or NULL. Must be freed
   11862             :  * with OSRFreeSRSArray()
   11863             :  *
   11864             :  * @since GDAL 2.3
   11865             :  *
   11866             :  * @see OGRSpatialReference::FindBestMatch()
   11867             :  */
   11868             : OGRSpatialReferenceH *
   11869        1427 : OGRSpatialReference::FindMatches(char **papszOptions, int *pnEntries,
   11870             :                                  int **ppanMatchConfidence) const
   11871             : {
   11872        2854 :     TAKE_OPTIONAL_LOCK();
   11873             : 
   11874        1427 :     CPL_IGNORE_RET_VAL(papszOptions);
   11875             : 
   11876        1427 :     if (pnEntries)
   11877        1427 :         *pnEntries = 0;
   11878        1427 :     if (ppanMatchConfidence)
   11879        1427 :         *ppanMatchConfidence = nullptr;
   11880             : 
   11881        1427 :     d->refreshProjObj();
   11882        1427 :     if (!d->m_pj_crs)
   11883           0 :         return nullptr;
   11884             : 
   11885        1427 :     int *panConfidence = nullptr;
   11886        1427 :     auto ctxt = d->getPROJContext();
   11887             :     auto list =
   11888        1427 :         proj_identify(ctxt, d->m_pj_crs, nullptr, nullptr, &panConfidence);
   11889        1427 :     if (!list)
   11890           0 :         return nullptr;
   11891             : 
   11892        1427 :     const int nMatches = proj_list_get_count(list);
   11893             : 
   11894        1427 :     if (pnEntries)
   11895        1427 :         *pnEntries = static_cast<int>(nMatches);
   11896             :     OGRSpatialReferenceH *pahRet = static_cast<OGRSpatialReferenceH *>(
   11897        1427 :         CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   11898        1427 :     if (ppanMatchConfidence)
   11899             :     {
   11900        1427 :         *ppanMatchConfidence =
   11901        1427 :             static_cast<int *>(CPLMalloc(sizeof(int) * (nMatches + 1)));
   11902             :     }
   11903             : 
   11904        1427 :     bool bSortAgain = false;
   11905             : 
   11906        4245 :     for (int i = 0; i < nMatches; i++)
   11907             :     {
   11908        2818 :         PJ *obj = proj_list_get(ctxt, list, i);
   11909        2818 :         CPLAssert(obj);
   11910        2818 :         OGRSpatialReference *poSRS = new OGRSpatialReference();
   11911        2818 :         poSRS->d->setPjCRS(obj);
   11912        2818 :         pahRet[i] = ToHandle(poSRS);
   11913             : 
   11914             :         // Identify matches that only differ by axis order
   11915           9 :         if (panConfidence[i] == 50 && GetAxesCount() == 2 &&
   11916        2836 :             poSRS->GetAxesCount() == 2 &&
   11917        2827 :             GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2})
   11918             :         {
   11919           9 :             OGRAxisOrientation eThisAxis0 = OAO_Other;
   11920           9 :             OGRAxisOrientation eThisAxis1 = OAO_Other;
   11921           9 :             OGRAxisOrientation eSRSAxis0 = OAO_Other;
   11922           9 :             OGRAxisOrientation eSRSAxis1 = OAO_Other;
   11923           9 :             GetAxis(nullptr, 0, &eThisAxis0);
   11924           9 :             GetAxis(nullptr, 1, &eThisAxis1);
   11925           9 :             poSRS->GetAxis(nullptr, 0, &eSRSAxis0);
   11926           9 :             poSRS->GetAxis(nullptr, 1, &eSRSAxis1);
   11927           9 :             if (eThisAxis0 == OAO_East && eThisAxis1 == OAO_North &&
   11928           9 :                 eSRSAxis0 == OAO_North && eSRSAxis1 == OAO_East)
   11929             :             {
   11930             :                 auto pj_crs_normalized =
   11931           9 :                     proj_normalize_for_visualization(ctxt, poSRS->d->m_pj_crs);
   11932           9 :                 if (pj_crs_normalized)
   11933             :                 {
   11934           9 :                     if (proj_is_equivalent_to(d->m_pj_crs, pj_crs_normalized,
   11935           9 :                                               PJ_COMP_EQUIVALENT))
   11936             :                     {
   11937           3 :                         bSortAgain = true;
   11938           3 :                         panConfidence[i] = 90;
   11939           3 :                         poSRS->SetDataAxisToSRSAxisMapping({2, 1});
   11940             :                     }
   11941           9 :                     proj_destroy(pj_crs_normalized);
   11942             :                 }
   11943             :             }
   11944             :         }
   11945             : 
   11946        2818 :         if (ppanMatchConfidence)
   11947        2818 :             (*ppanMatchConfidence)[i] = panConfidence[i];
   11948             :     }
   11949             : 
   11950        1427 :     if (bSortAgain)
   11951             :     {
   11952           3 :         std::vector<int> anIndices;
   11953          12 :         for (int i = 0; i < nMatches; ++i)
   11954           9 :             anIndices.push_back(i);
   11955             : 
   11956           3 :         std::stable_sort(anIndices.begin(), anIndices.end(),
   11957           9 :                          [&panConfidence](int i, int j)
   11958           9 :                          { return panConfidence[i] > panConfidence[j]; });
   11959             : 
   11960             :         OGRSpatialReferenceH *pahRetSorted =
   11961             :             static_cast<OGRSpatialReferenceH *>(
   11962           3 :                 CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1));
   11963          12 :         for (int i = 0; i < nMatches; ++i)
   11964             :         {
   11965           9 :             pahRetSorted[i] = pahRet[anIndices[i]];
   11966           9 :             if (ppanMatchConfidence)
   11967           9 :                 (*ppanMatchConfidence)[i] = panConfidence[anIndices[i]];
   11968             :         }
   11969           3 :         CPLFree(pahRet);
   11970           3 :         pahRet = pahRetSorted;
   11971             :     }
   11972             : 
   11973        1427 :     pahRet[nMatches] = nullptr;
   11974        1427 :     proj_list_destroy(list);
   11975        1427 :     proj_int_list_destroy(panConfidence);
   11976             : 
   11977        1427 :     return pahRet;
   11978             : }
   11979             : 
   11980             : /************************************************************************/
   11981             : /*                          importFromEPSGA()                           */
   11982             : /************************************************************************/
   11983             : 
   11984             : /**
   11985             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   11986             :  * code.
   11987             :  *
   11988             :  * This method will initialize the spatial reference based on the
   11989             :  * passed in EPSG CRS code found in the PROJ database.
   11990             :  *
   11991             :  * Since GDAL 3.0, this method is identical to importFromEPSG().
   11992             :  *
   11993             :  * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
   11994             :  * 7-parameter Helmert transformation to WGS84 when there is one and only one
   11995             :  * such method available for the CRS. This behavior might not always be
   11996             :  * desirable, so starting with GDAL 3.0.3, this is no longer done unless
   11997             :  * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
   11998             :  * The AddGuessedTOWGS84() method can also be used for that purpose.
   11999             :  *
   12000             :  * The method will also by default substitute a deprecated EPSG code by its
   12001             :  * non-deprecated replacement. If this behavior is not desired, the
   12002             :  * OSR_USE_NON_DEPRECATED configuration option can be set to NO.
   12003             :  *
   12004             :  * This method is the same as the C function OSRImportFromEPSGA().
   12005             :  *
   12006             :  * @param nCode a CRS code.
   12007             :  *
   12008             :  * @return OGRERR_NONE on success, or an error code on failure.
   12009             :  */
   12010             : 
   12011       42974 : OGRErr OGRSpatialReference::importFromEPSGA(int nCode)
   12012             : 
   12013             : {
   12014       85948 :     TAKE_OPTIONAL_LOCK();
   12015             : 
   12016       42974 :     Clear();
   12017             : 
   12018             :     const char *pszUseNonDeprecated =
   12019       42975 :         CPLGetConfigOption("OSR_USE_NON_DEPRECATED", nullptr);
   12020             :     const bool bUseNonDeprecated =
   12021       42975 :         CPLTestBool(pszUseNonDeprecated ? pszUseNonDeprecated : "YES");
   12022       42973 :     const bool bAddTOWGS84 = CPLTestBool(
   12023             :         CPLGetConfigOption("OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG", "NO"));
   12024       42975 :     auto tlsCache = OSRGetProjTLSCache();
   12025       42975 :     if (tlsCache)
   12026             :     {
   12027             :         auto cachedObj =
   12028       42975 :             tlsCache->GetPJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84);
   12029       42974 :         if (cachedObj)
   12030             :         {
   12031       32848 :             d->setPjCRS(cachedObj);
   12032       32848 :             return OGRERR_NONE;
   12033             :         }
   12034             :     }
   12035             : 
   12036       20253 :     CPLString osCode;
   12037       10127 :     osCode.Printf("%d", nCode);
   12038             :     PJ *obj;
   12039       10126 :     constexpr int FIRST_NON_DEPRECATED_ESRI_CODE = 53001;
   12040       10126 :     if (nCode < FIRST_NON_DEPRECATED_ESRI_CODE)
   12041             :     {
   12042       10121 :         obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   12043             :                                         osCode.c_str(), PJ_CATEGORY_CRS, true,
   12044             :                                         nullptr);
   12045       10121 :         if (!obj)
   12046             :         {
   12047          23 :             return OGRERR_FAILURE;
   12048             :         }
   12049             :     }
   12050             :     else
   12051             :     {
   12052             :         // Likely to be an ESRI CRS...
   12053           5 :         CPLErr eLastErrorType = CE_None;
   12054           5 :         CPLErrorNum eLastErrorNum = CPLE_None;
   12055           5 :         std::string osLastErrorMsg;
   12056           5 :         bool bIsESRI = false;
   12057             :         {
   12058          10 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
   12059           5 :             CPLErrorReset();
   12060           5 :             obj = proj_create_from_database(d->getPROJContext(), "EPSG",
   12061             :                                             osCode.c_str(), PJ_CATEGORY_CRS,
   12062             :                                             true, nullptr);
   12063           5 :             if (!obj)
   12064             :             {
   12065           2 :                 eLastErrorType = CPLGetLastErrorType();
   12066           2 :                 eLastErrorNum = CPLGetLastErrorNo();
   12067           2 :                 osLastErrorMsg = CPLGetLastErrorMsg();
   12068           2 :                 obj = proj_create_from_database(d->getPROJContext(), "ESRI",
   12069             :                                                 osCode.c_str(), PJ_CATEGORY_CRS,
   12070             :                                                 true, nullptr);
   12071           2 :                 if (obj)
   12072           1 :                     bIsESRI = true;
   12073             :             }
   12074             :         }
   12075           5 :         if (!obj)
   12076             :         {
   12077           1 :             if (eLastErrorType != CE_None)
   12078           1 :                 CPLError(eLastErrorType, eLastErrorNum, "%s",
   12079             :                          osLastErrorMsg.c_str());
   12080           1 :             return OGRERR_FAILURE;
   12081             :         }
   12082           4 :         if (bIsESRI)
   12083             :         {
   12084           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12085             :                      "EPSG:%d is not a valid CRS code, but ESRI:%d is. "
   12086             :                      "Assuming ESRI:%d was meant",
   12087             :                      nCode, nCode, nCode);
   12088             :         }
   12089             :     }
   12090             : 
   12091       10102 :     if (bUseNonDeprecated && proj_is_deprecated(obj))
   12092             :     {
   12093         410 :         auto list = proj_get_non_deprecated(d->getPROJContext(), obj);
   12094         410 :         if (list)
   12095             :         {
   12096         410 :             const auto count = proj_list_get_count(list);
   12097         410 :             if (count == 1)
   12098             :             {
   12099             :                 auto nonDeprecated =
   12100         359 :                     proj_list_get(d->getPROJContext(), list, 0);
   12101         359 :                 if (nonDeprecated)
   12102             :                 {
   12103         359 :                     if (pszUseNonDeprecated == nullptr)
   12104             :                     {
   12105             :                         const char *pszNewAuth =
   12106         359 :                             proj_get_id_auth_name(nonDeprecated, 0);
   12107             :                         const char *pszNewCode =
   12108         359 :                             proj_get_id_code(nonDeprecated, 0);
   12109         359 :                         CPLError(CE_Warning, CPLE_AppDefined,
   12110             :                                  "CRS EPSG:%d is deprecated. "
   12111             :                                  "Its non-deprecated replacement %s:%s "
   12112             :                                  "will be used instead. "
   12113             :                                  "To use the original CRS, set the "
   12114             :                                  "OSR_USE_NON_DEPRECATED "
   12115             :                                  "configuration option to NO.",
   12116             :                                  nCode, pszNewAuth ? pszNewAuth : "(null)",
   12117             :                                  pszNewCode ? pszNewCode : "(null)");
   12118             :                     }
   12119         359 :                     proj_destroy(obj);
   12120         359 :                     obj = nonDeprecated;
   12121             :                 }
   12122             :             }
   12123             :         }
   12124         410 :         proj_list_destroy(list);
   12125             :     }
   12126             : 
   12127       10102 :     if (bAddTOWGS84)
   12128             :     {
   12129           1 :         auto boundCRS = proj_crs_create_bound_crs_to_WGS84(d->getPROJContext(),
   12130             :                                                            obj, nullptr);
   12131           1 :         if (boundCRS)
   12132             :         {
   12133           1 :             proj_destroy(obj);
   12134           1 :             obj = boundCRS;
   12135             :         }
   12136             :     }
   12137             : 
   12138       10102 :     d->setPjCRS(obj);
   12139             : 
   12140       10103 :     if (tlsCache)
   12141             :     {
   12142       10103 :         tlsCache->CachePJForEPSGCode(nCode, bUseNonDeprecated, bAddTOWGS84,
   12143             :                                      obj);
   12144             :     }
   12145             : 
   12146       10103 :     return OGRERR_NONE;
   12147             : }
   12148             : 
   12149             : /************************************************************************/
   12150             : /*                          AddGuessedTOWGS84()                         */
   12151             : /************************************************************************/
   12152             : 
   12153             : /**
   12154             :  * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
   12155             :  * to WGS84.
   12156             :  *
   12157             :  * This method try to attach a 3-parameter or 7-parameter Helmert transformation
   12158             :  * to WGS84 when there is one and only one such method available for the CRS.
   12159             :  * Note: this is more restrictive to how GDAL < 3 worked.
   12160             :  *
   12161             :  * This method is the same as the C function OSRAddGuessedTOWGS84().
   12162             :  *
   12163             :  * @return OGRERR_NONE on success, or an error code on failure (the CRS has
   12164             :  * already a transformation to WGS84 or none matching could be found).
   12165             :  *
   12166             :  * @since GDAL 3.0.3
   12167             :  */
   12168          18 : OGRErr OGRSpatialReference::AddGuessedTOWGS84()
   12169             : {
   12170          36 :     TAKE_OPTIONAL_LOCK();
   12171             : 
   12172          18 :     d->refreshProjObj();
   12173          18 :     if (!d->m_pj_crs)
   12174           0 :         return OGRERR_FAILURE;
   12175          18 :     auto boundCRS = GDAL_proj_crs_create_bound_crs_to_WGS84(
   12176          18 :         d->getPROJContext(), d->m_pj_crs, false, true);
   12177          18 :     if (!boundCRS)
   12178             :     {
   12179           0 :         return OGRERR_FAILURE;
   12180             :     }
   12181          18 :     d->setPjCRS(boundCRS);
   12182          18 :     return OGRERR_NONE;
   12183             : }
   12184             : 
   12185             : /************************************************************************/
   12186             : /*                         OSRImportFromEPSGA()                         */
   12187             : /************************************************************************/
   12188             : 
   12189             : /**
   12190             :  * \brief  Try to add a a 3-parameter or 7-parameter Helmert transformation
   12191             :  * to WGS84.
   12192             :  *
   12193             :  * This function is the same as OGRSpatialReference::AddGuessedTOWGS84().
   12194             :  *
   12195             :  * @since GDAL 3.0.3
   12196             :  */
   12197             : 
   12198           2 : OGRErr OSRAddGuessedTOWGS84(OGRSpatialReferenceH hSRS)
   12199             : 
   12200             : {
   12201           2 :     VALIDATE_POINTER1(hSRS, "OSRAddGuessedTOWGS84", OGRERR_FAILURE);
   12202             : 
   12203           2 :     return OGRSpatialReference::FromHandle(hSRS)->AddGuessedTOWGS84();
   12204             : }
   12205             : 
   12206             : /************************************************************************/
   12207             : /*                         OSRImportFromEPSGA()                         */
   12208             : /************************************************************************/
   12209             : 
   12210             : /**
   12211             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12212             :  * code.
   12213             :  *
   12214             :  * This function is the same as OGRSpatialReference::importFromEPSGA().
   12215             :  */
   12216             : 
   12217           3 : OGRErr CPL_STDCALL OSRImportFromEPSGA(OGRSpatialReferenceH hSRS, int nCode)
   12218             : 
   12219             : {
   12220           3 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSGA", OGRERR_FAILURE);
   12221             : 
   12222           3 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSGA(nCode);
   12223             : }
   12224             : 
   12225             : /************************************************************************/
   12226             : /*                           importFromEPSG()                           */
   12227             : /************************************************************************/
   12228             : 
   12229             : /**
   12230             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12231             :  * code.
   12232             :  *
   12233             :  * This method will initialize the spatial reference based on the
   12234             :  * passed in EPSG CRS code found in the PROJ database.
   12235             :  *
   12236             :  * This method is the same as the C function OSRImportFromEPSG().
   12237             :  *
   12238             :  * Before GDAL 3.0.3, this method would try to attach a 3-parameter or
   12239             :  * 7-parameter Helmert transformation to WGS84 when there is one and only one
   12240             :  * such method available for the CRS. This behavior might not always be
   12241             :  * desirable, so starting with GDAL 3.0.3, this is no longer done unless
   12242             :  * the OSR_ADD_TOWGS84_ON_IMPORT_FROM_EPSG configuration option is set to YES.
   12243             :  *
   12244             :  * @param nCode a GCS or PCS code from the horizontal coordinate system table.
   12245             :  *
   12246             :  * @return OGRERR_NONE on success, or an error code on failure.
   12247             :  */
   12248             : 
   12249       38085 : OGRErr OGRSpatialReference::importFromEPSG(int nCode)
   12250             : 
   12251             : {
   12252       38085 :     return importFromEPSGA(nCode);
   12253             : }
   12254             : 
   12255             : /************************************************************************/
   12256             : /*                         OSRImportFromEPSG()                          */
   12257             : /************************************************************************/
   12258             : 
   12259             : /**
   12260             :  * \brief  Initialize SRS based on EPSG geographic, projected or vertical CRS
   12261             :  * code.
   12262             :  *
   12263             :  * This function is the same as OGRSpatialReference::importFromEPSG().
   12264             :  */
   12265             : 
   12266        1456 : OGRErr CPL_STDCALL OSRImportFromEPSG(OGRSpatialReferenceH hSRS, int nCode)
   12267             : 
   12268             : {
   12269        1456 :     VALIDATE_POINTER1(hSRS, "OSRImportFromEPSG", OGRERR_FAILURE);
   12270             : 
   12271        1456 :     return OGRSpatialReference::FromHandle(hSRS)->importFromEPSG(nCode);
   12272             : }
   12273             : 
   12274             : /************************************************************************/
   12275             : /*                        EPSGTreatsAsLatLong()                         */
   12276             : /************************************************************************/
   12277             : 
   12278             : /**
   12279             :  * \brief This method returns TRUE if this geographic coordinate
   12280             :  * system should be treated as having lat/long coordinate ordering.
   12281             :  *
   12282             :  * Currently this returns TRUE for all geographic coordinate systems
   12283             :  * with axes set defining it as lat, long (prior to GDAL 3.10, it
   12284             :  * also checked that the CRS had belonged to EPSG authority, but this check
   12285             :  * has now been removed).
   12286             :  *
   12287             :  * \note Important change of behavior since GDAL 3.0. In previous versions,
   12288             :  * geographic CRS imported with importFromEPSG() would cause this method to
   12289             :  * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
   12290             :  * is now equivalent to importFromEPSGA().
   12291             :  *
   12292             :  * FALSE will be returned for all coordinate systems that are not geographic,
   12293             :  * or whose axes ordering is not latitude, longitude.
   12294             :  *
   12295             :  * This method is the same as the C function OSREPSGTreatsAsLatLong().
   12296             :  *
   12297             :  * @return TRUE or FALSE.
   12298             :  */
   12299             : 
   12300         866 : int OGRSpatialReference::EPSGTreatsAsLatLong() const
   12301             : 
   12302             : {
   12303        1732 :     TAKE_OPTIONAL_LOCK();
   12304             : 
   12305         866 :     if (!IsGeographic())
   12306         695 :         return FALSE;
   12307             : 
   12308         171 :     d->demoteFromBoundCRS();
   12309             : 
   12310         171 :     bool ret = false;
   12311         171 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12312             :     {
   12313             :         auto horizCRS =
   12314           3 :             proj_crs_get_sub_crs(d->getPROJContext(), d->m_pj_crs, 0);
   12315           3 :         if (horizCRS)
   12316             :         {
   12317             :             auto cs =
   12318           3 :                 proj_crs_get_coordinate_system(d->getPROJContext(), horizCRS);
   12319           3 :             if (cs)
   12320             :             {
   12321           3 :                 const char *pszDirection = nullptr;
   12322           3 :                 if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12323             :                                           nullptr, &pszDirection, nullptr,
   12324           3 :                                           nullptr, nullptr, nullptr))
   12325             :                 {
   12326           3 :                     if (EQUAL(pszDirection, "north"))
   12327             :                     {
   12328           3 :                         ret = true;
   12329             :                     }
   12330             :                 }
   12331             : 
   12332           3 :                 proj_destroy(cs);
   12333             :             }
   12334             : 
   12335           3 :             proj_destroy(horizCRS);
   12336             :         }
   12337             :     }
   12338             :     else
   12339             :     {
   12340             :         auto cs =
   12341         168 :             proj_crs_get_coordinate_system(d->getPROJContext(), d->m_pj_crs);
   12342         168 :         if (cs)
   12343             :         {
   12344         168 :             const char *pszDirection = nullptr;
   12345         168 :             if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr,
   12346             :                                       nullptr, &pszDirection, nullptr, nullptr,
   12347         168 :                                       nullptr, nullptr))
   12348             :             {
   12349         168 :                 if (EQUAL(pszDirection, "north"))
   12350             :                 {
   12351         119 :                     ret = true;
   12352             :                 }
   12353             :             }
   12354             : 
   12355         168 :             proj_destroy(cs);
   12356             :         }
   12357             :     }
   12358         171 :     d->undoDemoteFromBoundCRS();
   12359             : 
   12360         171 :     return ret;
   12361             : }
   12362             : 
   12363             : /************************************************************************/
   12364             : /*                       OSREPSGTreatsAsLatLong()                       */
   12365             : /************************************************************************/
   12366             : 
   12367             : /**
   12368             :  * \brief This function returns TRUE if this geographic coordinate
   12369             :  * system should be treated as having lat/long coordinate ordering.
   12370             :  *
   12371             :  * This function is the same as OGRSpatialReference::OSREPSGTreatsAsLatLong().
   12372             :  */
   12373             : 
   12374         180 : int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH hSRS)
   12375             : 
   12376             : {
   12377         180 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsLatLong", OGRERR_FAILURE);
   12378             : 
   12379         180 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsLatLong();
   12380             : }
   12381             : 
   12382             : /************************************************************************/
   12383             : /*                     EPSGTreatsAsNorthingEasting()                    */
   12384             : /************************************************************************/
   12385             : 
   12386             : /**
   12387             :  * \brief This method returns TRUE if this projected coordinate
   12388             :  * system should be treated as having northing/easting coordinate ordering.
   12389             :  *
   12390             :  * Currently this returns TRUE for all projected coordinate systems
   12391             :  * with axes set defining it as northing, easting (prior to GDAL 3.10, it
   12392             :  * also checked that the CRS had belonged to EPSG authority, but this check
   12393             :  * has now been removed).
   12394             :  *
   12395             :  * \note Important change of behavior since GDAL 3.0. In previous versions,
   12396             :  * projected CRS with northing, easting axis order imported with
   12397             :  * importFromEPSG() would cause this method to
   12398             :  * return FALSE on them, whereas now it returns TRUE, since importFromEPSG()
   12399             :  * is now equivalent to importFromEPSGA().
   12400             :  *
   12401             :  * FALSE will be returned for all coordinate systems that are not projected,
   12402             :  * or whose axes ordering is not northing, easting.
   12403             :  *
   12404             :  * This method is the same as the C function EPSGTreatsAsNorthingEasting().
   12405             :  *
   12406             :  * @return TRUE or FALSE.
   12407             :  *
   12408             :  * @since OGR 1.10.0
   12409             :  */
   12410             : 
   12411         754 : int OGRSpatialReference::EPSGTreatsAsNorthingEasting() const
   12412             : 
   12413             : {
   12414        1508 :     TAKE_OPTIONAL_LOCK();
   12415             : 
   12416         754 :     if (!IsProjected())
   12417          46 :         return FALSE;
   12418             : 
   12419         708 :     d->demoteFromBoundCRS();
   12420             :     PJ *projCRS;
   12421         708 :     const auto ctxt = d->getPROJContext();
   12422         708 :     if (d->m_pjType == PJ_TYPE_COMPOUND_CRS)
   12423             :     {
   12424           4 :         projCRS = proj_crs_get_sub_crs(ctxt, d->m_pj_crs, 0);
   12425           4 :         if (!projCRS || proj_get_type(projCRS) != PJ_TYPE_PROJECTED_CRS)
   12426             :         {
   12427           0 :             d->undoDemoteFromBoundCRS();
   12428           0 :             proj_destroy(projCRS);
   12429           0 :             return FALSE;
   12430             :         }
   12431             :     }
   12432             :     else
   12433             :     {
   12434         704 :         projCRS = proj_clone(ctxt, d->m_pj_crs);
   12435             :     }
   12436             : 
   12437         708 :     bool ret = false;
   12438         708 :     auto cs = proj_crs_get_coordinate_system(ctxt, projCRS);
   12439         708 :     proj_destroy(projCRS);
   12440         708 :     d->undoDemoteFromBoundCRS();
   12441             : 
   12442         708 :     if (cs)
   12443             :     {
   12444         708 :         ret = isNorthEastAxisOrder(ctxt, cs);
   12445         708 :         proj_destroy(cs);
   12446             :     }
   12447             : 
   12448         708 :     return ret;
   12449             : }
   12450             : 
   12451             : /************************************************************************/
   12452             : /*                     OSREPSGTreatsAsNorthingEasting()                 */
   12453             : /************************************************************************/
   12454             : 
   12455             : /**
   12456             :  * \brief This function returns TRUE if this projected coordinate
   12457             :  * system should be treated as having northing/easting coordinate ordering.
   12458             :  *
   12459             :  * This function is the same as
   12460             :  * OGRSpatialReference::EPSGTreatsAsNorthingEasting().
   12461             :  *
   12462             :  * @since OGR 1.10.0
   12463             :  */
   12464             : 
   12465         187 : int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH hSRS)
   12466             : 
   12467             : {
   12468         187 :     VALIDATE_POINTER1(hSRS, "OSREPSGTreatsAsNorthingEasting", OGRERR_FAILURE);
   12469             : 
   12470         187 :     return OGRSpatialReference::FromHandle(hSRS)->EPSGTreatsAsNorthingEasting();
   12471             : }
   12472             : 
   12473             : /************************************************************************/
   12474             : /*                     ImportFromESRIWisconsinWKT()                     */
   12475             : /*                                                                      */
   12476             : /*      Search a ESRI State Plane WKT and import it.                    */
   12477             : /************************************************************************/
   12478             : 
   12479             : // This is only used by the HFA driver and somewhat dubious we really need that
   12480             : // Coming from an old ESRI merge
   12481             : 
   12482           1 : OGRErr OGRSpatialReference::ImportFromESRIWisconsinWKT(const char *prjName,
   12483             :                                                        double centralMeridian,
   12484             :                                                        double latOfOrigin,
   12485             :                                                        const char *unitsName,
   12486             :                                                        const char *crsName)
   12487             : {
   12488           2 :     TAKE_OPTIONAL_LOCK();
   12489             : 
   12490           1 :     if (centralMeridian < -93 || centralMeridian > -87)
   12491           0 :         return OGRERR_FAILURE;
   12492           1 :     if (latOfOrigin < 40 || latOfOrigin > 47)
   12493           0 :         return OGRERR_FAILURE;
   12494             : 
   12495             :     // If the CS name is known.
   12496           1 :     if (!prjName && !unitsName && crsName)
   12497             :     {
   12498           0 :         const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12499           0 :         PJ_OBJ_LIST *list = proj_create_from_name(
   12500             :             d->getPROJContext(), "ESRI", crsName, &type, 1, false, 1, nullptr);
   12501           0 :         if (list)
   12502             :         {
   12503           0 :             if (proj_list_get_count(list) == 1)
   12504             :             {
   12505           0 :                 auto crs = proj_list_get(d->getPROJContext(), list, 0);
   12506           0 :                 if (crs)
   12507             :                 {
   12508           0 :                     Clear();
   12509           0 :                     d->setPjCRS(crs);
   12510           0 :                     proj_list_destroy(list);
   12511           0 :                     return OGRERR_NONE;
   12512             :                 }
   12513             :             }
   12514           0 :             proj_list_destroy(list);
   12515             :         }
   12516           0 :         return OGRERR_FAILURE;
   12517             :     }
   12518             : 
   12519           1 :     if (prjName == nullptr || unitsName == nullptr)
   12520             :     {
   12521           0 :         return OGRERR_FAILURE;
   12522             :     }
   12523             : 
   12524           1 :     const PJ_TYPE type = PJ_TYPE_PROJECTED_CRS;
   12525           1 :     PJ_OBJ_LIST *list = proj_create_from_name(d->getPROJContext(), "ESRI",
   12526             :                                               "NAD_1983_HARN_WISCRS_", &type, 1,
   12527             :                                               true, 0, nullptr);
   12528           1 :     if (list)
   12529             :     {
   12530           1 :         const auto listSize = proj_list_get_count(list);
   12531           8 :         for (int i = 0; i < listSize; i++)
   12532             :         {
   12533           8 :             auto crs = proj_list_get(d->getPROJContext(), list, i);
   12534           8 :             if (!crs)
   12535             :             {
   12536           7 :                 continue;
   12537             :             }
   12538             : 
   12539           8 :             auto conv = proj_crs_get_coordoperation(d->getPROJContext(), crs);
   12540           8 :             if (!conv)
   12541             :             {
   12542           0 :                 proj_destroy(crs);
   12543           0 :                 continue;
   12544             :             }
   12545           8 :             const char *pszMethodCode = nullptr;
   12546           8 :             proj_coordoperation_get_method_info(
   12547             :                 d->getPROJContext(), conv, nullptr, nullptr, &pszMethodCode);
   12548           8 :             const int nMethodCode = atoi(pszMethodCode ? pszMethodCode : "0");
   12549           8 :             if (!((EQUAL(prjName, SRS_PT_TRANSVERSE_MERCATOR) &&
   12550             :                    nMethodCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) ||
   12551           3 :                   (EQUAL(prjName, "Lambert_Conformal_Conic") &&
   12552             :                    nMethodCode ==
   12553             :                        EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)))
   12554             :             {
   12555           3 :                 proj_destroy(crs);
   12556           3 :                 proj_destroy(conv);
   12557           3 :                 continue;
   12558             :             }
   12559             : 
   12560             :             auto coordSys =
   12561           5 :                 proj_crs_get_coordinate_system(d->getPROJContext(), crs);
   12562           5 :             if (!coordSys)
   12563             :             {
   12564           0 :                 proj_destroy(crs);
   12565           0 :                 proj_destroy(conv);
   12566           0 :                 continue;
   12567             :             }
   12568             : 
   12569           5 :             double dfConvFactor = 0.0;
   12570           5 :             proj_cs_get_axis_info(d->getPROJContext(), coordSys, 0, nullptr,
   12571             :                                   nullptr, nullptr, &dfConvFactor, nullptr,
   12572             :                                   nullptr, nullptr);
   12573           5 :             proj_destroy(coordSys);
   12574             : 
   12575           6 :             if ((EQUAL(unitsName, "meters") && dfConvFactor != 1.0) ||
   12576           1 :                 (!EQUAL(unitsName, "meters") &&
   12577           0 :                  std::fabs(dfConvFactor - CPLAtof(SRS_UL_US_FOOT_CONV)) >
   12578             :                      1e-10))
   12579             :             {
   12580           4 :                 proj_destroy(crs);
   12581           4 :                 proj_destroy(conv);
   12582           4 :                 continue;
   12583             :             }
   12584             : 
   12585           1 :             int idx_lat = proj_coordoperation_get_param_index(
   12586             :                 d->getPROJContext(), conv,
   12587             :                 EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
   12588           1 :             double valueLat = -1000;
   12589           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lat,
   12590             :                                           nullptr, nullptr, nullptr, &valueLat,
   12591             :                                           nullptr, nullptr, nullptr, nullptr,
   12592             :                                           nullptr, nullptr);
   12593           1 :             int idx_lon = proj_coordoperation_get_param_index(
   12594             :                 d->getPROJContext(), conv,
   12595             :                 EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN);
   12596           1 :             double valueLong = -1000;
   12597           1 :             proj_coordoperation_get_param(d->getPROJContext(), conv, idx_lon,
   12598             :                                           nullptr, nullptr, nullptr, &valueLong,
   12599             :                                           nullptr, nullptr, nullptr, nullptr,
   12600             :                                           nullptr, nullptr);
   12601           1 :             if (std::fabs(centralMeridian - valueLong) <= 1e-10 &&
   12602           1 :                 std::fabs(latOfOrigin - valueLat) <= 1e-10)
   12603             :             {
   12604           1 :                 Clear();
   12605           1 :                 d->setPjCRS(crs);
   12606           1 :                 proj_list_destroy(list);
   12607           1 :                 proj_destroy(conv);
   12608           1 :                 return OGRERR_NONE;
   12609             :             }
   12610             : 
   12611           0 :             proj_destroy(crs);
   12612           0 :             proj_destroy(conv);
   12613             :         }
   12614           0 :         proj_list_destroy(list);
   12615             :     }
   12616             : 
   12617           0 :     return OGRERR_FAILURE;
   12618             : }
   12619             : 
   12620             : /************************************************************************/
   12621             : /*                      GetAxisMappingStrategy()                        */
   12622             : /************************************************************************/
   12623             : 
   12624             : /** \brief Return the data axis to CRS axis mapping strategy.
   12625             :  *
   12626             :  * <ul>
   12627             :  * <li>OAMS_TRADITIONAL_GIS_ORDER means that for geographic CRS with
   12628             :  *     lat/long order, the data will still be long/lat ordered. Similarly for
   12629             :  *     a projected CRS with northing/easting order, the data will still be
   12630             :  *     easting/northing ordered.
   12631             :  * <li>OAMS_AUTHORITY_COMPLIANT means that the data axis will be identical to
   12632             :  *     the CRS axis.
   12633             :  * <li>OAMS_CUSTOM means that the data axis are customly defined with
   12634             :  *     SetDataAxisToSRSAxisMapping()
   12635             :  * </ul>
   12636             :  * @return the data axis to CRS axis mapping strategy.
   12637             :  * @since GDAL 3.0
   12638             :  */
   12639          72 : OSRAxisMappingStrategy OGRSpatialReference::GetAxisMappingStrategy() const
   12640             : {
   12641          72 :     TAKE_OPTIONAL_LOCK();
   12642             : 
   12643         144 :     return d->m_axisMappingStrategy;
   12644             : }
   12645             : 
   12646             : /************************************************************************/
   12647             : /*                      OSRGetAxisMappingStrategy()                     */
   12648             : /************************************************************************/
   12649             : 
   12650             : /** \brief Return the data axis to CRS axis mapping strategy.
   12651             :  *
   12652             :  * See OGRSpatialReference::GetAxisMappingStrategy()
   12653             :  * @since GDAL 3.0
   12654             :  */
   12655          37 : OSRAxisMappingStrategy OSRGetAxisMappingStrategy(OGRSpatialReferenceH hSRS)
   12656             : {
   12657          37 :     VALIDATE_POINTER1(hSRS, "OSRGetAxisMappingStrategy", OAMS_CUSTOM);
   12658             : 
   12659          37 :     return OGRSpatialReference::FromHandle(hSRS)->GetAxisMappingStrategy();
   12660             : }
   12661             : 
   12662             : /************************************************************************/
   12663             : /*                      SetAxisMappingStrategy()                        */
   12664             : /************************************************************************/
   12665             : 
   12666             : /** \brief Set the data axis to CRS axis mapping strategy.
   12667             :  *
   12668             :  * Starting with GDAL 3.5, the OSR_DEFAULT_AXIS_MAPPING_STRATEGY configuration
   12669             :  * option can be set to "TRADITIONAL_GIS_ORDER" / "AUTHORITY_COMPLIANT" (the
   12670             :  * later being the default value when the option is not set) to control the
   12671             :  * value of the data axis to CRS axis mapping strategy when a
   12672             :  * OSRSpatialReference object is created. Calling SetAxisMappingStrategy() will
   12673             :  * override this default value.
   12674             :  *
   12675             :  * See OGRSpatialReference::GetAxisMappingStrategy()
   12676             :  * @since GDAL 3.0
   12677             :  */
   12678       85681 : void OGRSpatialReference::SetAxisMappingStrategy(
   12679             :     OSRAxisMappingStrategy strategy)
   12680             : {
   12681      171142 :     TAKE_OPTIONAL_LOCK();
   12682             : 
   12683       85534 :     d->m_axisMappingStrategy = strategy;
   12684       85551 :     d->refreshAxisMapping();
   12685       85446 : }
   12686             : 
   12687             : /************************************************************************/
   12688             : /*                      OSRSetAxisMappingStrategy()                     */
   12689             : /************************************************************************/
   12690             : 
   12691             : /** \brief Set the data axis to CRS axis mapping strategy.
   12692             :  *
   12693             :  * See OGRSpatialReference::SetAxisMappingStrategy()
   12694             :  * @since GDAL 3.0
   12695             :  */
   12696         788 : void OSRSetAxisMappingStrategy(OGRSpatialReferenceH hSRS,
   12697             :                                OSRAxisMappingStrategy strategy)
   12698             : {
   12699         788 :     VALIDATE_POINTER0(hSRS, "OSRSetAxisMappingStrategy");
   12700             : 
   12701         788 :     OGRSpatialReference::FromHandle(hSRS)->SetAxisMappingStrategy(strategy);
   12702             : }
   12703             : 
   12704             : /************************************************************************/
   12705             : /*                      GetDataAxisToSRSAxisMapping()                   */
   12706             : /************************************************************************/
   12707             : 
   12708             : /** \brief Return the data axis to SRS axis mapping.
   12709             :  *
   12710             :  * The number of elements of the vector will be the number of axis of the CRS.
   12711             :  * Values start at 1.
   12712             :  *
   12713             :  * If m = GetDataAxisToSRSAxisMapping(), then m[0] is the data axis number
   12714             :  * for the first axis of the CRS.
   12715             :  *
   12716             :  * @since GDAL 3.0
   12717             :  */
   12718     4196110 : const std::vector<int> &OGRSpatialReference::GetDataAxisToSRSAxisMapping() const
   12719             : {
   12720     4196110 :     TAKE_OPTIONAL_LOCK();
   12721             : 
   12722     8392110 :     return d->m_axisMapping;
   12723             : }
   12724             : 
   12725             : /************************************************************************/
   12726             : /*                     OSRGetDataAxisToSRSAxisMapping()                 */
   12727             : /************************************************************************/
   12728             : 
   12729             : /** \brief Return the data axis to SRS axis mapping.
   12730             :  *
   12731             :  * See OGRSpatialReference::GetDataAxisToSRSAxisMapping()
   12732             :  *
   12733             :  * @since GDAL 3.0
   12734             :  */
   12735         214 : const int *OSRGetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12736             :                                           int *pnCount)
   12737             : {
   12738         214 :     VALIDATE_POINTER1(hSRS, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12739         214 :     VALIDATE_POINTER1(pnCount, "OSRGetDataAxisToSRSAxisMapping", nullptr);
   12740             : 
   12741             :     const auto &v =
   12742         214 :         OGRSpatialReference::FromHandle(hSRS)->GetDataAxisToSRSAxisMapping();
   12743         214 :     *pnCount = static_cast<int>(v.size());
   12744         214 :     return v.data();
   12745             : }
   12746             : 
   12747             : /************************************************************************/
   12748             : /*                      SetDataAxisToSRSAxisMapping()                   */
   12749             : /************************************************************************/
   12750             : 
   12751             : /** \brief Set a custom data axis to CRS axis mapping.
   12752             :  *
   12753             :  * The number of elements of the mapping vector should be the number of axis
   12754             :  * of the CRS (as returned by GetAxesCount()) (although this method does not
   12755             :  * check that, beyond checking there are at least 2 elements, so that this
   12756             :  * method and setting the CRS can be done in any order).
   12757             :  * This is taken into account by OGRCoordinateTransformation to transform the
   12758             :  * order of coordinates to the order expected by the CRS before
   12759             :  * transformation, and back to the data order after transformation.
   12760             :  *
   12761             :  * The mapping[i] value (one based) represents the data axis number for the i(th)
   12762             :  * axis of the CRS. A negative value can also be used to ask for a sign
   12763             :  * reversal during coordinate transformation (to deal with northing vs southing,
   12764             :  * easting vs westing, heights vs depths).
   12765             :  *
   12766             :  * When used with OGRCoordinateTransformation,
   12767             :  * - the only valid values for mapping[0] (data axis number for the first axis
   12768             :  *   of the CRS) are 1, 2, -1, -2.
   12769             :  * - the only valid values for mapping[1] (data axis number for the second axis
   12770             :  *   of the CRS) are 1, 2, -1, -2.
   12771             :  *  - the only valid values mapping[2] are 3 or -3.
   12772             :  * Note: this method does not validate the values of mapping[].
   12773             :  *
   12774             :  * mapping=[2,1] typically expresses the inversion of axis between the data
   12775             :  * axis and the CRS axis for a 2D CRS.
   12776             :  *
   12777             :  * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
   12778             :  *
   12779             :  * This is the same as the C function OSRSetDataAxisToSRSAxisMapping().
   12780             :  *
   12781             :  * @param mapping The new data axis to CRS axis mapping.
   12782             :  *
   12783             :  * @since GDAL 3.0
   12784             :  * @see OGRSpatialReference::GetDataAxisToSRSAxisMapping()
   12785             :  */
   12786        8267 : OGRErr OGRSpatialReference::SetDataAxisToSRSAxisMapping(
   12787             :     const std::vector<int> &mapping)
   12788             : {
   12789       16534 :     TAKE_OPTIONAL_LOCK();
   12790             : 
   12791        8267 :     if (mapping.size() < 2)
   12792           0 :         return OGRERR_FAILURE;
   12793        8267 :     d->m_axisMappingStrategy = OAMS_CUSTOM;
   12794        8267 :     d->m_axisMapping = mapping;
   12795        8267 :     return OGRERR_NONE;
   12796             : }
   12797             : 
   12798             : /************************************************************************/
   12799             : /*                     OSRSetDataAxisToSRSAxisMapping()                 */
   12800             : /************************************************************************/
   12801             : 
   12802             : /** \brief Set a custom data axis to CRS axis mapping.
   12803             :  *
   12804             :  * Automatically implies SetAxisMappingStrategy(OAMS_CUSTOM)
   12805             :  *
   12806             :  * This is the same as the C++ method
   12807             :  * OGRSpatialReference::SetDataAxisToSRSAxisMapping()
   12808             :  *
   12809             :  * @since GDAL 3.1
   12810             :  */
   12811          15 : OGRErr OSRSetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS,
   12812             :                                       int nMappingSize, const int *panMapping)
   12813             : {
   12814          15 :     VALIDATE_POINTER1(hSRS, "OSRSetDataAxisToSRSAxisMapping", OGRERR_FAILURE);
   12815          15 :     VALIDATE_POINTER1(panMapping, "OSRSetDataAxisToSRSAxisMapping",
   12816             :                       OGRERR_FAILURE);
   12817             : 
   12818          15 :     if (nMappingSize < 0)
   12819           0 :         return OGRERR_FAILURE;
   12820             : 
   12821          30 :     std::vector<int> mapping(nMappingSize);
   12822          15 :     if (nMappingSize)
   12823          15 :         memcpy(&mapping[0], panMapping, nMappingSize * sizeof(int));
   12824          15 :     return OGRSpatialReference::FromHandle(hSRS)->SetDataAxisToSRSAxisMapping(
   12825          15 :         mapping);
   12826             : }
   12827             : 
   12828             : /************************************************************************/
   12829             : /*                               GetAreaOfUse()                         */
   12830             : /************************************************************************/
   12831             : 
   12832             : /** \brief Return the area of use of the CRS.
   12833             :  *
   12834             :  * This method is the same as the OSRGetAreaOfUse() function.
   12835             :  *
   12836             :  * @param pdfWestLongitudeDeg Pointer to a double to receive the western-most
   12837             :  * longitude, expressed in degree. Might be NULL. If the returned value is
   12838             :  * -1000, the bounding box is unknown.
   12839             :  * @param pdfSouthLatitudeDeg Pointer to a double to receive the southern-most
   12840             :  * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
   12841             :  * the bounding box is unknown.
   12842             :  * @param pdfEastLongitudeDeg Pointer to a double to receive the eastern-most
   12843             :  * longitude, expressed in degree. Might be NULL. If the returned value is
   12844             :  * -1000, the bounding box is unknown.
   12845             :  * @param pdfNorthLatitudeDeg Pointer to a double to receive the northern-most
   12846             :  * latitude, expressed in degree. Might be NULL. If the returned value is -1000,
   12847             :  * the bounding box is unknown.
   12848             :  * @param ppszAreaName Pointer to a string to receive the name of the area of
   12849             :  * use. Might be NULL. Note that *ppszAreaName is short-lived and might be
   12850             :  * invalidated by further calls.
   12851             :  * @return true in case of success
   12852             :  * @since GDAL 3.0
   12853             :  */
   12854          32 : bool OGRSpatialReference::GetAreaOfUse(double *pdfWestLongitudeDeg,
   12855             :                                        double *pdfSouthLatitudeDeg,
   12856             :                                        double *pdfEastLongitudeDeg,
   12857             :                                        double *pdfNorthLatitudeDeg,
   12858             :                                        const char **ppszAreaName) const
   12859             : {
   12860          64 :     TAKE_OPTIONAL_LOCK();
   12861             : 
   12862          32 :     d->refreshProjObj();
   12863          32 :     if (!d->m_pj_crs)
   12864             :     {
   12865           0 :         return false;
   12866             :     }
   12867          32 :     d->demoteFromBoundCRS();
   12868          32 :     const char *pszAreaName = nullptr;
   12869          32 :     int bSuccess = proj_get_area_of_use(
   12870          32 :         d->getPROJContext(), d->m_pj_crs, pdfWestLongitudeDeg,
   12871             :         pdfSouthLatitudeDeg, pdfEastLongitudeDeg, pdfNorthLatitudeDeg,
   12872             :         &pszAreaName);
   12873          32 :     d->undoDemoteFromBoundCRS();
   12874          32 :     d->m_osAreaName = pszAreaName ? pszAreaName : "";
   12875          32 :     if (ppszAreaName)
   12876           1 :         *ppszAreaName = d->m_osAreaName.c_str();
   12877          32 :     return CPL_TO_BOOL(bSuccess);
   12878             : }
   12879             : 
   12880             : /************************************************************************/
   12881             : /*                               GetAreaOfUse()                         */
   12882             : /************************************************************************/
   12883             : 
   12884             : /** \brief Return the area of use of the CRS.
   12885             :  *
   12886             :  * This function is the same as the OGRSpatialReference::GetAreaOfUse() method.
   12887             :  *
   12888             :  * @since GDAL 3.0
   12889             :  */
   12890           1 : int OSRGetAreaOfUse(OGRSpatialReferenceH hSRS, double *pdfWestLongitudeDeg,
   12891             :                     double *pdfSouthLatitudeDeg, double *pdfEastLongitudeDeg,
   12892             :                     double *pdfNorthLatitudeDeg, const char **ppszAreaName)
   12893             : {
   12894           1 :     VALIDATE_POINTER1(hSRS, "OSRGetAreaOfUse", FALSE);
   12895             : 
   12896           1 :     return OGRSpatialReference::FromHandle(hSRS)->GetAreaOfUse(
   12897             :         pdfWestLongitudeDeg, pdfSouthLatitudeDeg, pdfEastLongitudeDeg,
   12898           1 :         pdfNorthLatitudeDeg, ppszAreaName);
   12899             : }
   12900             : 
   12901             : /************************************************************************/
   12902             : /*                     OSRGetCRSInfoListFromDatabase()                  */
   12903             : /************************************************************************/
   12904             : 
   12905             : /** \brief Enumerate CRS objects from the database.
   12906             :  *
   12907             :  * The returned object is an array of OSRCRSInfo* pointers, whose last
   12908             :  * entry is NULL. This array should be freed with OSRDestroyCRSInfoList()
   12909             :  *
   12910             :  * @param pszAuthName Authority name, used to restrict the search.
   12911             :  * Or NULL for all authorities.
   12912             :  * @param params Additional criteria. Must be set to NULL for now.
   12913             :  * @param pnOutResultCount Output parameter pointing to an integer to receive
   12914             :  * the size of the result list. Might be NULL
   12915             :  * @return an array of OSRCRSInfo* pointers to be freed with
   12916             :  * OSRDestroyCRSInfoList(), or NULL in case of error.
   12917             :  *
   12918             :  * @since GDAL 3.0
   12919             :  */
   12920             : OSRCRSInfo **
   12921          24 : OSRGetCRSInfoListFromDatabase(const char *pszAuthName,
   12922             :                               CPL_UNUSED const OSRCRSListParameters *params,
   12923             :                               int *pnOutResultCount)
   12924             : {
   12925          24 :     int nResultCount = 0;
   12926          24 :     auto projList = proj_get_crs_info_list_from_database(
   12927             :         OSRGetProjTLSContext(), pszAuthName, nullptr, &nResultCount);
   12928          24 :     if (pnOutResultCount)
   12929          24 :         *pnOutResultCount = nResultCount;
   12930          24 :     if (!projList)
   12931             :     {
   12932           0 :         return nullptr;
   12933             :     }
   12934          24 :     auto res = new OSRCRSInfo *[nResultCount + 1];
   12935       89181 :     for (int i = 0; i < nResultCount; i++)
   12936             :     {
   12937       89157 :         res[i] = new OSRCRSInfo;
   12938      178314 :         res[i]->pszAuthName = projList[i]->auth_name
   12939       89157 :                                   ? CPLStrdup(projList[i]->auth_name)
   12940             :                                   : nullptr;
   12941       89157 :         res[i]->pszCode =
   12942       89157 :             projList[i]->code ? CPLStrdup(projList[i]->code) : nullptr;
   12943       89157 :         res[i]->pszName =
   12944       89157 :             projList[i]->name ? CPLStrdup(projList[i]->name) : nullptr;
   12945       89157 :         res[i]->eType = OSR_CRS_TYPE_OTHER;
   12946       89157 :         switch (projList[i]->type)
   12947             :         {
   12948        8727 :             case PJ_TYPE_GEOGRAPHIC_2D_CRS:
   12949        8727 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_2D;
   12950        8727 :                 break;
   12951        2811 :             case PJ_TYPE_GEOGRAPHIC_3D_CRS:
   12952        2811 :                 res[i]->eType = OSR_CRS_TYPE_GEOGRAPHIC_3D;
   12953        2811 :                 break;
   12954        3066 :             case PJ_TYPE_GEOCENTRIC_CRS:
   12955        3066 :                 res[i]->eType = OSR_CRS_TYPE_GEOCENTRIC;
   12956        3066 :                 break;
   12957       67740 :             case PJ_TYPE_PROJECTED_CRS:
   12958       67740 :                 res[i]->eType = OSR_CRS_TYPE_PROJECTED;
   12959       67740 :                 break;
   12960        2808 :             case PJ_TYPE_VERTICAL_CRS:
   12961        2808 :                 res[i]->eType = OSR_CRS_TYPE_VERTICAL;
   12962        2808 :                 break;
   12963        4005 :             case PJ_TYPE_COMPOUND_CRS:
   12964        4005 :                 res[i]->eType = OSR_CRS_TYPE_COMPOUND;
   12965        4005 :                 break;
   12966           0 :             default:
   12967           0 :                 break;
   12968             :         }
   12969       89157 :         res[i]->bDeprecated = projList[i]->deprecated;
   12970       89157 :         res[i]->bBboxValid = projList[i]->bbox_valid;
   12971       89157 :         res[i]->dfWestLongitudeDeg = projList[i]->west_lon_degree;
   12972       89157 :         res[i]->dfSouthLatitudeDeg = projList[i]->south_lat_degree;
   12973       89157 :         res[i]->dfEastLongitudeDeg = projList[i]->east_lon_degree;
   12974       89157 :         res[i]->dfNorthLatitudeDeg = projList[i]->north_lat_degree;
   12975      178314 :         res[i]->pszAreaName = projList[i]->area_name
   12976       89157 :                                   ? CPLStrdup(projList[i]->area_name)
   12977             :                                   : nullptr;
   12978       89157 :         res[i]->pszProjectionMethod =
   12979       89157 :             projList[i]->projection_method_name
   12980       89157 :                 ? CPLStrdup(projList[i]->projection_method_name)
   12981             :                 : nullptr;
   12982             : #if PROJ_AT_LEAST_VERSION(8, 1, 0)
   12983             :         res[i]->pszCelestialBodyName =
   12984             :             projList[i]->celestial_body_name
   12985             :                 ? CPLStrdup(projList[i]->celestial_body_name)
   12986             :                 : nullptr;
   12987             : #else
   12988       89157 :         res[i]->pszCelestialBodyName =
   12989       89157 :             res[i]->pszAuthName && EQUAL(res[i]->pszAuthName, "EPSG")
   12990      178314 :                 ? CPLStrdup("Earth")
   12991             :                 : nullptr;
   12992             : #endif
   12993             :     }
   12994          24 :     res[nResultCount] = nullptr;
   12995          24 :     proj_crs_info_list_destroy(projList);
   12996          24 :     return res;
   12997             : }
   12998             : 
   12999             : /************************************************************************/
   13000             : /*                        OSRDestroyCRSInfoList()                       */
   13001             : /************************************************************************/
   13002             : 
   13003             : /** \brief Destroy the result returned by
   13004             :  * OSRGetCRSInfoListFromDatabase().
   13005             :  *
   13006             :  * @since GDAL 3.0
   13007             :  */
   13008          24 : void OSRDestroyCRSInfoList(OSRCRSInfo **list)
   13009             : {
   13010          24 :     if (list)
   13011             :     {
   13012       89181 :         for (int i = 0; list[i] != nullptr; i++)
   13013             :         {
   13014       89157 :             CPLFree(list[i]->pszAuthName);
   13015       89157 :             CPLFree(list[i]->pszCode);
   13016       89157 :             CPLFree(list[i]->pszName);
   13017       89157 :             CPLFree(list[i]->pszAreaName);
   13018       89157 :             CPLFree(list[i]->pszProjectionMethod);
   13019       89157 :             CPLFree(list[i]->pszCelestialBodyName);
   13020       89157 :             delete list[i];
   13021             :         }
   13022          24 :         delete[] list;
   13023             :     }
   13024          24 : }
   13025             : 
   13026             : /************************************************************************/
   13027             : /*                   OSRGetAuthorityListFromDatabase()                  */
   13028             : /************************************************************************/
   13029             : 
   13030             : /** \brief Return the list of CRS authorities used in the PROJ database.
   13031             :  *
   13032             :  * Such as "EPSG", "ESRI", "PROJ", "IGNF", "IAU_2015", etc.
   13033             :  *
   13034             :  * This is a direct mapping of https://proj.org/en/latest/development/reference/functions.html#c.proj_get_authorities_from_database
   13035             :  *
   13036             :  * @return nullptr in case of error, or a NULL terminated list of strings to
   13037             :  * free with CSLDestroy()
   13038             :  * @since GDAL 3.10
   13039             :  */
   13040           4 : char **OSRGetAuthorityListFromDatabase()
   13041             : {
   13042             :     PROJ_STRING_LIST list =
   13043           4 :         proj_get_authorities_from_database(OSRGetProjTLSContext());
   13044           4 :     if (!list)
   13045             :     {
   13046           0 :         return nullptr;
   13047             :     }
   13048           4 :     int count = 0;
   13049          24 :     while (list[count])
   13050          20 :         ++count;
   13051           4 :     char **res = static_cast<char **>(CPLCalloc(count + 1, sizeof(char *)));
   13052          24 :     for (int i = 0; i < count; ++i)
   13053          20 :         res[i] = CPLStrdup(list[i]);
   13054           4 :     proj_string_list_destroy(list);
   13055           4 :     return res;
   13056             : }
   13057             : 
   13058             : /************************************************************************/
   13059             : /*                    UpdateCoordinateSystemFromGeogCRS()               */
   13060             : /************************************************************************/
   13061             : 
   13062             : /*! @cond Doxygen_Suppress */
   13063             : /** \brief Used by gt_wkt_srs.cpp to create projected 3D CRS. Internal use only
   13064             :  *
   13065             :  * @since GDAL 3.1
   13066             :  */
   13067           1 : void OGRSpatialReference::UpdateCoordinateSystemFromGeogCRS()
   13068             : {
   13069           1 :     TAKE_OPTIONAL_LOCK();
   13070             : 
   13071           1 :     d->refreshProjObj();
   13072           1 :     if (!d->m_pj_crs)
   13073           0 :         return;
   13074           1 :     if (d->m_pjType != PJ_TYPE_PROJECTED_CRS)
   13075           0 :         return;
   13076           1 :     if (GetAxesCount() == 3)
   13077           0 :         return;
   13078           1 :     auto ctxt = d->getPROJContext();
   13079           1 :     auto baseCRS = proj_crs_get_geodetic_crs(ctxt, d->m_pj_crs);
   13080           1 :     if (!baseCRS)
   13081           0 :         return;
   13082           1 :     auto baseCRSCS = proj_crs_get_coordinate_system(ctxt, baseCRS);
   13083           1 :     if (!baseCRSCS)
   13084             :     {
   13085           0 :         proj_destroy(baseCRS);
   13086           0 :         return;
   13087             :     }
   13088           1 :     if (proj_cs_get_axis_count(ctxt, baseCRSCS) != 3)
   13089             :     {
   13090           0 :         proj_destroy(baseCRSCS);
   13091           0 :         proj_destroy(baseCRS);
   13092           0 :         return;
   13093             :     }
   13094           1 :     auto projCS = proj_crs_get_coordinate_system(ctxt, d->m_pj_crs);
   13095           1 :     if (!projCS || proj_cs_get_axis_count(ctxt, projCS) != 2)
   13096             :     {
   13097           0 :         proj_destroy(baseCRSCS);
   13098           0 :         proj_destroy(baseCRS);
   13099           0 :         proj_destroy(projCS);
   13100           0 :         return;
   13101             :     }
   13102             : 
   13103             :     PJ_AXIS_DESCRIPTION axis[3];
   13104           4 :     for (int i = 0; i < 3; i++)
   13105             :     {
   13106           3 :         const char *name = nullptr;
   13107           3 :         const char *abbreviation = nullptr;
   13108           3 :         const char *direction = nullptr;
   13109           3 :         double unit_conv_factor = 0;
   13110           3 :         const char *unit_name = nullptr;
   13111           3 :         proj_cs_get_axis_info(ctxt, i < 2 ? projCS : baseCRSCS, i, &name,
   13112             :                               &abbreviation, &direction, &unit_conv_factor,
   13113             :                               &unit_name, nullptr, nullptr);
   13114           3 :         axis[i].name = CPLStrdup(name);
   13115           3 :         axis[i].abbreviation = CPLStrdup(abbreviation);
   13116           3 :         axis[i].direction = CPLStrdup(direction);
   13117           3 :         axis[i].unit_name = CPLStrdup(unit_name);
   13118           3 :         axis[i].unit_conv_factor = unit_conv_factor;
   13119           3 :         axis[i].unit_type = PJ_UT_LINEAR;
   13120             :     }
   13121           1 :     proj_destroy(baseCRSCS);
   13122           1 :     proj_destroy(projCS);
   13123           1 :     auto cs = proj_create_cs(ctxt, PJ_CS_TYPE_CARTESIAN, 3, axis);
   13124           4 :     for (int i = 0; i < 3; i++)
   13125             :     {
   13126           3 :         CPLFree(axis[i].name);
   13127           3 :         CPLFree(axis[i].abbreviation);
   13128           3 :         CPLFree(axis[i].direction);
   13129           3 :         CPLFree(axis[i].unit_name);
   13130             :     }
   13131           1 :     if (!cs)
   13132             :     {
   13133           0 :         proj_destroy(baseCRS);
   13134           0 :         return;
   13135             :     }
   13136           1 :     auto conversion = proj_crs_get_coordoperation(ctxt, d->m_pj_crs);
   13137           1 :     auto crs = proj_create_projected_crs(ctxt, d->getProjCRSName(), baseCRS,
   13138             :                                          conversion, cs);
   13139           1 :     proj_destroy(baseCRS);
   13140           1 :     proj_destroy(conversion);
   13141           1 :     proj_destroy(cs);
   13142           1 :     d->setPjCRS(crs);
   13143             : }
   13144             : 
   13145             : /*! @endcond */
   13146             : 
   13147             : /************************************************************************/
   13148             : /*                             PromoteTo3D()                            */
   13149             : /************************************************************************/
   13150             : 
   13151             : /** \brief "Promotes" a 2D CRS to a 3D CRS one.
   13152             :  *
   13153             :  * The new axis will be ellipsoidal height, oriented upwards, and with metre
   13154             :  * units.
   13155             :  *
   13156             :  * @param pszName New name for the CRS. If set to NULL, the previous name will
   13157             :  * be used.
   13158             :  * @return OGRERR_NONE if no error occurred.
   13159             :  * @since GDAL 3.1 and PROJ 6.3
   13160             :  */
   13161          42 : OGRErr OGRSpatialReference::PromoteTo3D(const char *pszName)
   13162             : {
   13163          84 :     TAKE_OPTIONAL_LOCK();
   13164             : 
   13165          42 :     d->refreshProjObj();
   13166          42 :     if (!d->m_pj_crs)
   13167           0 :         return OGRERR_FAILURE;
   13168             :     auto newPj =
   13169          42 :         proj_crs_promote_to_3D(d->getPROJContext(), pszName, d->m_pj_crs);
   13170          42 :     if (!newPj)
   13171           0 :         return OGRERR_FAILURE;
   13172          42 :     d->setPjCRS(newPj);
   13173          42 :     return OGRERR_NONE;
   13174             : }
   13175             : 
   13176             : /************************************************************************/
   13177             : /*                             OSRPromoteTo3D()                         */
   13178             : /************************************************************************/
   13179             : 
   13180             : /** \brief "Promotes" a 2D CRS to a 3D CRS one.
   13181             :  *
   13182             :  * See OGRSpatialReference::PromoteTo3D()
   13183             :  *
   13184             :  * @since GDAL 3.1 and PROJ 6.3
   13185             :  */
   13186           3 : OGRErr OSRPromoteTo3D(OGRSpatialReferenceH hSRS, const char *pszName)
   13187             : {
   13188           3 :     VALIDATE_POINTER1(hSRS, "OSRPromoteTo3D", OGRERR_FAILURE);
   13189             : 
   13190           3 :     return OGRSpatialReference::FromHandle(hSRS)->PromoteTo3D(pszName);
   13191             : }
   13192             : 
   13193             : /************************************************************************/
   13194             : /*                             DemoteTo2D()                             */
   13195             : /************************************************************************/
   13196             : 
   13197             : /** \brief "Demote" a 3D CRS to a 2D CRS one.
   13198             :  *
   13199             :  * @param pszName New name for the CRS. If set to NULL, the previous name will
   13200             :  * be used.
   13201             :  * @return OGRERR_NONE if no error occurred.
   13202             :  * @since GDAL 3.2 and PROJ 6.3
   13203             :  */
   13204          45 : OGRErr OGRSpatialReference::DemoteTo2D(const char *pszName)
   13205             : {
   13206          90 :     TAKE_OPTIONAL_LOCK();
   13207             : 
   13208          45 :     d->refreshProjObj();
   13209          45 :     if (!d->m_pj_crs)
   13210           0 :         return OGRERR_FAILURE;
   13211             :     auto newPj =
   13212          45 :         proj_crs_demote_to_2D(d->getPROJContext(), pszName, d->m_pj_crs);
   13213          45 :     if (!newPj)
   13214           0 :         return OGRERR_FAILURE;
   13215          45 :     d->setPjCRS(newPj);
   13216          45 :     return OGRERR_NONE;
   13217             : }
   13218             : 
   13219             : /************************************************************************/
   13220             : /*                             OSRDemoteTo2D()                          */
   13221             : /************************************************************************/
   13222             : 
   13223             : /** \brief "Demote" a 3D CRS to a 2D CRS one.
   13224             :  *
   13225             :  * See OGRSpatialReference::DemoteTo2D()
   13226             :  *
   13227             :  * @since GDAL 3.2 and PROJ 6.3
   13228             :  */
   13229           1 : OGRErr OSRDemoteTo2D(OGRSpatialReferenceH hSRS, const char *pszName)
   13230             : {
   13231           1 :     VALIDATE_POINTER1(hSRS, "OSRDemoteTo2D", OGRERR_FAILURE);
   13232             : 
   13233           1 :     return OGRSpatialReference::FromHandle(hSRS)->DemoteTo2D(pszName);
   13234             : }
   13235             : 
   13236             : /************************************************************************/
   13237             : /*                           GetEPSGGeogCS()                            */
   13238             : /************************************************************************/
   13239             : 
   13240             : /** Try to establish what the EPSG code for this coordinate systems
   13241             :  * GEOGCS might be.  Returns -1 if no reasonable guess can be made.
   13242             :  *
   13243             :  * @return EPSG code
   13244             :  */
   13245             : 
   13246         342 : int OGRSpatialReference::GetEPSGGeogCS() const
   13247             : 
   13248             : {
   13249         684 :     TAKE_OPTIONAL_LOCK();
   13250             : 
   13251             :     /* -------------------------------------------------------------------- */
   13252             :     /*      Check axis order.                                               */
   13253             :     /* -------------------------------------------------------------------- */
   13254         684 :     auto poGeogCRS = std::unique_ptr<OGRSpatialReference>(CloneGeogCS());
   13255         342 :     if (!poGeogCRS)
   13256           0 :         return -1;
   13257             : 
   13258         342 :     bool ret = false;
   13259         342 :     poGeogCRS->d->demoteFromBoundCRS();
   13260         342 :     auto cs = proj_crs_get_coordinate_system(d->getPROJContext(),
   13261         342 :                                              poGeogCRS->d->m_pj_crs);
   13262         342 :     poGeogCRS->d->undoDemoteFromBoundCRS();
   13263         342 :     if (cs)
   13264             :     {
   13265         342 :         const char *pszDirection = nullptr;
   13266         342 :         if (proj_cs_get_axis_info(d->getPROJContext(), cs, 0, nullptr, nullptr,
   13267             :                                   &pszDirection, nullptr, nullptr, nullptr,
   13268         342 :                                   nullptr))
   13269             :         {
   13270         342 :             if (EQUAL(pszDirection, "north"))
   13271             :             {
   13272         143 :                 ret = true;
   13273             :             }
   13274             :         }
   13275             : 
   13276         342 :         proj_destroy(cs);
   13277             :     }
   13278         342 :     if (!ret)
   13279         199 :         return -1;
   13280             : 
   13281             :     /* -------------------------------------------------------------------- */
   13282             :     /*      Do we already have it?                                          */
   13283             :     /* -------------------------------------------------------------------- */
   13284         143 :     const char *pszAuthName = GetAuthorityName("GEOGCS");
   13285         143 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg"))
   13286          66 :         return atoi(GetAuthorityCode("GEOGCS"));
   13287             : 
   13288             :     /* -------------------------------------------------------------------- */
   13289             :     /*      Get the datum and geogcs names.                                 */
   13290             :     /* -------------------------------------------------------------------- */
   13291             : 
   13292          77 :     const char *pszGEOGCS = GetAttrValue("GEOGCS");
   13293          77 :     const char *pszDatum = GetAttrValue("DATUM");
   13294             : 
   13295             :     // We can only operate on coordinate systems with a geogcs.
   13296         154 :     OGRSpatialReference oSRSTmp;
   13297          77 :     if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13298             :     {
   13299             :         // Calling GetAttrValue("GEOGCS") will fail on a CRS that can't be
   13300             :         // export to WKT1, so try to extract the geographic CRS through PROJ
   13301             :         // API with CopyGeogCSFrom() and get the nodes' values from it.
   13302           1 :         oSRSTmp.CopyGeogCSFrom(this);
   13303           1 :         pszGEOGCS = oSRSTmp.GetAttrValue("GEOGCS");
   13304           1 :         pszDatum = oSRSTmp.GetAttrValue("DATUM");
   13305           1 :         if (pszGEOGCS == nullptr || pszDatum == nullptr)
   13306             :         {
   13307           0 :             return -1;
   13308             :         }
   13309             :     }
   13310             : 
   13311             :     // Lookup geographic CRS name
   13312          77 :     const PJ_TYPE type = PJ_TYPE_GEOGRAPHIC_2D_CRS;
   13313          77 :     PJ_OBJ_LIST *list = proj_create_from_name(
   13314             :         d->getPROJContext(), nullptr, pszGEOGCS, &type, 1, false, 1, nullptr);
   13315          77 :     if (list)
   13316             :     {
   13317          77 :         const auto listSize = proj_list_get_count(list);
   13318          77 :         if (listSize == 1)
   13319             :         {
   13320          49 :             auto crs = proj_list_get(d->getPROJContext(), list, 0);
   13321          49 :             if (crs)
   13322             :             {
   13323          49 :                 pszAuthName = proj_get_id_auth_name(crs, 0);
   13324          49 :                 const char *pszCode = proj_get_id_code(crs, 0);
   13325          49 :                 if (pszAuthName && pszCode && EQUAL(pszAuthName, "EPSG"))
   13326             :                 {
   13327          47 :                     const int nCode = atoi(pszCode);
   13328          47 :                     proj_destroy(crs);
   13329          47 :                     proj_list_destroy(list);
   13330          47 :                     return nCode;
   13331             :                 }
   13332           2 :                 proj_destroy(crs);
   13333             :             }
   13334             :         }
   13335          30 :         proj_list_destroy(list);
   13336             :     }
   13337             : 
   13338             :     /* -------------------------------------------------------------------- */
   13339             :     /*      Is this a "well known" geographic coordinate system?            */
   13340             :     /* -------------------------------------------------------------------- */
   13341          90 :     const bool bWGS = strstr(pszGEOGCS, "WGS") != nullptr ||
   13342          30 :                       strstr(pszDatum, "WGS") ||
   13343          30 :                       strstr(pszGEOGCS, "World Geodetic System") ||
   13344          30 :                       strstr(pszGEOGCS, "World_Geodetic_System") ||
   13345          90 :                       strstr(pszDatum, "World Geodetic System") ||
   13346          30 :                       strstr(pszDatum, "World_Geodetic_System");
   13347             : 
   13348          90 :     const bool bNAD = strstr(pszGEOGCS, "NAD") != nullptr ||
   13349          30 :                       strstr(pszDatum, "NAD") ||
   13350          30 :                       strstr(pszGEOGCS, "North American") ||
   13351          30 :                       strstr(pszGEOGCS, "North_American") ||
   13352          90 :                       strstr(pszDatum, "North American") ||
   13353          30 :                       strstr(pszDatum, "North_American");
   13354             : 
   13355          30 :     if (bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")))
   13356           0 :         return 4326;
   13357             : 
   13358          30 :     if (bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")))
   13359           0 :         return 4322;
   13360             : 
   13361             :     // This is questionable as there are several 'flavors' of NAD83 that
   13362             :     // are not the same as 4269
   13363          30 :     if (bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")))
   13364           0 :         return 4269;
   13365             : 
   13366          30 :     if (bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")))
   13367           0 :         return 4267;
   13368             : 
   13369             :     /* -------------------------------------------------------------------- */
   13370             :     /*      If we know the datum, associate the most likely GCS with        */
   13371             :     /*      it.                                                             */
   13372             :     /* -------------------------------------------------------------------- */
   13373          30 :     const OGRSpatialReference &oActiveObj = oSRSTmp.IsEmpty() ? *this : oSRSTmp;
   13374          30 :     pszAuthName = oActiveObj.GetAuthorityName("GEOGCS|DATUM");
   13375          30 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") &&
   13376           0 :         GetPrimeMeridian() == 0.0)
   13377             :     {
   13378           0 :         const int nDatum = atoi(oActiveObj.GetAuthorityCode("GEOGCS|DATUM"));
   13379             : 
   13380           0 :         if (nDatum >= 6000 && nDatum <= 6999)
   13381           0 :             return nDatum - 2000;
   13382             :     }
   13383             : 
   13384          30 :     return -1;
   13385             : }
   13386             : 
   13387             : /************************************************************************/
   13388             : /*                          SetCoordinateEpoch()                        */
   13389             : /************************************************************************/
   13390             : 
   13391             : /** Set the coordinate epoch, as decimal year.
   13392             :  *
   13393             :  * In a dynamic CRS, coordinates of a point on the surface of the Earth may
   13394             :  * change with time. To be unambiguous the coordinates must always be qualified
   13395             :  * with the epoch at which they are valid. The coordinate epoch is not
   13396             :  * necessarily the epoch at which the observation was collected.
   13397             :  *
   13398             :  * Pedantically the coordinate epoch of an observation belongs to the
   13399             :  * observation, and not to the CRS, however it is often more practical to
   13400             :  * bind it to the CRS. The coordinate epoch should be specified for dynamic
   13401             :  * CRS (see IsDynamic())
   13402             :  *
   13403             :  * This method is the same as the OSRSetCoordinateEpoch() function.
   13404             :  *
   13405             :  * @param dfCoordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3)
   13406             :  * @since OGR 3.4
   13407             :  */
   13408             : 
   13409         831 : void OGRSpatialReference::SetCoordinateEpoch(double dfCoordinateEpoch)
   13410             : {
   13411         831 :     d->m_coordinateEpoch = dfCoordinateEpoch;
   13412         831 : }
   13413             : 
   13414             : /************************************************************************/
   13415             : /*                      OSRSetCoordinateEpoch()                         */
   13416             : /************************************************************************/
   13417             : 
   13418             : /** \brief Set the coordinate epoch, as decimal year.
   13419             :  *
   13420             :  * See OGRSpatialReference::SetCoordinateEpoch()
   13421             :  *
   13422             :  * @since OGR 3.4
   13423             :  */
   13424          31 : void OSRSetCoordinateEpoch(OGRSpatialReferenceH hSRS, double dfCoordinateEpoch)
   13425             : {
   13426          31 :     VALIDATE_POINTER0(hSRS, "OSRSetCoordinateEpoch");
   13427             : 
   13428          31 :     return OGRSpatialReference::FromHandle(hSRS)->SetCoordinateEpoch(
   13429          31 :         dfCoordinateEpoch);
   13430             : }
   13431             : 
   13432             : /************************************************************************/
   13433             : /*                          GetCoordinateEpoch()                        */
   13434             : /************************************************************************/
   13435             : 
   13436             : /** Return the coordinate epoch, as decimal year.
   13437             :  *
   13438             :  * In a dynamic CRS, coordinates of a point on the surface of the Earth may
   13439             :  * change with time. To be unambiguous the coordinates must always be qualified
   13440             :  * with the epoch at which they are valid. The coordinate epoch is not
   13441             :  * necessarily the epoch at which the observation was collected.
   13442             :  *
   13443             :  * Pedantically the coordinate epoch of an observation belongs to the
   13444             :  * observation, and not to the CRS, however it is often more practical to
   13445             :  * bind it to the CRS. The coordinate epoch should be specified for dynamic
   13446             :  * CRS (see IsDynamic())
   13447             :  *
   13448             :  * This method is the same as the OSRGetCoordinateEpoch() function.
   13449             :  *
   13450             :  * @return coordinateEpoch Coordinate epoch as decimal year (e.g. 2021.3), or 0
   13451             :  *                         if not set, or relevant.
   13452             :  * @since OGR 3.4
   13453             :  */
   13454             : 
   13455       12308 : double OGRSpatialReference::GetCoordinateEpoch() const
   13456             : {
   13457       12308 :     return d->m_coordinateEpoch;
   13458             : }
   13459             : 
   13460             : /************************************************************************/
   13461             : /*                      OSRGetCoordinateEpoch()                        */
   13462             : /************************************************************************/
   13463             : 
   13464             : /** \brief Get the coordinate epoch, as decimal year.
   13465             :  *
   13466             :  * See OGRSpatialReference::GetCoordinateEpoch()
   13467             :  *
   13468             :  * @since OGR 3.4
   13469             :  */
   13470         665 : double OSRGetCoordinateEpoch(OGRSpatialReferenceH hSRS)
   13471             : {
   13472         665 :     VALIDATE_POINTER1(hSRS, "OSRGetCoordinateEpoch", 0);
   13473             : 
   13474         665 :     return OGRSpatialReference::FromHandle(hSRS)->GetCoordinateEpoch();
   13475             : }

Generated by: LCOV version 1.14