diff --git a/lib/tilejson.js b/lib/tilejson.js index e198b3b..927ecc9 100644 --- a/lib/tilejson.js +++ b/lib/tilejson.js @@ -2,6 +2,7 @@ var path = require('path'); var fs = require('fs'); var url = require('url'); var get = require('get'); +var mercator = new (require('sphericalmercator'))(); var tiletype = require('tiletype'); var EventEmitter = require('events').EventEmitter; var Agent = require('agentkeepalive'); @@ -131,10 +132,35 @@ TileJSON.prototype.getInfo = function(callback) { else callback(null, this.data); }; +TileJSON.prototype.validate = function(z, x, y) { + if (!this.data) { + // not loaded + return false; + } + + if (z < Math.max(0, this.data.minzoom | 0) || z > (this.data.maxzoom || Infinity)) { + // out of available zoom range + return false; + } + + var xyz = mercator.xyz(this.data.bounds || [-180, -85.0511, 180, 85.0511], z); + + if (x < xyz.minX || + x > xyz.maxX || + y < xyz.minY || + y > xyz.maxY) { + // out of bounds + return false; + } + + return true; +}; + // 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')); + if (!this.validate(z, x, y)) return setImmediate(callback, new Error('Tile does not exist')); var url = this._prepareURL(this.data.tiles[0], z, x, y); this.get(url, function(err, data, headers) { @@ -166,6 +192,7 @@ TileJSON.prototype._prepareURL = function(url, z, x, y) { 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')); + if (!this.validate(z, x, y)) return setImmediate(callback, new Error('Grid does not exist')); var url = this._prepareURL(this.data.grids[0], z, x, y); this.get(url, function(err, grid, headers) { diff --git a/package.json b/package.json index 259a41e..1e9e3a9 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "dependencies": { "get": "1.3.x", "agentkeepalive": "0.2.x", - "tiletype": "0.0.x" + "tiletype": "0.0.x", + "sphericalmercator": "^1.0.2" }, "devDependencies": { "mocha": "*" diff --git a/test/fixtures/world-bright.tilejson b/test/fixtures/world-bright.tilejson index a3b4820..5f09dc5 100644 --- a/test/fixtures/world-bright.tilejson +++ b/test/fixtures/world-bright.tilejson @@ -4,5 +4,5 @@ "tiles": [ "http://a.tiles.mapbox.com/v3/mapbox.world-bright/{z}/{x}/{y}.png" ], "minzoom": 0, "maxzoom": 11, - "bounds": [ -180, -85, 180, 85 ] + "bounds": [ -80, -85, 180, 85 ] } diff --git a/test/tilejson.test.js b/test/tilejson.test.js index ad1d435..27be798 100644 --- a/test/tilejson.test.js +++ b/test/tilejson.test.js @@ -265,6 +265,22 @@ describe('tiles', function() { }); }); + it('should fail for zoom out of range', function(done) { + world_bright.getTile(12, 2, 2, function(err, data, headers) { + assert.ok(err); + assert.equal(err.message, 'Tile does not exist'); + done(); + }); + }); + + it('should fail for coordinates out of bounds', function(done) { + world_bright.getTile(2, 0, 2, function(err, data, headers) { + assert.ok(err); + assert.equal(err.message, 'Tile does not exist'); + done(); + }); + }); + it('https should load tile 0/0/0', function(done) { world_bright_ssl.getTile(0, 0, 0, function(err, data, headers) { if (err) throw err; @@ -310,6 +326,14 @@ describe('grids', function() { done(); }); }); + + it('should fail for zoom out of range', function(done) { + grid_source.getGrid(12, 2, 2, function(err, data, headers) { + assert.ok(err); + assert.equal(err.message, 'Grid does not exist'); + done(); + }); + }); }); describe('tiles from bad server', function() {