/**
* @class
* Searches for places and adds labels (Marker and Limb) to a map.
* This class uses the google.maps.places.PlacesService to find places
* and displays them using a lucid.maps.limbs.LimbMarkerFactory.
*
* This logic can optionally be synchronised to a button (lucid.maps.places.LabelPlacesButton).
*
* @constructor
* @param {lucid.maps.places.LabelPlacesOptions} options Configuration for the labelling of places.
*/
lucid.maps.places.LabelPlaces = function( options )
{
var thisLabeller = this;
var markerManager;
var searchService;
var searchOrigin;
var searchTypes;
// This initialise function is called at the end of the constructor.
function init()
{
if (options.button)
google.maps.event.addListener( options.button, "click", handleButtonClick );
markerManager = new lucid.maps.limbs.LimbMarkerPlacesFactory( options );
searchService = new google.maps.places.PlacesService( options.map );
searchTypes = options.types;
}
/**
* Set the type(s) of place this instance searches for.
*
* @param {string[]} types A list of Google places types.
*/
this.setPlaceTypes = function( types )
{
searchTypes = types;
};
function handleButtonClick()
{
if (options.button.getState() == lucid.maps.places.LabelPlacesState.ACTIVE)
{
thisLabeller.removePlaces();
}
else if (options.button.getState() == lucid.maps.places.LabelPlacesState.PENDING)
{
// Ignore any further clicks until the current search completes.
}
else
{
thisLabeller.addPlaces();
}
}
/**
* Add places to the map.
* The Google PlacesService will be called to find the places to show. The locations
* returned are then marked on the map with a Marker and a LIMB.
* You can remove the labels with a call to 'removePlaces'.
*/
this.addPlaces = function()
{
if (options.button)
options.button.setPending();
searchOrigin = options.map.getCenter();
var placeSearchRequest =
{
location: searchOrigin,
// You can't specify a max radius when ranking by distance.
// So we'll cap the radius when we process the results.
//radius: options.maxDistance,
rankBy: google.maps.places.RankBy.DISTANCE,
types: searchTypes
};
searchService.nearbySearch( placeSearchRequest, handleSearchResults );
}
function handleSearchResults( results, status, pagination )
{
if (status == google.maps.places.PlacesServiceStatus.OK)
{
var processedResults = processSearchResults( results );
if (processedResults.length > 0)
{
handleNonZeroSearchResults( processedResults );
}
else
{
handleZeroSearchResults();
}
}
else if (status == google.maps.places.PlacesServiceStatus.ZERO_RESULTS)
{
handleZeroSearchResults();
}
else
{
handleErrorSearchResult();
}
}
function handleErrorSearchResult()
{
if (options.button)
options.button.setError();
}
function handleZeroSearchResults()
{
if (options.button)
options.button.setNoResults();
}
function handleNonZeroSearchResults( processedResults )
{
if (options.button)
options.button.setActive();
google.maps.event.trigger( thisLabeller, "pre_add_labels" );
for (var i=0; i<processedResults.length; i++)
{
var result = processedResults[i];
markerManager.add( result.geometry.location, result.name, result.reference );
}
markerManager.zoomToAll( { "additionalLocations": [ searchOrigin ] } );
google.maps.event.trigger( thisLabeller, "post_add_labels" );
}
function processSearchResults( results )
{
var processedResults = [];
for (var i=0; i<results.length; i++)
{
if (typeof options.maxPlaces === "number")
{
// Stop when we've reached the maximum places to be shown.
if (i >= options.maxPlaces)
{
break;
}
}
if (typeof options.maxDistance === "number")
{
// Stop when we've reach a result too far away.
// NB: We've ranked the results by distance so that
// once we find a result too far away subsequent results will always be further away.
var distanceToResults = google.maps.geometry.spherical.computeDistanceBetween( searchOrigin, results[i].geometry.location );
if (distanceToResults > options.maxDistance)
{
break;
}
}
processedResults[processedResults.length] = results[i];
}
return processedResults;
}
/**
* Remove places from the map.
*/
this.removePlaces = function()
{
if (markerManager.getNumberOfMarkers() > 0)
{
google.maps.event.trigger( thisLabeller, "pre_remove_labels" );
if (options.button)
options.button.setIdle();
markerManager.removeAll();
google.maps.event.trigger( thisLabeller, "post_remove_labels" );
}
};
/**
* Temporarily hide all places.
* This does not remove the LIMBs and markers, it just takes them off display.
* Call this if you hide the map element.
*/
this.hide = function()
{
markerManager.hide();
};
/**
* Re-display the places after being hidden with a call to 'hide'.
*/
this.show = function()
{
markerManager.show();
};
/**
* @return {number} The total number of places being labelled (includes any labels currently being hidden).
*/
this.getNumberOfPlaces = function()
{
return markerManager.getNumberOfMarkers();
};
init();
};
/**
* Raised before new places are labelled on the map.
*
* @event lucid.maps.places.LabelPlaces#pre_add_labels
* @type {object}
*/
/**
* Raised after labels have been added to the map.
*
* @event lucid.maps.places.LabelPlaces#post_add_labels
* @type {object}
*/
/**
* Raised before removing place labels from the map.
*
* @event lucid.maps.places.LabelPlaces#pre_remove_labels
* @type {object}
*/
/**
* Raised after labels have been removed from the map.
*
* @event lucid.maps.places.LabelPlaces#post_remove_labels
* @type {object}
*/
/**
* @type {object}
* @augments lucid.maps.limbs.LimbMarkerPlacesFactoryOptions
* @property {string[]} types The types of places to show.
* @property {number} maxDistance The maximum distance to find places.
* @property {number} maxPlaces The maximum number of places to show.
* @property {lucid.maps.places.LabelPlacesButton} [button] The button which will activate the places being shown.
* If undefined, you must trigger the places to be shown by calling the addPlaces function.
*/
lucid.maps.places.LabelPlacesOptions = {};