During my research, I came across a blog post that mirrored what I wanted to do. Scott Sheri had created an offline map mobile app using PhoneGap, Leaflet and TileMill which was very similar to what I wanted to accomplish. Seeing how Scott had blazed the trail for me, I decided to follow his path, only using Titanium instead of PhoneGap. I knew there would be a lot of differences in implementation, but it gave me a strong starting point.
Generating the Raster Images with TileMill Years ago, I spent a lot of time working with GIS data, so I have a very strong understanding of how to produce and display spatial data. I chose TileMill to produce my raster layer because it was easy use and readily saved the images in the mbtiles format. The beauty of this is that an .mbtiles file is nothing more than a SQLite database. I could imbed my generated .mbtiles in my application and query it natively using Titanium.
Pay no attention to my horrible choice of colors. I won’t go into details about how I produced this map. TileMill has some pretty good tutorials on their website. Needless to say, what we’re concerned about here is how it’s formatted when it’s exported.
The mbtiles files produced by TileMill are simple SQLite databases. The schema can be found on the MapBox website. To view the data within the file, I’m using SQLite Manager add-on for Firefox.
Now that we have our raster layer, we can work on getting Leaflet to work in a Titanium webview.
I overrode three of the L.TileLayer methods (_loadTile(), _createTile(), and _addTilesFromCenterOut()) and added one new method (_onLoad).
_createTile() – Originally, the Leaflet tiles (which are simply img html elements) didn’t have an id attribute that I could reference. This was going to be a necessity because the fireEvent() and addEventListener() methods are not asynchronous. I was going to have to reference the tile by id when the Titanium response for a request for a tile was returned. I simply created a random guid and set it as the id.
_loadTile – I removed the setting of the tile’s src attribute in this method. I placed the relevant information needed for requesting tiles (i.e. x, y, z, and id) in an array named aUpdate. When loaded, the map will consist of blank tiles and they will be populated by Titanium through a later request.
_addTilesFromCenterOut() – Leaflet uses a document fragment to load the img tiles to the DOM. I had to wait until this document fragment was added before I could send the request to Titanium to query for the actual image data. At the end of this method, I just added a call to _onLoad().
_onLoad() – This new method will fire once the blank map tiles are added to the DOM. It calls a Titanium application level event named getMbTiles. It passes a JSON form of the aUpdates array.
The getMbTiles event triggers the query of the database for the needed tiles. SELECT tile_data FROM images INNER JOIN map ON images.tile_id = map.tile_id WHERE zoom_level = ? AND tile_column = ? AND tile_row = ? These tiles are then base64 encoded and sent back to the webview using a custom event named receiveTileUrl. receiveTileUrl sets the src of the various tiles to the appropriate image data.
Source Files Leaflet Demo (does not include mbtiles file) .zip MasterView.js map.html
The Offline Map! Ta-da! The images are now shown on the Leaflet map imbedded in a Titanium webview. By turning on airplane mode will show that all the data and code is hosted locally and that no Internet connection is required to use this map.
Next Steps Now, this is just a proof-of-concept and is not production ready. The current obstacle is the size of the .mbtiles file. My simple demonstration map was about 4mb and contained very little visual data. A more robust raster layer can easily exceed the 50mb limit imposed by the Apple App Store. A strategy needs to be developed where the end user can pick and choose what data they will need in the field and download it accordingly through the app. But that’s another blog entry…