in modules/edit-modes/src/utils.ts [114:181]
export function nearestPointOnProjectedLine(
line: FeatureOf<LineString>,
inPoint: FeatureOf<Point>,
viewport: Viewport
): NearestPointType {
const wmViewport = new WebMercatorViewport(viewport);
// Project the line to viewport, then find the nearest point
const coordinates: Array<Array<number>> = line.geometry.coordinates as any;
const projectedCoords = coordinates.map(([x, y, z = 0]) => wmViewport.project([x, y, z]));
const [x, y] = wmViewport.project(inPoint.geometry.coordinates);
// console.log('projectedCoords', JSON.stringify(projectedCoords));
let minDistance = Infinity;
let minPointInfo = {};
projectedCoords.forEach(([x2, y2], index) => {
if (index === 0) {
return;
}
const [x1, y1] = projectedCoords[index - 1];
// line from projectedCoords[index - 1] to projectedCoords[index]
// convert to Ax + By + C = 0
const A = y1 - y2;
const B = x2 - x1;
const C = x1 * y2 - x2 * y1;
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
const div = A * A + B * B;
const distance = Math.abs(A * x + B * y + C) / Math.sqrt(div);
// TODO: Check if inside bounds
if (distance < minDistance) {
minDistance = distance;
minPointInfo = {
index,
x0: (B * (B * x - A * y) - A * C) / div,
y0: (A * (-B * x + A * y) - B * C) / div,
};
}
});
// @ts-ignore
const { index, x0, y0 } = minPointInfo;
const [x1, y1, z1 = 0] = projectedCoords[index - 1];
const [x2, y2, z2 = 0] = projectedCoords[index];
// calculate what ratio of the line we are on to find the proper z
const lineLength = distance2d(x1, y1, x2, y2);
const startToPointLength = distance2d(x1, y1, x0, y0);
const ratio = startToPointLength / lineLength;
const z0 = mix(z1, z2, ratio);
return {
type: 'Feature',
geometry: {
type: 'Point',
// @ts-expect-error
coordinates: wmViewport.unproject([x0, y0, z0]),
},
properties: {
// TODO: calculate the distance in proper units
dist: minDistance,
index: index - 1,
},
};
}