247 lines
6.4 KiB
JavaScript
Executable File
247 lines
6.4 KiB
JavaScript
Executable File
'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;
|