in modules/edit-modes/src/utils.ts [183:315]
export function nearestPointOnLine<G extends LineString | MultiLineString>(
lines: FeatureOf<LineString>,
inPoint: FeatureOf<Point>,
viewport?: Viewport
): NearestPointType {
let mercator;
if (viewport) {
mercator = new WebMercatorViewport(viewport);
}
let closestPoint: any = point([Infinity, Infinity], {
dist: Infinity,
});
if (!lines.geometry?.coordinates.length || lines.geometry?.coordinates.length < 2) {
return closestPoint;
}
// @ts-ignore
flattenEach(lines, (line: any) => {
const coords: any = getCoords(line);
// @ts-ignore
const pointCoords: any = getCoords(inPoint);
let minDist;
let to;
let from;
let x;
let y;
let segmentIdx;
let dist;
if (coords.length > 1 && pointCoords.length) {
let lineCoordinates;
let pointCoordinate;
// If viewport is given, then translate these coordinates to pixels to increase precision
if (mercator) {
lineCoordinates = coords.map((lineCoordinate) => mercator.project(lineCoordinate));
pointCoordinate = mercator.project(pointCoords);
} else {
lineCoordinates = coords;
pointCoordinate = pointCoords;
}
for (let n = 1; n < lineCoordinates.length; n++) {
if (lineCoordinates[n][0] !== lineCoordinates[n - 1][0]) {
const slope =
(lineCoordinates[n][1] - lineCoordinates[n - 1][1]) /
(lineCoordinates[n][0] - lineCoordinates[n - 1][0]);
const inverseSlope = lineCoordinates[n][1] - slope * lineCoordinates[n][0];
dist =
Math.abs(slope * pointCoordinate[0] + inverseSlope - pointCoordinate[1]) /
Math.sqrt(slope * slope + 1);
} else dist = Math.abs(pointCoordinate[0] - lineCoordinates[n][0]);
// length^2 of line segment
const rl2 =
Math.pow(lineCoordinates[n][1] - lineCoordinates[n - 1][1], 2) +
Math.pow(lineCoordinates[n][0] - lineCoordinates[n - 1][0], 2);
// distance^2 of pt to end line segment
const ln2 =
Math.pow(lineCoordinates[n][1] - pointCoordinate[1], 2) +
Math.pow(lineCoordinates[n][0] - pointCoordinate[0], 2);
// distance^2 of pt to begin line segment
const lnm12 =
Math.pow(lineCoordinates[n - 1][1] - pointCoordinate[1], 2) +
Math.pow(lineCoordinates[n - 1][0] - pointCoordinate[0], 2);
// minimum distance^2 of pt to infinite line
const dist2 = Math.pow(dist, 2);
// calculated length^2 of line segment
const calcrl2 = ln2 - dist2 + lnm12 - dist2;
// redefine minimum distance to line segment (not infinite line) if necessary
if (calcrl2 > rl2) {
dist = Math.sqrt(Math.min(ln2, lnm12));
}
if (minDist === null || minDist === undefined || minDist > dist) {
// eslint-disable-next-line max-depth
if (calcrl2 > rl2) {
// eslint-disable-next-line max-depth
if (lnm12 < ln2) {
to = 0; // nearer to previous point
from = 1;
} else {
from = 0; // nearer to current point
to = 1;
}
} else {
// perpendicular from point intersects line segment
to = Math.sqrt(lnm12 - dist2) / Math.sqrt(rl2);
from = Math.sqrt(ln2 - dist2) / Math.sqrt(rl2);
}
minDist = dist;
segmentIdx = n;
}
}
const dx = lineCoordinates[segmentIdx - 1][0] - lineCoordinates[segmentIdx][0];
const dy = lineCoordinates[segmentIdx - 1][1] - lineCoordinates[segmentIdx][1];
x = lineCoordinates[segmentIdx - 1][0] - dx * to;
y = lineCoordinates[segmentIdx - 1][1] - dy * to;
}
// index needs to be -1 because we have to account for the shift from initial backscan
let snapPoint = { x, y, idx: segmentIdx - 1, to, from };
if (mercator) {
const pixelToLatLong = mercator.unproject([snapPoint.x, snapPoint.y]);
snapPoint = {
x: pixelToLatLong[0],
y: pixelToLatLong[1],
idx: segmentIdx - 1,
to,
from,
};
}
closestPoint = point([snapPoint.x, snapPoint.y], {
dist: Math.abs(snapPoint.from - snapPoint.to),
index: snapPoint.idx,
});
});
return closestPoint;
}