(function() { // fixme: make all this work with different types of "points" // i.e. {lat, lng}, [lat, lng] function rad(point) { return { lat: Ox.rad(point.lat), lng: Ox.rad(point.lng) }; } /*@ Ox.crossesDateline Returns true if a given rectangle crosses the dateline @*/ Ox.crossesDateline = function(point0, point1) { return point0.lng > point1.lng; } /*@ Ox.getArea Returns the area in square meters of a given rectancle @*/ Ox.getArea = function(point0, point1) { /* area of a ring between two latitudes: 2 * PI * r^2 * abs(sin(lat0) - sin(lat1)) see http://mathforum.org/library/drmath/view/63767.html */ /* 2 * Math.PI * Math.pow(Ox.EARTH_RADIUS, 2) * Math.abs(Math.sin(Ox.rad(0)) - Math.sin(Ox.rad(1))) * Math.abs(Ox.rad(0) - Ox.rad(1)) / (2 * Math.PI) */ if (Ox.crossesDateline(point0, point1)) { point1.lng += 360; } var point0 = rad(point0), point1 = rad(point1); return Math.pow(Ox.EARTH_RADIUS, 2) * Math.abs(Math.sin(point0.lat) - Math.sin(point1.lat)) * Math.abs(point0.lng - point1.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(point0, point1) { var point0 = rad(point0), point1 = rad(point1), x = Math.cos(point0.lat) * Math.sin(point1.lat) - Math.sin(point0.lat) * Math.cos(point1.lat) * Math.cos(point1.lng - point0.lng), y = Math.sin(point1.lng - point0.lng) * Math.cos(point1.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(point0, point1) { var point0 = rad(point0), point1 = rad(point1), x = Math.cos(point1.lat) * Math.cos(point1.lng - point0.lng), y = Math.cos(point1.lat) * Math.sin(point1.lng - point0.lng), d = Math.sqrt( Math.pow(Math.cos(point0.lat) + x, 2) + Math.pow(y, 2) ), lat = Ox.deg( Math.atan2(Math.sin(point0.lat) + Math.sin(point1.lat), d) ), lng = Ox.deg( point0.lng + Math.atan2(y, Math.cos(point0.lat) + x) ); return {lat: lat, lng: lng}; }; /*@ 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(point0, point1) { var point0 = rad(point0), point1 = rad(point1); return Math.acos( Math.sin(point0.lat) * Math.sin(point1.lat) + Math.cos(point0.lat) * Math.cos(point1.lat) * Math.cos(point1.lng - point0.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.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.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.Line (undocumented) Ox.Line = function(point0, point1) { var self = { points: [point0, point1] }, 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(point0, point1) { var self = { points: [ new Point( Math.min(point0.lat(), point1.lat()), point0.lng() ), new Point( Math.max(point0.lat(), point1.lat()), point1.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; };