nearest-segment
Version:
Finds the segment of a line nearest to a point
97 lines (84 loc) • 3.29 kB
JavaScript
;
var dist = require('vectors/dist')(2)
var copy = require('vectors/copy')(2)
var sub = require('vectors/sub')(2)
var normalize = require('vectors/normalize')(2)
var dot = require('vectors/dot')(2)
var assert = require('assert')
var POINT_ERR = '`point` must be an array of 2 numbers'
var COORDS_ERR = '`coords` must be an array of at least 2 points'
/**
* Gets the index of the closest point to the given point
* and the indices of the points either side (in the order they're given)
* @param array coords array of point arrays
* @param array point point to search with
* @return object with indices into the original coords array of
* the closest, and 1 or both of prev and next
*/
function getClosestCoordIndices(coords, point) {
var smallestDist
var lastIndex
var coordIndices = {}
coords.forEach(function eachCoord(coord, i) {
var d = dist(point, coord)
if (!coords[coordIndices.closest] || d < smallestDist) {
smallestDist = d
coordIndices.prev = lastIndex
coordIndices.closest = i
coordIndices.next = undefined
} else if (coords[coordIndices.closest] && !coords[coordIndices.next]) {
coordIndices.next = i
}
lastIndex = i
})
return coordIndices
}
/**
* Given 2 vectors, returns a normalised vector between them
* @param array from point considered origin for the resultant vector
* @param array to point the resultant vector points at
* @return array resultant unit vector
*/
function getUnitVector(from, to) {
var vec = copy(to)
vec = sub(vec, from)
return normalize(vec)
}
/**
* Gets the indicies into the given coords array of the nearest 2
* points to the point given.
* @param array point the point to search for closest points e.g. [1, 2]
* @param array coords the points to search within e.g. [[1.5, 2], [1, 3]]
* @return array the indices of the nearest 2 points e.g. [0, 1]
*/
module.exports = function getNearestSegment(point, coords) {
assert(Array.isArray(point), POINT_ERR)
assert.equal(point.length, 2, POINT_ERR)
assert.equal(typeof point[0], 'number', POINT_ERR)
assert.equal(typeof point[1], 'number', POINT_ERR)
assert(Array.isArray(coords), COORDS_ERR)
assert(coords.length >= 2, COORDS_ERR)
var result = []
var coordIndices = getClosestCoordIndices(coords, point)
if (typeof coordIndices.next === 'undefined') {
// Closest to last coords, so select last 2 coordIndices
result = [coordIndices.prev, coordIndices.closest]
} else if (typeof coordIndices.prev === 'undefined') {
// Closest to first coords, so select first 2 coordIndices
result = [coordIndices.closest, coordIndices.next]
} else {
// Somewhere in the middle, use dot product
var closest = coords[coordIndices.closest]
var uPrev = getUnitVector(closest, coords[coordIndices.prev])
var uNext = getUnitVector(closest, coords[coordIndices.next])
var uPoint = getUnitVector(closest, point)
var prevDotPoint = dot(uPrev, uPoint)
var nextDotPoint = dot(uNext, uPoint)
if (prevDotPoint > nextDotPoint) {
result = [coordIndices.prev, coordIndices.closest]
} else {
result = [coordIndices.closest, coordIndices.next]
}
}
return result
}