/**
* @param {google.maps.Polygon|google.maps.Polyline} poly The polygon or polyline.
* @return {google.maps.LatLngBounds} The bounding box for the polygon or polyline.
*/
lucid.maps.geometry.computeBounds = function( poly )
{
var polyline = poly.getPath();
// NB: Polyline and a Polygon both support the 'getPath' method.
// In the case of a polygon 'getPath' will return the first path (the exterior path).
// We only need to process the exterior path to compute a Polygon's bounds.
var bounds = new google.maps.LatLngBounds();
var points = polyline.getArray();
for (var i=0; i<points.length; i++)
{
bounds.extend( points[i] );
}
return bounds;
};
/**
* @param {google.maps.LatLng} from The point the heading is measured from.
* @param {google.maps.LatLng} to The point the heading is measured to.
* @return {google.maps.LatLng} The heading from one LatLng to the other, normalised into the range 0 to 360.
*/
lucid.maps.geometry.computeNormalisedHeading = function( from, to )
{
// TODO Use non-geodesic heading.
// I have raised a feature request for this in the Google Maps API: https://code.google.com/p/gmaps-api-issues/issues/detail?id=5928
var heading = google.maps.geometry.spherical.computeHeading( from, to );
// The function above can return a negative value. So normalise the value into the range 0 to 360.
return lucid.maps.geometry.normaliseHeading( heading );
};
/**
* @param {google.maps.LatLngBounds} boundingBox The bounding box.
* @param {number} heading The heading from the centre of the bounding box.
* The heading must be normalised (i.e. in the range 0 to 360).
* @return {google.maps.LatLng} The point on the edge of the bounding box that is on the given heading.
*/
lucid.maps.geometry.computeLatLngBoundsIntersection = function( boundingBox, heading )
{
var boxWidth = boundingBox.getNorthEast().lng() - boundingBox.getSouthWest().lng();
var boxHeight = boundingBox.getNorthEast().lat() - boundingBox.getSouthWest().lat();
var intersection = lucid.maps.geometry.computeBoundingBoxIntersection( boxWidth, boxHeight, boundingBox.getCenter().lng(), boundingBox.getCenter().lat(), heading );
return new google.maps.LatLng( intersection.y, intersection.x );
};
/**
* @param {number} width The width of the bounding box.
* @param {number} height The height of the bounding box.
* @param {number} centreX The x coordinate of the centre of the bounding box.
* @param {number} centreY The y coordinate of the centre of the bounding box.
* @param {number} heading The heading, in degrees, measured clockwise from North. Must be in the range 0 to 360.
* @return {object} An object containing the x and y coordinates of the intersection on the edge of the bounding box.
*/
lucid.maps.geometry.computeBoundingBoxIntersection = function( width, height, centreX, centreY, heading )
{
var angleInRadians = lucid.maps.geometry.convertHeadingToAngle( heading );
return lucid.maps.geometry.computeBoundingBoxIntersectionAtAngle( width, height, centreX, centreY, angleInRadians );
}
/**
* @param {number} width The width of the bounding box.
* @param {number} height The height of the bounding box.
* @param {number} centreX The x coordinate of the centre of the bounding box.
* @param {number} centreY The y coordinate of the centre of the bounding box.
* @param {number} angle The angle, in radians, measured anti-clockwise from the right (East). Must be in the range 0 to 2PI.
* @return {object} An object containing the x and y coordinates of the intersection on the edge of the bounding box.
*/
lucid.maps.geometry.computeBoundingBoxIntersectionAtAngle = function( width, height, centreX, centreY, angle )
{
// NB: Maps tend to be landscape.
// So probability states the heading to a target object will exit through the top or bottom edge of the map.
// Look for an intersection on the top or bottom edge.
var intersectionY = (angle > Math.PI) ? -(height / 2) : (height / 2);
var distanceToIntersection = intersectionY / Math.sin( angle );
var intersectionX = distanceToIntersection * Math.cos( angle );
if (Math.abs( intersectionX ) > (width / 2))
{
// The intersection hits the left/right edge before the top/bottom edge.
// So re-compute the intersection for the left or right edge.
intersectionX = ((angle > (Math.HALF_PI)) && (angle < (Math.THREE_HALVES_PI))) ? -(width / 2) : (width / 2);
distanceToIntersection = intersectionX / Math.cos( angle );
intersectionY = distanceToIntersection * Math.sin( angle );
}
return { x: centreX + intersectionX,
y: centreY + intersectionY };
};
/**
* Adjust a heading to account for the view of the map being tilted.
*
* @param {number} heading The heading, in degrees, measured clockwise from North.
* @param {number} tilt The angle of incidence of the map, in degrees from the viewport plane to the map plane.
* An angle of 0 indicates the map is being viewed from directly overhead.
* @return {number} The angle the heading makes with the viewport.
*/
lucid.maps.geometry.tiltedHeading = function( heading, tilt )
{
var headingInRadians = lucid.maps.geometry.convertHeadingToAngle( heading );
return lucid.maps.geometry.tiltedAngle( headingInRadians, tilt );
};
/**
* Adjust an angle to account for the view of the map being tilted.
*
* @param {number} angle The angle, in radians, measured anti-clockwise from the right (East).
* @param {number} tilt The angle of incidence of the map, in degrees from the viewport plane to the map plane.
* An angle of 0 indicates the map is being viewed from directly overhead.
* @return {number} The angle the initial angle makes with the viewport.
*/
lucid.maps.geometry.tiltedAngle = function( angle, tilt )
{
var tiltInRadians = lucid.maps.geometry.convertToRadians( tilt );
var xLength = Math.cos( angle );
var yLength = Math.sin( angle );
// adjust for the tilt
yLength = yLength * Math.cos( tiltInRadians );
return Math.vectorAngle( xLength, yLength );
};
/**
* Convert a heading from degrees to radians.
*
* @param {number} heading The heading, in degrees, measured clockwise from North.
* @return {number} The mathematical angle, in radians, measured anti-clockwise from the right (East).
*/
lucid.maps.geometry.convertHeadingToAngle = function( heading )
{
return lucid.maps.geometry.convertToRadians( 90 - heading );
};
/**
* Convert an angle from degrees to radians.
*
* @param {number} angle The angle in degrees.
* @return {number} The angle in radians.
*/
lucid.maps.geometry.convertToRadians = function( angle )
{
return angle * Math.PI / 180;
};
/**
* @param {number} heading The heading, measured in degrees.
* @return {number} The heading normalised into the range 0 to 360.
*/
lucid.maps.geometry.normaliseHeading = function( heading )
{
while (heading > 360)
heading -= 360;
while (heading < 0)
heading += 360;
return heading;
};
/**
* @param {number} angle The angle, measured in radians.
* @return {number} The angle normalised into the range 0 to 2PI.
*/
lucid.maps.geometry.normaliseAngle = function( angle )
{
while (angle > Math.TWO_PI)
angle -= Math.TWO_PI;
while (angle < 0)
angle += Math.TWO_PI;
return angle;
};