LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpx - ogrgpxdatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 375 410 91.5 %
Date: 2026-10-23 13:59:38 Functions: 15 15 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GPX Translator
       4             :  * Purpose:  Implements OGRGPXDataSource class
       5             :  * Author:   Even Rouault, even dot rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2007-2011, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "ogr_gpx.h"
      15             : 
      16             : #include <algorithm>
      17             : #include <cstdarg>
      18             : #include <cstdio>
      19             : #include <cstring>
      20             : 
      21             : #include "cpl_conv.h"
      22             : #include "cpl_csv.h"
      23             : #include "cpl_error.h"
      24             : #include "cpl_string.h"
      25             : #include "cpl_vsi.h"
      26             : #ifdef HAVE_EXPAT
      27             : #include "expat.h"
      28             : #endif
      29             : #include "ogr_core.h"
      30             : #include "ogr_expat.h"
      31             : #include "ogr_spatialref.h"
      32             : #include "ogrsf_frmts.h"
      33             : #include "ogr_p.h"
      34             : 
      35             : constexpr int SPACE_FOR_METADATA_BOUNDS = 160;
      36             : 
      37             : /************************************************************************/
      38             : /*                         ~OGRGPXDataSource()                          */
      39             : /************************************************************************/
      40             : 
      41         108 : OGRGPXDataSource::~OGRGPXDataSource()
      42             : 
      43             : {
      44          54 :     if (m_fpOutput != nullptr)
      45             :     {
      46          24 :         if (m_nLastRteId != -1)
      47           1 :             PrintLine("</rte>");
      48          23 :         else if (m_nLastTrkId != -1)
      49             :         {
      50           2 :             PrintLine("  </trkseg>");
      51           2 :             PrintLine("</trk>");
      52             :         }
      53          24 :         PrintLine("</gpx>");
      54          24 :         if (m_bIsBackSeekable)
      55             :         {
      56             :             /* Write the <bounds> element in the reserved space */
      57          24 :             if (m_dfMinLon <= m_dfMaxLon)
      58             :             {
      59             :                 char szBounds[SPACE_FOR_METADATA_BOUNDS + 1];
      60             :                 int nRet =
      61          11 :                     CPLsnprintf(szBounds, SPACE_FOR_METADATA_BOUNDS,
      62             :                                 "<bounds minlat=\"%.15f\" minlon=\"%.15f\""
      63             :                                 " maxlat=\"%.15f\" maxlon=\"%.15f\"/>",
      64             :                                 m_dfMinLat, m_dfMinLon, m_dfMaxLat, m_dfMaxLon);
      65          11 :                 if (nRet < SPACE_FOR_METADATA_BOUNDS)
      66             :                 {
      67          11 :                     m_fpOutput->Seek(m_nOffsetBounds, SEEK_SET);
      68          11 :                     m_fpOutput->Write(szBounds, 1, strlen(szBounds));
      69             :                 }
      70             :             }
      71             :         }
      72             :     }
      73         108 : }
      74             : 
      75             : /************************************************************************/
      76             : /*                           TestCapability()                           */
      77             : /************************************************************************/
      78             : 
      79          32 : bool OGRGPXDataSource::TestCapability(const char *pszCap) const
      80             : 
      81             : {
      82          32 :     if (EQUAL(pszCap, ODsCCreateLayer))
      83          23 :         return TRUE;
      84           9 :     else if (EQUAL(pszCap, ODsCDeleteLayer))
      85           6 :         return FALSE;
      86           3 :     else if (EQUAL(pszCap, ODsCZGeometries))
      87           0 :         return TRUE;
      88             : 
      89           3 :     return FALSE;
      90             : }
      91             : 
      92             : /************************************************************************/
      93             : /*                              GetLayer()                              */
      94             : /************************************************************************/
      95             : 
      96         149 : const OGRLayer *OGRGPXDataSource::GetLayer(int iLayer) const
      97             : 
      98             : {
      99         149 :     if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
     100           0 :         return nullptr;
     101             : 
     102         149 :     return m_apoLayers[iLayer].get();
     103             : }
     104             : 
     105             : /************************************************************************/
     106             : /*                            ICreateLayer()                            */
     107             : /************************************************************************/
     108             : 
     109             : OGRLayer *
     110          30 : OGRGPXDataSource::ICreateLayer(const char *pszLayerName,
     111             :                                const OGRGeomFieldDefn *poGeomFieldDefn,
     112             :                                CSLConstList papszOptions)
     113             : {
     114             :     GPXGeometryType gpxGeomType;
     115          30 :     const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
     116          30 :     if (eType == wkbPoint || eType == wkbPoint25D)
     117             :     {
     118          10 :         if (EQUAL(pszLayerName, "track_points"))
     119           2 :             gpxGeomType = GPX_TRACK_POINT;
     120           8 :         else if (EQUAL(pszLayerName, "route_points"))
     121           2 :             gpxGeomType = GPX_ROUTE_POINT;
     122             :         else
     123           6 :             gpxGeomType = GPX_WPT;
     124             :     }
     125          20 :     else if (eType == wkbLineString || eType == wkbLineString25D)
     126             :     {
     127             :         const char *pszForceGPXTrack =
     128           5 :             CSLFetchNameValue(papszOptions, "FORCE_GPX_TRACK");
     129           5 :         if (pszForceGPXTrack && CPLTestBool(pszForceGPXTrack))
     130           0 :             gpxGeomType = GPX_TRACK;
     131             :         else
     132           5 :             gpxGeomType = GPX_ROUTE;
     133             :     }
     134          15 :     else if (eType == wkbMultiLineString || eType == wkbMultiLineString25D)
     135             :     {
     136             :         const char *pszForceGPXRoute =
     137           5 :             CSLFetchNameValue(papszOptions, "FORCE_GPX_ROUTE");
     138           5 :         if (pszForceGPXRoute && CPLTestBool(pszForceGPXRoute))
     139           0 :             gpxGeomType = GPX_ROUTE;
     140             :         else
     141           5 :             gpxGeomType = GPX_TRACK;
     142             :     }
     143          10 :     else if (eType == wkbUnknown)
     144             :     {
     145           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     146             :                  "Cannot create GPX layer %s with unknown geometry type",
     147             :                  pszLayerName);
     148           1 :         return nullptr;
     149             :     }
     150             :     else
     151             :     {
     152           9 :         CPLError(CE_Failure, CPLE_NotSupported,
     153             :                  "Geometry type of `%s' not supported in GPX.\n",
     154             :                  OGRGeometryTypeToName(eType));
     155           9 :         return nullptr;
     156             :     }
     157          20 :     m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     158          20 :         GetDescription(), pszLayerName, gpxGeomType, this, true, nullptr));
     159             : 
     160          20 :     return m_apoLayers.back().get();
     161             : }
     162             : 
     163             : #ifdef HAVE_EXPAT
     164             : 
     165             : /************************************************************************/
     166             : /*                      startElementValidateCbk()                       */
     167             : /************************************************************************/
     168             : 
     169        1255 : void OGRGPXDataSource::startElementValidateCbk(const char *pszNameIn,
     170             :                                                const char **ppszAttr)
     171             : {
     172        1255 :     if (m_validity == GPX_VALIDITY_UNKNOWN)
     173             :     {
     174          29 :         if (strcmp(pszNameIn, "gpx") == 0)
     175             :         {
     176          29 :             m_validity = GPX_VALIDITY_VALID;
     177         168 :             for (int i = 0; ppszAttr[i] != nullptr; i += 2)
     178             :             {
     179         139 :                 if (strcmp(ppszAttr[i], "version") == 0)
     180             :                 {
     181          29 :                     m_osVersion = ppszAttr[i + 1];
     182             :                 }
     183         110 :                 else if (strcmp(ppszAttr[i], "xmlns:ogr") == 0)
     184             :                 {
     185           1 :                     m_bUseExtensions = true;
     186             :                 }
     187             :             }
     188             :         }
     189             :         else
     190             :         {
     191           0 :             m_validity = GPX_VALIDITY_INVALID;
     192             :         }
     193             :     }
     194        1226 :     else if (m_validity == GPX_VALIDITY_VALID)
     195             :     {
     196        1226 :         if (m_nDepth == 1 && strcmp(pszNameIn, "metadata") == 0)
     197             :         {
     198          29 :             m_bInMetadata = true;
     199             :         }
     200        1197 :         else if (m_nDepth == 2 && m_bInMetadata)
     201             :         {
     202         159 :             if (strcmp(pszNameIn, "name") == 0)
     203             :             {
     204          17 :                 m_osMetadataKey = "NAME";
     205             :             }
     206         142 :             else if (strcmp(pszNameIn, "desc") == 0)
     207             :             {
     208          17 :                 m_osMetadataKey = "DESCRIPTION";
     209             :             }
     210         125 :             else if (strcmp(pszNameIn, "time") == 0)
     211             :             {
     212          19 :                 m_osMetadataKey = "TIME";
     213             :             }
     214         106 :             else if (strcmp(pszNameIn, "author") == 0)
     215             :             {
     216          14 :                 m_bInMetadataAuthor = true;
     217             :             }
     218          92 :             else if (strcmp(pszNameIn, "keywords") == 0)
     219             :             {
     220          17 :                 m_osMetadataKey = "KEYWORDS";
     221             :             }
     222          75 :             else if (strcmp(pszNameIn, "copyright") == 0)
     223             :             {
     224          14 :                 std::string osAuthor;
     225          28 :                 for (int i = 0; ppszAttr[i] != nullptr; i += 2)
     226             :                 {
     227          14 :                     if (strcmp(ppszAttr[i], "author") == 0)
     228             :                     {
     229          14 :                         osAuthor = ppszAttr[i + 1];
     230             :                     }
     231             :                 }
     232          14 :                 if (!osAuthor.empty())
     233             :                 {
     234          14 :                     SetMetadataItem("COPYRIGHT_AUTHOR", osAuthor.c_str());
     235             :                 }
     236          14 :                 m_bInMetadataCopyright = true;
     237             :             }
     238          61 :             else if (strcmp(pszNameIn, "link") == 0)
     239             :             {
     240          34 :                 ++m_nMetadataLinkCounter;
     241          34 :                 std::string osHref;
     242          68 :                 for (int i = 0; ppszAttr[i] != nullptr; i += 2)
     243             :                 {
     244          34 :                     if (strcmp(ppszAttr[i], "href") == 0)
     245             :                     {
     246          34 :                         osHref = ppszAttr[i + 1];
     247             :                     }
     248             :                 }
     249          34 :                 if (!osHref.empty())
     250             :                 {
     251          34 :                     SetMetadataItem(
     252             :                         CPLSPrintf("LINK_%d_HREF", m_nMetadataLinkCounter),
     253          34 :                         osHref.c_str());
     254             :                 }
     255          34 :                 m_bInMetadataLink = true;
     256         159 :             }
     257             :         }
     258        1038 :         else if (m_nDepth == 3 && m_bInMetadataAuthor)
     259             :         {
     260          42 :             if (strcmp(pszNameIn, "name") == 0)
     261             :             {
     262          14 :                 m_osMetadataKey = "AUTHOR_NAME";
     263             :             }
     264          28 :             else if (strcmp(pszNameIn, "email") == 0)
     265             :             {
     266          28 :                 std::string osId, osDomain;
     267          42 :                 for (int i = 0; ppszAttr[i] != nullptr; i += 2)
     268             :                 {
     269          28 :                     if (strcmp(ppszAttr[i], "id") == 0)
     270             :                     {
     271          14 :                         osId = ppszAttr[i + 1];
     272             :                     }
     273          14 :                     else if (strcmp(ppszAttr[i], "domain") == 0)
     274             :                     {
     275          14 :                         osDomain = ppszAttr[i + 1];
     276             :                     }
     277             :                 }
     278          14 :                 if (!osId.empty() && !osDomain.empty())
     279             :                 {
     280          14 :                     SetMetadataItem("AUTHOR_EMAIL",
     281          14 :                                     osId.append("@").append(osDomain).c_str());
     282             :                 }
     283             :             }
     284          14 :             else if (strcmp(pszNameIn, "link") == 0)
     285             :             {
     286          14 :                 std::string osHref;
     287          28 :                 for (int i = 0; ppszAttr[i] != nullptr; i += 2)
     288             :                 {
     289          14 :                     if (strcmp(ppszAttr[i], "href") == 0)
     290             :                     {
     291          14 :                         osHref = ppszAttr[i + 1];
     292             :                     }
     293             :                 }
     294          14 :                 if (!osHref.empty())
     295             :                 {
     296          14 :                     SetMetadataItem("AUTHOR_LINK_HREF", osHref.c_str());
     297             :                 }
     298          14 :                 m_bInMetadataAuthorLink = true;
     299          42 :             }
     300             :         }
     301         996 :         else if (m_nDepth == 3 && m_bInMetadataCopyright)
     302             :         {
     303          28 :             if (strcmp(pszNameIn, "year") == 0)
     304             :             {
     305          14 :                 m_osMetadataKey = "COPYRIGHT_YEAR";
     306             :             }
     307          14 :             else if (strcmp(pszNameIn, "license") == 0)
     308             :             {
     309          14 :                 m_osMetadataKey = "COPYRIGHT_LICENSE";
     310             :             }
     311             :         }
     312         968 :         else if (m_nDepth == 3 && m_bInMetadataLink)
     313             :         {
     314         314 :             if (strcmp(pszNameIn, "text") == 0)
     315             :             {
     316             :                 m_osMetadataKey =
     317          82 :                     CPLSPrintf("LINK_%d_TEXT", m_nMetadataLinkCounter);
     318             :             }
     319         232 :             else if (strcmp(pszNameIn, "type") == 0)
     320             :             {
     321             :                 m_osMetadataKey =
     322          82 :                     CPLSPrintf("LINK_%d_TYPE", m_nMetadataLinkCounter);
     323             :             }
     324             :         }
     325         654 :         else if (m_nDepth == 4 && m_bInMetadataAuthorLink)
     326             :         {
     327          28 :             if (strcmp(pszNameIn, "text") == 0)
     328             :             {
     329          14 :                 m_osMetadataKey = "AUTHOR_LINK_TEXT";
     330             :             }
     331          14 :             else if (strcmp(pszNameIn, "type") == 0)
     332             :             {
     333          14 :                 m_osMetadataKey = "AUTHOR_LINK_TYPE";
     334             :             }
     335             :         }
     336         626 :         else if (m_nDepth == 2 && strcmp(pszNameIn, "extensions") == 0)
     337             :         {
     338          16 :             m_bUseExtensions = true;
     339             :         }
     340             :     }
     341        1255 :     m_nDepth++;
     342        1255 : }
     343             : 
     344             : /************************************************************************/
     345             : /*                       endElementValidateCbk()                        */
     346             : /************************************************************************/
     347             : 
     348        1255 : void OGRGPXDataSource::endElementValidateCbk(const char * /*pszName */)
     349             : {
     350        1255 :     m_nDepth--;
     351        1255 :     if (m_nDepth == 4 && m_bInMetadataAuthorLink)
     352             :     {
     353          28 :         if (!m_osMetadataKey.empty())
     354             :         {
     355          28 :             SetMetadataItem(m_osMetadataKey.c_str(), m_osMetadataValue.c_str());
     356             :         }
     357          28 :         m_osMetadataKey.clear();
     358          28 :         m_osMetadataValue.clear();
     359             :     }
     360        1227 :     else if (m_nDepth == 3 && (m_bInMetadataAuthor || m_bInMetadataCopyright ||
     361         340 :                                m_bInMetadataLink))
     362             :     {
     363         384 :         if (!m_osMetadataKey.empty())
     364             :         {
     365         206 :             SetMetadataItem(m_osMetadataKey.c_str(), m_osMetadataValue.c_str());
     366             :         }
     367         384 :         m_osMetadataKey.clear();
     368         384 :         m_osMetadataValue.clear();
     369         384 :         m_bInMetadataAuthorLink = false;
     370             :     }
     371         843 :     else if (m_nDepth == 2 && m_bInMetadata)
     372             :     {
     373         159 :         if (!m_osMetadataKey.empty())
     374             :         {
     375          70 :             SetMetadataItem(m_osMetadataKey.c_str(), m_osMetadataValue.c_str());
     376             :         }
     377         159 :         m_osMetadataKey.clear();
     378         159 :         m_osMetadataValue.clear();
     379         159 :         m_bInMetadataAuthor = false;
     380         159 :         m_bInMetadataCopyright = false;
     381             :     }
     382         684 :     else if (m_nDepth == 1 && m_bInMetadata)
     383             :     {
     384          29 :         m_bInMetadata = false;
     385             :     }
     386        1255 : }
     387             : 
     388             : /************************************************************************/
     389             : /*                       dataHandlerValidateCbk()                       */
     390             : /************************************************************************/
     391             : 
     392        3301 : void OGRGPXDataSource::dataHandlerValidateCbk(const char *data, int nLen)
     393             : {
     394        3301 :     if (!m_osMetadataKey.empty())
     395             :     {
     396         304 :         m_osMetadataValue.append(data, nLen);
     397             :     }
     398             : 
     399        3301 :     m_nDataHandlerCounter++;
     400        3301 :     if (m_nDataHandlerCounter >= PARSER_BUF_SIZE)
     401             :     {
     402           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     403             :                  "File probably corrupted (million laugh pattern)");
     404           0 :         XML_StopParser(m_oCurrentParser, XML_FALSE);
     405             :     }
     406        3301 : }
     407             : 
     408        1255 : static void XMLCALL startElementValidateCbk(void *pUserData,
     409             :                                             const char *pszName,
     410             :                                             const char **ppszAttr)
     411             : {
     412        1255 :     OGRGPXDataSource *poDS = static_cast<OGRGPXDataSource *>(pUserData);
     413        1255 :     poDS->startElementValidateCbk(pszName, ppszAttr);
     414        1255 : }
     415             : 
     416        1255 : static void XMLCALL endElementValidateCbk(void *pUserData, const char *pszName)
     417             : {
     418        1255 :     OGRGPXDataSource *poDS = static_cast<OGRGPXDataSource *>(pUserData);
     419        1255 :     poDS->endElementValidateCbk(pszName);
     420        1255 : }
     421             : 
     422        3301 : static void XMLCALL dataHandlerValidateCbk(void *pUserData, const char *data,
     423             :                                            int nLen)
     424             : {
     425        3301 :     OGRGPXDataSource *poDS = static_cast<OGRGPXDataSource *>(pUserData);
     426        3301 :     poDS->dataHandlerValidateCbk(data, nLen);
     427        3301 : }
     428             : #endif
     429             : 
     430             : /************************************************************************/
     431             : /*                                Open()                                */
     432             : /************************************************************************/
     433             : 
     434          29 : int OGRGPXDataSource::Open(GDALOpenInfo *poOpenInfo)
     435             : 
     436             : {
     437          29 :     const char *pszFilename = poOpenInfo->pszFilename;
     438          29 :     if (poOpenInfo->eAccess == GA_Update)
     439             :     {
     440           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     441             :                  "OGR/GPX driver does not support opening a file in "
     442             :                  "update mode");
     443           0 :         return FALSE;
     444             :     }
     445             : #ifdef HAVE_EXPAT
     446          29 :     SetDescription(pszFilename);
     447             : 
     448             :     /* -------------------------------------------------------------------- */
     449             :     /*      Try to open the file.                                           */
     450             :     /* -------------------------------------------------------------------- */
     451          29 :     VSILFILE *fp = VSIFOpenL(pszFilename, "r");
     452          29 :     if (fp == nullptr)
     453           0 :         return FALSE;
     454             : 
     455          29 :     m_validity = GPX_VALIDITY_UNKNOWN;
     456             : 
     457          29 :     XML_Parser oParser = OGRCreateExpatXMLParser();
     458          29 :     m_oCurrentParser = oParser;
     459          29 :     XML_SetUserData(oParser, this);
     460          29 :     XML_SetElementHandler(oParser, ::startElementValidateCbk,
     461             :                           ::endElementValidateCbk);
     462          29 :     XML_SetCharacterDataHandler(oParser, ::dataHandlerValidateCbk);
     463             : 
     464          29 :     std::vector<char> aBuf(PARSER_BUF_SIZE);
     465          29 :     int nDone = 0;
     466          29 :     unsigned int nLen = 0;
     467          29 :     int nCount = 0;
     468             : 
     469             :     /* Begin to parse the file and look for the <gpx> element */
     470             :     /* It *MUST* be the first element of an XML file */
     471             :     /* So once we have read the first element, we know if we can */
     472             :     /* handle the file or not with that driver */
     473          29 :     uint64_t nTotalBytesRead = 0;
     474           0 :     do
     475             :     {
     476          29 :         m_nDataHandlerCounter = 0;
     477          29 :         nLen = static_cast<unsigned int>(
     478          29 :             VSIFReadL(aBuf.data(), 1, aBuf.size(), fp));
     479          29 :         nTotalBytesRead += nLen;
     480          29 :         nDone = (nLen < aBuf.size());
     481          29 :         if (XML_Parse(oParser, aBuf.data(), nLen, nDone) == XML_STATUS_ERROR)
     482             :         {
     483           0 :             if (nLen <= PARSER_BUF_SIZE - 1)
     484           0 :                 aBuf[nLen] = 0;
     485             :             else
     486           0 :                 aBuf[PARSER_BUF_SIZE - 1] = 0;
     487           0 :             if (strstr(aBuf.data(), "<?xml") && strstr(aBuf.data(), "<gpx"))
     488             :             {
     489           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     490             :                          "XML parsing of GPX file failed: %s",
     491             :                          OGRExpatGetParseError(oParser));
     492             :             }
     493           0 :             m_validity = GPX_VALIDITY_INVALID;
     494           0 :             break;
     495             :         }
     496          29 :         if (m_validity == GPX_VALIDITY_INVALID)
     497             :         {
     498           0 :             break;
     499             :         }
     500          29 :         else if (m_validity == GPX_VALIDITY_VALID)
     501             :         {
     502             :             /* If we have recognized the <gpx> element, now we try */
     503             :             /* to recognize if they are <extensions> tags */
     504             :             /* But we stop to look for after an arbitrary amount of bytes */
     505          29 :             if (m_bUseExtensions)
     506          15 :                 break;
     507          14 :             else if (nTotalBytesRead > 1024 * 1024)
     508           0 :                 break;
     509             :         }
     510             :         else
     511             :         {
     512             :             // After reading 50 * PARSER_BUF_SIZE bytes, and not finding whether the
     513             :             // file is GPX or not, we give up and fail silently.
     514           0 :             nCount++;
     515           0 :             if (nCount == 50)
     516           0 :                 break;
     517             :         }
     518          14 :     } while (!nDone && nLen > 0);
     519             : 
     520          29 :     XML_ParserFree(oParser);
     521             : 
     522          29 :     VSIFCloseL(fp);
     523             : 
     524          29 :     if (m_validity == GPX_VALIDITY_VALID)
     525             :     {
     526          29 :         CPLDebug("GPX", "%s seems to be a GPX file.", pszFilename);
     527          29 :         if (m_bUseExtensions)
     528          15 :             CPLDebug("GPX", "It uses <extensions>");
     529             : 
     530          29 :         if (m_osVersion.empty())
     531             :         {
     532             :             /* Default to 1.1 */
     533           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     534             :                      "GPX schema version is unknown. "
     535             :                      "The driver may not be able to handle the file correctly "
     536             :                      "and will behave as if it is GPX 1.1.");
     537           0 :             m_osVersion = "1.1";
     538             :         }
     539          29 :         else if (m_osVersion == "1.0" || m_osVersion == "1.1")
     540             :         {
     541             :             /* Fine */
     542             :         }
     543             :         else
     544             :         {
     545           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     546             :                      "GPX schema version '%s' is not handled by the driver. "
     547             :                      "The driver may not be able to handle the file correctly "
     548             :                      "and will behave as if it is GPX 1.1.",
     549             :                      m_osVersion.c_str());
     550             :         }
     551             : 
     552          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     553          29 :             GetDescription(), "waypoints", GPX_WPT, this, false,
     554          58 :             poOpenInfo->papszOpenOptions));
     555          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     556          29 :             GetDescription(), "routes", GPX_ROUTE, this, false,
     557          58 :             poOpenInfo->papszOpenOptions));
     558          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     559          29 :             GetDescription(), "tracks", GPX_TRACK, this, false,
     560          58 :             poOpenInfo->papszOpenOptions));
     561          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     562          29 :             GetDescription(), "route_points", GPX_ROUTE_POINT, this, false,
     563          58 :             poOpenInfo->papszOpenOptions));
     564          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     565          58 :             GetDescription(), "track_points", GPX_TRACK_POINT, this, false,
     566          58 :             poOpenInfo->papszOpenOptions));
     567             :     }
     568             : 
     569          29 :     return m_validity == GPX_VALIDITY_VALID;
     570             : #else
     571             :     VSILFILE *fp = VSIFOpenL(pszFilename, "r");
     572             :     if (fp)
     573             :     {
     574             :         char aBuf[256];
     575             :         unsigned int nLen =
     576             :             static_cast<unsigned int>(VSIFReadL(aBuf, 1, 255, fp));
     577             :         aBuf[nLen] = 0;
     578             :         if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
     579             :         {
     580             :             CPLError(CE_Failure, CPLE_NotSupported,
     581             :                      "OGR/GPX driver has not been built with read support. "
     582             :                      "Expat library required");
     583             :         }
     584             :         VSIFCloseL(fp);
     585             :     }
     586             :     return FALSE;
     587             : #endif
     588             : }
     589             : 
     590             : /************************************************************************/
     591             : /*                               Create()                               */
     592             : /************************************************************************/
     593             : 
     594          25 : int OGRGPXDataSource::Create(const char *pszFilename, CSLConstList papszOptions)
     595             : {
     596          25 :     if (strcmp(pszFilename, "/dev/stdout") == 0)
     597           0 :         pszFilename = "/vsistdout/";
     598             : 
     599             :     /* -------------------------------------------------------------------- */
     600             :     /*     Do not overwrite exiting file.                                   */
     601             :     /* -------------------------------------------------------------------- */
     602             :     VSIStatBufL sStatBuf;
     603             : 
     604          25 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
     605             :     {
     606           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     607             :                  "You have to delete %s before being able to create it with "
     608             :                  "the GPX driver",
     609             :                  pszFilename);
     610           0 :         return FALSE;
     611             :     }
     612             : 
     613             :     /* -------------------------------------------------------------------- */
     614             :     /*      Create the output file.                                         */
     615             :     /* -------------------------------------------------------------------- */
     616             : 
     617          25 :     SetDescription(pszFilename);
     618             : 
     619          25 :     if (strcmp(pszFilename, "/vsistdout/") == 0)
     620             :     {
     621           0 :         m_bIsBackSeekable = false;
     622           0 :         m_fpOutput.reset(VSIFOpenL(pszFilename, "w"));
     623             :     }
     624             :     else
     625          25 :         m_fpOutput.reset(VSIFOpenL(pszFilename, "w+"));
     626          25 :     if (m_fpOutput == nullptr)
     627             :     {
     628           1 :         CPLError(CE_Failure, CPLE_OpenFailed, "Failed to create GPX file %s.",
     629             :                  pszFilename);
     630           1 :         return FALSE;
     631             :     }
     632             : 
     633             :     /* -------------------------------------------------------------------- */
     634             :     /*      End of line character.                                          */
     635             :     /* -------------------------------------------------------------------- */
     636          24 :     const char *pszCRLFFormat = CSLFetchNameValue(papszOptions, "LINEFORMAT");
     637             : 
     638          24 :     bool bUseCRLF =
     639             : #ifdef _WIN32
     640             :         true
     641             : #else
     642             :         false
     643             : #endif
     644             :         ;
     645          24 :     if (pszCRLFFormat == nullptr)
     646             :     {
     647             :         // Use default value for OS.
     648             :     }
     649           1 :     else if (EQUAL(pszCRLFFormat, "CRLF"))
     650           0 :         bUseCRLF = true;
     651           1 :     else if (EQUAL(pszCRLFFormat, "LF"))
     652           1 :         bUseCRLF = false;
     653             :     else
     654             :     {
     655           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     656             :                  "LINEFORMAT=%s not understood, use one of CRLF or LF.",
     657             :                  pszCRLFFormat);
     658             :         // Use default value for OS.
     659             :     }
     660          24 :     m_pszEOL = (bUseCRLF) ? "\r\n" : "\n";
     661             : 
     662             :     /* -------------------------------------------------------------------- */
     663             :     /*      Look at use extensions options.                                 */
     664             :     /* -------------------------------------------------------------------- */
     665             :     const char *pszUseExtensions =
     666          24 :         CSLFetchNameValue(papszOptions, "GPX_USE_EXTENSIONS");
     667          24 :     const char *pszExtensionsNSURL = nullptr;
     668          24 :     if (pszUseExtensions && CPLTestBool(pszUseExtensions))
     669             :     {
     670           1 :         m_bUseExtensions = true;
     671             : 
     672             :         const char *pszExtensionsNSOption =
     673           1 :             CSLFetchNameValue(papszOptions, "GPX_EXTENSIONS_NS");
     674             :         const char *pszExtensionsNSURLOption =
     675           1 :             CSLFetchNameValue(papszOptions, "GPX_EXTENSIONS_NS_URL");
     676           1 :         if (pszExtensionsNSOption && pszExtensionsNSURLOption)
     677             :         {
     678           0 :             m_osExtensionsNS = pszExtensionsNSOption;
     679           0 :             pszExtensionsNSURL = pszExtensionsNSURLOption;
     680             :         }
     681             :         else
     682             :         {
     683           1 :             m_osExtensionsNS = "ogr";
     684           1 :             pszExtensionsNSURL = "http://osgeo.org/gdal";
     685             :         }
     686             :     }
     687             : 
     688             :     /* -------------------------------------------------------------------- */
     689             :     /*     Output header of GPX file.                                       */
     690             :     /* -------------------------------------------------------------------- */
     691          24 :     PrintLine("<?xml version=\"1.0\"?>");
     692          24 :     m_fpOutput->Printf("<gpx version=\"1.1\" creator=\"");
     693          24 :     const char *pszCreator = CSLFetchNameValue(papszOptions, "CREATOR");
     694          24 :     if (pszCreator)
     695             :     {
     696           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszCreator);
     697           1 :         m_fpOutput->Printf("%s", pszXML);
     698           1 :         CPLFree(pszXML);
     699             :     }
     700             :     else
     701             :     {
     702          23 :         m_fpOutput->Printf("GDAL %s", GDALVersionInfo("RELEASE_NAME"));
     703             :     }
     704          24 :     m_fpOutput->Printf(
     705             :         "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
     706          24 :     if (m_bUseExtensions)
     707           1 :         m_fpOutput->Printf("xmlns:%s=\"%s\" ", m_osExtensionsNS.c_str(),
     708             :                            pszExtensionsNSURL);
     709          24 :     m_fpOutput->Printf("xmlns=\"http://www.topografix.com/GPX/1/1\" ");
     710          24 :     PrintLine("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 "
     711             :               "http://www.topografix.com/GPX/1/1/gpx.xsd\">");
     712          24 :     PrintLine("<metadata>");
     713             :     /*
     714             :     Something like:
     715             :         <metadata>
     716             :             <name>metadata name</name>
     717             :             <desc>metadata desc</desc>
     718             :             <author>
     719             :                 <name>metadata author name</name>
     720             :                 <email id="foo" domain="example.com"/>
     721             :                 <link href="author_href"><text>author_text</text><type>author_type</type></link>
     722             :             </author>
     723             :             <copyright author="copyright author"><year>2023</year><license>my license</license></copyright>
     724             :             <link href="href"><text>text</text><type>type</type></link>
     725             :             <link href="href2"><text>text2</text><type>type2</type></link>
     726             :             <time>2007-11-25T17:58:00+01:00</time>
     727             :             <keywords>kw</keywords>
     728             :             <bounds minlat="-90" minlon="-180" maxlat="90" maxlon="179.9999999"/>
     729             :         </metadata>
     730             :     */
     731             : 
     732          24 :     if (const char *pszMetadataName =
     733          24 :             CSLFetchNameValue(papszOptions, "METADATA_NAME"))
     734             :     {
     735           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataName);
     736           1 :         PrintLine("  <name>%s</name>", pszXML);
     737           1 :         CPLFree(pszXML);
     738             :     }
     739             : 
     740          24 :     if (const char *pszMetadataDesc =
     741          24 :             CSLFetchNameValue(papszOptions, "METADATA_DESCRIPTION"))
     742             :     {
     743           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataDesc);
     744           1 :         PrintLine("  <desc>%s</desc>", pszXML);
     745           1 :         CPLFree(pszXML);
     746             :     }
     747             : 
     748             :     const char *pszMetadataAuthorName =
     749          24 :         CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_NAME");
     750             :     const char *pszMetadataAuthorEmail =
     751          24 :         CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_EMAIL");
     752             :     const char *pszMetadataAuthorLinkHref =
     753          24 :         CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_LINK_HREF");
     754          24 :     if (pszMetadataAuthorName || pszMetadataAuthorEmail ||
     755             :         pszMetadataAuthorLinkHref)
     756             :     {
     757           1 :         PrintLine("  <author>");
     758           1 :         if (pszMetadataAuthorName)
     759             :         {
     760           1 :             char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataAuthorName);
     761           1 :             PrintLine("    <name>%s</name>", pszXML);
     762           1 :             CPLFree(pszXML);
     763             :         }
     764           1 :         if (pszMetadataAuthorEmail)
     765             :         {
     766           2 :             const std::string osEmail(pszMetadataAuthorEmail);
     767           1 :             auto nPos = osEmail.find('@');
     768           1 :             if (nPos != std::string::npos)
     769             :             {
     770           1 :                 char *pszId = OGRGetXML_UTF8_EscapedString(
     771           2 :                     osEmail.substr(0, nPos).c_str());
     772           1 :                 char *pszDomain = OGRGetXML_UTF8_EscapedString(
     773           2 :                     osEmail.substr(nPos + 1).c_str());
     774           1 :                 PrintLine("    <email id=\"%s\" domain=\"%s\"/>", pszId,
     775             :                           pszDomain);
     776           1 :                 CPLFree(pszId);
     777           1 :                 CPLFree(pszDomain);
     778             :             }
     779             :         }
     780           1 :         if (pszMetadataAuthorLinkHref)
     781             :         {
     782             :             {
     783             :                 char *pszXML =
     784           1 :                     OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkHref);
     785           1 :                 PrintLine("    <link href=\"%s\">", pszXML);
     786           1 :                 CPLFree(pszXML);
     787             :             }
     788           1 :             if (const char *pszMetadataAuthorLinkText = CSLFetchNameValue(
     789             :                     papszOptions, "METADATA_AUTHOR_LINK_TEXT"))
     790             :             {
     791             :                 char *pszXML =
     792           1 :                     OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkText);
     793           1 :                 PrintLine("      <text>%s</text>", pszXML);
     794           1 :                 CPLFree(pszXML);
     795             :             }
     796           1 :             if (const char *pszMetadataAuthorLinkType = CSLFetchNameValue(
     797             :                     papszOptions, "METADATA_AUTHOR_LINK_TYPE"))
     798             :             {
     799             :                 char *pszXML =
     800           1 :                     OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkType);
     801           1 :                 PrintLine("      <type>%s</type>", pszXML);
     802           1 :                 CPLFree(pszXML);
     803             :             }
     804           1 :             PrintLine("    </link>");
     805             :         }
     806           1 :         PrintLine("  </author>");
     807             :     }
     808             : 
     809          24 :     if (const char *pszMetadataCopyrightAuthor =
     810          24 :             CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_AUTHOR"))
     811             :     {
     812             :         {
     813             :             char *pszXML =
     814           1 :                 OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightAuthor);
     815           1 :             PrintLine("  <copyright author=\"%s\">", pszXML);
     816           1 :             CPLFree(pszXML);
     817             :         }
     818           1 :         if (const char *pszMetadataCopyrightYear =
     819           1 :                 CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_YEAR"))
     820             :         {
     821             :             char *pszXML =
     822           1 :                 OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightYear);
     823           1 :             PrintLine("      <year>%s</year>", pszXML);
     824           1 :             CPLFree(pszXML);
     825             :         }
     826           1 :         if (const char *pszMetadataCopyrightLicense =
     827           1 :                 CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_LICENSE"))
     828             :         {
     829             :             char *pszXML =
     830           1 :                 OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightLicense);
     831           1 :             PrintLine("      <license>%s</license>", pszXML);
     832           1 :             CPLFree(pszXML);
     833             :         }
     834           1 :         PrintLine("  </copyright>");
     835             :     }
     836             : 
     837          45 :     for (CSLConstList papszIter = papszOptions; papszIter && *papszIter;
     838             :          ++papszIter)
     839             :     {
     840          21 :         if (STARTS_WITH_CI(*papszIter, "METADATA_LINK_") &&
     841           6 :             strstr(*papszIter, "_HREF"))
     842             :         {
     843           2 :             const int nLinkNum = atoi(*papszIter + strlen("METADATA_LINK_"));
     844           2 :             const char *pszVal = strchr(*papszIter, '=');
     845           2 :             if (pszVal)
     846             :             {
     847             :                 {
     848           2 :                     char *pszXML = OGRGetXML_UTF8_EscapedString(pszVal + 1);
     849           2 :                     PrintLine("  <link href=\"%s\">", pszXML);
     850           2 :                     CPLFree(pszXML);
     851             :                 }
     852           2 :                 if (const char *pszText = CSLFetchNameValue(
     853             :                         papszOptions,
     854             :                         CPLSPrintf("METADATA_LINK_%d_TEXT", nLinkNum)))
     855             :                 {
     856           2 :                     char *pszXML = OGRGetXML_UTF8_EscapedString(pszText);
     857           2 :                     PrintLine("      <text>%s</text>", pszXML);
     858           2 :                     CPLFree(pszXML);
     859             :                 }
     860           2 :                 if (const char *pszType = CSLFetchNameValue(
     861             :                         papszOptions,
     862             :                         CPLSPrintf("METADATA_LINK_%d_TYPE", nLinkNum)))
     863             :                 {
     864           2 :                     char *pszXML = OGRGetXML_UTF8_EscapedString(pszType);
     865           2 :                     PrintLine("      <type>%s</type>", pszXML);
     866           2 :                     CPLFree(pszXML);
     867             :                 }
     868           2 :                 PrintLine("  </link>");
     869             :             }
     870             :         }
     871             :     }
     872             : 
     873          24 :     if (const char *pszMetadataTime =
     874          24 :             CSLFetchNameValue(papszOptions, "METADATA_TIME"))
     875             :     {
     876           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataTime);
     877           1 :         PrintLine("  <time>%s</time>", pszXML);
     878           1 :         CPLFree(pszXML);
     879             :     }
     880             : 
     881          24 :     if (const char *pszMetadataKeywords =
     882          24 :             CSLFetchNameValue(papszOptions, "METADATA_KEYWORDS"))
     883             :     {
     884           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataKeywords);
     885           1 :         PrintLine("  <keywords>%s</keywords>", pszXML);
     886           1 :         CPLFree(pszXML);
     887             :     }
     888             : 
     889          24 :     if (m_bIsBackSeekable)
     890             :     {
     891             :         /* Reserve space for <bounds .../> within <metadata> */
     892             :         char szBounds[SPACE_FOR_METADATA_BOUNDS + 1];
     893          24 :         memset(szBounds, ' ', SPACE_FOR_METADATA_BOUNDS);
     894          24 :         szBounds[SPACE_FOR_METADATA_BOUNDS] = '\0';
     895          24 :         m_nOffsetBounds = m_fpOutput->Tell();
     896          24 :         PrintLine("%s", szBounds);
     897             :     }
     898          24 :     PrintLine("</metadata>");
     899             : 
     900          24 :     return TRUE;
     901             : }
     902             : 
     903             : /************************************************************************/
     904             : /*                              AddCoord()                              */
     905             : /************************************************************************/
     906             : 
     907          41 : void OGRGPXDataSource::AddCoord(double dfLon, double dfLat)
     908             : {
     909          41 :     m_dfMinLon = std::min(m_dfMinLon, dfLon);
     910          41 :     m_dfMinLat = std::min(m_dfMinLat, dfLat);
     911          41 :     m_dfMaxLon = std::max(m_dfMaxLon, dfLon);
     912          41 :     m_dfMaxLat = std::max(m_dfMaxLat, dfLat);
     913          41 : }
     914             : 
     915             : /************************************************************************/
     916             : /*                             PrintLine()                              */
     917             : /************************************************************************/
     918             : 
     919         372 : void OGRGPXDataSource::PrintLine(const char *fmt, ...)
     920             : {
     921         744 :     CPLString osWork;
     922             :     va_list args;
     923             : 
     924         372 :     va_start(args, fmt);
     925         372 :     osWork.vPrintf(fmt, args);
     926         372 :     va_end(args);
     927             : 
     928         372 :     m_fpOutput->Write(osWork.c_str(), 1, osWork.size());
     929         372 :     m_fpOutput->Write(m_pszEOL, 1, strlen(m_pszEOL));
     930         372 : }

Generated by: LCOV version 1.14