index.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. 'use strict';
  2. var meta = require('@turf/meta');
  3. var invariant = require('@turf/invariant');
  4. var helpers = require('@turf/helpers');
  5. /**
  6. * https://github.com/rook2pawn/node-intersection
  7. *
  8. * Author @rook2pawn
  9. */
  10. /**
  11. * AB
  12. *
  13. * @private
  14. * @param {Array<Array<number>>} segment - 2 vertex line segment
  15. * @returns {Array<number>} coordinates [x, y]
  16. */
  17. function ab(segment) {
  18. var start = segment[0];
  19. var end = segment[1];
  20. return [end[0] - start[0], end[1] - start[1]];
  21. }
  22. /**
  23. * Cross Product
  24. *
  25. * @private
  26. * @param {Array<number>} v1 coordinates [x, y]
  27. * @param {Array<number>} v2 coordinates [x, y]
  28. * @returns {Array<number>} Cross Product
  29. */
  30. function crossProduct(v1, v2) {
  31. return v1[0] * v2[1] - v2[0] * v1[1];
  32. }
  33. /**
  34. * Add
  35. *
  36. * @private
  37. * @param {Array<number>} v1 coordinates [x, y]
  38. * @param {Array<number>} v2 coordinates [x, y]
  39. * @returns {Array<number>} Add
  40. */
  41. function add(v1, v2) {
  42. return [v1[0] + v2[0], v1[1] + v2[1]];
  43. }
  44. /**
  45. * Sub
  46. *
  47. * @private
  48. * @param {Array<number>} v1 coordinates [x, y]
  49. * @param {Array<number>} v2 coordinates [x, y]
  50. * @returns {Array<number>} Sub
  51. */
  52. function sub(v1, v2) {
  53. return [v1[0] - v2[0], v1[1] - v2[1]];
  54. }
  55. /**
  56. * scalarMult
  57. *
  58. * @private
  59. * @param {number} s scalar
  60. * @param {Array<number>} v coordinates [x, y]
  61. * @returns {Array<number>} scalarMult
  62. */
  63. function scalarMult(s, v) {
  64. return [s * v[0], s * v[1]];
  65. }
  66. /**
  67. * Intersect Segments
  68. *
  69. * @private
  70. * @param {Array<number>} a coordinates [x, y]
  71. * @param {Array<number>} b coordinates [x, y]
  72. * @returns {Array<number>} intersection
  73. */
  74. function intersectSegments(a, b) {
  75. var p = a[0];
  76. var r = ab(a);
  77. var q = b[0];
  78. var s = ab(b);
  79. var cross = crossProduct(r, s);
  80. var qmp = sub(q, p);
  81. var numerator = crossProduct(qmp, s);
  82. var t = numerator / cross;
  83. var intersection = add(p, scalarMult(t, r));
  84. return intersection;
  85. }
  86. /**
  87. * Is Parallel
  88. *
  89. * @private
  90. * @param {Array<number>} a coordinates [x, y]
  91. * @param {Array<number>} b coordinates [x, y]
  92. * @returns {boolean} true if a and b are parallel (or co-linear)
  93. */
  94. function isParallel(a, b) {
  95. var r = ab(a);
  96. var s = ab(b);
  97. return crossProduct(r, s) === 0;
  98. }
  99. /**
  100. * Intersection
  101. *
  102. * @private
  103. * @param {Array<number>} a coordinates [x, y]
  104. * @param {Array<number>} b coordinates [x, y]
  105. * @returns {Array<number>|boolean} true if a and b are parallel (or co-linear)
  106. */
  107. function intersection(a, b) {
  108. if (isParallel(a, b)) return false;
  109. return intersectSegments(a, b);
  110. }
  111. /**
  112. * Takes a {@link LineString|line} and returns a {@link LineString|line} at offset by the specified distance.
  113. *
  114. * @name lineOffset
  115. * @param {Geometry|Feature<LineString|MultiLineString>} geojson input GeoJSON
  116. * @param {number} distance distance to offset the line (can be of negative value)
  117. * @param {Object} [options={}] Optional parameters
  118. * @param {string} [options.units='kilometers'] can be degrees, radians, miles, kilometers, inches, yards, meters
  119. * @returns {Feature<LineString|MultiLineString>} Line offset from the input line
  120. * @example
  121. * var line = turf.lineString([[-83, 30], [-84, 36], [-78, 41]], { "stroke": "#F00" });
  122. *
  123. * var offsetLine = turf.lineOffset(line, 2, {units: 'miles'});
  124. *
  125. * //addToMap
  126. * var addToMap = [offsetLine, line]
  127. * offsetLine.properties.stroke = "#00F"
  128. */
  129. function lineOffset(geojson, distance, options) {
  130. // Optional parameters
  131. options = options || {};
  132. if (!helpers.isObject(options)) throw new Error("options is invalid");
  133. var units = options.units;
  134. // Valdiation
  135. if (!geojson) throw new Error("geojson is required");
  136. if (distance === undefined || distance === null || isNaN(distance))
  137. throw new Error("distance is required");
  138. var type = invariant.getType(geojson);
  139. var properties = geojson.properties;
  140. switch (type) {
  141. case "LineString":
  142. return lineOffsetFeature(geojson, distance, units);
  143. case "MultiLineString":
  144. var coords = [];
  145. meta.flattenEach(geojson, function (feature) {
  146. coords.push(
  147. lineOffsetFeature(feature, distance, units).geometry.coordinates
  148. );
  149. });
  150. return helpers.multiLineString(coords, properties);
  151. default:
  152. throw new Error("geometry " + type + " is not supported");
  153. }
  154. }
  155. /**
  156. * Line Offset
  157. *
  158. * @private
  159. * @param {Geometry|Feature<LineString>} line input line
  160. * @param {number} distance distance to offset the line (can be of negative value)
  161. * @param {string} [units=kilometers] units
  162. * @returns {Feature<LineString>} Line offset from the input line
  163. */
  164. function lineOffsetFeature(line, distance, units) {
  165. var segments = [];
  166. var offsetDegrees = helpers.lengthToDegrees(distance, units);
  167. var coords = invariant.getCoords(line);
  168. var finalCoords = [];
  169. coords.forEach(function (currentCoords, index) {
  170. if (index !== coords.length - 1) {
  171. var segment = processSegment(
  172. currentCoords,
  173. coords[index + 1],
  174. offsetDegrees
  175. );
  176. segments.push(segment);
  177. if (index > 0) {
  178. var seg2Coords = segments[index - 1];
  179. var intersects = intersection(segment, seg2Coords);
  180. // Handling for line segments that aren't straight
  181. if (intersects !== false) {
  182. seg2Coords[1] = intersects;
  183. segment[0] = intersects;
  184. }
  185. finalCoords.push(seg2Coords[0]);
  186. if (index === coords.length - 2) {
  187. finalCoords.push(segment[0]);
  188. finalCoords.push(segment[1]);
  189. }
  190. }
  191. // Handling for lines that only have 1 segment
  192. if (coords.length === 2) {
  193. finalCoords.push(segment[0]);
  194. finalCoords.push(segment[1]);
  195. }
  196. }
  197. });
  198. return helpers.lineString(finalCoords, line.properties);
  199. }
  200. /**
  201. * Process Segment
  202. * Inspiration taken from http://stackoverflow.com/questions/2825412/draw-a-parallel-line
  203. *
  204. * @private
  205. * @param {Array<number>} point1 Point coordinates
  206. * @param {Array<number>} point2 Point coordinates
  207. * @param {number} offset Offset
  208. * @returns {Array<Array<number>>} offset points
  209. */
  210. function processSegment(point1, point2, offset) {
  211. var L = Math.sqrt(
  212. (point1[0] - point2[0]) * (point1[0] - point2[0]) +
  213. (point1[1] - point2[1]) * (point1[1] - point2[1])
  214. );
  215. var out1x = point1[0] + (offset * (point2[1] - point1[1])) / L;
  216. var out2x = point2[0] + (offset * (point2[1] - point1[1])) / L;
  217. var out1y = point1[1] + (offset * (point1[0] - point2[0])) / L;
  218. var out2y = point2[1] + (offset * (point1[0] - point2[0])) / L;
  219. return [
  220. [out1x, out1y],
  221. [out2x, out2y],
  222. ];
  223. }
  224. module.exports = lineOffset;
  225. module.exports.default = lineOffset;