/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements OGROSMLayer class
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
 *
 ******************************************************************************
 * Copyright (c) 2012-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 <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <time.h>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_progress.h"
#include "cpl_string.h"
#include "cpl_time.h"
#include "cpl_vsi.h"
#include "ogr_core.h"
#include "ogr_feature.h"
#include "ogr_geometry.h"
#include "ogr_p.h"
#include "ogr_spatialref.h"
#include "ogrsf_frmts.h"
#include "ogr_osm.h"
#include "osm_parser.h"
#include "sqlite3.h"


constexpr int SWITCH_THRESHOLD = 10000;
constexpr int MAX_THRESHOLD = 100000;

constexpr int ALLTAGS_LENGTH = 8192;

/************************************************************************/
/*                          OGROSMLayer()                               */
/************************************************************************/

OGROSMLayer::OGROSMLayer( OGROSMDataSource* poDSIn, int nIdxLayerIn,
                          const char* pszName ) :
    m_poDS(poDSIn),
    m_nIdxLayer(nIdxLayerIn),
    m_poFeatureDefn(new OGRFeatureDefn( pszName )),
    m_poSRS(new OGRSpatialReference()),
    m_pszAllTags(static_cast<char *>(CPLMalloc(ALLTAGS_LENGTH)))
{
    SetDescription( m_poFeatureDefn->GetName() );
    m_poFeatureDefn->Reference();

    m_poSRS->SetWellKnownGeogCS("WGS84");
    m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

    if( m_poFeatureDefn->GetGeomFieldCount() != 0 )
        m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poSRS);
}

/************************************************************************/
/*                          ~OGROSMLayer()                           */
/************************************************************************/

OGROSMLayer::~OGROSMLayer()
{
    m_poFeatureDefn->Release();

    if( m_poSRS )
        m_poSRS->Release();

    for( int i=0; i<m_nFeatureArraySize; i++ )
    {
        if( m_papoFeatures[i] )
            delete m_papoFeatures[i];
    }

    for( int i=0; i<static_cast<int>(m_apszNames.size()); i++ )
        CPLFree(m_apszNames[i]);

    for( int i=0; i<static_cast<int>(apszInsignificantKeys.size()); i++ )
        CPLFree(apszInsignificantKeys[i]);

    for( int i=0; i<static_cast<int>(apszIgnoreKeys.size()); i++ )
        CPLFree(apszIgnoreKeys[i]);

    for( int i=0; i<static_cast<int>(m_oComputedAttributes.size()); i++ )
    {
        sqlite3_finalize(m_oComputedAttributes[i].hStmt);
    }

    CPLFree(m_pszAllTags);

    CPLFree(m_papoFeatures);
}

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

void OGROSMLayer::ResetReading()
{
    if( !m_bResetReadingAllowed || m_poDS->IsInterleavedReading() )
        return;

    m_poDS->MyResetReading();
}

/************************************************************************/
/*                        ForceResetReading()                           */
/************************************************************************/

void OGROSMLayer::ForceResetReading()
{
    for(int i=0;i<m_nFeatureArraySize;i++)
    {
        if( m_papoFeatures[i] )
            delete m_papoFeatures[i];
    }
    m_nFeatureArrayIndex = 0;
    m_nFeatureArraySize = 0;
    m_nFeatureCount = 0;
    m_bResetReadingAllowed = false;
}

/************************************************************************/
/*                        SetAttributeFilter()                          */
/************************************************************************/

OGRErr OGROSMLayer::SetAttributeFilter( const char* pszAttrQuery )
{
    if( pszAttrQuery == nullptr && m_pszAttrQueryString == nullptr )
        return OGRERR_NONE;
    if( pszAttrQuery != nullptr && m_pszAttrQueryString != nullptr &&
        strcmp(pszAttrQuery, m_pszAttrQueryString) == 0 )
        return OGRERR_NONE;

    OGRErr eErr = OGRLayer::SetAttributeFilter(pszAttrQuery);
    if( eErr != OGRERR_NONE )
        return eErr;

    if( m_nFeatureArrayIndex == 0 )
    {
        if( !m_poDS->IsInterleavedReading() )
        {
            m_poDS->MyResetReading();
        }
    }
    else
    {
        CPLError( CE_Warning, CPLE_AppDefined,
                  "The new attribute filter will "
                  "not be taken into account immediately. It is advised to "
                  "set attribute filters for all needed layers, before "
                  "reading *any* layer" );
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/************************************************************************/

GIntBig OGROSMLayer::GetFeatureCount( int bForce )
{
    if( m_poDS->IsFeatureCountEnabled() )
        return OGRLayer::GetFeatureCount(bForce);

    return -1;
}

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

OGRFeature *OGROSMLayer::GetNextFeature()
{
    OGROSMLayer* poNewCurLayer = nullptr;
    OGRFeature* poFeature = MyGetNextFeature(&poNewCurLayer, nullptr, nullptr);
    m_poDS->SetCurrentLayer(poNewCurLayer);
    return poFeature;
}

OGRFeature *OGROSMLayer::MyGetNextFeature( OGROSMLayer** ppoNewCurLayer,
                                           GDALProgressFunc pfnProgress,
                                           void* pProgressData )
{
    *ppoNewCurLayer = m_poDS->GetCurrentLayer();
    m_bResetReadingAllowed = true;

    if( m_nFeatureArraySize == 0 )
    {
        if( m_poDS->IsInterleavedReading() )
        {
            if( *ppoNewCurLayer  == nullptr )
            {
                 *ppoNewCurLayer = this;
            }
            else if( *ppoNewCurLayer  != this )
            {
                return nullptr;
            }

            // If too many features have been accumulated in another layer, we
            // force a switch to that layer, so that it gets emptied.
            for( int i = 0; i < m_poDS->GetLayerCount(); i++ )
            {
                if( m_poDS->m_papoLayers[i] != this &&
                    m_poDS->m_papoLayers[i]->m_nFeatureArraySize > SWITCH_THRESHOLD )
                {
                    *ppoNewCurLayer = m_poDS->m_papoLayers[i];
                    CPLDebug("OSM", "Switching to '%s' as they are too many "
                                    "features in '%s'",
                             m_poDS->m_papoLayers[i]->GetName(),
                             GetName());
                    return nullptr;
                }
            }

            // Read some more data and accumulate features.
            m_poDS->ParseNextChunk(m_nIdxLayer, pfnProgress, pProgressData);

            if( m_nFeatureArraySize == 0 )
            {
                // If there are really no more features to read in the
                // current layer, force a switch to another non-empty layer.

                for( int i = 0; i < m_poDS->GetLayerCount(); i++ )
                {
                    if( m_poDS->m_papoLayers[i] != this &&
                        m_poDS->m_papoLayers[i]->m_nFeatureArraySize > 0 )
                    {
                        *ppoNewCurLayer = m_poDS->m_papoLayers[i];
                        CPLDebug("OSM",
                                 "Switching to '%s' as they are "
                                 "no more feature in '%s'",
                                 m_poDS->m_papoLayers[i]->GetName(),
                                 GetName());
                        return nullptr;
                    }
                }

                /* Game over : no more data to read from the stream */
                *ppoNewCurLayer = nullptr;
                return nullptr;
            }
        }
        else
        {
            while( true )
            {
                int bRet = m_poDS->ParseNextChunk(m_nIdxLayer, nullptr, nullptr);
                // cppcheck-suppress knownConditionTrueFalse
                if( m_nFeatureArraySize != 0 )
                    break;
                if( bRet == FALSE )
                    return nullptr;
            }
        }
    }

    OGRFeature* poFeature = m_papoFeatures[m_nFeatureArrayIndex];

    m_papoFeatures[m_nFeatureArrayIndex] = nullptr;
    m_nFeatureArrayIndex++;

    if( m_nFeatureArrayIndex == m_nFeatureArraySize )
        m_nFeatureArrayIndex = m_nFeatureArraySize = 0;

    return poFeature;
}

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

int OGROSMLayer::TestCapability( const char * pszCap )
{
    if( EQUAL(pszCap, OLCFastGetExtent) )
    {
        OGREnvelope sExtent;
        if( m_poDS->GetExtent(&sExtent) == OGRERR_NONE )
            return TRUE;
    }

    return FALSE;
}

/************************************************************************/
/*                             AddToArray()                             */
/************************************************************************/

bool OGROSMLayer::AddToArray( OGRFeature* poFeature,
                              int bCheckFeatureThreshold )
{
    if( bCheckFeatureThreshold && m_nFeatureArraySize > MAX_THRESHOLD )
    {
        if( !m_bHasWarnedTooManyFeatures )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                    "Too many features have accumulated in %s layer. "
                    "Use the OGR_INTERLEAVED_READING=YES configuration option, "
                    "or the INTERLEAVED_READING=YES open option, or the "
                    "GDALDataset::GetNextFeature() / GDALDatasetGetNextFeature() API.",
                    GetName());
        }
        m_bHasWarnedTooManyFeatures = true;
        return false;
    }

    if( m_nFeatureArraySize == m_nFeatureArrayMaxSize )
    {
        m_nFeatureArrayMaxSize =
            m_nFeatureArrayMaxSize + m_nFeatureArrayMaxSize / 2 + 128;
        CPLDebug("OSM",
                 "For layer %s, new max size is %d",
                 GetName(), m_nFeatureArrayMaxSize);
        OGRFeature** papoNewFeatures = static_cast<OGRFeature **>(
            VSI_REALLOC_VERBOSE(m_papoFeatures,
                                m_nFeatureArrayMaxSize * sizeof(OGRFeature*)));
        if( papoNewFeatures == nullptr )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "For layer %s, cannot resize feature array to %d features",
                     GetName(), m_nFeatureArrayMaxSize);
            return false;
        }
        m_papoFeatures = papoNewFeatures;
    }
    m_papoFeatures[m_nFeatureArraySize ++] = poFeature;

    return true;
}

/************************************************************************/
/*                        EvaluateAttributeFilter()                     */
/************************************************************************/

int OGROSMLayer::EvaluateAttributeFilter(OGRFeature* poFeature)
{
    return (m_poAttrQuery == nullptr
            || m_poAttrQuery->Evaluate( poFeature ));
}

/************************************************************************/
/*                             AddFeature()                             */
/************************************************************************/

int  OGROSMLayer::AddFeature(OGRFeature* poFeature,
                             int bAttrFilterAlreadyEvaluated,
                             int* pbFilteredOut,
                             int bCheckFeatureThreshold)
{
    if( !m_bUserInterested )
    {
        if( pbFilteredOut )
            *pbFilteredOut = TRUE;
        delete poFeature;
        return TRUE;
    }

    OGRGeometry* poGeom = poFeature->GetGeometryRef();
    if( poGeom )
        poGeom->assignSpatialReference( m_poSRS );

    if( (m_poFilterGeom == nullptr
        || FilterGeometry( poFeature->GetGeometryRef() ) )
        && (m_poAttrQuery == nullptr || bAttrFilterAlreadyEvaluated
            || m_poAttrQuery->Evaluate( poFeature )) )
    {
        if( !AddToArray(poFeature, bCheckFeatureThreshold) )
        {
            delete poFeature;
            return FALSE;
        }
    }
    else
    {
        if( pbFilteredOut )
            *pbFilteredOut = TRUE;
        delete poFeature;
        return TRUE;
    }

    if( pbFilteredOut )
        *pbFilteredOut = FALSE;
    return TRUE;
}

/************************************************************************/
/*                             GetExtent()                              */
/************************************************************************/

OGRErr OGROSMLayer::GetExtent( OGREnvelope *psExtent,
                               int /* bForce */ )
{
    if( m_poDS->GetExtent(psExtent) == OGRERR_NONE )
        return OGRERR_NONE;

    /* return OGRLayer::GetExtent(psExtent, bForce);*/
    return OGRERR_FAILURE;
}

/************************************************************************/
/*                          GetLaunderedFieldName()                     */
/************************************************************************/

const char* OGROSMLayer::GetLaunderedFieldName( const char* pszName )
{
    if( m_poDS->DoesAttributeNameLaundering()  &&
        strchr(pszName, ':') != nullptr )
    {
        size_t i = 0;
        for( ;
             i < sizeof(szLaunderedFieldName) - 1 && pszName[i] != '\0';
             i++ )
        {
            if( pszName[i] == ':' )
                szLaunderedFieldName[i] = '_';
            else
                szLaunderedFieldName[i] = pszName[i];
        }
        szLaunderedFieldName[i] = '\0';
        return szLaunderedFieldName;
    }

    return pszName;
}

/************************************************************************/
/*                              AddField()                              */
/************************************************************************/

void OGROSMLayer::AddField( const char* pszName, OGRFieldType eFieldType )
{
    const char* pszLaunderedName = GetLaunderedFieldName(pszName);
    OGRFieldDefn oField(pszLaunderedName, eFieldType);
    m_poFeatureDefn->AddFieldDefn(&oField);

    int nIndex = m_poFeatureDefn->GetFieldCount() - 1;
    char* pszDupName = CPLStrdup(pszName);
    m_apszNames.push_back(pszDupName);
    m_oMapFieldNameToIndex[pszDupName] = nIndex;

    if( strcmp(pszName, "osm_id") == 0 )
        m_nIndexOSMId = nIndex;

    else if( strcmp(pszName, "osm_way_id") == 0 )
        m_nIndexOSMWayId = nIndex;

    else if( strcmp(pszName, "other_tags") == 0 )
        m_nIndexOtherTags = nIndex;

    else if( strcmp(pszName, "all_tags") == 0 )
        m_nIndexAllTags = nIndex;
}

/************************************************************************/
/*                              GetFieldIndex()                         */
/************************************************************************/

int OGROSMLayer::GetFieldIndex( const char* pszName )
{
    std::map<const char*, int, ConstCharComp>::iterator oIter =
        m_oMapFieldNameToIndex.find(pszName);
    if( oIter != m_oMapFieldNameToIndex.end() )
        return oIter->second;

    return -1;
}

/************************************************************************/
/*                         AddInOtherOrAllTags()                        */
/************************************************************************/

int OGROSMLayer::AddInOtherOrAllTags( const char* pszK )
{
    bool bAddToOtherTags = false;

    if( aoSetIgnoreKeys.find(pszK) == aoSetIgnoreKeys.end() )
    {
        char* pszColon = strchr((char*) pszK, ':');
        if( pszColon )
        {
            char chBackup = pszColon[1];
            pszColon[1] = '\0';  /* Evil but OK */
            bAddToOtherTags = ( aoSetIgnoreKeys.find(pszK) ==
                                aoSetIgnoreKeys.end() );
            // cppcheck-suppress redundantAssignment
            pszColon[1] = chBackup;
        }
        else
            bAddToOtherTags = true;
    }

    return bAddToOtherTags;
}

/************************************************************************/
/*                        OGROSMFormatForHSTORE()                       */
/************************************************************************/

static int OGROSMFormatForHSTORE( const char* pszV, char* pszAllTags )
{
    int nAllTagsOff = 0;

    pszAllTags[nAllTagsOff++] = '"';

    for( int k=0; pszV[k] != '\0'; k++ )
    {
        if( pszV[k] == '"' || pszV[k] == '\\' )
            pszAllTags[nAllTagsOff++] = '\\';
        pszAllTags[nAllTagsOff++] = pszV[k];
    }

    pszAllTags[nAllTagsOff++] = '"';

    return nAllTagsOff;
}

/************************************************************************/
/*                            GetValueOfTag()                           */
/************************************************************************/

static const char* GetValueOfTag(const char* pszKeyToSearch,
                                 unsigned int nTags, const OSMTag* pasTags)
{
    for(unsigned int k = 0; k < nTags; k++)
    {
        const char* pszK = pasTags[k].pszK;
        if( strcmp(pszK, pszKeyToSearch) == 0 )
        {
            return pasTags[k].pszV;
        }
    }
    return nullptr;
}

/************************************************************************/
/*                        SetFieldsFromTags()                           */
/************************************************************************/

void OGROSMLayer::SetFieldsFromTags(OGRFeature* poFeature,
                                    GIntBig nID,
                                    bool bIsWayID,
                                    unsigned int nTags, OSMTag* pasTags,
                                    OSMInfo* psInfo)
{
    if( !bIsWayID )
    {
        poFeature->SetFID( nID );

        if( m_bHasOSMId )
        {
            char szID[32];
            snprintf(szID, sizeof(szID), CPL_FRMT_GIB, nID );
            poFeature->SetField(m_nIndexOSMId, szID);
        }
    }
    else
    {
        poFeature->SetFID( nID );

        if( m_nIndexOSMWayId >= 0 )
        {
            char szID[32];
            snprintf(szID, sizeof(szID), CPL_FRMT_GIB, nID );
            poFeature->SetField(m_nIndexOSMWayId, szID );
        }
    }

    if( m_bHasVersion )
    {
        poFeature->SetField("osm_version", psInfo->nVersion);
    }
    if( m_bHasTimestamp )
    {
        if( psInfo->bTimeStampIsStr )
        {
            OGRField sField;
            if( OGRParseXMLDateTime(psInfo->ts.pszTimeStamp, &sField) )
            {
                poFeature->SetField("osm_timestamp", &sField);
            }
        }
        else
        {
            struct tm brokendown;
            CPLUnixTimeToYMDHMS(psInfo->ts.nTimeStamp, &brokendown);
            poFeature->SetField("osm_timestamp",
                                brokendown.tm_year + 1900,
                                brokendown.tm_mon + 1,
                                brokendown.tm_mday,
                                brokendown.tm_hour,
                                brokendown.tm_min,
                                static_cast<float>(brokendown.tm_sec),
                                0);
        }
    }
    if( m_bHasUID )
    {
        poFeature->SetField("osm_uid", psInfo->nUID);
    }
    if( m_bHasUser )
    {
        poFeature->SetField("osm_user", psInfo->pszUserSID);
    }
    if( m_bHasChangeset )
    {
        poFeature->SetField("osm_changeset", (int) psInfo->nChangeset);
    }

    int nAllTagsOff = 0;
    for(unsigned int j = 0; j < nTags; j++)
    {
        const char* pszK = pasTags[j].pszK;
        const char* pszV = pasTags[j].pszV;
        int nIndex = GetFieldIndex(pszK);
        if( nIndex >= 0 && nIndex != m_nIndexOSMId )
        {
            poFeature->SetField(nIndex, pszV);
            if( m_nIndexAllTags < 0 )
                continue;
        }
        if( m_nIndexAllTags >= 0 || m_nIndexOtherTags >= 0 )
        {
            if( AddInOtherOrAllTags(pszK) )
            {
                int nLenK = (int)strlen(pszK);
                int nLenV = (int)strlen(pszV);
                if( nAllTagsOff +
                    1 + 2 * nLenK + 1 +
                    2 +
                    1 + 2 * nLenV + 1 +
                    1 >= ALLTAGS_LENGTH - 1 )
                {
                    if( !m_bHasWarnedAllTagsTruncated )
                        CPLDebug( "OSM",
                                  "all_tags/other_tags field truncated for "
                                  "feature " CPL_FRMT_GIB, nID);
                    m_bHasWarnedAllTagsTruncated = true;
                    continue;
                }

                if( nAllTagsOff )
                    m_pszAllTags[nAllTagsOff++] = ',';

                nAllTagsOff += OGROSMFormatForHSTORE(pszK,
                                                     m_pszAllTags + nAllTagsOff);

                m_pszAllTags[nAllTagsOff++] = '=';
                m_pszAllTags[nAllTagsOff++] = '>';

                nAllTagsOff += OGROSMFormatForHSTORE(pszV,
                                                     m_pszAllTags + nAllTagsOff);
            }

#ifdef notdef
            if( aoSetWarnKeys.find(pszK) == aoSetWarnKeys.end() )
            {
                aoSetWarnKeys.insert(pszK);
                CPLDebug("OSM_KEY", "Ignored key : %s", pszK);
            }
#endif
        }
    }

    if( nAllTagsOff )
    {
        m_pszAllTags[nAllTagsOff] = '\0';
        if( m_nIndexAllTags >= 0 )
            poFeature->SetField(m_nIndexAllTags, m_pszAllTags);
        else
            poFeature->SetField(m_nIndexOtherTags, m_pszAllTags);
    }

    for(size_t i=0; i<m_oComputedAttributes.size();i++)
    {
        const OGROSMComputedAttribute& oAttr = m_oComputedAttributes[i];
        if( oAttr.bHardcodedZOrder )
        {
            const int nHighwayIdx = oAttr.anIndexToBind[0];
            const int nBridgeIdx = oAttr.anIndexToBind[1];
            const int nTunnelIdx = oAttr.anIndexToBind[2];
            const int nRailwayIdx = oAttr.anIndexToBind[3];
            const int nLayerIdx = oAttr.anIndexToBind[4];

            int nZOrder = 0;
    /*
        "SELECT (CASE [highway] WHEN 'minor' THEN 3 WHEN 'road' THEN 3 "
        "WHEN 'unclassified' THEN 3 WHEN 'residential' THEN 3 WHEN "
        "'tertiary_link' THEN 4 WHEN 'tertiary' THEN 4 WHEN 'secondary_link' "
        "THEN 6 WHEN 'secondary' THEN 6 WHEN 'primary_link' THEN 7 WHEN "
        "'primary' THEN 7 WHEN 'trunk_link' THEN 8 WHEN 'trunk' THEN 8 "
        "WHEN 'motorway_link' THEN 9 WHEN 'motorway' THEN 9 ELSE 0 END) + "
        "(CASE WHEN [bridge] IN ('yes', 'true', '1') THEN 10 ELSE 0 END) + "
        "(CASE WHEN [tunnel] IN ('yes', 'true', '1') THEN -10 ELSE 0 END) + "
        "(CASE WHEN [railway] IS NOT NULL THEN 5 ELSE 0 END) + "
        "(CASE WHEN [layer] IS NOT NULL THEN 10 * CAST([layer] AS INTEGER) " */

            const char* pszHighway = nullptr;
            if( nHighwayIdx >= 0)
            {
                if( poFeature->IsFieldSetAndNotNull(nHighwayIdx) )
                {
                    pszHighway = poFeature->GetFieldAsString(nHighwayIdx);
                }
            }
            else
                pszHighway = GetValueOfTag("highway", nTags, pasTags);
            if( pszHighway )
            {
                if( strcmp(pszHighway, "minor") == 0 ||
                    strcmp(pszHighway, "road") == 0 ||
                    strcmp(pszHighway, "unclassified") == 0 ||
                    strcmp(pszHighway, "residential") == 0 )
                {
                    nZOrder += 3;
                }
                else if( strcmp(pszHighway, "tertiary_link") == 0 ||
                         strcmp(pszHighway, "tertiary") == 0 )
                {
                    nZOrder += 4;
                }
                else if( strcmp(pszHighway, "secondary_link") == 0 ||
                         strcmp(pszHighway, "secondary") == 0 )
                {
                    nZOrder += 6;
                }
                else if( strcmp(pszHighway, "primary_link") == 0 ||
                         strcmp(pszHighway, "primary") == 0 )
                {
                    nZOrder += 7;
                }
                else if( strcmp(pszHighway, "trunk_link") == 0 ||
                         strcmp(pszHighway, "trunk") == 0 )
                {
                    nZOrder += 8;
                }
                else if( strcmp(pszHighway, "motorway_link") == 0 ||
                         strcmp(pszHighway, "motorway") == 0 )
                {
                    nZOrder += 9;
                }
            }

            const char* pszBridge = nullptr;
            if( nBridgeIdx >= 0)
            {
                if( poFeature->IsFieldSetAndNotNull(nBridgeIdx) )
                {
                    pszBridge = poFeature->GetFieldAsString(nBridgeIdx);
                }
            }
            else
                pszBridge = GetValueOfTag("bridge", nTags, pasTags);
            if( pszBridge )
            {
                if( strcmp(pszBridge, "yes") == 0 ||
                    strcmp(pszBridge, "true") == 0 ||
                    strcmp(pszBridge, "1") == 0 )
                {
                    nZOrder += 10;
                }
            }

            const char* pszTunnel = nullptr;
            if( nTunnelIdx >= 0)
            {
                if( poFeature->IsFieldSetAndNotNull(nTunnelIdx) )
                {
                    pszTunnel = poFeature->GetFieldAsString(nTunnelIdx);
                }
            }
            else
                pszTunnel = GetValueOfTag("tunnel", nTags, pasTags);
            if( pszTunnel )
            {
                if( strcmp(pszTunnel, "yes") == 0 ||
                    strcmp(pszTunnel, "true") == 0 ||
                    strcmp(pszTunnel, "1") == 0 )
                {
                    nZOrder -= 10;
                }
            }

            const char* pszRailway = nullptr;
            if( nRailwayIdx >= 0 )
            {
                if( poFeature->IsFieldSetAndNotNull(nRailwayIdx) )
                {
                    pszRailway = poFeature->GetFieldAsString(nRailwayIdx);
                }
            }
            else
                pszRailway = GetValueOfTag("railway", nTags, pasTags);
            if( pszRailway )
            {
                nZOrder += 5;
            }

            const char* pszLayer = nullptr;
            if( nLayerIdx >= 0 )
            {
                if( poFeature->IsFieldSetAndNotNull(nLayerIdx) )
                {
                    pszLayer = poFeature->GetFieldAsString(nLayerIdx);
                }
            }
            else
                pszLayer = GetValueOfTag("layer", nTags, pasTags);
            if( pszLayer )
            {
                nZOrder += 10 * atoi(pszLayer);
            }

            poFeature->SetField( oAttr.nIndex, nZOrder);

            continue;
        }

        for(int j=0;j<static_cast<int>(oAttr.anIndexToBind.size());j++)
        {
            if( oAttr.anIndexToBind[j] >= 0 )
            {
                if( !poFeature->IsFieldSetAndNotNull(oAttr.anIndexToBind[j]) )
                {
                    sqlite3_bind_null( oAttr.hStmt, j + 1 );
                }
                else
                {
                    OGRFieldType eType =
                        m_poFeatureDefn->GetFieldDefn(oAttr.anIndexToBind[j])->
                          GetType();
                    if( eType == OFTInteger )
                        sqlite3_bind_int( oAttr.hStmt, j + 1,
                                          poFeature->GetFieldAsInteger(oAttr.anIndexToBind[j]) );
                    else if( eType == OFTInteger64 )
                        sqlite3_bind_int64( oAttr.hStmt, j + 1,
                                          poFeature->GetFieldAsInteger64(oAttr.anIndexToBind[j]) );
                    else if( eType == OFTReal )
                        sqlite3_bind_double( oAttr.hStmt, j + 1,
                                             poFeature->GetFieldAsDouble(oAttr.anIndexToBind[j]) );
                    else
                        sqlite3_bind_text( oAttr.hStmt, j + 1,
                                        poFeature->GetFieldAsString(oAttr.anIndexToBind[j]),
                                        -1, SQLITE_TRANSIENT);
                }
            }
            else
            {
                bool bTagFound = false;
                for(unsigned int k = 0; k < nTags; k++)
                {
                    const char* pszK = pasTags[k].pszK;
                    const char* pszV = pasTags[k].pszV;
                    if( strcmp(pszK, oAttr.aosAttrToBind[j]) == 0 )
                    {
                        sqlite3_bind_text( oAttr.hStmt, j + 1, pszV, -1, SQLITE_TRANSIENT);
                        bTagFound = true;
                        break;
                    }
                }
                if( !bTagFound )
                    sqlite3_bind_null( oAttr.hStmt, j + 1 );
            }
        }

        if( sqlite3_step( oAttr.hStmt ) == SQLITE_ROW &&
            sqlite3_column_count( oAttr.hStmt ) == 1 )
        {
            switch( sqlite3_column_type( oAttr.hStmt, 0 ) )
            {
                case SQLITE_INTEGER:
                    poFeature->SetField( oAttr.nIndex,
                            (GIntBig)sqlite3_column_int64(oAttr.hStmt, 0) );
                    break;
                case SQLITE_FLOAT:
                    poFeature->SetField( oAttr.nIndex,
                            sqlite3_column_double(oAttr.hStmt, 0) );
                    break;
                case SQLITE_TEXT:
                    poFeature->SetField( oAttr.nIndex,
                            (const char*)sqlite3_column_text(oAttr.hStmt, 0) );
                    break;
                default:
                    break;
            }
        }

        sqlite3_reset( oAttr.hStmt );
    }
}

/************************************************************************/
/*                      GetSpatialFilterEnvelope()                      */
/************************************************************************/

const OGREnvelope* OGROSMLayer::GetSpatialFilterEnvelope()
{
    if( m_poFilterGeom != nullptr )
        return &m_sFilterEnvelope;
    else
        return nullptr;
}

/************************************************************************/
/*                        AddInsignificantKey()                         */
/************************************************************************/

void OGROSMLayer::AddInsignificantKey( const char* pszK )
{
    char* pszKDup = CPLStrdup(pszK);
    apszInsignificantKeys.push_back(pszKDup);
    aoSetInsignificantKeys[pszKDup] = 1;
}

/************************************************************************/
/*                          AddIgnoreKey()                              */
/************************************************************************/

void OGROSMLayer::AddIgnoreKey( const char* pszK )
{
    char* pszKDup = CPLStrdup(pszK);
    apszIgnoreKeys.push_back(pszKDup);
    aoSetIgnoreKeys[pszKDup] = 1;
}

/************************************************************************/
/*                           AddWarnKey()                               */
/************************************************************************/

void OGROSMLayer::AddWarnKey( const char* pszK )
{
    aoSetWarnKeys.insert(pszK);
}

/************************************************************************/
/*                           AddWarnKey()                               */
/************************************************************************/

void OGROSMLayer::AddComputedAttribute( const char* pszName,
                                        OGRFieldType eType,
                                        const char* pszSQL )
{
    if( m_poDS->m_hDBForComputedAttributes == nullptr )
    {
        const int rc =
            sqlite3_open_v2(
                ":memory:", &(m_poDS->m_hDBForComputedAttributes),
                SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
                SQLITE_OPEN_NOMUTEX,
                nullptr );
        if( rc != SQLITE_OK )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Cannot open temporary sqlite DB" );
            return;
        }
    }

    if( m_poFeatureDefn->GetFieldIndex(pszName) >= 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "A field with same name %s already exists", pszName );
        return;
    }

    CPLString osSQL(pszSQL);
    const bool bHardcodedZOrder = (eType == OFTInteger) &&
      strcmp(pszSQL,
        "SELECT (CASE [highway] WHEN 'minor' THEN 3 WHEN 'road' THEN 3 "
        "WHEN 'unclassified' THEN 3 WHEN 'residential' THEN 3 WHEN "
        "'tertiary_link' THEN 4 WHEN 'tertiary' THEN 4 WHEN 'secondary_link' "
        "THEN 6 WHEN 'secondary' THEN 6 WHEN 'primary_link' THEN 7 WHEN "
        "'primary' THEN 7 WHEN 'trunk_link' THEN 8 WHEN 'trunk' THEN 8 "
        "WHEN 'motorway_link' THEN 9 WHEN 'motorway' THEN 9 ELSE 0 END) + "
        "(CASE WHEN [bridge] IN ('yes', 'true', '1') THEN 10 ELSE 0 END) + "
        "(CASE WHEN [tunnel] IN ('yes', 'true', '1') THEN -10 ELSE 0 END) + "
        "(CASE WHEN [railway] IS NOT NULL THEN 5 ELSE 0 END) + "
        "(CASE WHEN [layer] IS NOT NULL THEN 10 * CAST([layer] AS INTEGER) "
        "ELSE 0 END)") == 0;
    std::vector<CPLString> aosAttrToBind;
    std::vector<int> anIndexToBind;
    size_t nStartSearch = 0;
    while( true )
    {
        size_t nPos = osSQL.find("[", nStartSearch);
        if( nPos == std::string::npos )
            break;
        nStartSearch = nPos + 1;
        if( nPos > 0 && osSQL[nPos-1] != '\\' )
        {
            CPLString osAttr = osSQL.substr(nPos + 1);
            size_t nPos2 = osAttr.find("]");
            if( nPos2 == std::string::npos )
                break;
            osAttr.resize(nPos2);

            osSQL = osSQL.substr(0, nPos) + "?" + osSQL.substr(nPos + 1 + nPos2+1);

            aosAttrToBind.push_back(osAttr);
            anIndexToBind.push_back(m_poFeatureDefn->GetFieldIndex(osAttr));
        }
    }
    while( true )
    {
        size_t nPos = osSQL.find("\\");
        if( nPos == std::string::npos || nPos == osSQL.size() - 1 )
            break;
        osSQL = osSQL.substr(0, nPos) + osSQL.substr(nPos + 1);
    }

    CPLDebug("OSM", "SQL : \"%s\"", osSQL.c_str());

    sqlite3_stmt *hStmt = nullptr;
    int rc = sqlite3_prepare_v2( m_poDS->m_hDBForComputedAttributes, osSQL, -1,
                              &hStmt, nullptr );
    if( rc != SQLITE_OK )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "sqlite3_prepare_v2() failed :  %s",
                  sqlite3_errmsg(m_poDS->m_hDBForComputedAttributes) );
        return;
    }

    OGRFieldDefn oField(pszName, eType);
    m_poFeatureDefn->AddFieldDefn(&oField);
    m_oComputedAttributes.push_back(OGROSMComputedAttribute(pszName));
    OGROSMComputedAttribute& oComputedAttribute = m_oComputedAttributes.back();
    oComputedAttribute.eType = eType;
    oComputedAttribute.nIndex = m_poFeatureDefn->GetFieldCount() - 1;
    oComputedAttribute.osSQL = pszSQL;
    oComputedAttribute.hStmt = hStmt;
    oComputedAttribute.aosAttrToBind = aosAttrToBind;
    oComputedAttribute.anIndexToBind = anIndexToBind;
    oComputedAttribute.bHardcodedZOrder = bHardcodedZOrder;
}
