Skip to content

Commit

Permalink
Merge pull request CesiumGS#6453 from AnalyticalGraphicsInc/pelias
Browse files Browse the repository at this point in the history
Add support for the Pelias geocoder
  • Loading branch information
Hannah authored Apr 17, 2018
2 parents 31bf9cd + 0c74ff8 commit c32d946
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Change Log
* Added option `logDepthBuffer` to `Viewer`. With this option there is typically a single frustum using logarithmic depth rendered. This increases performance by issuing less draw calls to the GPU and helps to avoid artifacts on the connection of two frustums. [#5851](https://github.com/AnalyticalGraphicsInc/cesium/pull/5851)
* When a log depth buffer is supported, the frustum near and far planes default to `0.1` and `1e10` respectively.
* Added `Math.log2` to compute the base 2 logarithm of a number.
* Added 'PeliasGeocoderService', which provides geocoding via a [Pelias](https://pelias.io) server.
* Added `GeocodeType` enum and use it as an optional parameter to all `GeocoderService` instances to differentiate between autocomplete and search requests.

##### Fixes :wrench:
* Fixed bugs in `TimeIntervalCollection.removeInterval`. [#6418](https://github.com/AnalyticalGraphicsInc/cesium/pull/6418).
Expand Down
32 changes: 32 additions & 0 deletions Source/Core/GeocodeType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
define([
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';

/**
* The type of geocoding to be performed by a {@link GeocoderService}.
* @exports GeocodeType
* @see Geocoder
*/
var GeocodeType = {
/**
* Perform a search where the input is considered complete.
*
* @type {Number}
* @constant
*/
SEARCH: 0,

/**
* Perform an auto-complete using partial input, typically
* reserved for providing possible results as a user is typing.
*
* @type {Number}
* @constant
*/
AUTOCOMPLETE: 1
};

return freezeObject(GeocodeType);
});
2 changes: 2 additions & 0 deletions Source/Core/GeocoderService.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ define([
* @constructor
*
* @see BingMapsGeocoderService
* @see PeliasGeocoderService
*/
function GeocoderService() {
}
Expand All @@ -25,6 +26,7 @@ define([
* @function
*
* @param {String} query The query to be sent to the geocoder service
* @param {GeocodeType} [type=GeocodeType.SEARCH] The type of geocode to perform.
* @returns {Promise<GeocoderResult[]>}
*/
GeocoderService.prototype.geocode = DeveloperError.throwInstantiationError;
Expand Down
103 changes: 103 additions & 0 deletions Source/Core/PeliasGeocoderService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
define([
'./Check',
'./defined',
'./defineProperties',
'./GeocodeType',
'./Rectangle',
'./Resource'
], function (
Check,
defined,
defineProperties,
GeocodeType,
Rectangle,
Resource) {
'use strict';

/**
* Provides geocoding via a {@link https://pelias.io/|Pelias} server.
* @alias PeliasGeocoderService
* @constructor
*
* @param {Resource|String} url The endpoint to the Pelias server.
*
* @example
* // Configure a Viewer to use the Pelias server hosted by https://geocode.earth/
* var viewer = new Cesium.Viewer('cesiumContainer', {
* geocoder: new Cesium.PeliasGeocoderService(new Cesium.Resource({
* url: 'https://api.geocode.earth/v1/',
* queryParameters: {
* api_key: '<Your geocode.earth API key>'
* }
* }))
* });
*/
function PeliasGeocoderService(url) {
//>>includeStart('debug', pragmas.debug);
Check.defined('url', url);
//>>includeEnd('debug');

this._url = Resource.createIfNeeded(url);
}

defineProperties(PeliasGeocoderService.prototype, {
/**
* The Resource used to access the Pelias endpoint.
* @type {Resource}
* @memberof {PeliasGeocoderService.prototype}
* @readonly
*/
url: {
get: function () {
return this._url;
}
}
});

/**
* @function
*
* @param {String} query The query to be sent to the geocoder service
* @param {GeocodeType} [type=GeocodeType.SEARCH] The type of geocode to perform.
* @returns {Promise<GeocoderResult[]>}
*/
PeliasGeocoderService.prototype.geocode = function(query, type) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string('query', query);
//>>includeEnd('debug');

var resource = this._url.getDerivedResource({
url: type === GeocodeType.AUTOCOMPLETE ? 'autocomplete' : 'search',
queryParameters: {
text: query
}
});

return resource.fetchJson()
.then(function (results) {
return results.features.map(function (resultObject) {
var bboxDegrees = resultObject.bbox;

// Pelias does not always provide bounding information
// so just expand the location slightly.
if (!defined(bboxDegrees)) {
var lon = resultObject.geometry.coordinates[0];
var lat = resultObject.geometry.coordinates[1];
bboxDegrees = [
lon - 0.001,
lat - 0.001,
lon + 0.001,
lat + 0.001
];
}

return {
displayName: resultObject.properties.label,
destination: Rectangle.fromDegrees(bboxDegrees[0], bboxDegrees[1], bboxDegrees[2], bboxDegrees[3])
};
});
});
};

return PeliasGeocoderService;
});
17 changes: 10 additions & 7 deletions Source/Widgets/Geocoder/GeocoderViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ define([
'../../Core/defineProperties',
'../../Core/DeveloperError',
'../../Core/Event',
'../../Core/GeocodeType',
'../../Core/Matrix4',
'../../ThirdParty/knockout',
'../../ThirdParty/when',
Expand All @@ -19,6 +20,7 @@ define([
defineProperties,
DeveloperError,
Event,
GeocodeType,
Matrix4,
knockout,
when,
Expand Down Expand Up @@ -79,7 +81,8 @@ define([
return suggestionsNotEmpty && showSuggestions;
});

this._searchCommand = createCommand(function() {
this._searchCommand = createCommand(function(geocodeType) {
geocodeType = defaultValue(geocodeType, GeocodeType.SEARCH);
that._focusTextbox = false;
if (defined(that._selectedSuggestion)) {
that.activateSuggestion(that._selectedSuggestion);
Expand All @@ -89,7 +92,7 @@ define([
if (that.isSearchInProgress) {
cancelGeocode(that);
} else {
geocode(that, that._geocoderServices);
geocode(that, that._geocoderServices, geocodeType);
}
});

Expand Down Expand Up @@ -338,13 +341,13 @@ define([
});
}

function chainPromise(promise, geocoderService, query) {
function chainPromise(promise, geocoderService, query, geocodeType) {
return promise
.then(function(result) {
if (defined(result) && result.state === 'fulfilled' && result.value.length > 0){
return result;
}
var nextPromise = geocoderService.geocode(query)
var nextPromise = geocoderService.geocode(query, geocodeType)
.then(function (result) {
return {state: 'fulfilled', value: result};
})
Expand All @@ -356,7 +359,7 @@ define([
});
}

function geocode(viewModel, geocoderServices) {
function geocode(viewModel, geocoderServices, geocodeType) {
var query = viewModel._searchText;

if (hasOnlyWhitespace(query)) {
Expand All @@ -368,7 +371,7 @@ define([

var promise = when.resolve();
for (var i = 0; i < geocoderServices.length; i++) {
promise = chainPromise(promise, geocoderServices[i], query);
promise = chainPromise(promise, geocoderServices[i], query, geocodeType);
}

viewModel._geocodePromise = promise;
Expand Down Expand Up @@ -442,7 +445,7 @@ define([
if (results.length >= 5) {
return results;
}
return service.geocode(query)
return service.geocode(query, GeocodeType.AUTOCOMPLETE)
.then(function(newResults) {
results = results.concat(newResults);
return results;
Expand Down
93 changes: 93 additions & 0 deletions Specs/Core/PeliasGeocoderServiceSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
defineSuite([
'Core/PeliasGeocoderService',
'Core/GeocodeType',
'Core/Rectangle',
'Core/Resource',
'ThirdParty/when'
], function(
PeliasGeocoderService,
GeocodeType,
Rectangle,
Resource,
when) {
'use strict';

it('constructor throws without url', function() {
expect(function() {
return new PeliasGeocoderService(undefined);
}).toThrowDeveloperError();
});

it('returns geocoder results', function () {
var service = new PeliasGeocoderService('http://test.invalid/v1/');

var query = 'some query';
var data = {
features: [{
type: "Feature",
geometry: {
type: "Point",
coordinates: [-75.172489, 39.927828]
},
properties: {
label: "1826 S 16th St, Philadelphia, PA, USA"
}
}]
};
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.features[0].properties.label);
expect(results[0].destination).toBeInstanceOf(Rectangle);
});
});

it('returns no geocoder results if Pelias has no results', function() {
var service = new PeliasGeocoderService('http://test.invalid/v1/');

var query = 'some query';
var data = { features: [] };
spyOn(Resource.prototype, 'fetchJson').and.returnValue(when.resolve(data));

return service.geocode(query)
.then(function(results) {
expect(results.length).toEqual(0);
});
});

it('calls search endpoint if specified', function () {
var service = new PeliasGeocoderService('http://test.invalid/v1/');

var query = 'some query';
var data = { features: [] };
spyOn(Resource.prototype, 'fetchJson').and.returnValue(when.resolve(data));
var getDerivedResource = spyOn(service._url, 'getDerivedResource').and.callThrough();

service.geocode(query, GeocodeType.SEARCH);
expect(getDerivedResource).toHaveBeenCalledWith({
url: 'search',
queryParameters: {
text: query
}
});
});

it('calls autocomplete endpoint if specified', function () {
var service = new PeliasGeocoderService('http://test.invalid/v1/');

var query = 'some query';
var data = { features: [] };
spyOn(Resource.prototype, 'fetchJson').and.returnValue(when.resolve(data));
var getDerivedResource = spyOn(service._url, 'getDerivedResource').and.callThrough();

service.geocode(query, GeocodeType.AUTOCOMPLETE);
expect(getDerivedResource).toHaveBeenCalledWith({
url: 'autocomplete',
queryParameters: {
text: query
}
});
});
});

0 comments on commit c32d946

Please sign in to comment.