| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 | import rbush from 'geojson-rbush';import square from '@turf/square';import bbox from '@turf/bbox';import truncate from '@turf/truncate';import lineSegment from '@turf/line-segment';import lineIntersect from '@turf/line-intersect';import nearestPointOnLine from '@turf/nearest-point-on-line';import { getType, getCoords, getCoord } from '@turf/invariant';import { flattenEach, featureEach, featureReduce } from '@turf/meta';import { featureCollection, lineString } from '@turf/helpers';/** * Split a LineString by another GeoJSON Feature. * * @name lineSplit * @param {Feature<LineString>} line LineString Feature to split * @param {Feature<any>} splitter Feature used to split line * @returns {FeatureCollection<LineString>} Split LineStrings * @example * var line = turf.lineString([[120, -25], [145, -25]]); * var splitter = turf.lineString([[130, -15], [130, -35]]); * * var split = turf.lineSplit(line, splitter); * * //addToMap * var addToMap = [line, splitter] */function lineSplit(line, splitter) {  if (!line) throw new Error("line is required");  if (!splitter) throw new Error("splitter is required");  var lineType = getType(line);  var splitterType = getType(splitter);  if (lineType !== "LineString") throw new Error("line must be LineString");  if (splitterType === "FeatureCollection")    throw new Error("splitter cannot be a FeatureCollection");  if (splitterType === "GeometryCollection")    throw new Error("splitter cannot be a GeometryCollection");  // remove excessive decimals from splitter  // to avoid possible approximation issues in rbush  var truncatedSplitter = truncate(splitter, { precision: 7 });  switch (splitterType) {    case "Point":      return splitLineWithPoint(line, truncatedSplitter);    case "MultiPoint":      return splitLineWithPoints(line, truncatedSplitter);    case "LineString":    case "MultiLineString":    case "Polygon":    case "MultiPolygon":      return splitLineWithPoints(line, lineIntersect(line, truncatedSplitter));  }}/** * Split LineString with MultiPoint * * @private * @param {Feature<LineString>} line LineString * @param {FeatureCollection<Point>} splitter Point * @returns {FeatureCollection<LineString>} split LineStrings */function splitLineWithPoints(line, splitter) {  var results = [];  var tree = rbush();  flattenEach(splitter, function (point) {    // Add index/id to features (needed for filter)    results.forEach(function (feature, index) {      feature.id = index;    });    // First Point - doesn't need to handle any previous line results    if (!results.length) {      results = splitLineWithPoint(line, point).features;      // Add Square BBox to each feature for GeoJSON-RBush      results.forEach(function (feature) {        if (!feature.bbox) feature.bbox = square(bbox(feature));      });      tree.load(featureCollection(results));      // Split with remaining points - lines might needed to be split multiple times    } else {      // Find all lines that are within the splitter's bbox      var search = tree.search(point);      if (search.features.length) {        // RBush might return multiple lines - only process the closest line to splitter        var closestLine = findClosestFeature(point, search);        // Remove closest line from results since this will be split into two lines        // This removes any duplicates inside the results & index        results = results.filter(function (feature) {          return feature.id !== closestLine.id;        });        tree.remove(closestLine);        // Append the two newly split lines into the results        featureEach(splitLineWithPoint(closestLine, point), function (line) {          results.push(line);          tree.insert(line);        });      }    }  });  return featureCollection(results);}/** * Split LineString with Point * * @private * @param {Feature<LineString>} line LineString * @param {Feature<Point>} splitter Point * @returns {FeatureCollection<LineString>} split LineStrings */function splitLineWithPoint(line, splitter) {  var results = [];  // handle endpoints  var startPoint = getCoords(line)[0];  var endPoint = getCoords(line)[line.geometry.coordinates.length - 1];  if (    pointsEquals(startPoint, getCoord(splitter)) ||    pointsEquals(endPoint, getCoord(splitter))  )    return featureCollection([line]);  // Create spatial index  var tree = rbush();  var segments = lineSegment(line);  tree.load(segments);  // Find all segments that are within bbox of splitter  var search = tree.search(splitter);  // Return itself if point is not within spatial index  if (!search.features.length) return featureCollection([line]);  // RBush might return multiple lines - only process the closest line to splitter  var closestSegment = findClosestFeature(splitter, search);  // Initial value is the first point of the first segments (beginning of line)  var initialValue = [startPoint];  var lastCoords = featureReduce(    segments,    function (previous, current, index) {      var currentCoords = getCoords(current)[1];      var splitterCoords = getCoord(splitter);      // Location where segment intersects with line      if (index === closestSegment.id) {        previous.push(splitterCoords);        results.push(lineString(previous));        // Don't duplicate splitter coordinate (Issue #688)        if (pointsEquals(splitterCoords, currentCoords))          return [splitterCoords];        return [splitterCoords, currentCoords];        // Keep iterating over coords until finished or intersection is found      } else {        previous.push(currentCoords);        return previous;      }    },    initialValue  );  // Append last line to final split results  if (lastCoords.length > 1) {    results.push(lineString(lastCoords));  }  return featureCollection(results);}/** * Find Closest Feature * * @private * @param {Feature<Point>} point Feature must be closest to this point * @param {FeatureCollection<LineString>} lines Collection of Features * @returns {Feature<LineString>} closest LineString */function findClosestFeature(point, lines) {  if (!lines.features.length) throw new Error("lines must contain features");  // Filter to one segment that is the closest to the line  if (lines.features.length === 1) return lines.features[0];  var closestFeature;  var closestDistance = Infinity;  featureEach(lines, function (segment) {    var pt = nearestPointOnLine(segment, point);    var dist = pt.properties.dist;    if (dist < closestDistance) {      closestFeature = segment;      closestDistance = dist;    }  });  return closestFeature;}/** * Compares two points and returns if they are equals * * @private * @param {Array<number>} pt1 point * @param {Array<number>} pt2 point * @returns {boolean} true if they are equals */function pointsEquals(pt1, pt2) {  return pt1[0] === pt2[0] && pt1[1] === pt2[1];}export default lineSplit;
 |