Merge pull request #5329 from tschaub/utfgrid

UTFGrid updates.
This commit is contained in:
Tim Schaub
2016-05-13 12:59:56 -06:00
6 changed files with 199 additions and 48 deletions
+17
View File
@@ -4,6 +4,23 @@
This option is no longer needed, so it was removed from the API. 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: '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'
});
```
### v3.15.0 ### v3.15.0
#### Internet Explorer 9 support #### Internet Explorer 9 support
+2
View File
@@ -6,6 +6,8 @@ docs: >
<p>Point to a country to see its name and flag.</p> <p>Point to a country to see its name and flag.</p>
Tiles made with [TileMill](http://tilemill.com). Hosting on MapBox.com or with open-source [TileServer](https://github.com/klokantech/tileserver-php/). 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" tags: "utfgrid, tileutfgrid, tilejson"
cloak:
pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg: Your Mapbox access token from http://mapbox.com/ here
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>
<div style="display: none;"> <div style="display: none;">
+5 -2
View File
@@ -5,14 +5,17 @@ goog.require('ol.layer.Tile');
goog.require('ol.source.TileJSON'); goog.require('ol.source.TileJSON');
goog.require('ol.source.TileUTFGrid'); goog.require('ol.source.TileUTFGrid');
var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
var mapLayer = new ol.layer.Tile({ var mapLayer = new ol.layer.Tile({
source: new ol.source.TileJSON({ 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({ var gridSource = new ol.source.TileUTFGrid({
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}); var gridLayer = new ol.layer.Tile({source: gridSource});
+10
View File
@@ -4193,6 +4193,7 @@ olx.source.ClusterOptions.prototype.wrapX;
/** /**
* @typedef {{preemptive: (boolean|undefined), * @typedef {{preemptive: (boolean|undefined),
* jsonp: (boolean|undefined),
* tileJSON: (TileJSON|undefined), * tileJSON: (TileJSON|undefined),
* url: (string|undefined)}} * url: (string|undefined)}}
* @api * @api
@@ -4200,6 +4201,15 @@ olx.source.ClusterOptions.prototype.wrapX;
olx.source.TileUTFGridOptions; 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". * If `true` the TileUTFGrid source loads the tiles based on their "visibility".
* This improves the speed of response, but increases traffic. * This improves the speed of response, but increases traffic.
+120 -10
View File
@@ -49,8 +49,23 @@ ol.source.TileUTFGrid = function(options) {
*/ */
this.template_ = undefined; this.template_ = undefined;
/**
* @private
* @type {boolean}
*/
this.jsonp_ = options.jsonp || false;
if (options.url) { 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) { } else if (options.tileJSON) {
this.handleTileJSONResponse(options.tileJSON); this.handleTileJSONResponse(options.tileJSON);
} else { } else {
@@ -60,6 +75,36 @@ ol.source.TileUTFGrid = function(options) {
goog.inherits(ol.source.TileUTFGrid, ol.source.Tile); 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 the template from TileJSON.
* @return {string|undefined} The template from TileJSON. * @return {string|undefined} The template from TileJSON.
@@ -76,7 +121,7 @@ ol.source.TileUTFGrid.prototype.getTemplate = function() {
* in case of an error). * in case of an error).
* @param {ol.Coordinate} coordinate Coordinate. * @param {ol.Coordinate} coordinate Coordinate.
* @param {number} resolution Resolution. * @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 {T=} opt_this The object to use as `this` in the callback.
* @param {boolean=} opt_request If `true` the callback is always async. * @param {boolean=} opt_request If `true` the callback is always async.
* The tile data is requested if not yet loaded. * The tile data is requested if not yet loaded.
@@ -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 * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse
* @protected * @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 ? ol.TileState.IDLE : ol.TileState.EMPTY,
tileUrl !== undefined ? tileUrl : '', tileUrl !== undefined ? tileUrl : '',
this.tileGrid.getTileCoordExtent(tileCoord), this.tileGrid.getTileCoordExtent(tileCoord),
this.preemptive_); this.preemptive_,
this.jsonp_);
this.tileCache.set(tileCoordKey, tile); this.tileCache.set(tileCoordKey, tile);
return tile; return tile;
} }
@@ -211,9 +265,10 @@ ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) {
* @param {string} src Image source URI. * @param {string} src Image source URI.
* @param {ol.Extent} extent Extent of the tile. * @param {ol.Extent} extent Extent of the tile.
* @param {boolean} preemptive Load the tile when visible (before it's needed). * @param {boolean} preemptive Load the tile when visible (before it's needed).
* @param {boolean} jsonp Load the tile as a script.
* @private * @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); goog.base(this, tileCoord, state);
@@ -252,6 +307,14 @@ ol.source.TileUTFGridTile_ = function(tileCoord, state, src, extent, preemptive)
* @type {Object.<string, Object>|undefined} * @type {Object.<string, Object>|undefined}
*/ */
this.data_ = null; this.data_ = null;
/**
* @private
* @type {boolean}
*/
this.jsonp_ = jsonp;
}; };
goog.inherits(ol.source.TileUTFGridTile_, ol.Tile); goog.inherits(ol.source.TileUTFGridTile_, ol.Tile);
@@ -270,10 +333,10 @@ ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) {
/** /**
* Synchronously returns data at given coordinate (if available). * Synchronously returns data at given coordinate (if available).
* @param {ol.Coordinate} coordinate Coordinate. * @param {ol.Coordinate} coordinate Coordinate.
* @return {Object} The data. * @return {*} The data.
*/ */
ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) { ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
if (!this.grid_ || !this.keys_ || !this.data_) { if (!this.grid_ || !this.keys_) {
return null; return null;
} }
var xRelative = (coordinate[0] - this.extent_[0]) / var xRelative = (coordinate[0] - this.extent_[0]) /
@@ -296,7 +359,16 @@ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
} }
code -= 32; 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;
}; };
@@ -304,7 +376,7 @@ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
* Calls the callback (synchronously by default) with the available data * Calls the callback (synchronously by default) with the available data
* for given coordinate (or `null` if not yet loaded). * for given coordinate (or `null` if not yet loaded).
* @param {ol.Coordinate} coordinate Coordinate. * @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 {T=} opt_this The object to use as `this` in the callback.
* @param {boolean=} opt_request If `true` the callback is always async. * @param {boolean=} opt_request If `true` the callback is always async.
* The tile data is requested if not yet loaded. * The tile data is requested if not yet loaded.
@@ -365,12 +437,50 @@ ol.source.TileUTFGridTile_.prototype.handleLoad_ = function(json) {
ol.source.TileUTFGridTile_.prototype.loadInternal_ = function() { ol.source.TileUTFGridTile_.prototype.loadInternal_ = function() {
if (this.state == ol.TileState.IDLE) { if (this.state == ol.TileState.IDLE) {
this.state = ol.TileState.LOADING; this.state = ol.TileState.LOADING;
ol.net.jsonp(this.src_, this.handleLoad_.bind(this), if (this.jsonp_) {
this.handleError_.bind(this)); 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 {!UTFGridJSON} */(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(event) {
this.handleError_();
};
/** /**
* Load not yet loaded URI. * Load not yet loaded URI.
*/ */
+45 -36
View File
@@ -2,11 +2,10 @@ goog.provide('ol.test.source.TileUTFGrid');
describe('ol.source.TileUTFGrid', function() { describe('ol.source.TileUTFGrid', function() {
var url = 'spec/ol/data/tileutfgrid.json';
var tileJson = null; 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 // Load and parse the UTFGrid fixture
// tests. This way we can test `handleTileJSONResponse` and also remove
// the dependency from the external service / URL.
before(function(done) { before(function(done) {
var client = new XMLHttpRequest(); var client = new XMLHttpRequest();
client.addEventListener('load', function() { client.addEventListener('load', function() {
@@ -14,68 +13,77 @@ describe('ol.source.TileUTFGrid', function() {
done(); done();
}); });
client.addEventListener('error', function() { 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(); client.send();
}); });
after(function() { after(function() {
tileJson = null; tileJson = null;
}); });
var url = 'some-tileutfgrid-url';
function getTileUTFGrid() { function getTileUTFGrid() {
var source = new ol.source.TileUTFGrid({ return new ol.source.TileUTFGrid({
url: url url: url
}); });
return source;
} }
describe('constructor', function() { describe('constructor', function() {
it('needs to be constructed with url option', 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() { expect(function() {
// no options: will throw // no options: will throw
source = new ol.source.TileUTFGrid(); return new ol.source.TileUTFGrid();
}).to.throwException(); }).to.throwException();
expect(source).to.be(undefined);
expect(function() { expect(function() {
// no url-option: will throw // no url-option: will throw
source = new ol.source.TileUTFGrid({}); return new ol.source.TileUTFGrid({});
}).to.throwException(); }).to.throwException();
expect(source).to.be(undefined);
expect(function() { expect(getTileUTFGrid()).to.be.an(ol.source.TileUTFGrid);
// 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);
}); });
it('immediately fetches the passed URL', function() { });
// spy on the jsonp method
var jsonpSpy = sinon.spy(ol.net, 'jsonp');
getTileUTFGrid(); describe('change event (ready)', function() {
expect(jsonpSpy.calledOnce).to.be(true); it('is fired when the source is ready', function(done) {
expect(jsonpSpy.lastCall.calledWith(url)).to.be(true); var source = new ol.source.TileUTFGrid({
url: url
});
expect(source.getState()).to.be(ol.source.State.LOADING);
expect(source.tileGrid).to.be(null);
// cleanup source.on('change', function(event) {
ol.net.jsonp.restore(); if (source.getState() === ol.source.State.READY) {
jsonpSpy = null; 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() { describe('#handleTileJSONResponse', function() {
@@ -281,6 +289,7 @@ describe('ol.source.TileUTFGrid', function() {
goog.require('ol.net'); goog.require('ol.net');
goog.require('ol.proj'); goog.require('ol.proj');
goog.require('ol.source.State'); goog.require('ol.source.State');
goog.require('ol.source.Tile');
goog.require('ol.source.TileUTFGrid'); goog.require('ol.source.TileUTFGrid');
goog.require('ol.tilegrid.TileGrid'); goog.require('ol.tilegrid.TileGrid');
goog.require('ol.TileState'); goog.require('ol.TileState');