123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- 'use strict';
- var meta = require('@turf/meta');
- var invariant = require('@turf/invariant');
- var helpers = require('@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 (!helpers.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 = invariant.getType(geojson);
- var properties = geojson.properties;
- switch (type) {
- case "LineString":
- return lineOffsetFeature(geojson, distance, units);
- case "MultiLineString":
- var coords = [];
- meta.flattenEach(geojson, function (feature) {
- coords.push(
- lineOffsetFeature(feature, distance, units).geometry.coordinates
- );
- });
- return helpers.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 = helpers.lengthToDegrees(distance, units);
- var coords = invariant.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 helpers.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],
- ];
- }
- module.exports = lineOffset;
- module.exports.default = lineOffset;
|