/******************************************************************************
 *
 * Project:  GPX Translator
 * Purpose:  Implements OGRGPXLayer class.
 * Author:   Even Rouault, even dot rouault at spatialys.com
 *
 ******************************************************************************
 * Copyright (c) 2007-2014, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_port.h"
#include "ogr_gpx.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_minixml.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#ifdef HAVE_EXPAT
#include "expat.h"
#endif
#include "ogr_core.h"
#include "ogr_expat.h"
#include "ogr_feature.h"
#include "ogr_geometry.h"
#include "ogr_p.h"
#include "ogr_spatialref.h"

constexpr int FLD_TRACK_FID = 0;
constexpr int FLD_TRACK_SEG_ID = 1;
#ifdef HAVE_EXPAT
constexpr int FLD_TRACK_PT_ID = 2;
#endif
constexpr int FLD_TRACK_NAME = 3;

constexpr int FLD_ROUTE_FID = 0;
#ifdef HAVE_EXPAT
constexpr int FLD_ROUTE_PT_ID = 1;
#endif
constexpr int FLD_ROUTE_NAME = 2;

/************************************************************************/
/*                            OGRGPXLayer()                             */
/*                                                                      */
/*      Note that the OGRGPXLayer assumes ownership of the passed       */
/*      file pointer.                                                   */
/************************************************************************/

OGRGPXLayer::OGRGPXLayer(const char *pszFilename, const char *pszLayerName,
                         GPXGeometryType gpxGeomTypeIn,
                         OGRGPXDataSource *poDSIn, int bWriteModeIn)
    : poDS(poDSIn), gpxGeomType(gpxGeomTypeIn),
      bWriteMode(CPL_TO_BOOL(bWriteModeIn)), nNextFID(0),
#ifdef HAVE_EXPAT
      oSchemaParser(nullptr),
#endif
      inInterestingElement(false), hasFoundLat(false), hasFoundLon(false),
#ifdef HAVE_EXPAT
      latVal(0.0), lonVal(0.0), iCurrentField(0),
#endif
      multiLineString(nullptr), lineString(nullptr), depthLevel(0),
      interestingDepthLevel(0),
#ifdef HAVE_EXPAT
      currentFieldDefn(nullptr), inExtensions(false), extensionsDepthLevel(0),
      inLink(false), iCountLink(0),
#endif
      trkFID(0), trkSegId(0), trkSegPtId(0), rteFID(0), rtePtId(0)
#ifdef HAVE_EXPAT
      ,
      bStopParsing(false), nWithoutEventCounter(0), nDataHandlerCounter(0)
#endif
{
#ifdef HAVE_EXPAT
    const char *gpxVersion = poDS->GetVersion();
#endif

    nMaxLinks = atoi(CPLGetConfigOption("GPX_N_MAX_LINKS", "2"));
    if (nMaxLinks < 0)
        nMaxLinks = 2;
    if (nMaxLinks > 100)
        nMaxLinks = 100;

    bEleAs25D = CPLTestBool(CPLGetConfigOption("GPX_ELE_AS_25D", "NO"));

    const bool bShortNames =
        CPLTestBool(CPLGetConfigOption("GPX_SHORT_NAMES", "NO"));

    poFeatureDefn = new OGRFeatureDefn(pszLayerName);
    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->Reference();

    if (gpxGeomType == GPX_TRACK_POINT)
    {
        /* Don't move this code. This fields must be number 0, 1 and 2 */
        /* in order to make OGRGPXLayer::startElementCbk work */
        OGRFieldDefn oFieldTrackFID("track_fid", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldTrackFID);

        OGRFieldDefn oFieldTrackSegID(
            (bShortNames) ? "trksegid" : "track_seg_id", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldTrackSegID);

        OGRFieldDefn oFieldTrackSegPointID(
            (bShortNames) ? "trksegptid" : "track_seg_point_id", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldTrackSegPointID);

        if (bWriteMode)
        {
            OGRFieldDefn oFieldName("track_name", OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldName);
        }
    }
    else if (gpxGeomType == GPX_ROUTE_POINT)
    {
        /* Don't move this code. See above */
        OGRFieldDefn oFieldRouteFID("route_fid", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldRouteFID);

        OGRFieldDefn oFieldRoutePointID(
            (bShortNames) ? "rteptid" : "route_point_id", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldRoutePointID);

        if (bWriteMode)
        {
            OGRFieldDefn oFieldName("route_name", OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldName);
        }
    }

    iFirstGPXField = poFeatureDefn->GetFieldCount();

    if (gpxGeomType == GPX_WPT || gpxGeomType == GPX_TRACK_POINT ||
        gpxGeomType == GPX_ROUTE_POINT)
    {
        poFeatureDefn->SetGeomType((bEleAs25D) ? wkbPoint25D : wkbPoint);
        /* Position info */

        OGRFieldDefn oFieldEle("ele", OFTReal);
        poFeatureDefn->AddFieldDefn(&oFieldEle);

        OGRFieldDefn oFieldTime("time", OFTDateTime);
        poFeatureDefn->AddFieldDefn(&oFieldTime);

#ifdef HAVE_EXPAT
        if (gpxGeomType == GPX_TRACK_POINT && gpxVersion &&
            strcmp(gpxVersion, "1.0") == 0)
        {
            OGRFieldDefn oFieldCourse("course", OFTReal);
            poFeatureDefn->AddFieldDefn(&oFieldCourse);

            OGRFieldDefn oFieldSpeed("speed", OFTReal);
            poFeatureDefn->AddFieldDefn(&oFieldSpeed);
        }
#endif

        OGRFieldDefn oFieldMagVar("magvar", OFTReal);
        poFeatureDefn->AddFieldDefn(&oFieldMagVar);

        OGRFieldDefn oFieldGeoidHeight("geoidheight", OFTReal);
        poFeatureDefn->AddFieldDefn(&oFieldGeoidHeight);

        /* Description info */

        OGRFieldDefn oFieldName("name", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldName);

        OGRFieldDefn oFieldCmt("cmt", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldCmt);

        OGRFieldDefn oFieldDesc("desc", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldDesc);

        OGRFieldDefn oFieldSrc("src", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldSrc);

#ifdef HAVE_EXPAT
        if (gpxVersion && strcmp(gpxVersion, "1.0") == 0)
        {
            OGRFieldDefn oFieldUrl("url", OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldUrl);

            OGRFieldDefn oFieldUrlName("urlname", OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldUrlName);
        }
        else
#endif
        {
            for (int i = 1; i <= nMaxLinks; i++)
            {
                char szFieldName[32];
                snprintf(szFieldName, sizeof(szFieldName), "link%d_href", i);
                OGRFieldDefn oFieldLinkHref(szFieldName, OFTString);
                poFeatureDefn->AddFieldDefn(&oFieldLinkHref);

                snprintf(szFieldName, sizeof(szFieldName), "link%d_text", i);
                OGRFieldDefn oFieldLinkText(szFieldName, OFTString);
                poFeatureDefn->AddFieldDefn(&oFieldLinkText);

                snprintf(szFieldName, sizeof(szFieldName), "link%d_type", i);
                OGRFieldDefn oFieldLinkType(szFieldName, OFTString);
                poFeatureDefn->AddFieldDefn(&oFieldLinkType);
            }
        }

        OGRFieldDefn oFieldSym("sym", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldSym);

        OGRFieldDefn oFieldType("type", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldType);

        /* Accuracy info */

        OGRFieldDefn oFieldFix("fix", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldFix);

        OGRFieldDefn oFieldSat("sat", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldSat);

        OGRFieldDefn oFieldHdop("hdop", OFTReal);
        poFeatureDefn->AddFieldDefn(&oFieldHdop);

        OGRFieldDefn oFieldVdop("vdop", OFTReal);
        poFeatureDefn->AddFieldDefn(&oFieldVdop);

        OGRFieldDefn oFieldPdop("pdop", OFTReal);
        poFeatureDefn->AddFieldDefn(&oFieldPdop);

        OGRFieldDefn oFieldAgeofgpsdata("ageofdgpsdata", OFTReal);
        poFeatureDefn->AddFieldDefn(&oFieldAgeofgpsdata);

        OGRFieldDefn oFieldDgpsid("dgpsid", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldDgpsid);
    }
    else
    {
        if (gpxGeomType == GPX_TRACK)
            poFeatureDefn->SetGeomType((bEleAs25D) ? wkbMultiLineString25D
                                                   : wkbMultiLineString);
        else
            poFeatureDefn->SetGeomType((bEleAs25D) ? wkbLineString25D
                                                   : wkbLineString);

        OGRFieldDefn oFieldName("name", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldName);

        OGRFieldDefn oFieldCmt("cmt", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldCmt);

        OGRFieldDefn oFieldDesc("desc", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldDesc);

        OGRFieldDefn oFieldSrc("src", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldSrc);

        for (int i = 1; i <= nMaxLinks; i++)
        {
            char szFieldName[32];
            snprintf(szFieldName, sizeof(szFieldName), "link%d_href", i);
            OGRFieldDefn oFieldLinkHref(szFieldName, OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldLinkHref);

            snprintf(szFieldName, sizeof(szFieldName), "link%d_text", i);
            OGRFieldDefn oFieldLinkText(szFieldName, OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldLinkText);

            snprintf(szFieldName, sizeof(szFieldName), "link%d_type", i);
            OGRFieldDefn oFieldLinkType(szFieldName, OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldLinkType);
        }

        OGRFieldDefn oFieldNumber("number", OFTInteger);
        poFeatureDefn->AddFieldDefn(&oFieldNumber);

        OGRFieldDefn oFieldType("type", OFTString);
        poFeatureDefn->AddFieldDefn(&oFieldType);
    }

    /* Number of 'standard' GPX attributes */
    nGPXFields = poFeatureDefn->GetFieldCount();

    ppoFeatureTab = nullptr;
    nFeatureTabIndex = 0;
    nFeatureTabLength = 0;
    pszSubElementName = nullptr;
    pszSubElementValue = nullptr;
    nSubElementValueLen = 0;

    poSRS = new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

    if (poFeatureDefn->GetGeomFieldCount() != 0)
        poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);

    poFeature = nullptr;

#ifdef HAVE_EXPAT
    oParser = nullptr;
#endif

    if (!bWriteMode)
    {
        fpGPX = VSIFOpenL(pszFilename, "r");
        if (fpGPX == nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
                     pszFilename);
            return;
        }

        if (poDS->GetUseExtensions() ||
            CPLTestBool(CPLGetConfigOption("GPX_USE_EXTENSIONS", "FALSE")))
        {
            LoadExtensionsSchema();
        }
    }
    else
        fpGPX = nullptr;

    OGRGPXLayer::ResetReading();
}

/************************************************************************/
/*                            ~OGRGPXLayer()                            */
/************************************************************************/

OGRGPXLayer::~OGRGPXLayer()

{
#ifdef HAVE_EXPAT
    if (oParser)
        XML_ParserFree(oParser);
#endif
    poFeatureDefn->Release();

    if (poSRS != nullptr)
        poSRS->Release();

    CPLFree(pszSubElementName);
    CPLFree(pszSubElementValue);

    for (int i = nFeatureTabIndex; i < nFeatureTabLength; i++)
        delete ppoFeatureTab[i];
    CPLFree(ppoFeatureTab);

    if (poFeature)
        delete poFeature;

    if (fpGPX)
        VSIFCloseL(fpGPX);
}

#ifdef HAVE_EXPAT

static void XMLCALL startElementCbk(void *pUserData, const char *pszName,
                                    const char **ppszAttr)
{
    static_cast<OGRGPXLayer *>(pUserData)->startElementCbk(pszName, ppszAttr);
}

static void XMLCALL endElementCbk(void *pUserData, const char *pszName)
{
    static_cast<OGRGPXLayer *>(pUserData)->endElementCbk(pszName);
}

static void XMLCALL dataHandlerCbk(void *pUserData, const char *data, int nLen)
{
    static_cast<OGRGPXLayer *>(pUserData)->dataHandlerCbk(data, nLen);
}

#endif

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRGPXLayer::ResetReading()

{
    nNextFID = 0;
    if (fpGPX)
    {
        VSIFSeekL(fpGPX, 0, SEEK_SET);
#ifdef HAVE_EXPAT
        if (oParser)
            XML_ParserFree(oParser);

        oParser = OGRCreateExpatXMLParser();
        XML_SetElementHandler(oParser, ::startElementCbk, ::endElementCbk);
        XML_SetCharacterDataHandler(oParser, ::dataHandlerCbk);
        XML_SetUserData(oParser, this);
#endif
    }
    hasFoundLat = false;
    hasFoundLon = false;
    inInterestingElement = false;
    CPLFree(pszSubElementName);
    pszSubElementName = nullptr;
    CPLFree(pszSubElementValue);
    pszSubElementValue = nullptr;
    nSubElementValueLen = 0;

    for (int i = nFeatureTabIndex; i < nFeatureTabLength; i++)
        delete ppoFeatureTab[i];
    CPLFree(ppoFeatureTab);
    nFeatureTabIndex = 0;
    nFeatureTabLength = 0;
    ppoFeatureTab = nullptr;
    if (poFeature)
        delete poFeature;
    poFeature = nullptr;
    multiLineString = nullptr;
    lineString = nullptr;

    depthLevel = 0;
    interestingDepthLevel = 0;

    trkFID = 0;
    trkSegId = 0;
    trkSegPtId = 0;
    rteFID = 0;
    rtePtId = 0;
}

#ifdef HAVE_EXPAT

/************************************************************************/
/*                        startElementCbk()                             */
/************************************************************************/

/** Replace ':' from XML NS element name by '_' more OGR friendly */
static char *OGRGPX_GetOGRCompatibleTagName(const char *pszName)
{
    char *pszModName = CPLStrdup(pszName);
    for (int i = 0; pszModName[i] != 0; i++)
    {
        if (pszModName[i] == ':')
            pszModName[i] = '_';
    }
    return pszModName;
}

void OGRGPXLayer::AddStrToSubElementValue(const char *pszStr)
{
    int len = (int)strlen(pszStr);
    char *pszNewSubElementValue = static_cast<char *>(
        VSI_REALLOC_VERBOSE(pszSubElementValue, nSubElementValueLen + len + 1));
    if (pszNewSubElementValue == nullptr)
    {
        XML_StopParser(oParser, XML_FALSE);
        bStopParsing = true;
        return;
    }
    pszSubElementValue = pszNewSubElementValue;
    memcpy(pszSubElementValue + nSubElementValueLen, pszStr, len);
    nSubElementValueLen += len;
}

void OGRGPXLayer::startElementCbk(const char *pszName, const char **ppszAttr)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    if ((gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0) ||
        (gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rtept") == 0) ||
        (gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkpt") == 0))
    {
        interestingDepthLevel = depthLevel;

        if (poFeature)
            delete poFeature;

        poFeature = new OGRFeature(poFeatureDefn);
        inInterestingElement = true;
        hasFoundLat = false;
        hasFoundLon = false;
        inExtensions = false;
        inLink = false;
        iCountLink = 0;

        for (int i = 0; ppszAttr[i]; i += 2)
        {
            if (strcmp(ppszAttr[i], "lat") == 0 && ppszAttr[i + 1][0])
            {
                hasFoundLat = true;
                latVal = CPLAtof(ppszAttr[i + 1]);
            }
            else if (strcmp(ppszAttr[i], "lon") == 0 && ppszAttr[i + 1][0])
            {
                hasFoundLon = true;
                lonVal = CPLAtof(ppszAttr[i + 1]);
            }
        }

        poFeature->SetFID(nNextFID++);

        if (hasFoundLat && hasFoundLon)
        {
            poFeature->SetGeometryDirectly(new OGRPoint(lonVal, latVal));
        }
        else
        {
            CPLDebug("GPX", "Skipping %s (FID=%d) without lat and/or lon",
                     pszName, nNextFID);
        }

        if (gpxGeomType == GPX_ROUTE_POINT)
        {
            rtePtId++;
            poFeature->SetField(FLD_ROUTE_FID, rteFID - 1);
            poFeature->SetField(FLD_ROUTE_PT_ID, rtePtId - 1);
        }
        else if (gpxGeomType == GPX_TRACK_POINT)
        {
            trkSegPtId++;

            poFeature->SetField(FLD_TRACK_FID, trkFID - 1);
            poFeature->SetField(FLD_TRACK_SEG_ID, trkSegId - 1);
            poFeature->SetField(FLD_TRACK_PT_ID, trkSegPtId - 1);
        }
    }
    else if (gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
    {
        interestingDepthLevel = depthLevel;

        if (poFeature)
            delete poFeature;
        inExtensions = false;
        inLink = false;
        iCountLink = 0;
        poFeature = new OGRFeature(poFeatureDefn);
        inInterestingElement = true;

        multiLineString = new OGRMultiLineString();
        lineString = nullptr;

        poFeature->SetFID(nNextFID++);
        poFeature->SetGeometryDirectly(multiLineString);
    }
    else if (gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trk") == 0)
    {
        trkFID++;
        trkSegId = 0;
    }
    else if (gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkseg") == 0)
    {
        trkSegId++;
        trkSegPtId = 0;
    }
    else if (gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
    {
        interestingDepthLevel = depthLevel;

        if (poFeature)
            delete poFeature;

        poFeature = new OGRFeature(poFeatureDefn);
        inInterestingElement = true;
        inExtensions = false;
        inLink = false;
        iCountLink = 0;

        lineString = new OGRLineString();
        poFeature->SetFID(nNextFID++);
        poFeature->SetGeometryDirectly(lineString);
    }
    else if (gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rte") == 0)
    {
        rteFID++;
        rtePtId = 0;
    }
    else if (inInterestingElement)
    {
        if (gpxGeomType == GPX_TRACK && strcmp(pszName, "trkseg") == 0 &&
            depthLevel == interestingDepthLevel + 1)
        {
            if (multiLineString)
            {
                lineString = new OGRLineString();
                multiLineString->addGeometryDirectly(lineString);
            }
        }
        else if (gpxGeomType == GPX_TRACK && strcmp(pszName, "trkpt") == 0 &&
                 depthLevel == interestingDepthLevel + 2)
        {
            if (lineString)
            {
                hasFoundLat = false;
                hasFoundLon = false;
                for (int i = 0; ppszAttr[i]; i += 2)
                {
                    if (strcmp(ppszAttr[i], "lat") == 0 && ppszAttr[i + 1][0])
                    {
                        hasFoundLat = true;
                        latVal = CPLAtof(ppszAttr[i + 1]);
                    }
                    else if (strcmp(ppszAttr[i], "lon") == 0 &&
                             ppszAttr[i + 1][0])
                    {
                        hasFoundLon = true;
                        lonVal = CPLAtof(ppszAttr[i + 1]);
                    }
                }

                if (hasFoundLat && hasFoundLon)
                {
                    lineString->addPoint(lonVal, latVal);
                }
                else
                {
                    CPLDebug("GPX", "Skipping %s without lat and/or lon",
                             pszName);
                }
            }
        }
        else if (gpxGeomType == GPX_ROUTE && strcmp(pszName, "rtept") == 0 &&
                 depthLevel == interestingDepthLevel + 1)
        {
            if (lineString)
            {
                hasFoundLat = false;
                hasFoundLon = false;
                for (int i = 0; ppszAttr[i]; i += 2)
                {
                    if (strcmp(ppszAttr[i], "lat") == 0 && ppszAttr[i + 1][0])
                    {
                        hasFoundLat = true;
                        latVal = CPLAtof(ppszAttr[i + 1]);
                    }
                    else if (strcmp(ppszAttr[i], "lon") == 0 &&
                             ppszAttr[i + 1][0])
                    {
                        hasFoundLon = true;
                        lonVal = CPLAtof(ppszAttr[i + 1]);
                    }
                }

                if (hasFoundLat && hasFoundLon)
                {
                    lineString->addPoint(lonVal, latVal);
                }
                else
                {
                    CPLDebug("GPX", "Skipping %s without lat and/or lon",
                             pszName);
                }
            }
        }
        else if (bEleAs25D && strcmp(pszName, "ele") == 0 &&
                 lineString != nullptr &&
                 ((gpxGeomType == GPX_ROUTE &&
                   depthLevel == interestingDepthLevel + 2) ||
                  (gpxGeomType == GPX_TRACK &&
                   depthLevel == interestingDepthLevel + 3)))
        {
            CPLFree(pszSubElementName);
            pszSubElementName = CPLStrdup(pszName);
        }
        else if (depthLevel == interestingDepthLevel + 1 &&
                 strcmp(pszName, "extensions") == 0)
        {
            if (poDS->GetUseExtensions())
            {
                inExtensions = true;
            }
        }
        else if (depthLevel == interestingDepthLevel + 1 ||
                 (inExtensions && depthLevel == interestingDepthLevel + 2))
        {
            CPLFree(pszSubElementName);
            pszSubElementName = nullptr;
            iCurrentField = -1;

            if (strcmp(pszName, "link") == 0)
            {
                iCountLink++;
                if (iCountLink <= nMaxLinks)
                {
                    if (ppszAttr[0] && ppszAttr[1] &&
                        strcmp(ppszAttr[0], "href") == 0)
                    {
                        char szFieldName[32];
                        snprintf(szFieldName, sizeof(szFieldName),
                                 "link%d_href", iCountLink);
                        iCurrentField =
                            poFeatureDefn->GetFieldIndex(szFieldName);
                        poFeature->SetField(iCurrentField, ppszAttr[1]);
                    }
                }
                else
                {
                    static int once = 1;
                    if (once)
                    {
                        once = 0;
                        CPLError(CE_Warning, CPLE_AppDefined,
                                 "GPX driver only reads %d links per element. "
                                 "Others will be ignored. "
                                 "This can be changed with the GPX_N_MAX_LINKS "
                                 "environment variable",
                                 nMaxLinks);
                    }
                }
                inLink = true;
                iCurrentField = -1;
            }
            else
            {
                for (int iField = 0; iField < poFeatureDefn->GetFieldCount();
                     iField++)
                {
                    bool bMatch = false;
                    if (iField >= nGPXFields)
                    {
                        char *pszCompatibleName =
                            OGRGPX_GetOGRCompatibleTagName(pszName);
                        bMatch = strcmp(poFeatureDefn->GetFieldDefn(iField)
                                            ->GetNameRef(),
                                        pszCompatibleName) == 0;
                        CPLFree(pszCompatibleName);
                    }
                    else
                    {
                        bMatch = strcmp(poFeatureDefn->GetFieldDefn(iField)
                                            ->GetNameRef(),
                                        pszName) == 0;
                    }

                    if (bMatch)
                    {
                        iCurrentField = iField;
                        pszSubElementName = CPLStrdup(pszName);
                        break;
                    }
                }
            }
        }
        else if (depthLevel == interestingDepthLevel + 2 && inLink)
        {
            CPLFree(pszSubElementName);
            pszSubElementName = nullptr;
            iCurrentField = -1;
            if (iCountLink <= nMaxLinks)
            {
                if (strcmp(pszName, "type") == 0)
                {
                    char szFieldName[32];
                    snprintf(szFieldName, sizeof(szFieldName), "link%d_type",
                             iCountLink);
                    iCurrentField = poFeatureDefn->GetFieldIndex(szFieldName);
                    pszSubElementName = CPLStrdup(pszName);
                }
                else if (strcmp(pszName, "text") == 0)
                {
                    char szFieldName[32];
                    snprintf(szFieldName, sizeof(szFieldName), "link%d_text",
                             iCountLink);
                    iCurrentField = poFeatureDefn->GetFieldIndex(szFieldName);
                    pszSubElementName = CPLStrdup(pszName);
                }
            }
        }
        else if (inExtensions && depthLevel > interestingDepthLevel + 2)
        {
            AddStrToSubElementValue((ppszAttr[0] == nullptr)
                                        ? CPLSPrintf("<%s>", pszName)
                                        : CPLSPrintf("<%s ", pszName));
            for (int i = 0; ppszAttr[i]; i += 2)
            {
                AddStrToSubElementValue(
                    CPLSPrintf("%s=\"%s\" ", ppszAttr[i], ppszAttr[i + 1]));
            }
            if (ppszAttr[0] != nullptr)
            {
                AddStrToSubElementValue(">");
            }
        }
    }

    depthLevel++;
}

/************************************************************************/
/*                           endElementCbk()                            */
/************************************************************************/

void OGRGPXLayer::endElementCbk(const char *pszName)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    depthLevel--;

    if (inInterestingElement)
    {
        if ((gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0) ||
            (gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rtept") == 0) ||
            (gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkpt") == 0))
        {
            const bool bIsValid = (hasFoundLat && hasFoundLon);
            inInterestingElement = false;

            if (bIsValid &&
                (m_poFilterGeom == nullptr ||
                 FilterGeometry(poFeature->GetGeometryRef())) &&
                (m_poAttrQuery == nullptr ||
                 m_poAttrQuery->Evaluate(poFeature)))
            {
                if (poFeature->GetGeometryRef() != nullptr)
                {
                    poFeature->GetGeometryRef()->assignSpatialReference(poSRS);

                    if (bEleAs25D)
                    {
                        for (int iField = 0;
                             iField < poFeatureDefn->GetFieldCount(); iField++)
                        {
                            if (strcmp(poFeatureDefn->GetFieldDefn(iField)
                                           ->GetNameRef(),
                                       "ele") == 0)
                            {
                                if (poFeature->IsFieldSetAndNotNull(iField))
                                {
                                    double val =
                                        poFeature->GetFieldAsDouble(iField);
                                    poFeature->GetGeometryRef()
                                        ->toPoint()
                                        ->setZ(val);
                                    poFeature->GetGeometryRef()
                                        ->setCoordinateDimension(3);
                                }
                                break;
                            }
                        }
                    }
                }

                ppoFeatureTab = static_cast<OGRFeature **>(
                    CPLRealloc(ppoFeatureTab,
                               sizeof(OGRFeature *) * (nFeatureTabLength + 1)));
                ppoFeatureTab[nFeatureTabLength] = poFeature;
                nFeatureTabLength++;
            }
            else
            {
                delete poFeature;
            }
            poFeature = nullptr;
        }
        else if (gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
        {
            inInterestingElement = false;
            if ((m_poFilterGeom == nullptr ||
                 FilterGeometry(poFeature->GetGeometryRef())) &&
                (m_poAttrQuery == nullptr ||
                 m_poAttrQuery->Evaluate(poFeature)))
            {
                if (poFeature->GetGeometryRef() != nullptr)
                {
                    poFeature->GetGeometryRef()->assignSpatialReference(poSRS);
                }

                ppoFeatureTab = static_cast<OGRFeature **>(
                    CPLRealloc(ppoFeatureTab,
                               sizeof(OGRFeature *) * (nFeatureTabLength + 1)));
                ppoFeatureTab[nFeatureTabLength] = poFeature;
                nFeatureTabLength++;
            }
            else
            {
                delete poFeature;
            }
            poFeature = nullptr;
            multiLineString = nullptr;
            lineString = nullptr;
        }
        else if (gpxGeomType == GPX_TRACK && strcmp(pszName, "trkseg") == 0 &&
                 depthLevel == interestingDepthLevel + 1)
        {
            lineString = nullptr;
        }
        else if (gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
        {
            inInterestingElement = false;
            if ((m_poFilterGeom == nullptr ||
                 FilterGeometry(poFeature->GetGeometryRef())) &&
                (m_poAttrQuery == nullptr ||
                 m_poAttrQuery->Evaluate(poFeature)))
            {
                if (poFeature->GetGeometryRef() != nullptr)
                {
                    poFeature->GetGeometryRef()->assignSpatialReference(poSRS);
                }

                ppoFeatureTab = (OGRFeature **)CPLRealloc(
                    ppoFeatureTab,
                    sizeof(OGRFeature *) * (nFeatureTabLength + 1));
                ppoFeatureTab[nFeatureTabLength] = poFeature;
                nFeatureTabLength++;
            }
            else
            {
                delete poFeature;
            }
            poFeature = nullptr;
            lineString = nullptr;
        }
        else if (bEleAs25D && strcmp(pszName, "ele") == 0 &&
                 lineString != nullptr &&
                 ((gpxGeomType == GPX_ROUTE &&
                   depthLevel == interestingDepthLevel + 2) ||
                  (gpxGeomType == GPX_TRACK &&
                   depthLevel == interestingDepthLevel + 3)))
        {
            poFeature->GetGeometryRef()->setCoordinateDimension(3);

            if (nSubElementValueLen)
            {
                pszSubElementValue[nSubElementValueLen] = 0;

                double val = CPLAtof(pszSubElementValue);
                int i = lineString->getNumPoints() - 1;
                if (i >= 0)
                    lineString->setPoint(i, lineString->getX(i),
                                         lineString->getY(i), val);
            }

            CPLFree(pszSubElementName);
            pszSubElementName = nullptr;
            CPLFree(pszSubElementValue);
            pszSubElementValue = nullptr;
            nSubElementValueLen = 0;
        }
        else if (depthLevel == interestingDepthLevel + 1 &&
                 strcmp(pszName, "extensions") == 0)
        {
            inExtensions = false;
        }
        else if ((depthLevel == interestingDepthLevel + 1 ||
                  (inExtensions && depthLevel == interestingDepthLevel + 2)) &&
                 pszSubElementName && strcmp(pszName, pszSubElementName) == 0)
        {
            if (poFeature && pszSubElementValue && nSubElementValueLen)
            {
                pszSubElementValue[nSubElementValueLen] = 0;
                if (strcmp(pszSubElementName, "time") == 0 &&
                    iCurrentField >= 0 &&
                    poFeature->GetFieldDefnRef(iCurrentField)->GetType() ==
                        OFTDateTime)
                {
                    OGRField sField;
                    if (OGRParseXMLDateTime(pszSubElementValue, &sField))
                    {
                        poFeature->SetField(iCurrentField, &sField);
                    }
                    else
                    {
                        CPLError(CE_Warning, CPLE_AppDefined,
                                 "Could not parse %s as a valid dateTime",
                                 pszSubElementValue);
                    }
                }
                else
                {
                    poFeature->SetField(iCurrentField, pszSubElementValue);
                }
            }
            if (strcmp(pszName, "link") == 0)
                inLink = false;

            CPLFree(pszSubElementName);
            pszSubElementName = nullptr;
            CPLFree(pszSubElementValue);
            pszSubElementValue = nullptr;
            nSubElementValueLen = 0;
        }
        else if (inLink && depthLevel == interestingDepthLevel + 2)
        {
            if (iCurrentField != -1 && pszSubElementName &&
                strcmp(pszName, pszSubElementName) == 0 && poFeature &&
                pszSubElementValue && nSubElementValueLen)
            {
                pszSubElementValue[nSubElementValueLen] = 0;
                poFeature->SetField(iCurrentField, pszSubElementValue);
            }

            CPLFree(pszSubElementName);
            pszSubElementName = nullptr;
            CPLFree(pszSubElementValue);
            pszSubElementValue = nullptr;
            nSubElementValueLen = 0;
        }
        else if (inExtensions && depthLevel > interestingDepthLevel + 2)
        {
            AddStrToSubElementValue(CPLSPrintf("</%s>", pszName));
        }
    }
}

/************************************************************************/
/*                          dataHandlerCbk()                            */
/************************************************************************/

void OGRGPXLayer::dataHandlerCbk(const char *data, int nLen)
{
    if (bStopParsing)
        return;

    nDataHandlerCounter++;
    if (nDataHandlerCounter >= BUFSIZ)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "File probably corrupted (million laugh pattern)");
        XML_StopParser(oParser, XML_FALSE);
        bStopParsing = true;
        return;
    }

    nWithoutEventCounter = 0;

    if (pszSubElementName)
    {
        if (inExtensions && depthLevel > interestingDepthLevel + 2)
        {
            if (data[0] == '\n')
                return;
        }
        char *pszNewSubElementValue = static_cast<char *>(VSI_REALLOC_VERBOSE(
            pszSubElementValue, nSubElementValueLen + nLen + 1));
        if (pszNewSubElementValue == nullptr)
        {
            XML_StopParser(oParser, XML_FALSE);
            bStopParsing = true;
            return;
        }
        pszSubElementValue = pszNewSubElementValue;
        memcpy(pszSubElementValue + nSubElementValueLen, data, nLen);
        nSubElementValueLen += nLen;
        if (nSubElementValueLen > 100000)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Too much data inside one element. "
                     "File probably corrupted");
            XML_StopParser(oParser, XML_FALSE);
            bStopParsing = true;
        }
    }
}
#endif

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRGPXLayer::GetNextFeature()
{
    if (bWriteMode)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot read features when writing a GPX file");
        return nullptr;
    }

    if (fpGPX == nullptr)
        return nullptr;

#ifdef HAVE_EXPAT

    if (bStopParsing)
        return nullptr;

    if (nFeatureTabIndex < nFeatureTabLength)
    {
        return ppoFeatureTab[nFeatureTabIndex++];
    }

    if (VSIFEofL(fpGPX))
        return nullptr;

    char aBuf[BUFSIZ];

    CPLFree(ppoFeatureTab);
    ppoFeatureTab = nullptr;
    nFeatureTabLength = 0;
    nFeatureTabIndex = 0;
    nWithoutEventCounter = 0;

    int nDone = 0;
    do
    {
        nDataHandlerCounter = 0;
        unsigned int nLen =
            static_cast<unsigned int>(VSIFReadL(aBuf, 1, sizeof(aBuf), fpGPX));
        nDone = VSIFEofL(fpGPX);
        if (XML_Parse(oParser, aBuf, nLen, nDone) == XML_STATUS_ERROR)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "XML parsing of GPX file failed : "
                     "%s at line %d, column %d",
                     XML_ErrorString(XML_GetErrorCode(oParser)),
                     static_cast<int>(XML_GetCurrentLineNumber(oParser)),
                     static_cast<int>(XML_GetCurrentColumnNumber(oParser)));
            bStopParsing = true;
            break;
        }
        nWithoutEventCounter++;
    } while (!nDone && nFeatureTabLength == 0 && !bStopParsing &&
             nWithoutEventCounter < 10);

    if (nWithoutEventCounter == 10)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Too much data inside one element. File probably corrupted");
        bStopParsing = true;
    }

    return (nFeatureTabLength) ? ppoFeatureTab[nFeatureTabIndex++] : nullptr;
#else
    return nullptr;
#endif
}

/************************************************************************/
/*                  OGRGPX_GetXMLCompatibleTagName()                    */
/************************************************************************/

static char *OGRGPX_GetXMLCompatibleTagName(const char *pszExtensionsNS,
                                            const char *pszName)
{
    /* Skip "ogr_" for example if NS is "ogr". Useful for GPX -> GPX roundtrip
     */
    if (strncmp(pszName, pszExtensionsNS, strlen(pszExtensionsNS)) == 0 &&
        pszName[strlen(pszExtensionsNS)] == '_')
    {
        pszName += strlen(pszExtensionsNS) + 1;
    }

    char *pszModName = CPLStrdup(pszName);
    for (int i = 0; pszModName[i] != 0; i++)
    {
        if (pszModName[i] == ' ')
            pszModName[i] = '_';
    }
    return pszModName;
}

/************************************************************************/
/*                     OGRGPX_GetUTF8String()                           */
/************************************************************************/

static char *OGRGPX_GetUTF8String(const char *pszString)
{
    char *pszEscaped = nullptr;
    if (!CPLIsUTF8(pszString, -1) &&
        CPLTestBool(CPLGetConfigOption("OGR_FORCE_ASCII", "YES")))
    {
        static bool bFirstTime = true;
        if (bFirstTime)
        {
            bFirstTime = false;
            CPLError(CE_Warning, CPLE_AppDefined,
                     "%s is not a valid UTF-8 string. Forcing it to ASCII.\n"
                     "If you still want the original string and change the XML "
                     "file encoding\n"
                     "afterwards, you can define OGR_FORCE_ASCII=NO as "
                     "configuration option.\n"
                     "This warning won't be issued anymore",
                     pszString);
        }
        else
        {
            CPLDebug("OGR",
                     "%s is not a valid UTF-8 string. Forcing it to ASCII",
                     pszString);
        }
        pszEscaped = CPLForceToASCII(pszString, -1, '?');
    }
    else
    {
        pszEscaped = CPLStrdup(pszString);
    }

    return pszEscaped;
}

/************************************************************************/
/*                   OGRGPX_WriteXMLExtension()                          */
/************************************************************************/

bool OGRGPXLayer::OGRGPX_WriteXMLExtension(const char *pszTagName,
                                           const char *pszContent)
{
    CPLXMLNode *poXML = CPLParseXMLString(pszContent);
    if (poXML)
    {
        const char *pszUnderscore = strchr(pszTagName, '_');
        char *pszTagNameWithNS = CPLStrdup(pszTagName);
        if (pszUnderscore)
            pszTagNameWithNS[pszUnderscore - pszTagName] = ':';

        /* If we detect a Garmin GPX extension, add its xmlns */
        const char *pszXMLNS = nullptr;
        if (strcmp(pszTagName, "gpxx_WaypointExtension") == 0)
            pszXMLNS = " xmlns:gpxx=\"http://www.garmin.com/xmlschemas/"
                       "GpxExtensions/v3\"";

        /* Don't XML escape here */
        char *pszUTF8 = OGRGPX_GetUTF8String(pszContent);
        poDS->PrintLine("    <%s%s>%s</%s>", pszTagNameWithNS,
                        (pszXMLNS) ? pszXMLNS : "", pszUTF8, pszTagNameWithNS);
        CPLFree(pszUTF8);

        CPLFree(pszTagNameWithNS);
        CPLDestroyXMLNode(poXML);

        return true;
    }

    return false;
}

/************************************************************************/
/*                      WriteFeatureAttributes()                        */
/************************************************************************/

static void AddIdent(VSILFILE *fp, int nIdentLevel)
{
    for (int i = 0; i < nIdentLevel; i++)
        VSIFPrintfL(fp, "  ");
}

void OGRGPXLayer::WriteFeatureAttributes(OGRFeature *poFeatureIn,
                                         int nIdentLevel)
{
    VSILFILE *fp = poDS->GetOutputFP();

    /* Begin with standard GPX fields */
    int i = iFirstGPXField;
    for (; i < nGPXFields; i++)
    {
        OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(i);
        if (poFeatureIn->IsFieldSetAndNotNull(i))
        {
            const char *pszName = poFieldDefn->GetNameRef();
            if (strcmp(pszName, "time") == 0)
            {
                char *pszDate =
                    OGRGetXMLDateTime(poFeatureIn->GetRawFieldRef(i));
                AddIdent(fp, nIdentLevel);
                poDS->PrintLine("<time>%s</time>", pszDate);
                CPLFree(pszDate);
            }
            else if (STARTS_WITH(pszName, "link"))
            {
                if (strstr(pszName, "href"))
                {
                    AddIdent(fp, nIdentLevel);
                    VSIFPrintfL(fp, "<link href=\"%s\">",
                                poFeatureIn->GetFieldAsString(i));
                    if (poFeatureIn->IsFieldSetAndNotNull(i + 1))
                        VSIFPrintfL(fp, "<text>%s</text>",
                                    poFeatureIn->GetFieldAsString(i + 1));
                    if (poFeatureIn->IsFieldSetAndNotNull(i + 2))
                        VSIFPrintfL(fp, "<type>%s</type>",
                                    poFeatureIn->GetFieldAsString(i + 2));
                    poDS->PrintLine("</link>");
                }
            }
            else if (poFieldDefn->GetType() == OFTReal)
            {
                char szValue[64];
                OGRFormatDouble(szValue, sizeof(szValue),
                                poFeatureIn->GetFieldAsDouble(i), '.');
                AddIdent(fp, nIdentLevel);
                poDS->PrintLine("<%s>%s</%s>", pszName, szValue, pszName);
            }
            else
            {
                char *pszValue = OGRGetXML_UTF8_EscapedString(
                    poFeatureIn->GetFieldAsString(i));
                AddIdent(fp, nIdentLevel);
                poDS->PrintLine("<%s>%s</%s>", pszName, pszValue, pszName);
                CPLFree(pszValue);
            }
        }
    }

    /* Write "extra" fields within the <extensions> tag */
    int n = poFeatureDefn->GetFieldCount();
    if (i < n)
    {
        const char *pszExtensionsNS = poDS->GetExtensionsNS();
        AddIdent(fp, nIdentLevel);
        poDS->PrintLine("<extensions>");
        for (; i < n; i++)
        {
            OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(i);
            if (poFeatureIn->IsFieldSetAndNotNull(i))
            {
                char *compatibleName = OGRGPX_GetXMLCompatibleTagName(
                    pszExtensionsNS, poFieldDefn->GetNameRef());

                if (poFieldDefn->GetType() == OFTReal)
                {
                    char szValue[64];
                    OGRFormatDouble(szValue, sizeof(szValue),
                                    poFeatureIn->GetFieldAsDouble(i), '.');
                    AddIdent(fp, nIdentLevel + 1);
                    poDS->PrintLine("<%s:%s>%s</%s:%s>", pszExtensionsNS,
                                    compatibleName, szValue, pszExtensionsNS,
                                    compatibleName);
                }
                else
                {
                    const char *pszRaw = poFeatureIn->GetFieldAsString(i);

                    /* Try to detect XML content */
                    if (pszRaw[0] == '<' && pszRaw[strlen(pszRaw) - 1] == '>')
                    {
                        if (OGRGPX_WriteXMLExtension(compatibleName, pszRaw))
                        {
                            CPLFree(compatibleName);
                            continue;
                        }
                    }

                    /* Try to detect XML escaped content */
                    else if (STARTS_WITH(pszRaw, "&lt;") &&
                             STARTS_WITH(pszRaw + strlen(pszRaw) - 4, "&gt;"))
                    {
                        char *pszUnescapedContent =
                            CPLUnescapeString(pszRaw, nullptr, CPLES_XML);

                        if (OGRGPX_WriteXMLExtension(compatibleName,
                                                     pszUnescapedContent))
                        {
                            CPLFree(pszUnescapedContent);
                            CPLFree(compatibleName);
                            continue;
                        }

                        CPLFree(pszUnescapedContent);
                    }

                    /* Remove leading spaces for a numeric field */
                    if (poFieldDefn->GetType() == OFTInteger)
                    {
                        while (*pszRaw == ' ')
                            pszRaw++;
                    }

                    char *pszEscaped = OGRGetXML_UTF8_EscapedString(pszRaw);
                    AddIdent(fp, nIdentLevel + 1);
                    poDS->PrintLine("<%s:%s>%s</%s:%s>", pszExtensionsNS,
                                    compatibleName, pszEscaped, pszExtensionsNS,
                                    compatibleName);
                    CPLFree(pszEscaped);
                }
                CPLFree(compatibleName);
            }
        }
        AddIdent(fp, nIdentLevel);
        poDS->PrintLine("</extensions>");
    }
}

/************************************************************************/
/*                CheckAndFixCoordinatesValidity()                      */
/************************************************************************/

OGRErr OGRGPXLayer::CheckAndFixCoordinatesValidity(double *pdfLatitude,
                                                   double *pdfLongitude)
{
    if (pdfLatitude != nullptr && (*pdfLatitude < -90 || *pdfLatitude > 90))
    {
        static bool bFirstWarning = true;
        if (bFirstWarning)
        {
            bFirstWarning = false;
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Latitude %f is invalid. Valid range is [-90,90]. "
                     "This warning will not be issued any more",
                     *pdfLatitude);
        }
        return OGRERR_FAILURE;
    }

    if (pdfLongitude != nullptr &&
        (*pdfLongitude < -180 || *pdfLongitude > 180))
    {
        static bool bFirstWarning = true;
        if (bFirstWarning)
        {
            bFirstWarning = false;
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Longitude %f has been modified to fit into "
                     "range [-180,180]. This warning will not be "
                     "issued any more",
                     *pdfLongitude);
        }

        *pdfLongitude = fmod(*pdfLongitude + 180.0, 360.0) - 180.0;
        return OGRERR_NONE;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           ICreateFeature()                            */
/************************************************************************/

OGRErr OGRGPXLayer::ICreateFeature(OGRFeature *poFeatureIn)

{
    VSILFILE *fp = poDS->GetOutputFP();
    if (fp == nullptr)
        return OGRERR_FAILURE;

    char szLat[64];
    char szLon[64];
    char szAlt[64];

    OGRGeometry *poGeom = poFeatureIn->GetGeometryRef();

    if (gpxGeomType == GPX_WPT)
    {
        if (poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Cannot write a 'wpt' element after a 'rte' element.\n");
            return OGRERR_FAILURE;
        }
        else if (poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Cannot write a 'wpt' element after a 'trk' element.\n");
            return OGRERR_FAILURE;
        }

        poDS->SetLastGPXGeomTypeWritten(gpxGeomType);

        if (poGeom == nullptr ||
            wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Features without geometry or with non-ponctual geometries not "
                "supported by GPX writer in waypoints layer.");
            return OGRERR_FAILURE;
        }

        if (poGeom->getCoordinateDimension() == 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "POINT EMPTY geometries not supported by GPX writer.");
            return OGRERR_FAILURE;
        }

        OGRPoint *point = poGeom->toPoint();
        double lat = point->getY();
        double lon = point->getX();
        CheckAndFixCoordinatesValidity(&lat, &lon);
        poDS->AddCoord(lon, lat);
        OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
        OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
        poDS->PrintLine("<wpt lat=\"%s\" lon=\"%s\">", szLat, szLon);
        WriteFeatureAttributes(poFeatureIn);
        poDS->PrintLine("</wpt>");
    }
    else if (gpxGeomType == GPX_ROUTE)
    {
        if (poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK ||
            poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Cannot write a 'rte' element after a 'trk' element.\n");
            return OGRERR_FAILURE;
        }

        if (poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
            poDS->nLastRteId != -1)
        {
            poDS->PrintLine("</rte>");
            poDS->nLastRteId = -1;
        }

        poDS->SetLastGPXGeomTypeWritten(gpxGeomType);

        OGRLineString *line = nullptr;

        if (poGeom == nullptr)
        {
            poDS->PrintLine("<rte>");
            WriteFeatureAttributes(poFeatureIn);
            poDS->PrintLine("</rte>");
            return OGRERR_NONE;
        }

        switch (poGeom->getGeometryType())
        {
            case wkbLineString:
            case wkbLineString25D:
            {
                line = poGeom->toLineString();
                break;
            }

            case wkbMultiLineString:
            case wkbMultiLineString25D:
            {
                int nGeometries =
                    poGeom->toMultiLineString()->getNumGeometries();
                if (nGeometries == 0)
                {
                    line = nullptr;
                }
                else if (nGeometries == 1)
                {
                    line = poGeom->toMultiLineString()->getGeometryRef(0);
                }
                else
                {
                    CPLError(CE_Failure, CPLE_NotSupported,
                             "Multiline with more than one line is not "
                             "supported for 'rte' element.");
                    return OGRERR_FAILURE;
                }
                break;
            }

            default:
            {
                CPLError(
                    CE_Failure, CPLE_NotSupported,
                    "Geometry type of `%s' not supported for 'rte' element.\n",
                    OGRGeometryTypeToName(poGeom->getGeometryType()));
                return OGRERR_FAILURE;
            }
        }

        poDS->PrintLine("<rte>");
        WriteFeatureAttributes(poFeatureIn);
        if (line)
        {
            const int n = line->getNumPoints();
            for (int i = 0; i < n; i++)
            {
                double lat = line->getY(i);
                double lon = line->getX(i);
                CheckAndFixCoordinatesValidity(&lat, &lon);
                poDS->AddCoord(lon, lat);
                OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
                OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
                poDS->PrintLine("  <rtept lat=\"%s\" lon=\"%s\">", szLat,
                                szLon);
                if (poGeom->getGeometryType() == wkbLineString25D ||
                    poGeom->getGeometryType() == wkbMultiLineString25D)
                {
                    OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i), '.');
                    poDS->PrintLine("    <ele>%s</ele>", szAlt);
                }
                poDS->PrintLine("  </rtept>");
            }
        }
        poDS->PrintLine("</rte>");
    }
    else if (gpxGeomType == GPX_TRACK)
    {
        if (poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
            poDS->nLastRteId != -1)
        {
            poDS->PrintLine("</rte>");
            poDS->nLastRteId = -1;
        }
        if (poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT &&
            poDS->nLastTrkId != -1)
        {
            poDS->PrintLine("  </trkseg>");
            poDS->PrintLine("</trk>");
            poDS->nLastTrkId = -1;
            poDS->nLastTrkSegId = -1;
        }

        poDS->SetLastGPXGeomTypeWritten(gpxGeomType);

        if (poGeom == nullptr)
        {
            poDS->PrintLine("<trk>");
            WriteFeatureAttributes(poFeatureIn);
            poDS->PrintLine("</trk>");
            return OGRERR_NONE;
        }

        switch (poGeom->getGeometryType())
        {
            case wkbLineString:
            case wkbLineString25D:
            {
                OGRLineString *line = poGeom->toLineString();
                int n = line->getNumPoints();
                poDS->PrintLine("<trk>");
                WriteFeatureAttributes(poFeatureIn);
                poDS->PrintLine("  <trkseg>");
                for (int i = 0; i < n; i++)
                {
                    double lat = line->getY(i);
                    double lon = line->getX(i);
                    CheckAndFixCoordinatesValidity(&lat, &lon);
                    poDS->AddCoord(lon, lat);
                    OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
                    OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
                    poDS->PrintLine("    <trkpt lat=\"%s\" lon=\"%s\">", szLat,
                                    szLon);
                    if (line->getGeometryType() == wkbLineString25D)
                    {
                        OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i),
                                        '.');
                        poDS->PrintLine("        <ele>%s</ele>", szAlt);
                    }
                    poDS->PrintLine("    </trkpt>");
                }
                poDS->PrintLine("  </trkseg>");
                poDS->PrintLine("</trk>");
                break;
            }

            case wkbMultiLineString:
            case wkbMultiLineString25D:
            {
                poDS->PrintLine("<trk>");
                WriteFeatureAttributes(poFeatureIn);
                for (auto &&line : poGeom->toMultiLineString())
                {
                    const int n = (line) ? line->getNumPoints() : 0;
                    poDS->PrintLine("  <trkseg>");
                    for (int i = 0; i < n; i++)
                    {
                        double lat = line->getY(i);
                        double lon = line->getX(i);
                        CheckAndFixCoordinatesValidity(&lat, &lon);
                        poDS->AddCoord(lon, lat);
                        OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
                        OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
                        poDS->PrintLine("    <trkpt lat=\"%s\" lon=\"%s\">",
                                        szLat, szLon);
                        if (line->getGeometryType() == wkbLineString25D)
                        {
                            OGRFormatDouble(szAlt, sizeof(szAlt), line->getZ(i),
                                            '.');
                            poDS->PrintLine("        <ele>%s</ele>", szAlt);
                        }
                        poDS->PrintLine("    </trkpt>");
                    }
                    poDS->PrintLine("  </trkseg>");
                }
                poDS->PrintLine("</trk>");
                break;
            }

            default:
            {
                CPLError(
                    CE_Failure, CPLE_NotSupported,
                    "Geometry type of `%s' not supported for 'trk' element.\n",
                    OGRGeometryTypeToName(poGeom->getGeometryType()));
                return OGRERR_FAILURE;
            }
        }
    }
    else if (gpxGeomType == GPX_ROUTE_POINT)
    {
        if (poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK ||
            poDS->GetLastGPXGeomTypeWritten() == GPX_TRACK_POINT)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Cannot write a 'rte' element after a 'trk' element.\n");
            return OGRERR_FAILURE;
        }

        if (poGeom == nullptr ||
            wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Features without geometry or with non-ponctual geometries not "
                "supported by GPX writer in route_points layer.");
            return OGRERR_FAILURE;
        }

        if (poGeom->getCoordinateDimension() == 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "POINT EMPTY geometries not supported by GPX writer.");
            return OGRERR_FAILURE;
        }

        if (!poFeatureIn->IsFieldSetAndNotNull(FLD_ROUTE_FID))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Field %s must be set.",
                     poFeatureDefn->GetFieldDefn(FLD_ROUTE_FID)->GetNameRef());
            return OGRERR_FAILURE;
        }
        if (poFeatureIn->GetFieldAsInteger(FLD_ROUTE_FID) < 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
                     poFeatureDefn->GetFieldDefn(FLD_ROUTE_FID)->GetNameRef());
            return OGRERR_FAILURE;
        }

        poDS->SetLastGPXGeomTypeWritten(gpxGeomType);

        if (poDS->nLastRteId != poFeatureIn->GetFieldAsInteger(FLD_ROUTE_FID))
        {
            if (poDS->nLastRteId != -1)
            {
                poDS->PrintLine("</rte>");
            }
            poDS->PrintLine("<rte>");
            if (poFeatureIn->IsFieldSetAndNotNull(FLD_ROUTE_NAME))
            {
                char *pszValue = OGRGetXML_UTF8_EscapedString(
                    poFeatureIn->GetFieldAsString(FLD_ROUTE_NAME));
                poDS->PrintLine("  <%s>%s</%s>", "name", pszValue, "name");
                CPLFree(pszValue);
            }
        }

        poDS->nLastRteId = poFeatureIn->GetFieldAsInteger(FLD_ROUTE_FID);

        OGRPoint *point = poGeom->toPoint();
        double lat = point->getY();
        double lon = point->getX();
        CheckAndFixCoordinatesValidity(&lat, &lon);
        poDS->AddCoord(lon, lat);
        OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
        OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
        poDS->PrintLine("  <rtept lat=\"%s\" lon=\"%s\">", szLat, szLon);
        WriteFeatureAttributes(poFeatureIn, 2);
        poDS->PrintLine("  </rtept>");
    }
    else
    {
        if (poDS->GetLastGPXGeomTypeWritten() == GPX_ROUTE_POINT &&
            poDS->nLastRteId != -1)
        {
            poDS->PrintLine("</rte>");
            poDS->nLastRteId = -1;
        }

        if (poGeom == nullptr ||
            wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Features without geometry or with non-ponctual geometries not "
                "supported by GPX writer in track_points layer.");
            return OGRERR_FAILURE;
        }

        if (poGeom->getCoordinateDimension() == 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "POINT EMPTY geometries not supported by GPX writer.");
            return OGRERR_FAILURE;
        }

        if (!poFeatureIn->IsFieldSetAndNotNull(FLD_TRACK_FID))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Field %s must be set.",
                     poFeatureDefn->GetFieldDefn(FLD_TRACK_FID)->GetNameRef());
            return OGRERR_FAILURE;
        }
        if (poFeatureIn->GetFieldAsInteger(FLD_TRACK_FID) < 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
                     poFeatureDefn->GetFieldDefn(FLD_TRACK_FID)->GetNameRef());
            return OGRERR_FAILURE;
        }
        if (!poFeatureIn->IsFieldSetAndNotNull(FLD_TRACK_SEG_ID))
        {
            CPLError(
                CE_Failure, CPLE_AppDefined, "Field %s must be set.",
                poFeatureDefn->GetFieldDefn(FLD_TRACK_SEG_ID)->GetNameRef());
            return OGRERR_FAILURE;
        }
        if (poFeatureIn->GetFieldAsInteger(FLD_TRACK_SEG_ID) < 0)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined, "Invalid value for field %s.",
                poFeatureDefn->GetFieldDefn(FLD_TRACK_SEG_ID)->GetNameRef());
            return OGRERR_FAILURE;
        }

        poDS->SetLastGPXGeomTypeWritten(gpxGeomType);

        if (poDS->nLastTrkId != poFeatureIn->GetFieldAsInteger(FLD_TRACK_FID))
        {
            if (poDS->nLastTrkId != -1)
            {
                poDS->PrintLine("  </trkseg>");
                poDS->PrintLine("</trk>");
            }
            poDS->PrintLine("<trk>");

            if (poFeatureIn->IsFieldSetAndNotNull(FLD_TRACK_NAME))
            {
                char *pszValue = OGRGetXML_UTF8_EscapedString(
                    poFeatureIn->GetFieldAsString(FLD_TRACK_NAME));
                poDS->PrintLine("  <%s>%s</%s>", "name", pszValue, "name");
                CPLFree(pszValue);
            }

            poDS->PrintLine("  <trkseg>");
        }
        else if (poDS->nLastTrkSegId !=
                 poFeatureIn->GetFieldAsInteger(FLD_TRACK_SEG_ID))
        {
            poDS->PrintLine("  </trkseg>");
            poDS->PrintLine("  <trkseg>");
        }

        poDS->nLastTrkId = poFeatureIn->GetFieldAsInteger(FLD_TRACK_FID);
        poDS->nLastTrkSegId = poFeatureIn->GetFieldAsInteger(FLD_TRACK_SEG_ID);

        OGRPoint *point = poGeom->toPoint();
        double lat = point->getY();
        double lon = point->getX();
        CheckAndFixCoordinatesValidity(&lat, &lon);
        poDS->AddCoord(lon, lat);
        OGRFormatDouble(szLat, sizeof(szLat), lat, '.');
        OGRFormatDouble(szLon, sizeof(szLon), lon, '.');
        poDS->PrintLine("    <trkpt lat=\"%s\" lon=\"%s\">", szLat, szLon);
        WriteFeatureAttributes(poFeatureIn, 3);
        poDS->PrintLine("    </trkpt>");
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr OGRGPXLayer::CreateField(OGRFieldDefn *poField, CPL_UNUSED int bApproxOK)
{
    for (int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
    {
        if (strcmp(poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
                   poField->GetNameRef()) == 0)
        {
            return OGRERR_NONE;
        }
    }
    if (!poDS->GetUseExtensions())
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Field of name '%s' is not supported in GPX schema. "
                 "Use GPX_USE_EXTENSIONS creation option to allow use of the "
                 "<extensions> element.",
                 poField->GetNameRef());
        return OGRERR_FAILURE;
    }
    else
    {
        poFeatureDefn->AddFieldDefn(poField);
        return OGRERR_NONE;
    }
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRGPXLayer::TestCapability(const char *pszCap)

{
    if (EQUAL(pszCap, OLCSequentialWrite))
        return bWriteMode;
    else if (EQUAL(pszCap, OLCCreateField))
        return bWriteMode;
    else if (EQUAL(pszCap, OLCStringsAsUTF8))
        return TRUE;
    else if (EQUAL(pszCap, OLCZGeometries))
        return TRUE;

    else
        return FALSE;
}

/************************************************************************/
/*                       LoadExtensionsSchema()                         */
/************************************************************************/

#ifdef HAVE_EXPAT

static void XMLCALL startElementLoadSchemaCbk(void *pUserData,
                                              const char *pszName,
                                              const char **ppszAttr)
{
    static_cast<OGRGPXLayer *>(pUserData)->startElementLoadSchemaCbk(pszName,
                                                                     ppszAttr);
}

static void XMLCALL endElementLoadSchemaCbk(void *pUserData,
                                            const char *pszName)
{
    static_cast<OGRGPXLayer *>(pUserData)->endElementLoadSchemaCbk(pszName);
}

static void XMLCALL dataHandlerLoadSchemaCbk(void *pUserData, const char *data,
                                             int nLen)
{
    static_cast<OGRGPXLayer *>(pUserData)->dataHandlerLoadSchemaCbk(data, nLen);
}

/** This function parses the whole file to detect the extensions fields */
void OGRGPXLayer::LoadExtensionsSchema()
{
    oSchemaParser = OGRCreateExpatXMLParser();
    XML_SetElementHandler(oSchemaParser, ::startElementLoadSchemaCbk,
                          ::endElementLoadSchemaCbk);
    XML_SetCharacterDataHandler(oSchemaParser, ::dataHandlerLoadSchemaCbk);
    XML_SetUserData(oSchemaParser, this);

    VSIFSeekL(fpGPX, 0, SEEK_SET);

    inInterestingElement = false;
    inExtensions = false;
    depthLevel = 0;
    currentFieldDefn = nullptr;
    pszSubElementName = nullptr;
    pszSubElementValue = nullptr;
    nSubElementValueLen = 0;
    nWithoutEventCounter = 0;
    bStopParsing = false;

    char aBuf[BUFSIZ];
    int nDone = 0;
    do
    {
        nDataHandlerCounter = 0;
        unsigned int nLen =
            static_cast<unsigned int>(VSIFReadL(aBuf, 1, sizeof(aBuf), fpGPX));
        nDone = VSIFEofL(fpGPX);
        if (XML_Parse(oSchemaParser, aBuf, nLen, nDone) == XML_STATUS_ERROR)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "XML parsing of GPX file failed : "
                "%s at line %d, column %d",
                XML_ErrorString(XML_GetErrorCode(oSchemaParser)),
                static_cast<int>(XML_GetCurrentLineNumber(oSchemaParser)),
                static_cast<int>(XML_GetCurrentColumnNumber(oSchemaParser)));
            bStopParsing = true;
            break;
        }
        nWithoutEventCounter++;
    } while (!nDone && !bStopParsing && nWithoutEventCounter < 10);

    if (nWithoutEventCounter == 10)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Too much data inside one element. File probably corrupted");
        bStopParsing = true;
    }

    XML_ParserFree(oSchemaParser);
    oSchemaParser = nullptr;

    VSIFSeekL(fpGPX, 0, SEEK_SET);
}

/************************************************************************/
/*                  startElementLoadSchemaCbk()                         */
/************************************************************************/

void OGRGPXLayer::startElementLoadSchemaCbk(const char *pszName,
                                            CPL_UNUSED const char **ppszAttr)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    if (gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0)
    {
        inInterestingElement = true;
        inExtensions = false;
        interestingDepthLevel = depthLevel;
    }
    else if (gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
    {
        inInterestingElement = true;
        inExtensions = false;
        interestingDepthLevel = depthLevel;
    }
    else if (gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
    {
        inInterestingElement = true;
        inExtensions = false;
        interestingDepthLevel = depthLevel;
    }
    else if (gpxGeomType == GPX_TRACK_POINT && strcmp(pszName, "trkpt") == 0)
    {
        inInterestingElement = true;
        inExtensions = false;
        interestingDepthLevel = depthLevel;
    }
    else if (gpxGeomType == GPX_ROUTE_POINT && strcmp(pszName, "rtept") == 0)
    {
        inInterestingElement = true;
        inExtensions = false;
        interestingDepthLevel = depthLevel;
    }
    else if (inInterestingElement)
    {
        if (depthLevel == interestingDepthLevel + 1 &&
            strcmp(pszName, "extensions") == 0)
        {
            inExtensions = true;
            extensionsDepthLevel = depthLevel;
        }
        else if (inExtensions && depthLevel == extensionsDepthLevel + 1)
        {
            CPLFree(pszSubElementName);
            pszSubElementName = CPLStrdup(pszName);

            int iField = 0;  // Used after for.
            for (; iField < poFeatureDefn->GetFieldCount(); iField++)
            {
                bool bMatch = false;
                if (iField >= nGPXFields)
                {
                    char *pszCompatibleName =
                        OGRGPX_GetOGRCompatibleTagName(pszName);
                    bMatch =
                        strcmp(
                            poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
                            pszCompatibleName) == 0;
                    CPLFree(pszCompatibleName);
                }
                else
                {
                    bMatch =
                        strcmp(
                            poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
                            pszName) == 0;
                }

                if (bMatch)
                {
                    currentFieldDefn = poFeatureDefn->GetFieldDefn(iField);
                    break;
                }
            }
            if (iField == poFeatureDefn->GetFieldCount())
            {
                char *pszCompatibleName =
                    OGRGPX_GetOGRCompatibleTagName(pszName);
                OGRFieldDefn newFieldDefn(pszCompatibleName, OFTInteger);
                CPLFree(pszCompatibleName);

                poFeatureDefn->AddFieldDefn(&newFieldDefn);
                currentFieldDefn = poFeatureDefn->GetFieldDefn(
                    poFeatureDefn->GetFieldCount() - 1);

                if (poFeatureDefn->GetFieldCount() == 100)
                {
                    CPLError(CE_Failure, CPLE_AppDefined,
                             "Too many fields. File probably corrupted");
                    XML_StopParser(oSchemaParser, XML_FALSE);
                    bStopParsing = true;
                }
            }
        }
    }

    depthLevel++;
}

/************************************************************************/
/*                   endElementLoadSchemaCbk()                           */
/************************************************************************/

static bool OGRGPXIsInt(const char *pszStr)
{
    while (*pszStr == ' ')
        pszStr++;

    for (int i = 0; pszStr[i]; i++)
    {
        if (pszStr[i] == '+' || pszStr[i] == '-')
        {
            if (i != 0)
                return false;
        }
        else if (!(pszStr[i] >= '0' && pszStr[i] <= '9'))
            return false;
    }
    return true;
}

void OGRGPXLayer::endElementLoadSchemaCbk(const char *pszName)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    depthLevel--;

    if (inInterestingElement)
    {
        if (gpxGeomType == GPX_WPT && strcmp(pszName, "wpt") == 0)
        {
            inInterestingElement = false;
            inExtensions = false;
        }
        else if (gpxGeomType == GPX_TRACK && strcmp(pszName, "trk") == 0)
        {
            inInterestingElement = false;
            inExtensions = false;
        }
        else if (gpxGeomType == GPX_ROUTE && strcmp(pszName, "rte") == 0)
        {
            inInterestingElement = false;
            inExtensions = false;
        }
        else if (gpxGeomType == GPX_TRACK_POINT &&
                 strcmp(pszName, "trkpt") == 0)
        {
            inInterestingElement = false;
            inExtensions = false;
        }
        else if (gpxGeomType == GPX_ROUTE_POINT &&
                 strcmp(pszName, "rtept") == 0)
        {
            inInterestingElement = false;
            inExtensions = false;
        }
        else if (depthLevel == interestingDepthLevel + 1 &&
                 strcmp(pszName, "extensions") == 0)
        {
            inExtensions = false;
        }
        else if (inExtensions && depthLevel == extensionsDepthLevel + 1 &&
                 pszSubElementName && strcmp(pszName, pszSubElementName) == 0)
        {
            if (pszSubElementValue && nSubElementValueLen && currentFieldDefn)
            {
                pszSubElementValue[nSubElementValueLen] = 0;
                if (currentFieldDefn->GetType() == OFTInteger ||
                    currentFieldDefn->GetType() == OFTReal)
                {
                    char *pszRemainingStr = nullptr;
                    CPLStrtod(pszSubElementValue, &pszRemainingStr);
                    if (pszRemainingStr == nullptr || *pszRemainingStr == 0 ||
                        *pszRemainingStr == ' ')
                    {
                        if (currentFieldDefn->GetType() == OFTInteger)
                        {
                            if (!OGRGPXIsInt(pszSubElementValue))
                            {
                                currentFieldDefn->SetType(OFTReal);
                            }
                        }
                    }
                    else
                    {
                        currentFieldDefn->SetType(OFTString);
                    }
                }
            }

            CPLFree(pszSubElementName);
            pszSubElementName = nullptr;
            CPLFree(pszSubElementValue);
            pszSubElementValue = nullptr;
            nSubElementValueLen = 0;
            currentFieldDefn = nullptr;
        }
    }
}

/************************************************************************/
/*                   dataHandlerLoadSchemaCbk()                         */
/************************************************************************/

void OGRGPXLayer::dataHandlerLoadSchemaCbk(const char *data, int nLen)
{
    if (bStopParsing)
        return;

    nDataHandlerCounter++;
    if (nDataHandlerCounter >= BUFSIZ)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "File probably corrupted (million laugh pattern)");
        XML_StopParser(oSchemaParser, XML_FALSE);
        bStopParsing = true;
        return;
    }

    nWithoutEventCounter = 0;

    if (pszSubElementName)
    {
        char *pszNewSubElementValue = static_cast<char *>(VSI_REALLOC_VERBOSE(
            pszSubElementValue, nSubElementValueLen + nLen + 1));
        if (pszNewSubElementValue == nullptr)
        {
            XML_StopParser(oSchemaParser, XML_FALSE);
            bStopParsing = true;
            return;
        }
        pszSubElementValue = pszNewSubElementValue;
        memcpy(pszSubElementValue + nSubElementValueLen, data, nLen);
        nSubElementValueLen += nLen;
        if (nSubElementValueLen > 100000)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Too much data inside one element. "
                     "File probably corrupted");
            XML_StopParser(oSchemaParser, XML_FALSE);
            bStopParsing = true;
        }
    }
}
#else
void OGRGPXLayer::LoadExtensionsSchema()
{
}
#endif
