diff --git a/lib/mbtiles.js b/lib/mbtiles.js index 4de9f71..689e71d 100644 --- a/lib/mbtiles.js +++ b/lib/mbtiles.js @@ -229,6 +229,18 @@ MBTiles.prototype.getInfo = function(callback) { if (err && err.errno !== 1) return callback(err); if (rows) rows.forEach(function(row) { switch (row.name) { + // The special "json" key/value pair allows JSON to be serialized + // and merged into the metadata of an MBTiles based source. This + // enables nested properties and non-string datatypes to be + // captured by the MBTiles metadata table. + case 'json': + try { var jsondata = JSON.parse(row.value); } + catch (err) { return callback(err); } + Object.keys(jsondata).reduce(function(memo, key) { + memo[key] = memo[key] || jsondata[key]; + return memo; + }, info); + break; case 'minzoom': case 'maxzoom': info[row.name] = parseInt(row.value, 10); @@ -537,11 +549,21 @@ MBTiles.prototype.putInfo = function(data, callback) { if (!this.open) return callback(new Error('MBTiles not yet loaded')); if (!this._isWritable) return callback(new Error('MBTiles not in write mode')); + var jsondata; var stmt = this._db.prepare('REPLACE INTO metadata (name, value) VALUES (?, ?)'); stmt.on('error', callback); for (var key in data) { - stmt.run(key, String(data[key])); + // If a data property is a javascript hash/object, slip it into + // the 'json' field which contains stringified JSON to be merged + // in at read time. Allows nested/deep metadata to be recorded. + if (typeof data[key] === 'object' && !Array.isArray(data[key])) { + jsondata = jsondata || {}; + jsondata[key] = data[key]; + } else { + stmt.run(key, String(data[key])); + } } + if (jsondata) stmt.run('json', JSON.stringify(jsondata)); var mbtiles = this; stmt.finalize(function(err) { diff --git a/test/fixtures/plain_1.mbtiles b/test/fixtures/plain_1.mbtiles index 2ca8645..5fdce65 100644 Binary files a/test/fixtures/plain_1.mbtiles and b/test/fixtures/plain_1.mbtiles differ diff --git a/test/info.test.js b/test/info.test.js index c88c7fa..edee2ce 100644 --- a/test/info.test.js +++ b/test/info.test.js @@ -30,7 +30,8 @@ describe('info', function() { formatter: null, center: [ 0, 7.500000001278025, 2 ], bounds: [ -179.9999999749438, -69.99999999526695, 179.9999999749438, 84.99999999782301 ], - + // Test that json data is merged in. + level1: { level2: 'property' }, // These aren't part of TileJSON, but exist in an MBTiles file. filesize: 561152, type: 'baselayer', @@ -45,6 +46,11 @@ describe('info', function() { it('get/put metadata from empty file', function(done) { this.timeout(10e3); + var info = { + version: '1.0.0', + level1: { level2: 'property' } + }; + new MBTiles(fixtures.empty, function(err, mbtiles) { assert.ifError(err); @@ -58,14 +64,14 @@ describe('info', function() { scheme: "tms" }, data); - mbtiles.putInfo({ version: '1.0.0' }, function(err) { + mbtiles.putInfo(info, function(err) { assert.ok(err); assert.equal(err.message, 'MBTiles not in write mode'); mbtiles.startWriting(function(err) { assert.ifError(err); - mbtiles.putInfo({ version: '1.0.0' }, function(err) { + mbtiles.putInfo(info, function(err) { assert.ifError(err); mbtiles.stopWriting(function(err) { @@ -79,7 +85,8 @@ describe('info', function() { filesize: 0, id: "empty", scheme: "tms", - version: "1.0.0" + version: "1.0.0", + level1: { level2: "property" }, }, data); done();