Source: maps/limbs/LimbMarkerFactory.js


/**
 * @class
 * Manages multiple lucid.maps.limbs.Limb.
 * This provides a slight performance gain by refreshing multiple LIMBs within a single map-moved event handler.
 * This class will also create the LIMB for you, and the associated google.maps.Marker instance too.
 *
 * @constructor
 * @param {lucid.maps.limbs.LimbMarkerFactoryOptions} options  The factory options.
 */
lucid.maps.limbs.LimbMarkerFactory = function( options )
{
	var markers;
	var limbs;
	var zoomedToAll;
	
	// The zoom level used when the user clicks for info on a place.
	// This is only used when the map is zoomed out to show all places.
	// If the user has zoomed in to a custom zoom level then their custom zoom is preserved in that instance.
	var selectPlaceZoomLevel;
	
	
	// This initialise function is called at the end of the constructor.
	function init()
	{
		markers = [];
		limbs = new lucid.maps.limbs.LimbFactory( options.map );
		zoomedToAll = false;
		selectPlaceZoomLevel = (typeof options.zoomLevelWhenSelected === "undefined") ? 16 : options.zoomLevelWhenSelected;
		
		google.maps.event.addListener( options.map, "zoom_changed", handleZoomChanged );
	}
	
	
	function handleZoomChanged()
	{
		// The map is now at a user-chosen zoom level.
		zoomedToAll = false;
	}
	
	/**
	 * Remove all markers and LIMBs created by this manager.
	 */
	this.removeAll = function()
	{
		for (var i=0; i<markers.length; i++)
		{
			if (typeof options.infoBox !== "undefined")
				options.infoBox.remove( markers[i] );
			
			markers[i].setMap( null );
			markers[i] = null;
		}
		markers = [];
		
		limbs.destroyAll();
	};
	
	/**
	 * Add a new marker and a LIMB to the map.
	 * The map instance and label styling are set in the options passed into the manager's constructor.
	 * 
	 * @param {google.maps.LatLng} position  The location to be marked.
	 * @param {string} title  The name/title of the location. Used as the title of the marker created on the map.
	 * @param {string} [placeReference]  Optionally associated the marker with a place using the place reference.
	 */
	this.add = function( position, title, placeReference )
	{
		// TODO Take a deep copy.
		var markerOptions = options.markerOptions;
		markerOptions.position = position;
		markerOptions.title = title;
		markerOptions.map = options.map;
		
		var marker = new google.maps.Marker( markerOptions );
		if (typeof placeReference !== "undefined")
		{
			marker.placeReference = placeReference;
		}
		markers[markers.length] = marker;
		
		
		var limbOptions = {};
		if (typeof options.limbOptions !== "undefined")
		{
			lucid.maps.limbs.applyLimbOptions( limbOptions, options.limbOptions );
		}
		limbOptions.marker = marker;
		
		var limb = limbs.create( limbOptions );
		
		
		if (typeof options.infoBox !== "undefined")
		{
			options.infoBox.add( marker );
		}
		google.maps.event.addListener( marker, "click", createClickMarkerHandler( marker ) );
		google.maps.event.addListener( limb, "click", createClickLimbHandler( marker ) );
	};

	function createClickMarkerHandler( marker )
	{
		return function()
		{
			if (typeof options.infoBox === "undefined")
			{
				options.map.setCenter( marker.getPosition() );
			}
			// else: The info box will appear when the marker is clicked.
			//       Let the map centre be centred around the info box.
			
			if (zoomedToAll === true)
			{
				if (selectPlaceZoomLevel != null)
				{
					options.map.setZoom( selectPlaceZoomLevel );
				}
				// else: the zoom is turned off
			}
			// else: keep the map at the user-chosen zoom level
		}
	}
	
	function createClickLimbHandler( marker )
	{
		return function()
		{
			// Perform the same action as if the user clicked on the marker.
			google.maps.event.trigger( marker, "click" );
		};
	}
	
	/**
	 * Zoom and pan the map so that all markers are in view.
	 * You can optionally specify additional locations to be included in the map view by setting LatLng in the options parameter.
	 *
	 * @param {object} zoomOptions
	 * @param {google.maps.LatLng[]} [zoomOptions.additionalLocations]  Locations, in addition to the markers, which should be kept in view when the map view is changed.
	 */
	this.zoomToAll = function( zoomOptions )
	{
		var locations = [];
		
		for (var i=0; i<markers.length; i++)
		{
			locations[locations.length] = markers[i].getPosition();
		}
		
		if (zoomOptions.additionalLocations)
		{
			for (var i=0; i<zoomOptions.additionalLocations.length; i++)
			{
				locations[locations.length] = zoomOptions.additionalLocations[i];
			}
		}
		
		if (locations.length > 0)
		{
			var bounds = new google.maps.LatLngBounds( locations[0], locations[0] );
			for (var i=0; i<locations.length; i++)
			{
				bounds = bounds.extend( locations[i] );
			}
			
			options.map.fitBounds( bounds );
		}
		
		zoomedToAll = true;
	};
	
	/**
	 * Temporarily hide the labels.
	 * 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()
	{
		limbs.hide();
		// NB: The markers are part of the map and should be hidden by hiding the map element.
	};
	
	/**
	 * Re-display the labels after being hidden with a call to 'hide'.
	 */
	this.show = function()
	{
		limbs.show();
		// NB: The markers are part of the map and should be shown by showing the map element.
	};
	
	/**
	 * Destroy this instance and any associated resources.
	 * This method should be called when the instance is no longer required.
	 */
	this.destroy = function()
	{
		this.removeAll();
	};
	
	/**
	 * @return {number}  The total number of places being labelled (includes any labels currently being hidden).
	 */
	this.getNumberOfMarkers = function()
	{
		return markers.length;
	};
	
	
	init();
};


/**
 * @type {object}
 * @property {google.maps.Map} map  The map the markers and LIMBs are to be associated with.
 * @property {google.maps.MarkerOptions} markerOptions  The settings for the marker shown on the map for these labels.
 *                                                      The LimbMarkerFactory will ignore the position property. This will be set when the places are added with a call to addPlaces.
 * @property {lucid.maps.limbs.LimbOptions} [limbOptions] The settings for the LIMB shown on the map for these labels.
 *                                                        The LimbMarkerFactory will ignore the marker property. This will be set to the place's marker.
 *                                                        The LimbMarkerFactory will ignore the independent property. This will always be false.
 *                                                        If undefined the LIMB will take its styling from the marker.
 * @property {lucid.maps.limbs.InfoBox} [infoBox]  The class that displays an info box when the user clicks on the label.
 *                                                 Leave undefined if no info box should appear.
 * @property {number} [zoomLevelWhenSelected]  The zoom level to be used when a marker is clicked.
 *                                             Set this to null if you do not want the zoom to change.
 *                                             Defaults to 16 if not defined.
 */
lucid.maps.limbs.LimbMarkerFactoryOptions = {};