Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the Pelias geocoder #6453

Merged
merged 1 commit into from Apr 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
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
@@ -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
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
@@ -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
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
@@ -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
}
});
});
});