'use strict'; (function() { // fixme: make all this work with different types of "points" // i.e. {lat, lng}, [lat, lng] function deg(point) { return Ox.map(point, function(val) { return Ox.mod(Ox.deg(val) + 180, 360) - 180; }); } function rad(point) { return Ox.map(point, function(val) { return Ox.rad(val); }); } /*@ Ox.crossesDateline Returns true if a given line crosses the dateline @*/ Ox.crossesDateline = function(pointA, pointB) { return pointA.lng > pointB.lng; }; /*@ Ox.getArea Returns the area in square meters of a given rectancle @*/ Ox.getArea = function(pointA, pointB) { /* area of a ring between two latitudes: 2 * PI * r^2 * abs(sin(latA) - sin(latB)) see http://mathforum.org/library/drmath/view/63767.html => 2 * Math.PI * Math.pow(Ox.EARTH_RADIUS, 2) * Math.abs(Math.sin(Ox.rad(latA)) - Math.sin(Ox.rad(latB))) * Math.abs(Ox.rad(lngA) - Ox.rad(lngB)) / (2 * Math.PI) */ if (Ox.crossesDateline(pointA, pointB)) { pointB.lng += 360; } pointA = rad(pointA); pointB = rad(pointB); return Math.pow(Ox.EARTH_RADIUS, 2) * Math.abs(Math.sin(pointA.lat) - Math.sin(pointB.lat)) * Math.abs(pointA.lng - pointB.lng); }; /*@ Ox.getBearing Returns the bearing from one point to another > Ox.getBearing({lat: -45, lng: 0}, {lat: 45, lng: 0}) 0 @*/ Ox.getBearing = function(pointA, pointB) { var pointA = rad(pointA), pointB = rad(pointB), x = Math.cos(pointA.lat) * Math.sin(pointB.lat) - Math.sin(pointA.lat) * Math.cos(pointB.lat) * Math.cos(pointB.lng - pointA.lng), y = Math.sin(pointB.lng - pointA.lng) * Math.cos(pointB.lat); return (Ox.deg(Math.atan2(y, x)) + 360) % 360; }; /*@ Ox.getCenter Returns the center of a recangle on a spehre > Ox.getCenter({lat: -45, lng: -90}, {lat: 45, lng: 90}) {lat: 0, lng: 0} @*/ Ox.getCenter = function(pointA, pointB) { var pointA = rad(pointA), pointB = rad(pointB), x = Math.cos(pointB.lat) * Math.cos(pointB.lng - pointA.lng), y = Math.cos(pointB.lat) * Math.sin(pointB.lng - pointA.lng), d = Math.sqrt( Math.pow(Math.cos(pointA.lat) + x, 2) + Math.pow(y, 2) ), lat = Math.atan2(Math.sin(pointA.lat) + Math.sin(pointB.lat), d), lng = pointA.lng + Math.atan2(y, Math.cos(pointA.lat) + x); return deg({lat: lat, lng: lng}); }; /*@ Ox.getCircle Returns points on a circle around a given point (center, radius, precision) -> Points center Center point ({lat, lng}) radius Radius in meters precision Precision (the circle will have 2^precision segments) @*/ Ox.getCircle = function(center, radius, precision) { return Ox.range( 0, 360, 360 / Math.pow(2, precision) ).map(function(bearing) { return Ox.getPoint(center, radius, bearing); }); }; /*@ Ox.getDegreesPerMeter Returns degrees per meter at a given latitude > 360 / Ox.getDegreesPerMeter(0) Ox.EARTH_CIRCUMFERENCE @*/ Ox.getDegreesPerMeter = function(lat) { return 360 / Ox.EARTH_CIRCUMFERENCE / Math.cos(lat * Math.PI / 180); }; /*@ Ox.getDistance Returns the distance in meters between two points > Ox.getDistance({lat: -45, lng: -90}, {lat: 45, lng: 90}) * 2 Ox.EARTH_CIRCUMFERENCE @*/ Ox.getDistance = function(pointA, pointB) { var pointA = rad(pointA), pointB = rad(pointB); return Math.acos( Math.sin(pointA.lat) * Math.sin(pointB.lat) + Math.cos(pointA.lat) * Math.cos(pointB.lat) * Math.cos(pointB.lng - pointA.lng) ) * Ox.EARTH_RADIUS; }; /*@ Ox.getLatLngByXY Returns lat/lng for a given x/y on a 1x1 mercator projection > Ox.getLatLngByXY({x: 0.5, y: 0.5}) {lat: 0, lng: 0} @*/ Ox.getLatLngByXY = function(xy) { function getVal(val) { return (val - 0.5) * 2 * Math.PI; } return { lat: -Ox.deg(Math.atan(Ox.sinh(getVal(xy.y)))), lng: Ox.deg(getVal(xy.x)) }; }; /*@ Ox.getLine Returns points on a line between two points (pointA, pointB, precision) -> Points pointA Start point ({lat, lng}) pointB End point ({lat, lng}) precision Precision (the line will have 2^precision segments) @*/ Ox.getLine = function(pointA, pointB, precision) { var line = [pointA, pointB], points; while (precision > 0) { points = [line[0]]; Ox.loop(line.length - 1, function(i) { points.push( Ox.getCenter(line[i], line[i + 1]), line[i + 1] ); }); line = points; precision--; } return line; }; /*@ Ox.getMetersPerDegree Returns meters per degree at a given latitude > Ox.getMetersPerDegree(0) * 360 Ox.EARTH_CIRCUMFERENCE @*/ Ox.getMetersPerDegree = function(lat) { return Math.cos(lat * Math.PI / 180) * Ox.EARTH_CIRCUMFERENCE / 360; }; /*@ Ox.getPoint Returns a point at a given distance/bearing from a given point > Ox.getPoint({lat: -45, lng: 0}, Ox.EARTH_CIRCUMFERENCE / 4, 0) {lat: 45, lng: 0} @*/ Ox.getPoint = function(point, distance, bearing) { var pointB = {}; point = rad(point); distance /= Ox.EARTH_RADIUS; bearing = Ox.rad(bearing); pointB.lat = Math.asin( Math.sin(point.lat) * Math.cos(distance) + Math.cos(point.lat) * Math.sin(distance) * Math.cos(bearing) ); pointB.lng = point.lng + Math.atan2( Math.sin(bearing) * Math.sin(distance) * Math.cos(point.lat), Math.cos(distance) - Math.sin(point.lat) * Math.sin(pointB.lat) ); return deg(pointB); }; /*@ Ox.getXYByLatLng Returns x/y on a 1x1 mercator projection for a given lat/lng > Ox.getXYByLatLng({lat: 0, lng: 0}) {x: 0.5, y: 0.5} @*/ Ox.getXYByLatLng = function(latlng) { function getVal(val) { return (val / (2 * Math.PI) + 0.5) } return { x: getVal(Ox.rad(latlng.lng)), y: getVal(Ox.asinh(Math.tan(Ox.rad(-latlng.lat)))) }; }; /*@ Ox.isPolar Returns true if a given point is outside the bounds of a mercator projection @*/ Ox.isPolar = function(point) { return point.lat < Ox.MIN_LATITUDE || point.lat > Ox.MAX_LATITUDE; }; }()); //@ Ox.Line (undocumented) Ox.Line = function(pointA, pointB) { var self = { points: [pointA, pointB] }, that = this; function rad() { return self.points.map(function(point) { return { lat: Ox.rad(point.lat()), lng: Ox.rad(point.lng()) }; }); } that.getArea = function() { }; that.getBearing = function() { }; that.getDistance = function() { var points = rad(); return Math.acos( Math.sin(point[0].lat) * Math.sin(point[1].lat) + Math.cos(point[0].lat) * Math.cos(point[1].lat) * Math.cos(point[1].lng - point[0].lng) ) * Ox.EARTH_RADIUS; }; that.getMidpoint = function() { var points = rad(), x = Math.cos(point[1].lat) * Math.cos(point[1].lng - point[0].lng), y = Math.cos(point[1].lat) * Math.sin(point[1].lng - point[0].lng), d = Math.sqrt( Math.pow(Math.cos(point[0].lat) + x, 2) + Math.pow(y, 2) ), lat = Ox.deg( Math.atan2(Math.sin(points[0].lat) + Math.sin(points[1].lat), d) ), lng = Ox.deg( points[0].lng + Math.atan2(y, math.cos(points[0].lat) + x) ); return new Point(lat, lng); }; that.points = function() { }; return that; }; //@ Ox.Point (undocumented) Ox.Point = function(lat, lng) { var self = {lat: lat, lng: lng}, that = this; that.lat = function() { }; that.latlng = function() { }; that.lng = function() { }; that.getMetersPerDegree = function() { return Math.cos(self.lat * Math.PI / 180) * Ox.EARTH_CIRCUMFERENCE / 360; } that.getXY = function() { return [ getXY(Ox.rad(self.lng)), getXY(Ox.asinh(Math.tan(Ox.rad(-self.lat)))) ]; }; return that; }; //@ Ox.Rectangle (undocumented) Ox.Rectangle = function(pointA, pointB) { var self = { points: [ new Point( Math.min(pointA.lat(), pointB.lat()), pointA.lng() ), new Point( Math.max(pointA.lat(), pointB.lat()), pointB.lng() ) ] }, that = this; that.contains = function(rectangle) { }; that.crossesDateline = function() { return self.points[0].lng > self.points[1].lng; } that.getCenter = function() { return new Ox.Line(self.points[0], self.points[1]).getMidpoint(); }; that.intersects = function(rectangle) { }; return that; };