From 4c82b3403ca404c530e470efe0a4c6e7bb0eb073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Ber=C3=A9nyi?= Date: Mon, 9 May 2016 08:56:31 -0600 Subject: [PATCH 1/7] Use XHR by default --- externs/olx.js | 10 ++ src/ol/source/tileutfgridsource.js | 111 +++++++++++++++++- test/spec/ol/source/tileutfgridsource.test.js | 13 -- 3 files changed, 116 insertions(+), 18 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 64615ed687..6fe6e8f9c4 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4193,6 +4193,7 @@ olx.source.ClusterOptions.prototype.wrapX; /** * @typedef {{preemptive: (boolean|undefined), + * jsonp: (boolean|undefined), * tileJSON: (TileJSON|undefined), * url: (string|undefined)}} * @api @@ -4200,6 +4201,15 @@ olx.source.ClusterOptions.prototype.wrapX; olx.source.TileUTFGridOptions; +/** + * Use JSONP with callback to load the TileJSON. Useful when the server + * does not support CORS. Default is `false`. + * @type {boolean|undefined} + * @api + */ +olx.source.TileUTFGridOptions.prototype.jsonp; + + /** * If `true` the TileUTFGrid source loads the tiles based on their "visibility". * This improves the speed of response, but increases traffic. diff --git a/src/ol/source/tileutfgridsource.js b/src/ol/source/tileutfgridsource.js index a074e381e2..96dbeb2b4e 100644 --- a/src/ol/source/tileutfgridsource.js +++ b/src/ol/source/tileutfgridsource.js @@ -49,8 +49,23 @@ ol.source.TileUTFGrid = function(options) { */ this.template_ = undefined; + /** + * @private + * @type {boolean} + */ + this.jsonp_ = options.jsonp || false; + if (options.url) { - ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this)); + if (this.jsonp_) { + ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this), + this.handleTileJSONError.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', options.url); + client.send(); + } } else if (options.tileJSON) { this.handleTileJSONResponse(options.tileJSON); } else { @@ -60,6 +75,36 @@ ol.source.TileUTFGrid = function(options) { goog.inherits(ol.source.TileUTFGrid, ol.source.Tile); +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileUTFGrid.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + if (client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {TileJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleTileJSONError(); + return; + } + this.handleTileJSONResponse(response); + } else { + this.handleTileJSONError(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileUTFGrid.prototype.onXHRError_ = function(event) { + this.handleTileJSONError(); +}; + + /** * Return the template from TileJSON. * @return {string|undefined} The template from TileJSON. @@ -103,6 +148,14 @@ ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function( }; +/** + * @protected + */ +ol.source.TileUTFGrid.prototype.handleTileJSONError = function() { + this.setState(ol.source.State.ERROR); +}; + + /** * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse * @protected @@ -185,7 +238,8 @@ ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projecti tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY, tileUrl !== undefined ? tileUrl : '', this.tileGrid.getTileCoordExtent(tileCoord), - this.preemptive_); + this.preemptive_, + this.jsonp_); this.tileCache.set(tileCoordKey, tile); return tile; } @@ -211,9 +265,10 @@ ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) { * @param {string} src Image source URI. * @param {ol.Extent} extent Extent of the tile. * @param {boolean} preemptive Load the tile when visible (before it's needed). + * @param {boolean} jsonp Load the tile as a script. * @private */ -ol.source.TileUTFGridTile_ = function(tileCoord, state, src, extent, preemptive) { +ol.source.TileUTFGridTile_ = function(tileCoord, state, src, extent, preemptive, jsonp) { goog.base(this, tileCoord, state); @@ -252,6 +307,14 @@ ol.source.TileUTFGridTile_ = function(tileCoord, state, src, extent, preemptive) * @type {Object.|undefined} */ this.data_ = null; + + + /** + * @private + * @type {boolean} + */ + this.jsonp_ = jsonp; + }; goog.inherits(ol.source.TileUTFGridTile_, ol.Tile); @@ -365,12 +428,50 @@ ol.source.TileUTFGridTile_.prototype.handleLoad_ = function(json) { ol.source.TileUTFGridTile_.prototype.loadInternal_ = function() { if (this.state == ol.TileState.IDLE) { this.state = ol.TileState.LOADING; - ol.net.jsonp(this.src_, this.handleLoad_.bind(this), - this.handleError_.bind(this)); + if (this.jsonp_) { + ol.net.jsonp(this.src_, this.handleLoad_.bind(this), + this.handleError_.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', this.src_); + client.send(); + } } }; +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileUTFGridTile_.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + if (client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {TileJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleError_(); + return; + } + this.handleLoad_(response); + } else { + this.handleError_(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileUTFGridTile_.prototype.onXHRError_ = function() { + this.handleError_(); +}; + + /** * Load not yet loaded URI. */ diff --git a/test/spec/ol/source/tileutfgridsource.test.js b/test/spec/ol/source/tileutfgridsource.test.js index 97bf635428..d54ac5360b 100644 --- a/test/spec/ol/source/tileutfgridsource.test.js +++ b/test/spec/ol/source/tileutfgridsource.test.js @@ -63,19 +63,6 @@ describe('ol.source.TileUTFGrid', function() { expect(source).to.be.an(ol.source.TileUTFGrid); }); - it('immediately fetches the passed URL', function() { - // spy on the jsonp method - var jsonpSpy = sinon.spy(ol.net, 'jsonp'); - - getTileUTFGrid(); - expect(jsonpSpy.calledOnce).to.be(true); - expect(jsonpSpy.lastCall.calledWith(url)).to.be(true); - - // cleanup - ol.net.jsonp.restore(); - jsonpSpy = null; - }); - }); describe('#handleTileJSONResponse', function() { From 5c076280448c28d6cf731fb1b2e04dd6c115d84a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 9 May 2016 08:56:55 -0600 Subject: [PATCH 2/7] Return id if no data is present --- src/ol/source/tileutfgridsource.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ol/source/tileutfgridsource.js b/src/ol/source/tileutfgridsource.js index 96dbeb2b4e..752e602dd7 100644 --- a/src/ol/source/tileutfgridsource.js +++ b/src/ol/source/tileutfgridsource.js @@ -336,7 +336,7 @@ ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) { * @return {Object} The data. */ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) { - if (!this.grid_ || !this.keys_ || !this.data_) { + if (!this.grid_ || !this.keys_) { return null; } var xRelative = (coordinate[0] - this.extent_[0]) / @@ -359,7 +359,16 @@ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) { } code -= 32; - return (code in this.keys_) ? this.data_[this.keys_[code]] : null; + var data = null; + if (code in this.keys_) { + var id = this.keys_[code]; + if (this.data_ && id in this.data_) { + data = this.data_[id]; + } else { + data = id; + } + } + return data; }; From a810feb2576544f9fa8cf1ea1b81f2b32d824ca1 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 10 May 2016 07:01:40 -0600 Subject: [PATCH 3/7] Test XHR loading --- test/spec/ol/source/tileutfgridsource.test.js | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/test/spec/ol/source/tileutfgridsource.test.js b/test/spec/ol/source/tileutfgridsource.test.js index d54ac5360b..90f5294d92 100644 --- a/test/spec/ol/source/tileutfgridsource.test.js +++ b/test/spec/ol/source/tileutfgridsource.test.js @@ -2,11 +2,10 @@ goog.provide('ol.test.source.TileUTFGrid'); describe('ol.source.TileUTFGrid', function() { + var url = 'spec/ol/data/tileutfgrid.json'; var tileJson = null; - // Called once for this describe section, this method loads a local - // tileutfgrid and stores it in a variable to compare it in the actual - // tests. This way we can test `handleTileJSONResponse` and also remove - // the dependency from the external service / URL. + + // Load and parse the UTFGrid fixture before(function(done) { var client = new XMLHttpRequest(); client.addEventListener('load', function() { @@ -14,57 +13,79 @@ describe('ol.source.TileUTFGrid', function() { done(); }); client.addEventListener('error', function() { - done(new Error('Failed to fetch local tileutfgrid.json')) + done(new Error('Failed to fetch ' + url)); }); - client.open('GET', 'spec/ol/data/tileutfgrid.json'); + client.open('GET', url); client.send(); }); + after(function() { tileJson = null; }); - var url = 'some-tileutfgrid-url'; function getTileUTFGrid() { - var source = new ol.source.TileUTFGrid({ + return new ol.source.TileUTFGrid({ url: url }); - return source; } describe('constructor', function() { it('needs to be constructed with url option', function() { - var source; + + var source = new ol.source.TileUTFGrid({url: url}); + expect(source).to.be.an(ol.source.TileUTFGrid); + expect(source).to.be.an(ol.source.Tile); expect(function() { // no options: will throw - source = new ol.source.TileUTFGrid(); + return new ol.source.TileUTFGrid(); }).to.throwException(); - expect(source).to.be(undefined); expect(function() { // no url-option: will throw - source = new ol.source.TileUTFGrid({}); + return new ol.source.TileUTFGrid({}); }).to.throwException(); - expect(source).to.be(undefined); - expect(function() { - // with a (bogus) url option all is fine - source = new ol.source.TileUTFGrid({ - url: url - }); - }).to.not.throwException(); - expect(source).to.be.an(ol.source.TileUTFGrid); - - expect(function() { - // also test our utility method - source = getTileUTFGrid(); - }).to.not.throwException(); - expect(source).to.be.an(ol.source.TileUTFGrid); + expect(getTileUTFGrid()).to.be.an(ol.source.TileUTFGrid); }); }); + describe('change event (ready)', function() { + it('is fired when the source is ready', function(done) { + var source = new ol.source.TileUTFGrid({ + url: url + }); + expect(source.getState()).to.be(ol.source.State.LOADING); + expect(source.tileGrid).to.be(null); + + source.on('change', function(event) { + if (source.getState() === ol.source.State.READY) { + expect(source.tileGrid).to.be.an(ol.tilegrid.TileGrid); + done(); + } + }); + }); + }); + + describe('change event (error)', function(done) { + it('is fired when the source fails to initialize', function(done) { + var source = new ol.source.TileUTFGrid({ + url: 'Bogus UTFGrid URL' + }); + expect(source.getState()).to.be(ol.source.State.LOADING); + expect(source.tileGrid).to.be(null); + + source.on('change', function(event) { + if (source.getState() === ol.source.State.ERROR) { + expect(source.tileGrid).to.be(null); + done(); + } + }); + }); + }); + describe('#handleTileJSONResponse', function() { it('sets up a tileGrid', function() { @@ -268,6 +289,7 @@ describe('ol.source.TileUTFGrid', function() { goog.require('ol.net'); goog.require('ol.proj'); goog.require('ol.source.State'); +goog.require('ol.source.Tile'); goog.require('ol.source.TileUTFGrid'); goog.require('ol.tilegrid.TileGrid'); goog.require('ol.TileState'); From 94b8c853a71c4bbac8a13aa64ffa2edd0ac14bd9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 10 May 2016 11:47:18 -0600 Subject: [PATCH 4/7] Use legacy UTFGrid source (which doesn't require a token) --- examples/tileutfgrid.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/tileutfgrid.js b/examples/tileutfgrid.js index ebc1cd96aa..e9be2fe97e 100644 --- a/examples/tileutfgrid.js +++ b/examples/tileutfgrid.js @@ -12,6 +12,7 @@ var mapLayer = new ol.layer.Tile({ }); var gridSource = new ol.source.TileUTFGrid({ + jsonp: true, // for v4 and above, leave this option off url: 'http://api.tiles.mapbox.com/v3/mapbox.geography-class.json' }); From 476191b0d0d8319059491c5d104236609c6084eb Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 10 May 2016 11:57:43 -0600 Subject: [PATCH 5/7] Notes on using the v4 Mapbox API --- changelog/upgrade-notes.md | 17 +++++++++++++++++ examples/tileutfgrid.html | 2 ++ examples/tileutfgrid.js | 8 +++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index d8933833bf..8261ec6b73 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -4,6 +4,23 @@ This option is no longer needed, so it was removed from the API. +#### XHR loading for `ol.source.TileUTFGrid` + +The `ol.source.TileUTFGrid` now uses XMLHttpRequest to load UTFGrid tiles by default. This works out of the box with the v4 Mapbox API. To work with the v3 API, you must use the new `jsonp` option on the source. See the examples below for detail. + +```js +// To work with the v4 API +var v4source = new ol.source.TileUTFGrid({ + url: 'http://api.tiles.mapbox.com/v4/example.json?access_token=' + YOUR_KEY_HERE +}); + +// To work with the v3 API +var v3source = new ol.source.TileUTFGrid({ + jsonp: true, // <--- this is required for v3 + url: 'http://api.tiles.mapbox.com/v3/example.json?access_token=' + YOUR_KEY_HERE +}); +``` + ### v3.15.0 #### Internet Explorer 9 support diff --git a/examples/tileutfgrid.html b/examples/tileutfgrid.html index 417702cec6..86df71aaa8 100644 --- a/examples/tileutfgrid.html +++ b/examples/tileutfgrid.html @@ -6,6 +6,8 @@ docs: >

Point to a country to see its name and flag.

Tiles made with [TileMill](http://tilemill.com). Hosting on MapBox.com or with open-source [TileServer](https://github.com/klokantech/tileserver-php/). tags: "utfgrid, tileutfgrid, tilejson" +cloak: + pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg: Your Mapbox access token from http://mapbox.com/ here ---
diff --git a/examples/tileutfgrid.js b/examples/tileutfgrid.js index e9be2fe97e..217c073c9a 100644 --- a/examples/tileutfgrid.js +++ b/examples/tileutfgrid.js @@ -5,15 +5,17 @@ goog.require('ol.layer.Tile'); goog.require('ol.source.TileJSON'); goog.require('ol.source.TileUTFGrid'); +var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; + var mapLayer = new ol.layer.Tile({ source: new ol.source.TileJSON({ - url: 'http://api.tiles.mapbox.com/v3/mapbox.geography-class.json' + url: 'http://api.tiles.mapbox.com/v4/mapbox.geography-class.json?access_token=' + key }) }); + var gridSource = new ol.source.TileUTFGrid({ - jsonp: true, // for v4 and above, leave this option off - url: 'http://api.tiles.mapbox.com/v3/mapbox.geography-class.json' + url: 'http://api.tiles.mapbox.com/v4/mapbox.geography-class.json?access_token=' + key }); var gridLayer = new ol.layer.Tile({source: gridSource}); From 5a5ad7ce3186c2208c5285132d38382b037c42a2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 10 May 2016 22:47:12 -0600 Subject: [PATCH 6/7] Correct types --- src/ol/source/tileutfgridsource.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ol/source/tileutfgridsource.js b/src/ol/source/tileutfgridsource.js index 752e602dd7..a9faa67a82 100644 --- a/src/ol/source/tileutfgridsource.js +++ b/src/ol/source/tileutfgridsource.js @@ -121,7 +121,7 @@ ol.source.TileUTFGrid.prototype.getTemplate = function() { * in case of an error). * @param {ol.Coordinate} coordinate Coordinate. * @param {number} resolution Resolution. - * @param {function(this: T, Object)} callback Callback. + * @param {function(this: T, *)} callback Callback. * @param {T=} opt_this The object to use as `this` in the callback. * @param {boolean=} opt_request If `true` the callback is always async. * The tile data is requested if not yet loaded. @@ -333,7 +333,7 @@ ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) { /** * Synchronously returns data at given coordinate (if available). * @param {ol.Coordinate} coordinate Coordinate. - * @return {Object} The data. + * @return {*} The data. */ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) { if (!this.grid_ || !this.keys_) { @@ -376,7 +376,7 @@ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) { * Calls the callback (synchronously by default) with the available data * for given coordinate (or `null` if not yet loaded). * @param {ol.Coordinate} coordinate Coordinate. - * @param {function(this: T, Object)} callback Callback. + * @param {function(this: T, *)} callback Callback. * @param {T=} opt_this The object to use as `this` in the callback. * @param {boolean=} opt_request If `true` the callback is always async. * The tile data is requested if not yet loaded. @@ -460,7 +460,7 @@ ol.source.TileUTFGridTile_.prototype.onXHRLoad_ = function(event) { if (client.status >= 200 && client.status < 300) { var response; try { - response = /** @type {TileJSON} */(JSON.parse(client.responseText)); + response = /** @type {!UTFGridJSON} */(JSON.parse(client.responseText)); } catch (err) { this.handleError_(); return; @@ -476,7 +476,7 @@ ol.source.TileUTFGridTile_.prototype.onXHRLoad_ = function(event) { * @private * @param {Event} event The error event. */ -ol.source.TileUTFGridTile_.prototype.onXHRError_ = function() { +ol.source.TileUTFGridTile_.prototype.onXHRError_ = function(event) { this.handleError_(); }; From fb402d120e8f2bd5036536f6cb3f1e4fd8f90fd9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 13 May 2016 12:08:31 -0600 Subject: [PATCH 7/7] Removing access_token in notes for v3 use --- changelog/upgrade-notes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 8261ec6b73..06e531e058 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -11,13 +11,13 @@ The `ol.source.TileUTFGrid` now uses XMLHttpRequest to load UTFGrid tiles by def ```js // To work with the v4 API var v4source = new ol.source.TileUTFGrid({ - url: 'http://api.tiles.mapbox.com/v4/example.json?access_token=' + YOUR_KEY_HERE + url: 'https://api.tiles.mapbox.com/v4/example.json?access_token=' + YOUR_KEY_HERE }); // To work with the v3 API var v3source = new ol.source.TileUTFGrid({ jsonp: true, // <--- this is required for v3 - url: 'http://api.tiles.mapbox.com/v3/example.json?access_token=' + YOUR_KEY_HERE + url: 'http://api.tiles.mapbox.com/v3/example.json' }); ```