/******************************************************************************
 * $Id: osm_parser.cpp 33138 2016-01-24 11:18:11Z rouault $
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Author:   Even Rouault, <even dot rouault at mines dash paris dot org>
 * Purpose:  OSM XML and OSM PBF parser
 *
 ******************************************************************************
 * Copyright (c) 2012-2013, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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 "osm_parser.h"
#include "gpb.h"

#include "cpl_conv.h"
#include "cpl_string.h"
#include "cpl_vsi.h"

#ifdef HAVE_EXPAT
#include "ogr_expat.h"
#endif

/* The buffer that are passed to GPB decoding are extended with 0's */
/* to be sure that we will be able to read a single 64bit value without */
/* doing checks for each byte */
#define EXTRA_BYTES     1

#define XML_BUFSIZE 64*1024

CPL_CVSID("$Id: osm_parser.cpp 33138 2016-01-24 11:18:11Z rouault $");

/************************************************************************/
/*                            INIT_INFO()                               */
/************************************************************************/

#define INIT_INFO(sInfo) \
    sInfo.ts.nTimeStamp = 0; \
    sInfo.nChangeset = 0; \
    sInfo.nVersion = 0; \
    sInfo.nUID = 0; \
    sInfo.bTimeStampIsStr = false; \
    sInfo.pszUserSID = NULL;
/*    \    sInfo.nVisible = 1; */


/************************************************************************/
/*                            _OSMContext                               */
/************************************************************************/

struct _OSMContext
{
    char          *pszStrBuf;
    int           *panStrOff;
    unsigned int   nStrCount;
    unsigned int   nStrAllocated;

    OSMNode       *pasNodes;
    unsigned int   nNodesAllocated;

    OSMTag        *pasTags;
    unsigned int   nTagsAllocated;

    OSMMember     *pasMembers;
    unsigned int   nMembersAllocated;

    GIntBig       *panNodeRefs;
    unsigned int   nNodeRefsAllocated;

    int            nGranularity;
    int            nDateGranularity;
    GIntBig        nLatOffset;
    GIntBig        nLonOffset;

    unsigned int   nBlobSizeAllocated;
    GByte         *pabyBlob;

    GByte         *pabyUncompressed;
    unsigned int   nUncompressedAllocated;

#ifdef HAVE_EXPAT
    XML_Parser     hXMLParser;
    bool           bEOF;
    bool           bStopParsing;
    bool           bHasFoundFeature;
    int            nWithoutEventCounter;
    int            nDataHandlerCounter;

    unsigned int   nStrLength;
    unsigned int   nTags;

    bool           bInNode;
    bool           bInWay;
    bool           bInRelation;

    OSMWay         sWay;
    OSMRelation    sRelation;

    bool           bTryToFetchBounds;
#endif

    VSILFILE      *fp;

    int            bPBF;

    double         dfLeft;
    double         dfRight;
    double         dfTop;
    double         dfBottom;

    GUIntBig        nBytesRead;

    NotifyNodesFunc     pfnNotifyNodes;
    NotifyWayFunc       pfnNotifyWay;
    NotifyRelationFunc  pfnNotifyRelation;
    NotifyBoundsFunc    pfnNotifyBounds;
    void               *user_data;
};

/************************************************************************/
/*                          ReadBlobHeader()                            */
/************************************************************************/

#define BLOBHEADER_IDX_TYPE         1
#define BLOBHEADER_IDX_INDEXDATA    2
#define BLOBHEADER_IDX_DATASIZE     3

typedef enum
{
    BLOB_UNKNOWN,
    BLOB_OSMHEADER,
    BLOB_OSMDATA
} BlobType;

static
int ReadBlobHeader(GByte* pabyData, GByte* pabyDataLimit,
                   unsigned int* pnBlobSize, BlobType* peBlobType)
{
    *pnBlobSize = 0;
    *peBlobType = BLOB_UNKNOWN;

    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(BLOBHEADER_IDX_TYPE, WT_DATA))
        {
            unsigned int nDataLength;
            READ_SIZE(pabyData, pabyDataLimit, nDataLength);

            if (nDataLength == 7 && memcmp(pabyData, "OSMData", 7) == 0)
            {
                *peBlobType = BLOB_OSMDATA;
            }
            else if (nDataLength == 9 && memcmp(pabyData, "OSMHeader", 9) == 0)
            {
                *peBlobType = BLOB_OSMHEADER;
            }

            pabyData += nDataLength;
        }
        else if (nKey == MAKE_KEY(BLOBHEADER_IDX_INDEXDATA, WT_DATA))
        {
            /* Ignored if found */
            unsigned int nDataLength;
            READ_SIZE(pabyData, pabyDataLimit, nDataLength);
            pabyData += nDataLength;
        }
        else if (nKey == MAKE_KEY(BLOBHEADER_IDX_DATASIZE, WT_VARINT))
        {
            unsigned int nBlobSize;
            READ_VARUINT32(pabyData, pabyDataLimit, nBlobSize);
            /* printf("nBlobSize = %d\n", nBlobSize); */
            *pnBlobSize = nBlobSize;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    return pabyData == pabyDataLimit;

end_error:
    return FALSE;
}

/************************************************************************/
/*                          ReadHeaderBBox()                            */
/************************************************************************/

#define HEADERBBOX_IDX_LEFT     1
#define HEADERBBOX_IDX_RIGHT    2
#define HEADERBBOX_IDX_TOP      3
#define HEADERBBOX_IDX_BOTTOM   4

static
int ReadHeaderBBox(GByte* pabyData, GByte* pabyDataLimit,
                   OSMContext* psCtxt)
{
    psCtxt->dfLeft = 0.0;
    psCtxt->dfRight = 0.0;
    psCtxt->dfTop = 0.0;
    psCtxt->dfBottom = 0.0;

    /* printf(">ReadHeaderBBox\n"); */

    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(HEADERBBOX_IDX_LEFT, WT_VARINT))
        {
            GIntBig nLeft;
            READ_VARSINT64(pabyData, pabyDataLimit, nLeft);
            psCtxt->dfLeft = nLeft * 1e-9;
        }
        else if (nKey == MAKE_KEY(HEADERBBOX_IDX_RIGHT, WT_VARINT))
        {
            GIntBig nRight;
            READ_VARSINT64(pabyData, pabyDataLimit, nRight);
            psCtxt->dfRight = nRight * 1e-9;
        }
        else if (nKey == MAKE_KEY(HEADERBBOX_IDX_TOP, WT_VARINT))
        {
            GIntBig nTop;
            READ_VARSINT64(pabyData, pabyDataLimit, nTop);
            psCtxt->dfTop = nTop * 1e-9;
        }
        else if (nKey == MAKE_KEY(HEADERBBOX_IDX_BOTTOM, WT_VARINT))
        {
            GIntBig nBottom;
            READ_VARSINT64(pabyData, pabyDataLimit, nBottom);
            psCtxt->dfBottom = nBottom * 1e-9;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    psCtxt->pfnNotifyBounds(psCtxt->dfLeft, psCtxt->dfBottom,
                            psCtxt->dfRight, psCtxt->dfTop,
                            psCtxt, psCtxt->user_data);

    /* printf("<ReadHeaderBBox\n"); */
    return pabyData == pabyDataLimit;

end_error:
    /* printf("<ReadHeaderBBox\n"); */
    return FALSE;
}

/************************************************************************/
/*                          ReadOSMHeader()                             */
/************************************************************************/

#define OSMHEADER_IDX_BBOX                  1
#define OSMHEADER_IDX_REQUIRED_FEATURES     4
#define OSMHEADER_IDX_OPTIONAL_FEATURES     5
#define OSMHEADER_IDX_WRITING_PROGRAM       16
#define OSMHEADER_IDX_SOURCE                17

/* Ignored */
#define OSMHEADER_IDX_OSMOSIS_REPLICATION_TIMESTAMP  32
#define OSMHEADER_IDX_OSMOSIS_REPLICATION_SEQ_NUMBER 33
#define OSMHEADER_IDX_OSMOSIS_REPLICATION_BASE_URL   34

static
int ReadOSMHeader(GByte* pabyData, GByte* pabyDataLimit,
                  OSMContext* psCtxt)
{
    char* pszTxt;

    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(OSMHEADER_IDX_BBOX, WT_DATA))
        {
            unsigned int nBBOXSize;
            READ_SIZE(pabyData, pabyDataLimit, nBBOXSize);

            if (!ReadHeaderBBox(pabyData, pabyData + nBBOXSize, psCtxt)) GOTO_END_ERROR;

            pabyData += nBBOXSize;
        }
        else if (nKey == MAKE_KEY(OSMHEADER_IDX_REQUIRED_FEATURES, WT_DATA))
        {
            READ_TEXT(pabyData, pabyDataLimit, pszTxt);
            /* printf("OSMHEADER_IDX_REQUIRED_FEATURES = %s\n", pszTxt); */
            if (!(strcmp(pszTxt, "OsmSchema-V0.6") == 0 ||
                  strcmp(pszTxt, "DenseNodes") == 0))
            {
                fprintf(stderr, "Error: unsupported required feature : %s\n", pszTxt);
                VSIFree(pszTxt);
                GOTO_END_ERROR;
            }
            VSIFree(pszTxt);
        }
        else if (nKey == MAKE_KEY(OSMHEADER_IDX_OPTIONAL_FEATURES, WT_DATA))
        {
            READ_TEXT(pabyData, pabyDataLimit, pszTxt);
            /* printf("OSMHEADER_IDX_OPTIONAL_FEATURES = %s\n", pszTxt); */
            VSIFree(pszTxt);
        }
        else if (nKey == MAKE_KEY(OSMHEADER_IDX_WRITING_PROGRAM, WT_DATA))
        {
            READ_TEXT(pabyData, pabyDataLimit, pszTxt);
            /* printf("OSMHEADER_IDX_WRITING_PROGRAM = %s\n", pszTxt); */
            VSIFree(pszTxt);
        }
        else if (nKey == MAKE_KEY(OSMHEADER_IDX_SOURCE, WT_DATA))
        {
            READ_TEXT(pabyData, pabyDataLimit, pszTxt);
            /* printf("OSMHEADER_IDX_SOURCE = %s\n", pszTxt); */
            VSIFree(pszTxt);
        }
        else if (nKey == MAKE_KEY(OSMHEADER_IDX_OSMOSIS_REPLICATION_TIMESTAMP, WT_VARINT))
        {
            SKIP_VARINT(pabyData, pabyDataLimit);
        }
        else if (nKey == MAKE_KEY(OSMHEADER_IDX_OSMOSIS_REPLICATION_SEQ_NUMBER, WT_VARINT))
        {
            SKIP_VARINT(pabyData, pabyDataLimit);
        }
        else if (nKey == MAKE_KEY(OSMHEADER_IDX_OSMOSIS_REPLICATION_BASE_URL, WT_DATA))
        {
            READ_TEXT(pabyData, pabyDataLimit, pszTxt);
            /* printf("OSMHEADER_IDX_OSMOSIS_REPLICATION_BASE_URL = %s\n", pszTxt); */
            VSIFree(pszTxt);
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    return pabyData == pabyDataLimit;

end_error:
    return FALSE;
}

/************************************************************************/
/*                         ReadStringTable()                            */
/************************************************************************/

#define READSTRINGTABLE_IDX_STRING  1

static
int ReadStringTable(GByte* pabyData, GByte* pabyDataLimit,
                    OSMContext* psCtxt)
{
    char* pszStrBuf = (char*)pabyData;

    unsigned int nStrCount = 0;
    int* panStrOff = psCtxt->panStrOff;

    psCtxt->pszStrBuf = pszStrBuf;

    if ((unsigned int)(pabyDataLimit - pabyData) > psCtxt->nStrAllocated)
    {
        int* panStrOffNew;
        psCtxt->nStrAllocated = MAX(psCtxt->nStrAllocated * 2,
                                          (unsigned int)(pabyDataLimit - pabyData));
        panStrOffNew = (int*) VSI_REALLOC_VERBOSE(
            panStrOff, psCtxt->nStrAllocated * sizeof(int));
        if( panStrOffNew == NULL )
            GOTO_END_ERROR;
        panStrOff = panStrOffNew;
    }

    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        while (nKey == MAKE_KEY(READSTRINGTABLE_IDX_STRING, WT_DATA))
        {
            GByte* pbSaved;
            unsigned int nDataLength;
            READ_SIZE(pabyData, pabyDataLimit, nDataLength);

            panStrOff[nStrCount ++] = static_cast<int>(pabyData - (GByte*)pszStrBuf);
            pbSaved = &pabyData[nDataLength];

            pabyData += nDataLength;

            if (pabyData < pabyDataLimit)
            {
                READ_FIELD_KEY(nKey);
                *pbSaved = 0;
                /* printf("string[%d] = %s\n", nStrCount-1, pbSaved - nDataLength); */
            }
            else
            {
                *pbSaved = 0;
                /* printf("string[%d] = %s\n", nStrCount-1, pbSaved - nDataLength); */
                break;
            }
        }

        if (pabyData < pabyDataLimit)
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    psCtxt->panStrOff = panStrOff;
    psCtxt->nStrCount = nStrCount;

    return pabyData == pabyDataLimit;

end_error:

    psCtxt->panStrOff = panStrOff;
    psCtxt->nStrCount = nStrCount;

    return FALSE;
}

/************************************************************************/
/*                         ReadDenseNodes()                             */
/************************************************************************/

#define DENSEINFO_IDX_VERSION     1
#define DENSEINFO_IDX_TIMESTAMP   2
#define DENSEINFO_IDX_CHANGESET   3
#define DENSEINFO_IDX_UID         4
#define DENSEINFO_IDX_USER_SID    5
#define DENSEINFO_IDX_VISIBLE     6

#define DENSENODES_IDX_ID           1
#define DENSENODES_IDX_DENSEINFO    5
#define DENSENODES_IDX_LAT          8
#define DENSENODES_IDX_LON          9
#define DENSENODES_IDX_KEYVALS      10

static
int ReadDenseNodes(GByte* pabyData, GByte* pabyDataLimit,
                   OSMContext* psCtxt)
{
    GByte* pabyDataIDs = NULL;
    GByte* pabyDataIDsLimit = NULL;
    GByte* pabyDataLat = NULL;
    GByte* pabyDataLon = NULL;
    GByte* apabyData[DENSEINFO_IDX_VISIBLE] = {NULL, NULL, NULL, NULL, NULL, NULL};
    GByte* pabyDataKeyVal = NULL;

    /* printf(">ReadDenseNodes\n"); */
    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if( nKey == MAKE_KEY(DENSENODES_IDX_ID, WT_DATA) )
        {
            unsigned int nSize;

            if (pabyDataIDs != NULL)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (nSize > psCtxt->nNodesAllocated)
            {
                OSMNode* pasNodesNew;
                psCtxt->nNodesAllocated = MAX(psCtxt->nNodesAllocated * 2,
                                                 nSize);
                pasNodesNew = (OSMNode*) VSI_REALLOC_VERBOSE(
                    psCtxt->pasNodes, psCtxt->nNodesAllocated * sizeof(OSMNode));
                if( pasNodesNew == NULL )
                    GOTO_END_ERROR;
                psCtxt->pasNodes = pasNodesNew;
            }

            pabyDataIDs = pabyData;
            pabyDataIDsLimit = pabyData + nSize;
            pabyData += nSize;
        }
        else if( nKey == MAKE_KEY(DENSENODES_IDX_DENSEINFO, WT_DATA) )
        {
            unsigned int nSize;
            GByte* pabyDataNewLimit;

            READ_SIZE(pabyData, pabyDataLimit, nSize);

            /* Inline reading of DenseInfo structure */

            pabyDataNewLimit = pabyData + nSize;
            while(pabyData < pabyDataNewLimit)
            {
                int nFieldNumber;
                READ_FIELD_KEY(nKey);

                nFieldNumber = GET_FIELDNUMBER(nKey);
                if (GET_WIRETYPE(nKey) == WT_DATA &&
                    nFieldNumber >= DENSEINFO_IDX_VERSION && nFieldNumber <= DENSEINFO_IDX_VISIBLE)
                {
                    if( apabyData[nFieldNumber - 1] != NULL) GOTO_END_ERROR;
                    READ_SIZE(pabyData, pabyDataNewLimit, nSize);

                    apabyData[nFieldNumber - 1] = pabyData;
                    pabyData += nSize;
                }
                else
                {
                    SKIP_UNKNOWN_FIELD(pabyData, pabyDataNewLimit, TRUE);
                }
            }

            if( pabyData != pabyDataNewLimit )
                GOTO_END_ERROR;
        }
        else if( nKey == MAKE_KEY(DENSENODES_IDX_LAT, WT_DATA) )
        {
            unsigned int nSize;
            if (pabyDataLat != NULL)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);
            pabyDataLat = pabyData;
            pabyData += nSize;
        }
        else if( nKey == MAKE_KEY(DENSENODES_IDX_LON, WT_DATA) )
        {
            unsigned int nSize;
            if (pabyDataLon != NULL)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);
            pabyDataLon = pabyData;
            pabyData += nSize;
        }
        else if( nKey == MAKE_KEY(DENSENODES_IDX_KEYVALS, WT_DATA) )
        {
            unsigned int nSize;
            if( pabyDataKeyVal != NULL )
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            pabyDataKeyVal = pabyData;

            if (nSize > psCtxt->nTagsAllocated)
            {
                OSMTag* pasTagsNew;

                psCtxt->nTagsAllocated = MAX(
                    psCtxt->nTagsAllocated * 2, nSize);
                pasTagsNew = (OSMTag*) VSI_REALLOC_VERBOSE(
                    psCtxt->pasTags,
                    psCtxt->nTagsAllocated * sizeof(OSMTag));
                if( pasTagsNew == NULL )
                    GOTO_END_ERROR;
                psCtxt->pasTags = pasTagsNew;
            }

            pabyData += nSize;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    if( pabyData != pabyDataLimit )
        GOTO_END_ERROR;

    if( pabyDataIDs != NULL && pabyDataLat != NULL && pabyDataLon != NULL )
    {
        GByte* pabyDataVersion = apabyData[DENSEINFO_IDX_VERSION - 1];
        GByte* pabyDataTimeStamp = apabyData[DENSEINFO_IDX_TIMESTAMP - 1];
        GByte* pabyDataChangeset = apabyData[DENSEINFO_IDX_CHANGESET - 1];
        GByte* pabyDataUID = apabyData[DENSEINFO_IDX_UID - 1];
        GByte* pabyDataUserSID = apabyData[DENSEINFO_IDX_USER_SID - 1];
        /* GByte* pabyDataVisible = apabyData[DENSEINFO_IDX_VISIBLE - 1]; */

        GIntBig nID = 0;
        GIntBig nLat = 0;
        GIntBig nLon = 0;
        GIntBig nTimeStamp = 0;
        GIntBig nChangeset = 0;
        int nUID = 0;
        unsigned int nUserSID = 0;
        int nTags = 0;
        int nNodes = 0;

        const char* pszStrBuf = psCtxt->pszStrBuf;
        int* panStrOff = psCtxt->panStrOff;
        const unsigned int nStrCount = psCtxt->nStrCount;
        OSMTag* pasTags = psCtxt->pasTags;
        OSMNode* pasNodes = psCtxt->pasNodes;

        int nVersion = 0;
        /* int nVisible = 1; */

        while(pabyDataIDs < pabyDataIDsLimit)
        {
            GIntBig nDelta1, nDelta2;
            int nKVIndexStart = nTags;

            READ_VARSINT64_NOCHECK(pabyDataIDs, pabyDataIDsLimit, nDelta1);
            READ_VARSINT64(pabyDataLat, pabyDataLimit, nDelta2);
            nID += nDelta1;
            nLat += nDelta2;

            READ_VARSINT64(pabyDataLon, pabyDataLimit, nDelta1);
            nLon += nDelta1;

            if( pabyDataTimeStamp )
            {
                READ_VARSINT64(pabyDataTimeStamp, pabyDataLimit, nDelta2);
                nTimeStamp += nDelta2;
            }
            if( pabyDataChangeset )
            {
                READ_VARSINT64(pabyDataChangeset, pabyDataLimit, nDelta1);
                nChangeset += nDelta1;
            }
            if( pabyDataVersion )
            {
                READ_VARINT32(pabyDataVersion, pabyDataLimit, nVersion);
            }
            if( pabyDataUID )
            {
                int nDeltaUID;
                READ_VARSINT32(pabyDataUID, pabyDataLimit, nDeltaUID);
                nUID += nDeltaUID;
            }
            if( pabyDataUserSID )
            {
                int nDeltaUserSID;
                READ_VARSINT32(pabyDataUserSID, pabyDataLimit, nDeltaUserSID);
                nUserSID += nDeltaUserSID;
                if (nUserSID >= nStrCount)
                    GOTO_END_ERROR;
            }
            /* if( pabyDataVisible )
                READ_VARINT32(pabyDataVisible, pabyDataLimit, nVisible); */

            if( pabyDataKeyVal != NULL && pasTags != NULL )
            {
                while( true )
                {
                    unsigned int nKey, nVal;
                    READ_VARUINT32(pabyDataKeyVal, pabyDataLimit, nKey);
                    if (nKey == 0)
                        break;
                    if (nKey >= nStrCount)
                        GOTO_END_ERROR;

                    READ_VARUINT32(pabyDataKeyVal, pabyDataLimit, nVal);
                    if (nVal >= nStrCount)
                        GOTO_END_ERROR;

                    pasTags[nTags].pszK = pszStrBuf + panStrOff[nKey];
                    pasTags[nTags].pszV = pszStrBuf + panStrOff[nVal];
                    nTags ++;

                    /* printf("nKey = %d, nVal = %d\n", nKey, nVal); */
                }
            }

            if( pasTags != NULL && nTags > nKVIndexStart )
                pasNodes[nNodes].pasTags = pasTags + nKVIndexStart;
            else
                pasNodes[nNodes].pasTags = NULL;
            pasNodes[nNodes].nTags = nTags - nKVIndexStart;

            pasNodes[nNodes].nID = nID;
            pasNodes[nNodes].dfLat = .000000001 * (psCtxt->nLatOffset + ((double)psCtxt->nGranularity * nLat));
            pasNodes[nNodes].dfLon = .000000001 * (psCtxt->nLonOffset + ((double)psCtxt->nGranularity * nLon));
            if( pasNodes[nNodes].dfLon < -180 || pasNodes[nNodes].dfLon > 180 ||
                pasNodes[nNodes].dfLat < -90 || pasNodes[nNodes].dfLat > 90 )
                GOTO_END_ERROR;
            pasNodes[nNodes].sInfo.bTimeStampIsStr = false;
            pasNodes[nNodes].sInfo.ts.nTimeStamp = nTimeStamp;
            pasNodes[nNodes].sInfo.nChangeset = nChangeset;
            pasNodes[nNodes].sInfo.nVersion = nVersion;
            pasNodes[nNodes].sInfo.nUID = nUID;
            if( nUserSID >= nStrCount )
                pasNodes[nNodes].sInfo.pszUserSID = "";
            else
                pasNodes[nNodes].sInfo.pszUserSID = pszStrBuf + panStrOff[nUserSID];
            /* pasNodes[nNodes].sInfo.nVisible = nVisible; */
            nNodes ++;
            /* printf("nLat = " CPL_FRMT_GIB "\n", nLat); printf("nLon = " CPL_FRMT_GIB "\n", nLon); */
        }

        psCtxt->pfnNotifyNodes(nNodes, pasNodes, psCtxt, psCtxt->user_data);

        if(pabyDataIDs != pabyDataIDsLimit)
            GOTO_END_ERROR;
    }

    /* printf("<ReadDenseNodes\n"); */

    return TRUE;

end_error:
    /* printf("<ReadDenseNodes\n"); */

    return FALSE;
}

/************************************************************************/
/*                           ReadOSMInfo()                              */
/************************************************************************/

#define INFO_IDX_VERSION     1
#define INFO_IDX_TIMESTAMP   2
#define INFO_IDX_CHANGESET   3
#define INFO_IDX_UID         4
#define INFO_IDX_USER_SID    5
#define INFO_IDX_VISIBLE     6

static
int ReadOSMInfo(GByte* pabyData, GByte* pabyDataLimit,
             OSMInfo* psInfo, OSMContext* psContext) CPL_NO_INLINE;

static
int ReadOSMInfo(GByte* pabyData, GByte* pabyDataLimit,
             OSMInfo* psInfo, OSMContext* psContext)
{
    /* printf(">ReadOSMInfo\n"); */
    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(INFO_IDX_VERSION, WT_VARINT))
        {
            READ_VARINT32(pabyData, pabyDataLimit, psInfo->nVersion);
        }
        else if (nKey == MAKE_KEY(INFO_IDX_TIMESTAMP, WT_VARINT))
        {
            READ_VARINT64(pabyData, pabyDataLimit, psInfo->ts.nTimeStamp);
        }
        else if (nKey == MAKE_KEY(INFO_IDX_CHANGESET, WT_VARINT))
        {
            READ_VARINT64(pabyData, pabyDataLimit, psInfo->nChangeset);
        }
        else if (nKey == MAKE_KEY(INFO_IDX_UID, WT_VARINT))
        {
            READ_VARINT32(pabyData, pabyDataLimit, psInfo->nUID);
        }
        else if (nKey == MAKE_KEY(INFO_IDX_USER_SID, WT_VARINT))
        {
            unsigned int nUserSID;
            READ_VARUINT32(pabyData, pabyDataLimit, nUserSID);
            if( nUserSID < psContext->nStrCount)
                psInfo->pszUserSID = psContext->pszStrBuf +
                                     psContext->panStrOff[nUserSID];
        }
        else if (nKey == MAKE_KEY(INFO_IDX_VISIBLE, WT_VARINT))
        {
            SKIP_VARINT(pabyData, pabyDataLimit);
            //int nVisible;
            //READ_VARINT32(pabyData, pabyDataLimit, /*psInfo->*/nVisible);
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }
    /* printf("<ReadOSMInfo\n"); */

    return pabyData == pabyDataLimit;

end_error:
    /* printf("<ReadOSMInfo\n"); */

    return FALSE;
}

/************************************************************************/
/*                             ReadNode()                               */
/************************************************************************/

/* From https://github.com/openstreetmap/osmosis/blob/master/osmosis-osm-binary/src/main/protobuf/osmformat.proto */
/* The one advertized in http://wiki.openstreetmap.org/wiki/PBF_Format and */
/* used previously seem wrong/old-dated */

#define NODE_IDX_ID      1
#define NODE_IDX_LAT     8
#define NODE_IDX_LON     9
#define NODE_IDX_KEYS    2
#define NODE_IDX_VALS    3
#define NODE_IDX_INFO    4

static
int ReadNode(GByte* pabyData, GByte* pabyDataLimit,
             OSMContext* psCtxt)
{
    OSMNode sNode;

    sNode.nID = 0;
    sNode.dfLat = 0.0;
    sNode.dfLon = 0.0;
    INIT_INFO(sNode.sInfo);
    sNode.nTags = 0;
    sNode.pasTags = NULL;

    /* printf(">ReadNode\n"); */
    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(NODE_IDX_ID, WT_VARINT))
        {
            READ_VARSINT64_NOCHECK(pabyData, pabyDataLimit, sNode.nID);
        }
        else if (nKey == MAKE_KEY(NODE_IDX_LAT, WT_VARINT))
        {
            GIntBig nLat;
            READ_VARSINT64_NOCHECK(pabyData, pabyDataLimit, nLat);
            sNode.dfLat = .000000001 * (psCtxt->nLatOffset + ((double)psCtxt->nGranularity * nLat));
        }
        else if (nKey == MAKE_KEY(NODE_IDX_LON, WT_VARINT))
        {
            GIntBig nLon;
            READ_VARSINT64_NOCHECK(pabyData, pabyDataLimit, nLon);
            sNode.dfLon = .000000001 * (psCtxt->nLonOffset + ((double)psCtxt->nGranularity * nLon));
        }
        else if (nKey == MAKE_KEY(NODE_IDX_KEYS, WT_DATA))
        {
            unsigned int nSize;
            GByte* pabyDataNewLimit;
            if (sNode.nTags != 0)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (nSize > psCtxt->nTagsAllocated)
            {
                OSMTag* pasTagsNew;

                psCtxt->nTagsAllocated = MAX(
                    psCtxt->nTagsAllocated * 2, nSize);
                pasTagsNew = (OSMTag*) VSI_REALLOC_VERBOSE(
                    psCtxt->pasTags,
                    psCtxt->nTagsAllocated * sizeof(OSMTag));
                if( pasTagsNew == NULL )
                    GOTO_END_ERROR;
                psCtxt->pasTags = pasTagsNew;
            }

            pabyDataNewLimit = pabyData + nSize;
            while (pabyData < pabyDataNewLimit)
            {
                unsigned int nKey2;
                READ_VARUINT32(pabyData, pabyDataNewLimit, nKey2);

                if (nKey2 >= psCtxt->nStrCount)
                    GOTO_END_ERROR;

                psCtxt->pasTags[sNode.nTags].pszK = psCtxt->pszStrBuf +
                                              psCtxt->panStrOff[nKey2];
                psCtxt->pasTags[sNode.nTags].pszV = "";
                sNode.nTags ++;
            }
            if (pabyData != pabyDataNewLimit)
                GOTO_END_ERROR;
        }
        else if (nKey == MAKE_KEY(NODE_IDX_VALS, WT_DATA))
        {
            //unsigned int nSize;
            unsigned int nIter = 0;
            if (sNode.nTags == 0)
                GOTO_END_ERROR;
            //READ_VARUINT32(pabyData, pabyDataLimit, nSize);
            SKIP_VARINT(pabyData, pabyDataLimit);

            for(; nIter < sNode.nTags; nIter ++)
            {
                unsigned int nVal;
                READ_VARUINT32(pabyData, pabyDataLimit, nVal);

                if (nVal >= psCtxt->nStrCount)
                    GOTO_END_ERROR;

                psCtxt->pasTags[nIter].pszV = psCtxt->pszStrBuf +
                                              psCtxt->panStrOff[nVal];
            }
        }
        else if (nKey == MAKE_KEY(NODE_IDX_INFO, WT_DATA))
        {
            unsigned int nSize;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (!ReadOSMInfo(pabyData, pabyDataLimit + nSize, &sNode.sInfo, psCtxt))
                GOTO_END_ERROR;

            pabyData += nSize;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    if( sNode.dfLon < -180 || sNode.dfLon > 180 ||
        sNode.dfLat < -90 || sNode.dfLat > 90 )
        GOTO_END_ERROR;

    if( pabyData != pabyDataLimit )
        GOTO_END_ERROR;

    if (sNode.nTags)
        sNode.pasTags = psCtxt->pasTags;
    else
        sNode.pasTags = NULL;
    psCtxt->pfnNotifyNodes(1, &sNode, psCtxt, psCtxt->user_data);

    /* printf("<ReadNode\n"); */

    return TRUE;

end_error:
    /* printf("<ReadNode\n"); */

    return FALSE;
}


/************************************************************************/
/*                              ReadWay()                               */
/************************************************************************/

#define WAY_IDX_ID      1
#define WAY_IDX_KEYS    2
#define WAY_IDX_VALS    3
#define WAY_IDX_INFO    4
#define WAY_IDX_REFS    8

static
int ReadWay(GByte* pabyData, GByte* pabyDataLimit,
            OSMContext* psCtxt)
{
    OSMWay sWay;
    sWay.nID = 0;
    INIT_INFO(sWay.sInfo);
    sWay.nTags = 0;
    sWay.nRefs = 0;

    /* printf(">ReadWay\n"); */
    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(WAY_IDX_ID, WT_VARINT))
        {
            READ_VARINT64(pabyData, pabyDataLimit, sWay.nID);
        }
        else if (nKey == MAKE_KEY(WAY_IDX_KEYS, WT_DATA))
        {
            unsigned int nSize;
            GByte* pabyDataNewLimit;
            if (sWay.nTags != 0)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (nSize > psCtxt->nTagsAllocated)
            {
                OSMTag* pasTagsNew;

                psCtxt->nTagsAllocated = MAX(
                    psCtxt->nTagsAllocated * 2, nSize);
                pasTagsNew = (OSMTag*) VSI_REALLOC_VERBOSE(
                    psCtxt->pasTags,
                    psCtxt->nTagsAllocated * sizeof(OSMTag));
                if( pasTagsNew == NULL )
                    GOTO_END_ERROR;
                psCtxt->pasTags = pasTagsNew;
            }

            pabyDataNewLimit = pabyData + nSize;
            while (pabyData < pabyDataNewLimit)
            {
                unsigned int nKey2;
                READ_VARUINT32(pabyData, pabyDataNewLimit, nKey2);

                if (nKey2 >= psCtxt->nStrCount)
                    GOTO_END_ERROR;

                psCtxt->pasTags[sWay.nTags].pszK = psCtxt->pszStrBuf +
                                                   psCtxt->panStrOff[nKey2];
                psCtxt->pasTags[sWay.nTags].pszV = "";
                sWay.nTags ++;
            }
            if (pabyData != pabyDataNewLimit)
                GOTO_END_ERROR;
        }
        else if (nKey == MAKE_KEY(WAY_IDX_VALS, WT_DATA))
        {
            //unsigned int nSize;
            unsigned int nIter = 0;
            if (sWay.nTags == 0)
                GOTO_END_ERROR;
            //READ_VARUINT32(pabyData, pabyDataLimit, nSize);
            SKIP_VARINT(pabyData, pabyDataLimit);

            for(; nIter < sWay.nTags; nIter ++)
            {
                unsigned int nVal;
                READ_VARUINT32(pabyData, pabyDataLimit, nVal);

                if (nVal >= psCtxt->nStrCount)
                    GOTO_END_ERROR;

                psCtxt->pasTags[nIter].pszV = psCtxt->pszStrBuf +
                                              psCtxt->panStrOff[nVal];
            }
        }
        else if (nKey == MAKE_KEY(WAY_IDX_INFO, WT_DATA))
        {
            unsigned int nSize;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (!ReadOSMInfo(pabyData, pabyData + nSize, &sWay.sInfo, psCtxt))
                GOTO_END_ERROR;

            pabyData += nSize;
        }
        else if (nKey == MAKE_KEY(WAY_IDX_REFS, WT_DATA))
        {
            GIntBig nRefVal = 0;
            unsigned int nSize;
            GByte* pabyDataNewLimit;
            if (sWay.nRefs != 0)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (nSize > psCtxt->nNodeRefsAllocated)
            {
                GIntBig* panNodeRefsNew;
                psCtxt->nNodeRefsAllocated =
                    MAX(psCtxt->nNodeRefsAllocated * 2, nSize);
                panNodeRefsNew = (GIntBig*) VSI_REALLOC_VERBOSE(
                        psCtxt->panNodeRefs,
                        psCtxt->nNodeRefsAllocated * sizeof(GIntBig));
                if( panNodeRefsNew == NULL )
                    GOTO_END_ERROR;
                psCtxt->panNodeRefs = panNodeRefsNew;
            }

            pabyDataNewLimit = pabyData + nSize;
            while (pabyData < pabyDataNewLimit)
            {
                GIntBig nDeltaRef;
                READ_VARSINT64_NOCHECK(pabyData, pabyDataNewLimit, nDeltaRef);
                nRefVal += nDeltaRef;

                psCtxt->panNodeRefs[sWay.nRefs ++] = nRefVal;
            }

            if (pabyData != pabyDataNewLimit)
                GOTO_END_ERROR;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    if( pabyData != pabyDataLimit )
        GOTO_END_ERROR;

    /* printf("<ReadWay\n"); */

    if (sWay.nTags)
        sWay.pasTags = psCtxt->pasTags;
    else
        sWay.pasTags = NULL;
    sWay.panNodeRefs = psCtxt->panNodeRefs;

    psCtxt->pfnNotifyWay(&sWay, psCtxt, psCtxt->user_data);

    return TRUE;

end_error:
    /* printf("<ReadWay\n"); */

    return FALSE;
}

/************************************************************************/
/*                            ReadRelation()                            */
/************************************************************************/

#define RELATION_IDX_ID           1
#define RELATION_IDX_KEYS         2
#define RELATION_IDX_VALS         3
#define RELATION_IDX_INFO         4
#define RELATION_IDX_ROLES_SID    8
#define RELATION_IDX_MEMIDS       9
#define RELATION_IDX_TYPES        10

static
int ReadRelation(GByte* pabyData, GByte* pabyDataLimit,
                 OSMContext* psCtxt)
{
    OSMRelation sRelation;
    sRelation.nID = 0;
    INIT_INFO(sRelation.sInfo);
    sRelation.nTags = 0;
    sRelation.nMembers = 0;

    /* printf(">ReadRelation\n"); */
    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(RELATION_IDX_ID, WT_VARINT))
        {
            READ_VARINT64(pabyData, pabyDataLimit, sRelation.nID);
        }
        else if (nKey == MAKE_KEY(RELATION_IDX_KEYS, WT_DATA))
        {
            unsigned int nSize;
            GByte* pabyDataNewLimit;
            if (sRelation.nTags != 0)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (nSize > psCtxt->nTagsAllocated)
            {
                OSMTag* pasTagsNew;

                psCtxt->nTagsAllocated = MAX(
                    psCtxt->nTagsAllocated * 2, nSize);
                pasTagsNew = (OSMTag*) VSI_REALLOC_VERBOSE(
                    psCtxt->pasTags,
                    psCtxt->nTagsAllocated * sizeof(OSMTag));
                if( pasTagsNew == NULL )
                    GOTO_END_ERROR;
                psCtxt->pasTags = pasTagsNew;
            }

            pabyDataNewLimit = pabyData + nSize;
            while (pabyData < pabyDataNewLimit)
            {
                unsigned int nKey2;
                READ_VARUINT32(pabyData, pabyDataNewLimit, nKey2);

                if (nKey2 >= psCtxt->nStrCount)
                    GOTO_END_ERROR;

                psCtxt->pasTags[sRelation.nTags].pszK = psCtxt->pszStrBuf +
                                                        psCtxt->panStrOff[nKey2];
                psCtxt->pasTags[sRelation.nTags].pszV = "";
                sRelation.nTags ++;
            }
            if (pabyData != pabyDataNewLimit)
                GOTO_END_ERROR;
        }
        else if (nKey == MAKE_KEY(RELATION_IDX_VALS, WT_DATA))
        {
            //unsigned int nSize;
            unsigned int nIter = 0;
            if (sRelation.nTags == 0)
                GOTO_END_ERROR;
            //READ_VARUINT32(pabyData, pabyDataLimit, nSize);
            SKIP_VARINT(pabyData, pabyDataLimit);

            for(; nIter < sRelation.nTags; nIter ++)
            {
                unsigned int nVal;
                READ_VARUINT32(pabyData, pabyDataLimit, nVal);

                if (nVal >= psCtxt->nStrCount)
                    GOTO_END_ERROR;

                psCtxt->pasTags[nIter].pszV = psCtxt->pszStrBuf +
                                              psCtxt->panStrOff[nVal];
            }
        }
        else if (nKey == MAKE_KEY(RELATION_IDX_INFO, WT_DATA))
        {
            unsigned int nSize;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (!ReadOSMInfo(pabyData, pabyData + nSize, &sRelation.sInfo, psCtxt))
                GOTO_END_ERROR;

            pabyData += nSize;
        }
        else if (nKey == MAKE_KEY(RELATION_IDX_ROLES_SID, WT_DATA))
        {
            unsigned int nSize;
            GByte* pabyDataNewLimit;
            if (sRelation.nMembers != 0)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (nSize > psCtxt->nMembersAllocated)
            {
                OSMMember* pasMembersNew;
                psCtxt->nMembersAllocated =
                    MAX(psCtxt->nMembersAllocated * 2, nSize);
                pasMembersNew = (OSMMember*) VSI_REALLOC_VERBOSE(
                        psCtxt->pasMembers,
                        psCtxt->nMembersAllocated * sizeof(OSMMember));
                if( pasMembersNew == NULL )
                    GOTO_END_ERROR;
                psCtxt->pasMembers = pasMembersNew;
            }

            pabyDataNewLimit = pabyData + nSize;
            while (pabyData < pabyDataNewLimit)
            {
                unsigned int nRoleSID;
                READ_VARUINT32(pabyData, pabyDataNewLimit, nRoleSID);
                if (nRoleSID >= psCtxt->nStrCount)
                    GOTO_END_ERROR;

                psCtxt->pasMembers[sRelation.nMembers].pszRole =
                    psCtxt->pszStrBuf + psCtxt->panStrOff[nRoleSID];
                psCtxt->pasMembers[sRelation.nMembers].nID = 0;
                psCtxt->pasMembers[sRelation.nMembers].eType = MEMBER_NODE;
                sRelation.nMembers ++;
            }

            if (pabyData != pabyDataNewLimit)
                GOTO_END_ERROR;
        }
        else if (nKey == MAKE_KEY(RELATION_IDX_MEMIDS, WT_DATA))
        {
            unsigned int nIter = 0;
            GIntBig nMemID = 0;
            //unsigned int nSize;
            if (sRelation.nMembers == 0)
                GOTO_END_ERROR;
            //READ_VARUINT32(pabyData, pabyDataLimit, nSize);
            SKIP_VARINT(pabyData, pabyDataLimit);

            for(; nIter < sRelation.nMembers; nIter++)
            {
                GIntBig nDeltaMemID;
                READ_VARSINT64(pabyData, pabyDataLimit, nDeltaMemID);
                nMemID += nDeltaMemID;

                psCtxt->pasMembers[nIter].nID = nMemID;
            }
        }
        else if (nKey == MAKE_KEY(RELATION_IDX_TYPES, WT_DATA))
        {
            unsigned int nIter = 0;
            unsigned int nSize;
            if (sRelation.nMembers == 0)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);
            if (nSize != sRelation.nMembers)
                GOTO_END_ERROR;

            for(; nIter < sRelation.nMembers; nIter++)
            {
                unsigned int nType = pabyData[nIter];
                if (nType > MEMBER_RELATION)
                    GOTO_END_ERROR;

                psCtxt->pasMembers[nIter].eType = (OSMMemberType) nType;
            }
            pabyData += nSize;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }
    /* printf("<ReadRelation\n"); */

    if( pabyData != pabyDataLimit )
        GOTO_END_ERROR;

    if (sRelation.nTags)
        sRelation.pasTags = psCtxt->pasTags;
    else
        sRelation.pasTags = NULL;

    sRelation.pasMembers = psCtxt->pasMembers;

    psCtxt->pfnNotifyRelation(&sRelation, psCtxt, psCtxt->user_data);

    return TRUE;

end_error:
    /* printf("<ReadRelation\n"); */

    return FALSE;
}

/************************************************************************/
/*                          ReadPrimitiveGroup()                        */
/************************************************************************/

#define PRIMITIVEGROUP_IDX_NODES      1
#define PRIMITIVEGROUP_IDX_DENSE      2
#define PRIMITIVEGROUP_IDX_WAYS       3
#define PRIMITIVEGROUP_IDX_RELATIONS  4
#define PRIMITIVEGROUP_IDX_CHANGESETS 5

typedef int (*PrimitiveFuncType)(GByte* pabyData, GByte* pabyDataLimit,
                                 OSMContext* psCtxt);

static const PrimitiveFuncType apfnPrimitives[] =
{
    ReadNode,
    ReadDenseNodes,
    ReadWay,
    ReadRelation
};

static
int ReadPrimitiveGroup(GByte* pabyData, GByte* pabyDataLimit,
                       OSMContext* psCtxt)
{
    /* printf(">ReadPrimitiveGroup\n"); */
    while(pabyData < pabyDataLimit)
    {
        int nKey;
        int nFieldNumber;
        READ_FIELD_KEY(nKey);

        nFieldNumber = GET_FIELDNUMBER(nKey) - 1;
        if( GET_WIRETYPE(nKey) == WT_DATA &&
            nFieldNumber >= PRIMITIVEGROUP_IDX_NODES - 1 &&
            nFieldNumber <= PRIMITIVEGROUP_IDX_RELATIONS - 1 )
        {
            unsigned int nSize;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (!apfnPrimitives[nFieldNumber](pabyData, pabyData + nSize, psCtxt))
                GOTO_END_ERROR;

            pabyData += nSize;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }
    /* printf("<ReadPrimitiveGroup\n"); */

    return pabyData == pabyDataLimit;

end_error:
    /* printf("<ReadPrimitiveGroup\n"); */

    return FALSE;
}

/************************************************************************/
/*                          ReadPrimitiveBlock()                        */
/************************************************************************/

#define PRIMITIVEBLOCK_IDX_STRINGTABLE      1
#define PRIMITIVEBLOCK_IDX_PRIMITIVEGROUP   2
#define PRIMITIVEBLOCK_IDX_GRANULARITY      17
#define PRIMITIVEBLOCK_IDX_DATE_GRANULARITY 18
#define PRIMITIVEBLOCK_IDX_LAT_OFFSET       19
#define PRIMITIVEBLOCK_IDX_LON_OFFSET       20

static
int ReadPrimitiveBlock(GByte* pabyData, GByte* pabyDataLimit,
                       OSMContext* psCtxt)
{
    GByte* pabyDataSave = pabyData;

    psCtxt->pszStrBuf = NULL;
    psCtxt->nStrCount = 0;
    psCtxt->nGranularity = 100;
    psCtxt->nDateGranularity = 1000;
    psCtxt->nLatOffset = 0;
    psCtxt->nLonOffset = 0;

    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(PRIMITIVEBLOCK_IDX_GRANULARITY, WT_VARINT))
        {
            READ_VARINT32(pabyData, pabyDataLimit, psCtxt->nGranularity);
            if( psCtxt->nGranularity <= 0 )
                GOTO_END_ERROR;
        }
        else if (nKey == MAKE_KEY(PRIMITIVEBLOCK_IDX_DATE_GRANULARITY, WT_VARINT))
        {
            READ_VARINT32(pabyData, pabyDataLimit, psCtxt->nDateGranularity);
        }
        else if (nKey == MAKE_KEY(PRIMITIVEBLOCK_IDX_LAT_OFFSET, WT_VARINT))
        {
            READ_VARINT64(pabyData, pabyDataLimit, psCtxt->nLatOffset);
        }
        else if (nKey == MAKE_KEY(PRIMITIVEBLOCK_IDX_LON_OFFSET, WT_VARINT))
        {
            READ_VARINT64(pabyData, pabyDataLimit, psCtxt->nLonOffset);
        }
        else
        {
            SKIP_UNKNOWN_FIELD_INLINE(pabyData, pabyDataLimit, FALSE);
        }
    }

    if (pabyData != pabyDataLimit)
        GOTO_END_ERROR;

    pabyData = pabyDataSave;
    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(PRIMITIVEBLOCK_IDX_STRINGTABLE, WT_DATA))
        {
            GByte bSaveAfterByte;
            GByte* pbSaveAfterByte;
            unsigned int nSize;
            if (psCtxt->nStrCount != 0)
                GOTO_END_ERROR;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            /* Dirty little trick */
            /* ReadStringTable() will over-write the byte after the */
            /* StringTable message with a NUL character, so we backup */
            /* it to be able to restore it just before issuing the next */
            /* READ_FIELD_KEY. Then we will re-NUL it to have valid */
            /* NUL terminated strings */
            /* This trick enable us to keep the strings where there are */
            /* in RAM */
            pbSaveAfterByte = pabyData + nSize;
            bSaveAfterByte = *pbSaveAfterByte;

            if (!ReadStringTable(pabyData, pabyData + nSize,
                                 psCtxt))
                GOTO_END_ERROR;

            pabyData += nSize;

            *pbSaveAfterByte = bSaveAfterByte;
            if (pabyData == pabyDataLimit)
                break;

            READ_FIELD_KEY(nKey);
            *pbSaveAfterByte = 0;

            if (nKey == MAKE_KEY(PRIMITIVEBLOCK_IDX_STRINGTABLE, WT_DATA))
                GOTO_END_ERROR;

            /* Yes we go on ! */
        }

        if (nKey == MAKE_KEY(PRIMITIVEBLOCK_IDX_PRIMITIVEGROUP, WT_DATA))
        {
            unsigned int nSize;
            READ_SIZE(pabyData, pabyDataLimit, nSize);

            if (!ReadPrimitiveGroup(pabyData, pabyData + nSize,
                                    psCtxt))
                GOTO_END_ERROR;

            pabyData += nSize;
        }
        else
        {
            SKIP_UNKNOWN_FIELD_INLINE(pabyData, pabyDataLimit, FALSE);
        }
    }

    return pabyData == pabyDataLimit;

end_error:

    return FALSE;
}

/************************************************************************/
/*                              ReadBlob()                              */
/************************************************************************/

#define BLOB_IDX_RAW         1
#define BLOB_IDX_RAW_SIZE    2
#define BLOB_IDX_ZLIB_DATA   3

static
int ReadBlob(GByte* pabyData, unsigned int nDataSize, BlobType eType,
             OSMContext* psCtxt)
{
    unsigned int nUncompressedSize = 0;
    int bRet = TRUE;
    GByte* pabyDataLimit = pabyData + nDataSize;

    while(pabyData < pabyDataLimit)
    {
        int nKey;
        READ_FIELD_KEY(nKey);

        if (nKey == MAKE_KEY(BLOB_IDX_RAW, WT_DATA))
        {
            unsigned int nDataLength;
            READ_SIZE(pabyData, pabyDataLimit, nDataLength);
            if (nDataLength > 64 * 1024 * 1024) GOTO_END_ERROR;

            /* printf("raw data size = %d\n", nDataLength); */

            if (eType == BLOB_OSMHEADER)
            {
                bRet = ReadOSMHeader(pabyData, pabyData + nDataLength, psCtxt);
            }
            else if (eType == BLOB_OSMDATA)
            {
                bRet = ReadPrimitiveBlock(pabyData, pabyData + nDataLength,
                                          psCtxt);
            }

            pabyData += nDataLength;
        }
        else if (nKey == MAKE_KEY(BLOB_IDX_RAW_SIZE, WT_VARINT))
        {
            READ_VARUINT32(pabyData, pabyDataLimit, nUncompressedSize);
            /* printf("nUncompressedSize = %d\n", nUncompressedSize); */
        }
        else if (nKey == MAKE_KEY(BLOB_IDX_ZLIB_DATA, WT_DATA))
        {
            unsigned int nZlibCompressedSize;
            READ_VARUINT32(pabyData, pabyDataLimit, nZlibCompressedSize);
            if (CHECK_OOB && nZlibCompressedSize > nDataSize) GOTO_END_ERROR;

            /* printf("nZlibCompressedSize = %d\n", nZlibCompressedSize); */

            if (nUncompressedSize != 0)
            {
                void* pOut;

                if (nUncompressedSize > psCtxt->nUncompressedAllocated)
                {
                    GByte* pabyUncompressedNew;
                    if( psCtxt->nUncompressedAllocated <= INT_MAX )
                        psCtxt->nUncompressedAllocated =
                            MAX(psCtxt->nUncompressedAllocated * 2, nUncompressedSize);
                    else
                        psCtxt->nUncompressedAllocated = nUncompressedSize;
                    if( psCtxt->nUncompressedAllocated > 0xFFFFFFFFU - EXTRA_BYTES )
                        GOTO_END_ERROR;
                    pabyUncompressedNew = (GByte*)VSI_REALLOC_VERBOSE(psCtxt->pabyUncompressed,
                                        psCtxt->nUncompressedAllocated + EXTRA_BYTES);
                    if( pabyUncompressedNew == NULL )
                        GOTO_END_ERROR;
                    psCtxt->pabyUncompressed = pabyUncompressedNew;
                }
                memset(psCtxt->pabyUncompressed + nUncompressedSize, 0, EXTRA_BYTES);

                /* printf("inflate %d -> %d\n", nZlibCompressedSize, nUncompressedSize); */

                pOut = CPLZLibInflate( pabyData, nZlibCompressedSize,
                                       psCtxt->pabyUncompressed, nUncompressedSize,
                                       NULL );
                if( pOut == NULL )
                    GOTO_END_ERROR;

                if (eType == BLOB_OSMHEADER)
                {
                    bRet = ReadOSMHeader(psCtxt->pabyUncompressed,
                                         psCtxt->pabyUncompressed + nUncompressedSize,
                                         psCtxt);
                }
                else if (eType == BLOB_OSMDATA)
                {
                    bRet = ReadPrimitiveBlock(psCtxt->pabyUncompressed,
                                              psCtxt->pabyUncompressed + nUncompressedSize,
                                              psCtxt);
                }
            }

            pabyData += nZlibCompressedSize;
        }
        else
        {
            SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, TRUE);
        }
    }

    return bRet;

end_error:
    return FALSE;
}

/************************************************************************/
/*                        EmptyNotifyNodesFunc()                        */
/************************************************************************/

static void EmptyNotifyNodesFunc(unsigned int /* nNodes */,
                                 OSMNode* /* pasNodes */,
                                 OSMContext* /* psCtxt */,
                                 void* /* user_data */)
{}


/************************************************************************/
/*                         EmptyNotifyWayFunc()                         */
/************************************************************************/

static void EmptyNotifyWayFunc(OSMWay* /* psWay */,
                               OSMContext* /* psCtxt */,
                               void* /* user_data */)
{}

/************************************************************************/
/*                       EmptyNotifyRelationFunc()                      */
/************************************************************************/

static void EmptyNotifyRelationFunc( OSMRelation* /* psRelation */,
                                     OSMContext* /* psCtxt */,
                                     void* /* user_data */)
{}

/************************************************************************/
/*                         EmptyNotifyBoundsFunc()                      */
/************************************************************************/

static void EmptyNotifyBoundsFunc( double /* dfXMin */,
                                   double /* dfYMin */,
                                   double /* dfXMax */,
                                   double /* dfYMax */,
                                   OSMContext* /*psCtxt */,
                                   void * /* user_data */)
{}

#ifdef HAVE_EXPAT

/************************************************************************/
/*                          OSM_AddString()                             */
/************************************************************************/

static const char* OSM_AddString(OSMContext* psCtxt, const char* pszStr)
{
    char* pszRet;
    int nLen = (int)strlen(pszStr);
    if( psCtxt->nStrLength + nLen + 1 > psCtxt->nStrAllocated )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "String buffer too small");
        return "";
    }
    pszRet = psCtxt->pszStrBuf + psCtxt->nStrLength;
    memcpy(pszRet, pszStr, nLen);
    pszRet[nLen] = '\0';
    psCtxt->nStrLength += nLen + 1;
    return pszRet;
}


/************************************************************************/
/*                            OSM_Atoi64()                              */
/************************************************************************/

static GIntBig OSM_Atoi64( const char *pszString )
{
    GIntBig    iValue;

#if defined(__MSVCRT__) || (defined(WIN32) && defined(_MSC_VER))
    iValue = (GIntBig)_atoi64( pszString );
# elif HAVE_ATOLL
    iValue = atoll( pszString );
#else
    iValue = atol( pszString );
#endif

    return iValue;
}

/************************************************************************/
/*                      OSM_XML_startElementCbk()                       */
/************************************************************************/

static void XMLCALL OSM_XML_startElementCbk( void *pUserData,
                                             const char *pszName,
                                             const char **ppszAttr)
{
    OSMContext* psCtxt = (OSMContext*) pUserData;
    const char** ppszIter = ppszAttr;

    if (psCtxt->bStopParsing) return;

    psCtxt->nWithoutEventCounter = 0;

    if( psCtxt->bTryToFetchBounds )
    {
        if( strcmp(pszName, "bounds") == 0 ||
            strcmp(pszName, "bound") == 0 /* osmosis uses bound */ )
        {
            int nCountCoords = 0;

            psCtxt->bTryToFetchBounds = false;

            if( ppszIter )
            {
                while( ppszIter[0] != NULL )
                {
                    if( strcmp(ppszIter[0], "minlon") == 0 )
                    {
                        psCtxt->dfLeft = CPLAtof( ppszIter[1] );
                        nCountCoords ++;
                    }
                    else if( strcmp(ppszIter[0], "minlat") == 0 )
                    {
                        psCtxt->dfBottom = CPLAtof( ppszIter[1] );
                        nCountCoords ++;
                    }
                    else if( strcmp(ppszIter[0], "maxlon") == 0 )
                    {
                        psCtxt->dfRight = CPLAtof( ppszIter[1] );
                        nCountCoords ++;
                    }
                    else if( strcmp(ppszIter[0], "maxlat") == 0 )
                    {
                        psCtxt->dfTop = CPLAtof( ppszIter[1] );
                        nCountCoords ++;
                    }
                    else if( strcmp(ppszIter[0], "box") == 0  /* osmosis uses box */ )
                    {
                        char** papszTokens = CSLTokenizeString2( ppszIter[1], ",", 0 );
                        if( CSLCount(papszTokens) == 4 )
                        {
                            psCtxt->dfBottom = CPLAtof( papszTokens[0] );
                            psCtxt->dfLeft = CPLAtof( papszTokens[1] );
                            psCtxt->dfTop = CPLAtof( papszTokens[2] );
                            psCtxt->dfRight = CPLAtof( papszTokens[3] );
                            nCountCoords = 4;
                        }
                        CSLDestroy(papszTokens);
                    }
                    ppszIter += 2;
                }
            }

            if( nCountCoords == 4 )
            {
                psCtxt->pfnNotifyBounds(psCtxt->dfLeft, psCtxt->dfBottom,
                                        psCtxt->dfRight, psCtxt->dfTop,
                                        psCtxt, psCtxt->user_data);
            }
        }
    }

    if( !psCtxt->bInNode && !psCtxt->bInWay && !psCtxt->bInRelation &&
        strcmp(pszName, "node") == 0 )
    {
        psCtxt->bInNode = true;
        psCtxt->bTryToFetchBounds = false;

        psCtxt->nStrLength = 0;
        psCtxt->pszStrBuf[0] = '\0';
        psCtxt->nTags = 0;

        memset( &(psCtxt->pasNodes[0]), 0, sizeof(OSMNode) );
        psCtxt->pasNodes[0].sInfo.pszUserSID = "";

        if( ppszIter )
        {
            while( ppszIter[0] != NULL )
            {
                if( strcmp(ppszIter[0], "id") == 0 )
                {
                    psCtxt->pasNodes[0].nID = OSM_Atoi64( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "lat") == 0 )
                {
                    psCtxt->pasNodes[0].dfLat = CPLAtof( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "lon") == 0 )
                {
                    psCtxt->pasNodes[0].dfLon = CPLAtof( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "version") == 0 )
                {
                    psCtxt->pasNodes[0].sInfo.nVersion = atoi( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "changeset") == 0 )
                {
                    psCtxt->pasNodes[0].sInfo.nChangeset = OSM_Atoi64( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "user") == 0 )
                {
                    psCtxt->pasNodes[0].sInfo.pszUserSID = OSM_AddString(psCtxt, ppszIter[1]);
                }
                else if( strcmp(ppszIter[0], "uid") == 0 )
                {
                    psCtxt->pasNodes[0].sInfo.nUID = atoi( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "timestamp") == 0 )
                {
                    psCtxt->pasNodes[0].sInfo.ts.pszTimeStamp = OSM_AddString(psCtxt, ppszIter[1]);
                    psCtxt->pasNodes[0].sInfo.bTimeStampIsStr = true;
                }
                ppszIter += 2;
            }
        }
    }

    else if( !psCtxt->bInNode && !psCtxt->bInWay && !psCtxt->bInRelation &&
             strcmp(pszName, "way") == 0 )
    {
        psCtxt->bInWay = true;

        psCtxt->nStrLength = 0;
        psCtxt->pszStrBuf[0] = '\0';
        psCtxt->nTags = 0;

        memset( &(psCtxt->sWay), 0, sizeof(OSMWay) );
        psCtxt->sWay.sInfo.pszUserSID = "";

        if( ppszIter )
        {
            while( ppszIter[0] != NULL )
            {
                if( strcmp(ppszIter[0], "id") == 0 )
                {
                    psCtxt->sWay.nID = OSM_Atoi64( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "version") == 0 )
                {
                    psCtxt->sWay.sInfo.nVersion = atoi( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "changeset") == 0 )
                {
                    psCtxt->sWay.sInfo.nChangeset = OSM_Atoi64( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "user") == 0 )
                {
                    psCtxt->sWay.sInfo.pszUserSID = OSM_AddString(psCtxt, ppszIter[1]);
                }
                else if( strcmp(ppszIter[0], "uid") == 0 )
                {
                    psCtxt->sWay.sInfo.nUID = atoi( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "timestamp") == 0 )
                {
                    psCtxt->sWay.sInfo.ts.pszTimeStamp = OSM_AddString(psCtxt, ppszIter[1]);
                    psCtxt->sWay.sInfo.bTimeStampIsStr = true;
                }
                ppszIter += 2;
            }
        }
    }

    else if( !psCtxt->bInNode && !psCtxt->bInWay && !psCtxt->bInRelation &&
             strcmp(pszName, "relation") == 0 )
    {
        psCtxt->bInRelation = true;

        psCtxt->nStrLength = 0;
        psCtxt->pszStrBuf[0] = '\0';
        psCtxt->nTags = 0;

        memset( &(psCtxt->sRelation), 0, sizeof(OSMRelation) );
        psCtxt->sRelation.sInfo.pszUserSID = "";

        if( ppszIter )
        {
            while( ppszIter[0] != NULL )
            {
                if( strcmp(ppszIter[0], "id") == 0 )
                {
                    psCtxt->sRelation.nID = OSM_Atoi64( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "version") == 0 )
                {
                    psCtxt->sRelation.sInfo.nVersion = atoi( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "changeset") == 0 )
                {
                    psCtxt->sRelation.sInfo.nChangeset = OSM_Atoi64( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "user") == 0 )
                {
                    psCtxt->sRelation.sInfo.pszUserSID = OSM_AddString(psCtxt, ppszIter[1]);
                }
                else if( strcmp(ppszIter[0], "uid") == 0 )
                {
                    psCtxt->sRelation.sInfo.nUID = atoi( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "timestamp") == 0 )
                {
                    psCtxt->sRelation.sInfo.ts.pszTimeStamp = OSM_AddString(psCtxt, ppszIter[1]);
                    psCtxt->sRelation.sInfo.bTimeStampIsStr = true;

                }
                ppszIter += 2;
            }
        }
    }

    else if( psCtxt->bInWay &&
             strcmp(pszName, "nd") == 0 )
    {
        if( ppszAttr != NULL && ppszAttr[0] != NULL &&
            strcmp(ppszAttr[0], "ref") == 0 )
        {
            if( psCtxt->sWay.nRefs < psCtxt->nNodeRefsAllocated )
            {
                psCtxt->panNodeRefs[psCtxt->sWay.nRefs] = OSM_Atoi64( ppszAttr[1] );
                psCtxt->sWay.nRefs ++;
            }
            else
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Too many nodes referenced in way " CPL_FRMT_GIB,
                         psCtxt->sWay.nID);
            }
        }
    }

    else if( psCtxt->bInRelation &&
             strcmp(pszName, "member") == 0 )
    {
        /* 300 is the recommended value, but there are files with more than 2000 so we should be able */
        /* to realloc over that value */
        if (psCtxt->sRelation.nMembers >= psCtxt->nMembersAllocated)
        {
            OSMMember* pasMembersNew;
            int nMembersAllocated =
                MAX(psCtxt->nMembersAllocated * 2, psCtxt->sRelation.nMembers + 1);
            pasMembersNew = (OSMMember*) VSI_REALLOC_VERBOSE(
                    psCtxt->pasMembers,
                    nMembersAllocated * sizeof(OSMMember));
            if( pasMembersNew == NULL )
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                        "Cannot allocate enough memory to store members of relation " CPL_FRMT_GIB,
                        psCtxt->sRelation.nID);
                return;
            }
            psCtxt->nMembersAllocated = nMembersAllocated;
            psCtxt->pasMembers = pasMembersNew;
        }

        OSMMember* psMember = &(psCtxt->pasMembers[psCtxt->sRelation.nMembers]);
        psCtxt->sRelation.nMembers ++;

        psMember->nID = 0;
        psMember->pszRole = "";
        psMember->eType = MEMBER_NODE;

        if( ppszIter )
        {
            while( ppszIter[0] != NULL )
            {
                if( strcmp(ppszIter[0], "ref") == 0 )
                {
                    psMember->nID = OSM_Atoi64( ppszIter[1] );
                }
                else if( strcmp(ppszIter[0], "type") == 0 )
                {
                    if( strcmp( ppszIter[1], "node") == 0 )
                        psMember->eType = MEMBER_NODE;
                    else if( strcmp( ppszIter[1], "way") == 0 )
                        psMember->eType = MEMBER_WAY;
                    else if( strcmp( ppszIter[1], "relation") == 0 )
                        psMember->eType = MEMBER_RELATION;
                }
                else if( strcmp(ppszIter[0], "role") == 0 )
                {
                    psMember->pszRole = OSM_AddString(psCtxt, ppszIter[1]);
                }
                ppszIter += 2;
            }
        }
    }
    else if( (psCtxt->bInNode || psCtxt->bInWay || psCtxt->bInRelation) &&
             strcmp(pszName, "tag") == 0 )
    {
        if( psCtxt->nTags < psCtxt->nTagsAllocated )
        {
            OSMTag* psTag = &(psCtxt->pasTags[psCtxt->nTags]);
            psCtxt->nTags ++;

            psTag->pszK = "";
            psTag->pszV = "";

            if( ppszIter )
            {
                while( ppszIter[0] != NULL )
                {
                    if( ppszIter[0][0] == 'k' )
                    {
                        psTag->pszK = OSM_AddString(psCtxt, ppszIter[1]);
                    }
                    else if( ppszIter[0][0] == 'v' )
                    {
                        psTag->pszV = OSM_AddString(psCtxt, ppszIter[1]);
                    }
                    ppszIter += 2;
                }
            }
        }
        else
        {
            if (psCtxt->bInNode)
                CPLError(CE_Failure, CPLE_AppDefined,
                        "Too many tags in node " CPL_FRMT_GIB,
                         psCtxt->pasNodes[0].nID);
            else if (psCtxt->bInWay)
                CPLError(CE_Failure, CPLE_AppDefined,
                        "Too many tags in way " CPL_FRMT_GIB,
                         psCtxt->sWay.nID);
            else if (psCtxt->bInRelation)
                CPLError(CE_Failure, CPLE_AppDefined,
                        "Too many tags in relation " CPL_FRMT_GIB,
                         psCtxt->sRelation.nID);
        }
    }
}

/************************************************************************/
/*                       OSM_XML_endElementCbk()                        */
/************************************************************************/

static void XMLCALL OSM_XML_endElementCbk(void *pUserData, const char *pszName)
{
    OSMContext* psCtxt = (OSMContext*) pUserData;

    if (psCtxt->bStopParsing) return;

    psCtxt->nWithoutEventCounter = 0;

    if( psCtxt->bInNode && strcmp(pszName, "node") == 0 )
    {
        if( psCtxt->pasNodes[0].dfLon < -180 || psCtxt->pasNodes[0].dfLon > 180 ||
            psCtxt->pasNodes[0].dfLat < -90 || psCtxt->pasNodes[0].dfLat > 90 )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Invalid lon=%f lat=%f",
                     psCtxt->pasNodes[0].dfLon,
                     psCtxt->pasNodes[0].dfLat);
        }
        else
        {
            psCtxt->pasNodes[0].nTags = psCtxt->nTags;
            psCtxt->pasNodes[0].pasTags = psCtxt->pasTags;

            psCtxt->pfnNotifyNodes(1, psCtxt->pasNodes, psCtxt, psCtxt->user_data);

            psCtxt->bHasFoundFeature = true;
        }
        psCtxt->bInNode = FALSE;
    }

    else
    if( psCtxt->bInWay && strcmp(pszName, "way") == 0 )
    {
        psCtxt->sWay.nTags = psCtxt->nTags;
        psCtxt->sWay.pasTags = psCtxt->pasTags;

        psCtxt->sWay.panNodeRefs = psCtxt->panNodeRefs;

        psCtxt->pfnNotifyWay(&(psCtxt->sWay), psCtxt, psCtxt->user_data);

        psCtxt->bHasFoundFeature = true;

        psCtxt->bInWay = FALSE;
    }

    else
    if( psCtxt->bInRelation && strcmp(pszName, "relation") == 0 )
    {
        psCtxt->sRelation.nTags = psCtxt->nTags;
        psCtxt->sRelation.pasTags = psCtxt->pasTags;

        psCtxt->sRelation.pasMembers = psCtxt->pasMembers;

        psCtxt->pfnNotifyRelation(&(psCtxt->sRelation), psCtxt, psCtxt->user_data);

        psCtxt->bHasFoundFeature = true;

        psCtxt->bInRelation = false;
    }
}
/************************************************************************/
/*                           dataHandlerCbk()                           */
/************************************************************************/

static void XMLCALL OSM_XML_dataHandlerCbk( void *pUserData,
                                            const char * /* data */,
                                            int /* nLen */)
{
    OSMContext* psCtxt = static_cast<OSMContext *>(pUserData);

    if (psCtxt->bStopParsing) return;

    psCtxt->nWithoutEventCounter = 0;

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

/************************************************************************/
/*                          XML_ProcessBlock()                          */
/************************************************************************/

static OSMRetCode XML_ProcessBlock(OSMContext* psCtxt)
{
    if( psCtxt->bEOF )
        return OSM_EOF;
    if( psCtxt->bStopParsing )
        return OSM_ERROR;

    psCtxt->bHasFoundFeature = false;
    psCtxt->nWithoutEventCounter = 0;

    do
    {
        int eErr;
        unsigned int nLen;

        psCtxt->nDataHandlerCounter = 0;

        nLen = (unsigned int)VSIFReadL( psCtxt->pabyBlob, 1,
                                        XML_BUFSIZE, psCtxt->fp );

        psCtxt->nBytesRead += nLen;

        psCtxt->bEOF = CPL_TO_BOOL(VSIFEofL(psCtxt->fp));
        eErr = XML_Parse(psCtxt->hXMLParser, (const char*) psCtxt->pabyBlob,
                         nLen, psCtxt->bEOF );

        if (eErr == XML_STATUS_ERROR)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "XML parsing of OSM file failed : %s "
                     "at line %d, column %d",
                     XML_ErrorString(XML_GetErrorCode(psCtxt->hXMLParser)),
                     (int)XML_GetCurrentLineNumber(psCtxt->hXMLParser),
                     (int)XML_GetCurrentColumnNumber(psCtxt->hXMLParser));
            psCtxt->bStopParsing = true;
        }
        psCtxt->nWithoutEventCounter ++;
    } while (!psCtxt->bEOF && !psCtxt->bStopParsing &&
             !psCtxt->bHasFoundFeature &&
             psCtxt->nWithoutEventCounter < 10);

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

    return psCtxt->bStopParsing ? OSM_ERROR : psCtxt->bEOF ? OSM_EOF : OSM_OK;
}

#endif

/************************************************************************/
/*                              OSM_Open()                              */
/************************************************************************/

OSMContext* OSM_Open( const char* pszFilename,
                      NotifyNodesFunc pfnNotifyNodes,
                      NotifyWayFunc pfnNotifyWay,
                      NotifyRelationFunc pfnNotifyRelation,
                      NotifyBoundsFunc pfnNotifyBounds,
                      void* user_data )
{

    VSILFILE* fp = VSIFOpenL(pszFilename, "rb");
    if (fp == NULL)
        return NULL;

    GByte abyHeader[1024];
    int nRead = static_cast<int>(
        VSIFReadL(abyHeader, 1, sizeof(abyHeader)-1, fp));
    abyHeader[nRead] = '\0';

    bool bPBF = false;

    if( strstr((const char*)abyHeader, "<osm") != NULL )
    {
        /* OSM XML */
#ifndef HAVE_EXPAT
        CPLError(CE_Failure, CPLE_AppDefined,
                 "OSM XML detected, but Expat parser not available");
        VSIFCloseL(fp);
        return NULL;
#endif
    }
    else
    {
        const int nLimitI = nRead - static_cast<int>(strlen("OSMHeader"));
        for( int i = 0; i < nLimitI; i++)
        {
            if( memcmp(abyHeader + i, "OSMHeader", strlen("OSMHeader") ) == 0 )
            {
                bPBF = TRUE;
                break;
            }
        }
        if( !bPBF )
        {
            VSIFCloseL(fp);
            return NULL;
        }
    }

    VSIFSeekL(fp, 0, SEEK_SET);

    OSMContext* psCtxt = static_cast<OSMContext *>(
        VSI_MALLOC_VERBOSE(sizeof(OSMContext)) );
    if (psCtxt == NULL)
    {
        VSIFCloseL(fp);
        return NULL;
    }
    memset(psCtxt, 0, sizeof(OSMContext));
    psCtxt->bPBF = bPBF;
    psCtxt->fp = fp;
    psCtxt->pfnNotifyNodes = pfnNotifyNodes;
    if( pfnNotifyNodes == NULL )
        psCtxt->pfnNotifyNodes = EmptyNotifyNodesFunc;
    psCtxt->pfnNotifyWay = pfnNotifyWay;
    if( pfnNotifyWay == NULL )
        psCtxt->pfnNotifyWay = EmptyNotifyWayFunc;
    psCtxt->pfnNotifyRelation = pfnNotifyRelation;
    if( pfnNotifyRelation == NULL )
        psCtxt->pfnNotifyRelation = EmptyNotifyRelationFunc;
    psCtxt->pfnNotifyBounds = pfnNotifyBounds;
    if( pfnNotifyBounds == NULL )
        psCtxt->pfnNotifyBounds = EmptyNotifyBoundsFunc;
    psCtxt->user_data = user_data;

    if( bPBF )
    {
        psCtxt->nBlobSizeAllocated = 64 * 1024 + EXTRA_BYTES;
    }
#ifdef HAVE_EXPAT
    else
    {
        psCtxt->nBlobSizeAllocated = XML_BUFSIZE;

        psCtxt->nStrAllocated = 65536;
        psCtxt->pszStrBuf = (char*) VSI_MALLOC_VERBOSE(psCtxt->nStrAllocated);
        if( psCtxt->pszStrBuf )
            psCtxt->pszStrBuf[0] = '\0';

        psCtxt->hXMLParser = OGRCreateExpatXMLParser();
        XML_SetUserData(psCtxt->hXMLParser, psCtxt);
        XML_SetElementHandler(psCtxt->hXMLParser,
                              OSM_XML_startElementCbk,
                              OSM_XML_endElementCbk);
        XML_SetCharacterDataHandler(psCtxt->hXMLParser, OSM_XML_dataHandlerCbk);

        psCtxt->bTryToFetchBounds = true;

        psCtxt->nNodesAllocated = 1;
        psCtxt->pasNodes = (OSMNode*) VSI_MALLOC_VERBOSE(sizeof(OSMNode) * psCtxt->nNodesAllocated);

        psCtxt->nTagsAllocated = 256;
        psCtxt->pasTags = (OSMTag*) VSI_MALLOC_VERBOSE(sizeof(OSMTag) * psCtxt->nTagsAllocated);

        /* 300 is the recommended value, but there are files with more than 2000 so we should be able */
        /* to realloc over that value */
        psCtxt->nMembersAllocated = 2000;
        psCtxt->pasMembers = (OSMMember*) VSI_MALLOC_VERBOSE(sizeof(OSMMember) * psCtxt->nMembersAllocated);

        psCtxt->nNodeRefsAllocated = 2000;
        psCtxt->panNodeRefs = (GIntBig*) VSI_MALLOC_VERBOSE(sizeof(GIntBig) * psCtxt->nNodeRefsAllocated);

        if( psCtxt->pszStrBuf == NULL ||
            psCtxt->pasNodes == NULL ||
            psCtxt->pasTags == NULL ||
            psCtxt->pasMembers == NULL ||
            psCtxt->panNodeRefs == NULL )
        {
            OSM_Close(psCtxt);
            return NULL;
        }

    }
#endif

    psCtxt->pabyBlob = (GByte*)VSI_MALLOC_VERBOSE(psCtxt->nBlobSizeAllocated);
    if( psCtxt->pabyBlob == NULL )
    {
        OSM_Close(psCtxt);
        return NULL;
    }

    return psCtxt;
}

/************************************************************************/
/*                              OSM_Close()                             */
/************************************************************************/

void OSM_Close(OSMContext* psCtxt)
{
    if( psCtxt == NULL )
        return;

#ifdef HAVE_EXPAT
    if( !psCtxt->bPBF )
    {
        if (psCtxt->hXMLParser)
            XML_ParserFree(psCtxt->hXMLParser);

        CPLFree(psCtxt->pszStrBuf); /* only for XML case ! */
    }
#endif

    VSIFree(psCtxt->pabyBlob);
    VSIFree(psCtxt->pabyUncompressed);
    VSIFree(psCtxt->panStrOff);
    VSIFree(psCtxt->pasNodes);
    VSIFree(psCtxt->pasTags);
    VSIFree(psCtxt->pasMembers);
    VSIFree(psCtxt->panNodeRefs);

    VSIFCloseL(psCtxt->fp);
    VSIFree(psCtxt);
}
/************************************************************************/
/*                          OSM_ResetReading()                          */
/************************************************************************/

void OSM_ResetReading( OSMContext* psCtxt )
{
    VSIFSeekL(psCtxt->fp, 0, SEEK_SET);

    psCtxt->nBytesRead = 0;

#ifdef HAVE_EXPAT
    if( !psCtxt->bPBF )
    {
        XML_ParserFree(psCtxt->hXMLParser);
        psCtxt->hXMLParser = OGRCreateExpatXMLParser();
        XML_SetUserData(psCtxt->hXMLParser, psCtxt);
        XML_SetElementHandler(psCtxt->hXMLParser,
                              OSM_XML_startElementCbk,
                              OSM_XML_endElementCbk);
        XML_SetCharacterDataHandler(psCtxt->hXMLParser, OSM_XML_dataHandlerCbk);
        psCtxt->bEOF = false;
        psCtxt->bStopParsing = false;
        psCtxt->nStrLength = 0;
        psCtxt->pszStrBuf[0] = '\0';
        psCtxt->nTags = 0;

        psCtxt->bTryToFetchBounds = true;
        psCtxt->bInNode = false;
        psCtxt->bInWay = FALSE;
        psCtxt->bInRelation = false;
    }
#endif
}

/************************************************************************/
/*                          OSM_ProcessBlock()                          */
/************************************************************************/

static OSMRetCode PBF_ProcessBlock(OSMContext* psCtxt)
{
    int nRet = FALSE;
    GByte abyHeaderSize[4];
    unsigned int nHeaderSize;
    unsigned int nBlobSize = 0;
    BlobType eType;

    if (VSIFReadL(abyHeaderSize, 4, 1, psCtxt->fp) != 1)
    {
        return OSM_EOF;
    }
    nHeaderSize = (abyHeaderSize[0] << 24) | (abyHeaderSize[1] << 16) |
                    (abyHeaderSize[2] << 8) | abyHeaderSize[3];

    psCtxt->nBytesRead += 4;

    /* printf("nHeaderSize = %d\n", nHeaderSize); */
    if (nHeaderSize > 64 * 1024)
        GOTO_END_ERROR;
    if (VSIFReadL(psCtxt->pabyBlob, 1, nHeaderSize, psCtxt->fp) != nHeaderSize)
        GOTO_END_ERROR;

    psCtxt->nBytesRead += nHeaderSize;

    memset(psCtxt->pabyBlob + nHeaderSize, 0, EXTRA_BYTES);
    nRet = ReadBlobHeader(psCtxt->pabyBlob, psCtxt->pabyBlob + nHeaderSize, &nBlobSize, &eType);
    if (!nRet || eType == BLOB_UNKNOWN)
        GOTO_END_ERROR;

    if (nBlobSize > 64*1024*1024)
        GOTO_END_ERROR;
    if (nBlobSize > psCtxt->nBlobSizeAllocated)
    {
        GByte* pabyBlobNew;
        psCtxt->nBlobSizeAllocated = MAX(psCtxt->nBlobSizeAllocated * 2, nBlobSize);
        pabyBlobNew = (GByte*)VSI_REALLOC_VERBOSE(psCtxt->pabyBlob,
                                        psCtxt->nBlobSizeAllocated + EXTRA_BYTES);
        if( pabyBlobNew == NULL )
            GOTO_END_ERROR;
        psCtxt->pabyBlob = pabyBlobNew;
    }
    if (VSIFReadL(psCtxt->pabyBlob, 1, nBlobSize, psCtxt->fp) != nBlobSize)
        GOTO_END_ERROR;

    psCtxt->nBytesRead += nBlobSize;

    memset(psCtxt->pabyBlob + nBlobSize, 0, EXTRA_BYTES);
    nRet = ReadBlob(psCtxt->pabyBlob, nBlobSize, eType,
                    psCtxt);
    if (!nRet)
        GOTO_END_ERROR;

    return OSM_OK;

end_error:

    return OSM_ERROR;
}

/************************************************************************/
/*                          OSM_ProcessBlock()                          */
/************************************************************************/

OSMRetCode OSM_ProcessBlock(OSMContext* psCtxt)
{
#ifdef HAVE_EXPAT
    if( psCtxt->bPBF )
        return PBF_ProcessBlock(psCtxt);
    else
        return XML_ProcessBlock(psCtxt);
#else
    return PBF_ProcessBlock(psCtxt);
#endif
}

/************************************************************************/
/*                          OSM_GetBytesRead()                          */
/************************************************************************/

GUIntBig OSM_GetBytesRead( OSMContext* psCtxt )
{
    return psCtxt->nBytesRead;
}
