@turf/nearest-point-on-line 7.1.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,15 +4,19 @@
4
4
 
5
5
  ## nearestPointOnLine
6
6
 
7
- Takes a [Point][1] and a [LineString][2] and calculates the closest Point on the (Multi)LineString.
7
+ Returns the nearest point on a line to a given point.
8
+
9
+ If any of the segments in the input line string are antipodal and therefore
10
+ have an undefined arc, this function will instead return that the point lies
11
+ on the line.
8
12
 
9
13
  ### Parameters
10
14
 
11
- * `lines` **([Geometry][3] | [Feature][4]<([LineString][2] | [MultiLineString][5])>)** lines to snap to
12
- * `pt` **([Geometry][3] | [Feature][4]<[Point][1]> | [Array][6]<[number][7]>)** point to snap from
15
+ * `lines` **([Geometry][1] | [Feature][2]<([LineString][3] | [MultiLineString][4])>)** lines to snap to
16
+ * `pt` **([Geometry][1] | [Feature][2]<[Point][5]> | [Array][6]<[number][7]>)** point to snap from
13
17
  * `options` **[Object][8]** Optional parameters (optional, default `{}`)
14
18
 
15
- * `options.units` **[string][9]** can be degrees, radians, miles, or kilometers (optional, default `'kilometers'`)
19
+ * `options.units` **Units** Supports all valid Turf [Units][9] (optional, default `'kilometers'`)
16
20
 
17
21
  ### Examples
18
22
 
@@ -34,17 +38,17 @@ var addToMap = [line, pt, snapped];
34
38
  snapped.properties['marker-color'] = '#00f';
35
39
  ```
36
40
 
37
- Returns **[Feature][4]<[Point][1]>** 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.
41
+ Returns **[Feature][2]<[Point][5]>** 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.
38
42
 
39
- [1]: https://tools.ietf.org/html/rfc7946#section-3.1.2
43
+ [1]: https://tools.ietf.org/html/rfc7946#section-3.1
40
44
 
41
- [2]: https://tools.ietf.org/html/rfc7946#section-3.1.4
45
+ [2]: https://tools.ietf.org/html/rfc7946#section-3.2
42
46
 
43
- [3]: https://tools.ietf.org/html/rfc7946#section-3.1
47
+ [3]: https://tools.ietf.org/html/rfc7946#section-3.1.4
44
48
 
45
- [4]: https://tools.ietf.org/html/rfc7946#section-3.2
49
+ [4]: https://tools.ietf.org/html/rfc7946#section-3.1.5
46
50
 
47
- [5]: https://tools.ietf.org/html/rfc7946#section-3.1.5
51
+ [5]: https://tools.ietf.org/html/rfc7946#section-3.1.2
48
52
 
49
53
  [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
50
54
 
@@ -52,7 +56,7 @@ Returns **[Feature][4]<[Point][1]>** closest point on the `line` to `point`. The
52
56
 
53
57
  [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
54
58
 
55
- [9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
59
+ [9]: https://turfjs.org/docs/api/types/Units
56
60
 
57
61
  <!-- 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. -->
58
62
 
@@ -19,17 +19,19 @@ var __spreadValues = (a, b) => {
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
20
 
21
21
  // index.ts
22
- var _bearing = require('@turf/bearing');
23
22
  var _distance = require('@turf/distance');
24
- var _destination = require('@turf/destination');
25
- var _lineintersect = require('@turf/line-intersect');
26
23
  var _meta = require('@turf/meta');
24
+
25
+
26
+
27
+
27
28
  var _helpers = require('@turf/helpers');
28
29
  var _invariant = require('@turf/invariant');
29
30
  function nearestPointOnLine(lines, pt, options = {}) {
30
31
  if (!lines || !pt) {
31
32
  throw new Error("lines and pt are required arguments");
32
33
  }
34
+ const ptPos = _invariant.getCoord.call(void 0, pt);
33
35
  let closestPt = _helpers.point.call(void 0, [Infinity, Infinity], {
34
36
  dist: Infinity,
35
37
  index: -1,
@@ -43,65 +45,35 @@ function nearestPointOnLine(lines, pt, options = {}) {
43
45
  const coords = _invariant.getCoords.call(void 0, line);
44
46
  for (let i = 0; i < coords.length - 1; i++) {
45
47
  const start = _helpers.point.call(void 0, coords[i]);
46
- start.properties.dist = _distance.distance.call(void 0, pt, start, options);
48
+ const startPos = _invariant.getCoord.call(void 0, start);
47
49
  const stop = _helpers.point.call(void 0, coords[i + 1]);
48
- stop.properties.dist = _distance.distance.call(void 0, pt, stop, options);
50
+ const stopPos = _invariant.getCoord.call(void 0, stop);
49
51
  const sectionLength = _distance.distance.call(void 0, start, stop, options);
50
- const heightDistance = Math.max(
51
- start.properties.dist,
52
- stop.properties.dist
53
- );
54
- const direction = _bearing.bearing.call(void 0, start, stop);
55
- const perpendicularPt1 = _destination.destination.call(void 0,
56
- pt,
57
- heightDistance,
58
- direction + 90,
59
- options
60
- );
61
- const perpendicularPt2 = _destination.destination.call(void 0,
62
- pt,
63
- heightDistance,
64
- direction - 90,
65
- options
66
- );
67
- const intersect = _lineintersect.lineIntersect.call(void 0,
68
- _helpers.lineString.call(void 0, [
69
- perpendicularPt1.geometry.coordinates,
70
- perpendicularPt2.geometry.coordinates
71
- ]),
72
- _helpers.lineString.call(void 0, [start.geometry.coordinates, stop.geometry.coordinates])
73
- );
74
- let intersectPt;
75
- if (intersect.features.length > 0 && intersect.features[0]) {
76
- intersectPt = __spreadProps(__spreadValues({}, intersect.features[0]), {
77
- properties: {
78
- dist: _distance.distance.call(void 0, pt, intersect.features[0], options),
79
- multiFeatureIndex,
80
- location: length + _distance.distance.call(void 0, start, intersect.features[0], options)
81
- }
82
- });
83
- }
84
- if (start.properties.dist < closestPt.properties.dist) {
85
- closestPt = __spreadProps(__spreadValues({}, start), {
86
- properties: __spreadProps(__spreadValues({}, start.properties), {
87
- index: i,
88
- multiFeatureIndex,
89
- location: length
90
- })
91
- });
92
- }
93
- if (stop.properties.dist < closestPt.properties.dist) {
94
- closestPt = __spreadProps(__spreadValues({}, stop), {
95
- properties: __spreadProps(__spreadValues({}, stop.properties), {
96
- index: i + 1,
97
- multiFeatureIndex,
98
- location: length + sectionLength
99
- })
100
- });
52
+ let intersectPos;
53
+ let wasEnd;
54
+ if (stopPos[0] === ptPos[0] && stopPos[1] === ptPos[1]) {
55
+ [intersectPos, wasEnd] = [stopPos, true];
56
+ } else if (startPos[0] === ptPos[0] && startPos[1] === ptPos[1]) {
57
+ [intersectPos, wasEnd] = [startPos, false];
58
+ } else {
59
+ [intersectPos, wasEnd] = nearestPointOnSegment(
60
+ startPos,
61
+ stopPos,
62
+ ptPos
63
+ );
101
64
  }
102
- if (intersectPt && intersectPt.properties.dist < closestPt.properties.dist) {
65
+ const intersectPt = _helpers.point.call(void 0, intersectPos, {
66
+ dist: _distance.distance.call(void 0, pt, intersectPos, options),
67
+ multiFeatureIndex,
68
+ location: length + _distance.distance.call(void 0, start, intersectPos, options)
69
+ });
70
+ if (intersectPt.properties.dist < closestPt.properties.dist) {
103
71
  closestPt = __spreadProps(__spreadValues({}, intersectPt), {
104
- properties: __spreadProps(__spreadValues({}, intersectPt.properties), { index: i })
72
+ properties: __spreadProps(__spreadValues({}, intersectPt.properties), {
73
+ // Legacy behaviour where index progresses to next segment # if we
74
+ // went with the end point this iteration.
75
+ index: wasEnd ? i + 1 : i
76
+ })
105
77
  });
106
78
  }
107
79
  length += sectionLength;
@@ -110,9 +82,74 @@ function nearestPointOnLine(lines, pt, options = {}) {
110
82
  );
111
83
  return closestPt;
112
84
  }
113
- var turf_nearest_point_on_line_default = nearestPointOnLine;
85
+ function dot(v1, v2) {
86
+ const [v1x, v1y, v1z] = v1;
87
+ const [v2x, v2y, v2z] = v2;
88
+ return v1x * v2x + v1y * v2y + v1z * v2z;
89
+ }
90
+ function cross(v1, v2) {
91
+ const [v1x, v1y, v1z] = v1;
92
+ const [v2x, v2y, v2z] = v2;
93
+ return [v1y * v2z - v1z * v2y, v1z * v2x - v1x * v2z, v1x * v2y - v1y * v2x];
94
+ }
95
+ function magnitude(v) {
96
+ return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2) + Math.pow(v[2], 2));
97
+ }
98
+ function normalize(v) {
99
+ const mag = magnitude(v);
100
+ return [v[0] / mag, v[1] / mag, v[2] / mag];
101
+ }
102
+ function lngLatToVector(a) {
103
+ const lat = _helpers.degreesToRadians.call(void 0, a[1]);
104
+ const lng = _helpers.degreesToRadians.call(void 0, a[0]);
105
+ return [
106
+ Math.cos(lat) * Math.cos(lng),
107
+ Math.cos(lat) * Math.sin(lng),
108
+ Math.sin(lat)
109
+ ];
110
+ }
111
+ function vectorToLngLat(v) {
112
+ const [x, y, z] = v;
113
+ const zClamp = Math.min(Math.max(z, -1), 1);
114
+ const lat = _helpers.radiansToDegrees.call(void 0, Math.asin(zClamp));
115
+ const lng = _helpers.radiansToDegrees.call(void 0, Math.atan2(y, x));
116
+ return [lng, lat];
117
+ }
118
+ function nearestPointOnSegment(posA, posB, posC) {
119
+ const A = lngLatToVector(posA);
120
+ const B = lngLatToVector(posB);
121
+ const C = lngLatToVector(posC);
122
+ const segmentAxis = cross(A, B);
123
+ if (segmentAxis[0] === 0 && segmentAxis[1] === 0 && segmentAxis[2] === 0) {
124
+ if (dot(A, B) > 0) {
125
+ return [[...posB], true];
126
+ } else {
127
+ return [[...posC], false];
128
+ }
129
+ }
130
+ const targetAxis = cross(segmentAxis, C);
131
+ if (targetAxis[0] === 0 && targetAxis[1] === 0 && targetAxis[2] === 0) {
132
+ return [[...posB], true];
133
+ }
134
+ const intersectionAxis = cross(targetAxis, segmentAxis);
135
+ const I1 = normalize(intersectionAxis);
136
+ const I2 = [-I1[0], -I1[1], -I1[2]];
137
+ const I = dot(C, I1) > dot(C, I2) ? I1 : I2;
138
+ const segmentAxisNorm = normalize(segmentAxis);
139
+ const cmpAI = dot(cross(A, I), segmentAxisNorm);
140
+ const cmpIB = dot(cross(I, B), segmentAxisNorm);
141
+ if (cmpAI >= 0 && cmpIB >= 0) {
142
+ return [vectorToLngLat(I), false];
143
+ }
144
+ if (dot(A, C) > dot(B, C)) {
145
+ return [[...posA], false];
146
+ } else {
147
+ return [[...posB], true];
148
+ }
149
+ }
150
+ var index_default = nearestPointOnLine;
114
151
 
115
152
 
116
153
 
117
- exports.default = turf_nearest_point_on_line_default; exports.nearestPointOnLine = nearestPointOnLine;
154
+ exports.default = index_default; exports.nearestPointOnLine = nearestPointOnLine;
118
155
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,sBAAsB;AAChD,SAAS,mBAAmB;AAC5B,SAAS,OAAO,kBAAgC;AAChD,SAAS,iBAAiB;AA4B1B,SAAS,mBACP,OACA,IACA,UAA6B,CAAC,GAU9B;AACA,MAAI,CAAC,SAAS,CAAC,IAAI;AACjB,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAEA,MAAI,YAGA,MAAM,CAAC,UAAU,QAAQ,GAAG;AAAA,IAC9B,MAAM;AAAA,IACN,OAAO;AAAA,IACP,mBAAmB;AAAA,IACnB,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,SAAS;AACb;AAAA,IACE;AAAA,IACA,SAAU,MAAW,eAAuB,mBAA2B;AACrE,YAAM,SAAc,UAAU,IAAI;AAElC,eAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAE1C,cAAM,QAA0C,MAAM,OAAO,CAAC,CAAC;AAC/D,cAAM,WAAW,OAAO,SAAS,IAAI,OAAO,OAAO;AAEnD,cAAM,OAAyC,MAAM,OAAO,IAAI,CAAC,CAAC;AAClE,aAAK,WAAW,OAAO,SAAS,IAAI,MAAM,OAAO;AAEjD,cAAM,gBAAgB,SAAS,OAAO,MAAM,OAAO;AAEnD,cAAM,iBAAiB,KAAK;AAAA,UAC1B,MAAM,WAAW;AAAA,UACjB,KAAK,WAAW;AAAA,QAClB;AACA,cAAM,YAAY,QAAQ,OAAO,IAAI;AACrC,cAAM,mBAAmB;AAAA,UACvB;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QACF;AACA,cAAM,mBAAmB;AAAA,UACvB;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QACF;AACA,cAAM,YAAY;AAAA,UAChB,WAAW;AAAA,YACT,iBAAiB,SAAS;AAAA,YAC1B,iBAAiB,SAAS;AAAA,UAC5B,CAAC;AAAA,UACD,WAAW,CAAC,MAAM,SAAS,aAAa,KAAK,SAAS,WAAW,CAAC;AAAA,QACpE;AACA,YAAI;AAOJ,YAAI,UAAU,SAAS,SAAS,KAAK,UAAU,SAAS,CAAC,GAAG;AAC1D,wBAAc,iCACT,UAAU,SAAS,CAAC,IADX;AAAA,YAEZ,YAAY;AAAA,cACV,MAAM,SAAS,IAAI,UAAU,SAAS,CAAC,GAAG,OAAO;AAAA,cACjD;AAAA,cACA,UACE,SAAS,SAAS,OAAO,UAAU,SAAS,CAAC,GAAG,OAAO;AAAA,YAC3D;AAAA,UACF;AAAA,QACF;AAEA,YAAI,MAAM,WAAW,OAAO,UAAU,WAAW,MAAM;AACrD,sBAAY,iCACP,QADO;AAAA,YAEV,YAAY,iCACP,MAAM,aADC;AAAA,cAEV,OAAO;AAAA,cACP;AAAA,cACA,UAAU;AAAA,YACZ;AAAA,UACF;AAAA,QACF;AAEA,YAAI,KAAK,WAAW,OAAO,UAAU,WAAW,MAAM;AACpD,sBAAY,iCACP,OADO;AAAA,YAEV,YAAY,iCACP,KAAK,aADE;AAAA,cAEV,OAAO,IAAI;AAAA,cACX;AAAA,cACA,UAAU,SAAS;AAAA,YACrB;AAAA,UACF;AAAA,QACF;AAEA,YACE,eACA,YAAY,WAAW,OAAO,UAAU,WAAW,MACnD;AACA,sBAAY,iCACP,cADO;AAAA,YAEV,YAAY,iCAAK,YAAY,aAAjB,EAA6B,OAAO,EAAE;AAAA,UACpD;AAAA,QACF;AAEA,kBAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,IAAO,qCAAQ","sourcesContent":["import { Feature, Point, LineString, MultiLineString } from \"geojson\";\nimport { bearing } from \"@turf/bearing\";\nimport { distance } from \"@turf/distance\";\nimport { destination } from \"@turf/destination\";\nimport { lineIntersect as lineIntersects } from \"@turf/line-intersect\";\nimport { flattenEach } from \"@turf/meta\";\nimport { point, lineString, Coord, Units } from \"@turf/helpers\";\nimport { getCoords } from \"@turf/invariant\";\n\n/**\n * Takes a {@link Point} and a {@link LineString} and calculates the closest Point on the (Multi)LineString.\n *\n * @name nearestPointOnLine\n * @param {Geometry|Feature<LineString|MultiLineString>} lines lines to snap to\n * @param {Geometry|Feature<Point>|number[]} pt point to snap from\n * @param {Object} [options={}] Optional parameters\n * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers\n * @returns {Feature<Point>} 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<G extends LineString | MultiLineString>(\n lines: Feature<G> | 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 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\n const start: Feature<Point, { dist: number }> = point(coords[i]);\n start.properties.dist = distance(pt, start, options);\n //stop\n const stop: Feature<Point, { dist: number }> = point(coords[i + 1]);\n stop.properties.dist = distance(pt, stop, options);\n // sectionLength\n const sectionLength = distance(start, stop, options);\n //perpendicular\n const heightDistance = Math.max(\n start.properties.dist,\n stop.properties.dist\n );\n const direction = bearing(start, stop);\n const perpendicularPt1 = destination(\n pt,\n heightDistance,\n direction + 90,\n options\n );\n const perpendicularPt2 = destination(\n pt,\n heightDistance,\n direction - 90,\n options\n );\n const intersect = lineIntersects(\n lineString([\n perpendicularPt1.geometry.coordinates,\n perpendicularPt2.geometry.coordinates,\n ]),\n lineString([start.geometry.coordinates, stop.geometry.coordinates])\n );\n let intersectPt:\n | Feature<\n Point,\n { dist: number; multiFeatureIndex: number; location: number }\n >\n | undefined;\n\n if (intersect.features.length > 0 && intersect.features[0]) {\n intersectPt = {\n ...intersect.features[0],\n properties: {\n dist: distance(pt, intersect.features[0], options),\n multiFeatureIndex: multiFeatureIndex,\n location:\n length + distance(start, intersect.features[0], options),\n },\n };\n }\n\n if (start.properties.dist < closestPt.properties.dist) {\n closestPt = {\n ...start,\n properties: {\n ...start.properties,\n index: i,\n multiFeatureIndex: multiFeatureIndex,\n location: length,\n },\n };\n }\n\n if (stop.properties.dist < closestPt.properties.dist) {\n closestPt = {\n ...stop,\n properties: {\n ...stop.properties,\n index: i + 1,\n multiFeatureIndex: multiFeatureIndex,\n location: length + sectionLength,\n },\n };\n }\n\n if (\n intersectPt &&\n intersectPt.properties.dist < closestPt.properties.dist\n ) {\n closestPt = {\n ...intersectPt,\n properties: { ...intersectPt.properties, index: i },\n };\n }\n // update length\n length += sectionLength;\n }\n }\n );\n\n return closestPt;\n}\n\nexport { nearestPointOnLine };\nexport default nearestPointOnLine;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/turf/turf/packages/turf-nearest-point-on-line/dist/cjs/index.cjs","../../index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/turf/turf/packages/turf-nearest-point-on-line/dist/cjs/index.cjs","sourcesContent":[null,"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<LineString|MultiLineString>} lines lines to snap to\n * @param {Geometry|Feature<Point>|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<Point>} 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<G extends LineString | MultiLineString>(\n lines: Feature<G> | 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, { dist: number }> = point(coords[i]);\n const startPos = getCoord(start);\n\n //stop - end of current line section\n const stop: Feature<Point, { dist: number }> = 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"]}
@@ -2,13 +2,17 @@ import { LineString, MultiLineString, Feature, Point } from 'geojson';
2
2
  import { Coord, Units } from '@turf/helpers';
3
3
 
4
4
  /**
5
- * Takes a {@link Point} and a {@link LineString} and calculates the closest Point on the (Multi)LineString.
5
+ * Returns the nearest point on a line to a given point.
6
6
  *
7
- * @name nearestPointOnLine
7
+ * If any of the segments in the input line string are antipodal and therefore
8
+ * have an undefined arc, this function will instead return that the point lies
9
+ * on the line.
10
+ *
11
+ * @function
8
12
  * @param {Geometry|Feature<LineString|MultiLineString>} lines lines to snap to
9
13
  * @param {Geometry|Feature<Point>|number[]} pt point to snap from
10
14
  * @param {Object} [options={}] Optional parameters
11
- * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
15
+ * @param {Units} [options.units='kilometers'] Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}
12
16
  * @returns {Feature<Point>} 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.
13
17
  * @example
14
18
  * var line = turf.lineString([
@@ -2,13 +2,17 @@ import { LineString, MultiLineString, Feature, Point } from 'geojson';
2
2
  import { Coord, Units } from '@turf/helpers';
3
3
 
4
4
  /**
5
- * Takes a {@link Point} and a {@link LineString} and calculates the closest Point on the (Multi)LineString.
5
+ * Returns the nearest point on a line to a given point.
6
6
  *
7
- * @name nearestPointOnLine
7
+ * If any of the segments in the input line string are antipodal and therefore
8
+ * have an undefined arc, this function will instead return that the point lies
9
+ * on the line.
10
+ *
11
+ * @function
8
12
  * @param {Geometry|Feature<LineString|MultiLineString>} lines lines to snap to
9
13
  * @param {Geometry|Feature<Point>|number[]} pt point to snap from
10
14
  * @param {Object} [options={}] Optional parameters
11
- * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
15
+ * @param {Units} [options.units='kilometers'] Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}
12
16
  * @returns {Feature<Point>} 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.
13
17
  * @example
14
18
  * var line = turf.lineString([
package/dist/esm/index.js CHANGED
@@ -19,17 +19,19 @@ var __spreadValues = (a, b) => {
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
20
 
21
21
  // index.ts
22
- import { bearing } from "@turf/bearing";
23
22
  import { distance } from "@turf/distance";
24
- import { destination } from "@turf/destination";
25
- import { lineIntersect as lineIntersects } from "@turf/line-intersect";
26
23
  import { flattenEach } from "@turf/meta";
27
- import { point, lineString } from "@turf/helpers";
28
- import { getCoords } from "@turf/invariant";
24
+ import {
25
+ point,
26
+ degreesToRadians,
27
+ radiansToDegrees
28
+ } from "@turf/helpers";
29
+ import { getCoord, getCoords } from "@turf/invariant";
29
30
  function nearestPointOnLine(lines, pt, options = {}) {
30
31
  if (!lines || !pt) {
31
32
  throw new Error("lines and pt are required arguments");
32
33
  }
34
+ const ptPos = getCoord(pt);
33
35
  let closestPt = point([Infinity, Infinity], {
34
36
  dist: Infinity,
35
37
  index: -1,
@@ -43,65 +45,35 @@ function nearestPointOnLine(lines, pt, options = {}) {
43
45
  const coords = getCoords(line);
44
46
  for (let i = 0; i < coords.length - 1; i++) {
45
47
  const start = point(coords[i]);
46
- start.properties.dist = distance(pt, start, options);
48
+ const startPos = getCoord(start);
47
49
  const stop = point(coords[i + 1]);
48
- stop.properties.dist = distance(pt, stop, options);
50
+ const stopPos = getCoord(stop);
49
51
  const sectionLength = distance(start, stop, options);
50
- const heightDistance = Math.max(
51
- start.properties.dist,
52
- stop.properties.dist
53
- );
54
- const direction = bearing(start, stop);
55
- const perpendicularPt1 = destination(
56
- pt,
57
- heightDistance,
58
- direction + 90,
59
- options
60
- );
61
- const perpendicularPt2 = destination(
62
- pt,
63
- heightDistance,
64
- direction - 90,
65
- options
66
- );
67
- const intersect = lineIntersects(
68
- lineString([
69
- perpendicularPt1.geometry.coordinates,
70
- perpendicularPt2.geometry.coordinates
71
- ]),
72
- lineString([start.geometry.coordinates, stop.geometry.coordinates])
73
- );
74
- let intersectPt;
75
- if (intersect.features.length > 0 && intersect.features[0]) {
76
- intersectPt = __spreadProps(__spreadValues({}, intersect.features[0]), {
77
- properties: {
78
- dist: distance(pt, intersect.features[0], options),
79
- multiFeatureIndex,
80
- location: length + distance(start, intersect.features[0], options)
81
- }
82
- });
83
- }
84
- if (start.properties.dist < closestPt.properties.dist) {
85
- closestPt = __spreadProps(__spreadValues({}, start), {
86
- properties: __spreadProps(__spreadValues({}, start.properties), {
87
- index: i,
88
- multiFeatureIndex,
89
- location: length
90
- })
91
- });
92
- }
93
- if (stop.properties.dist < closestPt.properties.dist) {
94
- closestPt = __spreadProps(__spreadValues({}, stop), {
95
- properties: __spreadProps(__spreadValues({}, stop.properties), {
96
- index: i + 1,
97
- multiFeatureIndex,
98
- location: length + sectionLength
99
- })
100
- });
52
+ let intersectPos;
53
+ let wasEnd;
54
+ if (stopPos[0] === ptPos[0] && stopPos[1] === ptPos[1]) {
55
+ [intersectPos, wasEnd] = [stopPos, true];
56
+ } else if (startPos[0] === ptPos[0] && startPos[1] === ptPos[1]) {
57
+ [intersectPos, wasEnd] = [startPos, false];
58
+ } else {
59
+ [intersectPos, wasEnd] = nearestPointOnSegment(
60
+ startPos,
61
+ stopPos,
62
+ ptPos
63
+ );
101
64
  }
102
- if (intersectPt && intersectPt.properties.dist < closestPt.properties.dist) {
65
+ const intersectPt = point(intersectPos, {
66
+ dist: distance(pt, intersectPos, options),
67
+ multiFeatureIndex,
68
+ location: length + distance(start, intersectPos, options)
69
+ });
70
+ if (intersectPt.properties.dist < closestPt.properties.dist) {
103
71
  closestPt = __spreadProps(__spreadValues({}, intersectPt), {
104
- properties: __spreadProps(__spreadValues({}, intersectPt.properties), { index: i })
72
+ properties: __spreadProps(__spreadValues({}, intersectPt.properties), {
73
+ // Legacy behaviour where index progresses to next segment # if we
74
+ // went with the end point this iteration.
75
+ index: wasEnd ? i + 1 : i
76
+ })
105
77
  });
106
78
  }
107
79
  length += sectionLength;
@@ -110,9 +82,74 @@ function nearestPointOnLine(lines, pt, options = {}) {
110
82
  );
111
83
  return closestPt;
112
84
  }
113
- var turf_nearest_point_on_line_default = nearestPointOnLine;
85
+ function dot(v1, v2) {
86
+ const [v1x, v1y, v1z] = v1;
87
+ const [v2x, v2y, v2z] = v2;
88
+ return v1x * v2x + v1y * v2y + v1z * v2z;
89
+ }
90
+ function cross(v1, v2) {
91
+ const [v1x, v1y, v1z] = v1;
92
+ const [v2x, v2y, v2z] = v2;
93
+ return [v1y * v2z - v1z * v2y, v1z * v2x - v1x * v2z, v1x * v2y - v1y * v2x];
94
+ }
95
+ function magnitude(v) {
96
+ return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2) + Math.pow(v[2], 2));
97
+ }
98
+ function normalize(v) {
99
+ const mag = magnitude(v);
100
+ return [v[0] / mag, v[1] / mag, v[2] / mag];
101
+ }
102
+ function lngLatToVector(a) {
103
+ const lat = degreesToRadians(a[1]);
104
+ const lng = degreesToRadians(a[0]);
105
+ return [
106
+ Math.cos(lat) * Math.cos(lng),
107
+ Math.cos(lat) * Math.sin(lng),
108
+ Math.sin(lat)
109
+ ];
110
+ }
111
+ function vectorToLngLat(v) {
112
+ const [x, y, z] = v;
113
+ const zClamp = Math.min(Math.max(z, -1), 1);
114
+ const lat = radiansToDegrees(Math.asin(zClamp));
115
+ const lng = radiansToDegrees(Math.atan2(y, x));
116
+ return [lng, lat];
117
+ }
118
+ function nearestPointOnSegment(posA, posB, posC) {
119
+ const A = lngLatToVector(posA);
120
+ const B = lngLatToVector(posB);
121
+ const C = lngLatToVector(posC);
122
+ const segmentAxis = cross(A, B);
123
+ if (segmentAxis[0] === 0 && segmentAxis[1] === 0 && segmentAxis[2] === 0) {
124
+ if (dot(A, B) > 0) {
125
+ return [[...posB], true];
126
+ } else {
127
+ return [[...posC], false];
128
+ }
129
+ }
130
+ const targetAxis = cross(segmentAxis, C);
131
+ if (targetAxis[0] === 0 && targetAxis[1] === 0 && targetAxis[2] === 0) {
132
+ return [[...posB], true];
133
+ }
134
+ const intersectionAxis = cross(targetAxis, segmentAxis);
135
+ const I1 = normalize(intersectionAxis);
136
+ const I2 = [-I1[0], -I1[1], -I1[2]];
137
+ const I = dot(C, I1) > dot(C, I2) ? I1 : I2;
138
+ const segmentAxisNorm = normalize(segmentAxis);
139
+ const cmpAI = dot(cross(A, I), segmentAxisNorm);
140
+ const cmpIB = dot(cross(I, B), segmentAxisNorm);
141
+ if (cmpAI >= 0 && cmpIB >= 0) {
142
+ return [vectorToLngLat(I), false];
143
+ }
144
+ if (dot(A, C) > dot(B, C)) {
145
+ return [[...posA], false];
146
+ } else {
147
+ return [[...posB], true];
148
+ }
149
+ }
150
+ var index_default = nearestPointOnLine;
114
151
  export {
115
- turf_nearest_point_on_line_default as default,
152
+ index_default as default,
116
153
  nearestPointOnLine
117
154
  };
118
155
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../index.ts"],"sourcesContent":["import { Feature, Point, LineString, MultiLineString } from \"geojson\";\nimport { bearing } from \"@turf/bearing\";\nimport { distance } from \"@turf/distance\";\nimport { destination } from \"@turf/destination\";\nimport { lineIntersect as lineIntersects } from \"@turf/line-intersect\";\nimport { flattenEach } from \"@turf/meta\";\nimport { point, lineString, Coord, Units } from \"@turf/helpers\";\nimport { getCoords } from \"@turf/invariant\";\n\n/**\n * Takes a {@link Point} and a {@link LineString} and calculates the closest Point on the (Multi)LineString.\n *\n * @name nearestPointOnLine\n * @param {Geometry|Feature<LineString|MultiLineString>} lines lines to snap to\n * @param {Geometry|Feature<Point>|number[]} pt point to snap from\n * @param {Object} [options={}] Optional parameters\n * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers\n * @returns {Feature<Point>} 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<G extends LineString | MultiLineString>(\n lines: Feature<G> | 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 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\n const start: Feature<Point, { dist: number }> = point(coords[i]);\n start.properties.dist = distance(pt, start, options);\n //stop\n const stop: Feature<Point, { dist: number }> = point(coords[i + 1]);\n stop.properties.dist = distance(pt, stop, options);\n // sectionLength\n const sectionLength = distance(start, stop, options);\n //perpendicular\n const heightDistance = Math.max(\n start.properties.dist,\n stop.properties.dist\n );\n const direction = bearing(start, stop);\n const perpendicularPt1 = destination(\n pt,\n heightDistance,\n direction + 90,\n options\n );\n const perpendicularPt2 = destination(\n pt,\n heightDistance,\n direction - 90,\n options\n );\n const intersect = lineIntersects(\n lineString([\n perpendicularPt1.geometry.coordinates,\n perpendicularPt2.geometry.coordinates,\n ]),\n lineString([start.geometry.coordinates, stop.geometry.coordinates])\n );\n let intersectPt:\n | Feature<\n Point,\n { dist: number; multiFeatureIndex: number; location: number }\n >\n | undefined;\n\n if (intersect.features.length > 0 && intersect.features[0]) {\n intersectPt = {\n ...intersect.features[0],\n properties: {\n dist: distance(pt, intersect.features[0], options),\n multiFeatureIndex: multiFeatureIndex,\n location:\n length + distance(start, intersect.features[0], options),\n },\n };\n }\n\n if (start.properties.dist < closestPt.properties.dist) {\n closestPt = {\n ...start,\n properties: {\n ...start.properties,\n index: i,\n multiFeatureIndex: multiFeatureIndex,\n location: length,\n },\n };\n }\n\n if (stop.properties.dist < closestPt.properties.dist) {\n closestPt = {\n ...stop,\n properties: {\n ...stop.properties,\n index: i + 1,\n multiFeatureIndex: multiFeatureIndex,\n location: length + sectionLength,\n },\n };\n }\n\n if (\n intersectPt &&\n intersectPt.properties.dist < closestPt.properties.dist\n ) {\n closestPt = {\n ...intersectPt,\n properties: { ...intersectPt.properties, index: i },\n };\n }\n // update length\n length += sectionLength;\n }\n }\n );\n\n return closestPt;\n}\n\nexport { nearestPointOnLine };\nexport default nearestPointOnLine;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,sBAAsB;AAChD,SAAS,mBAAmB;AAC5B,SAAS,OAAO,kBAAgC;AAChD,SAAS,iBAAiB;AA4B1B,SAAS,mBACP,OACA,IACA,UAA6B,CAAC,GAU9B;AACA,MAAI,CAAC,SAAS,CAAC,IAAI;AACjB,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAEA,MAAI,YAGA,MAAM,CAAC,UAAU,QAAQ,GAAG;AAAA,IAC9B,MAAM;AAAA,IACN,OAAO;AAAA,IACP,mBAAmB;AAAA,IACnB,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,SAAS;AACb;AAAA,IACE;AAAA,IACA,SAAU,MAAW,eAAuB,mBAA2B;AACrE,YAAM,SAAc,UAAU,IAAI;AAElC,eAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAE1C,cAAM,QAA0C,MAAM,OAAO,CAAC,CAAC;AAC/D,cAAM,WAAW,OAAO,SAAS,IAAI,OAAO,OAAO;AAEnD,cAAM,OAAyC,MAAM,OAAO,IAAI,CAAC,CAAC;AAClE,aAAK,WAAW,OAAO,SAAS,IAAI,MAAM,OAAO;AAEjD,cAAM,gBAAgB,SAAS,OAAO,MAAM,OAAO;AAEnD,cAAM,iBAAiB,KAAK;AAAA,UAC1B,MAAM,WAAW;AAAA,UACjB,KAAK,WAAW;AAAA,QAClB;AACA,cAAM,YAAY,QAAQ,OAAO,IAAI;AACrC,cAAM,mBAAmB;AAAA,UACvB;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QACF;AACA,cAAM,mBAAmB;AAAA,UACvB;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QACF;AACA,cAAM,YAAY;AAAA,UAChB,WAAW;AAAA,YACT,iBAAiB,SAAS;AAAA,YAC1B,iBAAiB,SAAS;AAAA,UAC5B,CAAC;AAAA,UACD,WAAW,CAAC,MAAM,SAAS,aAAa,KAAK,SAAS,WAAW,CAAC;AAAA,QACpE;AACA,YAAI;AAOJ,YAAI,UAAU,SAAS,SAAS,KAAK,UAAU,SAAS,CAAC,GAAG;AAC1D,wBAAc,iCACT,UAAU,SAAS,CAAC,IADX;AAAA,YAEZ,YAAY;AAAA,cACV,MAAM,SAAS,IAAI,UAAU,SAAS,CAAC,GAAG,OAAO;AAAA,cACjD;AAAA,cACA,UACE,SAAS,SAAS,OAAO,UAAU,SAAS,CAAC,GAAG,OAAO;AAAA,YAC3D;AAAA,UACF;AAAA,QACF;AAEA,YAAI,MAAM,WAAW,OAAO,UAAU,WAAW,MAAM;AACrD,sBAAY,iCACP,QADO;AAAA,YAEV,YAAY,iCACP,MAAM,aADC;AAAA,cAEV,OAAO;AAAA,cACP;AAAA,cACA,UAAU;AAAA,YACZ;AAAA,UACF;AAAA,QACF;AAEA,YAAI,KAAK,WAAW,OAAO,UAAU,WAAW,MAAM;AACpD,sBAAY,iCACP,OADO;AAAA,YAEV,YAAY,iCACP,KAAK,aADE;AAAA,cAEV,OAAO,IAAI;AAAA,cACX;AAAA,cACA,UAAU,SAAS;AAAA,YACrB;AAAA,UACF;AAAA,QACF;AAEA,YACE,eACA,YAAY,WAAW,OAAO,UAAU,WAAW,MACnD;AACA,sBAAY,iCACP,cADO;AAAA,YAEV,YAAY,iCAAK,YAAY,aAAjB,EAA6B,OAAO,EAAE;AAAA,UACpD;AAAA,QACF;AAEA,kBAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,IAAO,qCAAQ;","names":[]}
1
+ {"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<LineString|MultiLineString>} lines lines to snap to\n * @param {Geometry|Feature<Point>|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<Point>} 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<G extends LineString | MultiLineString>(\n lines: Feature<G> | 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, { dist: number }> = point(coords[i]);\n const startPos = getCoord(start);\n\n //stop - end of current line section\n const stop: Feature<Point, { dist: number }> = 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":[]}
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@turf/nearest-point-on-line",
3
- "version": "7.1.0",
4
- "description": "turf nearest-point-on-line module",
3
+ "version": "7.3.0",
4
+ "description": "Finds the nearest point on a line to a given point",
5
5
  "author": "Turf Authors",
6
+ "contributors": [
7
+ "Angel Lacret <@alacret>",
8
+ "Jon Miles <@jonmiles>",
9
+ "John Ziebro <@Insighttful>"
10
+ ],
6
11
  "license": "MIT",
7
12
  "bugs": {
8
13
  "url": "https://github.com/Turfjs/turf/issues"
@@ -46,30 +51,27 @@
46
51
  "test:types": "tsc --esModuleInterop --module node16 --moduleResolution node16 --noEmit --strict types.ts"
47
52
  },
48
53
  "devDependencies": {
49
- "@turf/along": "^7.1.0",
50
- "@turf/length": "^7.1.0",
51
- "@turf/truncate": "^7.1.0",
54
+ "@turf/along": "7.3.0",
55
+ "@turf/length": "7.3.0",
56
+ "@turf/truncate": "7.3.0",
52
57
  "@types/benchmark": "^2.1.5",
53
- "@types/tape": "^4.2.32",
58
+ "@types/tape": "^5.8.1",
54
59
  "benchmark": "^2.1.4",
55
60
  "load-json-file": "^7.0.1",
56
61
  "npm-run-all": "^4.1.5",
57
- "tape": "^5.7.2",
58
- "tsup": "^8.0.1",
59
- "tsx": "^4.6.2",
60
- "typescript": "^5.2.2",
61
- "write-json-file": "^5.0.0"
62
+ "tape": "^5.9.0",
63
+ "tsup": "^8.4.0",
64
+ "tsx": "^4.19.4",
65
+ "typescript": "^5.8.3",
66
+ "write-json-file": "^6.0.0"
62
67
  },
63
68
  "dependencies": {
64
- "@turf/bearing": "^7.1.0",
65
- "@turf/destination": "^7.1.0",
66
- "@turf/distance": "^7.1.0",
67
- "@turf/helpers": "^7.1.0",
68
- "@turf/invariant": "^7.1.0",
69
- "@turf/line-intersect": "^7.1.0",
70
- "@turf/meta": "^7.1.0",
69
+ "@turf/distance": "7.3.0",
70
+ "@turf/helpers": "7.3.0",
71
+ "@turf/invariant": "7.3.0",
72
+ "@turf/meta": "7.3.0",
71
73
  "@types/geojson": "^7946.0.10",
72
- "tslib": "^2.6.2"
74
+ "tslib": "^2.8.1"
73
75
  },
74
- "gitHead": "68915eeebc9278bb40dec3f1034499698a0561ef"
76
+ "gitHead": "9f58a103e8f9a587ab640307ed03ba5233913ddd"
75
77
  }