Source: maps/places/LabelPlaces.js


/**
 * @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 = {};