Source: maps/limbs/location/BoundsEdgeLocationStrategy.js


/**
 * @class
 * A location strategy that uses the corner or an edge-centre-point of the target's bounding box.
 * 
 * This location strategy takes the bounding box of the target shape
 * and determines which corner or edge of the bounding box is nearest the map viewport.
 * 
 * The point location of the 2D shape is taken to be either the closest corner or
 * a point along the nearest edge of the bounding box.
 * 
 * @constructor
 * @param {lucid.maps.limbs.location.CentreLocationStrategyOptions} options  The options controlling the location calculation.
 */
lucid.maps.limbs.location.BoundsEdgeLocationStrategy = function( options )
{
	lucid.maps.limbs.location.LocationStrategy.apply( this, [ options ] );
	
	
	this.computeMarkerLocation = function( target, mapBounds )
	{
		// Markers are a special case.
		// Don't use the bounds. Simply use the position of the marker.
		return target.getPosition();
	};
	
	this.computePolylineLocation = function( target, mapBounds )
	{
		var targetBounds = lucid.maps.geometry.computeBounds( target );
		
		return nearestCornerOrEdgePoint( targetBounds, mapBounds );
	};
	
	this.computePolygonLocation = function( target, mapBounds )
	{
		var targetBounds = lucid.maps.geometry.computeBounds( target );
		
		return nearestCornerOrEdgePoint( targetBounds, mapBounds );
	};
	
	this.computeRectangleLocation = function( target, mapBounds )
	{
		return nearestCornerOrEdgePoint( target.getBounds(), mapBounds );
	};
	
	this.computeCircleLocation = function( target, mapBounds )
	{
		return nearestCornerOrEdgePoint( target.getBounds(), mapBounds );
	};
	
	
	/**
	 * Determine in which of eight zones (N, NE, E, SE, S, SW, W, NW) the target object lies.
	 * Then calculate the nearest corner point or point along the nearest edge to the map view.
	 * 
	 * When the target lies beyond the corner of the map (NE, NW, SE, SW)
	 * then the corner of the target's bounding box is used as the 'nearest' point.
	 * 
	 * When the target lies beyond the side of the map (N, S, E, W)
	 * then a point on the closest edge of bounding box is used as the 'nearest' point.
	 * 
	 * @param {google.maps.LatLngBounds} targetBounds  The bounds of the target object.
	 * @param {google.maps.LatLngBounds} mapBounds  The bounds of the current map view.
	 * @return {google.maps.LatLng}  The point on the target bounds that is closest to the map bounds.
	 */
	function nearestCornerOrEdgePoint( targetBounds, mapBounds )
	{
		var targetLiesNorth = (targetBounds.getSouthWest().lat() > mapBounds.getNorthEast().lat());
		var targetLiesSouth = (targetBounds.getNorthEast().lat() < mapBounds.getSouthWest().lat());
		var targetLiesEast  = (targetBounds.getSouthWest().lng() > mapBounds.getNorthEast().lng());
		var targetLiesWest  = (targetBounds.getNorthEast().lng() < mapBounds.getSouthWest().lng());
		
		
		if (targetLiesNorth && targetLiesEast)
		{
			// South west corner.
			return targetBounds.getSouthWest();
		}
		if (targetLiesNorth && targetLiesWest)
		{
			// South east corner.
			return new google.maps.LatLng( targetBounds.getSouthWest().lat(), targetBounds.getNorthEast().lng() );
		}
		if (targetLiesSouth && targetLiesEast)
		{
			// North west corner.
			return new google.maps.LatLng( targetBounds.getNorthEast().lat(), targetBounds.getSouthWest().lng() );
		}
		if (targetLiesSouth && targetLiesWest)
		{
			// North east corner.
			return targetBounds.getNorthEast();
		}
		
		
		if (targetLiesNorth)
		{
			// Centre of the southern edge.
			return new google.maps.LatLng( targetBounds.getSouthWest().lat(), computeHorizontalEdgeXCoordinate( targetBounds, mapBounds ) );
		}
		if (targetLiesSouth)
		{
			// Centre of the northern edge.
			return new google.maps.LatLng( targetBounds.getNorthEast().lat(), computeHorizontalEdgeXCoordinate( targetBounds, mapBounds ) );
		}
		if (targetLiesEast)
		{
			// Centre of the western edge.
			return new google.maps.LatLng( computeVerticalEdgeYCoordinate( targetBounds, mapBounds ), targetBounds.getSouthWest().lng() );
		}
		if (targetLiesWest)
		{
			// Centre of the eastern edge.
			return new google.maps.LatLng( computeVerticalEdgeYCoordinate( targetBounds, mapBounds ), targetBounds.getNorthEast().lng() );
		}
		
		
		// Finally, if the shape lies in none of the directions then it must be in the map view.
		return mapBounds.getCenter();
	}
	
	/* Implementation Note
	 * 
	 * Using the centre of an edge causes the LIMB to jump when the target moves from a 'side zone' into a 'corner zone'.
	 * This is because the centre of the closest edge (used when the target is in a 'side zone') is much further away than
	 * the corner point (used when the target is in a 'corner zone').
	 * As the target transitions between the two zones the LIMB jumps backwards.
	 * 
	 * To avoid this we don't use the centre of the edge.
	 * We take the section of the edge that overlaps the edge of the map. We then use the centre point of this section
	 * of the bounding box's edge.
	 * As the bounding box moves towards a corner of the map the edge that overlaps shrinks until all that remains is the corner point
	 * of the bounding box. This ensures a smooth tansition into the 'corner zone' where the corner point is used as the target location.
	 */
	
	/**
	 * Determine an 'appropriate' point along the horizontal edge of the target bounds.
	 * 
	 * @param {google.maps.LatLngBounds} targetBounds  The bounds of the target object.
	 * @param {google.maps.LatLngBounds} mapBounds  The bounds of the current map view.
	 * @return {number}  The x-coordinate along the horizontal edge of the target bounds that is closest to the map bounds.
	 */
	function computeHorizontalEdgeXCoordinate( targetBounds, mapBounds )
	{
		var mapWestCoord = mapBounds.getSouthWest().lng();
		var mapEastCoord = mapBounds.getNorthEast().lng();
		
		var targetWestCoord = targetBounds.getSouthWest().lng();
		var targetEastCoord = targetBounds.getNorthEast().lng();
		
		var overlappingWestCoord = (mapWestCoord > targetWestCoord) ? mapWestCoord : targetWestCoord;
		var overlappingEastCoord = (mapEastCoord < targetEastCoord) ? mapEastCoord : targetEastCoord;
		
		return (overlappingWestCoord + overlappingEastCoord) / 2;
	}
	
	/**
	 * Determine an 'appropriate' point along the vertical edge of the target bounds.
	 * 
	 * @param {google.maps.LatLngBounds} targetBounds  The bounds of the target object.
	 * @param {google.maps.LatLngBounds} mapBounds  The bounds of the current map view.
	 * @return {number}  The y-coordinate along the vertical edge of the target bounds that is closest to the map bounds.
	 */
	function computeVerticalEdgeYCoordinate( targetBounds, mapBounds )
	{
		var mapSouthCoord = mapBounds.getSouthWest().lat();
		var mapNorthCoord = mapBounds.getNorthEast().lat();
		
		var targetSouthCoord = targetBounds.getSouthWest().lat();
		var targetNorthCoord = targetBounds.getNorthEast().lat();
		
		var overlappingSouthCoord = (mapSouthCoord > targetSouthCoord) ? mapSouthCoord : targetSouthCoord;
		var overlappingNorthCoord = (mapNorthCoord < targetNorthCoord) ? mapNorthCoord : targetNorthCoord;
		
		return (overlappingSouthCoord + overlappingNorthCoord) / 2;
	}
	
};


/**
 * @type {object}
 * @augments lucid.maps.limbs.location.LocationStrategyOptions
 */
lucid.maps.limbs.location.CentreLocationStrategyOptions = {};