diff --git a/lib/mbtiles.js b/lib/mbtiles.js index 6e4e9f4..afffc9f 100644 --- a/lib/mbtiles.js +++ b/lib/mbtiles.js @@ -30,17 +30,17 @@ function MBTiles(uri, callback) { this.filename = uri.pathname; Step(function() { - mbtiles.db = new sqlite3.cached.Database(mbtiles.filename, this) + mbtiles._db = new sqlite3.cached.Database(mbtiles.filename, this) }, function(err) { if (err) return callback(err); fs.stat(mbtiles.filename, this); }, function(err, stat) { if (err) return callback(err); - mbtiles.stat = stat; - mbtiles.exists('map', this); + mbtiles._stat = stat; + mbtiles._exists('map', this); }, function(err, exists) { if (err) return callback(err); - else if (!exists) mbtiles.setup(this); + else if (!exists) mbtiles._setup(this); else this(null); }, function(err) { if (err) callback(err); @@ -78,31 +78,31 @@ MBTiles.findID = function(filepath, id, callback) { // Retrieve the schema of the current mbtiles database and inform the caller of // whether the specified table exists. -MBTiles.prototype.exists = function(table, callback) { +MBTiles.prototype._exists = function(table, callback) { if (typeof callback !== 'function') callback = noop; - if (this.schema) { - return callback(null, _(this.schema).include(table)); + if (this._schema) { + return callback(null, _(this._schema).include(table)); } else { - this.schema = []; - this.db.all( + this._schema = []; + this._db.all( 'SELECT name FROM sqlite_master WHERE type IN (?, ?)', 'table', 'view', function(err, rows) { if (err) return callback(err); - this.schema = _(rows).pluck('name'); - this.exists(table, callback); + this._schema = _(rows).pluck('name'); + this._exists(table, callback); }.bind(this) ); } }; // DB integrity check. -MBTiles.prototype.integrity = function(callback) { +MBTiles.prototype._integrity = function(callback) { if (typeof callback !== 'function') callback = noop; - this.db.get('PRAGMA quick_check(1)', function(err, row) { + this._db.get('PRAGMA quick_check(1)', function(err, row) { if (!(row && row.integrity_check && row.integrity_check === 'ok')) { callback(new Error('Corrupted database.')); } else { @@ -114,14 +114,14 @@ MBTiles.prototype.integrity = function(callback) { // Setup schema, indices, views for a new mbtiles database. // Sets the synchronous flag to OFF for (much) faster inserts. // See http://www.sqlite3.org/pragma.html#pragma_synchronous -MBTiles.prototype.setup = function(callback) { +MBTiles.prototype._setup = function(callback) { if (typeof callback !== 'function') callback = noop; fs.readFile(__dirname + '/schema.sql', 'utf8', function(err, sql) { if (err) return callback(err); - this.db.serialize(function() { - this.db.run('PRAGMA synchronous = 0'); - this.db.exec(sql, callback); + this._db.serialize(function() { + this._db.run('PRAGMA synchronous = 0'); + this._db.exec(sql, callback); }.bind(this)); }.bind(this)); }; @@ -132,14 +132,14 @@ MBTiles.prototype.setup = function(callback) { // - `objects` Array. Objects to be inserted, where each object attribute // has key/value pairs as a hash corresponding to column name and row value. // - `callback` Function. -MBTiles.prototype.insert = function(table, objects, callback) { +MBTiles.prototype._insert = function(table, objects, callback) { if (typeof callback !== 'function') callback = noop; if (!objects.length) return callback(null); var keys = _(objects[0]).keys(); var placeholders = []; _(keys).each(function(k) { placeholders.push('?'); }); - var stmt = this.db.prepare( + var stmt = this._db.prepare( 'INSERT OR IGNORE INTO ' + table + ' ' + '(' + keys.join(',') + ') ' + 'VALUES (' + placeholders.join(',') + ')' @@ -154,23 +154,13 @@ MBTiles.prototype.insert = function(table, objects, callback) { // // - @param {Object} metadata key, value hash of metadata to be inserted. // - @param {Function} callback -MBTiles.prototype.insertMetadata = function(data, callback) { +MBTiles.prototype._insertMetadata = function(data, callback) { if (typeof callback !== 'function') callback = noop; var metadata = _(data).map(function(value, key) { return { name: key, value: value}; }); - this.insert('metadata', metadata, callback); -}; - -// Insert a tile. Scheme is XYZ. -MBTiles.prototype.putTile = function(z, x, y, data, callback) { - if (typeof callback !== 'function') callback = noop; - - // Flip Y coordinate because MBTiles files are TMS. - y = Math.pow(2, z) - 1 - y; - - this.insertTiles([ { z: z, x: x, y: y, data: data }], callback); + this._insert('metadata', metadata, callback); }; // Insert a set of tiles into an mbtiles database. Scheme is TMS. @@ -178,7 +168,7 @@ MBTiles.prototype.putTile = function(z, x, y, data, callback) { // - @param {Array} renders array of images to be inserted. Each item should // be an object of the form { z: z, x: x, y: y, data: [Image buffer] }. // - @param {Function} callback -MBTiles.prototype.insertTiles = function(data, callback) { +MBTiles.prototype._insertTiles = function(data, callback) { var that = this, map = [], images = [], @@ -202,8 +192,8 @@ MBTiles.prototype.insertTiles = function(data, callback) { Step( function() { var group = this.group(); - that.insert('images', images, group()); - that.insert('map', map, group()); + that._insert('images', images, group()); + that._insert('map', map, group()); }, callback ); @@ -214,7 +204,7 @@ MBTiles.prototype.insertTiles = function(data, callback) { // - @param {Array} renders array of grids to be inserted. Each item should // be an object of the form { z: z, x: x, y: y, data: [Image buffer], keys: [] }. // - @param {Function} callback -MBTiles.prototype.insertGrids = function(data, callback) { +MBTiles.prototype._insertGrids = function(data, callback) { if (typeof callback !== 'function') callback = noop; var that = this, @@ -256,10 +246,10 @@ MBTiles.prototype.insertGrids = function(data, callback) { Step( function() { var group = this.group(); - that.insert('grid_utfgrid', grids, group()); - that.insert('grid_key', grid_keys, group()); - that.insert('keymap', features, group()); - that.insertGridTiles(map, group()); + that._insert('grid_utfgrid', grids, group()); + that._insert('grid_key', grid_keys, group()); + that._insert('keymap', features, group()); + that._insertGridTiles(map, group()); }, callback ); @@ -269,10 +259,10 @@ MBTiles.prototype.insertGrids = function(data, callback) { // // - @param {Object} tile tile object to be inserted. // - @param {Function} callback -MBTiles.prototype.insertGridTiles = function(map, callback) { +MBTiles.prototype._insertGridTiles = function(map, callback) { if (typeof callback !== 'function') callback = noop; - var stmt = this.db.prepare('UPDATE OR REPLACE map SET grid_id = ? WHERE ' + + var stmt = this._db.prepare('UPDATE OR REPLACE map SET grid_id = ? WHERE ' + ' zoom_level = ? AND tile_column = ? AND tile_row = ?'); for (var i = 0; i < map.length; i++) { @@ -300,7 +290,7 @@ MBTiles.prototype.getTile = function(z, x, y, callback) { y = Math.pow(2, z) - 1 - y; var mbtiles = this; - this.db.get('SELECT tile_data FROM tiles WHERE ' + + this._db.get('SELECT tile_data FROM tiles WHERE ' + 'zoom_level = ? AND tile_column = ? AND tile_row = ?', z, x, y, function(err, row) { @@ -311,8 +301,8 @@ MBTiles.prototype.getTile = function(z, x, y, callback) { } else { var options = { 'Content-Type': MBTiles.utils.getMimeType(row.tile_data), - 'Last-Modified': mbtiles.stat.mtime, - 'ETag': mbtiles.stat.size + '-' + Number(mbtiles.stat.mtime) + 'Last-Modified': mbtiles._stat.mtime, + 'ETag': mbtiles._stat.size + '-' + Number(mbtiles._stat.mtime) }; return callback(null, row.tile_data, options); } @@ -334,12 +324,12 @@ MBTiles.prototype.getGrid = function(z, x, y, callback) { var that = this; Step( function() { - that.db.get('SELECT grid FROM grids WHERE ' + + that._db.get('SELECT grid FROM grids WHERE ' + 'zoom_level = ? AND tile_column = ? AND tile_row = ?', z, x, y, this.parallel() ); - that.db.all('SELECT key_name, key_json FROM grid_data WHERE ' + + that._db.all('SELECT key_name, key_json FROM grid_data WHERE ' + 'zoom_level = ? AND tile_column = ? AND tile_row = ?', z, x, y, this.parallel() @@ -374,10 +364,10 @@ MBTiles.prototype.getGrid = function(z, x, y, callback) { // Select a metadata value from the database. // // - @param {Function} callback -MBTiles.prototype.metadata = function(key, callback) { +MBTiles.prototype._metadata = function(key, callback) { if (typeof callback !== 'function') callback = noop; - this.db.get('SELECT value FROM metadata WHERE name = ?', + this._db.get('SELECT value FROM metadata WHERE name = ?', key, function(err, row) { if (!row || (err && err.errno == 1)) callback(new Error('Key does not exist')); @@ -394,13 +384,13 @@ MBTiles.prototype.getInfo = function(callback) { var that = this; var info = {}; - info.size = this.stat.size; + info.filesize = this._stat.size; info.scheme = 'tms'; info.basename = path.basename(that.filename); - info.id = info.basename.replace(path.extname(that.filename), ''); + info.id = path.basename(that.filename, path.extname(that.filename)); Step(function() { var end = this; - that.db.all('SELECT name, value FROM metadata', function(err, rows) { + that._db.all('SELECT name, value FROM metadata', function(err, rows) { if (rows) for (var i = 0; i < rows.length; i++) { info[rows[i].name] = rows[i].value; } @@ -414,7 +404,7 @@ MBTiles.prototype.getInfo = function(callback) { && info.minzoom !== undefined) return this(); var step = this; - var zoomquery = that.db.prepare('SELECT zoom_level FROM tiles ' + + var zoomquery = that._db.prepare('SELECT zoom_level FROM tiles ' + 'WHERE zoom_level = ? LIMIT 1', function(err) { if (err) { if (err.errno === 1) step(); @@ -449,7 +439,7 @@ MBTiles.prototype.getInfo = function(callback) { var next = this; Step( function() { - that.db.get( + that._db.get( 'SELECT MAX(tile_column) AS maxx, ' + 'MIN(tile_column) AS minx, MAX(tile_row) AS maxy, ' + 'MIN(tile_row) AS miny FROM tiles ' + @@ -494,3 +484,15 @@ MBTiles.prototype.getInfo = function(callback) { callback(null, info); }); }; + +// Insert a tile. Scheme is XYZ. +MBTiles.prototype.putTile = function(z, x, y, data, callback) { + + // Flip Y coordinate because MBTiles files are TMS. + y = Math.pow(2, z) - 1 - y; + + this.run('INSERT INGORE INTO ') + + this._insertTiles([ { z: z, x: x, y: y, data: data }], callback); +}; + diff --git a/test/read.test.js b/test/read.test.js index 198c489..212121f 100644 --- a/test/read.test.js +++ b/test/read.test.js @@ -14,27 +14,35 @@ var fixtures = { try { fs.unlink(fixtures.non_existent); } catch (err) {} exports['get metadata'] = function(beforeExit) { - var completion = {}; + var completed = false; - var mbtiles = new MBTiles(fixtures.plain_1); - 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('invalid', function(err, value) { completion.invalid = err.message; }); + new MBTiles(fixtures.plain_1, function(err, mbtiles) { + mbtiles.getInfo(function(err, data) { + completed = true; + if (err) throw err; + + assert.deepEqual({ + name: 'plain_1', + description: 'demo description', + version: '1.0.3', + scheme: 'tms', + minzoom: 0, + maxzoom: 4, + formatter: null, + center: [ 0, 7.500000001278025, 2 ], + bounds: [ -179.9999999749438, -69.99999999526695, 179.9999999749438, 84.99999999782301 ], + + // These aren't part of TileJSON, but exist in an MBTiles file. + filesize: 561152, + type: 'baselayer', + id: 'plain_1', + basename: 'plain_1.mbtiles' + }, data); + }) + }); beforeExit(function() { - assert.deepEqual(completion, { - name: 'plain_1', - type: 'baselayer', - description: 'demo description', - version: '1.0.3', - formatter: null, - bounds: '-179.9999999749438,-69.99999999526695,179.9999999749438,84.99999999782301', - invalid: 'Key does not exist' - }); + assert.ok(completed); }); }; diff --git a/test/write.test.js b/test/write.test.js index f1f2e7c..bdca60b 100644 --- a/test/write.test.js +++ b/test/write.test.js @@ -5,12 +5,19 @@ var MBTiles = require('..'); var fixtureDir = __dirname + '/fixtures/output'; // Recreate output directory to remove previous tests. -try { fs.rmdirSync(fixtureDir); } catch(err) {} -fs.mkdirSync(fixtureDir, 0755); +try { fs.unlinkSync(fixtureDir + '/test_1.mbtiles'); } catch(err) {} +try { fs.mkdirSync(fixtureDir, 0755); } catch(err) {} exports['test mbtiles file creation'] = function(beforeExit) { - var mbtiles = new MBTiles(fixtureDir + '/test_1.mbtiles'); - mbtiles.setup(function(err) { - fs.unlinkSync(fixtureDir + '/test_1.mbtiles'); + var completed = {}; + new MBTiles(fixtureDir + '/test_1.mbtiles', function(err, mbtiles) { + completed.open = true; + if (err) throw err; }); + + beforeExit(function() { + assert.deepEqual({ + open: true + }, completed); + }) };