/** * Finds the angle formed by two adjacent segments defined by 3 points. The result will be the (positive clockwise) * angle with origin on the `startPoint-midPoint` segment, or its explementary angle if required. * * @name angle * @param {Coord} startPoint Start Point Coordinates * @param {Coord} midPoint Mid Point Coordinates * @param {Coord} endPoint End Point Coordinates * @param {Object} [options={}] Optional parameters * @param {boolean} [options.explementary=false] Returns the explementary angle instead (360 - angle) * @param {boolean} [options.mercator=false] if calculations should be performed over Mercator or WGS84 projection * @returns {number} Angle between the provided points, or its explementary. * @example * turf.angle([5, 5], [5, 6], [3, 4]); * //=45 */ function angle(startPoint: Coord, midPoint: Coord, endPoint: Coord, options: { explementary?: boolean mercator?: boolean, } = {}): number { // Optional Parameters if (!isObject(options)) { throw new Error("options is invalid"); } // Validation if (!startPoint) { throw new Error("startPoint is required"); } if (!midPoint) { throw new Error("midPoint is required"); } if (!endPoint) { throw new Error("endPoint is required"); } // Rename to shorter variables const A = startPoint; const O = midPoint; const B = endPoint; // Main const azimuthAO = bearingToAzimuth((options.mercator !== true) ? bearing(A, O) : rhumbBearing(A, O)); const azimuthBO = bearingToAzimuth((options.mercator !== true) ? bearing(B, O) : rhumbBearing(B, O)); const angleAO = Math.abs(azimuthAO - azimuthBO); // Explementary angle if (options.explementary === true) { return 360 - angleAO; } return angleAO; }
/** * Takes a {@link LineString} and returns a {@link Point} at a specified distance along the line. * * @name along * @param {Feature<LineString>} line input line * @param {number} distance distance along the line * @param {Object} [options] Optional parameters * @param {string} [options.units="kilometers"] can be degrees, radians, miles, or kilometers * @returns {Feature<Point>} Point `distance` `units` along the line * @example * var line = turf.lineString([[-83, 30], [-84, 36], [-78, 41]]); * var options = {units: 'miles'}; * * var along = turf.along(line, 200, options); * * //addToMap * var addToMap = [along, line] */ function along(line: Feature<LineString>| LineString, distance: number, options: { units?: Units } = {}): Feature<Point> { // Optional parameters if (!isObject(options)) throw new Error('options is invalid'); // Validation let coords; if (line.type === 'Feature') coords = line.geometry.coordinates; else if (line.type === 'LineString') coords = line.coordinates; else throw new Error('input must be a LineString Feature or Geometry'); if (!isNumber(distance)) throw new Error('distance must be a number'); let travelled = 0; for (let i = 0; i < coords.length; i++) { if (distance >= travelled && i === coords.length - 1) break; else if (travelled >= distance) { const overshot = distance - travelled; if (!overshot) return point(coords[i]); else { const direction = bearing(coords[i], coords[i - 1]) - 180; const interpolated = destination(coords[i], overshot, direction, options); return interpolated; } } else { travelled += measureDistance(coords[i], coords[i + 1], options); } } return point(coords[coords.length - 1]); }
/** * Takes a {@link LineString} and returns a {@link Point} at a specified distance along the line. * * @name along * @param {Feature<LineString>} line input line * @param {number} distance distance along the line * @param {Object} [options] Optional parameters * @param {string} [options.units="kilometers"] can be degrees, radians, miles, or kilometers * @returns {Feature<Point>} Point `distance` `units` along the line * @example * var line = turf.lineString([[-83, 30], [-84, 36], [-78, 41]]); * var options = {units: 'miles'}; * * var along = turf.along(line, 200, options); * * //addToMap * var addToMap = [along, line] */ export default function along( line: Feature<LineString> | LineString, distance: number, options: {units?: Units} = {}, ): Feature<Point> { // Get Coords const geom = getGeom(line); const coords = geom.coordinates; let travelled = 0; for (let i = 0; i < coords.length; i++) { if (distance >= travelled && i === coords.length - 1) { break; } else if (travelled >= distance) { const overshot = distance - travelled; if (!overshot) { return point(coords[i]); } else { const direction = bearing(coords[i], coords[i - 1]) - 180; const interpolated = destination(coords[i], overshot, direction, options); return interpolated; } } else { travelled += measureDistance(coords[i], coords[i + 1], options); } } return point(coords[coords.length - 1]); }
flattenEach(lines, function (line: any) { const coords: any = getCoords(line); for (let i = 0; i < coords.length - 1; i++) { //start const start = point(coords[i]); start.properties.dist = distance(pt, start, options); //stop const stop = point(coords[i + 1]); stop.properties.dist = distance(pt, stop, options); // sectionLength const sectionLength = distance(start, stop, options); //perpendicular const heightDistance = Math.max(start.properties.dist, stop.properties.dist); const direction = bearing(start, stop); const perpendicularPt1 = destination(pt, heightDistance, direction + 90, options); const perpendicularPt2 = destination(pt, heightDistance, direction - 90, options); const intersect = lineIntersects( lineString([perpendicularPt1.geometry.coordinates, perpendicularPt2.geometry.coordinates]), lineString([start.geometry.coordinates, stop.geometry.coordinates]) ); let intersectPt = null; if (intersect.features.length > 0) { intersectPt = intersect.features[0]; intersectPt.properties.dist = distance(pt, intersectPt, options); intersectPt.properties.location = length + distance(start, intersectPt, options); } if (start.properties.dist < closestPt.properties.dist) { closestPt = start; closestPt.properties.index = i; closestPt.properties.location = length; } if (stop.properties.dist < closestPt.properties.dist) { closestPt = stop; closestPt.properties.index = i + 1; closestPt.properties.location = length + sectionLength; } if (intersectPt && intersectPt.properties.dist < closestPt.properties.dist) { closestPt = intersectPt; closestPt.properties.index = i; } // update length length += sectionLength; } });
/** * @private * @name getCosAndSin * @param {Array<Array<number>>} coordinates * @returns {Array<number>} [cos, sin] */ function getCosAndSin(coordinates: number[][], isPlanar: boolean): [number, number] { const beginPoint: number[] = coordinates[0]; const endPoint: number[] = coordinates[coordinates.length - 1]; if (isPlanar) { const [x0, y0]: number[] = beginPoint; const [x1, y1]: number[] = endPoint; const dx: number = x1 - x0; const dy: number = y1 - y0; const h = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); if (h < 0.000000001) { return [NaN, NaN]; } const sin1 = dy / h; const cos1 = dx / h; return [sin1, cos1]; } else { const angle = bearingToCartesian(bearing(beginPoint, endPoint)); const radian = angle * Math.PI / 180; return [Math.sin(radian), Math.cos(radian)]; } }