From 8389c16315453e52992d28c62a604f4518d212d5 Mon Sep 17 00:00:00 2001 From: Alessandro Isaacs Date: Fri, 30 Oct 2015 16:15:04 -0700 Subject: [PATCH 1/5] Add CartoDB tile source. --- examples/cartodb.html | 26 +++++ examples/cartodb.js | 48 ++++++++ externs/olx.js | 98 +++++++++++++++++ src/ol/source/cartodb.js | 127 ++++++++++++++++++++++ test/spec/ol/source/cartodbsource.test.js | 18 +++ 5 files changed, 317 insertions(+) create mode 100644 examples/cartodb.html create mode 100644 examples/cartodb.js create mode 100644 src/ol/source/cartodb.js create mode 100644 test/spec/ol/source/cartodbsource.test.js diff --git a/examples/cartodb.html b/examples/cartodb.html new file mode 100644 index 0000000000..6d3fb99ebc --- /dev/null +++ b/examples/cartodb.html @@ -0,0 +1,26 @@ +--- +template: example.html +title: CartoDB source example +shortdesc: Example of a cartodb map. +docs: > + A simple map with customized Attribution control. +tags: "simple, openstreetmap, attribution" +--- +
+
+
+
+
+ +
+
+
diff --git a/examples/cartodb.js b/examples/cartodb.js new file mode 100644 index 0000000000..8822a7ebf9 --- /dev/null +++ b/examples/cartodb.js @@ -0,0 +1,48 @@ +goog.require('ol.Map'); +goog.require('ol.View'); +goog.require('ol.layer.Tile'); +goog.require('ol.source.CartoDB'); +goog.require('ol.source.OSM'); + +var mapConfig = { + 'layers': [{ + 'type': 'cartodb', + 'options': { + 'cartocss_version': '2.1.1', + 'cartocss': '#layer { polygon-fill: #F00; }', + 'sql': 'select * from european_countries_e where area > 0' + } + }] +}; + +var cartoDBSource = new ol.source.CartoDB({ + account: 'documentation', + config: mapConfig +}); + +var map = new ol.Map({ + layers: [ + new ol.layer.Tile({ + source: new ol.source.OSM() + }), + new ol.layer.Tile({ + source: cartoDBSource + }) + ], + target: 'map', + view: new ol.View({ + center: [0, 0], + zoom: 2 + }) +}); + +function setArea(n) { + mapConfig.layers[0].options.sql = + 'select * from european_countries_e where area > ' + n; + cartoDBSource.setConfig(mapConfig); +} + + +document.getElementById('country-area').addEventListener('change', function() { + setArea(this.value); +}); diff --git a/externs/olx.js b/externs/olx.js index 3178bea698..16dfcbab43 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -6209,6 +6209,104 @@ olx.source.XYZOptions.prototype.urls; */ olx.source.XYZOptions.prototype.wrapX; +/** + * @typedef {{attributions: (Array.|undefined), + * crossOrigin: (null|string|undefined), + * logo: (string|olx.LogoOptions|undefined), + * projection: ol.proj.ProjectionLike, + * maxZoom: (number|undefined), + * minZoom: (number|undefined), + * wrapX: (boolean|undefined), + * config: (Object|undefined), + * map: string, + * account: (string|undefined)}} + * @api + */ +olx.source.CartoDBOptions; + + +/** + * Attributions. + * @type {Array.|undefined} + * @api stable + */ +olx.source.CartoDBOptions.prototype.attributions; + + +/** + * The `crossOrigin` attribute for loaded images. Note that you must provide a + * `crossOrigin` value if you are using the WebGL renderer or if you want to + * access pixel data with the Canvas renderer. See + * {@link https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image} + * for more detail. + * @type {null|string|undefined} + * @api stable + */ +olx.source.CartoDBOptions.prototype.crossOrigin; + + +/** + * Logo. + * @type {string|olx.LogoOptions|undefined} + * @api stable + */ +olx.source.CartoDBOptions.prototype.logo; + + +/** + * Projection. Default is `EPSG:3857`. + * @type {ol.proj.ProjectionLike} + * @api + */ +olx.source.CartoDBOptions.prototype.projection; + + +/** + * Optional max zoom level. Default is `18`. + * @type {number|undefined} + * @api + */ +olx.source.CartoDBOptions.prototype.maxZoom; + + +/** + * Whether to wrap the world horizontally. Default is `true`. + * @type {boolean|undefined} + * @api + */ +olx.source.CartoDBOptions.prototype.wrapX; + + +/** + * If using anonymous maps, the CartoDB config to use. See + * {@link http://docs.cartodb.com/cartodb-platform/maps-api.html#anonymous-maps} + * for more detail. + * If using named maps, a key-value lookup with the template parameters. + * See {@link http://docs.cartodb.com/cartodb-platform/maps-api.html#named-maps} + * for more detail. + * @type {Object|undefined} + * @api + */ +olx.source.CartoDBOptions.prototype.config; + + +/** + * If using named maps, this will be the name of the template to load. + * See {@link http://docs.cartodb.com/cartodb-platform/maps-api.html#named-maps} + * for more detail. + * @type {boolean|undefined} + * @api + */ +olx.source.CartoDBOptions.prototype.map; + + +/** + * CartoDB account name + * @type {string} + * @api + */ +olx.source.CartoDBOptions.prototype.account; + /** * @typedef {{attributions: (olx.source.AttributionOption|undefined), diff --git a/src/ol/source/cartodb.js b/src/ol/source/cartodb.js new file mode 100644 index 0000000000..d41af2a54f --- /dev/null +++ b/src/ol/source/cartodb.js @@ -0,0 +1,127 @@ +goog.provide('ol.source.CartoDB'); + +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.net.EventType'); +goog.require('goog.net.XhrIo'); +goog.require('goog.net.XhrIo.ResponseType'); +goog.require('ol.source.XYZ'); + + +/** + * @classdesc + * Layer source for the CartoDB tiles. + * + * @constructor + * @extends {ol.source.XYZ} + * @param {olx.source.CartoDBOptions} options CartoDB options. + * @api + */ +ol.source.CartoDB = function(options) { + this.account_ = options.account; + this.mapId_ = options.map || ''; + this.config_ = options.config || {}; + this.templateCache_ = {}; + delete options.map; + goog.base(this, options); + this.initializeMap_(); +}; +goog.inherits(ol.source.CartoDB, ol.source.XYZ); + + +/** + * Returns the current config. + * @return {Object} The current configuration. + * @api + */ +ol.source.CartoDB.prototype.getConfig = function() { + return this.config_; +}; + + +/** + * Updates the carto db config. + * @param {Object} config a key-value lookup. Values will replace current values + * in the config. + * @api + */ +ol.source.CartoDB.prototype.updateConfig = function(config) { + for (var key in config) { + this.config_[key] = config[key]; + } + this.initializeMap_(); +}; + + +/** + * Sets the CartoDB config + * @param {Object} config In the case of anonymous maps, a CartoDB configuration + * object. + * If using named maps, a key-value lookup with the template parameters. + */ +ol.source.CartoDB.prototype.setConfig = function(config) { + this.config_ = config || {}; + this.initializeMap_(); +}; + + +/** + * Issue a request to initialize the CartoDB map. + * @private + */ +ol.source.CartoDB.prototype.initializeMap_ = function() { + var paramHash = JSON.stringify(this.config_); + if (this.templateCache_[paramHash]) { + this.applyTemplate_(this.templateCache_[paramHash]); + return; + } + var protocol = window.location.protocol; + var mapUrl = protocol + '//' + this.account_ + + '.cartodb.com/api/v1/map'; + + if (this.mapId_) { + mapUrl += '/named/' + this.mapId_; + } + + var xhrIo = new goog.net.XhrIo(); + xhrIo.setResponseType(goog.net.XhrIo.ResponseType.TEXT); + xhrIo.setWithCredentials(false); + goog.events.listen(xhrIo, goog.net.EventType.COMPLETE, + this.handleInitResponse_.bind(this, paramHash)); + xhrIo.send(mapUrl, + 'POST', + JSON.stringify(this.config_), + {'Content-Type': 'application/json'}); +}; + + +/** + * Handle map initialization response. + * @param {string} paramHash a hash representing the parameter set that was used + * for the request + * @param {Event} event Event. + * @private + */ +ol.source.CartoDB.prototype.handleInitResponse_ = function(paramHash, event) { + var xhrIo = event.target; + goog.asserts.assertInstanceof(xhrIo, goog.net.XhrIo, + 'event.target/xhrIo is an instance of goog.net.XhrIo'); + var data = xhrIo.getResponseJson(); + if (xhrIo.isSuccess()) { + this.applyTemplate_(data); + } + this.templateCache_[paramHash] = data; +}; + + +/** + * Apply the new tile urls returned by carto db + * @param {Object} data Result of carto db call. + * @private + */ +ol.source.CartoDB.prototype.applyTemplate_ = function(data) { + var layerId = data['layergroupid']; + var tilesUrl = 'https://' + data['cdn_url']['https'] + '/' + this.account_ + + '/api/v1/map/' + layerId + '/{z}/{x}/{y}.png'; + this.setUrl(tilesUrl); +}; diff --git a/test/spec/ol/source/cartodbsource.test.js b/test/spec/ol/source/cartodbsource.test.js new file mode 100644 index 0000000000..9f62f6981c --- /dev/null +++ b/test/spec/ol/source/cartodbsource.test.js @@ -0,0 +1,18 @@ +goog.provide('ol.test.source.CartoDBSource'); + +goog.require('ol.source.CartoDB'); +goog.require('ol.source.XYZ'); + +describe('ol.source.CartoDB', function() { + + describe('constructor', function() { + it('returns a CartoDB source', function() { + var source = new ol.source.CartoDB({ + map: 'example', + config: {} + }); + expect(source).to.be.a(ol.source.XYZ); + expect(source).to.be.a(ol.source.CartoDB); + }); + }); +}); From 94998c8ec7f3b3928384d3ec64fe809a6a5476ba Mon Sep 17 00:00:00 2001 From: Alessandro Isaacs Date: Wed, 2 Mar 2016 14:21:45 -0800 Subject: [PATCH 2/5] Update olx --- externs/olx.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 16dfcbab43..2528159664 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -6218,7 +6218,7 @@ olx.source.XYZOptions.prototype.wrapX; * minZoom: (number|undefined), * wrapX: (boolean|undefined), * config: (Object|undefined), - * map: string, + * map: (string|undefined), * account: (string|undefined)}} * @api */ @@ -6294,7 +6294,7 @@ olx.source.CartoDBOptions.prototype.config; * If using named maps, this will be the name of the template to load. * See {@link http://docs.cartodb.com/cartodb-platform/maps-api.html#named-maps} * for more detail. - * @type {boolean|undefined} + * @type {string|undefined} * @api */ olx.source.CartoDBOptions.prototype.map; From 2fd6352a57bc35f3812a5d8f5614f4f95cebb7d8 Mon Sep 17 00:00:00 2001 From: Alessandro Isaacs Date: Wed, 2 Mar 2016 14:21:57 -0800 Subject: [PATCH 3/5] Use https always --- src/ol/source/cartodb.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ol/source/cartodb.js b/src/ol/source/cartodb.js index d41af2a54f..b92c59eae9 100644 --- a/src/ol/source/cartodb.js +++ b/src/ol/source/cartodb.js @@ -75,8 +75,7 @@ ol.source.CartoDB.prototype.initializeMap_ = function() { this.applyTemplate_(this.templateCache_[paramHash]); return; } - var protocol = window.location.protocol; - var mapUrl = protocol + '//' + this.account_ + + var mapUrl = 'https://' + this.account_ + '.cartodb.com/api/v1/map'; if (this.mapId_) { From 4a33db370b37e2ae153c26846a8ff4b8343758a3 Mon Sep 17 00:00:00 2001 From: Alessandro Isaacs Date: Wed, 2 Mar 2016 14:35:49 -0800 Subject: [PATCH 4/5] Update example yml --- examples/cartodb.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cartodb.html b/examples/cartodb.html index 6d3fb99ebc..11f43a06b3 100644 --- a/examples/cartodb.html +++ b/examples/cartodb.html @@ -1,9 +1,9 @@ --- -template: example.html +layout: example.html title: CartoDB source example shortdesc: Example of a cartodb map. docs: > - A simple map with customized Attribution control. + A simple example with an anonymous cartodb map tags: "simple, openstreetmap, attribution" ---
From 85f11b093db54767202a803ea4abcd8b979ca341 Mon Sep 17 00:00:00 2001 From: Alessandro Isaacs Date: Wed, 30 Mar 2016 09:00:47 -0700 Subject: [PATCH 5/5] Use XHR --- src/ol/source/cartodb.js | 48 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/ol/source/cartodb.js b/src/ol/source/cartodb.js index b92c59eae9..afb9c8c262 100644 --- a/src/ol/source/cartodb.js +++ b/src/ol/source/cartodb.js @@ -1,10 +1,6 @@ goog.provide('ol.source.CartoDB'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.net.EventType'); -goog.require('goog.net.XhrIo'); -goog.require('goog.net.XhrIo.ResponseType'); +goog.require('ol.source.State'); goog.require('ol.source.XYZ'); @@ -82,15 +78,12 @@ ol.source.CartoDB.prototype.initializeMap_ = function() { mapUrl += '/named/' + this.mapId_; } - var xhrIo = new goog.net.XhrIo(); - xhrIo.setResponseType(goog.net.XhrIo.ResponseType.TEXT); - xhrIo.setWithCredentials(false); - goog.events.listen(xhrIo, goog.net.EventType.COMPLETE, - this.handleInitResponse_.bind(this, paramHash)); - xhrIo.send(mapUrl, - 'POST', - JSON.stringify(this.config_), - {'Content-Type': 'application/json'}); + var client = new XMLHttpRequest(); + client.addEventListener('load', this.handleInitResponse_.bind(this, paramHash)); + client.addEventListener('error', this.handleInitError_.bind(this)); + client.open('POST', mapUrl); + client.setRequestHeader('Content-type', 'application/json'); + client.send(JSON.stringify(this.config_)); }; @@ -102,16 +95,29 @@ ol.source.CartoDB.prototype.initializeMap_ = function() { * @private */ ol.source.CartoDB.prototype.handleInitResponse_ = function(paramHash, event) { - var xhrIo = event.target; - goog.asserts.assertInstanceof(xhrIo, goog.net.XhrIo, - 'event.target/xhrIo is an instance of goog.net.XhrIo'); - var data = xhrIo.getResponseJson(); - if (xhrIo.isSuccess()) { - this.applyTemplate_(data); + var client = /** @type {XMLHttpRequest} */ (event.target); + if (client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {Object} */(JSON.parse(client.responseText)); + } catch (err) { + this.setState(ol.source.State.ERROR); + return; + } + this.applyTemplate_(response); + this.templateCache_[paramHash] = response; + } else { + this.setState(ol.source.State.ERROR); } - this.templateCache_[paramHash] = data; }; +/** + * @private + * @param {Event} event Event. + */ +ol.source.CartoDB.prototype.handleInitError_ = function(event) { + this.setState(ol.source.State.ERROR); +} /** * Apply the new tile urls returned by carto db