/******************************************************************************
 * $Id: mbtilesdataset.cpp 27729 2014-09-24 00:40:16Z goatbar $
 *
 * Project:  GDAL MBTiles driver
 * Purpose:  Implement GDAL MBTiles support using OGR SQLite driver
 * Author:   Even Rouault, <even dot rouault at mines dash paris dot org>
 *
 **********************************************************************
 * 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 "gdal_frmts.h"
#include "gdal_pam.h"
#include "ogr_api.h"
#include "cpl_vsil_curl_priv.h"

#include "zlib.h"
#include "json.h"

#include <math.h>

extern "C" void GDALRegister_MBTiles();

CPL_CVSID("$Id: mbtilesdataset.cpp 27729 2014-09-24 00:40:16Z goatbar $");

static const char * const apszAllowedDrivers[] = {"JPEG", "PNG", NULL};

class MBTilesBand;

/************************************************************************/
/* ==================================================================== */
/*                              MBTilesDataset                          */
/* ==================================================================== */
/************************************************************************/

class MBTilesDataset : public GDALPamDataset
{
    friend class MBTilesBand;

  public:
                 MBTilesDataset();
                 MBTilesDataset(MBTilesDataset* poMainDS, int nLevel);

    virtual     ~MBTilesDataset();

    virtual CPLErr GetGeoTransform(double* padfGeoTransform);
    virtual const char* GetProjectionRef();
    
    virtual char      **GetMetadataDomainList();
    virtual char      **GetMetadata( const char * pszDomain = "" );

    static GDALDataset *Open( GDALOpenInfo * );
    static int          Identify( GDALOpenInfo * );

    char*               FindKey(int iPixel, int iLine,
                                int& nTileColumn, int& nTileRow, int& nZoomLevel);
    void                ComputeTileColTileRowZoomLevel( int nBlockXOff,
                                                        int nBlockYOff,
                                                        int &nTileColumn,
                                                        int &nTileRow,
                                                        int &nZoomLevel );
    int                 HasNonEmptyGrids();

  protected:
    virtual int         CloseDependentDatasets();

  private:

    int bMustFree;
    MBTilesDataset* poMainDS;
    int nLevel;
    int nMinTileCol, nMinTileRow;
    int nMinLevel;

    char** papszMetadata;
    char** papszImageStructure;

    int nResolutions;
    MBTilesDataset** papoOverviews;

    OGRDataSourceH hDS;

    int bFetchedMetadata;
    CPLStringList aosList;

    int bHasNonEmptyGrids;
};

/************************************************************************/
/* ==================================================================== */
/*                              MBTilesBand                             */
/* ==================================================================== */
/************************************************************************/

class MBTilesBand: public GDALPamRasterBand
{
    friend class MBTilesDataset;

    CPLString               osLocationInfo;

  public:
                            MBTilesBand( MBTilesDataset* poDS, int nBand,
                                            GDALDataType eDataType,
                                            int nBlockXSize, int nBlockYSize);

    virtual GDALColorInterp GetColorInterpretation();

    virtual int             GetOverviewCount();
    virtual GDALRasterBand* GetOverview(int nLevel);

    virtual CPLErr          IReadBlock( int, int, void * );

    virtual char      **GetMetadataDomainList();
    virtual const char *GetMetadataItem( const char * pszName,
                                         const char * pszDomain = "" );
};

/************************************************************************/
/*                            MBTilesBand()                          */
/************************************************************************/

MBTilesBand::MBTilesBand(MBTilesDataset* poDS, int nBand,
                                GDALDataType eDataType,
                                int nBlockXSize, int nBlockYSize)
{
    this->poDS = poDS;
    this->nBand = nBand;
    this->eDataType = eDataType;
    this->nBlockXSize = nBlockXSize;
    this->nBlockYSize = nBlockYSize;
}

/************************************************************************/
/*                            IReadBlock()                              */
/************************************************************************/

CPLErr MBTilesBand::IReadBlock( int nBlockXOff, int nBlockYOff, void * pImage)
{
    MBTilesDataset* poGDS = (MBTilesDataset*) poDS;

    int bGotTile = FALSE;
    CPLAssert(eDataType == GDT_Byte);

    int nTileColumn, nTileRow, nZoomLevel;
    poGDS->ComputeTileColTileRowZoomLevel(nBlockXOff, nBlockYOff,
                                          nTileColumn, nTileRow, nZoomLevel);

    const char* pszSQL = CPLSPrintf("SELECT tile_data FROM tiles WHERE "
                                    "tile_column = %d AND tile_row = %d AND zoom_level=%d",
                                    nTileColumn, nTileRow, nZoomLevel);
    CPLDebug("MBTILES", "nBand=%d, nBlockXOff=%d, nBlockYOff=%d, %s",
             nBand, nBlockXOff, nBlockYOff, pszSQL);
    OGRLayerH hSQLLyr = OGR_DS_ExecuteSQL(poGDS->hDS, pszSQL, NULL, NULL);

    OGRFeatureH hFeat = hSQLLyr ? OGR_L_GetNextFeature(hSQLLyr) : NULL;
    if (hFeat != NULL)
    {
        CPLString osMemFileName;
        osMemFileName.Printf("/vsimem/%p", this);

        int nDataSize = 0;
        GByte* pabyData = OGR_F_GetFieldAsBinary(hFeat, 0, &nDataSize);

        VSILFILE * fp = VSIFileFromMemBuffer( osMemFileName.c_str(), pabyData,
                                            nDataSize, FALSE);
        VSIFCloseL(fp);

        GDALDatasetH hDSTile = GDALOpenInternal(osMemFileName.c_str(), GA_ReadOnly, apszAllowedDrivers);
        if (hDSTile != NULL)
        {
            int nTileBands = GDALGetRasterCount(hDSTile);
            if (nTileBands == 4 && poGDS->nBands == 3)
                nTileBands = 3;

            if (GDALGetRasterXSize(hDSTile) == nBlockXSize &&
                GDALGetRasterYSize(hDSTile) == nBlockYSize &&
                (nTileBands == poGDS->nBands ||
                 (nTileBands == 1 && (poGDS->nBands == 3 || poGDS->nBands == 4)) ||
                 (nTileBands == 3 && poGDS->nBands == 4)))
            {
                int iBand;
                void* pSrcImage = NULL;
                GByte abyTranslation[256][4];

                bGotTile = TRUE;

                GDALColorTableH hCT = GDALGetRasterColorTable(GDALGetRasterBand(hDSTile, 1));
                if (nTileBands == 1 && (poGDS->nBands == 3 || poGDS->nBands == 4))
                {
                    if (hCT != NULL)
                        pSrcImage = CPLMalloc(nBlockXSize * nBlockYSize);
                    iBand = 1;
                }
                else
                    iBand = nBand;

                if (nTileBands == 3 && poGDS->nBands == 4 && iBand == 4)
                    memset(pImage, 255, nBlockXSize * nBlockYSize);
                else
                {
                    GDALRasterIO(GDALGetRasterBand(hDSTile, iBand), GF_Read,
                                0, 0, nBlockXSize, nBlockYSize,
                                pImage, nBlockXSize, nBlockYSize, eDataType, 0, 0);
                }

                if (pSrcImage != NULL && hCT != NULL)
                {
                    int i;
                    memcpy(pSrcImage, pImage, nBlockXSize * nBlockYSize);

                    int nEntryCount = GDALGetColorEntryCount( hCT );
                    if (nEntryCount > 256)
                        nEntryCount = 256;
                    for(i = 0; i < nEntryCount; i++)
                    {
                        const GDALColorEntry* psEntry = GDALGetColorEntry( hCT, i );
                        abyTranslation[i][0] = (GByte) psEntry->c1;
                        abyTranslation[i][1] = (GByte) psEntry->c2;
                        abyTranslation[i][2] = (GByte) psEntry->c3;
                        abyTranslation[i][3] = (GByte) psEntry->c4;
                    }
                    for(; i < 256; i++)
                    {
                        abyTranslation[i][0] = 0;
                        abyTranslation[i][1] = 0;
                        abyTranslation[i][2] = 0;
                        abyTranslation[i][3] = 0;
                    }

                    for(i = 0; i < nBlockXSize * nBlockYSize; i++)
                    {
                        ((GByte*)pImage)[i] = abyTranslation[((GByte*)pSrcImage)[i]][nBand-1];
                    }
                }

                for(int iOtherBand=1;iOtherBand<=poGDS->nBands;iOtherBand++)
                {
                    GDALRasterBlock *poBlock;

                    if (iOtherBand == nBand)
                        continue;

                    poBlock = ((MBTilesBand*)poGDS->GetRasterBand(iOtherBand))->
                        TryGetLockedBlockRef(nBlockXOff,nBlockYOff);

                    if (poBlock != NULL)
                    {
                        poBlock->DropLock();
                        continue;
                    }

                    poBlock = poGDS->GetRasterBand(iOtherBand)->
                        GetLockedBlockRef(nBlockXOff,nBlockYOff, TRUE);
                    if (poBlock == NULL)
                        break;

                    GByte* pabySrcBlock = (GByte *) poBlock->GetDataRef();
                    if( pabySrcBlock == NULL )
                    {
                        poBlock->DropLock();
                        break;
                    }

                    if (nTileBands == 3 && poGDS->nBands == 4 && iOtherBand == 4)
                        memset(pabySrcBlock, 255, nBlockXSize * nBlockYSize);
                    else if (nTileBands == 1 && (poGDS->nBands == 3 || poGDS->nBands == 4))
                    {
                        int i;
                        if (pSrcImage)
                        {
                            for(i = 0; i < nBlockXSize * nBlockYSize; i++)
                            {
                                ((GByte*)pabySrcBlock)[i] =
                                    abyTranslation[((GByte*)pSrcImage)[i]][iOtherBand-1];
                            }
                        }
                        else
                            memcpy(pabySrcBlock, pImage, nBlockXSize * nBlockYSize);
                    }
                    else
                    {
                        GDALRasterIO(GDALGetRasterBand(hDSTile, iOtherBand), GF_Read,
                            0, 0, nBlockXSize, nBlockYSize,
                            pabySrcBlock, nBlockXSize, nBlockYSize, eDataType, 0, 0);
                    }

                    poBlock->DropLock();
                }

                CPLFree(pSrcImage);
            }
            else if (GDALGetRasterXSize(hDSTile) == nBlockXSize &&
                     GDALGetRasterYSize(hDSTile) == nBlockYSize &&
                     (nTileBands == 3 && poGDS->nBands == 1))
            {
                bGotTile = TRUE;

                GByte* pabyRGBImage = (GByte*)CPLMalloc(3 * nBlockXSize * nBlockYSize);
                GDALDatasetRasterIO(hDSTile, GF_Read,
                                    0, 0, nBlockXSize, nBlockYSize,
                                    pabyRGBImage, nBlockXSize, nBlockYSize, eDataType,
                                    3, NULL, 3, 3 * nBlockXSize, 1);
                for(int i=0;i<nBlockXSize*nBlockYSize;i++)
                {
                    int R = pabyRGBImage[3*i];
                    int G = pabyRGBImage[3*i+1];
                    int B = pabyRGBImage[3*i+2];
                    GByte Y = (GByte)((213 * R + 715 * G + 72 * B) / 1000);
                    ((GByte*)pImage)[i] = Y;
                }
                CPLFree(pabyRGBImage);
            }
            else
            {
                CPLDebug("MBTILES", "tile size = %d, tile height = %d, tile bands = %d",
                         GDALGetRasterXSize(hDSTile), GDALGetRasterYSize(hDSTile),
                         GDALGetRasterCount(hDSTile));
            }
            GDALClose(hDSTile);
        }

        VSIUnlink( osMemFileName.c_str() );

        OGR_F_Destroy(hFeat);
    }

    OGR_DS_ReleaseResultSet(poGDS->hDS, hSQLLyr);

    if (!bGotTile)
    {
        memset(pImage, (nBand == 4) ? 0 : 255, nBlockXSize * nBlockYSize);

        for(int iOtherBand=1;iOtherBand<=poGDS->nBands;iOtherBand++)
        {
            GDALRasterBlock *poBlock;

            if (iOtherBand == nBand)
                continue;

            poBlock = poGDS->GetRasterBand(iOtherBand)->
                GetLockedBlockRef(nBlockXOff,nBlockYOff, TRUE);
            if (poBlock == NULL)
                break;

            GByte* pabySrcBlock = (GByte *) poBlock->GetDataRef();
            if( pabySrcBlock == NULL )
            {
                poBlock->DropLock();
                break;
            }

            memset(pabySrcBlock, (iOtherBand == 4) ? 0 : 255,
                   nBlockXSize * nBlockYSize);

            poBlock->DropLock();
        }
    }

    return CE_None;
}

/************************************************************************/
/*                           utf8decode()                               */
/************************************************************************/

static unsigned utf8decode(const char* p, const char* end, int* len)
{
  unsigned char c = *(unsigned char*)p;
  if (c < 0x80) {
    *len = 1;
    return c;
  } else if (c < 0xc2) {
    goto FAIL;
  }
  if (p+1 >= end || (p[1]&0xc0) != 0x80) goto FAIL;
  if (c < 0xe0) {
    *len = 2;
    return
      ((p[0] & 0x1f) << 6) +
      ((p[1] & 0x3f));
  } else if (c == 0xe0) {
    if (((unsigned char*)p)[1] < 0xa0) goto FAIL;
    goto UTF8_3;
#if STRICT_RFC3629
  } else if (c == 0xed) {
    // RFC 3629 says surrogate chars are illegal.
    if (((unsigned char*)p)[1] >= 0xa0) goto FAIL;
    goto UTF8_3;
  } else if (c == 0xef) {
    // 0xfffe and 0xffff are also illegal characters
    if (((unsigned char*)p)[1]==0xbf &&
    ((unsigned char*)p)[2]>=0xbe) goto FAIL;
    goto UTF8_3;
#endif
  } else if (c < 0xf0) {
  UTF8_3:
    if (p+2 >= end || (p[2]&0xc0) != 0x80) goto FAIL;
    *len = 3;
    return
      ((p[0] & 0x0f) << 12) +
      ((p[1] & 0x3f) << 6) +
      ((p[2] & 0x3f));
  } else if (c == 0xf0) {
    if (((unsigned char*)p)[1] < 0x90) goto FAIL;
    goto UTF8_4;
  } else if (c < 0xf4) {
  UTF8_4:
    if (p+3 >= end || (p[2]&0xc0) != 0x80 || (p[3]&0xc0) != 0x80) goto FAIL;
    *len = 4;
#if STRICT_RFC3629
    // RFC 3629 says all codes ending in fffe or ffff are illegal:
    if ((p[1]&0xf)==0xf &&
    ((unsigned char*)p)[2] == 0xbf &&
    ((unsigned char*)p)[3] >= 0xbe) goto FAIL;
#endif
    return
      ((p[0] & 0x07) << 18) +
      ((p[1] & 0x3f) << 12) +
      ((p[2] & 0x3f) << 6) +
      ((p[3] & 0x3f));
  } else if (c == 0xf4) {
    if (((unsigned char*)p)[1] > 0x8f) goto FAIL; // after 0x10ffff
    goto UTF8_4;
  } else {
  FAIL:
    *len = 1;
    return 0xfffd; // Unicode REPLACEMENT CHARACTER
  }
}


/************************************************************************/
/*                  ComputeTileColTileRowZoomLevel()                    */
/************************************************************************/

void MBTilesDataset::ComputeTileColTileRowZoomLevel(int nBlockXOff,
                                                    int nBlockYOff,
                                                    int &nTileColumn,
                                                    int &nTileRow,
                                                    int &nZoomLevel)
{
    const int nBlockYSize = 256;

    int _nMinLevel = (poMainDS) ? poMainDS->nMinLevel : nMinLevel;
    int _nMinTileCol = (poMainDS) ? poMainDS->nMinTileCol : nMinTileCol;
    int _nMinTileRow = (poMainDS) ? poMainDS->nMinTileRow : nMinTileRow;
    _nMinTileCol >>= nLevel;

    nTileColumn = nBlockXOff + _nMinTileCol;
    nTileRow = (((nRasterYSize / nBlockYSize - 1 - nBlockYOff) << nLevel) + _nMinTileRow) >> nLevel;
    nZoomLevel = ((poMainDS) ? poMainDS->nResolutions : nResolutions) - nLevel + _nMinLevel;
}

/************************************************************************/
/*                          HasNonEmptyGrids()                          */
/************************************************************************/

int MBTilesDataset::HasNonEmptyGrids()
{
    OGRLayerH hSQLLyr;
    OGRFeatureH hFeat;
    const char* pszSQL;

    if (poMainDS)
        return poMainDS->HasNonEmptyGrids();

    if (bHasNonEmptyGrids >= 0)
        return bHasNonEmptyGrids;

    bHasNonEmptyGrids = FALSE;

    if (OGR_DS_GetLayerByName(hDS, "grids") == NULL)
        return FALSE;

    pszSQL = "SELECT type FROM sqlite_master WHERE name = 'grids'";
    CPLDebug("MBTILES", "%s", pszSQL);
    hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
    if (hSQLLyr == NULL)
        return FALSE;

    hFeat = OGR_L_GetNextFeature(hSQLLyr);
    if (hFeat == NULL || !OGR_F_IsFieldSet(hFeat, 0))
    {
        OGR_F_Destroy(hFeat);
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
        return FALSE;
    }

    int bGridsIsView = strcmp(OGR_F_GetFieldAsString(hFeat, 0), "view") == 0;

    OGR_F_Destroy(hFeat);
    OGR_DS_ReleaseResultSet(hDS, hSQLLyr);

    bHasNonEmptyGrids = TRUE;

    /* In the case 'grids' is a view (and a join between the 'map' and 'grid_utfgrid' layers */
    /* the cost of evaluating a join is very long, even if grid_utfgrid is empty */
    /* so check it is not empty */
    if (bGridsIsView)
    {
        OGRLayerH hGridUTFGridLyr;
        hGridUTFGridLyr = OGR_DS_GetLayerByName(hDS, "grid_utfgrid");
        if (hGridUTFGridLyr != NULL)
        {
            OGR_L_ResetReading(hGridUTFGridLyr);
            hFeat = OGR_L_GetNextFeature(hGridUTFGridLyr);
            OGR_F_Destroy(hFeat);

            bHasNonEmptyGrids = hFeat != NULL;
        }
    }

    return bHasNonEmptyGrids;
}

/************************************************************************/
/*                             FindKey()                                */
/************************************************************************/

char* MBTilesDataset::FindKey(int iPixel, int iLine,
                              int& nTileColumn, int& nTileRow, int& nZoomLevel)
{
    const int nBlockXSize = 256, nBlockYSize = 256;
    int nBlockXOff = iPixel / nBlockXSize;
    int nBlockYOff = iLine / nBlockYSize;

    int nColInBlock = iPixel % nBlockXSize;
    int nRowInBlock = iLine % nBlockXSize;

    ComputeTileColTileRowZoomLevel(nBlockXOff, nBlockYOff,
                                   nTileColumn, nTileRow, nZoomLevel);

    char* pszKey = NULL;

    OGRLayerH hSQLLyr;
    OGRFeatureH hFeat;
    const char* pszSQL;
    json_object* poGrid = NULL;
    int i;

    /* See https://github.com/mapbox/utfgrid-spec/blob/master/1.0/utfgrid.md */
    /* for the explanation of the following processings */

    pszSQL = CPLSPrintf("SELECT grid FROM grids WHERE "
                        "zoom_level = %d AND tile_column = %d AND tile_row = %d",
                        nZoomLevel, nTileColumn, nTileRow);
    CPLDebug("MBTILES", "%s", pszSQL);
    hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
    if (hSQLLyr == NULL)
        return NULL;

    hFeat = OGR_L_GetNextFeature(hSQLLyr);
    if (hFeat == NULL || !OGR_F_IsFieldSet(hFeat, 0))
    {
        OGR_F_Destroy(hFeat);
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
        return NULL;
    }

    int nDataSize = 0;
    GByte* pabyData = OGR_F_GetFieldAsBinary(hFeat, 0, &nDataSize);

    int nUncompressedSize = 256*256;
    GByte* pabyUncompressed = (GByte*)CPLMalloc(nUncompressedSize + 1);

    z_stream sStream;
    memset(&sStream, 0, sizeof(sStream));
    inflateInit(&sStream);
    sStream.next_in   = pabyData;
    sStream.avail_in  = nDataSize;
    sStream.next_out  = pabyUncompressed;
    sStream.avail_out = nUncompressedSize;
    int nStatus = inflate(&sStream, Z_FINISH);
    inflateEnd(&sStream);
    if (nStatus != Z_OK && nStatus != Z_STREAM_END)
    {
        CPLDebug("MBTILES", "Error unzipping grid");
        nUncompressedSize = 0;
        pabyUncompressed[nUncompressedSize] = 0;
    }
    else
    {
        nUncompressedSize -= sStream.avail_out;
        pabyUncompressed[nUncompressedSize] = 0;
        //CPLDebug("MBTILES", "Grid size = %d", nUncompressedSize);
        //CPLDebug("MBTILES", "Grid value = %s", (const char*)pabyUncompressed);
    }

    struct json_tokener *jstok = NULL;
    json_object* jsobj = NULL;

    if (nUncompressedSize == 0)
    {
        goto end;
    }

    jstok = json_tokener_new();
    jsobj = json_tokener_parse_ex(jstok, (const char*)pabyUncompressed, -1);
    if( jstok->err != json_tokener_success)
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                    "JSON parsing error: %s (at offset %d)",
                    json_tokener_error_desc(jstok->err),
                    jstok->char_offset);
        json_tokener_free(jstok);

        goto end;
    }

    json_tokener_free(jstok);

    if (json_object_is_type(jsobj, json_type_object))
    {
        poGrid = json_object_object_get(jsobj, "grid");
    }
    if (poGrid != NULL && json_object_is_type(poGrid, json_type_array))
    {
        int nLines;
        int nFactor;
        json_object* poRow;
        char* pszRow = NULL;

        nLines = json_object_array_length(poGrid);
        if (nLines == 0)
            goto end;

        nFactor = 256 / nLines;
        nRowInBlock /= nFactor;
        nColInBlock /= nFactor;

        poRow = json_object_array_get_idx(poGrid, nRowInBlock);

        /* Extract line of interest in grid */
        if (poRow != NULL && json_object_is_type(poRow, json_type_string))
        {
            pszRow = CPLStrdup(json_object_get_string(poRow));
        }

        if (pszRow == NULL)
            goto end;

        /* Unapply JSON encoding */
        for (i = 0; pszRow[i] != '\0'; i++)
        {
            unsigned char c = ((GByte*)pszRow)[i];
            if (c >= 93) c--;
            if (c >= 35) c--;
            if (c < 32)
            {
                CPLDebug("MBTILES", "Invalid character at byte %d", i);
                break;
            }
            c -= 32;
            ((GByte*)pszRow)[i] = c;
        }

        if (pszRow[i] == '\0')
        {
            char* pszEnd = pszRow + i;

            int iCol = 0;
            i = 0;
            int nKey = -1;
            while(pszRow + i < pszEnd)
            {
                int len = 0;
                unsigned int res = utf8decode(pszRow + i, pszEnd, &len);

                /* Invalid UTF8 ? */
                if (res > 127 && len == 1)
                    break;

                if (iCol == nColInBlock)
                {
                    nKey = (int)res;
                    //CPLDebug("MBTILES", "Key index = %d", nKey);
                    break;
                }
                i += len;
                iCol ++;
            }

            /* Find key */
            json_object* poKeys = json_object_object_get(jsobj, "keys");
            if (nKey >= 0 && poKeys != NULL &&
                json_object_is_type(poKeys, json_type_array) &&
                nKey < json_object_array_length(poKeys))
            {
                json_object* poKey = json_object_array_get_idx(poKeys, nKey);
                if (poKey != NULL && json_object_is_type(poKey, json_type_string))
                {
                    pszKey = CPLStrdup(json_object_get_string(poKey));
                }
            }
        }

        CPLFree(pszRow);
    }

end:
    if (jsobj)
        json_object_put(jsobj);
    if (pabyUncompressed)
        CPLFree(pabyUncompressed);
    if (hFeat)
        OGR_F_Destroy(hFeat);
    if (hSQLLyr)
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);

    return pszKey;
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **MBTilesBand::GetMetadataDomainList()
{
    return CSLAddString(GDALPamRasterBand::GetMetadataDomainList(), "LocationInfo");
}

/************************************************************************/
/*                         GetMetadataItem()                            */
/************************************************************************/

const char *MBTilesBand::GetMetadataItem( const char * pszName,
                                          const char * pszDomain )
{
    MBTilesDataset* poGDS = (MBTilesDataset*) poDS;

/* ==================================================================== */
/*      LocationInfo handling.                                          */
/* ==================================================================== */
    if( pszDomain != NULL
        && EQUAL(pszDomain,"LocationInfo")
        && (EQUALN(pszName,"Pixel_",6) || EQUALN(pszName,"GeoPixel_",9)) )
    {
        int iPixel, iLine;

        if (!poGDS->HasNonEmptyGrids())
            return NULL;

/* -------------------------------------------------------------------- */
/*      What pixel are we aiming at?                                    */
/* -------------------------------------------------------------------- */
        if( EQUALN(pszName,"Pixel_",6) )
        {
            if( sscanf( pszName+6, "%d_%d", &iPixel, &iLine ) != 2 )
                return NULL;
        }
        else if( EQUALN(pszName,"GeoPixel_",9) )
        {
            double adfGeoTransform[6];
            double adfInvGeoTransform[6];
            double dfGeoX, dfGeoY;

            if( sscanf( pszName+9, "%lf_%lf", &dfGeoX, &dfGeoY ) != 2 )
                return NULL;

            if( GetDataset() == NULL )
                return NULL;

            if( GetDataset()->GetGeoTransform( adfGeoTransform ) != CE_None )
                return NULL;

            if( !GDALInvGeoTransform( adfGeoTransform, adfInvGeoTransform ) )
                return NULL;

            iPixel = (int) floor(
                adfInvGeoTransform[0]
                + adfInvGeoTransform[1] * dfGeoX
                + adfInvGeoTransform[2] * dfGeoY );
            iLine = (int) floor(
                adfInvGeoTransform[3]
                + adfInvGeoTransform[4] * dfGeoX
                + adfInvGeoTransform[5] * dfGeoY );
        }
        else
            return NULL;

        if( iPixel < 0 || iLine < 0
            || iPixel >= GetXSize()
            || iLine >= GetYSize() )
            return NULL;

        int nTileColumn = -1, nTileRow = -1, nZoomLevel = -1;
        char* pszKey = poGDS->FindKey(iPixel, iLine, nTileColumn, nTileRow, nZoomLevel);

        if (pszKey != NULL)
        {
            //CPLDebug("MBTILES", "Key = %s", pszKey);

            osLocationInfo = "<LocationInfo>";
            osLocationInfo += "<Key>";
            char* pszXMLEscaped = CPLEscapeString(pszKey, -1, CPLES_XML_BUT_QUOTES);
            osLocationInfo += pszXMLEscaped;
            CPLFree(pszXMLEscaped);
            osLocationInfo += "</Key>";

            if (OGR_DS_GetLayerByName(poGDS->hDS, "grid_data") != NULL &&
                strchr(pszKey, '\'') == NULL)
            {
                const char* pszSQL;
                OGRLayerH hSQLLyr;
                OGRFeatureH hFeat;

                pszSQL = CPLSPrintf("SELECT key_json FROM keymap WHERE "
                                    "key_name = '%s'",
                                    pszKey);
                CPLDebug("MBTILES", "%s", pszSQL);
                hSQLLyr = OGR_DS_ExecuteSQL(poGDS->hDS, pszSQL, NULL, NULL);
                if (hSQLLyr)
                {
                    hFeat = OGR_L_GetNextFeature(hSQLLyr);
                    if (hFeat != NULL && OGR_F_IsFieldSet(hFeat, 0))
                    {
                        const char* pszJSon = OGR_F_GetFieldAsString(hFeat, 0);
                        //CPLDebug("MBTILES", "JSon = %s", pszJSon);

                        osLocationInfo += "<JSon>";
#ifdef CPLES_XML_BUT_QUOTES
                        pszXMLEscaped = CPLEscapeString(pszJSon, -1, CPLES_XML_BUT_QUOTES);
#else
                        pszXMLEscaped = CPLEscapeString(pszJSon, -1, CPLES_XML);
#endif
                        osLocationInfo += pszXMLEscaped;
                        CPLFree(pszXMLEscaped);
                        osLocationInfo += "</JSon>";
                    }
                    OGR_F_Destroy(hFeat);
                }
                OGR_DS_ReleaseResultSet(poGDS->hDS, hSQLLyr);
            }

            osLocationInfo += "</LocationInfo>";

            CPLFree(pszKey);

            return osLocationInfo.c_str();
        }

        return NULL;
    }
    else
        return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
}

/************************************************************************/
/*                         GetOverviewCount()                           */
/************************************************************************/

int MBTilesBand::GetOverviewCount()
{
    MBTilesDataset* poGDS = (MBTilesDataset*) poDS;

    if (poGDS->nResolutions >= 1)
        return poGDS->nResolutions;
    else
        return GDALPamRasterBand::GetOverviewCount();
}

/************************************************************************/
/*                              GetOverview()                           */
/************************************************************************/

GDALRasterBand* MBTilesBand::GetOverview(int nLevel)
{
    MBTilesDataset* poGDS = (MBTilesDataset*) poDS;

    if (poGDS->nResolutions == 0)
        return GDALPamRasterBand::GetOverview(nLevel);

    if (nLevel < 0 || nLevel >= poGDS->nResolutions)
        return NULL;

    GDALDataset* poOvrDS = poGDS->papoOverviews[nLevel];
    if (poOvrDS)
        return poOvrDS->GetRasterBand(nBand);
    else
        return NULL;
}

/************************************************************************/
/*                   GetColorInterpretation()                           */
/************************************************************************/

GDALColorInterp MBTilesBand::GetColorInterpretation()
{
    MBTilesDataset* poGDS = (MBTilesDataset*) poDS;
    if (poGDS->nBands == 1)
    {
        return GCI_GrayIndex;
    }
    else if (poGDS->nBands == 3 || poGDS->nBands == 4)
    {
        if (nBand == 1)
            return GCI_RedBand;
        else if (nBand == 2)
            return GCI_GreenBand;
        else if (nBand == 3)
            return GCI_BlueBand;
        else if (nBand == 4)
            return GCI_AlphaBand;
    }

    return GCI_Undefined;
}

/************************************************************************/
/*                         MBTilesDataset()                          */
/************************************************************************/

MBTilesDataset::MBTilesDataset()
{
    bMustFree = FALSE;
    nLevel = 0;
    poMainDS = NULL;
    nResolutions = 0;
    hDS = NULL;
    papoOverviews = NULL;
    papszMetadata = NULL;
    papszImageStructure =
        CSLAddString(NULL, "INTERLEAVE=PIXEL");
    nMinTileCol = nMinTileRow = 0;
    nMinLevel = 0;
    bFetchedMetadata = FALSE;
    bHasNonEmptyGrids = -1;
}

/************************************************************************/
/*                          MBTilesDataset()                            */
/************************************************************************/

MBTilesDataset::MBTilesDataset(MBTilesDataset* poMainDS, int nLevel)
{
    bMustFree = FALSE;
    this->nLevel = nLevel;
    this->poMainDS = poMainDS;
    nResolutions = poMainDS->nResolutions - nLevel;
    hDS = poMainDS->hDS;
    papoOverviews = poMainDS->papoOverviews + nLevel;
    papszMetadata = poMainDS->papszMetadata;
    papszImageStructure =  poMainDS->papszImageStructure;

    nRasterXSize = poMainDS->nRasterXSize / (1 << nLevel);
    nRasterYSize = poMainDS->nRasterYSize / (1 << nLevel);
    nMinTileCol = nMinTileRow = 0;
    nMinLevel = 0;
    bFetchedMetadata = FALSE;
    bHasNonEmptyGrids = -1;
}

/************************************************************************/
/*                        ~MBTilesDataset()                             */
/************************************************************************/

MBTilesDataset::~MBTilesDataset()
{
    CloseDependentDatasets();
}

/************************************************************************/
/*                      CloseDependentDatasets()                        */
/************************************************************************/

int MBTilesDataset::CloseDependentDatasets()
{
    int bRet = GDALPamDataset::CloseDependentDatasets();

    if (poMainDS == NULL && !bMustFree)
    {
        CSLDestroy(papszMetadata);
        papszMetadata = NULL;
        CSLDestroy(papszImageStructure);
        papszImageStructure = NULL;

        int i;

        if (papoOverviews)
        {
            for(i=0;i<nResolutions;i++)
            {
                if (papoOverviews[i] != NULL &&
                    papoOverviews[i]->bMustFree)
                {
                    papoOverviews[i]->poMainDS = NULL;
                }
                delete papoOverviews[i];
            }
            CPLFree(papoOverviews);
            papoOverviews = NULL;
            nResolutions = 0;
            bRet = TRUE;
        }

        if (hDS != NULL)
            OGRReleaseDataSource(hDS);
        hDS = NULL;
    }
    else if (poMainDS != NULL && bMustFree)
    {
        poMainDS->papoOverviews[nLevel-1] = NULL;
        delete poMainDS;
        poMainDS = NULL;
        bRet = TRUE;
    }

    return bRet;
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

//#define MAX_GM 20037508.3427892
#define MAX_GM 20037500.

CPLErr MBTilesDataset::GetGeoTransform(double* padfGeoTransform)
{
    int nMaxLevel = nMinLevel + nResolutions;
    if (nMaxLevel == 0)
    {
        padfGeoTransform[0] = -MAX_GM;
        padfGeoTransform[1] = 2 * MAX_GM / nRasterXSize;
        padfGeoTransform[2] = 0;
        padfGeoTransform[3] = MAX_GM;
        padfGeoTransform[4] = 0;
        padfGeoTransform[5] = -2 * MAX_GM / nRasterYSize;
    }
    else
    {
        int nMaxTileCol = nMinTileCol + nRasterXSize / 256;
        int nMaxTileRow = nMinTileRow + nRasterYSize / 256;
        int nMiddleTile = (1 << nMaxLevel) / 2;
        padfGeoTransform[0] = 2 * MAX_GM * (nMinTileCol - nMiddleTile) / (1 << nMaxLevel);
        padfGeoTransform[1] = 2 * MAX_GM * (nMaxTileCol - nMinTileCol) / (1 << nMaxLevel) / nRasterXSize;
        padfGeoTransform[2] = 0;
        padfGeoTransform[3] = 2 * MAX_GM * (nMaxTileRow - nMiddleTile) / (1 << nMaxLevel);
        padfGeoTransform[4] = 0;
        padfGeoTransform[5] = -2 * MAX_GM * (nMaxTileRow - nMinTileRow) / (1 << nMaxLevel) / nRasterYSize;
    }
    return CE_None;
}

/************************************************************************/
/*                         GetProjectionRef()                           */
/************************************************************************/

const char* MBTilesDataset::GetProjectionRef()
{
    return "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]";
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **MBTilesDataset::GetMetadataDomainList()
{
    return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(),
                                   TRUE,
                                   "", NULL);
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char** MBTilesDataset::GetMetadata( const char * pszDomain )
{
    if (pszDomain != NULL && !EQUAL(pszDomain, ""))
        return GDALPamDataset::GetMetadata(pszDomain);

    if (bFetchedMetadata)
        return aosList.List();

    bFetchedMetadata = TRUE;

    OGRLayerH hSQLLyr = OGR_DS_ExecuteSQL(hDS,
            "SELECT name, value FROM metadata", NULL, NULL);
    if (hSQLLyr == NULL)
        return NULL;

    if (OGR_FD_GetFieldCount(OGR_L_GetLayerDefn(hSQLLyr)) != 2)
    {
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
        return NULL;
    }

    OGRFeatureH hFeat;
    while( (hFeat = OGR_L_GetNextFeature(hSQLLyr)) != NULL )
    {
        if (OGR_F_IsFieldSet(hFeat, 0) && OGR_F_IsFieldSet(hFeat, 1))
        {
            const char* pszName = OGR_F_GetFieldAsString(hFeat, 0);
            const char* pszValue = OGR_F_GetFieldAsString(hFeat, 1);
            if (pszValue[0] != '\0' &&
                strncmp(pszValue, "function(",9) != 0 &&
                strstr(pszValue, "<img ") == NULL &&
                strstr(pszValue, "<p>") == NULL &&
                strstr(pszValue, "</p>") == NULL &&
                strstr(pszValue, "<div") == NULL)
            {
                aosList.AddNameValue(pszName, pszValue);
            }
        }
        OGR_F_Destroy(hFeat);
    }
    OGR_DS_ReleaseResultSet(hDS, hSQLLyr);

    return aosList.List();
}

/************************************************************************/
/*                             Identify()                               */
/************************************************************************/

int MBTilesDataset::Identify(GDALOpenInfo* poOpenInfo)
{
    if (EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "MBTILES") &&
        poOpenInfo->nHeaderBytes >= 1024 &&
        EQUALN((const char*)poOpenInfo->pabyHeader, "SQLite Format 3", 15))
    {
        return TRUE;
    }

    return FALSE;
}

/************************************************************************/
/*                        MBTilesGetMinMaxZoomLevel()                   */
/************************************************************************/

static
int MBTilesGetMinMaxZoomLevel(OGRDataSourceH hDS, int bHasMap,
                                int &nMinLevel, int &nMaxLevel)
{
    const char* pszSQL;
    OGRLayerH hSQLLyr;
    OGRFeatureH hFeat;
    int bHasMinMaxLevel = FALSE;

    pszSQL = "SELECT value FROM metadata WHERE name = 'minzoom' UNION ALL "
             "SELECT value FROM metadata WHERE name = 'maxzoom'";
    CPLDebug("MBTILES", "%s", pszSQL);
    hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
    if (hSQLLyr)
    {
        hFeat = OGR_L_GetNextFeature(hSQLLyr);
        if (hFeat)
        {
            int bHasMinLevel = FALSE;
            if (OGR_F_IsFieldSet(hFeat, 0))
            {
                nMinLevel = OGR_F_GetFieldAsInteger(hFeat, 0);
                bHasMinLevel = TRUE;
            }
            OGR_F_Destroy(hFeat);

            if (bHasMinLevel)
            {
                hFeat = OGR_L_GetNextFeature(hSQLLyr);
                if (hFeat)
                {
                    if (OGR_F_IsFieldSet(hFeat, 0))
                    {
                        nMaxLevel = OGR_F_GetFieldAsInteger(hFeat, 0);
                        bHasMinMaxLevel = TRUE;
                    }
                    OGR_F_Destroy(hFeat);
                }
            }
        }

        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
    }

    if( !bHasMinMaxLevel )
    {
#define OPTIMIZED_FOR_VSICURL
#ifdef  OPTIMIZED_FOR_VSICURL
        int iLevel;
        for(iLevel = 0; nMinLevel < 0 && iLevel < 16; iLevel ++)
        {
            pszSQL = CPLSPrintf(
                "SELECT zoom_level FROM %s WHERE zoom_level = %d LIMIT 1",
                (bHasMap) ? "map" : "tiles", iLevel);
            CPLDebug("MBTILES", "%s", pszSQL);
            hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
            if (hSQLLyr)
            {
                hFeat = OGR_L_GetNextFeature(hSQLLyr);
                if (hFeat)
                {
                    nMinLevel = iLevel;
                    OGR_F_Destroy(hFeat);
                }
                OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
            }
        }

        if (nMinLevel < 0)
            return FALSE;

        for(iLevel = 32; nMaxLevel < 0 && iLevel >= nMinLevel; iLevel --)
        {
            pszSQL = CPLSPrintf(
                "SELECT zoom_level FROM %s WHERE zoom_level = %d LIMIT 1",
                (bHasMap) ? "map" : "tiles", iLevel);
            CPLDebug("MBTILES", "%s", pszSQL);
            hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
            if (hSQLLyr)
            {
                hFeat = OGR_L_GetNextFeature(hSQLLyr);
                if (hFeat)
                {
                    nMaxLevel = iLevel;
                    bHasMinMaxLevel = TRUE;
                    OGR_F_Destroy(hFeat);
                }
                OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
            }
        }
#else
        pszSQL = "SELECT min(zoom_level), max(zoom_level) FROM tiles";
        CPLDebug("MBTILES", "%s", pszSQL);
        hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
        if (hSQLLyr == NULL)
        {
            return FALSE;
        }

        hFeat = OGR_L_GetNextFeature(hSQLLyr);
        if (hFeat == NULL)
        {
            OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
            return FALSE;
        }

        if (OGR_F_IsFieldSet(hFeat, 0) && OGR_F_IsFieldSet(hFeat, 1))
        {
            nMinLevel = OGR_F_GetFieldAsInteger(hFeat, 0);
            nMaxLevel = OGR_F_GetFieldAsInteger(hFeat, 1);
            bHasMinMaxLevel = TRUE;
        }

        OGR_F_Destroy(hFeat);
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
#endif
    }

    return bHasMinMaxLevel;
}

/************************************************************************/
/*                           MBTilesGetBounds()                         */
/************************************************************************/

static
int MBTilesGetBounds(OGRDataSourceH hDS, CPL_UNUSED int nMinLevel, int nMaxLevel,
                     int& nMinTileRow, int& nMaxTileRow,
                     int& nMinTileCol, int &nMaxTileCol)
{
    const char* pszSQL;
    int bHasBounds = FALSE;
    OGRLayerH hSQLLyr;
    OGRFeatureH hFeat;

    pszSQL = "SELECT value FROM metadata WHERE name = 'bounds'";
    CPLDebug("MBTILES", "%s", pszSQL);
    hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
    if (hSQLLyr)
    {
        hFeat = OGR_L_GetNextFeature(hSQLLyr);
        if (hFeat != NULL)
        {
            const char* pszBounds = OGR_F_GetFieldAsString(hFeat, 0);
            char** papszTokens = CSLTokenizeString2(pszBounds, ",", 0);
            if (CSLCount(papszTokens) != 4 ||
                fabs(atof(papszTokens[0])) > 180 ||
                fabs(atof(papszTokens[1])) > 86 ||
                fabs(atof(papszTokens[2])) > 180 ||
                fabs(atof(papszTokens[3])) > 86 ||
                atof(papszTokens[0]) > atof(papszTokens[2]) ||
                atof(papszTokens[1]) > atof(papszTokens[3]))
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for 'bounds' metadata");
                CSLDestroy(papszTokens);
                OGR_F_Destroy(hFeat);
                OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
                return FALSE;
            }

            #define FORTPI      0.78539816339744833
            /* Latitude to Google-mercator northing */
            #define LAT_TO_NORTHING(lat) \
                6378137 * log(tan(FORTPI + .5 * (lat) / 180 * (4 * FORTPI)))

            nMinTileCol = (int)(((atof(papszTokens[0]) + 180) / 360) * (1 << nMaxLevel));
            nMaxTileCol = (int)(((atof(papszTokens[2]) + 180) / 360) * (1 << nMaxLevel));
            nMinTileRow = (int)(0.5 + ((LAT_TO_NORTHING(atof(papszTokens[1])) + MAX_GM) / (2* MAX_GM)) * (1 << nMaxLevel));
            nMaxTileRow = (int)(0.5 + ((LAT_TO_NORTHING(atof(papszTokens[3])) + MAX_GM) / (2* MAX_GM)) * (1 << nMaxLevel));

            bHasBounds = TRUE;

            CSLDestroy(papszTokens);

            OGR_F_Destroy(hFeat);
        }
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
    }

    if (!bHasBounds)
    {
        pszSQL = CPLSPrintf("SELECT min(tile_column), max(tile_column), "
                            "min(tile_row), max(tile_row) FROM tiles "
                            "WHERE zoom_level = %d", nMaxLevel);
        CPLDebug("MBTILES", "%s", pszSQL);
        hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
        if (hSQLLyr == NULL)
        {
            return FALSE;
        }

        hFeat = OGR_L_GetNextFeature(hSQLLyr);
        if (hFeat == NULL)
        {
            OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
            return FALSE;
        }

        if (OGR_F_IsFieldSet(hFeat, 0) &&
            OGR_F_IsFieldSet(hFeat, 1) &&
            OGR_F_IsFieldSet(hFeat, 2) &&
            OGR_F_IsFieldSet(hFeat, 3))
        {
            nMinTileCol = OGR_F_GetFieldAsInteger(hFeat, 0);
            nMaxTileCol = OGR_F_GetFieldAsInteger(hFeat, 1);
            nMinTileRow = OGR_F_GetFieldAsInteger(hFeat, 2);
            nMaxTileRow = OGR_F_GetFieldAsInteger(hFeat, 3);
            bHasBounds = TRUE;
        }

        OGR_F_Destroy(hFeat);
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
    }

    return bHasBounds;
}

/************************************************************************/
/*                        MBTilesCurlReadCbk()                          */
/************************************************************************/

/* We spy the data received by CURL for the initial request where we try */
/* to get a first tile to see its characteristics. We just need the header */
/* to determine that, so let's make VSICurl stop reading after we have found it */

static int MBTilesCurlReadCbk(CPL_UNUSED VSILFILE* fp,
                              void *pabyBuffer, size_t nBufferSize,
                              void* pfnUserData)
{
    const GByte abyPNGSig[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, /* PNG signature */
                                0x00, 0x00, 0x00, 0x0D, /* IHDR length */
                                0x49, 0x48, 0x44, 0x52  /* IHDR chunk */ };

    /* JPEG SOF0 (Start Of Frame 0) marker */
    const GByte abyJPEG1CompSig[] = { 0xFF, 0xC0, /* marker */
                                      0x00, 0x0B, /* data length = 8 + 1 * 3 */
                                      0x08,       /* depth : 8 bit */
                                      0x01, 0x00, /* width : 256 */
                                      0x01, 0x00, /* height : 256 */
                                      0x01        /* components : 1 */
                                    };
    const GByte abyJPEG3CompSig[] = { 0xFF, 0xC0, /* marker */
                                      0x00, 0x11, /* data length = 8 + 3 * 3 */
                                      0x08,       /* depth : 8 bit */
                                      0x01, 0x00, /* width : 256 */
                                      0x01, 0x00, /* width : 256 */
                                      0x03        /* components : 3 */
                                     };

    int i;
    for(i = 0; i < (int)nBufferSize - (int)sizeof(abyPNGSig); i++)
    {
        if (memcmp(((GByte*)pabyBuffer) + i, abyPNGSig, sizeof(abyPNGSig)) == 0 &&
            i + sizeof(abyPNGSig) + 4 + 4 + 1 + 1 < nBufferSize)
        {
            GByte* ptr = ((GByte*)(pabyBuffer)) + i + (int)sizeof(abyPNGSig);

            int nWidth;
            memcpy(&nWidth, ptr, 4);
            CPL_MSBPTR32(&nWidth);
            ptr += 4;

            int nHeight;
            memcpy(&nHeight, ptr, 4);
            CPL_MSBPTR32(&nHeight);
            ptr += 4;

            GByte nDepth = *ptr;
            ptr += 1;

            GByte nColorType = *ptr;
            CPLDebug("MBTILES", "PNG: nWidth=%d nHeight=%d depth=%d nColorType=%d",
                        nWidth, nHeight, nDepth, nColorType);

            int* pnBands = (int*) pfnUserData;
            *pnBands = -2;
            if (nWidth == 256 && nHeight == 256 && nDepth == 8)
            {
                if (nColorType == 0)
                    *pnBands = 1; /* Gray */
                else if (nColorType == 2)
                    *pnBands = 3; /* RGB */
                else if (nColorType == 3)
                {
                    /* This might also be a color table with transparency */
                    /* but we cannot tell ! */
                    *pnBands = -1;
                    return TRUE;
                }
                else if (nColorType == 4)
                    *pnBands = 2; /* Gray + alpha */
                else if (nColorType == 6)
                    *pnBands = 4; /* RGB + alpha */
            }

            return FALSE;
        }
    }

    for(i = 0; i < (int)nBufferSize - (int)sizeof(abyJPEG1CompSig); i++)
    {
        if (memcmp(((GByte*)pabyBuffer) + i, abyJPEG1CompSig, sizeof(abyJPEG1CompSig)) == 0)
        {
            CPLDebug("MBTILES", "JPEG: nWidth=%d nHeight=%d depth=%d nBands=%d",
                        256, 256, 8, 1);

            int* pnBands = (int*) pfnUserData;
            *pnBands = 1;

            return FALSE;
        }
        else if (memcmp(((GByte*)pabyBuffer) + i, abyJPEG3CompSig, sizeof(abyJPEG3CompSig)) == 0)
        {
            CPLDebug("MBTILES", "JPEG: nWidth=%d nHeight=%d depth=%d nBands=%d",
                        256, 256, 8, 3);

            int* pnBands = (int*) pfnUserData;
            *pnBands = 3;

            return FALSE;
        }
    }

    return TRUE;
}

/************************************************************************/
/*                        MBTilesGetBandCount()                         */
/************************************************************************/

static
int MBTilesGetBandCount(OGRDataSourceH &hDS, CPL_UNUSED int nMinLevel, int nMaxLevel,
                        int nMinTileRow, int nMaxTileRow,
                        int nMinTileCol, int nMaxTileCol)
{
    OGRLayerH hSQLLyr;
    OGRFeatureH hFeat;
    const char* pszSQL;
    VSILFILE* fpCURLOGR = NULL;
    int bFirstSelect = TRUE;

    int nBands = -1;

    /* Small trick to get the VSILFILE associated with the OGR SQLite */
    /* DB */
    CPLString osDSName(OGR_DS_GetName(hDS));
    if (strncmp(osDSName.c_str(), "/vsicurl/", 9) == 0)
    {
        CPLErrorReset();
        CPLPushErrorHandler(CPLQuietErrorHandler);
        hSQLLyr = OGR_DS_ExecuteSQL(hDS, "GetVSILFILE()", NULL, NULL);
        CPLPopErrorHandler();
        CPLErrorReset();
        if (hSQLLyr != NULL)
        {
            hFeat = OGR_L_GetNextFeature(hSQLLyr);
            if (hFeat)
            {
                if (OGR_F_IsFieldSet(hFeat, 0))
                {
                    const char* pszPointer = OGR_F_GetFieldAsString(hFeat, 0);
                    fpCURLOGR = (VSILFILE* )CPLScanPointer( pszPointer, strlen(pszPointer) );
                }
                OGR_F_Destroy(hFeat);
            }
            OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
        }
    }

    pszSQL = CPLSPrintf("SELECT tile_data FROM tiles WHERE "
                            "tile_column = %d AND tile_row = %d AND zoom_level = %d",
                            (nMinTileCol  + nMaxTileCol) / 2,
                            (nMinTileRow  + nMaxTileRow) / 2,
                            nMaxLevel);
    CPLDebug("MBTILES", "%s", pszSQL);

    if (fpCURLOGR)
    {
        /* Install a spy on the file connexion that will intercept */
        /* PNG or JPEG headers, to interrupt their downloading */
        /* once the header is found. Speeds up dataset opening. */
        CPLErrorReset();
        VSICurlInstallReadCbk(fpCURLOGR, MBTilesCurlReadCbk, &nBands, TRUE);

        CPLErrorReset();
        CPLPushErrorHandler(CPLQuietErrorHandler);
        hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
        CPLPopErrorHandler();

        VSICurlUninstallReadCbk(fpCURLOGR);

        /* Did the spy intercept something interesting ? */
        if (nBands != -1)
        {
            CPLErrorReset();

            OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
            hSQLLyr = NULL;

            /* Re-open OGR SQLite DB, because with our spy we have simulated an I/O error */
            /* that SQLite will have difficulies to recover within the existing connection */
            /* No worry ! This will be fast because the /vsicurl/ cache has cached the already */
            /* read blocks */
            OGRReleaseDataSource(hDS);
            hDS = OGROpen(osDSName.c_str(), FALSE, NULL);
            if (hDS == NULL)
                return -1;

            /* Unrecognized form of PNG. Error out */
            if (nBands <= 0)
                return -1;

            return nBands;
        }
        else if (CPLGetLastErrorType() == CE_Failure)
        {
            CPLError(CE_Failure, CPLGetLastErrorNo(), "%s", CPLGetLastErrorMsg());
        }
    }
    else
    {
        hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
    }

    while( TRUE )
    {
        if (hSQLLyr == NULL && bFirstSelect)
        {
            bFirstSelect = FALSE;
            pszSQL = CPLSPrintf("SELECT tile_data FROM tiles WHERE "
                                "zoom_level = %d LIMIT 1", nMaxLevel);
            CPLDebug("MBTILES", "%s", pszSQL);
            hSQLLyr = OGR_DS_ExecuteSQL(hDS, pszSQL, NULL, NULL);
            if (hSQLLyr == NULL)
                return -1;
        }

        hFeat = OGR_L_GetNextFeature(hSQLLyr);
        if (hFeat == NULL)
        {
            OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
            hSQLLyr = NULL;
            if( !bFirstSelect )
                return -1;
        }
        else
            break;
    }

    CPLString osMemFileName;
    osMemFileName.Printf("/vsimem/%p", hSQLLyr);

    int nDataSize = 0;
    GByte* pabyData = OGR_F_GetFieldAsBinary(hFeat, 0, &nDataSize);

    VSIFCloseL(VSIFileFromMemBuffer( osMemFileName.c_str(), pabyData,
                                    nDataSize, FALSE));

    GDALDatasetH hDSTile =
        GDALOpenInternal(osMemFileName.c_str(), GA_ReadOnly, apszAllowedDrivers);
    if (hDSTile == NULL)
    {
        VSIUnlink(osMemFileName.c_str());
        OGR_F_Destroy(hFeat);
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
        return -1;
    }

    nBands = GDALGetRasterCount(hDSTile);

    if ((nBands != 1 && nBands != 3 && nBands != 4) ||
        GDALGetRasterXSize(hDSTile) != 256 ||
        GDALGetRasterYSize(hDSTile) != 256 ||
        GDALGetRasterDataType(GDALGetRasterBand(hDSTile, 1)) != GDT_Byte)
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported tile characteristics");
        GDALClose(hDSTile);
        VSIUnlink(osMemFileName.c_str());
        OGR_F_Destroy(hFeat);
        OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
        return -1;
    }

    GDALColorTableH hCT = GDALGetRasterColorTable(GDALGetRasterBand(hDSTile, 1));
    if (nBands == 1 && hCT != NULL)
    {
        nBands = 3;
        if( GDALGetColorEntryCount(hCT) > 0 )
        {
            /* Typical of paletted PNG with transparency */
            const GDALColorEntry* psEntry = GDALGetColorEntry( hCT, 0 );
            if( psEntry->c4 == 0 )
                nBands = 4;
        }
    }

    GDALClose(hDSTile);
    VSIUnlink(osMemFileName.c_str());
    OGR_F_Destroy(hFeat);
    OGR_DS_ReleaseResultSet(hDS, hSQLLyr);

    return nBands;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset* MBTilesDataset::Open(GDALOpenInfo* poOpenInfo)
{
    CPLString osFileName;
    CPLString osTableName;

    if (!Identify(poOpenInfo))
        return NULL;

    if (OGRGetDriverCount() == 0)
        OGRRegisterAll();

/* -------------------------------------------------------------------- */
/*      Open underlying OGR DB                                          */
/* -------------------------------------------------------------------- */

    OGRDataSourceH hDS = OGROpen(poOpenInfo->pszFilename, FALSE, NULL);

    MBTilesDataset* poDS = NULL;

    if (hDS == NULL)
        goto end;

/* -------------------------------------------------------------------- */
/*      Build dataset                                                   */
/* -------------------------------------------------------------------- */
    {
        CPLString osMetadataTableName, osRasterTableName;
        CPLString osSQL;
        OGRLayerH hMetadataLyr, hRasterLyr;
        OGRFeatureH hFeat;
        int nResolutions;
        int iBand, nBands, nBlockXSize, nBlockYSize;
        GDALDataType eDataType;
        OGRLayerH hSQLLyr = NULL;
        int nMinLevel = -1, nMaxLevel = -1;
        int nMinTileRow = 0, nMaxTileRow = 0, nMinTileCol = 0, nMaxTileCol = 0;
        int bHasBounds = FALSE;
        int bHasMinMaxLevel = FALSE;
        int bHasMap;
        const char* pszBandCount;

        osMetadataTableName = "metadata";

        hMetadataLyr = OGR_DS_GetLayerByName(hDS, osMetadataTableName.c_str());
        if (hMetadataLyr == NULL)
            goto end;

        osRasterTableName += "tiles";

        hRasterLyr = OGR_DS_GetLayerByName(hDS, osRasterTableName.c_str());
        if (hRasterLyr == NULL)
            goto end;

        bHasMap = OGR_DS_GetLayerByName(hDS, "map") != NULL;
        if (bHasMap)
        {
            bHasMap = FALSE;

            hSQLLyr = OGR_DS_ExecuteSQL(hDS, "SELECT type FROM sqlite_master WHERE name = 'tiles'", NULL, NULL);
            if (hSQLLyr != NULL)
            {
                hFeat = OGR_L_GetNextFeature(hSQLLyr);
                if (hFeat)
                {
                    if (OGR_F_IsFieldSet(hFeat, 0))
                    {
                        bHasMap = strcmp(OGR_F_GetFieldAsString(hFeat, 0),
                                         "view") == 0;
                        if (!bHasMap)
                        {
                            CPLDebug("MBTILES", "Weird! 'tiles' is not a view, but 'map' exists");
                        }
                    }
                    OGR_F_Destroy(hFeat);
                }
                OGR_DS_ReleaseResultSet(hDS, hSQLLyr);
            }
        }

/* -------------------------------------------------------------------- */
/*      Get minimum and maximum zoom levels                             */
/* -------------------------------------------------------------------- */

        bHasMinMaxLevel = MBTilesGetMinMaxZoomLevel(hDS, bHasMap,
                                                    nMinLevel, nMaxLevel);

        if (bHasMinMaxLevel && (nMinLevel < 0 || nMinLevel > nMaxLevel))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Inconsistant values : min(zoom_level) = %d, max(zoom_level) = %d",
                     nMinLevel, nMaxLevel);
            goto end;
        }

        if (bHasMinMaxLevel && nMaxLevel > 22)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "zoom_level > 22 not supported");
            goto end;
        }

        if (!bHasMinMaxLevel)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find min and max zoom_level");
            goto end;
        }

/* -------------------------------------------------------------------- */
/*      Get bounds                                                      */
/* -------------------------------------------------------------------- */

        bHasBounds = MBTilesGetBounds(hDS, nMinLevel, nMaxLevel,
                                      nMinTileRow, nMaxTileRow,
                                      nMinTileCol, nMaxTileCol);
        if (!bHasBounds)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find min and max tile numbers");
            goto end;
        }

/* -------------------------------------------------------------------- */
/*      Get number of bands                                             */
/* -------------------------------------------------------------------- */

        pszBandCount = CPLGetConfigOption("MBTILES_BAND_COUNT", "-1");
        nBands = atoi(pszBandCount);

        if( ! (nBands == 1 || nBands == 3 || nBands == 4) )
        {
            nBands = MBTilesGetBandCount(hDS, nMinLevel, nMaxLevel,
                                         nMinTileRow, nMaxTileRow,
                                         nMinTileCol, nMaxTileCol);
            if (nBands < 0)
                goto end;
        }

/* -------------------------------------------------------------------- */
/*      Set dataset attributes                                          */
/* -------------------------------------------------------------------- */

        poDS = new MBTilesDataset();
        poDS->eAccess = poOpenInfo->eAccess;
        poDS->hDS = hDS;

        /* poDS will release it from now */
        hDS = NULL;

/* -------------------------------------------------------------------- */
/*      Store resolutions                                               */
/* -------------------------------------------------------------------- */
        poDS->nMinLevel = nMinLevel;
        poDS->nResolutions = nResolutions = nMaxLevel - nMinLevel;

/* -------------------------------------------------------------------- */
/*      Round bounds to the lowest zoom level                           */
/* -------------------------------------------------------------------- */

        //CPLDebug("MBTILES", "%d %d %d %d", nMinTileCol, nMinTileRow, nMaxTileCol, nMaxTileRow);
        nMinTileCol = (int)(1.0 * nMinTileCol / (1 << nResolutions)) * (1 << nResolutions);
        nMinTileRow = (int)(1.0 * nMinTileRow / (1 << nResolutions)) * (1 << nResolutions);
        nMaxTileCol = (int)ceil(1.0 * nMaxTileCol / (1 << nResolutions)) * (1 << nResolutions);
        nMaxTileRow = (int)ceil(1.0 * nMaxTileRow / (1 << nResolutions)) * (1 << nResolutions);

/* -------------------------------------------------------------------- */
/*      Compute raster size, geotransform and projection                */
/* -------------------------------------------------------------------- */
        poDS->nMinTileCol = nMinTileCol;
        poDS->nMinTileRow = nMinTileRow;
        poDS->nRasterXSize = (nMaxTileCol-nMinTileCol) * 256;
        poDS->nRasterYSize = (nMaxTileRow-nMinTileRow) * 256;

        nBlockXSize = nBlockYSize = 256;
        eDataType = GDT_Byte;

/* -------------------------------------------------------------------- */
/*      Add bands                                                       */
/* -------------------------------------------------------------------- */

        for(iBand=0;iBand<nBands;iBand++)
            poDS->SetBand(iBand+1, new MBTilesBand(poDS, iBand+1, eDataType,
                                                  nBlockXSize, nBlockYSize));

/* -------------------------------------------------------------------- */
/*      Add overview levels as internal datasets                        */
/* -------------------------------------------------------------------- */
        if (nResolutions >= 1)
        {
            poDS->papoOverviews = (MBTilesDataset**)
                CPLCalloc(nResolutions, sizeof(MBTilesDataset*));
            int nLev;
            for(nLev=1;nLev<=nResolutions;nLev++)
            {
                poDS->papoOverviews[nLev-1] = new MBTilesDataset(poDS, nLev);

                for(iBand=0;iBand<nBands;iBand++)
                {
                    poDS->papoOverviews[nLev-1]->SetBand(iBand+1,
                        new MBTilesBand(poDS->papoOverviews[nLev-1], iBand+1, eDataType,
                                           nBlockXSize, nBlockYSize));
                }
            }
        }

        poDS->SetMetadata(poDS->papszImageStructure, "IMAGE_STRUCTURE");

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
        poDS->SetDescription( poOpenInfo->pszFilename );

        if ( !EQUALN(poOpenInfo->pszFilename, "/vsicurl/", 9) )
            poDS->TryLoadXML();
        else
        {
            poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
        }
    }

end:
    if (hDS)
        OGRReleaseDataSource(hDS);

    return poDS;
}

/************************************************************************/
/*                       GDALRegister_MBTiles()                         */
/************************************************************************/

void GDALRegister_MBTiles()

{
    GDALDriver  *poDriver;

    if (! GDAL_CHECK_VERSION("MBTiles driver"))
        return;

    if( GDALGetDriverByName( "MBTiles" ) == NULL )
    {
        poDriver = new GDALDriver();

        poDriver->SetDescription( "MBTiles" );
        poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
                                   "MBTiles" );
        poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
                                   "frmt_mbtiles.html" );
        poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "mbtiles" );

        poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

        poDriver->pfnOpen = MBTilesDataset::Open;
        poDriver->pfnIdentify = MBTilesDataset::Identify;

        GetGDALDriverManager()->RegisterDriver( poDriver );
    }
}
