LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdf_sentinel3_sral_mwr.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 219 227 96.5 %
Date: 2024-05-04 12:52:34 Functions: 14 14 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  netCDF read/write Driver
       4             :  * Purpose:  Sentinel 3 SRAL/MWR Level 2 products
       5             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : // Example product:
      30             : // https://scihub.copernicus.eu/dhus/odata/v1/Products('65b615b0-0db9-4ced-8020-eb17818f0c26')/$value
      31             : // Specification:
      32             : // https://sentinel.esa.int/documents/247904/2753172/Sentinel-3-Product-Data-Format-Specification-Level-2-Land
      33             : 
      34             : #include "netcdfdataset.h"
      35             : 
      36             : #include <limits>
      37             : 
      38             : /************************************************************************/
      39             : /*                      Sentinel3_SRAL_MWR_Layer                        */
      40             : /************************************************************************/
      41             : 
      42             : class Sentinel3_SRAL_MWR_Layer final : public OGRLayer
      43             : {
      44             :     OGRFeatureDefn *m_poFDefn = nullptr;
      45             :     int m_cdfid;
      46             :     size_t m_nCurIdx = 0;
      47             :     size_t m_nFeatureCount = 0;
      48             :     CPLStringList m_aosMetadata{};
      49             : 
      50             :     struct VariableInfo
      51             :     {
      52             :         int varid;
      53             :         nc_type nctype;
      54             :         double scale;
      55             :         double offset;
      56             :         double nodata;
      57             :     };
      58             : 
      59             :     std::vector<VariableInfo> m_asVarInfo{};
      60             :     int m_iLongitude = -1;
      61             :     int m_iLatitude = -1;
      62             :     double m_dfLongScale = 1.0;
      63             :     double m_dfLongOffset = 0.0;
      64             :     double m_dfLatScale = 1.0;
      65             :     double m_dfLatOffset = 0.0;
      66             : 
      67             :     OGRFeature *TranslateFeature(size_t nIndex);
      68             :     OGRFeature *GetNextRawFeature();
      69             : 
      70             :   public:
      71             :     Sentinel3_SRAL_MWR_Layer(const std::string &name, int cdfid, int dimid);
      72             :     ~Sentinel3_SRAL_MWR_Layer();
      73             : 
      74          10 :     OGRFeatureDefn *GetLayerDefn() override
      75             :     {
      76          10 :         return m_poFDefn;
      77             :     }
      78             : 
      79             :     void ResetReading() override;
      80             :     OGRFeature *GetNextFeature() override;
      81             :     OGRFeature *GetFeature(GIntBig nFID) override;
      82             :     GIntBig GetFeatureCount(int bForce) override;
      83             :     int TestCapability(const char *pszCap) override;
      84             :     char **GetMetadata(const char *pszDomain) override;
      85             :     const char *GetMetadataItem(const char *pszKey,
      86             :                                 const char *pszDomain) override;
      87             : };
      88             : 
      89             : /************************************************************************/
      90             : /*                      Sentinel3_SRAL_MWR_Layer()                      */
      91             : /************************************************************************/
      92             : 
      93           3 : Sentinel3_SRAL_MWR_Layer::Sentinel3_SRAL_MWR_Layer(const std::string &name,
      94           3 :                                                    int cdfid, int dimid)
      95           3 :     : m_cdfid(cdfid)
      96             : {
      97           3 :     m_poFDefn = new OGRFeatureDefn(name.c_str());
      98           3 :     m_poFDefn->SetGeomType(wkbPoint);
      99           3 :     m_poFDefn->Reference();
     100           3 :     SetDescription(name.c_str());
     101             : 
     102           3 :     nc_inq_dimlen(cdfid, dimid, &m_nFeatureCount);
     103             : 
     104           3 :     int nVars = 0;
     105           3 :     NCDF_ERR(nc_inq(cdfid, nullptr, &nVars, nullptr, nullptr));
     106          45 :     for (int iVar = 0; iVar < nVars; iVar++)
     107             :     {
     108          42 :         int nVarDims = 0;
     109          42 :         NCDF_ERR(nc_inq_varndims(cdfid, iVar, &nVarDims));
     110          42 :         if (nVarDims != 1)
     111          34 :             continue;
     112             : 
     113          42 :         int vardimid = -1;
     114          42 :         NCDF_ERR(nc_inq_vardimid(cdfid, iVar, &vardimid));
     115          42 :         if (vardimid != dimid)
     116          28 :             continue;
     117             : 
     118          14 :         char szVarName[NC_MAX_NAME + 1] = {};
     119          14 :         NCDF_ERR(nc_inq_varname(cdfid, iVar, szVarName));
     120             : 
     121          14 :         nc_type vartype = NC_NAT;
     122          14 :         nc_inq_vartype(cdfid, iVar, &vartype);
     123             : 
     124          14 :         int nbAttr = 0;
     125          14 :         NCDF_ERR(nc_inq_varnatts(cdfid, iVar, &nbAttr));
     126          14 :         std::string scaleFactor;
     127          14 :         std::string offset;
     128          14 :         std::string fillValue;
     129          14 :         CPLStringList aosMetadata;
     130          93 :         for (int iAttr = 0; iAttr < nbAttr; iAttr++)
     131             :         {
     132             :             char szAttrName[NC_MAX_NAME + 1];
     133          79 :             szAttrName[0] = 0;
     134          79 :             NCDF_ERR(nc_inq_attname(cdfid, iVar, iAttr, szAttrName));
     135          79 :             char *pszMetaTemp = nullptr;
     136         158 :             if (NCDFGetAttr(cdfid, iVar, szAttrName, &pszMetaTemp) == CE_None &&
     137          79 :                 pszMetaTemp)
     138             :             {
     139          79 :                 if (EQUAL(szAttrName, "scale_factor"))
     140             :                 {
     141           9 :                     scaleFactor = pszMetaTemp;
     142             :                 }
     143          70 :                 else if (EQUAL(szAttrName, "add_offset"))
     144             :                 {
     145           9 :                     offset = pszMetaTemp;
     146             :                 }
     147          61 :                 else if (EQUAL(szAttrName, "_FillValue"))
     148             :                 {
     149           5 :                     fillValue = pszMetaTemp;
     150             :                 }
     151          56 :                 else if (!EQUAL(szAttrName, "coordinates"))
     152             :                 {
     153          51 :                     aosMetadata.SetNameValue(szAttrName, pszMetaTemp);
     154             :                 }
     155             :             }
     156          79 :             CPLFree(pszMetaTemp);
     157             :         }
     158             : 
     159             :         const char *pszStandardName =
     160          14 :             aosMetadata.FetchNameValue("standard_name");
     161          14 :         if (pszStandardName)
     162             :         {
     163          10 :             if (EQUAL(pszStandardName, "longitude") && vartype == NC_INT)
     164             :             {
     165           3 :                 m_iLongitude = iVar;
     166           3 :                 if (!scaleFactor.empty())
     167           3 :                     m_dfLongScale = CPLAtof(scaleFactor.c_str());
     168           3 :                 if (!offset.empty())
     169           3 :                     m_dfLongOffset = CPLAtof(offset.c_str());
     170           3 :                 continue;
     171             :             }
     172           7 :             if (EQUAL(pszStandardName, "latitude") && vartype == NC_INT)
     173             :             {
     174           3 :                 m_iLatitude = iVar;
     175           3 :                 if (!scaleFactor.empty())
     176           3 :                     m_dfLatScale = CPLAtof(scaleFactor.c_str());
     177           3 :                 if (!offset.empty())
     178           3 :                     m_dfLatOffset = CPLAtof(offset.c_str());
     179           3 :                 continue;
     180             :             }
     181             :         }
     182             : 
     183          35 :         for (int i = 0; i < aosMetadata.size(); i++)
     184             :             m_aosMetadata.AddString(
     185          27 :                 (std::string(szVarName) + '_' + aosMetadata[i]).c_str());
     186             : 
     187           8 :         OGRFieldType eType = OFTReal;
     188           8 :         if (!scaleFactor.empty())
     189             :         {
     190             :             // do nothing
     191             :         }
     192           5 :         else if (!offset.empty())
     193             :         {
     194             :             // do nothing
     195             :         }
     196           5 :         else if (vartype == NC_BYTE || vartype == NC_SHORT ||
     197           4 :                  vartype == NC_INT || vartype == NC_USHORT ||
     198           3 :                  vartype == NC_UINT)
     199             :         {
     200           2 :             eType = OFTInteger;
     201             :         }
     202          16 :         OGRFieldDefn oField(szVarName, eType);
     203           8 :         m_poFDefn->AddFieldDefn(&oField);
     204             :         VariableInfo varInfo;
     205           8 :         varInfo.varid = iVar;
     206           8 :         varInfo.nctype = vartype;
     207           8 :         varInfo.scale =
     208           8 :             scaleFactor.empty() ? 1.0 : CPLAtof(scaleFactor.c_str());
     209           8 :         varInfo.offset = offset.empty() ? 0.0 : CPLAtof(offset.c_str());
     210           8 :         varInfo.nodata = fillValue.empty()
     211           8 :                              ? std::numeric_limits<double>::quiet_NaN()
     212           5 :                              : CPLAtof(fillValue.c_str());
     213           8 :         m_asVarInfo.emplace_back(varInfo);
     214             :     }
     215           3 : }
     216             : 
     217             : /************************************************************************/
     218             : /*                     ~Sentinel3_SRAL_MWR_Layer()                      */
     219             : /************************************************************************/
     220             : 
     221           6 : Sentinel3_SRAL_MWR_Layer::~Sentinel3_SRAL_MWR_Layer()
     222             : {
     223           3 :     m_poFDefn->Release();
     224           6 : }
     225             : 
     226             : /************************************************************************/
     227             : /*                           GetMetadata()                              */
     228             : /************************************************************************/
     229             : 
     230           1 : char **Sentinel3_SRAL_MWR_Layer::GetMetadata(const char *pszDomain)
     231             : {
     232           1 :     if (pszDomain == nullptr || EQUAL(pszDomain, ""))
     233           1 :         return m_aosMetadata.List();
     234           0 :     return OGRLayer::GetMetadata(pszDomain);
     235             : }
     236             : 
     237             : /************************************************************************/
     238             : /*                           GetMetadataItem()                          */
     239             : /************************************************************************/
     240             : 
     241           1 : const char *Sentinel3_SRAL_MWR_Layer::GetMetadataItem(const char *pszKey,
     242             :                                                       const char *pszDomain)
     243             : {
     244           1 :     if (pszDomain == nullptr || EQUAL(pszDomain, ""))
     245           1 :         return m_aosMetadata.FetchNameValue(pszKey);
     246           0 :     return OGRLayer::GetMetadataItem(pszKey, pszDomain);
     247             : }
     248             : 
     249             : /************************************************************************/
     250             : /*                           ResetReading()                             */
     251             : /************************************************************************/
     252             : 
     253           8 : void Sentinel3_SRAL_MWR_Layer::ResetReading()
     254             : {
     255           8 :     m_nCurIdx = 0;
     256           8 : }
     257             : 
     258             : /************************************************************************/
     259             : /*                          GetFeatureCount()                           */
     260             : /************************************************************************/
     261             : 
     262           2 : GIntBig Sentinel3_SRAL_MWR_Layer::GetFeatureCount(int bForce)
     263             : {
     264           2 :     if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
     265           1 :         return m_nFeatureCount;
     266           1 :     return OGRLayer::GetFeatureCount(bForce);
     267             : }
     268             : 
     269             : /************************************************************************/
     270             : /*                           TestCapability()                           */
     271             : /************************************************************************/
     272             : 
     273           4 : int Sentinel3_SRAL_MWR_Layer::TestCapability(const char *pszCap)
     274             : {
     275           4 :     if (EQUAL(pszCap, OLCFastFeatureCount))
     276           1 :         return m_poFilterGeom == nullptr && m_poAttrQuery == nullptr;
     277           3 :     if (EQUAL(pszCap, OLCRandomRead))
     278           1 :         return true;
     279           2 :     return false;
     280             : }
     281             : 
     282             : /************************************************************************/
     283             : /*                        TranslateFeature()                            */
     284             : /************************************************************************/
     285             : 
     286          12 : OGRFeature *Sentinel3_SRAL_MWR_Layer::TranslateFeature(size_t nIndex)
     287             : {
     288          12 :     OGRFeature *poFeat = new OGRFeature(m_poFDefn);
     289          12 :     poFeat->SetFID(nIndex + 1);
     290          12 :     if (m_iLongitude >= 0 && m_iLatitude >= 0)
     291             :     {
     292          12 :         int nLong = 0;
     293          12 :         int status = nc_get_var1_int(m_cdfid, m_iLongitude, &nIndex, &nLong);
     294          12 :         if (status == NC_NOERR)
     295             :         {
     296          12 :             int nLat = 0;
     297          12 :             status = nc_get_var1_int(m_cdfid, m_iLatitude, &nIndex, &nLat);
     298          12 :             if (status == NC_NOERR)
     299             :             {
     300          12 :                 const double dfLong = nLong * m_dfLongScale + m_dfLongOffset;
     301          12 :                 const double dfLat = nLat * m_dfLatScale + m_dfLatOffset;
     302          12 :                 auto poGeom = new OGRPoint(dfLong, dfLat);
     303          12 :                 auto poGeomField = m_poFDefn->GetGeomFieldDefn(0);
     304          12 :                 poGeom->assignSpatialReference(poGeomField->GetSpatialRef());
     305          12 :                 poFeat->SetGeometryDirectly(poGeom);
     306             :             }
     307             :         }
     308             :     }
     309             : 
     310          66 :     for (int i = 0; i < static_cast<int>(m_asVarInfo.size()); i++)
     311             :     {
     312          54 :         if (m_asVarInfo[i].nctype == NC_BYTE)
     313             :         {
     314          10 :             signed char nVal = 0;
     315          10 :             int status = nc_get_var1_schar(m_cdfid, m_asVarInfo[i].varid,
     316             :                                            &nIndex, &nVal);
     317          10 :             if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
     318             :             {
     319           4 :                 poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
     320           4 :                                         m_asVarInfo[i].offset);
     321             :             }
     322             :         }
     323          44 :         else if (m_asVarInfo[i].nctype == NC_SHORT)
     324             :         {
     325          10 :             short nVal = 0;
     326          10 :             int status = nc_get_var1_short(m_cdfid, m_asVarInfo[i].varid,
     327             :                                            &nIndex, &nVal);
     328          10 :             if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
     329             :             {
     330           4 :                 poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
     331           4 :                                         m_asVarInfo[i].offset);
     332             :             }
     333             :         }
     334          34 :         else if (m_asVarInfo[i].nctype == NC_USHORT)
     335             :         {
     336           2 :             unsigned short nVal = 0;
     337           2 :             int status = nc_get_var1_ushort(m_cdfid, m_asVarInfo[i].varid,
     338             :                                             &nIndex, &nVal);
     339           2 :             if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
     340             :             {
     341           1 :                 poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
     342           1 :                                         m_asVarInfo[i].offset);
     343             :             }
     344             :         }
     345          32 :         else if (m_asVarInfo[i].nctype == NC_INT)
     346             :         {
     347          10 :             int nVal = 0;
     348             :             int status =
     349          10 :                 nc_get_var1_int(m_cdfid, m_asVarInfo[i].varid, &nIndex, &nVal);
     350          10 :             if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
     351             :             {
     352           4 :                 poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
     353           4 :                                         m_asVarInfo[i].offset);
     354             :             }
     355             :         }
     356          22 :         else if (m_asVarInfo[i].nctype == NC_UINT)
     357             :         {
     358          10 :             unsigned int nVal = 0;
     359             :             int status =
     360          10 :                 nc_get_var1_uint(m_cdfid, m_asVarInfo[i].varid, &nIndex, &nVal);
     361          10 :             if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
     362             :             {
     363           4 :                 poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
     364           4 :                                         m_asVarInfo[i].offset);
     365             :             }
     366             :         }
     367          12 :         else if (m_asVarInfo[i].nctype == NC_DOUBLE)
     368             :         {
     369          12 :             double dfVal = 0.0;
     370          12 :             int status = nc_get_var1_double(m_cdfid, m_asVarInfo[i].varid,
     371             :                                             &nIndex, &dfVal);
     372          12 :             if (status == NC_NOERR && dfVal != m_asVarInfo[i].nodata)
     373             :             {
     374          12 :                 poFeat->SetField(i, dfVal * m_asVarInfo[i].scale +
     375          12 :                                         m_asVarInfo[i].offset);
     376             :             }
     377             :         }
     378             :         else
     379             :         {
     380           0 :             CPLDebug("netCDF", "Unhandled data type %d for %s",
     381           0 :                      m_asVarInfo[i].nctype,
     382           0 :                      m_poFDefn->GetFieldDefn(i)->GetNameRef());
     383             :         }
     384             :     }
     385             : 
     386          12 :     return poFeat;
     387             : }
     388             : 
     389             : /************************************************************************/
     390             : /*                        GetNextRawFeature()                           */
     391             : /************************************************************************/
     392             : 
     393          16 : OGRFeature *Sentinel3_SRAL_MWR_Layer::GetNextRawFeature()
     394             : {
     395          16 :     if (m_nCurIdx == m_nFeatureCount)
     396           5 :         return nullptr;
     397          11 :     OGRFeature *poFeat = TranslateFeature(m_nCurIdx);
     398          11 :     m_nCurIdx++;
     399          11 :     return poFeat;
     400             : }
     401             : 
     402             : /************************************************************************/
     403             : /*                           GetFeature()                               */
     404             : /************************************************************************/
     405             : 
     406           3 : OGRFeature *Sentinel3_SRAL_MWR_Layer::GetFeature(GIntBig nFID)
     407             : {
     408           3 :     if (nFID <= 0 || static_cast<size_t>(nFID) > m_nFeatureCount)
     409           2 :         return nullptr;
     410           1 :     return TranslateFeature(static_cast<size_t>(nFID - 1));
     411             : }
     412             : 
     413             : /************************************************************************/
     414             : /*                           GetNextFeature()                           */
     415             : /************************************************************************/
     416             : 
     417          16 : OGRFeature *Sentinel3_SRAL_MWR_Layer::GetNextFeature()
     418             : {
     419             :     while (true)
     420             :     {
     421          16 :         OGRFeature *poFeature = GetNextRawFeature();
     422          16 :         if (poFeature == nullptr)
     423           5 :             return nullptr;
     424             : 
     425          26 :         if ((m_poFilterGeom == nullptr ||
     426          18 :              FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter))) &&
     427           7 :             (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
     428           5 :             return poFeature;
     429             : 
     430           6 :         delete poFeature;
     431           6 :     }
     432             : }
     433             : 
     434             : /************************************************************************/
     435             : /*                       ProcessSentinel3_SRAL_MWR()                    */
     436             : /************************************************************************/
     437             : 
     438           1 : void netCDFDataset::ProcessSentinel3_SRAL_MWR()
     439             : {
     440           1 :     int nDimCount = -1;
     441           1 :     int status = nc_inq_ndims(cdfid, &nDimCount);
     442           1 :     NCDF_ERR(status);
     443           1 :     if (status != NC_NOERR || nDimCount == 0 || nDimCount > 1000)
     444           0 :         return;
     445           1 :     std::vector<int> dimIds(nDimCount);
     446           1 :     int nDimCount2 = -1;
     447           1 :     status = nc_inq_dimids(cdfid, &nDimCount2, &dimIds[0], FALSE);
     448           1 :     NCDF_ERR(status);
     449           1 :     if (status != NC_NOERR)
     450           0 :         return;
     451           1 :     CPLAssert(nDimCount == nDimCount2);
     452             : 
     453           1 :     OGRSpatialReference *poSRS = nullptr;
     454             :     const char *pszSemiMajor =
     455           1 :         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#semi_major_ellipsoid_axis");
     456             :     const char *pszFlattening =
     457           1 :         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ellipsoid_flattening");
     458           2 :     if (pszSemiMajor && EQUAL(pszSemiMajor, "6378137") && pszFlattening &&
     459           1 :         fabs(CPLAtof(pszFlattening) - 0.00335281066474748) < 1e-16)
     460             :     {
     461             :         int iAttr =
     462           1 :             CSLFindName(papszMetadata, "NC_GLOBAL#semi_major_ellipsoid_axis");
     463           1 :         if (iAttr >= 0)
     464           1 :             papszMetadata = CSLRemoveStrings(papszMetadata, iAttr, 1, nullptr);
     465           1 :         iAttr = CSLFindName(papszMetadata, "NC_GLOBAL#ellipsoid_flattening");
     466           1 :         if (iAttr >= 0)
     467           1 :             papszMetadata = CSLRemoveStrings(papszMetadata, iAttr, 1, nullptr);
     468           1 :         poSRS = new OGRSpatialReference();
     469           1 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     470           1 :         poSRS->importFromEPSG(4326);
     471             :     }
     472             : 
     473           4 :     for (int i = 0; i < nDimCount; i++)
     474             :     {
     475           3 :         char szDimName[NC_MAX_NAME + 1] = {};
     476           3 :         status = nc_inq_dimname(cdfid, dimIds[i], szDimName);
     477           3 :         NCDF_ERR(status);
     478           3 :         if (status != NC_NOERR)
     479           0 :             break;
     480           6 :         std::string name(CPLGetBasename(GetDescription()));
     481           3 :         name += '_';
     482           3 :         name += szDimName;
     483             :         std::shared_ptr<OGRLayer> poLayer(
     484           9 :             new Sentinel3_SRAL_MWR_Layer(name.c_str(), cdfid, dimIds[i]));
     485           3 :         auto poGeomField = poLayer->GetLayerDefn()->GetGeomFieldDefn(0);
     486           3 :         if (poGeomField)
     487           3 :             poGeomField->SetSpatialRef(poSRS);
     488           3 :         papoLayers.emplace_back(poLayer);
     489             :     }
     490             : 
     491           1 :     if (poSRS)
     492           1 :         poSRS->Release();
     493             : }

Generated by: LCOV version 1.14