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 412 91.0 %
Date: 2024-11-21 22:18:42 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 : int OGRGPXDataSource::TestCapability(const char *pszCap)
      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 : OGRLayer *OGRGPXDataSource::GetLayer(int iLayer)
      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 at line %d, "
     491             :                          "column %d",
     492             :                          XML_ErrorString(XML_GetErrorCode(oParser)),
     493           0 :                          static_cast<int>(XML_GetCurrentLineNumber(oParser)),
     494           0 :                          static_cast<int>(XML_GetCurrentColumnNumber(oParser)));
     495             :             }
     496           0 :             m_validity = GPX_VALIDITY_INVALID;
     497           0 :             break;
     498             :         }
     499          29 :         if (m_validity == GPX_VALIDITY_INVALID)
     500             :         {
     501           0 :             break;
     502             :         }
     503          29 :         else if (m_validity == GPX_VALIDITY_VALID)
     504             :         {
     505             :             /* If we have recognized the <gpx> element, now we try */
     506             :             /* to recognize if they are <extensions> tags */
     507             :             /* But we stop to look for after an arbitrary amount of bytes */
     508          29 :             if (m_bUseExtensions)
     509          15 :                 break;
     510          14 :             else if (nTotalBytesRead > 1024 * 1024)
     511           0 :                 break;
     512             :         }
     513             :         else
     514             :         {
     515             :             // After reading 50 * PARSER_BUF_SIZE bytes, and not finding whether the
     516             :             // file is GPX or not, we give up and fail silently.
     517           0 :             nCount++;
     518           0 :             if (nCount == 50)
     519           0 :                 break;
     520             :         }
     521          14 :     } while (!nDone && nLen > 0);
     522             : 
     523          29 :     XML_ParserFree(oParser);
     524             : 
     525          29 :     VSIFCloseL(fp);
     526             : 
     527          29 :     if (m_validity == GPX_VALIDITY_VALID)
     528             :     {
     529          29 :         CPLDebug("GPX", "%s seems to be a GPX file.", pszFilename);
     530          29 :         if (m_bUseExtensions)
     531          15 :             CPLDebug("GPX", "It uses <extensions>");
     532             : 
     533          29 :         if (m_osVersion.empty())
     534             :         {
     535             :             /* Default to 1.1 */
     536           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     537             :                      "GPX schema version is unknown. "
     538             :                      "The driver may not be able to handle the file correctly "
     539             :                      "and will behave as if it is GPX 1.1.");
     540           0 :             m_osVersion = "1.1";
     541             :         }
     542          29 :         else if (m_osVersion == "1.0" || m_osVersion == "1.1")
     543             :         {
     544             :             /* Fine */
     545             :         }
     546             :         else
     547             :         {
     548           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     549             :                      "GPX schema version '%s' is not handled by the driver. "
     550             :                      "The driver may not be able to handle the file correctly "
     551             :                      "and will behave as if it is GPX 1.1.",
     552             :                      m_osVersion.c_str());
     553             :         }
     554             : 
     555          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     556          29 :             GetDescription(), "waypoints", GPX_WPT, this, false,
     557          58 :             poOpenInfo->papszOpenOptions));
     558          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     559          29 :             GetDescription(), "routes", GPX_ROUTE, this, false,
     560          58 :             poOpenInfo->papszOpenOptions));
     561          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     562          29 :             GetDescription(), "tracks", GPX_TRACK, this, false,
     563          58 :             poOpenInfo->papszOpenOptions));
     564          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     565          29 :             GetDescription(), "route_points", GPX_ROUTE_POINT, this, false,
     566          58 :             poOpenInfo->papszOpenOptions));
     567          29 :         m_apoLayers.emplace_back(std::make_unique<OGRGPXLayer>(
     568          58 :             GetDescription(), "track_points", GPX_TRACK_POINT, this, false,
     569          58 :             poOpenInfo->papszOpenOptions));
     570             :     }
     571             : 
     572          29 :     return m_validity == GPX_VALIDITY_VALID;
     573             : #else
     574             :     VSILFILE *fp = VSIFOpenL(pszFilename, "r");
     575             :     if (fp)
     576             :     {
     577             :         char aBuf[256];
     578             :         unsigned int nLen =
     579             :             static_cast<unsigned int>(VSIFReadL(aBuf, 1, 255, fp));
     580             :         aBuf[nLen] = 0;
     581             :         if (strstr(aBuf, "<?xml") && strstr(aBuf, "<gpx"))
     582             :         {
     583             :             CPLError(CE_Failure, CPLE_NotSupported,
     584             :                      "OGR/GPX driver has not been built with read support. "
     585             :                      "Expat library required");
     586             :         }
     587             :         VSIFCloseL(fp);
     588             :     }
     589             :     return FALSE;
     590             : #endif
     591             : }
     592             : 
     593             : /************************************************************************/
     594             : /*                               Create()                               */
     595             : /************************************************************************/
     596             : 
     597          25 : int OGRGPXDataSource::Create(const char *pszFilename, char **papszOptions)
     598             : {
     599          25 :     if (strcmp(pszFilename, "/dev/stdout") == 0)
     600           0 :         pszFilename = "/vsistdout/";
     601             : 
     602             :     /* -------------------------------------------------------------------- */
     603             :     /*     Do not overwrite exiting file.                                   */
     604             :     /* -------------------------------------------------------------------- */
     605             :     VSIStatBufL sStatBuf;
     606             : 
     607          25 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
     608             :     {
     609           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     610             :                  "You have to delete %s before being able to create it with "
     611             :                  "the GPX driver",
     612             :                  pszFilename);
     613           0 :         return FALSE;
     614             :     }
     615             : 
     616             :     /* -------------------------------------------------------------------- */
     617             :     /*      Create the output file.                                         */
     618             :     /* -------------------------------------------------------------------- */
     619             : 
     620          25 :     SetDescription(pszFilename);
     621             : 
     622          25 :     if (strcmp(pszFilename, "/vsistdout/") == 0)
     623             :     {
     624           0 :         m_bIsBackSeekable = false;
     625           0 :         m_fpOutput.reset(VSIFOpenL(pszFilename, "w"));
     626             :     }
     627             :     else
     628          25 :         m_fpOutput.reset(VSIFOpenL(pszFilename, "w+"));
     629          25 :     if (m_fpOutput == nullptr)
     630             :     {
     631           1 :         CPLError(CE_Failure, CPLE_OpenFailed, "Failed to create GPX file %s.",
     632             :                  pszFilename);
     633           1 :         return FALSE;
     634             :     }
     635             : 
     636             :     /* -------------------------------------------------------------------- */
     637             :     /*      End of line character.                                          */
     638             :     /* -------------------------------------------------------------------- */
     639          24 :     const char *pszCRLFFormat = CSLFetchNameValue(papszOptions, "LINEFORMAT");
     640             : 
     641          24 :     bool bUseCRLF =
     642             : #ifdef _WIN32
     643             :         true
     644             : #else
     645             :         false
     646             : #endif
     647             :         ;
     648          24 :     if (pszCRLFFormat == nullptr)
     649             :     {
     650             :         // Use default value for OS.
     651             :     }
     652           1 :     else if (EQUAL(pszCRLFFormat, "CRLF"))
     653           0 :         bUseCRLF = true;
     654           1 :     else if (EQUAL(pszCRLFFormat, "LF"))
     655           1 :         bUseCRLF = false;
     656             :     else
     657             :     {
     658           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     659             :                  "LINEFORMAT=%s not understood, use one of CRLF or LF.",
     660             :                  pszCRLFFormat);
     661             :         // Use default value for OS.
     662             :     }
     663          24 :     m_pszEOL = (bUseCRLF) ? "\r\n" : "\n";
     664             : 
     665             :     /* -------------------------------------------------------------------- */
     666             :     /*      Look at use extensions options.                                 */
     667             :     /* -------------------------------------------------------------------- */
     668             :     const char *pszUseExtensions =
     669          24 :         CSLFetchNameValue(papszOptions, "GPX_USE_EXTENSIONS");
     670          24 :     const char *pszExtensionsNSURL = nullptr;
     671          24 :     if (pszUseExtensions && CPLTestBool(pszUseExtensions))
     672             :     {
     673           1 :         m_bUseExtensions = true;
     674             : 
     675             :         const char *pszExtensionsNSOption =
     676           1 :             CSLFetchNameValue(papszOptions, "GPX_EXTENSIONS_NS");
     677             :         const char *pszExtensionsNSURLOption =
     678           1 :             CSLFetchNameValue(papszOptions, "GPX_EXTENSIONS_NS_URL");
     679           1 :         if (pszExtensionsNSOption && pszExtensionsNSURLOption)
     680             :         {
     681           0 :             m_osExtensionsNS = pszExtensionsNSOption;
     682           0 :             pszExtensionsNSURL = pszExtensionsNSURLOption;
     683             :         }
     684             :         else
     685             :         {
     686           1 :             m_osExtensionsNS = "ogr";
     687           1 :             pszExtensionsNSURL = "http://osgeo.org/gdal";
     688             :         }
     689             :     }
     690             : 
     691             :     /* -------------------------------------------------------------------- */
     692             :     /*     Output header of GPX file.                                       */
     693             :     /* -------------------------------------------------------------------- */
     694          24 :     PrintLine("<?xml version=\"1.0\"?>");
     695          24 :     m_fpOutput->Printf("<gpx version=\"1.1\" creator=\"");
     696          24 :     const char *pszCreator = CSLFetchNameValue(papszOptions, "CREATOR");
     697          24 :     if (pszCreator)
     698             :     {
     699           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszCreator);
     700           1 :         m_fpOutput->Printf("%s", pszXML);
     701           1 :         CPLFree(pszXML);
     702             :     }
     703             :     else
     704             :     {
     705          23 :         m_fpOutput->Printf("GDAL %s", GDALVersionInfo("RELEASE_NAME"));
     706             :     }
     707          24 :     m_fpOutput->Printf(
     708             :         "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
     709          24 :     if (m_bUseExtensions)
     710           1 :         m_fpOutput->Printf("xmlns:%s=\"%s\" ", m_osExtensionsNS.c_str(),
     711             :                            pszExtensionsNSURL);
     712          24 :     m_fpOutput->Printf("xmlns=\"http://www.topografix.com/GPX/1/1\" ");
     713          24 :     PrintLine("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 "
     714             :               "http://www.topografix.com/GPX/1/1/gpx.xsd\">");
     715          24 :     PrintLine("<metadata>");
     716             :     /*
     717             :     Something like:
     718             :         <metadata>
     719             :             <name>metadata name</name>
     720             :             <desc>metadata desc</desc>
     721             :             <author>
     722             :                 <name>metadata author name</name>
     723             :                 <email id="foo" domain="example.com"/>
     724             :                 <link href="author_href"><text>author_text</text><type>author_type</type></link>
     725             :             </author>
     726             :             <copyright author="copyright author"><year>2023</year><license>my license</license></copyright>
     727             :             <link href="href"><text>text</text><type>type</type></link>
     728             :             <link href="href2"><text>text2</text><type>type2</type></link>
     729             :             <time>2007-11-25T17:58:00+01:00</time>
     730             :             <keywords>kw</keywords>
     731             :             <bounds minlat="-90" minlon="-180" maxlat="90" maxlon="179.9999999"/>
     732             :         </metadata>
     733             :     */
     734             : 
     735          24 :     if (const char *pszMetadataName =
     736          24 :             CSLFetchNameValue(papszOptions, "METADATA_NAME"))
     737             :     {
     738           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataName);
     739           1 :         PrintLine("  <name>%s</name>", pszXML);
     740           1 :         CPLFree(pszXML);
     741             :     }
     742             : 
     743          24 :     if (const char *pszMetadataDesc =
     744          24 :             CSLFetchNameValue(papszOptions, "METADATA_DESCRIPTION"))
     745             :     {
     746           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataDesc);
     747           1 :         PrintLine("  <desc>%s</desc>", pszXML);
     748           1 :         CPLFree(pszXML);
     749             :     }
     750             : 
     751             :     const char *pszMetadataAuthorName =
     752          24 :         CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_NAME");
     753             :     const char *pszMetadataAuthorEmail =
     754          24 :         CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_EMAIL");
     755             :     const char *pszMetadataAuthorLinkHref =
     756          24 :         CSLFetchNameValue(papszOptions, "METADATA_AUTHOR_LINK_HREF");
     757          24 :     if (pszMetadataAuthorName || pszMetadataAuthorEmail ||
     758             :         pszMetadataAuthorLinkHref)
     759             :     {
     760           1 :         PrintLine("  <author>");
     761           1 :         if (pszMetadataAuthorName)
     762             :         {
     763           1 :             char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataAuthorName);
     764           1 :             PrintLine("    <name>%s</name>", pszXML);
     765           1 :             CPLFree(pszXML);
     766             :         }
     767           1 :         if (pszMetadataAuthorEmail)
     768             :         {
     769           2 :             std::string osEmail = pszMetadataAuthorEmail;
     770           1 :             auto nPos = osEmail.find('@');
     771           1 :             if (nPos != std::string::npos)
     772             :             {
     773           1 :                 char *pszId = OGRGetXML_UTF8_EscapedString(
     774           2 :                     osEmail.substr(0, nPos).c_str());
     775           1 :                 char *pszDomain = OGRGetXML_UTF8_EscapedString(
     776           2 :                     osEmail.substr(nPos + 1).c_str());
     777           1 :                 PrintLine("    <email id=\"%s\" domain=\"%s\"/>", pszId,
     778             :                           pszDomain);
     779           1 :                 CPLFree(pszId);
     780           1 :                 CPLFree(pszDomain);
     781             :             }
     782             :         }
     783           1 :         if (pszMetadataAuthorLinkHref)
     784             :         {
     785             :             {
     786             :                 char *pszXML =
     787           1 :                     OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkHref);
     788           1 :                 PrintLine("    <link href=\"%s\">", pszXML);
     789           1 :                 CPLFree(pszXML);
     790             :             }
     791           1 :             if (const char *pszMetadataAuthorLinkText = CSLFetchNameValue(
     792             :                     papszOptions, "METADATA_AUTHOR_LINK_TEXT"))
     793             :             {
     794             :                 char *pszXML =
     795           1 :                     OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkText);
     796           1 :                 PrintLine("      <text>%s</text>", pszXML);
     797           1 :                 CPLFree(pszXML);
     798             :             }
     799           1 :             if (const char *pszMetadataAuthorLinkType = CSLFetchNameValue(
     800             :                     papszOptions, "METADATA_AUTHOR_LINK_TYPE"))
     801             :             {
     802             :                 char *pszXML =
     803           1 :                     OGRGetXML_UTF8_EscapedString(pszMetadataAuthorLinkType);
     804           1 :                 PrintLine("      <type>%s</type>", pszXML);
     805           1 :                 CPLFree(pszXML);
     806             :             }
     807           1 :             PrintLine("    </link>");
     808             :         }
     809           1 :         PrintLine("  </author>");
     810             :     }
     811             : 
     812          24 :     if (const char *pszMetadataCopyrightAuthor =
     813          24 :             CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_AUTHOR"))
     814             :     {
     815             :         {
     816             :             char *pszXML =
     817           1 :                 OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightAuthor);
     818           1 :             PrintLine("  <copyright author=\"%s\">", pszXML);
     819           1 :             CPLFree(pszXML);
     820             :         }
     821           1 :         if (const char *pszMetadataCopyrightYear =
     822           1 :                 CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_YEAR"))
     823             :         {
     824             :             char *pszXML =
     825           1 :                 OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightYear);
     826           1 :             PrintLine("      <year>%s</year>", pszXML);
     827           1 :             CPLFree(pszXML);
     828             :         }
     829           1 :         if (const char *pszMetadataCopyrightLicense =
     830           1 :                 CSLFetchNameValue(papszOptions, "METADATA_COPYRIGHT_LICENSE"))
     831             :         {
     832             :             char *pszXML =
     833           1 :                 OGRGetXML_UTF8_EscapedString(pszMetadataCopyrightLicense);
     834           1 :             PrintLine("      <license>%s</license>", pszXML);
     835           1 :             CPLFree(pszXML);
     836             :         }
     837           1 :         PrintLine("  </copyright>");
     838             :     }
     839             : 
     840          45 :     for (CSLConstList papszIter = papszOptions; papszIter && *papszIter;
     841             :          ++papszIter)
     842             :     {
     843          21 :         if (STARTS_WITH_CI(*papszIter, "METADATA_LINK_") &&
     844           6 :             strstr(*papszIter, "_HREF"))
     845             :         {
     846           2 :             const int nLinkNum = atoi(*papszIter + strlen("METADATA_LINK_"));
     847           2 :             const char *pszVal = strchr(*papszIter, '=');
     848           2 :             if (pszVal)
     849             :             {
     850             :                 {
     851           2 :                     char *pszXML = OGRGetXML_UTF8_EscapedString(pszVal + 1);
     852           2 :                     PrintLine("  <link href=\"%s\">", pszXML);
     853           2 :                     CPLFree(pszXML);
     854             :                 }
     855           2 :                 if (const char *pszText = CSLFetchNameValue(
     856             :                         papszOptions,
     857             :                         CPLSPrintf("METADATA_LINK_%d_TEXT", nLinkNum)))
     858             :                 {
     859           2 :                     char *pszXML = OGRGetXML_UTF8_EscapedString(pszText);
     860           2 :                     PrintLine("      <text>%s</text>", pszXML);
     861           2 :                     CPLFree(pszXML);
     862             :                 }
     863           2 :                 if (const char *pszType = CSLFetchNameValue(
     864             :                         papszOptions,
     865             :                         CPLSPrintf("METADATA_LINK_%d_TYPE", nLinkNum)))
     866             :                 {
     867           2 :                     char *pszXML = OGRGetXML_UTF8_EscapedString(pszType);
     868           2 :                     PrintLine("      <type>%s</type>", pszXML);
     869           2 :                     CPLFree(pszXML);
     870             :                 }
     871           2 :                 PrintLine("  </link>");
     872             :             }
     873             :         }
     874             :     }
     875             : 
     876          24 :     if (const char *pszMetadataTime =
     877          24 :             CSLFetchNameValue(papszOptions, "METADATA_TIME"))
     878             :     {
     879           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataTime);
     880           1 :         PrintLine("  <time>%s</time>", pszXML);
     881           1 :         CPLFree(pszXML);
     882             :     }
     883             : 
     884          24 :     if (const char *pszMetadataKeywords =
     885          24 :             CSLFetchNameValue(papszOptions, "METADATA_KEYWORDS"))
     886             :     {
     887           1 :         char *pszXML = OGRGetXML_UTF8_EscapedString(pszMetadataKeywords);
     888           1 :         PrintLine("  <keywords>%s</keywords>", pszXML);
     889           1 :         CPLFree(pszXML);
     890             :     }
     891             : 
     892          24 :     if (m_bIsBackSeekable)
     893             :     {
     894             :         /* Reserve space for <bounds .../> within <metadata> */
     895             :         char szBounds[SPACE_FOR_METADATA_BOUNDS + 1];
     896          24 :         memset(szBounds, ' ', SPACE_FOR_METADATA_BOUNDS);
     897          24 :         szBounds[SPACE_FOR_METADATA_BOUNDS] = '\0';
     898          24 :         m_nOffsetBounds = m_fpOutput->Tell();
     899          24 :         PrintLine("%s", szBounds);
     900             :     }
     901          24 :     PrintLine("</metadata>");
     902             : 
     903          24 :     return TRUE;
     904             : }
     905             : 
     906             : /************************************************************************/
     907             : /*                             AddCoord()                               */
     908             : /************************************************************************/
     909             : 
     910          41 : void OGRGPXDataSource::AddCoord(double dfLon, double dfLat)
     911             : {
     912          41 :     m_dfMinLon = std::min(m_dfMinLon, dfLon);
     913          41 :     m_dfMinLat = std::min(m_dfMinLat, dfLat);
     914          41 :     m_dfMaxLon = std::max(m_dfMaxLon, dfLon);
     915          41 :     m_dfMaxLat = std::max(m_dfMaxLat, dfLat);
     916          41 : }
     917             : 
     918             : /************************************************************************/
     919             : /*                            PrintLine()                               */
     920             : /************************************************************************/
     921             : 
     922         372 : void OGRGPXDataSource::PrintLine(const char *fmt, ...)
     923             : {
     924         744 :     CPLString osWork;
     925             :     va_list args;
     926             : 
     927         372 :     va_start(args, fmt);
     928         372 :     osWork.vPrintf(fmt, args);
     929         372 :     va_end(args);
     930             : 
     931         372 :     m_fpOutput->Write(osWork.c_str(), 1, osWork.size());
     932         372 :     m_fpOutput->Write(m_pszEOL, 1, strlen(m_pszEOL));
     933         372 : }

Generated by: LCOV version 1.14