[GIS] Offline SLIPPY Map (Tiles) Database for Leaflet

databasehtml5leaflet

How can create an offline cache of a large number of SLIPPY tile (PNG, 256×256) inside a web browser and then display them from that offline cache. For example, can one use HTML5 IndexedDB as an storage location for offline map tiles?

Best Answer

Here is one way to do that:

Components used:

  1. Leaflet
  2. PouchDB (a framework that uses indexedDB in your browser)
  3. XHR2 (XMLHTTP) to fetch the files, since JQUERY does not yet support binary blob XMLHTTP
  4. KendoUI (an HTML5 web control library)

Running the Demo:

http://codepen.io/DrYSG/pen/hpqoD

  1. Delete any old DB (Press the Delete Button)
  2. Reload the page
  3. Wait till Status is READY
  4. Press [Download Tiles]

This should load 341 blobs from Google Drive (NASA Blue World, in EPSG 3857 projection). It will show a sample image when it is done. Sorry, FireFox has a poor implementation of IDB, so it is very slow (FF uses SQLLite a relational database, and IDB is no-SQL).

Once you have done that, then try:

http://codepen.io/DrYSG/pen/oEItn

Which will show that there is a manifest, and 341 tiles, and should overlay the Tiles from PouchDB on your map. You can use the layer control on the left side to turn off the PouchDB layer.

The Algorithm it uses is as follows:

  • Test for presence of XHR2, IndexedDB, and Chrome (which does not have binary blobs, but Base64). and show this status info
  • Fetch a JSON manifest of PNG tiles from GoogleDrive (I have 341 PNG tiles, each 256x256 in size). The manifest lists their names and sizes.
  • Store the JSON manifest in the DB
  • MVVM and UI controls are from KendoUI http://www.kendoui.com/ (This time I did not use their superb grid control, since I wanted to explore CSS3 Grid Styling).
  • XHR2 is from: https://github.com/p-m-p/xhr2-lib/blob/master/test/index.html
  • I am using the nightly build of PouchDB
  • All files PNG file are stored on Google Drive (NASA Blue Marble). \
  • I created the tile pyramid with Safe FME 2013 Desktop. http://www.safe.com/fme/fme-technology/fme-desktop/overview

Before Pressing the button "Download Tiles" Check to see that the manifest has been stored in the DB, and that 341 tiles are present. If you already ran the test then your PouchDB is going to already have tiles in the DB, and you will get errors. In that case, Press Delete DB, and then reload the page.

When you press "Download Tiles" The following steps occur:

  • The Manifest is fetched from the DB
  • A XHR2 Fetch loop grabs the PNG blobs from GoogleDrive.
  • As loop runs, it starts saving the Blobs into PouchDB.
  • Note: Fetching and Saving are on overlapped threads (think co-routines), since those (fetch & store) operations are running async on separate threads.
  • When the Fetch loop is done it reports the elapsed time.
  • Note: This time is not pure Fetch work, since PouchDB putAttachments() are running in parallel.
  • When all the Tiles are saved, it will report full statistics, and display a tile fetched from PouchDB.
  • The Blob-Rate the total fetch and store time per each png tile

Leaflet Map Display

The key idea in getting Leaflet to display maps is to use Ishmael Smyrnow's Functional Tile plug-in: https://github.com/ismyrnow/Leaflet.functionaltilelayer. You will need to use JQUERY DEFFER to allow the PouchDB to asynchronously fetch the tiles from the DB. You also need to understand CreateURL() to create a binary blob url which is used in the tag to show the raster image.

I create my database tile layer this way:

function dbMap() {
    var label = {};
    var settings = {
        tms: false,
        opacity: 0.6,
        errorTileUrl: "./images/EmptyTile.png"
    };
    var layer = new L.TileLayer.Functional(function (view) {
        var deferred = new jQuery.Deferred(); // this is what gets returned
        var id = view.zoom + "-" + view.tile.column + "-" + view.tile.row;
        DB.getAttachment(id + "/pic", function (err, response) {
            if (err) {
                console.warn("Could find blob id: ", id, " error: ", err.error, " reason: ", err.reason, " status: ", err.status);
            } else {
                var blob = response;
                var imgURL = URL.createObjectURL(blob);
                deferred.resolve(imgURL); // let leaflet know we're done, and give it the URL
                URL.revokeObjectURL(imgURL); // not sure if we're revoking it too soon here
            }
        });
        return deferred;
    }, settings);
    MAP.addLayer(layer);
    label["Pouch DB"] = layer;
    $.extend(OverLayers, label);
}

Notes

Right now Chrome is running fine. Firefox is very slow. I found this out a few months when I did a native IndexedDB API. So I don't think this is a PouchDB issue. Probably more due to FireFox using SQLlite which is a relational approach to a no-SQL DB.

IE10 is not working. This is sad, since my prior tests with IE10 shows it has a fantastically fast IndexedDB solution: https://stackoverflow.com/questions/14113278/storing-image-data-for-offline-web-application-client-side-storage-database

Related Question