vizicities/src/geo/Geo.js

207 wiersze
5.5 KiB
JavaScript
Executable File

import {latLon as LatLon} from './LatLon';
import {point as Point} from './Point';
var Geo = {};
// Radius / WGS84 semi-major axis
Geo.R = 6378137;
Geo.MAX_LATITUDE = 85.0511287798;
// WGS84 eccentricity
Geo.ECC = 0.081819191;
Geo.ECC2 = 0.081819191 * 0.081819191;
// Multiplier is used to expand or compress the WebGL coordinate space relative
// to the EPSG:3857 / Pseudo-Mercator coordinate space (metres).
//
// This is useful for keeping coordinate calculations to smaller numbers and
// helps increase accuracy of things like the z-buffer, object intersection,
// and camera near and far clipping.
//
// A multiplier of 1 would mean a 1:1 mapping between WebGL and EPSG:3857
// coordinates (1 EPSG:3857 metre === 1 WebGL unit)
//
// A multiplier of 0.1 would mean a 1:0.1 mapping between WebGL and EPSG:3857
// coordinates (1 EPSG:3857 metre === 0.1 WebGL units)
Geo.multiplier = 0.1;
Geo.project = function(latlon) {
var d = Math.PI / 180;
var max = Geo.MAX_LATITUDE;
var lat = Math.max(Math.min(max, latlon.lat), -max);
var sin = Math.sin(lat * d);
return Point(
Geo.R * latlon.lon * d,
Geo.R * Math.log((1 + sin) / (1 - sin)) / 2
);
},
Geo.unproject = function(point) {
var d = 180 / Math.PI;
return LatLon(
(2 * Math.atan(Math.exp(point.y / Geo.R)) - (Math.PI / 2)) * d,
point.x * d / Geo.R
);
};
// Converts geo coords to pixel / WebGL ones
// This just reverses the Y axis to match WebGL
Geo.latLonToPoint = function(latlon) {
var projected = Geo.project(latlon);
projected.y *= -1;
projected.x *= Geo.multiplier;
projected.y *= Geo.multiplier;
return projected;
};
// Converts pixel / WebGL coords to geo coords
// This just reverses the Y axis to match WebGL
Geo.pointToLatLon = function(point) {
var _point = Point(point.x, point.y * -1);
_point.x /= Geo.multiplier;
_point.y /= Geo.multiplier;
return Geo.unproject(_point);
};
// Scale factor for converting between real metres and projected metres
//
// projectedMetres = realMetres * pointScale
// realMetres = projectedMetres / pointScale
//
// Accurate scale factor uses proper Web Mercator scaling
// See pg.9: http://www.hydrometronics.com/downloads/Web%20Mercator%20-%20Non-Conformal,%20Non-Mercator%20(notes).pdf
// See: http://jsfiddle.net/robhawkes/yws924cf/
Geo.pointScale = function(latlon, accurate) {
var rad = Math.PI / 180;
var k;
if (!accurate) {
k = 1 / Math.cos(latlon.lat * rad);
// [scaleX, scaleY]
return [k, k];
} else {
var lat = latlon.lat * rad;
var lon = latlon.lon * rad;
var a = Geo.R;
var sinLat = Math.sin(lat);
var sinLat2 = sinLat * sinLat;
var cosLat = Math.cos(lat);
// Radius meridian
var p = a * (1 - Geo.ECC2) / Math.pow(1 - Geo.ECC2 * sinLat2, 3 / 2);
// Radius prime meridian
var v = a / Math.sqrt(1 - Geo.ECC2 * sinLat2);
// Scale N/S
var h = (a / p) / cosLat;
// Scale E/W
k = (a / v) / cosLat;
// [scaleX, scaleY]
return [k, h];
}
};
// Convert real metres to projected units
//
// Latitude scale is chosen because it fluctuates more than longitude
Geo.metresToProjected = function(metres, pointScale) {
return metres * pointScale[1];
};
// Convert projected units to real metres
//
// Latitude scale is chosen because it fluctuates more than longitude
Geo.projectedToMetres = function(projectedUnits, pointScale) {
return projectedUnits / pointScale[1];
};
// Convert real metres to a value in world (WebGL) units
Geo.metresToWorld = function(metres, pointScale) {
// Transform metres to projected metres using the latitude point scale
//
// Latitude scale is chosen because it fluctuates more than longitude
var projectedMetres = Geo.metresToProjected(metres, pointScale);
return projectedMetres * Geo.multiplier;
};
// Convert world (WebGL) units to a value in real metres
Geo.worldToMetres = function(worldUnits, pointScale) {
var projectedUnits = worldUnits;
var realMetres = Geo.projectedToMetres(projectedUnits, pointScale);
return realMetres / Geo.multiplier;
};
// Returns the world width in pixels for a given zoom, assuming tile dimensions
// of 256x256 pixels
Geo.scale = function(zoom) {
return 256 * Math.pow(2, zoom);
};
// Returns zoom level for a given scale value
// This only works with a scale value that is based on map pixel width
Geo.zoom = function(scale) {
return Math.log(scale / 256) / Math.LN2;
};
// Distance between two geographical points using spherical law of cosines
// approximation or Haversine
//
// See: http://www.movable-type.co.uk/scripts/latlong.html
Geo.distance = function(latlon1, latlon2, accurate) {
var rad = Math.PI / 180;
var lat1;
var lat2;
var a;
if (!accurate) {
lat1 = latlon1.lat * rad;
lat2 = latlon2.lat * rad;
a = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlon2.lon - latlon1.lon) * rad);
return Geo.R * Math.acos(Math.min(a, 1));
} else {
lat1 = latlon1.lat * rad;
lat2 = latlon2.lat * rad;
var lon1 = latlon1.lon * rad;
var lon2 = latlon2.lon * rad;
var deltaLat = lat2 - lat1;
var deltaLon = lon2 - lon1;
var halfDeltaLat = deltaLat / 2;
var halfDeltaLon = deltaLon / 2;
a = Math.sin(halfDeltaLat) * Math.sin(halfDeltaLat) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(halfDeltaLon) * Math.sin(halfDeltaLon);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return Geo.R * c;
}
};
Geo.bounds = (function() {
var d = Geo.R * Math.PI * Geo.multiplier;
return [[-d, -d], [d, d]];
})();
export default Geo;