oxjs/source/Ox/js/Geo.js
2012-01-06 17:27:54 +05:30

355 lines
No EOL
10 KiB
JavaScript

'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 <f> Returns true if a given line crosses the dateline
> Ox.crossesDateline({lat: 0, lng: -90}, {lat: 0, lng: 90})
false
> Ox.crossesDateline({lat: 0, lng: 90}, {lat: 0, lng: -90})
true
@*/
Ox.crossesDateline = function(pointA, pointB) {
return pointA.lng > pointB.lng;
};
/*@
Ox.getArea <f> 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 <f> Returns the bearing from one point to another
> Ox.getBearing({lat: -45, lng: 0}, {lat: 45, lng: 0})
0
> Ox.getBearing({lat: 0, lng: -90}, {lat: 0, lng: 90})
90
@*/
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 <f> 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 <f> Returns points on a circle around a given point
(center, radius, precision) -> <a> Points
center <o> Center point ({lat, lng})
radius <n> Radius in meters
precision <n> 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 <f> 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 <f> 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 <f> 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 <f> Returns points on a line between two points
(pointA, pointB, precision) -> <a> Points
pointA <o> Start point ({lat, lng})
pointB <o> End point ({lat, lng})
precision <n> 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 <f> 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 <f> 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 <f> 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 <f> Returns true if a given point is outside the bounds of a mercator projection
> Ox.isPolar({lat: 90, lng: 0})
true
@*/
Ox.isPolar = function(point) {
return point.lat < Ox.MIN_LATITUDE || point.lat > Ox.MAX_LATITUDE;
};
}());
//@ Ox.Line <f> (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 <f> (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 <f> (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;
};