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;