package/LICENSE000644 000765 000024 00000002731 11734734215 014643 0ustar00willwhitestaff000000 000000 Copyright (c) 2011, Development Seed All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name "Development Seed" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package/package.json000644 000765 000024 00000001203 11734734215 016115 0ustar00willwhitestaff000000 000000 { "name": "tilejson", "version": "0.1.2", "main": "./lib/tilejson.js", "description": "Tile source backend for online tile sources", "url": "http://github.com/mapbox/node-tilejson", "licenses": [{ "type": "BSD" }], "repositories": [{ "type": "git", "url": "http://github.com/mapbox/node-tilejson.git" }], "author": { "name": "MapBox", "url": "http://mapbox.com/", "email": "info@mapbox.com" }, "contributors": [ "Konstantin Käfer " ], "dependencies": { "get": "~1.1.2", "step": "~0.0.5" } } package/lib/tilejson.js000644 000765 000024 00000014761 11734734215 016577 0ustar00willwhitestaff000000 000000 var path = require('path'); var fs = require('fs'); var url = require('url'); var get = require('get'); var Step = require('step'); function getMimeType(data) { if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47 && data[4] === 0x0D && data[5] === 0x0A && data[6] === 0x1A && data[7] === 0x0A) { return 'image/png'; } else if (data[0] === 0xFF && data[1] === 0xD8 && data[data.length - 2] === 0xFF && data[data.length - 1] === 0xD9) { return 'image/jpeg'; } else if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x38 && (data[4] === 0x39 || data[4] === 0x37) && data[5] === 0x61) { return 'image/gif'; } }; var cache = {}; module.exports = TileJSON; require('util').inherits(TileJSON, require('events').EventEmitter) function TileJSON(uri, callback) { if (typeof callback !== 'function') throw new Error('callback required'); if (typeof uri === 'string') uri = url.parse(uri, true); else if (typeof uri.query === 'string') uri.query = qs.parse(uri.query); if (!uri.pathname) { callback(new Error('Invalid URI ' + url.format(uri))); return; } if (uri.hostname === '.' || uri.hostname == '..') { uri.pathname = uri.hostname + uri.pathname; delete uri.hostname; delete uri.host; } uri.query = uri.query || {}; var key = url.format(uri); if (!cache[key]) { cache[key] = this; this._open(uri); } var tilejson = cache[key]; if (!tilejson.open) { tilejson.once('open', callback); } else { callback(null, tilejson); } return undefined; } TileJSON.prototype._open = function(uri) { var tilejson = this; function error(err) { process.nextTick(function() { tilejson.close(); tilejson.emit('open', err); }); } var key = url.format(uri); Step(function() { if (uri.protocol === 'http:' || uri.protocol === 'https:') { new get(url.format(uri)).asString(this); } else { tilejson.filename = uri.pathname; fs.watchFile(uri.pathname, {persistent:false}, function(cur, prev) { // On OS X calls to fs.readFile can trigger this as well so we // need to accept multiple calls. See https://gist.github.com/2229386 if (cache[key]) { // Make sure we throw away this object when the file changed. cache[key].close(); delete cache[key]; } }); fs.readFile(uri.pathname, 'utf8', this); } }, function(err, data) { if (err) return error(err); data = data.replace(/^\s*\w+\s*\(\s*|\s*\)\s*;?\s*$/g, ''); try { tilejson.data = JSON.parse(data); } catch(err) { return error(err); } if (!tilejson.data.id) { tilejson.data.id = path.basename(uri.pathname, path.extname(uri.pathname)); } tilejson.open = true; tilejson.emit('open', null, tilejson); }); } TileJSON.prototype.close = function(callback) { if (this.filename) fs.unwatchFile(this.filename); if (callback) callback(null); }; TileJSON.registerProtocols = function(tilelive) { tilelive.protocols['tilejson:'] = TileJSON; }; TileJSON.list = function(filepath, callback) { filepath = path.resolve(filepath); fs.readdir(filepath, function(err, files) { if (err && err.code === 'ENOENT') return callback(null, {}); if (err) return callback(err); for (var result = {}, i = 0; i < files.length; i++) { var name = files[i].match(/^([\w-]+)\.tilejson$/); if (name) result[name[1]] = 'tilejson://' + path.join(filepath, name[0]); } callback(null, result); }); }; TileJSON.findID = function(filepath, id, callback) { filepath = path.resolve(filepath); var file = path.join(filepath, id + '.tilejson'); fs.stat(file, function(err, stats) { if (err) callback(err); else callback(null, 'tilejson://' + file); }); }; TileJSON.prototype.getInfo = function(callback) { if (!this.data) callback(new Error('Tilesource not loaded')); else callback(null, this.data); }; // z, x, y are XYZ coordinates. TileJSON.prototype.getTile = function(z, x, y, callback) { if (!this.data) return callback(new Error('Tilesource not loaded')); if (!this.data.tiles) return callback(new Error('Tile does not exist')); var url = this._prepareURL(this.data.tiles[0], z, x, y); new get({ uri:url, headers: {Connection:'Keep-Alive'} }).asBuffer(function(err, data, headers) { if (err) return callback(new Error('Tile does not exist')); var modified = headers['last-modified'] ? new Date(headers['last-modified']) : new Date; var responseHeaders = { 'Content-Type': getMimeType(data), 'Last-Modified': modified, 'ETag': headers['etag'] || (headers['content-length'] + '-' + +modified) }; if (headers['cache-control']) { responseHeaders['Cache-Control'] = headers['cache-control']; } callback(null, data, responseHeaders); }); }; TileJSON.prototype._prepareURL = function(url, z, x, y) { return (url .replace(/\{z\}/g, z) .replace(/\{x\}/g, x) .replace(/\{y\}/g, (this.data.scheme === 'tms') ? (1 << z) - 1 - y : y)); }; // z, x, y are XYZ coordinates. TileJSON.prototype.getGrid = function(z, x, y, callback) { if (!this.data) return callback(new Error('Gridsource not loaded')); if (!this.data.grids) return callback(new Error('Grid does not exist')); var url = this._prepareURL(this.data.grids[0], z, x, y); new get({ uri:url, headers: {Connection:'Keep-Alive'} }).asString(function(err, grid, headers) { if (err) return callback(new Error('Grid does not exist')); var modified = headers['last-modified'] ? new Date(headers['last-modified']) : new Date; var responseHeaders = { 'Content-Type': 'application/json', 'Last-Modified': modified, 'ETag': headers['etag'] || (headers['content-length'] + '-' + +modified) }; if (headers['cache-control']) { responseHeaders['Cache-Control'] = headers['cache-control']; } // TODO: compression grid = grid.replace(/^\s*\w+\s*\(|\)\s*;?\s*$/g, ''); callback(null, JSON.parse(grid), responseHeaders); }); }; package/test/grid.test.js000644 000765 000024 00000003116 11734734215 017054 0ustar00willwhitestaff000000 000000 var assert = require('assert'); var crypto = require('crypto'); var fs = require('fs'); var TileJSON = require('..'); function md5(str) { return crypto.createHash('md5').update(str).digest('hex'); } try { fs.unlink(__dirname + '/fixtures/grid.tilejson.cache'); } catch (err) {} exports['test loading interactivity'] = function(beforeExit) { var completed = {}; var source = new TileJSON('tilejson://' + __dirname + '/fixtures/grid.tilejson', function(err) { completed.load = true; if (err) throw err; source.getGrid(6, 29, 30, function(err, data, headers) { completed.tile_6_29_30_1 = true; assert.isNull(err); assert.equal('4f8790dc72e204132531f1e12dea20a1', md5(JSON.stringify(data))); assert.ok('Content-Type' in headers); assert.ok('Last-Modified' in headers); assert.ok('ETag' in headers); // Request the same again to test caching. source.getGrid(6, 29, 30, function(err, data, headers) { completed.tile_6_29_30_2 = true; assert.isNull(err); assert.equal('4f8790dc72e204132531f1e12dea20a1', md5(JSON.stringify(data))); assert.ok('Content-Type' in headers); assert.ok('Last-Modified' in headers); assert.ok('ETag' in headers); source.close(); }); }); }); beforeExit(function() { assert.deepEqual(completed, { load: true, tile_6_29_30_1: true, tile_6_29_30_2: true }); }); }; package/test/http.test.js000644 000765 000024 00000001474 11734734215 017113 0ustar00willwhitestaff000000 000000 var assert = require('assert'); var crypto = require('crypto'); var fs = require('fs'); var TileJSON = require('..'); function md5(str) { return crypto.createHash('md5').update(str).digest('hex'); } exports['test async calling'] = function(beforeExit) { var completed = false; new TileJSON('http://a.tiles.mapbox.com/mapbox/1.0.0/world-bright/layer.json', function(err, source) { if (err) throw err; source.getTile(0, 0, 0, function(err, data, headers) { completed = true; if (err) throw err; assert.equal('max-age=14400', headers['Cache-Control']); assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data)); clearInterval(source._deleteInterval); }); }); beforeExit(function() { assert.ok(completed); }); }; package/test/tilejson.test.js000644 000765 000024 00000004330 11734734215 017755 0ustar00willwhitestaff000000 000000 var assert = require('assert'); var crypto = require('crypto'); var fs = require('fs'); var TileJSON = require('..'); function md5(str) { return crypto.createHash('md5').update(str).digest('hex'); } exports['test loading tile'] = function(beforeExit) { var completed = {}; new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) { completed.load = true; if (err) throw err; source.getTile(0, 0, 0, function(err, data) { completed.tile_0_0_0 = true; if (err) throw err; assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data)); }); source.getTile(2, 2, 2, function(err, data) { completed.tile_2_2_2 = true; if (err) throw err; assert.equal('84044cc921ee458cd1ece905e2682db0', md5(data)); source.close(); }); }); beforeExit(function() { assert.deepEqual(completed, { load: true, tile_0_0_0: true, tile_2_2_2: true }); }); }; exports['test loading interactivity'] = function(beforeExit) { var completed = {}; new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) { completed.load = true; if (err) throw err; source.getGrid(0, 0, 0, function(err, data) { completed.tile_0_0_0 = true; assert.ok(err); assert.equal(err.message, 'Grid does not exist'); source.close(); }); }); beforeExit(function() { assert.deepEqual(completed, { load: true, tile_0_0_0: true }); }); }; exports['test error'] = function(beforeExit) { var completed = { }; new TileJSON('tilejson://' + __dirname + '/fixtures/enoent.tilejson', function(err, source) { completed.enoent = err.code; return; }); new TileJSON('tilejson://' + __dirname + '/fixtures/bad.tilejson', function(err, source) { completed.parseErr = err.type; return; }); beforeExit(function() { assert.deepEqual(completed, { enoent: 'ENOENT', parseErr: 'unexpected_token' }); }); }; package/test/fixtures/bad.tilejson000644 000765 000024 00000000353 11734734215 020763 0ustar00willwhitestaff000000 000000 { // Invalid JSON "name": "World Bright", "scheme": "tms", "tiles": [ "http://a.tiles.mapbox.com/mapbox/1.0.0/world-bright/{z}/{x}/{y}.png" ], "minzoom": 0, "maxzoom": 11, "bounds": [ -180, -85, 180, 85 ] } package/test/fixtures/grid.tilejson000644 000765 000024 00000000624 11734734215 021163 0ustar00willwhitestaff000000 000000 { "name": "Geography Class", "scheme": "tms", "tiles": [ "http://a.tiles.mapbox.com/mapbox/1.0.0/geography-class/{z}/{x}/{y}.png" ], "grids": [ "http://a.tiles.mapbox.com/mapbox/1.0.0/geography-class/{z}/{x}/{y}.grid.json" ], "minzoom": 0, "maxzoom": 8, "bounds": [ -179.99992505544913, -85.05112231458043, 179.99992505544913, 85.05112231458043 ], "center": [ 0, 0, 4 ] } package/test/fixtures/world-bright.tilejson000644 000765 000024 00000000327 11734734215 022642 0ustar00willwhitestaff000000 000000 { "name": "World Bright", "scheme": "tms", "tiles": [ "http://a.tiles.mapbox.com/mapbox/1.0.0/world-bright/{z}/{x}/{y}.png" ], "minzoom": 0, "maxzoom": 11, "bounds": [ -180, -85, 180, 85 ] }