diff --git a/lib/mbtiles.js b/lib/mbtiles.js index 6d352dc..deaed98 100644 --- a/lib/mbtiles.js +++ b/lib/mbtiles.js @@ -3,6 +3,7 @@ var _ = require('underscore'), Step = require('step'), crypto = require('crypto'), zlib = require('zlib'), + get = require('get'), sqlite3 = require('sqlite3'); // MBTiles @@ -10,8 +11,16 @@ var _ = require('underscore'), // MBTiles class for doing common operations (schema setup, tile reading, // insertion, etc.) function MBTiles(filename, callback) { + var mbtiles = this; this.filename = filename; - this.db = new sqlite3.Database(filename, callback); + this.db = new sqlite3.Database(filename, function(err) { + if (err) return callback(err); + mbtiles.metadata('online', function(err, value) { + // Ignore not-existant key. + mbtiles.online = value; + if (callback) callback(null); + }); + }); }; // Retrieve the schema of the current mbtiles database and inform the caller of @@ -208,16 +217,39 @@ MBTiles.prototype.insertGridTiles = function(map, callback) { // - @param {Number} z tile z coordinate. // - @param {Function} callback MBTiles.prototype.tile = function(x, y, z, callback) { + var mbtiles = this; this.db.get('SELECT tile_data FROM tiles WHERE ' + 'zoom_level = ? AND tile_column = ? AND tile_row = ?', z, x, y, function(err, row) { if (err) callback(err); - else if (!row || !row.tile_data) callback(new Error('Tile does not exist')); + else if (!row || !row.tile_data) mbtiles.onlineTile(x, y, z, callback); else callback(null, row.tile_data); }); }; +// Requests a tile from an online source and cache it to the database. +// +// - @param {Number} x tile x coordinate. +// - @param {Number} y tile y coordinate. +// - @param {Number} z tile z coordinate. +// - @param {Function} callback +MBTiles.prototype.onlineTile = function(x, y, z, callback) { + var mbtiles = this; + if (!this.online) { + return callback(new Error('Tile does not exist')); + } + + var url = this.online.replace(/%x/g, x).replace(/%y/g, Math.pow(2, z) - 1 - y).replace(/%z/g, z); + new get(url).asBuffer(function(err, result) { + if (err) return callback(new Error(err)); + + // Cache image. + mbtiles.insertTiles([{ x: x, y: y, z: z, data: result }], function() {}); + callback(null, result); + }); +}; + // Select a grid and its data from an mbtiles database. // // - @param {Number} x tile x coordinate diff --git a/package.json b/package.json index 76b00f8..66194f7 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "optimist": "0.2.x", "sqlite3": "2.0.x", "step": "0.0.x", + "get": "0.3.x", "underscore": "1.1.x", "zlib": "1.0.x" }, diff --git a/test/fixtures/online.mbtiles b/test/fixtures/online.mbtiles new file mode 100644 index 0000000..07d5651 Binary files /dev/null and b/test/fixtures/online.mbtiles differ diff --git a/test/online.test.js b/test/online.test.js new file mode 100644 index 0000000..0b0b4b3 --- /dev/null +++ b/test/online.test.js @@ -0,0 +1,61 @@ +var assert = require('assert'); +var MBTiles = require('..').MBTiles; + +var fixture = __dirname + '/fixtures/online.mbtiles'; + +exports['get online metadata'] = function(beforeExit) { + var completion = {}; + + var mbtiles = new MBTiles(fixture); + mbtiles.metadata('name', function(err, value) { if (err) throw err; completion.name = value; }); + mbtiles.metadata('type', function(err, value) { if (err) throw err; completion.type = value; }); + mbtiles.metadata('description', function(err, value) { if (err) throw err; completion.description = value; }); + mbtiles.metadata('version', function(err, value) { if (err) throw err; completion.version = value; }); + mbtiles.metadata('formatter', function(err, value) { if (err) throw err; completion.formatter = value; }); + mbtiles.metadata('bounds', function(err, value) { if (err) throw err; completion.bounds = value; }); + mbtiles.metadata('online', function(err, value) { if (err) throw err; completion.online = value; }); + mbtiles.metadata('invalid', function(err, value) { completion.invalid = err.message; }); + + beforeExit(function() { + assert.deepEqual(completion, { + name: 'MapQuest streets', + type: 'baselayer', + description: 'MapQuest’s OpenStreetMap based street level tiles.', + version: '1.0.0', + formatter: null, + bounds: '-180,-90,180,90', + online: 'http://otile1.mqcdn.com/tiles/1.0.0/osm/%z/%x/%y.png', + invalid: 'Key does not exist' + }); + }); +}; + + +exports['get online tiles'] = function(beforeExit) { + var status = { + success: 0, + error: 0 + }; + + var mbtiles = new MBTiles(fixture); + + mbtiles.db.serialize(function() { + mbtiles.db.run("DELETE FROM map"); + mbtiles.db.run("DELETE FROM images"); + }); + + mbtiles.tile(0, 0, 0, function(err, tile) { + if (err) throw new Error(err); + status.success++; + }); + + mbtiles.tile(0, 0, 18, function(err, tile) { + if (err) throw new Error(err); + status.success++; + }); + + beforeExit(function() { + assert.equal(status.success, 2); + assert.equal(status.error, 0); + }); +}; \ No newline at end of file