{"version":3,"sources":["../../index.ts"],"sourcesContent":["import { Feature, Point, Position, LineString, MultiLineString } from \"geojson\";\nimport { distance } from \"@turf/distance\";\nimport { flattenEach } from \"@turf/meta\";\nimport {\n point,\n degreesToRadians,\n radiansToDegrees,\n Coord,\n Units,\n} from \"@turf/helpers\";\nimport { getCoord, getCoords } from \"@turf/invariant\";\n\n/**\n * Returns the nearest point on a line to a given point.\n *\n * If any of the segments in the input line string are antipodal and therefore\n * have an undefined arc, this function will instead return that the point lies\n * on the line.\n *\n * @function\n * @param {Geometry|Feature} lines lines to snap to\n * @param {Geometry|Feature|number[]} pt point to snap from\n * @param {Object} [options={}] Optional parameters\n * @param {Units} [options.units='kilometers'] Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}\n * @returns {Feature} closest point on the `line` to `point`. The properties object will contain four values: `index`: closest point was found on nth line part, `multiFeatureIndex`: closest point was found on the nth line of the `MultiLineString`, `dist`: distance between pt and the closest point, `location`: distance along the line between start and the closest point.\n * @example\n * var line = turf.lineString([\n * [-77.031669, 38.878605],\n * [-77.029609, 38.881946],\n * [-77.020339, 38.884084],\n * [-77.025661, 38.885821],\n * [-77.021884, 38.889563],\n * [-77.019824, 38.892368]\n * ]);\n * var pt = turf.point([-77.037076, 38.884017]);\n *\n * var snapped = turf.nearestPointOnLine(line, pt, {units: 'miles'});\n *\n * //addToMap\n * var addToMap = [line, pt, snapped];\n * snapped.properties['marker-color'] = '#00f';\n */\nfunction nearestPointOnLine(\n lines: Feature | G,\n pt: Coord,\n options: { units?: Units } = {}\n): Feature<\n Point,\n {\n dist: number;\n index: number;\n multiFeatureIndex: number;\n location: number;\n [key: string]: any;\n }\n> {\n if (!lines || !pt) {\n throw new Error(\"lines and pt are required arguments\");\n }\n\n const ptPos = getCoord(pt);\n\n let closestPt: Feature<\n Point,\n { dist: number; index: number; multiFeatureIndex: number; location: number }\n > = point([Infinity, Infinity], {\n dist: Infinity,\n index: -1,\n multiFeatureIndex: -1,\n location: -1,\n });\n\n let length = 0.0;\n flattenEach(\n lines,\n function (line: any, _featureIndex: number, multiFeatureIndex: number) {\n const coords: any = getCoords(line);\n\n for (let i = 0; i < coords.length - 1; i++) {\n //start - start of current line section\n const start: Feature = point(coords[i]);\n const startPos = getCoord(start);\n\n //stop - end of current line section\n const stop: Feature = point(coords[i + 1]);\n const stopPos = getCoord(stop);\n\n // sectionLength\n const sectionLength = distance(start, stop, options);\n let intersectPos: Position;\n let wasEnd: boolean;\n\n // Short circuit if snap point is start or end position of the line\n // Test the end position first for consistency in case they are\n // coincident\n if (stopPos[0] === ptPos[0] && stopPos[1] === ptPos[1]) {\n [intersectPos, wasEnd] = [stopPos, true];\n } else if (startPos[0] === ptPos[0] && startPos[1] === ptPos[1]) {\n [intersectPos, wasEnd] = [startPos, false];\n } else {\n // Otherwise, find the nearest point the hard way.\n [intersectPos, wasEnd] = nearestPointOnSegment(\n startPos,\n stopPos,\n ptPos\n );\n }\n\n const intersectPt = point(intersectPos, {\n dist: distance(pt, intersectPos, options),\n multiFeatureIndex: multiFeatureIndex,\n location: length + distance(start, intersectPos, options),\n });\n\n if (intersectPt.properties.dist < closestPt.properties.dist) {\n closestPt = {\n ...intersectPt,\n properties: {\n ...intersectPt.properties,\n // Legacy behaviour where index progresses to next segment # if we\n // went with the end point this iteration.\n index: wasEnd ? i + 1 : i,\n },\n };\n }\n\n // update length\n length += sectionLength;\n }\n }\n );\n\n return closestPt;\n}\n\n// A simple Vector3 type for cartesian operations.\ntype Vector = [number, number, number];\n\nfunction dot(v1: Vector, v2: Vector): number {\n const [v1x, v1y, v1z] = v1;\n const [v2x, v2y, v2z] = v2;\n return v1x * v2x + v1y * v2y + v1z * v2z;\n}\n\n// https://en.wikipedia.org/wiki/Cross_product\nfunction cross(v1: Vector, v2: Vector): Vector {\n const [v1x, v1y, v1z] = v1;\n const [v2x, v2y, v2z] = v2;\n return [v1y * v2z - v1z * v2y, v1z * v2x - v1x * v2z, v1x * v2y - v1y * v2x];\n}\n\nfunction magnitude(v: Vector): number {\n return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2) + Math.pow(v[2], 2));\n}\n\nfunction normalize(v: Vector): Vector {\n const mag = magnitude(v);\n return [v[0] / mag, v[1] / mag, v[2] / mag];\n}\n\nfunction lngLatToVector(a: Position): Vector {\n const lat = degreesToRadians(a[1]);\n const lng = degreesToRadians(a[0]);\n return [\n Math.cos(lat) * Math.cos(lng),\n Math.cos(lat) * Math.sin(lng),\n Math.sin(lat),\n ];\n}\n\nfunction vectorToLngLat(v: Vector): Position {\n const [x, y, z] = v;\n // Clamp the z-value to ensure that is inside the [-1, 1] domain as required\n // by asin. Note therefore that this function should only be applied to unit\n // vectors so z > 1 should not exist\n const zClamp = Math.min(Math.max(z, -1), 1);\n const lat = radiansToDegrees(Math.asin(zClamp));\n const lng = radiansToDegrees(Math.atan2(y, x));\n\n return [lng, lat];\n}\n\nfunction nearestPointOnSegment(\n posA: Position, // start point of segment to measure to\n posB: Position, // end point of segment to measure to\n posC: Position // point to measure from\n): [Position, boolean] {\n // Based heavily on this article on finding cross track distance to an arc:\n // https://gis.stackexchange.com/questions/209540/projecting-cross-track-distance-on-great-circle\n\n // Convert spherical (lng, lat) to cartesian vector coords (x, y, z)\n // In the below https://tikz.net/spherical_1/ we convert lng (𝜙) and lat (𝜃)\n // into vectors with x, y, and z components with a length (r) of 1.\n const A = lngLatToVector(posA); // the vector from 0,0,0 to posA\n const B = lngLatToVector(posB); // ... to posB\n const C = lngLatToVector(posC); // ... to posC\n\n // The axis (normal vector) of the great circle plane containing the line segment\n const segmentAxis = cross(A, B);\n\n // Two degenerate cases exist for the segment axis cross product. The first is\n // when vectors are aligned (within the bounds of floating point tolerance).\n // The second is where vectors are antipodal (again within the bounds of\n // tolerance. Both cases produce a [0, 0, 0] cross product which invalidates\n // the rest of the algorithm, but each case must be handled separately:\n // - The aligned case indicates coincidence of A and B. therefore this can be\n // an early return assuming the closest point is the end (for consistency).\n // - The antipodal case is truly degenerate - an infinte number of great\n // circles are possible and one will always pass through C. However, given\n // that this case is both highly unlikely to occur in practice and that is\n // will usually be logically sound to return that the point is on the\n // segment, we choose to return the provided point.\n if (segmentAxis[0] === 0 && segmentAxis[1] === 0 && segmentAxis[2] === 0) {\n if (dot(A, B) > 0) {\n return [[...posB], true];\n } else {\n return [[...posC], false];\n }\n }\n\n // The axis of the great circle passing through the segment's axis and the\n // target point\n const targetAxis = cross(segmentAxis, C);\n\n // This cross product also has a degenerate case where the segment axis is\n // coincident with or antipodal to the target point. In this case the point\n // is equidistant to the entire segment. For consistency, we early return the\n // endpoint as the matching point.\n if (targetAxis[0] === 0 && targetAxis[1] === 0 && targetAxis[2] === 0) {\n return [[...posB], true];\n }\n\n // The line of intersection between the two great circle planes\n const intersectionAxis = cross(targetAxis, segmentAxis);\n\n // Vectors to the two points these great circles intersect are the normalized\n // intersection and its antipodes\n const I1 = normalize(intersectionAxis);\n const I2: Vector = [-I1[0], -I1[1], -I1[2]];\n\n // Figure out which is the closest intersection to this segment of the great circle\n // Note that for points on a unit sphere, the dot product represents the\n // cosine of the angle between the two vectors which monotonically increases\n // the closer the two points are together and therefore determines proximity\n const I = dot(C, I1) > dot(C, I2) ? I1 : I2;\n\n // I is the closest intersection to the segment, though might not actually be\n // ON the segment. To test whether the closest intersection lies on the arc or\n // not, we do a cross product comparison to check rotation around the unit\n // circle defined by the great circle plane.\n const segmentAxisNorm = normalize(segmentAxis);\n const cmpAI = dot(cross(A, I), segmentAxisNorm);\n const cmpIB = dot(cross(I, B), segmentAxisNorm);\n\n // When both comparisons are positive, the rotation from A to I to B is in the\n // same direction, implying that I lies between A and B\n if (cmpAI >= 0 && cmpIB >= 0) {\n return [vectorToLngLat(I), false];\n }\n\n // Finally process the case where the intersection is not on the segment,\n // using the dot product with the original point to find the closest endpoint\n if (dot(A, C) > dot(B, C)) {\n // Clone position when returning as it is reasonable to not expect structural\n // sharing on the returned Position in all return cases\n return [[...posA], false];\n } else {\n return [[...posB], true];\n }\n}\n\nexport { nearestPointOnLine };\nexport default nearestPointOnLine;\n"],"mappings":"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","names":[]}