Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 100 additions & 14 deletions packages/turf-nearest-point-on-line/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@ this function. The new properties we recommend using as of v7.4 are:
multiFeatureIndex, index, location, and dist continue to work as previously
until at least the next major release.

`segmentIndex` always refers to the segment on which the nearest point was
found (the segment starting at `coords[segmentIndex]`). When the nearest
point lands exactly on a shared vertex it is *not* advanced to the following
segment. Callers that relied on the previous "favour the next segment"
behaviour (for example turn-by-turn navigation) can reconstruct it with the
[atEndOfSegment][1], [atEndOfLineString][2] and [hasNextSegment][3]
helpers exported by this package.

### Parameters

* `lines` **([Geometry][1] | [Feature][2]<([LineString][3] | [MultiLineString][4])>)** Lines to snap to
* `inputPoint` **([Geometry][1] | [Feature][2]<[Point][5]> | [Array][6]<[number][7]>)** Point to snap from
* `options` **[Object][8]** Optional parameters (optional, default `{}`)
* `lines` **([Geometry][4] | [Feature][5]<([LineString][6] | [MultiLineString][7])>)** Lines to snap to
* `inputPoint` **([Geometry][4] | [Feature][5]<[Point][8]> | [Array][9]<[number][10]>)** Point to snap from
* `options` **[Object][11]** Optional parameters (optional, default `{}`)

* `options.units` **Units** Supports all valid Turf [Units][9] (optional, default `'kilometers'`)
* `options.units` **Units** Supports all valid Turf [Units][12] (optional, default `'kilometers'`)

### Examples

Expand All @@ -51,25 +59,103 @@ var addToMap = [line, inputPoint, snapped];
snapped.properties['marker-color'] = '#00f';
```

Returns **[Feature][2]<[Point][5]>** closest point on the `lines` to the `inputPoint`. The point will have the following properties: `lineStringIndex`: closest point was found on the nth LineString (only relevant if input is MultiLineString), `segmentIndex`: closest point was found on nth line segment of the LineString, `totalDistance`: distance along the line from the absolute start of the MultiLineString, `lineDistance`: distance along the line from the start of the LineString where the closest point was found, `segmentDistance`: distance along the line from the start of the line segment where the closest point was found, `pointDistance`: distance to the input point.
Returns **[Feature][5]<[Point][8]>** closest point on the `lines` to the `inputPoint`. The point will have the following properties: `lineStringIndex`: closest point was found on the nth LineString (only relevant if input is MultiLineString), `segmentIndex`: closest point was found on nth line segment of the LineString, `totalDistance`: distance along the line from the absolute start of the MultiLineString, `lineDistance`: distance along the line from the start of the LineString where the closest point was found, `segmentDistance`: distance along the line from the start of the line segment where the closest point was found, `pointDistance`: distance to the input point.

## NearestPoint

The subset of `nearestPointOnLine`'s output that the segment helpers below
rely on. Any point returned by [nearestPointOnLine][13] satisfies this.

Type: [Feature][5]<[Point][8], {segmentIndex: [number][10], lineStringIndex: [number][10], : any}>

## resolveLineStringCoords

Resolves the array of coordinates for the LineString on which the nearest
point was found, transparently handling Feature vs Geometry inputs and
MultiLineStrings (via lineStringIndex).

### Parameters

* `nearestPoint` **[NearestPoint][14]**&#x20;
* `line` **LineInput**&#x20;

Returns **[Array][9]<[Position][15]>**&#x20;

## atEndOfSegment

Returns true if the nearest point lies exactly on the end vertex of its
segment (i.e. its coordinates equal `coords[segmentIndex + 1]`).

This is useful to restore the previous Turf behaviour of "favouring the next
segment" when the nearest point falls on a shared vertex between two
segments. Combine with [hasNextSegment][3] to decide whether it is safe to
advance `segmentIndex`.

### Parameters

* `nearestPoint` **[Feature][5]<[Point][8]>** A point returned by [nearestPointOnLine][13]
* `line` **([Feature][5]<([LineString][6] | [MultiLineString][7])> | [LineString][6] | [MultiLineString][7])** The original line passed to [nearestPointOnLine][13]

Returns **[boolean][16]** true if the point is at the end vertex of its segment

## atEndOfLineString

Returns true if the nearest point lies at the very last vertex of its
LineString. For MultiLineStrings, this checks within the specific
sub-LineString identified by `lineStringIndex`.

### Parameters

* `nearestPoint` **[Feature][5]<[Point][8]>** A point returned by [nearestPointOnLine][13]
* `line` **([Feature][5]<([LineString][6] | [MultiLineString][7])> | [LineString][6] | [MultiLineString][7])** The original line passed to [nearestPointOnLine][13]

Returns **[boolean][16]** true if the point is at the end of the LineString

## hasNextSegment

Returns true if there is another segment after the one on which the nearest
point was found, regardless of where on that segment the point landed. Useful
in combination with [atEndOfSegment][1] to determine whether it is safe to
advance to the next segment.

### Parameters

* `nearestPoint` **[Feature][5]<[Point][8]>** A point returned by [nearestPointOnLine][13]
* `line` **([Feature][5]<([LineString][6] | [MultiLineString][7])> | [LineString][6] | [MultiLineString][7])** The original line passed to [nearestPointOnLine][13]

Returns **[boolean][16]** true if a next segment exists

[1]: #atendofsegment

[2]: #atendoflinestring

[3]: #hasnextsegment

[4]: https://tools.ietf.org/html/rfc7946#section-3.1

[5]: https://tools.ietf.org/html/rfc7946#section-3.2

[6]: https://tools.ietf.org/html/rfc7946#section-3.1.4

[7]: https://tools.ietf.org/html/rfc7946#section-3.1.5

[1]: https://tools.ietf.org/html/rfc7946#section-3.1
[8]: https://tools.ietf.org/html/rfc7946#section-3.1.2

[2]: https://tools.ietf.org/html/rfc7946#section-3.2
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

[3]: https://tools.ietf.org/html/rfc7946#section-3.1.4
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

[4]: https://tools.ietf.org/html/rfc7946#section-3.1.5
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[5]: https://tools.ietf.org/html/rfc7946#section-3.1.2
[12]: https://turfjs.org/docs/api/types/Units

[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[13]: #nearestpointonline

[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[14]: #nearestpoint

[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[15]: https://developer.mozilla.org/docs/Web/API/Position

[9]: https://turfjs.org/docs/api/types/Units
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

<!-- This file is automatically generated. Please don't edit it directly. If you find an error, edit the source file of the module in question (likely index.js or index.ts), and re-run "yarn docs" from the root of the turf project. -->

Expand Down
122 changes: 110 additions & 12 deletions packages/turf-nearest-point-on-line/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ import { getCoord, getCoords } from "@turf/invariant";
* multiFeatureIndex, index, location, and dist continue to work as previously
* until at least the next major release.
*
* `segmentIndex` always refers to the segment on which the nearest point was
* found (the segment starting at `coords[segmentIndex]`). When the nearest
* point lands exactly on a shared vertex it is *not* advanced to the following
* segment. Callers that relied on the previous "favour the next segment"
* behaviour (for example turn-by-turn navigation) can reconstruct it with the
* {@link atEndOfSegment}, {@link atEndOfLineString} and {@link hasNextSegment}
* helpers exported by this package.
*
* @function
* @param {Geometry|Feature<LineString|MultiLineString>} lines Lines to snap to
* @param {Geometry|Feature<Point>|number[]} inputPoint Point to snap from
Expand Down Expand Up @@ -125,22 +133,17 @@ function nearestPointOnLine<G extends LineString | MultiLineString>(
// segmentLength
const segmentLength = distance(start, stop, options);
let intersectPos: Position;
let wasEnd: boolean;

// Short circuit if snap point is start or end position of the line
// Test the end position first for consistency in case they are
// coincident
if (stopPos[0] === inputPos[0] && stopPos[1] === inputPos[1]) {
[intersectPos, wasEnd] = [stopPos, true];
intersectPos = stopPos;
} else if (startPos[0] === inputPos[0] && startPos[1] === inputPos[1]) {
[intersectPos, wasEnd] = [startPos, false];
intersectPos = startPos;
} else {
// Otherwise, find the nearest point the hard way.
[intersectPos, wasEnd] = nearestPointOnSegment(
startPos,
stopPos,
inputPos
);
[intersectPos] = nearestPointOnSegment(startPos, stopPos, inputPos);
}

const pointDistance = distance(inputPoint, intersectPos, options);
Expand All @@ -149,9 +152,11 @@ function nearestPointOnLine<G extends LineString | MultiLineString>(
const segmentDistance = distance(start, intersectPos, options);
closestPt = point(intersectPos, {
lineStringIndex: lineStringIndex,
// Legacy behaviour where index progresses to next segment # if we
// went with the end point this iteration.
segmentIndex: wasEnd ? i + 1 : i,
// segmentIndex always refers to the segment on which the point was
// found (the one starting at coords[i]), never the following one.
// Use atEndOfSegment/hasNextSegment to detect and restore the old
// "favour the next segment" behaviour where it is still needed.
segmentIndex: i,
totalDistance: totalDistance + segmentDistance,
lineDistance: lineDistance + segmentDistance,
segmentDistance: segmentDistance,
Expand Down Expand Up @@ -183,6 +188,94 @@ function nearestPointOnLine<G extends LineString | MultiLineString>(
return closestPt;
}

/**
* The subset of `nearestPointOnLine`'s output that the segment helpers below
* rely on. Any point returned by {@link nearestPointOnLine} satisfies this.
*/
type NearestPoint = Feature<
Point,
{ segmentIndex: number; lineStringIndex: number; [key: string]: any }
>;

type LineInput =
| Feature<LineString | MultiLineString>
| LineString
| MultiLineString;

/**
* Resolves the array of coordinates for the LineString on which the nearest
* point was found, transparently handling Feature vs Geometry inputs and
* MultiLineStrings (via lineStringIndex).
*/
function resolveLineStringCoords(
nearestPoint: NearestPoint,
line: LineInput
): Position[] {
const geom = line.type === "Feature" ? line.geometry : line;
return geom.type === "MultiLineString"
? geom.coordinates[nearestPoint.properties.lineStringIndex]
: geom.coordinates;
}

/**
* Returns true if the nearest point lies exactly on the end vertex of its
* segment (i.e. its coordinates equal `coords[segmentIndex + 1]`).
*
* This is useful to restore the previous Turf behaviour of "favouring the next
* segment" when the nearest point falls on a shared vertex between two
* segments. Combine with {@link hasNextSegment} to decide whether it is safe to
* advance `segmentIndex`.
*
* @function
* @param {Feature<Point>} nearestPoint A point returned by {@link nearestPointOnLine}
* @param {Feature<LineString|MultiLineString>|LineString|MultiLineString} line The original line passed to {@link nearestPointOnLine}
* @returns {boolean} true if the point is at the end vertex of its segment
*/
function atEndOfSegment(nearestPoint: NearestPoint, line: LineInput): boolean {
const coords = resolveLineStringCoords(nearestPoint, line);
const segmentEnd = coords[nearestPoint.properties.segmentIndex + 1];
if (!segmentEnd) return false;

const [npLng, npLat] = nearestPoint.geometry.coordinates;
return npLng === segmentEnd[0] && npLat === segmentEnd[1];
}

/**
* Returns true if the nearest point lies at the very last vertex of its
* LineString. For MultiLineStrings, this checks within the specific
* sub-LineString identified by `lineStringIndex`.
*
* @function
* @param {Feature<Point>} nearestPoint A point returned by {@link nearestPointOnLine}
* @param {Feature<LineString|MultiLineString>|LineString|MultiLineString} line The original line passed to {@link nearestPointOnLine}
* @returns {boolean} true if the point is at the end of the LineString
*/
function atEndOfLineString(
nearestPoint: NearestPoint,
line: LineInput
): boolean {
const coords = resolveLineStringCoords(nearestPoint, line);
const isLastSegment =
nearestPoint.properties.segmentIndex === coords.length - 2;
return isLastSegment && atEndOfSegment(nearestPoint, line);
}

/**
* Returns true if there is another segment after the one on which the nearest
* point was found, regardless of where on that segment the point landed. Useful
* in combination with {@link atEndOfSegment} to determine whether it is safe to
* advance to the next segment.
*
* @function
* @param {Feature<Point>} nearestPoint A point returned by {@link nearestPointOnLine}
* @param {Feature<LineString|MultiLineString>|LineString|MultiLineString} line The original line passed to {@link nearestPointOnLine}
* @returns {boolean} true if a next segment exists
*/
function hasNextSegment(nearestPoint: NearestPoint, line: LineInput): boolean {
const coords = resolveLineStringCoords(nearestPoint, line);
return nearestPoint.properties.segmentIndex < coords.length - 2;
}

// A simple Vector3 type for cartesian operations.
type Vector = [number, number, number];

Expand Down Expand Up @@ -319,5 +412,10 @@ function nearestPointOnSegment(
}
}

export { nearestPointOnLine };
export {
nearestPointOnLine,
atEndOfSegment,
atEndOfLineString,
hasNextSegment,
};
export default nearestPointOnLine;
3 changes: 2 additions & 1 deletion packages/turf-nearest-point-on-line/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"Angel Lacret <@alacret>",
"Emil Junker <@EmilJunker>",
"Jon Miles <@jonmiles>",
"John Ziebro <@Insighttful>"
"John Ziebro <@Insighttful>",
"Felipe Moran <@Feliperm17>"
],
"license": "MIT",
"bugs": {
Expand Down
Loading