From b9eca2c124a88b2232ee849f023bffd3dde32e7c Mon Sep 17 00:00:00 2001 From: Mohamed marrouchi Date: Wed, 5 Sep 2018 17:39:04 +0100 Subject: [PATCH 1/4] Add support for OpenCage geocoder --- CHANGES.md | 1 + CONTRIBUTORS.md | 4 +- Source/Core/GeocoderService.js | 1 + Source/Core/OpenCageGeocoderService.js | 96 +++++++++++++++++++++++ Specs/Core/OpenCageGeocoderServiceSpec.js | 69 ++++++++++++++++ 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Source/Core/OpenCageGeocoderService.js create mode 100644 Specs/Core/OpenCageGeocoderServiceSpec.js diff --git a/CHANGES.md b/CHANGES.md index d1fb853b1431..3f6eed7dcaf4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -223,6 +223,7 @@ Change Log * Added ability to invoke `sampleTerrain` from node.js to enable offline terrain sampling * Added more ParticleSystem Sandcastle examples for rocket and comet tails and weather. [#6375](https://github.com/AnalyticalGraphicsInc/cesium/pull/6375) * Added color and scale attributes to the `ParticleSystem` class constructor. When defined the variables override startColor and endColor and startScale and endScale. [#6429](https://github.com/AnalyticalGraphicsInc/cesium/pull/6429) +* Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). ##### Fixes :wrench: * Fixed bugs in `TimeIntervalCollection.removeInterval`. [#6418](https://github.com/AnalyticalGraphicsInc/cesium/pull/6418). diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 37aec21b64e0..05ec845251ae 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -112,6 +112,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Jeremy Marzano](https://github.com/JeremyMarzano-ISPA/) * [Orbit Logic](http://www.orbitlogic.com) * [Roderick Green](https://github.com/roderickgreen/) +* [Hexastack](https://www.hexastack.com) + * [Mohamed Marrouchi](https://github.com/marrouchi/) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) @@ -123,7 +125,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Ayudh Das](https://github.com/ayudhDas) * [You Lu](https://github.com/YouLu) * [David Hite](https://github.com/dav3hit3) -* [Kevin Ring](https://github.com/kring) +* [Kevin Ring](https://gOpenCageithub.com/kring) * [M.Eng. René Schwarz](https://github.com/DigNative) * [Gilles Cébélieu (IGN France)](https://github.com/gcebelieu) * [Guillaume Beraudo](https://github.com/gberaudo) diff --git a/Source/Core/GeocoderService.js b/Source/Core/GeocoderService.js index 2d3109c6c99d..91e2de5a448e 100644 --- a/Source/Core/GeocoderService.js +++ b/Source/Core/GeocoderService.js @@ -18,6 +18,7 @@ define([ * * @see BingMapsGeocoderService * @see PeliasGeocoderService + * @see OpenCageGeocoderService */ function GeocoderService() { } diff --git a/Source/Core/OpenCageGeocoderService.js b/Source/Core/OpenCageGeocoderService.js new file mode 100644 index 000000000000..4c8c25a358f7 --- /dev/null +++ b/Source/Core/OpenCageGeocoderService.js @@ -0,0 +1,96 @@ +define([ + './Cartesian3', + './Check', + './defined', + './defineProperties', + './GeocodeType', + './Rectangle', + './Resource' +], function ( + Cartesian3, + Check, + defined, + defineProperties, + GeocodeType, + Rectangle, + Resource) { + 'use strict'; + + /** + * Provides geocoding via a {@link https://opencagedata.com/|OpenCage} server. + * @alias OpenCageGeocoderService + * @constructor + * + * @param {Resource|String} url The endpoint to the OpenCage server. + * @param {String} apiKey The OpenCage API Key. + * + * @example + * // Configure a Viewer to use the OpenCage server hosted by https://geocode.earth/ + * var viewer = new Cesium.Viewer('cesiumContainer', { + * geocoder: new Cesium.OpenCageGeocoderService(new Cesium.Resource({ + * url: 'https://api.opencagedata.com/geocode/v1/', + * queryParameters: { + * key: '' + * } + * })) + * }); + */ + function OpenCageGeocoderService(url, apiKey) { + //>>includeStart('debug', pragmas.debug); + Check.defined('url', url); + Check.defined('apiKey', apiKey); + //>>includeEnd('debug'); + + this._url = Resource.createIfNeeded(url); + this._url.appendForwardSlash(); + this._url.setQueryParameters({key: apiKey}); + } + + defineProperties(OpenCageGeocoderService.prototype, { + /** + * The Resource used to access the OpenCage endpoint. + * @type {Resource} + * @memberof {OpenCageGeocoderService.prototype} + * @readonly + */ + url: { + get: function () { + return this._url; + } + } + }); + + /** + * @function + * + * @param {String} query The query to be sent to the geocoder service + * @returns {Promise} + */ + OpenCageGeocoderService.prototype.geocode = function(query) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('query', query); + //>>includeEnd('debug'); + + var resource = this._url.getDerivedResource({ + url: 'json', + queryParameters: { + q: query + } + }); + return resource.fetchJson() + .then(function (response) { + return response.results.map(function (resultObject) { + var lon = resultObject.geometry.lat; + var lat = resultObject.geometry.lng; + var destination = Cartesian3.fromDegrees(lon, lat); + + return { + displayName: resultObject.formatted, + destination: destination + }; + }); + }); + }; + + return OpenCageGeocoderService; +}); diff --git a/Specs/Core/OpenCageGeocoderServiceSpec.js b/Specs/Core/OpenCageGeocoderServiceSpec.js new file mode 100644 index 000000000000..ae15178410fb --- /dev/null +++ b/Specs/Core/OpenCageGeocoderServiceSpec.js @@ -0,0 +1,69 @@ +defineSuite([ + 'Core/OpenCageGeocoderService', + 'Core/GeocodeType', + 'Core/Cartesian3', + 'Core/Resource', + 'ThirdParty/when' + ], function( + OpenCageGeocoderService, + GeocodeType, + Cartesian3, + Resource, + when) { + 'use strict'; + + var endpoint = 'https://api.opencagedata.com/geocode/v1/'; + var apiKey = 'c2a490d593b14612aefa6ec2e6b77c47'; + + it('constructor throws without url', function() { + expect(function() { + return new OpenCageGeocoderService(undefined); + }).toThrowDeveloperError(); + }); + + it('returns geocoder results', function () { + var service = new OpenCageGeocoderService(endpoint, apiKey); + + var query = '-22.6792,+14.5272'; + var data = { + results: [{ + bounds: { + northeast: { + lat: -22.6790826, + lng: 14.5269016 + }, + southwest: { + lat: -22.6792826, + lng: 14.5267016 + } + }, + formatted: 'Beryl\'s Restaurant, Woermann St, Swakopmund, Namibia', + geometry: { + lat: -22.6795394, + lng: 14.5276006 + } + }] + }; + spyOn(Resource.prototype, 'fetchJson').and.returnValue(when.resolve(data)); + + return service.geocode(query) + .then(function(results) { + expect(results.length).toEqual(1); + expect(results[0].displayName).toEqual(data.results[0].formatted); + expect(results[0].destination).toBeInstanceOf(Cartesian3); + }); + }); + + it('returns no geocoder results if OpenCage has no results', function() { + var service = new OpenCageGeocoderService(endpoint, apiKey); + + var query = ''; + var data = { results: [] }; + spyOn(Resource.prototype, 'fetchJson').and.returnValue(when.resolve(data)); + + return service.geocode(query) + .then(function(results) { + expect(results.length).toEqual(0); + }); + }); +}); From ccef75b5e2be05b04c2a80f07534068405857aa5 Mon Sep 17 00:00:00 2001 From: Mohamed marrouchi Date: Thu, 6 Sep 2018 13:43:48 +0100 Subject: [PATCH 2/4] Add optional params + bbox + minor fixes --- CONTRIBUTORS.md | 2 +- Source/Core/OpenCageGeocoderService.js | 38 ++++++++++++++++++----- Specs/Core/OpenCageGeocoderServiceSpec.js | 8 ++++- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 05ec845251ae..05ea54f574a3 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -125,7 +125,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Ayudh Das](https://github.com/ayudhDas) * [You Lu](https://github.com/YouLu) * [David Hite](https://github.com/dav3hit3) -* [Kevin Ring](https://gOpenCageithub.com/kring) +* [Kevin Ring](https://github.com/kring) * [M.Eng. René Schwarz](https://github.com/DigNative) * [Gilles Cébélieu (IGN France)](https://github.com/gcebelieu) * [Guillaume Beraudo](https://github.com/gberaudo) diff --git a/Source/Core/OpenCageGeocoderService.js b/Source/Core/OpenCageGeocoderService.js index 4c8c25a358f7..0721bd5cfa54 100644 --- a/Source/Core/OpenCageGeocoderService.js +++ b/Source/Core/OpenCageGeocoderService.js @@ -1,6 +1,7 @@ define([ './Cartesian3', './Check', + './defaultValue', './defined', './defineProperties', './GeocodeType', @@ -9,6 +10,7 @@ define([ ], function ( Cartesian3, Check, + defaultValue, defined, defineProperties, GeocodeType, @@ -64,25 +66,47 @@ define([ * @function * * @param {String} query The query to be sent to the geocoder service + * @param {Object} [params] An object with the following properties (See https://opencagedata.com/api#forward-opt): + * @param {Number} [params.abbrv] When set to 1 we attempt to abbreviate and shorten the formatted string we return. + * @param {Number} [options.add_request] When set to 1 the various request parameters are added to the response for ease of debugging. + * @param {String} [options.bounds] Provides the geocoder with a hint to the region that the query resides in. + * @param {String} [options.countrycode] Restricts the results to the specified country or countries (as defined by the ISO 3166-1 Alpha 2 standard). + * @param {String} [options.jsonp] Wraps the returned JSON with a function name. + * @param {String} [options.language] An IETF format language code. + * @param {Number} [options.limit] The maximum number of results we should return. + * @param {Number} [options.min_confidence] An integer from 1-10. Only results with at least this confidence will be returned. + * @param {Number} [options.no_annotations] When set to 1 results will not contain annotations. + * @param {Number} [options.no_dedupe] When set to 1 results will not be deduplicated. + * @param {Number} [options.no_record] When set to 1 the query contents are not logged. + * @param {Number} [options.pretty] When set to 1 results are 'pretty' printed for easier reading. Useful for debugging. + * @param {String} [options.proximity] Provides the geocoder with a hint to bias results in favour of those closer to the specified location (For example: 41.40139,2.12870). * @returns {Promise} */ - OpenCageGeocoderService.prototype.geocode = function(query) { + OpenCageGeocoderService.prototype.geocode = function(query, params) { //>>includeStart('debug', pragmas.debug); Check.typeOf.string('query', query); + if (defined(params)) { + Check.typeOf.object('value', params); + } //>>includeEnd('debug'); var resource = this._url.getDerivedResource({ url: 'json', - queryParameters: { - q: query - } + queryParameters: Object.assign(defaultValue(params, {}), {q: query}) }); return resource.fetchJson() .then(function (response) { return response.results.map(function (resultObject) { - var lon = resultObject.geometry.lat; - var lat = resultObject.geometry.lng; - var destination = Cartesian3.fromDegrees(lon, lat); + var destination; + var bounds = resultObject.bounds; + + if (defined(bounds)) { + destination = Rectangle.fromDegrees(bounds.southwest.lng, bounds.southwest.lat, bounds.northeast.lng, bounds.northeast.lat); + } else { + var lon = resultObject.geometry.lat; + var lat = resultObject.geometry.lng; + destination = Cartesian3.fromDegrees(lon, lat); + } return { displayName: resultObject.formatted, diff --git a/Specs/Core/OpenCageGeocoderServiceSpec.js b/Specs/Core/OpenCageGeocoderServiceSpec.js index ae15178410fb..fa5ff7a1922a 100644 --- a/Specs/Core/OpenCageGeocoderServiceSpec.js +++ b/Specs/Core/OpenCageGeocoderServiceSpec.js @@ -21,6 +21,12 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('constructor throws without API Key', function() { + expect(function() { + return new OpenCageGeocoderService(endpoint, undefined); + }).toThrowDeveloperError(); + }); + it('returns geocoder results', function () { var service = new OpenCageGeocoderService(endpoint, apiKey); @@ -50,7 +56,7 @@ defineSuite([ .then(function(results) { expect(results.length).toEqual(1); expect(results[0].displayName).toEqual(data.results[0].formatted); - expect(results[0].destination).toBeInstanceOf(Cartesian3); + expect(results[0].destination).toBeDefined(); }); }); From 395a795f0abdb60815026997e1335d10fdd5ad98 Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Tue, 11 Sep 2018 22:39:57 +0100 Subject: [PATCH 3/4] Move addition up to v1.50 --- CHANGES.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 3f6eed7dcaf4..c120fa32a3dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ Change Log ========== +### 1.50 + +##### Additions :tada: +* Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). +[#7015](https://github.com/AnalyticalGraphicsInc/cesium/pull/7015) + ### 1.49 - 2018-09-04 ##### Breaking Changes :mega: @@ -223,7 +229,6 @@ Change Log * Added ability to invoke `sampleTerrain` from node.js to enable offline terrain sampling * Added more ParticleSystem Sandcastle examples for rocket and comet tails and weather. [#6375](https://github.com/AnalyticalGraphicsInc/cesium/pull/6375) * Added color and scale attributes to the `ParticleSystem` class constructor. When defined the variables override startColor and endColor and startScale and endScale. [#6429](https://github.com/AnalyticalGraphicsInc/cesium/pull/6429) -* Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). ##### Fixes :wrench: * Fixed bugs in `TimeIntervalCollection.removeInterval`. [#6418](https://github.com/AnalyticalGraphicsInc/cesium/pull/6418). From bbc738d4a506b09a925b367fb1b6f514aa14202f Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Tue, 11 Sep 2018 22:40:52 +0100 Subject: [PATCH 4/4] Move params into the contructor + fix the example code --- Source/Core/OpenCageGeocoderService.js | 68 ++++++++++++++------------ 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/Source/Core/OpenCageGeocoderService.js b/Source/Core/OpenCageGeocoderService.js index 0721bd5cfa54..c5cd0868739f 100644 --- a/Source/Core/OpenCageGeocoderService.js +++ b/Source/Core/OpenCageGeocoderService.js @@ -25,27 +25,41 @@ define([ * * @param {Resource|String} url The endpoint to the OpenCage server. * @param {String} apiKey The OpenCage API Key. + * @param {Object} [params] An object with the following properties (See https://opencagedata.com/api#forward-opt): + * @param {Number} [params.abbrv] When set to 1 we attempt to abbreviate and shorten the formatted string we return. + * @param {Number} [options.add_request] When set to 1 the various request parameters are added to the response for ease of debugging. + * @param {String} [options.bounds] Provides the geocoder with a hint to the region that the query resides in. + * @param {String} [options.countrycode] Restricts the results to the specified country or countries (as defined by the ISO 3166-1 Alpha 2 standard). + * @param {String} [options.jsonp] Wraps the returned JSON with a function name. + * @param {String} [options.language] An IETF format language code. + * @param {Number} [options.limit] The maximum number of results we should return. + * @param {Number} [options.min_confidence] An integer from 1-10. Only results with at least this confidence will be returned. + * @param {Number} [options.no_annotations] When set to 1 results will not contain annotations. + * @param {Number} [options.no_dedupe] When set to 1 results will not be deduplicated. + * @param {Number} [options.no_record] When set to 1 the query contents are not logged. + * @param {Number} [options.pretty] When set to 1 results are 'pretty' printed for easier reading. Useful for debugging. + * @param {String} [options.proximity] Provides the geocoder with a hint to bias results in favour of those closer to the specified location (For example: 41.40139,2.12870). * * @example - * // Configure a Viewer to use the OpenCage server hosted by https://geocode.earth/ + * // Configure a Viewer to use the OpenCage Geocoder * var viewer = new Cesium.Viewer('cesiumContainer', { - * geocoder: new Cesium.OpenCageGeocoderService(new Cesium.Resource({ - * url: 'https://api.opencagedata.com/geocode/v1/', - * queryParameters: { - * key: '' - * } - * })) + * geocoder: new Cesium.OpenCageGeocoderService('https://api.opencagedata.com/geocode/v1/', '') * }); */ - function OpenCageGeocoderService(url, apiKey) { + function OpenCageGeocoderService(url, apiKey, params) { //>>includeStart('debug', pragmas.debug); Check.defined('url', url); Check.defined('apiKey', apiKey); + if (defined(params)) { + Check.typeOf.object('params', params); + } //>>includeEnd('debug'); - this._url = Resource.createIfNeeded(url); - this._url.appendForwardSlash(); - this._url.setQueryParameters({key: apiKey}); + url = Resource.createIfNeeded(url); + url.appendForwardSlash(); + url.setQueryParameters({key: apiKey}); + this._url = url; + this._params = defaultValue(params, {}); } defineProperties(OpenCageGeocoderService.prototype, { @@ -59,6 +73,17 @@ define([ get: function () { return this._url; } + }, + /** + * Optional params passed to OpenCage in order to customize geocoding + * @type {Object} + * @memberof {OpenCageGeocoderService.prototype} + * @readonly + */ + params: { + get: function () { + return this._params; + } } }); @@ -66,33 +91,16 @@ define([ * @function * * @param {String} query The query to be sent to the geocoder service - * @param {Object} [params] An object with the following properties (See https://opencagedata.com/api#forward-opt): - * @param {Number} [params.abbrv] When set to 1 we attempt to abbreviate and shorten the formatted string we return. - * @param {Number} [options.add_request] When set to 1 the various request parameters are added to the response for ease of debugging. - * @param {String} [options.bounds] Provides the geocoder with a hint to the region that the query resides in. - * @param {String} [options.countrycode] Restricts the results to the specified country or countries (as defined by the ISO 3166-1 Alpha 2 standard). - * @param {String} [options.jsonp] Wraps the returned JSON with a function name. - * @param {String} [options.language] An IETF format language code. - * @param {Number} [options.limit] The maximum number of results we should return. - * @param {Number} [options.min_confidence] An integer from 1-10. Only results with at least this confidence will be returned. - * @param {Number} [options.no_annotations] When set to 1 results will not contain annotations. - * @param {Number} [options.no_dedupe] When set to 1 results will not be deduplicated. - * @param {Number} [options.no_record] When set to 1 the query contents are not logged. - * @param {Number} [options.pretty] When set to 1 results are 'pretty' printed for easier reading. Useful for debugging. - * @param {String} [options.proximity] Provides the geocoder with a hint to bias results in favour of those closer to the specified location (For example: 41.40139,2.12870). * @returns {Promise} */ - OpenCageGeocoderService.prototype.geocode = function(query, params) { + OpenCageGeocoderService.prototype.geocode = function(query) { //>>includeStart('debug', pragmas.debug); Check.typeOf.string('query', query); - if (defined(params)) { - Check.typeOf.object('value', params); - } //>>includeEnd('debug'); var resource = this._url.getDerivedResource({ url: 'json', - queryParameters: Object.assign(defaultValue(params, {}), {q: query}) + queryParameters: Object.assign(this._params, {q: query}) }); return resource.fetchJson() .then(function (response) {