Saturday, February 16, 2013

Offline Mapping in HTML5 Mobile Apps

One of our top feature requests for Plot Hound has been to add satellite/terrain layers to the cruise maps.  We couldn't just drop in normal Google or Bing Maps because we needed the maps to be available out in the woods with no data connection.

There are fairly straightforward ways to do this in native apps (like offline map caching in the Google Maps Android app and the MapBox iOS SDK), but there aren't great solutions for HTML5 apps built with PhoneGap.  And you have to stay away from using Google's map tiles because they have a pretty restrictive terms of service (MapBox seems to be the current leader in programmatic access to raw map tiles).  The best solution I've seen was by Scott Davis who used MapBox's TileMill to create an mbTiles SQLite database which he then accessed through the Cordova SQLite plugin and a custom Leaflet TileLayer.  The cool thing about this solution is that it leverages SQLite's built-in compression to store map tiles in a really efficient way.

But... there was a problem.  We're heavy users of PhoneGap Build to compile our PhoneGap app, and PhoneGap Build only supports a small set of plugins right now.  And no, the SQLite plugin isn't one of them.

Still, it seemed like I could take the basic structure of Scott's solution but just store the raw tiles locally with the PhoneGap Files API and then point a standard Leaflet TileLayer at the local directory.  My only concern was that the uncompressed raw tiles would be too big to reasonably download.  However, for our use-case (high-zoom on a relatively small area, low-zoom on the surrounding area), it seemed like we might be able to get away with it.  After a quick test in Python, I found that even doing zoom levels 3-17, we only were downloading about 600 tiles totaling about 6MB.  Good enough!

After hacking on it for two days, I came up with a working proof of concept.  The source is available on GitHub.  The instructions and technical details are in the file.  I hope this code is a helpful start for developers trying to add offline mapping to their own apps - it was certainly a fun learning experience for me!

The final results: