diff --git a/CHANGES.md b/CHANGES.md index bf2ba5572d65..1bbc014b7874 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Change Log ##### Additions :tada: * Added `cartographicLimitRectangle` to `Globe`. Use this to limit terrain and imagery to a specific `Rectangle` area. [#6987](https://github.com/AnalyticalGraphicsInc/cesium/pull/6987) +* Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). [#7015](https://github.com/AnalyticalGraphicsInc/cesium/pull/7015) ##### Fixes :wrench: * Fixed an issue in the 3D Tiles traversal where empty tiles would be selected instead of their nearest loaded ancestors. [#7011](https://github.com/AnalyticalGraphicsInc/cesium/pull/7011) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 37aec21b64e0..05ea54f574a3 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) 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..c5cd0868739f --- /dev/null +++ b/Source/Core/OpenCageGeocoderService.js @@ -0,0 +1,128 @@ +define([ + './Cartesian3', + './Check', + './defaultValue', + './defined', + './defineProperties', + './GeocodeType', + './Rectangle', + './Resource' +], function ( + Cartesian3, + Check, + defaultValue, + 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. + * @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 Geocoder + * var viewer = new Cesium.Viewer('cesiumContainer', { + * geocoder: new Cesium.OpenCageGeocoderService('https://api.opencagedata.com/geocode/v1/', '') + * }); + */ + 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'); + + url = Resource.createIfNeeded(url); + url.appendForwardSlash(); + url.setQueryParameters({key: apiKey}); + this._url = url; + this._params = defaultValue(params, {}); + } + + defineProperties(OpenCageGeocoderService.prototype, { + /** + * The Resource used to access the OpenCage endpoint. + * @type {Resource} + * @memberof {OpenCageGeocoderService.prototype} + * @readonly + */ + url: { + 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; + } + } + }); + + /** + * @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: Object.assign(this._params, {q: query}) + }); + return resource.fetchJson() + .then(function (response) { + return response.results.map(function (resultObject) { + 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, + destination: destination + }; + }); + }); + }; + + return OpenCageGeocoderService; +}); diff --git a/Specs/Core/OpenCageGeocoderServiceSpec.js b/Specs/Core/OpenCageGeocoderServiceSpec.js new file mode 100644 index 000000000000..fa5ff7a1922a --- /dev/null +++ b/Specs/Core/OpenCageGeocoderServiceSpec.js @@ -0,0 +1,75 @@ +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('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); + + 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).toBeDefined(); + }); + }); + + 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); + }); + }); +});