Skip to content

Commit

Permalink
Merge pull request #6915 from AnalyticalGraphicsInc/geocoder-command
Browse files Browse the repository at this point in the history
Add optional custom geocoder callback function
  • Loading branch information
mramato authored Aug 14, 2018
2 parents 7e459c8 + 32bd0c4 commit 8fa64c1
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Change Log
##### Additions :tada:
* Added `ClippingPlaneCollection.planeAdded` and `ClippingPlaneCollection.planeRemoved` events. `planeAdded` is raised when a new plane is added to the collection and `planeRemoved` is raised when a plane is removed. [#6875](https://github.com/AnalyticalGraphicsInc/cesium/pull/6875)
* Added `Matrix4.setScale` for setting the scale on an affine transformation matrix [#6888](https://github.com/AnalyticalGraphicsInc/cesium/pull/6888)
* Added `GeocoderViewModel.destinationFound` for specifying a function that is called upon a successful geocode. The default behavior is to fly to the destination found by the geocoder. [#6915](https://github.com/AnalyticalGraphicsInc/cesium/pull/6915)

##### Fixes :wrench:
* The Geocoder widget now takes terrain altitude into account when calculating its final destination.
Expand Down
8 changes: 8 additions & 0 deletions Source/Widgets/Geocoder/Geocoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ define([
* @param {GeocoderService[]} [options.geocoderServices] The geocoder services to be used
* @param {Boolean} [options.autoComplete = true] True if the geocoder should query as the user types to autocomplete
* @param {Number} [options.flightDuration=1.5] The duration of the camera flight to an entered location, in seconds.
* @param {Geocoder~DestinationFoundFunction} [options.destinationFound=GeocoderViewModel.flyToDestination] A callback function that is called after a successful geocode. If not supplied, the default behavior is to fly the camera to the result destination.
*/
function Geocoder(options) {
//>>includeStart('debug', pragmas.debug);
Expand Down Expand Up @@ -210,5 +211,12 @@ css: { active: $data === $parent._selectedSuggestion }');
return destroyObject(this);
};

/**
* A function that handles the result of a successful geocode.
* @callback Geocoder~DestinationFoundFunction
* @param {GeocoderViewModel} viewModel The view model.
* @param {Cartesian3|Rectangle} destination The destination result of the geocode.
*/

return Geocoder;
});
33 changes: 24 additions & 9 deletions Source/Widgets/Geocoder/GeocoderViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ define([
* If more than one are supplied, suggestions will be gathered for the geocoders that support it,
* and if no suggestion is selected the result from the first geocoder service wil be used.
* @param {Number} [options.flightDuration] The duration of the camera flight to an entered location, in seconds.
* @param {Geocoder~DestinationFoundFunction} [options.destinationFound=GeocoderViewModel.flyToDestination] A callback function that is called after a successful geocode. If not supplied, the default behavior is to fly the camera to the result destination.
*/
function GeocoderViewModel(options) {
//>>includeStart('debug', pragmas.debug);
Expand Down Expand Up @@ -77,9 +78,7 @@ define([
this._suggestions = [];
this._selectedSuggestion = undefined;
this._showSuggestions = true;
this._updateCamera = updateCamera;
this._adjustSuggestionsScroll = adjustSuggestionsScroll;
this._updateSearchSuggestions = updateSearchSuggestions;

this._handleArrowDown = handleArrowDown;
this._handleArrowUp = handleArrowUp;

Expand Down Expand Up @@ -140,7 +139,7 @@ define([
that._searchText = data.displayName;
var destination = data.destination;
clearSuggestions(that);
updateCamera(that, destination);
that.destinationFound(that, destination);
};

this.hideSuggestions = function () {
Expand Down Expand Up @@ -173,14 +172,20 @@ define([
*/
this.autoComplete = defaultValue(options.autocomplete, true);

/**
* Gets and sets the command called when a geocode destination is found
* @type {Geocoder~DestinationFoundFunction}
*/
this.destinationFound = defaultValue(options.destinationFound, GeocoderViewModel.flyToDestination);

this._focusTextbox = false;

knockout.track(this, ['_searchText', '_isSearchInProgress', 'keepExpanded', '_suggestions', '_selectedSuggestion', '_showSuggestions', '_focusTextbox']);

var searchTextObservable = knockout.getObservable(this, '_searchText');
searchTextObservable.extend({ rateLimit: { timeout: 500 } });
this._suggestionSubscription = searchTextObservable.subscribe(function() {
updateSearchSuggestions(that);
GeocoderViewModel._updateSearchSuggestions(that);
});
/**
* Gets a value indicating whether a search is currently in progress. This property is observable.
Expand Down Expand Up @@ -326,7 +331,7 @@ define([
}
next = currentIndex - 1;
viewModel._selectedSuggestion = viewModel._suggestions[next];
adjustSuggestionsScroll(viewModel, next);
GeocoderViewModel._adjustSuggestionsScroll(viewModel, next);
}

function handleArrowDown(viewModel) {
Expand All @@ -338,7 +343,7 @@ define([
var next = (currentIndex + 1) % numberOfSuggestions;
viewModel._selectedSuggestion = viewModel._suggestions[next];

adjustSuggestionsScroll(viewModel, next);
GeocoderViewModel._adjustSuggestionsScroll(viewModel, next);
}

function computeFlyToLocationForCartographic(cartographic, terrainProvider) {
Expand All @@ -357,7 +362,7 @@ define([
});
}

function updateCamera(viewModel, destination) {
function flyToDestination(viewModel, destination) {
var scene = viewModel._scene;
var mapProjection = scene.mapProjection;
var ellipsoid = mapProjection.ellipsoid;
Expand Down Expand Up @@ -445,7 +450,7 @@ define([
var geocoderResults = result.value;
if (result.state === 'fulfilled' && defined(geocoderResults) && geocoderResults.length > 0) {
viewModel._searchText = geocoderResults[0].displayName;
updateCamera(viewModel, geocoderResults[0].destination);
viewModel.destinationFound(viewModel, geocoderResults[0].destination);
return;
}
viewModel._searchText = query + ' (not found)';
Expand Down Expand Up @@ -521,5 +526,15 @@ define([
});
}

/**
* A function to fly to the destination found by a successful geocode.
* @type {Geocoder~DestinationFoundFunction}
*/
GeocoderViewModel.flyToDestination = flyToDestination;

//exposed for testing
GeocoderViewModel._updateSearchSuggestions = updateSearchSuggestions;
GeocoderViewModel._adjustSuggestionsScroll = adjustSuggestionsScroll;

return GeocoderViewModel;
});
53 changes: 4 additions & 49 deletions Specs/Widgets/Geocoder/GeocoderSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,6 @@ defineSuite([

var scene;

var mockDestination = new Cartesian3(1.0, 2.0, 3.0);
var geocoderResults = [{
displayName: 'a',
destination: mockDestination
}, {
displayName: 'b',
destination: mockDestination
}, {
displayName: 'c',
destination: mockDestination
}];

var customGeocoderOptions = {
autoComplete : true,
geocode : function (input) {
return when.resolve(geocoderResults);
}
};
beforeEach(function() {
scene = createScene();
});
Expand All @@ -40,16 +22,19 @@ defineSuite([

it('constructor sets expected properties', function() {
var flightDuration = 1234;
var destinationFound = jasmine.createSpy();

var geocoder = new Geocoder({
container : document.body,
scene : scene,
flightDuration : flightDuration
flightDuration : flightDuration,
destinationFound : destinationFound
});

var viewModel = geocoder.viewModel;
expect(viewModel.scene).toBe(scene);
expect(viewModel.flightDuration).toBe(flightDuration);
expect(viewModel.destinationFound).toBe(destinationFound);
geocoder.destroy();
});

Expand Down Expand Up @@ -96,34 +81,4 @@ defineSuite([
});
}).toThrowDeveloperError();
});

it('automatic suggestions can be navigated by arrow up/down keys', function() {
var container = document.createElement('div');
container.id = 'testContainer';
document.body.appendChild(container);
var geocoder = new Geocoder({
container : 'testContainer',
scene : scene,
geocoderServices : [customGeocoderOptions]
});
var viewModel = geocoder._viewModel;
viewModel._searchText = 'some_text';
viewModel._updateSearchSuggestions(viewModel);

expect(viewModel._selectedSuggestion).toEqual(undefined);
viewModel._handleArrowDown(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('a');
viewModel._handleArrowDown(viewModel);
viewModel._handleArrowDown(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('c');
viewModel._handleArrowDown(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('a');
viewModel._handleArrowDown(viewModel);
viewModel._handleArrowUp(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('a');
viewModel._handleArrowUp(viewModel);
expect(viewModel._selectedSuggestion).toBeUndefined();
document.body.removeChild(container);
});

}, 'WebGL');
68 changes: 56 additions & 12 deletions Specs/Widgets/Geocoder/GeocoderViewModelSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ defineSuite([
}, {
displayName: 'b',
destination: mockDestination
}, {
displayName: 'c',
destination: mockDestination
}];
var customGeocoderOptions = {
autoComplete: true,
Expand Down Expand Up @@ -158,8 +161,8 @@ defineSuite([
geocoderServices : [customGeocoderOptions]
});
geocoder._searchText = 'some_text';
geocoder._updateSearchSuggestions(geocoder);
expect(geocoder._suggestions.length).toEqual(2);
GeocoderViewModel._updateSearchSuggestions(geocoder);
expect(geocoder._suggestions.length).toEqual(3);
});

it('update search suggestions results in empty list if the query is empty', function() {
Expand All @@ -168,35 +171,38 @@ defineSuite([
geocoderServices : [customGeocoderOptions]
});
geocoder._searchText = '';
spyOn(geocoder, '_adjustSuggestionsScroll');
geocoder._updateSearchSuggestions(geocoder);

GeocoderViewModel._updateSearchSuggestions(geocoder);
expect(geocoder._suggestions.length).toEqual(0);
});

it('can activate selected search suggestion', function () {
spyOn(GeocoderViewModel, 'flyToDestination');
var destination = new Rectangle(0.0, -0.1, 0.1, 0.1);
var geocoder = new GeocoderViewModel({
scene : scene,
geocoderServices : [customGeocoderOptions]
});
spyOn(geocoder, '_updateCamera');
spyOn(geocoder, '_adjustSuggestionsScroll');

var suggestion = { displayName: 'a', destination: new Rectangle(0.0, -0.1, 0.1, 0.1) };
var suggestion = { displayName: 'a', destination: destination };
geocoder._selectedSuggestion = suggestion;
geocoder.activateSuggestion(suggestion);
expect(geocoder._searchText).toEqual('a');
expect(GeocoderViewModel.flyToDestination).toHaveBeenCalledWith(geocoder, destination);
});

it('if more than one geocoder service is provided, use first result from first geocode in array order', function () {
spyOn(GeocoderViewModel, 'flyToDestination');

var geocoder = new GeocoderViewModel({
scene : scene,
geocoderServices : [noResultsGeocoder, customGeocoderOptions2]
});
geocoder._searchText = 'sthsnth'; // an empty query will prevent geocoding
spyOn(geocoder, '_updateCamera');
spyOn(geocoder, '_adjustSuggestionsScroll');

geocoder.search();
expect(geocoder._searchText).toEqual(geocoderResults2[0].displayName);
expect(GeocoderViewModel.flyToDestination).toHaveBeenCalledWith(geocoder, mockDestination);
});

it('can update autoComplete suggestions list using multiple geocoders', function () {
Expand All @@ -205,10 +211,48 @@ defineSuite([
geocoderServices : [customGeocoderOptions, customGeocoderOptions2]
});
geocoder._searchText = 'sthsnth'; // an empty query will prevent geocoding
spyOn(geocoder, '_updateCamera');
spyOn(geocoder, '_adjustSuggestionsScroll');
geocoder._updateSearchSuggestions(geocoder);
GeocoderViewModel._updateSearchSuggestions(geocoder);
expect(geocoder._suggestions.length).toEqual(geocoderResults1.length + geocoderResults2.length);
});

it('uses custom destination found callback', function () {
spyOn(GeocoderViewModel, 'flyToDestination');

var destinationFound = jasmine.createSpy();
var geocoder = new GeocoderViewModel({
scene : scene,
geocoderServices : [noResultsGeocoder, customGeocoderOptions2],
destinationFound: destinationFound
});
geocoder._searchText = 'sthsnth'; // an empty query will prevent geocoding
geocoder.search();

expect(geocoder._searchText).toEqual(geocoderResults2[0].displayName);
expect(GeocoderViewModel.flyToDestination).not.toHaveBeenCalled();
expect(destinationFound).toHaveBeenCalledWith(geocoder, mockDestination);
});

it('automatic suggestions can be navigated by arrow up/down keys', function() {
spyOn(GeocoderViewModel, '_adjustSuggestionsScroll');
var viewModel = new GeocoderViewModel({
scene : scene,
geocoderServices : [customGeocoderOptions]
});
viewModel._searchText = 'some_text';
GeocoderViewModel._updateSearchSuggestions(viewModel);

expect(viewModel._selectedSuggestion).toEqual(undefined);
viewModel._handleArrowDown(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('a');
viewModel._handleArrowDown(viewModel);
viewModel._handleArrowDown(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('c');
viewModel._handleArrowDown(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('a');
viewModel._handleArrowDown(viewModel);
viewModel._handleArrowUp(viewModel);
expect(viewModel._selectedSuggestion.displayName).toEqual('a');
viewModel._handleArrowUp(viewModel);
expect(viewModel._selectedSuggestion).toBeUndefined();
});
}, 'WebGL');

0 comments on commit 8fa64c1

Please sign in to comment.