/** * Takes a {@link Point} and calculates the location of a destination point given a distance in * degrees, radians, miles, or kilometers; and bearing in degrees. * This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature. * * @name destination * @param {Coord} origin starting point * @param {number} distance distance from the origin point * @param {number} bearing ranging from -180 to 180 * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] miles, kilometers, degrees, or radians * @param {Object} [options.properties={}] Translate properties to Point * @returns {Feature<Point>} destination point * @example * var point = turf.point([-75.343, 39.984]); * var distance = 50; * var bearing = 90; * var options = {units: 'miles'}; * * var destination = turf.destination(point, distance, bearing, options); * * //addToMap * var addToMap = [point, destination] * destination.properties['marker-color'] = '#f00'; * point.properties['marker-color'] = '#0f0'; */ export default function destination<P = Properties>( origin: Coord, distance: number, bearing: number, options: { units?: Units, properties?: P, } = {}, ): Feature<Point, P> { // Handle input const coordinates1 = getCoord(origin); const longitude1 = degreesToRadians(coordinates1[0]); const latitude1 = degreesToRadians(coordinates1[1]); const bearingRad = degreesToRadians(bearing); const radians = lengthToRadians(distance, options.units); // Main const latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(radians) + Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearingRad)); const longitude2 = longitude1 + Math.atan2(Math.sin(bearingRad) * Math.sin(radians) * Math.cos(latitude1), Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2)); const lng = radiansToDegrees(longitude2); const lat = radiansToDegrees(latitude2); return point([lng, lat], options.properties); }
/** * Returns the destination point having travelled along a rhumb line from origin point the given * distance on the given bearing. * Adapted from Geodesy: http://www.movable-type.co.uk/scripts/latlong.html#rhumblines * * @private * @param {Array<number>} origin - point * @param {number} distance - Distance travelled, in same units as earth radius (default: metres). * @param {number} bearing - Bearing in degrees from north. * @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres). * @returns {Array<number>} Destination point. */ function calculateRhumbDestination(origin: number[], distance: number, bearing: number, radius?: number) { // φ => phi // λ => lambda // ψ => psi // Δ => Delta // δ => delta // θ => theta radius = (radius === undefined) ? earthRadius : Number(radius); const delta = distance / radius; // angular distance in radians const lambda1 = origin[0] * Math.PI / 180; // to radians, but without normalize to 𝜋 const phi1 = degreesToRadians(origin[1]); const theta = degreesToRadians(bearing); const DeltaPhi = delta * Math.cos(theta); let phi2 = phi1 + DeltaPhi; // check for some daft bugger going past the pole, normalise latitude if so if (Math.abs(phi2) > Math.PI / 2) { phi2 = phi2 > 0 ? Math.PI - phi2 : -Math.PI - phi2; } const DeltaPsi = Math.log(Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4)); // E-W course becomes ill-conditioned with 0/0 const q = Math.abs(DeltaPsi) > 10e-12 ? DeltaPhi / DeltaPsi : Math.cos(phi1); const DeltaLambda = delta * Math.sin(theta) / q; const lambda2 = lambda1 + DeltaLambda; return [((lambda2 * 180 / Math.PI) + 540) % 360 - 180, phi2 * 180 / Math.PI]; // normalise to −180..+180° }
//http://en.wikipedia.org/wiki/Haversine_formula //http://www.movable-type.co.uk/scripts/latlong.html /** * Calculates the distance between two {@link Point|points} in degrees, radians, miles, or kilometers. * This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature. * * @name distance * @param {Coord} from origin point * @param {Coord} to destination point * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers * @returns {number} distance between the two points * @example * var from = turf.point([-75.343, 39.984]); * var to = turf.point([-75.534, 39.123]); * var options = {units: 'miles'}; * * var distance = turf.distance(from, to, options); * * //addToMap * var addToMap = [from, to]; * from.properties.distance = distance; * to.properties.distance = distance; */ function distance(from: Coord, to: Coord, options: { units?: Units, } = {}) { var coordinates1 = getCoord(from); var coordinates2 = getCoord(to); var dLat = degreesToRadians((coordinates2[1] - coordinates1[1])); var dLon = degreesToRadians((coordinates2[0] - coordinates1[0])); var lat1 = degreesToRadians(coordinates1[1]); var lat2 = degreesToRadians(coordinates2[1]); var a = Math.pow(Math.sin(dLat / 2), 2) + Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2); return radiansToLength(2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)), options.units); }
/** * Returns the bearing from ‘this’ point to destination point along a rhumb line. * Adapted from Geodesy: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js * * @private * @param {Array<number>} from - origin point. * @param {Array<number>} to - destination point. * @returns {number} Bearing in degrees from north. * @example * var p1 = new LatLon(51.127, 1.338); * var p2 = new LatLon(50.964, 1.853); * var d = p1.rhumbBearingTo(p2); // 116.7 m */ function calculateRhumbBearing(from: number[], to: number[]) { // φ => phi // Δλ => deltaLambda // Δψ => deltaPsi // θ => theta const phi1 = degreesToRadians(from[1]); const phi2 = degreesToRadians(to[1]); let deltaLambda = degreesToRadians((to[0] - from[0])); // if deltaLambdaon over 180° take shorter rhumb line across the anti-meridian: if (deltaLambda > Math.PI) { deltaLambda -= 2 * Math.PI; } if (deltaLambda < -Math.PI) { deltaLambda += 2 * Math.PI; } const deltaPsi = Math.log(Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4)); const theta = Math.atan2(deltaLambda, deltaPsi); return (radiansToDegrees(theta) + 360) % 360; }
// http://en.wikipedia.org/wiki/Haversine_formula // http://www.movable-type.co.uk/scripts/latlong.html /** * Takes two {@link Point|points} and finds the geographic bearing between them, * i.e. the angle measured in degrees from the north line (0 degrees) * * @name bearing * @param {Coord} start starting Point * @param {Coord} end ending Point * @param {Object} [options={}] Optional parameters * @param {boolean} [options.final=false] calculates the final bearing if true * @returns {number} bearing in decimal degrees, between -180 and 180 degrees (positive clockwise) * @example * var point1 = turf.point([-75.343, 39.984]); * var point2 = turf.point([-75.534, 39.123]); * * var bearing = turf.bearing(point1, point2); * * //addToMap * var addToMap = [point1, point2] * point1.properties['marker-color'] = '#f00' * point2.properties['marker-color'] = '#0f0' * point1.properties.bearing = bearing */ export default function bearing(start: Coord, end: Coord, options: { final?: boolean, } = {}): number { // Reverse calculation if (options.final === true) { return calculateFinalBearing(start, end); } const coordinates1 = getCoord(start); const coordinates2 = getCoord(end); const lon1 = degreesToRadians(coordinates1[0]); const lon2 = degreesToRadians(coordinates2[0]); const lat1 = degreesToRadians(coordinates1[1]); const lat2 = degreesToRadians(coordinates2[1]); const a = Math.sin(lon2 - lon1) * Math.cos(lat2); const b = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); return radiansToDegrees(Math.atan2(a, b)); }