| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 | import { flattenEach } from '@turf/meta';import { getType, getCoords } from '@turf/invariant';import { isObject, lineString, multiLineString, lengthToDegrees } from '@turf/helpers';/** * https://github.com/rook2pawn/node-intersection * * Author @rook2pawn *//** * AB * * @private * @param {Array<Array<number>>} segment - 2 vertex line segment * @returns {Array<number>} coordinates [x, y] */function ab(segment) {  var start = segment[0];  var end = segment[1];  return [end[0] - start[0], end[1] - start[1]];}/** * Cross Product * * @private * @param {Array<number>} v1 coordinates [x, y] * @param {Array<number>} v2 coordinates [x, y] * @returns {Array<number>} Cross Product */function crossProduct(v1, v2) {  return v1[0] * v2[1] - v2[0] * v1[1];}/** * Add * * @private * @param {Array<number>} v1 coordinates [x, y] * @param {Array<number>} v2 coordinates [x, y] * @returns {Array<number>} Add */function add(v1, v2) {  return [v1[0] + v2[0], v1[1] + v2[1]];}/** * Sub * * @private * @param {Array<number>} v1 coordinates [x, y] * @param {Array<number>} v2 coordinates [x, y] * @returns {Array<number>} Sub */function sub(v1, v2) {  return [v1[0] - v2[0], v1[1] - v2[1]];}/** * scalarMult * * @private * @param {number} s scalar * @param {Array<number>} v coordinates [x, y] * @returns {Array<number>} scalarMult */function scalarMult(s, v) {  return [s * v[0], s * v[1]];}/** * Intersect Segments * * @private * @param {Array<number>} a coordinates [x, y] * @param {Array<number>} b coordinates [x, y] * @returns {Array<number>} intersection */function intersectSegments(a, b) {  var p = a[0];  var r = ab(a);  var q = b[0];  var s = ab(b);  var cross = crossProduct(r, s);  var qmp = sub(q, p);  var numerator = crossProduct(qmp, s);  var t = numerator / cross;  var intersection = add(p, scalarMult(t, r));  return intersection;}/** * Is Parallel * * @private * @param {Array<number>} a coordinates [x, y] * @param {Array<number>} b coordinates [x, y] * @returns {boolean} true if a and b are parallel (or co-linear) */function isParallel(a, b) {  var r = ab(a);  var s = ab(b);  return crossProduct(r, s) === 0;}/** * Intersection * * @private * @param {Array<number>} a coordinates [x, y] * @param {Array<number>} b coordinates [x, y] * @returns {Array<number>|boolean} true if a and b are parallel (or co-linear) */function intersection(a, b) {  if (isParallel(a, b)) return false;  return intersectSegments(a, b);}/** * Takes a {@link LineString|line} and returns a {@link LineString|line} at offset by the specified distance. * * @name lineOffset * @param {Geometry|Feature<LineString|MultiLineString>} geojson input GeoJSON * @param {number} distance distance to offset the line (can be of negative value) * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] can be degrees, radians, miles, kilometers, inches, yards, meters * @returns {Feature<LineString|MultiLineString>} Line offset from the input line * @example * var line = turf.lineString([[-83, 30], [-84, 36], [-78, 41]], { "stroke": "#F00" }); * * var offsetLine = turf.lineOffset(line, 2, {units: 'miles'}); * * //addToMap * var addToMap = [offsetLine, line] * offsetLine.properties.stroke = "#00F" */function lineOffset(geojson, distance, options) {  // Optional parameters  options = options || {};  if (!isObject(options)) throw new Error("options is invalid");  var units = options.units;  // Valdiation  if (!geojson) throw new Error("geojson is required");  if (distance === undefined || distance === null || isNaN(distance))    throw new Error("distance is required");  var type = getType(geojson);  var properties = geojson.properties;  switch (type) {    case "LineString":      return lineOffsetFeature(geojson, distance, units);    case "MultiLineString":      var coords = [];      flattenEach(geojson, function (feature) {        coords.push(          lineOffsetFeature(feature, distance, units).geometry.coordinates        );      });      return multiLineString(coords, properties);    default:      throw new Error("geometry " + type + " is not supported");  }}/** * Line Offset * * @private * @param {Geometry|Feature<LineString>} line input line * @param {number} distance distance to offset the line (can be of negative value) * @param {string} [units=kilometers] units * @returns {Feature<LineString>} Line offset from the input line */function lineOffsetFeature(line, distance, units) {  var segments = [];  var offsetDegrees = lengthToDegrees(distance, units);  var coords = getCoords(line);  var finalCoords = [];  coords.forEach(function (currentCoords, index) {    if (index !== coords.length - 1) {      var segment = processSegment(        currentCoords,        coords[index + 1],        offsetDegrees      );      segments.push(segment);      if (index > 0) {        var seg2Coords = segments[index - 1];        var intersects = intersection(segment, seg2Coords);        // Handling for line segments that aren't straight        if (intersects !== false) {          seg2Coords[1] = intersects;          segment[0] = intersects;        }        finalCoords.push(seg2Coords[0]);        if (index === coords.length - 2) {          finalCoords.push(segment[0]);          finalCoords.push(segment[1]);        }      }      // Handling for lines that only have 1 segment      if (coords.length === 2) {        finalCoords.push(segment[0]);        finalCoords.push(segment[1]);      }    }  });  return lineString(finalCoords, line.properties);}/** * Process Segment * Inspiration taken from http://stackoverflow.com/questions/2825412/draw-a-parallel-line * * @private * @param {Array<number>} point1 Point coordinates * @param {Array<number>} point2 Point coordinates * @param {number} offset Offset * @returns {Array<Array<number>>} offset points */function processSegment(point1, point2, offset) {  var L = Math.sqrt(    (point1[0] - point2[0]) * (point1[0] - point2[0]) +      (point1[1] - point2[1]) * (point1[1] - point2[1])  );  var out1x = point1[0] + (offset * (point2[1] - point1[1])) / L;  var out2x = point2[0] + (offset * (point2[1] - point1[1])) / L;  var out1y = point1[1] + (offset * (point1[0] - point2[0])) / L;  var out2y = point2[1] + (offset * (point1[0] - point2[0])) / L;  return [    [out1x, out1y],    [out2x, out2y],  ];}export default lineOffset;
 |