///
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.GeoTools = {})));
}(this, (function (exports) { 'use strict';
/**
* Earth Radius used with the Harvesine formula and approximates using a spherical (non-ellipsoid) Earth.
*/
var earthRadius = 6371008.8;
/**
* Unit of measurement factors using a spherical (non-ellipsoid) earth radius.
*/
var factors = {
meters: earthRadius,
metres: earthRadius,
millimeters: earthRadius * 1000,
millimetres: earthRadius * 1000,
centimeters: earthRadius * 100,
centimetres: earthRadius * 100,
kilometers: earthRadius / 1000,
kilometres: earthRadius / 1000,
miles: earthRadius / 1609.344,
nauticalmiles: earthRadius / 1852,
inches: earthRadius * 39.370,
yards: earthRadius / 1.0936,
feet: earthRadius * 3.28084,
radians: 1,
degrees: earthRadius / 111325,
};
/**
* Units of measurement factors based on 1 meter.
*/
var unitsFactors = {
meters: 1,
metres: 1,
millimeters: 1000,
millimetres: 1000,
centimeters: 100,
centimetres: 100,
kilometers: 1 / 1000,
kilometres: 1 / 1000,
miles: 1 / 1609.344,
nauticalmiles: 1 / 1852,
inches: 39.370,
yards: 1 / 1.0936,
feet: 3.28084,
radians: 1 / earthRadius,
degrees: 1 / 111325,
};
/**
* Area of measurement factors based on 1 square meter.
*/
var areaFactors = {
meters: 1,
metres: 1,
millimeters: 1000000,
millimetres: 1000000,
centimeters: 10000,
centimetres: 10000,
kilometers: 0.000001,
kilometres: 0.000001,
acres: 0.000247105,
miles: 3.86e-7,
yards: 1.195990046,
feet: 10.763910417,
inches: 1550.003100006
};
/**
* Wraps a GeoJSON {@link Geometry} in a GeoJSON {@link Feature}.
*
* @name feature
* @param {Geometry} geometry input geometry
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} a GeoJSON Feature
* @example
* var geometry = {
* "type": "Point",
* "coordinates": [110, 50]
* };
*
* var feature = turf.feature(geometry);
*
* //=feature
*/
function feature(geometry, properties, options) {
// Optional Parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var bbox = options.bbox;
var id = options.id;
// Validation
if (geometry === undefined) throw new Error('geometry is required');
if (properties && properties.constructor !== Object) throw new Error('properties must be an Object');
if (bbox) validateBBox(bbox);
if (id) validateId(id);
// Main
var feat = {type: 'Feature'};
if (id) feat.id = id;
if (bbox) feat.bbox = bbox;
feat.properties = properties || {};
feat.geometry = geometry;
return feat;
}
/**
* Creates a GeoJSON {@link Geometry} from a Geometry string type & coordinates.
* For GeometryCollection type use `helpers.geometryCollection`
*
* @name geometry
* @param {string} type Geometry Type
* @param {Array} coordinates Coordinates
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Geometry
* @returns {Geometry} a GeoJSON Geometry
* @example
* var type = 'Point';
* var coordinates = [110, 50];
*
* var geometry = turf.geometry(type, coordinates);
*
* //=geometry
*/
function geometry(type, coordinates, options) {
// Optional Parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var bbox = options.bbox;
// Validation
if (!type) throw new Error('type is required');
if (!coordinates) throw new Error('coordinates is required');
if (!Array.isArray(coordinates)) throw new Error('coordinates must be an Array');
if (bbox) validateBBox(bbox);
// Main
var geom;
switch (type) {
case 'Point': geom = point(coordinates).geometry; break;
case 'LineString': geom = lineString(coordinates).geometry; break;
case 'Polygon': geom = polygon(coordinates).geometry; break;
case 'MultiPoint': geom = multiPoint(coordinates).geometry; break;
case 'MultiLineString': geom = multiLineString(coordinates).geometry; break;
case 'MultiPolygon': geom = multiPolygon(coordinates).geometry; break;
default: throw new Error(type + ' is invalid');
}
if (bbox) geom.bbox = bbox;
return geom;
}
/**
* Creates a {@link Point} {@link Feature} from a Position.
*
* @name point
* @param {Array} coordinates longitude, latitude position (each in decimal degrees)
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} a Point feature
* @example
* var point = turf.point([-75.343, 39.984]);
*
* //=point
*/
function point(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
if (!Array.isArray(coordinates)) throw new Error('coordinates must be an Array');
if (coordinates.length < 2) throw new Error('coordinates must be at least 2 numbers long');
if (!isNumber(coordinates[0]) || !isNumber(coordinates[1])) throw new Error('coordinates must contain numbers');
return feature({
type: 'Point',
coordinates: coordinates
}, properties, options);
}
/**
* Creates a {@link Point} {@link FeatureCollection} from an Array of Point coordinates.
*
* @name points
* @param {Array>} coordinates an array of Points
* @param {Object} [properties={}] Translate these properties to each Feature
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the FeatureCollection
* @param {string|number} [options.id] Identifier associated with the FeatureCollection
* @returns {FeatureCollection} Point Feature
* @example
* var points = turf.points([
* [-75, 39],
* [-80, 45],
* [-78, 50]
* ]);
*
* //=points
*/
function points(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
if (!Array.isArray(coordinates)) throw new Error('coordinates must be an Array');
return featureCollection(coordinates.map(function (coords) {
return point(coords, properties);
}), options);
}
/**
* Creates a {@link Polygon} {@link Feature} from an Array of LinearRings.
*
* @name polygon
* @param {Array>>} coordinates an array of LinearRings
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} Polygon Feature
* @example
* var polygon = turf.polygon([[[-5, 52], [-4, 56], [-2, 51], [-7, 54], [-5, 52]]], { name: 'poly1' });
*
* //=polygon
*/
function polygon(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
for (var i = 0; i < coordinates.length; i++) {
var ring = coordinates[i];
if (ring.length < 4) {
throw new Error('Each LinearRing of a Polygon must have 4 or more Positions.');
}
for (var j = 0; j < ring[ring.length - 1].length; j++) {
// Check if first point of Polygon contains two numbers
if (i === 0 && j === 0 && !isNumber(ring[0][0]) || !isNumber(ring[0][1])) throw new Error('coordinates must contain numbers');
if (ring[ring.length - 1][j] !== ring[0][j]) {
throw new Error('First and last Position are not equivalent.');
}
}
}
return feature({
type: 'Polygon',
coordinates: coordinates
}, properties, options);
}
/**
* Creates a {@link Polygon} {@link FeatureCollection} from an Array of Polygon coordinates.
*
* @name polygons
* @param {Array>>>} coordinates an array of Polygon coordinates
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the FeatureCollection
* @returns {FeatureCollection} Polygon FeatureCollection
* @example
* var polygons = turf.polygons([
* [[[-5, 52], [-4, 56], [-2, 51], [-7, 54], [-5, 52]]],
* [[[-15, 42], [-14, 46], [-12, 41], [-17, 44], [-15, 42]]],
* ]);
*
* //=polygons
*/
function polygons(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
if (!Array.isArray(coordinates)) throw new Error('coordinates must be an Array');
return featureCollection(coordinates.map(function (coords) {
return polygon(coords, properties);
}), options);
}
/**
* Creates a {@link LineString} {@link Feature} from an Array of Positions.
*
* @name lineString
* @param {Array>} coordinates an array of Positions
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} LineString Feature
* @example
* var linestring1 = turf.lineString([[-24, 63], [-23, 60], [-25, 65], [-20, 69]], {name: 'line 1'});
* var linestring2 = turf.lineString([[-14, 43], [-13, 40], [-15, 45], [-10, 49]], {name: 'line 2'});
*
* //=linestring1
* //=linestring2
*/
function lineString(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
if (coordinates.length < 2) throw new Error('coordinates must be an array of two or more positions');
// Check if first point of LineString contains two numbers
if (!isNumber(coordinates[0][1]) || !isNumber(coordinates[0][1])) throw new Error('coordinates must contain numbers');
return feature({
type: 'LineString',
coordinates: coordinates
}, properties, options);
}
/**
* Creates a {@link LineString} {@link FeatureCollection} from an Array of LineString coordinates.
*
* @name lineStrings
* @param {Array>} coordinates an array of LinearRings
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the FeatureCollection
* @param {string|number} [options.id] Identifier associated with the FeatureCollection
* @returns {FeatureCollection} LineString FeatureCollection
* @example
* var linestrings = turf.lineStrings([
* [[-24, 63], [-23, 60], [-25, 65], [-20, 69]],
* [[-14, 43], [-13, 40], [-15, 45], [-10, 49]]
* ]);
*
* //=linestrings
*/
function lineStrings(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
if (!Array.isArray(coordinates)) throw new Error('coordinates must be an Array');
return featureCollection(coordinates.map(function (coords) {
return lineString(coords, properties);
}), options);
}
/**
* Takes one or more {@link Feature|Features} and creates a {@link FeatureCollection}.
*
* @name featureCollection
* @param {Feature[]} features input features
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {FeatureCollection} FeatureCollection of Features
* @example
* var locationA = turf.point([-75.343, 39.984], {name: 'Location A'});
* var locationB = turf.point([-75.833, 39.284], {name: 'Location B'});
* var locationC = turf.point([-75.534, 39.123], {name: 'Location C'});
*
* var collection = turf.featureCollection([
* locationA,
* locationB,
* locationC
* ]);
*
* //=collection
*/
function featureCollection(features, options) {
// Optional Parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var bbox = options.bbox;
var id = options.id;
// Validation
if (!features) throw new Error('No features passed');
if (!Array.isArray(features)) throw new Error('features must be an Array');
if (bbox) validateBBox(bbox);
if (id) validateId(id);
// Main
var fc = {type: 'FeatureCollection'};
if (id) fc.id = id;
if (bbox) fc.bbox = bbox;
fc.features = features;
return fc;
}
/**
* Creates a {@link Feature} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiLineString
* @param {Array>>} coordinates an array of LineStrings
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} a MultiLineString feature
* @throws {Error} if no coordinates are passed
* @example
* var multiLine = turf.multiLineString([[[0,0],[10,10]]]);
*
* //=multiLine
*/
function multiLineString(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
return feature({
type: 'MultiLineString',
coordinates: coordinates
}, properties, options);
}
/**
* Creates a {@link Feature} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiPoint
* @param {Array>} coordinates an array of Positions
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} a MultiPoint feature
* @throws {Error} if no coordinates are passed
* @example
* var multiPt = turf.multiPoint([[0,0],[10,10]]);
*
* //=multiPt
*/
function multiPoint(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
return feature({
type: 'MultiPoint',
coordinates: coordinates
}, properties, options);
}
/**
* Creates a {@link Feature} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiPolygon
* @param {Array>>>} coordinates an array of Polygons
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} a multipolygon feature
* @throws {Error} if no coordinates are passed
* @example
* var multiPoly = turf.multiPolygon([[[[0,0],[0,10],[10,10],[10,0],[0,0]]]]);
*
* //=multiPoly
*
*/
function multiPolygon(coordinates, properties, options) {
if (!coordinates) throw new Error('coordinates is required');
return feature({
type: 'MultiPolygon',
coordinates: coordinates
}, properties, options);
}
/**
* Creates a {@link Feature} based on a
* coordinate array. Properties can be added optionally.
*
* @name geometryCollection
* @param {Array} geometries an array of GeoJSON Geometries
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} a GeoJSON GeometryCollection Feature
* @example
* var pt = {
* "type": "Point",
* "coordinates": [100, 0]
* };
* var line = {
* "type": "LineString",
* "coordinates": [ [101, 0], [102, 1] ]
* };
* var collection = turf.geometryCollection([pt, line]);
*
* //=collection
*/
function geometryCollection(geometries, properties, options) {
if (!geometries) throw new Error('geometries is required');
if (!Array.isArray(geometries)) throw new Error('geometries must be an Array');
return feature({
type: 'GeometryCollection',
geometries: geometries
}, properties, options);
}
/**
* Round number to precision
*
* @param {number} num Number
* @param {number} [precision=0] Precision
* @returns {number} rounded number
* @example
* turf.round(120.4321)
* //=120
*
* turf.round(120.4321, 2)
* //=120.43
*/
function round(num, precision) {
if (num === undefined || num === null || isNaN(num)) throw new Error('num is required');
if (precision && !(precision >= 0)) throw new Error('precision must be a positive number');
var multiplier = Math.pow(10, precision || 0);
return Math.round(num * multiplier) / multiplier;
}
/**
* Convert a distance measurement (assuming a spherical Earth) from radians to a more friendly unit.
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
*
* @name radiansToLength
* @param {number} radians in radians across the sphere
* @param {string} [units='kilometers'] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
* @returns {number} distance
*/
function radiansToLength(radians, units) {
if (radians === undefined || radians === null) throw new Error('radians is required');
if (units && typeof units !== 'string') throw new Error('units must be a string');
var factor = factors[units || 'kilometers'];
if (!factor) throw new Error(units + ' units is invalid');
return radians * factor;
}
/**
* Convert a distance measurement (assuming a spherical Earth) from a real-world unit into radians
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
*
* @name lengthToRadians
* @param {number} distance in real units
* @param {string} [units='kilometers'] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
* @returns {number} radians
*/
function lengthToRadians(distance, units) {
if (distance === undefined || distance === null) throw new Error('distance is required');
if (units && typeof units !== 'string') throw new Error('units must be a string');
var factor = factors[units || 'kilometers'];
if (!factor) throw new Error(units + ' units is invalid');
return distance / factor;
}
/**
* Convert a distance measurement (assuming a spherical Earth) from a real-world unit into degrees
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, centimeters, kilometres, feet
*
* @name lengthToDegrees
* @param {number} distance in real units
* @param {string} [units='kilometers'] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
* @returns {number} degrees
*/
function lengthToDegrees(distance, units) {
return radiansToDegrees(lengthToRadians(distance, units));
}
/**
* Converts any bearing angle from the north line direction (positive clockwise)
* and returns an angle between 0-360 degrees (positive clockwise), 0 being the north line
*
* @name bearingToAzimuth
* @param {number} bearing angle, between -180 and +180 degrees
* @returns {number} angle between 0 and 360 degrees
*/
function bearingToAzimuth(bearing) {
if (bearing === null || bearing === undefined) throw new Error('bearing is required');
var angle = bearing % 360;
if (angle < 0) angle += 360;
return angle;
}
/**
* Converts an angle in radians to degrees
*
* @name radiansToDegrees
* @param {number} radians angle in radians
* @returns {number} degrees between 0 and 360 degrees
*/
function radiansToDegrees(radians) {
if (radians === null || radians === undefined) throw new Error('radians is required');
var degrees = radians % (2 * Math.PI);
return degrees * 180 / Math.PI;
}
/**
* Converts an angle in degrees to radians
*
* @name degreesToRadians
* @param {number} degrees angle between 0 and 360 degrees
* @returns {number} angle in radians
*/
function degreesToRadians(degrees) {
if (degrees === null || degrees === undefined) throw new Error('degrees is required');
var radians = degrees % 360;
return radians * Math.PI / 180;
}
/**
* Converts a length to the requested unit.
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
*
* @param {number} length to be converted
* @param {string} originalUnit of the length
* @param {string} [finalUnit='kilometers'] returned unit
* @returns {number} the converted length
*/
function convertLength(length, originalUnit, finalUnit) {
if (length === null || length === undefined) throw new Error('length is required');
if (!(length >= 0)) throw new Error('length must be a positive number');
return radiansToLength(lengthToRadians(length, originalUnit), finalUnit || 'kilometers');
}
/**
* Converts a area to the requested unit.
* Valid units: kilometers, kilometres, meters, metres, centimetres, millimeters, acres, miles, yards, feet, inches
* @param {number} area to be converted
* @param {string} [originalUnit='meters'] of the distance
* @param {string} [finalUnit='kilometers'] returned unit
* @returns {number} the converted distance
*/
function convertArea(area, originalUnit, finalUnit) {
if (area === null || area === undefined) throw new Error('area is required');
if (!(area >= 0)) throw new Error('area must be a positive number');
var startFactor = areaFactors[originalUnit || 'meters'];
if (!startFactor) throw new Error('invalid original units');
var finalFactor = areaFactors[finalUnit || 'kilometers'];
if (!finalFactor) throw new Error('invalid final units');
return (area / startFactor) * finalFactor;
}
/**
* isNumber
*
* @param {*} num Number to validate
* @returns {boolean} true/false
* @example
* turf.isNumber(123)
* //=true
* turf.isNumber('foo')
* //=false
*/
function isNumber(num) {
return !isNaN(num) && num !== null && !Array.isArray(num);
}
/**
* isObject
*
* @param {*} input variable to validate
* @returns {boolean} true/false
* @example
* turf.isObject({elevation: 10})
* //=true
* turf.isObject('foo')
* //=false
*/
function isObject(input) {
return (!!input) && (input.constructor === Object);
}
/**
* Validate BBox
*
* @private
* @param {Array} bbox BBox to validate
* @returns {void}
* @throws Error if BBox is not valid
* @example
* validateBBox([-180, -40, 110, 50])
* //=OK
* validateBBox([-180, -40])
* //=Error
* validateBBox('Foo')
* //=Error
* validateBBox(5)
* //=Error
* validateBBox(null)
* //=Error
* validateBBox(undefined)
* //=Error
*/
function validateBBox(bbox) {
if (!bbox) throw new Error('bbox is required');
if (!Array.isArray(bbox)) throw new Error('bbox must be an Array');
if (bbox.length !== 4 && bbox.length !== 6) throw new Error('bbox must be an Array of 4 or 6 numbers');
bbox.forEach(function (num) {
if (!isNumber(num)) throw new Error('bbox must only contain numbers');
});
}
/**
* Validate Id
*
* @private
* @param {string|number} id Id to validate
* @returns {void}
* @throws Error if Id is not valid
* @example
* validateId([-180, -40, 110, 50])
* //=Error
* validateId([-180, -40])
* //=Error
* validateId('Foo')
* //=OK
* validateId(5)
* //=OK
* validateId(null)
* //=Error
* validateId(undefined)
* //=Error
*/
function validateId(id) {
if (!id) throw new Error('id is required');
if (['string', 'number'].indexOf(typeof id) === -1) throw new Error('id must be a number or a string');
}
// Deprecated methods
function radians2degrees() {
throw new Error('method has been renamed to `radiansToDegrees`');
}
function degrees2radians() {
throw new Error('method has been renamed to `degreesToRadians`');
}
function distanceToDegrees() {
throw new Error('method has been renamed to `lengthToDegrees`');
}
function distanceToRadians() {
throw new Error('method has been renamed to `lengthToRadians`');
}
function radiansToDistance() {
throw new Error('method has been renamed to `radiansToLength`');
}
function bearingToAngle() {
throw new Error('method has been renamed to `bearingToAzimuth`');
}
function convertDistance() {
throw new Error('method has been renamed to `convertLength`');
}
var main_es$1 = Object.freeze({
earthRadius: earthRadius,
factors: factors,
unitsFactors: unitsFactors,
areaFactors: areaFactors,
feature: feature,
geometry: geometry,
point: point,
points: points,
polygon: polygon,
polygons: polygons,
lineString: lineString,
lineStrings: lineStrings,
featureCollection: featureCollection,
multiLineString: multiLineString,
multiPoint: multiPoint,
multiPolygon: multiPolygon,
geometryCollection: geometryCollection,
round: round,
radiansToLength: radiansToLength,
lengthToRadians: lengthToRadians,
lengthToDegrees: lengthToDegrees,
bearingToAzimuth: bearingToAzimuth,
radiansToDegrees: radiansToDegrees,
degreesToRadians: degreesToRadians,
convertLength: convertLength,
convertArea: convertArea,
isNumber: isNumber,
isObject: isObject,
validateBBox: validateBBox,
validateId: validateId,
radians2degrees: radians2degrees,
degrees2radians: degrees2radians,
distanceToDegrees: distanceToDegrees,
distanceToRadians: distanceToRadians,
radiansToDistance: radiansToDistance,
bearingToAngle: bearingToAngle,
convertDistance: convertDistance
});
/**
* Callback for coordEach
*
* @callback coordEachCallback
* @param {Array} currentCoord The current coordinate being processed.
* @param {number} coordIndex The current index of the coordinate being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed.
* @param {number} geometryIndex The current index of the Geometry being processed.
*/
/**
* Iterate over coordinates in any GeoJSON object, similar to Array.forEach()
*
* @name coordEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentCoord, coordIndex, featureIndex, multiFeatureIndex)
* @param {boolean} [excludeWrapCoord=false] whether or not to include the final coordinate of LinearRings that wraps the ring in its iteration.
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {"foo": "bar"}),
* turf.point([36, 53], {"hello": "world"})
* ]);
*
* turf.coordEach(features, function (currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex) {
* //=currentCoord
* //=coordIndex
* //=featureIndex
* //=multiFeatureIndex
* //=geometryIndex
* });
*/
function coordEach(geojson, callback, excludeWrapCoord) {
// Handles null Geometry -- Skips this GeoJSON
if (geojson === null) return;
var j, k, l, geometry$$1, stopG, coords,
geometryMaybeCollection,
wrapShrink = 0,
coordIndex = 0,
isGeometryCollection,
type = geojson.type,
isFeatureCollection = type === 'FeatureCollection',
isFeature = type === 'Feature',
stop = isFeatureCollection ? geojson.features.length : 1;
// This logic may look a little weird. The reason why it is that way
// is because it's trying to be fast. GeoJSON supports multiple kinds
// of objects at its root: FeatureCollection, Features, Geometries.
// This function has the responsibility of handling all of them, and that
// means that some of the `for` loops you see below actually just don't apply
// to certain inputs. For instance, if you give this just a
// Point geometry, then both loops are short-circuited and all we do
// is gradually rename the input until it's called 'geometry'.
//
// This also aims to allocate as few resources as possible: just a
// few numbers and booleans, rather than any temporary arrays as would
// be required with the normalization approach.
for (var featureIndex = 0; featureIndex < stop; featureIndex++) {
geometryMaybeCollection = (isFeatureCollection ? geojson.features[featureIndex].geometry :
(isFeature ? geojson.geometry : geojson));
isGeometryCollection = (geometryMaybeCollection) ? geometryMaybeCollection.type === 'GeometryCollection' : false;
stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1;
for (var geomIndex = 0; geomIndex < stopG; geomIndex++) {
var multiFeatureIndex = 0;
var geometryIndex = 0;
geometry$$1 = isGeometryCollection ?
geometryMaybeCollection.geometries[geomIndex] : geometryMaybeCollection;
// Handles null Geometry -- Skips this geometry
if (geometry$$1 === null) continue;
coords = geometry$$1.coordinates;
var geomType = geometry$$1.type;
wrapShrink = (excludeWrapCoord && (geomType === 'Polygon' || geomType === 'MultiPolygon')) ? 1 : 0;
switch (geomType) {
case null:
break;
case 'Point':
callback(coords, coordIndex, featureIndex, multiFeatureIndex, geometryIndex);
coordIndex++;
multiFeatureIndex++;
break;
case 'LineString':
case 'MultiPoint':
for (j = 0; j < coords.length; j++) {
callback(coords[j], coordIndex, featureIndex, multiFeatureIndex, geometryIndex);
coordIndex++;
if (geomType === 'MultiPoint') multiFeatureIndex++;
}
if (geomType === 'LineString') multiFeatureIndex++;
break;
case 'Polygon':
case 'MultiLineString':
for (j = 0; j < coords.length; j++) {
for (k = 0; k < coords[j].length - wrapShrink; k++) {
callback(coords[j][k], coordIndex, featureIndex, multiFeatureIndex, geometryIndex);
coordIndex++;
}
if (geomType === 'MultiLineString') multiFeatureIndex++;
if (geomType === 'Polygon') geometryIndex++;
}
if (geomType === 'Polygon') multiFeatureIndex++;
break;
case 'MultiPolygon':
for (j = 0; j < coords.length; j++) {
if (geomType === 'MultiPolygon') geometryIndex = 0;
for (k = 0; k < coords[j].length; k++) {
for (l = 0; l < coords[j][k].length - wrapShrink; l++) {
callback(coords[j][k][l], coordIndex, featureIndex, multiFeatureIndex, geometryIndex);
coordIndex++;
}
geometryIndex++;
}
multiFeatureIndex++;
}
break;
case 'GeometryCollection':
for (j = 0; j < geometry$$1.geometries.length; j++)
coordEach(geometry$$1.geometries[j], callback, excludeWrapCoord);
break;
default:
throw new Error('Unknown Geometry Type');
}
}
}
}
/**
* Callback for coordReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @callback coordReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Array} currentCoord The current coordinate being processed.
* @param {number} coordIndex The current index of the coordinate being processed.
* Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed.
* @param {number} geometryIndex The current index of the Geometry being processed.
*/
/**
* Reduce coordinates in any GeoJSON object, similar to Array.reduce()
*
* @name coordReduce
* @param {FeatureCollection|Geometry|Feature} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentCoord, coordIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @param {boolean} [excludeWrapCoord=false] whether or not to include the final coordinate of LinearRings that wraps the ring in its iteration.
* @returns {*} The value that results from the reduction.
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {"foo": "bar"}),
* turf.point([36, 53], {"hello": "world"})
* ]);
*
* turf.coordReduce(features, function (previousValue, currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex) {
* //=previousValue
* //=currentCoord
* //=coordIndex
* //=featureIndex
* //=multiFeatureIndex
* //=geometryIndex
* return currentCoord;
* });
*/
function coordReduce(geojson, callback, initialValue, excludeWrapCoord) {
var previousValue = initialValue;
coordEach(geojson, function (currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex) {
if (coordIndex === 0 && initialValue === undefined) previousValue = currentCoord;
else previousValue = callback(previousValue, currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex);
}, excludeWrapCoord);
return previousValue;
}
/**
* Callback for propEach
*
* @callback propEachCallback
* @param {Object} currentProperties The current Properties being processed.
* @param {number} featureIndex The current index of the Feature being processed.
*/
/**
* Iterate over properties in any GeoJSON object, similar to Array.forEach()
*
* @name propEach
* @param {FeatureCollection|Feature} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentProperties, featureIndex)
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* turf.propEach(features, function (currentProperties, featureIndex) {
* //=currentProperties
* //=featureIndex
* });
*/
function propEach(geojson, callback) {
var i;
switch (geojson.type) {
case 'FeatureCollection':
for (i = 0; i < geojson.features.length; i++) {
callback(geojson.features[i].properties, i);
}
break;
case 'Feature':
callback(geojson.properties, 0);
break;
}
}
/**
* Callback for propReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @callback propReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {*} currentProperties The current Properties being processed.
* @param {number} featureIndex The current index of the Feature being processed.
*/
/**
* Reduce properties in any GeoJSON object into a single value,
* similar to how Array.reduce works. However, in this case we lazily run
* the reduction, so an array of all properties is unnecessary.
*
* @name propReduce
* @param {FeatureCollection|Feature} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentProperties, featureIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* turf.propReduce(features, function (previousValue, currentProperties, featureIndex) {
* //=previousValue
* //=currentProperties
* //=featureIndex
* return currentProperties
* });
*/
function propReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
propEach(geojson, function (currentProperties, featureIndex) {
if (featureIndex === 0 && initialValue === undefined) previousValue = currentProperties;
else previousValue = callback(previousValue, currentProperties, featureIndex);
});
return previousValue;
}
/**
* Callback for featureEach
*
* @callback featureEachCallback
* @param {Feature} currentFeature The current Feature being processed.
* @param {number} featureIndex The current index of the Feature being processed.
*/
/**
* Iterate over features in any GeoJSON object, similar to
* Array.forEach.
*
* @name featureEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentFeature, featureIndex)
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* turf.featureEach(features, function (currentFeature, featureIndex) {
* //=currentFeature
* //=featureIndex
* });
*/
function featureEach(geojson, callback) {
if (geojson.type === 'Feature') {
callback(geojson, 0);
} else if (geojson.type === 'FeatureCollection') {
for (var i = 0; i < geojson.features.length; i++) {
callback(geojson.features[i], i);
}
}
}
/**
* Callback for featureReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @callback featureReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Feature} currentFeature The current Feature being processed.
* @param {number} featureIndex The current index of the Feature being processed.
*/
/**
* Reduce features in any GeoJSON object, similar to Array.reduce().
*
* @name featureReduce
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentFeature, featureIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {"foo": "bar"}),
* turf.point([36, 53], {"hello": "world"})
* ]);
*
* turf.featureReduce(features, function (previousValue, currentFeature, featureIndex) {
* //=previousValue
* //=currentFeature
* //=featureIndex
* return currentFeature
* });
*/
function featureReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
featureEach(geojson, function (currentFeature, featureIndex) {
if (featureIndex === 0 && initialValue === undefined) previousValue = currentFeature;
else previousValue = callback(previousValue, currentFeature, featureIndex);
});
return previousValue;
}
/**
* Get all coordinates from any GeoJSON object.
*
* @name coordAll
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @returns {Array>} coordinate position array
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* var coords = turf.coordAll(features);
* //= [[26, 37], [36, 53]]
*/
function coordAll(geojson) {
var coords = [];
coordEach(geojson, function (coord) {
coords.push(coord);
});
return coords;
}
/**
* Callback for geomEach
*
* @callback geomEachCallback
* @param {Geometry} currentGeometry The current Geometry being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {Object} featureProperties The current Feature Properties being processed.
* @param {Array} featureBBox The current Feature BBox being processed.
* @param {number|string} featureId The current Feature Id being processed.
*/
/**
* Iterate over each geometry in any GeoJSON object, similar to Array.forEach()
*
* @name geomEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentGeometry, featureIndex, featureProperties, featureBBox, featureId)
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* turf.geomEach(features, function (currentGeometry, featureIndex, featureProperties, featureBBox, featureId) {
* //=currentGeometry
* //=featureIndex
* //=featureProperties
* //=featureBBox
* //=featureId
* });
*/
function geomEach(geojson, callback) {
var i, j, g, geometry$$1, stopG,
geometryMaybeCollection,
isGeometryCollection,
featureProperties,
featureBBox,
featureId,
featureIndex = 0,
isFeatureCollection = geojson.type === 'FeatureCollection',
isFeature = geojson.type === 'Feature',
stop = isFeatureCollection ? geojson.features.length : 1;
// This logic may look a little weird. The reason why it is that way
// is because it's trying to be fast. GeoJSON supports multiple kinds
// of objects at its root: FeatureCollection, Features, Geometries.
// This function has the responsibility of handling all of them, and that
// means that some of the `for` loops you see below actually just don't apply
// to certain inputs. For instance, if you give this just a
// Point geometry, then both loops are short-circuited and all we do
// is gradually rename the input until it's called 'geometry'.
//
// This also aims to allocate as few resources as possible: just a
// few numbers and booleans, rather than any temporary arrays as would
// be required with the normalization approach.
for (i = 0; i < stop; i++) {
geometryMaybeCollection = (isFeatureCollection ? geojson.features[i].geometry :
(isFeature ? geojson.geometry : geojson));
featureProperties = (isFeatureCollection ? geojson.features[i].properties :
(isFeature ? geojson.properties : {}));
featureBBox = (isFeatureCollection ? geojson.features[i].bbox :
(isFeature ? geojson.bbox : undefined));
featureId = (isFeatureCollection ? geojson.features[i].id :
(isFeature ? geojson.id : undefined));
isGeometryCollection = (geometryMaybeCollection) ? geometryMaybeCollection.type === 'GeometryCollection' : false;
stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1;
for (g = 0; g < stopG; g++) {
geometry$$1 = isGeometryCollection ?
geometryMaybeCollection.geometries[g] : geometryMaybeCollection;
// Handle null Geometry
if (geometry$$1 === null) {
callback(null, featureIndex, featureProperties, featureBBox, featureId);
continue;
}
switch (geometry$$1.type) {
case 'Point':
case 'LineString':
case 'MultiPoint':
case 'Polygon':
case 'MultiLineString':
case 'MultiPolygon': {
callback(geometry$$1, featureIndex, featureProperties, featureBBox, featureId);
break;
}
case 'GeometryCollection': {
for (j = 0; j < geometry$$1.geometries.length; j++) {
callback(geometry$$1.geometries[j], featureIndex, featureProperties, featureBBox, featureId);
}
break;
}
default:
throw new Error('Unknown Geometry Type');
}
}
// Only increase `featureIndex` per each feature
featureIndex++;
}
}
/**
* Callback for geomReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @callback geomReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Geometry} currentGeometry The current Geometry being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {Object} featureProperties The current Feature Properties being processed.
* @param {Array} featureBBox The current Feature BBox being processed.
* @param {number|string} featureId The current Feature Id being processed.
*/
/**
* Reduce geometry in any GeoJSON object, similar to Array.reduce().
*
* @name geomReduce
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentGeometry, featureIndex, featureProperties, featureBBox, featureId)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* turf.geomReduce(features, function (previousValue, currentGeometry, featureIndex, featureProperties, featureBBox, featureId) {
* //=previousValue
* //=currentGeometry
* //=featureIndex
* //=featureProperties
* //=featureBBox
* //=featureId
* return currentGeometry
* });
*/
function geomReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
geomEach(geojson, function (currentGeometry, featureIndex, featureProperties, featureBBox, featureId) {
if (featureIndex === 0 && initialValue === undefined) previousValue = currentGeometry;
else previousValue = callback(previousValue, currentGeometry, featureIndex, featureProperties, featureBBox, featureId);
});
return previousValue;
}
/**
* Callback for flattenEach
*
* @callback flattenEachCallback
* @param {Feature} currentFeature The current flattened feature being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed.
*/
/**
* Iterate over flattened features in any GeoJSON object, similar to
* Array.forEach.
*
* @name flattenEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentFeature, featureIndex, multiFeatureIndex)
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.multiPoint([[40, 30], [36, 53]], {hello: 'world'})
* ]);
*
* turf.flattenEach(features, function (currentFeature, featureIndex, multiFeatureIndex) {
* //=currentFeature
* //=featureIndex
* //=multiFeatureIndex
* });
*/
function flattenEach(geojson, callback) {
geomEach(geojson, function (geometry$$1, featureIndex, properties, bbox, id) {
// Callback for single geometry
var type = (geometry$$1 === null) ? null : geometry$$1.type;
switch (type) {
case null:
case 'Point':
case 'LineString':
case 'Polygon':
callback(feature(geometry$$1, properties, {bbox: bbox, id: id}), featureIndex, 0);
return;
}
var geomType;
// Callback for multi-geometry
switch (type) {
case 'MultiPoint':
geomType = 'Point';
break;
case 'MultiLineString':
geomType = 'LineString';
break;
case 'MultiPolygon':
geomType = 'Polygon';
break;
}
geometry$$1.coordinates.forEach(function (coordinate, multiFeatureIndex) {
var geom = {
type: geomType,
coordinates: coordinate
};
callback(feature(geom, properties), featureIndex, multiFeatureIndex);
});
});
}
/**
* Callback for flattenReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @callback flattenReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Feature} currentFeature The current Feature being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed.
*/
/**
* Reduce flattened features in any GeoJSON object, similar to Array.reduce().
*
* @name flattenReduce
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentFeature, featureIndex, multiFeatureIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.multiPoint([[40, 30], [36, 53]], {hello: 'world'})
* ]);
*
* turf.flattenReduce(features, function (previousValue, currentFeature, featureIndex, multiFeatureIndex) {
* //=previousValue
* //=currentFeature
* //=featureIndex
* //=multiFeatureIndex
* return currentFeature
* });
*/
function flattenReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
flattenEach(geojson, function (currentFeature, featureIndex, multiFeatureIndex) {
if (featureIndex === 0 && multiFeatureIndex === 0 && initialValue === undefined) previousValue = currentFeature;
else previousValue = callback(previousValue, currentFeature, featureIndex, multiFeatureIndex);
});
return previousValue;
}
/**
* Callback for segmentEach
*
* @callback segmentEachCallback
* @param {Feature} currentSegment The current Segment being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed.
* @param {number} geometryIndex The current index of the Geometry being processed.
* @param {number} segmentIndex The current index of the Segment being processed.
* @returns {void}
*/
/**
* Iterate over 2-vertex line segment in any GeoJSON object, similar to Array.forEach()
* (Multi)Point geometries do not contain segments therefore they are ignored during this operation.
*
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON
* @param {Function} callback a method that takes (currentSegment, featureIndex, multiFeatureIndex, geometryIndex, segmentIndex)
* @returns {void}
* @example
* var polygon = turf.polygon([[[-50, 5], [-40, -10], [-50, -10], [-40, 5], [-50, 5]]]);
*
* // Iterate over GeoJSON by 2-vertex segments
* turf.segmentEach(polygon, function (currentSegment, featureIndex, multiFeatureIndex, geometryIndex, segmentIndex) {
* //=currentSegment
* //=featureIndex
* //=multiFeatureIndex
* //=geometryIndex
* //=segmentIndex
* });
*
* // Calculate the total number of segments
* var total = 0;
* turf.segmentEach(polygon, function () {
* total++;
* });
*/
function segmentEach(geojson, callback) {
flattenEach(geojson, function (feature$$1, featureIndex, multiFeatureIndex) {
var segmentIndex = 0;
// Exclude null Geometries
if (!feature$$1.geometry) return;
// (Multi)Point geometries do not contain segments therefore they are ignored during this operation.
var type = feature$$1.geometry.type;
if (type === 'Point' || type === 'MultiPoint') return;
// Generate 2-vertex line segments
coordReduce(feature$$1, function (previousCoords, currentCoord, coordIndex, featureIndexCoord, mutliPartIndexCoord, geometryIndex) {
var currentSegment = lineString([previousCoords, currentCoord], feature$$1.properties);
callback(currentSegment, featureIndex, multiFeatureIndex, geometryIndex, segmentIndex);
segmentIndex++;
return currentCoord;
});
});
}
/**
* Callback for segmentReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @callback segmentReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Feature} currentSegment The current Segment being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed.
* @param {number} geometryIndex The current index of the Geometry being processed.
* @param {number} segmentIndex The current index of the Segment being processed.
*/
/**
* Reduce 2-vertex line segment in any GeoJSON object, similar to Array.reduce()
* (Multi)Point geometries do not contain segments therefore they are ignored during this operation.
*
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON
* @param {Function} callback a method that takes (previousValue, currentSegment, currentIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {void}
* @example
* var polygon = turf.polygon([[[-50, 5], [-40, -10], [-50, -10], [-40, 5], [-50, 5]]]);
*
* // Iterate over GeoJSON by 2-vertex segments
* turf.segmentReduce(polygon, function (previousSegment, currentSegment, featureIndex, multiFeatureIndex, geometryIndex, segmentIndex) {
* //= previousSegment
* //= currentSegment
* //= featureIndex
* //= multiFeatureIndex
* //= geometryIndex
* //= segmentInex
* return currentSegment
* });
*
* // Calculate the total number of segments
* var initialValue = 0
* var total = turf.segmentReduce(polygon, function (previousValue) {
* previousValue++;
* return previousValue;
* }, initialValue);
*/
function segmentReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
var started = false;
segmentEach(geojson, function (currentSegment, featureIndex, multiFeatureIndex, geometryIndex, segmentIndex) {
if (started === false && initialValue === undefined) previousValue = currentSegment;
else previousValue = callback(previousValue, currentSegment, featureIndex, multiFeatureIndex, geometryIndex, segmentIndex);
started = true;
});
return previousValue;
}
/**
* Callback for lineEach
*
* @callback lineEachCallback
* @param {Feature} currentLine The current LineString|LinearRing being processed
* @param {number} featureIndex The current index of the Feature being processed
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed
* @param {number} geometryIndex The current index of the Geometry being processed
*/
/**
* Iterate over line or ring coordinates in LineString, Polygon, MultiLineString, MultiPolygon Features or Geometries,
* similar to Array.forEach.
*
* @name lineEach
* @param {Geometry|Feature} geojson object
* @param {Function} callback a method that takes (currentLine, featureIndex, multiFeatureIndex, geometryIndex)
* @example
* var multiLine = turf.multiLineString([
* [[26, 37], [35, 45]],
* [[36, 53], [38, 50], [41, 55]]
* ]);
*
* turf.lineEach(multiLine, function (currentLine, featureIndex, multiFeatureIndex, geometryIndex) {
* //=currentLine
* //=featureIndex
* //=multiFeatureIndex
* //=geometryIndex
* });
*/
function lineEach(geojson, callback) {
// validation
if (!geojson) throw new Error('geojson is required');
flattenEach(geojson, function (feature$$1, featureIndex, multiFeatureIndex) {
if (feature$$1.geometry === null) return;
var type = feature$$1.geometry.type;
var coords = feature$$1.geometry.coordinates;
switch (type) {
case 'LineString':
callback(feature$$1, featureIndex, multiFeatureIndex, 0, 0);
break;
case 'Polygon':
for (var geometryIndex = 0; geometryIndex < coords.length; geometryIndex++) {
callback(lineString(coords[geometryIndex], feature$$1.properties), featureIndex, multiFeatureIndex, geometryIndex);
}
break;
}
});
}
/**
* Callback for lineReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @callback lineReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Feature} currentLine The current LineString|LinearRing being processed.
* @param {number} featureIndex The current index of the Feature being processed
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed
* @param {number} geometryIndex The current index of the Geometry being processed
*/
/**
* Reduce features in any GeoJSON object, similar to Array.reduce().
*
* @name lineReduce
* @param {Geometry|Feature} geojson object
* @param {Function} callback a method that takes (previousValue, currentLine, featureIndex, multiFeatureIndex, geometryIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var multiPoly = turf.multiPolygon([
* turf.polygon([[[12,48],[2,41],[24,38],[12,48]], [[9,44],[13,41],[13,45],[9,44]]]),
* turf.polygon([[[5, 5], [0, 0], [2, 2], [4, 4], [5, 5]]])
* ]);
*
* turf.lineReduce(multiPoly, function (previousValue, currentLine, featureIndex, multiFeatureIndex, geometryIndex) {
* //=previousValue
* //=currentLine
* //=featureIndex
* //=multiFeatureIndex
* //=geometryIndex
* return currentLine
* });
*/
function lineReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
lineEach(geojson, function (currentLine, featureIndex, multiFeatureIndex, geometryIndex) {
if (featureIndex === 0 && initialValue === undefined) previousValue = currentLine;
else previousValue = callback(previousValue, currentLine, featureIndex, multiFeatureIndex, geometryIndex);
});
return previousValue;
}
var main_es = Object.freeze({
coordEach: coordEach,
coordReduce: coordReduce,
propEach: propEach,
propReduce: propReduce,
featureEach: featureEach,
featureReduce: featureReduce,
coordAll: coordAll,
geomEach: geomEach,
geomReduce: geomReduce,
flattenEach: flattenEach,
flattenReduce: flattenReduce,
segmentEach: segmentEach,
segmentReduce: segmentReduce,
lineEach: lineEach,
lineReduce: lineReduce
});
/**
* Takes a set of features, calculates the bbox of all input features, and returns a bounding box.
*
* @name bbox
* @param {GeoJSON} geojson any GeoJSON object
* @returns {BBox} bbox extent in [minX, minY, maxX, maxY] order
* @example
* var line = turf.lineString([[-74, 40], [-78, 42], [-82, 35]]);
* var bbox = turf.bbox(line);
* var bboxPolygon = turf.bboxPolygon(bbox);
*
* //addToMap
* var addToMap = [line, bboxPolygon]
*/
function bbox(geojson) {
var BBox = [Infinity, Infinity, -Infinity, -Infinity];
coordEach(geojson, function (coord) {
if (BBox[0] > coord[0]) BBox[0] = coord[0];
if (BBox[1] > coord[1]) BBox[1] = coord[1];
if (BBox[2] < coord[0]) BBox[2] = coord[0];
if (BBox[3] < coord[1]) BBox[3] = coord[1];
});
return BBox;
}
/**
* Unwrap a coordinate from a Point Feature, Geometry or a single coordinate.
*
* @name getCoord
* @param {Array|Geometry|Feature} obj Object
* @returns {Array} coordinates
* @example
* var pt = turf.point([10, 10]);
*
* var coord = turf.getCoord(pt);
* //= [10, 10]
*/
function getCoord(obj) {
if (!obj) throw new Error('obj is required');
var coordinates = getCoords(obj);
// getCoord() must contain at least two numbers (Point)
if (coordinates.length > 1 && isNumber(coordinates[0]) && isNumber(coordinates[1])) {
return coordinates;
} else {
throw new Error('Coordinate is not a valid Point');
}
}
/**
* Unwrap coordinates from a Feature, Geometry Object or an Array of numbers
*
* @name getCoords
* @param {Array|Geometry|Feature} obj Object
* @returns {Array} coordinates
* @example
* var poly = turf.polygon([[[119.32, -8.7], [119.55, -8.69], [119.51, -8.54], [119.32, -8.7]]]);
*
* var coord = turf.getCoords(poly);
* //= [[[119.32, -8.7], [119.55, -8.69], [119.51, -8.54], [119.32, -8.7]]]
*/
function getCoords(obj) {
if (!obj) throw new Error('obj is required');
var coordinates;
// Array of numbers
if (obj.length) {
coordinates = obj;
// Geometry Object
} else if (obj.coordinates) {
coordinates = obj.coordinates;
// Feature
} else if (obj.geometry && obj.geometry.coordinates) {
coordinates = obj.geometry.coordinates;
}
// Checks if coordinates contains a number
if (coordinates) {
containsNumber(coordinates);
return coordinates;
}
throw new Error('No valid coordinates');
}
/**
* Checks if coordinates contains a number
*
* @name containsNumber
* @param {Array} coordinates GeoJSON Coordinates
* @returns {boolean} true if Array contains a number
*/
function containsNumber(coordinates) {
if (coordinates.length > 1 && isNumber(coordinates[0]) && isNumber(coordinates[1])) {
return true;
}
if (Array.isArray(coordinates[0]) && coordinates[0].length) {
return containsNumber(coordinates[0]);
}
throw new Error('coordinates must only contain numbers');
}
/**
* Enforce expectations about types of GeoJSON objects for Turf.
*
* @name geojsonType
* @param {GeoJSON} value any GeoJSON object
* @param {string} type expected GeoJSON type
* @param {string} name name of calling function
* @throws {Error} if value is not the expected type.
*/
function geojsonType(value, type, name) {
if (!type || !name) throw new Error('type and name required');
if (!value || value.type !== type) {
throw new Error('Invalid input to ' + name + ': must be a ' + type + ', given ' + value.type);
}
}
/**
* Enforce expectations about types of {@link Feature} inputs for Turf.
* Internally this uses {@link geojsonType} to judge geometry types.
*
* @name featureOf
* @param {Feature} feature a feature with an expected geometry type
* @param {string} type expected GeoJSON type
* @param {string} name name of calling function
* @throws {Error} error if value is not the expected type.
*/
function featureOf(feature$$1, type, name) {
if (!feature$$1) throw new Error('No feature passed');
if (!name) throw new Error('.featureOf() requires a name');
if (!feature$$1 || feature$$1.type !== 'Feature' || !feature$$1.geometry) {
throw new Error('Invalid input to ' + name + ', Feature with geometry required');
}
if (!feature$$1.geometry || feature$$1.geometry.type !== type) {
throw new Error('Invalid input to ' + name + ': must be a ' + type + ', given ' + feature$$1.geometry.type);
}
}
/**
* Enforce expectations about types of {@link FeatureCollection} inputs for Turf.
* Internally this uses {@link geojsonType} to judge geometry types.
*
* @name collectionOf
* @param {FeatureCollection} featureCollection a FeatureCollection for which features will be judged
* @param {string} type expected GeoJSON type
* @param {string} name name of calling function
* @throws {Error} if value is not the expected type.
*/
function collectionOf(featureCollection$$1, type, name) {
if (!featureCollection$$1) throw new Error('No featureCollection passed');
if (!name) throw new Error('.collectionOf() requires a name');
if (!featureCollection$$1 || featureCollection$$1.type !== 'FeatureCollection') {
throw new Error('Invalid input to ' + name + ', FeatureCollection required');
}
for (var i = 0; i < featureCollection$$1.features.length; i++) {
var feature$$1 = featureCollection$$1.features[i];
if (!feature$$1 || feature$$1.type !== 'Feature' || !feature$$1.geometry) {
throw new Error('Invalid input to ' + name + ', Feature with geometry required');
}
if (!feature$$1.geometry || feature$$1.geometry.type !== type) {
throw new Error('Invalid input to ' + name + ': must be a ' + type + ', given ' + feature$$1.geometry.type);
}
}
}
/**
* Get Geometry from Feature or Geometry Object
*
* @param {Feature|Geometry} geojson GeoJSON Feature or Geometry Object
* @returns {Geometry|null} GeoJSON Geometry Object
* @throws {Error} if geojson is not a Feature or Geometry Object
* @example
* var point = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [110, 40]
* }
* }
* var geom = turf.getGeom(point)
* //={"type": "Point", "coordinates": [110, 40]}
*/
function getGeom(geojson) {
if (!geojson) throw new Error('geojson is required');
if (geojson.geometry !== undefined) return geojson.geometry;
if (geojson.coordinates || geojson.geometries) return geojson;
throw new Error('geojson must be a valid Feature or Geometry Object');
}
/**
* Get Geometry Type from Feature or Geometry Object
*
* @throws {Error} **DEPRECATED** in v5.0.0 in favor of getType
*/
function getGeomType() {
throw new Error('invariant.getGeomType has been deprecated in v5.0 in favor of invariant.getType');
}
/**
* Get GeoJSON object's type, Geometry type is prioritize.
*
* @param {GeoJSON} geojson GeoJSON object
* @param {string} [name] name of the variable to display in error message
* @returns {string} GeoJSON type
* @example
* var point = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [110, 40]
* }
* }
* var geom = turf.getType(point)
* //="Point"
*/
function getType(geojson, name) {
if (!geojson) throw new Error((name || 'geojson') + ' is required');
// GeoJSON Feature & GeometryCollection
if (geojson.geometry && geojson.geometry.type) return geojson.geometry.type;
// GeoJSON Geometry & FeatureCollection
if (geojson.type) return geojson.type;
throw new Error((name || 'geojson') + ' is invalid');
}
var main_es$2 = Object.freeze({
getCoord: getCoord,
getCoords: getCoords,
containsNumber: containsNumber,
geojsonType: geojsonType,
featureOf: featureOf,
collectionOf: collectionOf,
getGeom: getGeom,
getGeomType: getGeomType,
getType: getType
});
/**
* @license GNU Affero General Public License.
* Copyright (c) 2015, 2015 Ronny Lorenz
* v. 1.2.0
* https://github.com/RaumZeit/MarchingSquares.js
*/
/**
* Compute the isocontour(s) of a scalar 2D field given
* a certain threshold by applying the Marching Squares
* Algorithm. The function returns a list of path coordinates
*/
var defaultSettings = {
successCallback: null,
verbose: false
};
var settings = {};
function isoContours(data, threshold, options) {
/* process options */
options = options ? options : {};
var optionKeys = Object.keys(defaultSettings);
for (var i = 0; i < optionKeys.length; i++) {
var key = optionKeys[i];
var val = options[key];
val = ((typeof val !== 'undefined') && (val !== null)) ? val : defaultSettings[key];
settings[key] = val;
}
if (settings.verbose)
console.log('MarchingSquaresJS-isoContours: computing isocontour for ' + threshold);
var ret = contourGrid2Paths(computeContourGrid(data, threshold));
if (typeof settings.successCallback === 'function')
settings.successCallback(ret);
return ret;
}
/*
Thats all for the public interface, below follows the actual
implementation
*/
/*
################################
Isocontour implementation below
################################
*/
/* assume that x1 == 1 && x0 == 0 */
function interpolateX(y, y0, y1) {
return (y - y0) / (y1 - y0);
}
/* compute the isocontour 4-bit grid */
function computeContourGrid(data, threshold) {
var rows = data.length - 1;
var cols = data[0].length - 1;
var ContourGrid = { rows: rows, cols: cols, cells: [] };
for (var j = 0; j < rows; ++j) {
ContourGrid.cells[j] = [];
for (var i = 0; i < cols; ++i) {
/* compose the 4-bit corner representation */
var cval = 0;
var tl = data[j + 1][i];
var tr = data[j + 1][i + 1];
var br = data[j][i + 1];
var bl = data[j][i];
if (isNaN(tl) || isNaN(tr) || isNaN(br) || isNaN(bl)) {
continue;
}
cval |= ((tl >= threshold) ? 8 : 0);
cval |= ((tr >= threshold) ? 4 : 0);
cval |= ((br >= threshold) ? 2 : 0);
cval |= ((bl >= threshold) ? 1 : 0);
/* resolve ambiguity for cval == 5 || 10 via averaging */
var flipped = false;
if (cval === 5 || cval === 10) {
var average = (tl + tr + br + bl) / 4;
if (cval === 5 && (average < threshold)) {
cval = 10;
flipped = true;
} else if (cval === 10 && (average < threshold)) {
cval = 5;
flipped = true;
}
}
/* add cell to ContourGrid if it contains edges */
if (cval !== 0 && cval !== 15) {
var top, bottom, left, right;
top = bottom = left = right = 0.5;
/* interpolate edges of cell */
if (cval === 1) {
left = 1 - interpolateX(threshold, tl, bl);
bottom = 1 - interpolateX(threshold, br, bl);
} else if (cval === 2) {
bottom = interpolateX(threshold, bl, br);
right = 1 - interpolateX(threshold, tr, br);
} else if (cval === 3) {
left = 1 - interpolateX(threshold, tl, bl);
right = 1 - interpolateX(threshold, tr, br);
} else if (cval === 4) {
top = interpolateX(threshold, tl, tr);
right = interpolateX(threshold, br, tr);
} else if (cval === 5) {
top = interpolateX(threshold, tl, tr);
right = interpolateX(threshold, br, tr);
bottom = 1 - interpolateX(threshold, br, bl);
left = 1 - interpolateX(threshold, tl, bl);
} else if (cval === 6) {
bottom = interpolateX(threshold, bl, br);
top = interpolateX(threshold, tl, tr);
} else if (cval === 7) {
left = 1 - interpolateX(threshold, tl, bl);
top = interpolateX(threshold, tl, tr);
} else if (cval === 8) {
left = interpolateX(threshold, bl, tl);
top = 1 - interpolateX(threshold, tr, tl);
} else if (cval === 9) {
bottom = 1 - interpolateX(threshold, br, bl);
top = 1 - interpolateX(threshold, tr, tl);
} else if (cval === 10) {
top = 1 - interpolateX(threshold, tr, tl);
right = 1 - interpolateX(threshold, tr, br);
bottom = interpolateX(threshold, bl, br);
left = interpolateX(threshold, bl, tl);
} else if (cval === 11) {
top = 1 - interpolateX(threshold, tr, tl);
right = 1 - interpolateX(threshold, tr, br);
} else if (cval === 12) {
left = interpolateX(threshold, bl, tl);
right = interpolateX(threshold, br, tr);
} else if (cval === 13) {
bottom = 1 - interpolateX(threshold, br, bl);
right = interpolateX(threshold, br, tr);
} else if (cval === 14) {
left = interpolateX(threshold, bl, tl);
bottom = interpolateX(threshold, bl, br);
} else {
console.log('MarchingSquaresJS-isoContours: Illegal cval detected: ' + cval);
}
ContourGrid.cells[j][i] = {
cval: cval,
flipped: flipped,
top: top,
right: right,
bottom: bottom,
left: left
};
}
}
}
return ContourGrid;
}
function isSaddle(cell) {
return cell.cval === 5 || cell.cval === 10;
}
function isTrivial(cell) {
return cell.cval === 0 || cell.cval === 15;
}
function clearCell(cell) {
if ((!isTrivial(cell)) && (cell.cval !== 5) && (cell.cval !== 10)) {
cell.cval = 15;
}
}
function getXY(cell, edge) {
if (edge === 'top') {
return [cell.top, 1.0];
} else if (edge === 'bottom') {
return [cell.bottom, 0.0];
} else if (edge === 'right') {
return [1.0, cell.right];
} else if (edge === 'left') {
return [0.0, cell.left];
}
}
function contourGrid2Paths(grid) {
var paths = [];
var path_idx = 0;
var rows = grid.rows;
var cols = grid.cols;
var epsilon = 1e-7;
grid.cells.forEach(function (g, j) {
g.forEach(function (gg, i) {
if ((typeof gg !== 'undefined') && (!isSaddle(gg)) && (!isTrivial(gg))) {
var p = tracePath(grid.cells, j, i);
var merged = false;
/* we may try to merge paths at this point */
if (p.info === 'mergeable') {
/*
search backwards through the path array to find an entry
that starts with where the current path ends...
*/
var x = p.path[p.path.length - 1][0],
y = p.path[p.path.length - 1][1];
for (var k = path_idx - 1; k >= 0; k--) {
if ((Math.abs(paths[k][0][0] - x) <= epsilon) && (Math.abs(paths[k][0][1] - y) <= epsilon)) {
for (var l = p.path.length - 2; l >= 0; --l) {
paths[k].unshift(p.path[l]);
}
merged = true;
break;
}
}
}
if (!merged)
paths[path_idx++] = p.path;
}
});
});
return paths;
}
/*
construct consecutive line segments from starting cell by
walking arround the enclosed area clock-wise
*/
function tracePath(grid, j, i) {
var maxj = grid.length;
var p = [];
var dxContour = [0, 0, 1, 1, 0, 0, 0, 0, -1, 0, 1, 1, -1, 0, -1, 0];
var dyContour = [0, -1, 0, 0, 1, 1, 1, 1, 0, -1, 0, 0, 0, -1, 0, 0];
var dx, dy;
var startEdge = ['none', 'left', 'bottom', 'left', 'right', 'none', 'bottom', 'left', 'top', 'top', 'none', 'top', 'right', 'right', 'bottom', 'none'];
var nextEdge = ['none', 'bottom', 'right', 'right', 'top', 'top', 'top', 'top', 'left', 'bottom', 'right', 'right', 'left', 'bottom', 'left', 'none'];
var edge;
var startCell = grid[j][i];
var currentCell = grid[j][i];
var cval = currentCell.cval;
var edge = startEdge[cval];
var pt = getXY(currentCell, edge);
/* push initial segment */
p.push([i + pt[0], j + pt[1]]);
edge = nextEdge[cval];
pt = getXY(currentCell, edge);
p.push([i + pt[0], j + pt[1]]);
clearCell(currentCell);
/* now walk arround the enclosed area in clockwise-direction */
var k = i + dxContour[cval];
var l = j + dyContour[cval];
var prev_cval = cval;
while ((k >= 0) && (l >= 0) && (l < maxj) && ((k != i) || (l != j))) {
currentCell = grid[l][k];
if (typeof currentCell === 'undefined') { /* path ends here */
//console.log(k + " " + l + " is undefined, stopping path!");
break;
}
cval = currentCell.cval;
if ((cval === 0) || (cval === 15)) {
return { path: p, info: 'mergeable' };
}
edge = nextEdge[cval];
dx = dxContour[cval];
dy = dyContour[cval];
if ((cval === 5) || (cval === 10)) {
/* select upper or lower band, depending on previous cells cval */
if (cval === 5) {
if (currentCell.flipped) { /* this is actually a flipped case 10 */
if (dyContour[prev_cval] === -1) {
edge = 'left';
dx = -1;
dy = 0;
} else {
edge = 'right';
dx = 1;
dy = 0;
}
} else { /* real case 5 */
if (dxContour[prev_cval] === -1) {
edge = 'bottom';
dx = 0;
dy = -1;
}
}
} else if (cval === 10) {
if (currentCell.flipped) { /* this is actually a flipped case 5 */
if (dxContour[prev_cval] === -1) {
edge = 'top';
dx = 0;
dy = 1;
} else {
edge = 'bottom';
dx = 0;
dy = -1;
}
} else { /* real case 10 */
if (dyContour[prev_cval] === 1) {
edge = 'left';
dx = -1;
dy = 0;
}
}
}
}
pt = getXY(currentCell, edge);
p.push([k + pt[0], l + pt[1]]);
clearCell(currentCell);
k += dx;
l += dy;
prev_cval = cval;
}
return { path: p, info: 'closed' };
}
/**
* Takes a {@link Point} grid and returns a correspondent matrix {Array>}
* of the 'property' values
*
* @name gridToMatrix
* @param {FeatureCollection} grid of points
* @param {Object} [options={}] Optional parameters
* @param {string} [options.zProperty='elevation'] the property name in `points` from which z-values will be pulled
* @param {boolean} [options.flip=false] returns the matrix upside-down
* @param {boolean} [options.flags=false] flags, adding a `matrixPosition` array field ([row, column]) to its properties,
* the grid points with coordinates on the matrix
* @returns {Array>} matrix of property values
* @example
* var extent = [-70.823364, -33.553984, -70.473175, -33.302986];
* var cellSize = 3;
* var grid = turf.pointGrid(extent, cellSize);
* // add a random property to each point between 0 and 60
* for (var i = 0; i < grid.features.length; i++) {
* grid.features[i].properties.elevation = (Math.random() * 60);
* }
* gridToMatrix(grid);
* //= [
* [ 1, 13, 10, 9, 10, 13, 18],
* [34, 8, 5, 4, 5, 8, 13],
* [10, 5, 2, 1, 2, 5, 4],
* [ 0, 4, 56, 19, 1, 4, 9],
* [10, 5, 2, 1, 2, 5, 10],
* [57, 8, 5, 4, 5, 0, 57],
* [ 3, 13, 10, 9, 5, 13, 18],
* [18, 13, 10, 9, 78, 13, 18]
* ]
*/
function gridToMatrix(grid, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var zProperty = options.zProperty || 'elevation';
var flip = options.flip;
var flags = options.flags;
// validation
collectionOf(grid, 'Point', 'input must contain Points');
var pointsMatrix = sortPointsByLatLng(grid, flip);
var matrix = [];
// create property matrix from sorted points
// looping order matters here
for (var r = 0; r < pointsMatrix.length; r++) {
var pointRow = pointsMatrix[r];
var row = [];
for (var c = 0; c < pointRow.length; c++) {
var point$$1 = pointRow[c];
// Check if zProperty exist
if (point$$1.properties[zProperty]) row.push(point$$1.properties[zProperty]);
else row.push(0);
// add flags
if (flags === true) point$$1.properties.matrixPosition = [r, c];
}
matrix.push(row);
}
return matrix;
}
/**
* Sorts points by latitude and longitude, creating a 2-dimensional array of points
*
* @private
* @param {FeatureCollection} points GeoJSON Point features
* @param {boolean} [flip=false] returns the matrix upside-down
* @returns {Array>} points ordered by latitude and longitude
*/
function sortPointsByLatLng(points$$1, flip) {
var pointsByLatitude = {};
// divide points by rows with the same latitude
featureEach(points$$1, function (point$$1) {
var lat = getCoords(point$$1)[1];
if (!pointsByLatitude[lat]) pointsByLatitude[lat] = [];
pointsByLatitude[lat].push(point$$1);
});
// sort points (with the same latitude) by longitude
var orderedRowsByLatitude = Object.keys(pointsByLatitude).map(function (lat) {
var row = pointsByLatitude[lat];
var rowOrderedByLongitude = row.sort(function (a, b) {
return getCoords(a)[0] - getCoords(b)[0];
});
return rowOrderedByLongitude;
});
// sort rows (of points with the same latitude) by latitude
var pointMatrix = orderedRowsByLatitude.sort(function (a, b) {
if (flip) return getCoords(a[0])[1] - getCoords(b[0])[1];
else return getCoords(b[0])[1] - getCoords(a[0])[1];
});
return pointMatrix;
}
/**
* Takes a grid {@link FeatureCollection} of {@link Point} features with z-values and an array of
* value breaks and generates [isolines](http://en.wikipedia.org/wiki/Isoline).
*
* @name isolines
* @param {FeatureCollection} pointGrid input points
* @param {Array} breaks values of `zProperty` where to draw isolines
* @param {Object} [options={}] Optional parameters
* @param {string} [options.zProperty='elevation'] the property name in `points` from which z-values will be pulled
* @param {Object} [options.commonProperties={}] GeoJSON properties passed to ALL isolines
* @param {Array} [options.breaksProperties=[]] GeoJSON properties passed, in order, to the correspondent isoline;
* the breaks array will define the order in which the isolines are created
* @returns {FeatureCollection} a FeatureCollection of {@link MultiLineString} features representing isolines
* @example
* // create a grid of points with random z-values in their properties
* var extent = [0, 30, 20, 50];
* var cellWidth = 100;
* var pointGrid = turf.pointGrid(extent, cellWidth, {units: 'miles'});
*
* for (var i = 0; i < pointGrid.features.length; i++) {
* pointGrid.features[i].properties.temperature = Math.random() * 10;
* }
* var breaks = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
*
* var lines = turf.isolines(pointGrid, breaks, {zProperty: 'temperature'});
*
* //addToMap
* var addToMap = [lines];
*/
function isolines(pointGrid, breaks, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var zProperty = options.zProperty || 'elevation';
var commonProperties = options.commonProperties || {};
var breaksProperties = options.breaksProperties || [];
// Input validation
collectionOf(pointGrid, 'Point', 'Input must contain Points');
if (!breaks) throw new Error('breaks is required');
if (!Array.isArray(breaks)) throw new Error('breaks must be an Array');
if (!isObject(commonProperties)) throw new Error('commonProperties must be an Object');
if (!Array.isArray(breaksProperties)) throw new Error('breaksProperties must be an Array');
// Isoline methods
var matrix = gridToMatrix(pointGrid, {zProperty: zProperty, flip: true});
var createdIsoLines = createIsoLines(matrix, breaks, zProperty, commonProperties, breaksProperties);
var scaledIsolines = rescaleIsolines(createdIsoLines, matrix, pointGrid);
return featureCollection(scaledIsolines);
}
/**
* Creates the isolines lines (featuresCollection of MultiLineString features) from the 2D data grid
*
* Marchingsquares process the grid data as a 3D representation of a function on a 2D plane, therefore it
* assumes the points (x-y coordinates) are one 'unit' distance. The result of the isolines function needs to be
* rescaled, with turfjs, to the original area and proportions on the map
*
* @private
* @param {Array>} matrix Grid Data
* @param {Array} breaks Breaks
* @param {string} zProperty name of the z-values property
* @param {Object} [commonProperties={}] GeoJSON properties passed to ALL isolines
* @param {Object} [breaksProperties=[]] GeoJSON properties passed to the correspondent isoline
* @returns {Array} isolines
*/
function createIsoLines(matrix, breaks, zProperty, commonProperties, breaksProperties) {
var results = [];
for (var i = 1; i < breaks.length; i++) {
var threshold = +breaks[i]; // make sure it's a number
var properties = Object.assign(
{},
commonProperties,
breaksProperties[i]
);
properties[zProperty] = threshold;
var isoline = multiLineString(isoContours(matrix, threshold), properties);
results.push(isoline);
}
return results;
}
/**
* Translates and scales isolines
*
* @private
* @param {Array} createdIsoLines to be rescaled
* @param {Array>} matrix Grid Data
* @param {Object} points Points by Latitude
* @returns {Array} isolines
*/
function rescaleIsolines(createdIsoLines, matrix, points$$1) {
// get dimensions (on the map) of the original grid
var gridBbox = bbox(points$$1); // [ minX, minY, maxX, maxY ]
var originalWidth = gridBbox[2] - gridBbox[0];
var originalHeigth = gridBbox[3] - gridBbox[1];
// get origin, which is the first point of the last row on the rectangular data on the map
var x0 = gridBbox[0];
var y0 = gridBbox[1];
// get number of cells per side
var matrixWidth = matrix[0].length - 1;
var matrixHeight = matrix.length - 1;
// calculate the scaling factor between matrix and rectangular grid on the map
var scaleX = originalWidth / matrixWidth;
var scaleY = originalHeigth / matrixHeight;
var resize = function (point$$1) {
point$$1[0] = point$$1[0] * scaleX + x0;
point$$1[1] = point$$1[1] * scaleY + y0;
};
// resize and shift each point/line of the createdIsoLines
createdIsoLines.forEach(function (isoline) {
coordEach(isoline, resize);
});
return createdIsoLines;
}
var quickselect = partialSort;
// Floyd-Rivest selection algorithm:
// Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right];
// The k-th element will have the (k - left + 1)th smallest value in [left, right]
function partialSort(arr, k, left, right, compare) {
left = left || 0;
right = right || (arr.length - 1);
compare = compare || defaultCompare;
while (right > left) {
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
partialSort(arr, k, newLeft, newRight, compare);
}
var t = arr[k];
var i = left;
var j = right;
swap(arr, left, k);
if (compare(arr[right], t) > 0) swap(arr, left, right);
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (compare(arr[i], t) < 0) i++;
while (compare(arr[j], t) > 0) j--;
}
if (compare(arr[left], t) === 0) swap(arr, left, j);
else {
j++;
swap(arr, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultCompare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
var rbush_1 = rbush;
function rbush(maxEntries, format) {
if (!(this instanceof rbush)) return new rbush(maxEntries, format);
// max entries in a node is 9 by default; min node fill is 40% for best performance
this._maxEntries = Math.max(4, maxEntries || 9);
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
if (format) {
this._initFormat(format);
}
this.clear();
}
rbush.prototype = {
all: function () {
return this._all(this.data, []);
},
search: function (bbox) {
var node = this.data,
result = [],
toBBox = this.toBBox;
if (!intersects$1(bbox, node)) return result;
var nodesToSearch = [],
i, len, child, childBBox;
while (node) {
for (i = 0, len = node.children.length; i < len; i++) {
child = node.children[i];
childBBox = node.leaf ? toBBox(child) : child;
if (intersects$1(bbox, childBBox)) {
if (node.leaf) result.push(child);
else if (contains(bbox, childBBox)) this._all(child, result);
else nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return result;
},
collides: function (bbox) {
var node = this.data,
toBBox = this.toBBox;
if (!intersects$1(bbox, node)) return false;
var nodesToSearch = [],
i, len, child, childBBox;
while (node) {
for (i = 0, len = node.children.length; i < len; i++) {
child = node.children[i];
childBBox = node.leaf ? toBBox(child) : child;
if (intersects$1(bbox, childBBox)) {
if (node.leaf || contains(bbox, childBBox)) return true;
nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return false;
},
load: function (data) {
if (!(data && data.length)) return this;
if (data.length < this._minEntries) {
for (var i = 0, len = data.length; i < len; i++) {
this.insert(data[i]);
}
return this;
}
// recursively build the tree with the given data from stratch using OMT algorithm
var node = this._build(data.slice(), 0, data.length - 1, 0);
if (!this.data.children.length) {
// save as is if tree is empty
this.data = node;
} else if (this.data.height === node.height) {
// split root if trees have the same height
this._splitRoot(this.data, node);
} else {
if (this.data.height < node.height) {
// swap trees if inserted one is bigger
var tmpNode = this.data;
this.data = node;
node = tmpNode;
}
// insert the small tree into the large tree at appropriate level
this._insert(node, this.data.height - node.height - 1, true);
}
return this;
},
insert: function (item) {
if (item) this._insert(item, this.data.height - 1);
return this;
},
clear: function () {
this.data = createNode([]);
return this;
},
remove: function (item, equalsFn) {
if (!item) return this;
var node = this.data,
bbox = this.toBBox(item),
path = [],
indexes = [],
i, parent, index, goingUp;
// depth-first iterative tree traversal
while (node || path.length) {
if (!node) { // go up
node = path.pop();
parent = path[path.length - 1];
i = indexes.pop();
goingUp = true;
}
if (node.leaf) { // check current node
index = findItem(item, node.children, equalsFn);
if (index !== -1) {
// item found, remove the item and condense tree upwards
node.children.splice(index, 1);
path.push(node);
this._condense(path);
return this;
}
}
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
path.push(node);
indexes.push(i);
i = 0;
parent = node;
node = node.children[0];
} else if (parent) { // go right
i++;
node = parent.children[i];
goingUp = false;
} else node = null; // nothing found
}
return this;
},
toBBox: function (item) { return item; },
compareMinX: compareNodeMinX,
compareMinY: compareNodeMinY,
toJSON: function () { return this.data; },
fromJSON: function (data) {
this.data = data;
return this;
},
_all: function (node, result) {
var nodesToSearch = [];
while (node) {
if (node.leaf) result.push.apply(result, node.children);
else nodesToSearch.push.apply(nodesToSearch, node.children);
node = nodesToSearch.pop();
}
return result;
},
_build: function (items, left, right, height) {
var N = right - left + 1,
M = this._maxEntries,
node;
if (N <= M) {
// reached leaf level; return leaf
node = createNode(items.slice(left, right + 1));
calcBBox(node, this.toBBox);
return node;
}
if (!height) {
// target height of the bulk-loaded tree
height = Math.ceil(Math.log(N) / Math.log(M));
// target number of root entries to maximize storage utilization
M = Math.ceil(N / Math.pow(M, height - 1));
}
node = createNode([]);
node.leaf = false;
node.height = height;
// split the items into M mostly square tiles
var N2 = Math.ceil(N / M),
N1 = N2 * Math.ceil(Math.sqrt(M)),
i, j, right2, right3;
multiSelect(items, left, right, N1, this.compareMinX);
for (i = left; i <= right; i += N1) {
right2 = Math.min(i + N1 - 1, right);
multiSelect(items, i, right2, N2, this.compareMinY);
for (j = i; j <= right2; j += N2) {
right3 = Math.min(j + N2 - 1, right2);
// pack each entry recursively
node.children.push(this._build(items, j, right3, height - 1));
}
}
calcBBox(node, this.toBBox);
return node;
},
_chooseSubtree: function (bbox, node, level, path) {
var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
while (true) {
path.push(node);
if (node.leaf || path.length - 1 === level) break;
minArea = minEnlargement = Infinity;
for (i = 0, len = node.children.length; i < len; i++) {
child = node.children[i];
area = bboxArea(child);
enlargement = enlargedArea(bbox, child) - area;
// choose entry with the least area enlargement
if (enlargement < minEnlargement) {
minEnlargement = enlargement;
minArea = area < minArea ? area : minArea;
targetNode = child;
} else if (enlargement === minEnlargement) {
// otherwise choose one with the smallest area
if (area < minArea) {
minArea = area;
targetNode = child;
}
}
}
node = targetNode || node.children[0];
}
return node;
},
_insert: function (item, level, isNode) {
var toBBox = this.toBBox,
bbox = isNode ? item : toBBox(item),
insertPath = [];
// find the best node for accommodating the item, saving all nodes along the path too
var node = this._chooseSubtree(bbox, this.data, level, insertPath);
// put the item into the node
node.children.push(item);
extend(node, bbox);
// split on node overflow; propagate upwards if necessary
while (level >= 0) {
if (insertPath[level].children.length > this._maxEntries) {
this._split(insertPath, level);
level--;
} else break;
}
// adjust bboxes along the insertion path
this._adjustParentBBoxes(bbox, insertPath, level);
},
// split overflowed node into two
_split: function (insertPath, level) {
var node = insertPath[level],
M = node.children.length,
m = this._minEntries;
this._chooseSplitAxis(node, m, M);
var splitIndex = this._chooseSplitIndex(node, m, M);
var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
newNode.height = node.height;
newNode.leaf = node.leaf;
calcBBox(node, this.toBBox);
calcBBox(newNode, this.toBBox);
if (level) insertPath[level - 1].children.push(newNode);
else this._splitRoot(node, newNode);
},
_splitRoot: function (node, newNode) {
// split root node
this.data = createNode([node, newNode]);
this.data.height = node.height + 1;
this.data.leaf = false;
calcBBox(this.data, this.toBBox);
},
_chooseSplitIndex: function (node, m, M) {
var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
minOverlap = minArea = Infinity;
for (i = m; i <= M - m; i++) {
bbox1 = distBBox(node, 0, i, this.toBBox);
bbox2 = distBBox(node, i, M, this.toBBox);
overlap = intersectionArea(bbox1, bbox2);
area = bboxArea(bbox1) + bboxArea(bbox2);
// choose distribution with minimum overlap
if (overlap < minOverlap) {
minOverlap = overlap;
index = i;
minArea = area < minArea ? area : minArea;
} else if (overlap === minOverlap) {
// otherwise choose distribution with minimum area
if (area < minArea) {
minArea = area;
index = i;
}
}
}
return index;
},
// sorts node children by the best axis for split
_chooseSplitAxis: function (node, m, M) {
var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX,
compareMinY = node.leaf ? this.compareMinY : compareNodeMinY,
xMargin = this._allDistMargin(node, m, M, compareMinX),
yMargin = this._allDistMargin(node, m, M, compareMinY);
// if total distributions margin value is minimal for x, sort by minX,
// otherwise it's already sorted by minY
if (xMargin < yMargin) node.children.sort(compareMinX);
},
// total margin of all possible split distributions where each node is at least m full
_allDistMargin: function (node, m, M, compare) {
node.children.sort(compare);
var toBBox = this.toBBox,
leftBBox = distBBox(node, 0, m, toBBox),
rightBBox = distBBox(node, M - m, M, toBBox),
margin = bboxMargin(leftBBox) + bboxMargin(rightBBox),
i, child;
for (i = m; i < M - m; i++) {
child = node.children[i];
extend(leftBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin(leftBBox);
}
for (i = M - m - 1; i >= m; i--) {
child = node.children[i];
extend(rightBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin(rightBBox);
}
return margin;
},
_adjustParentBBoxes: function (bbox, path, level) {
// adjust bboxes along the given tree path
for (var i = level; i >= 0; i--) {
extend(path[i], bbox);
}
},
_condense: function (path) {
// go through the path, removing empty nodes and updating bboxes
for (var i = path.length - 1, siblings; i >= 0; i--) {
if (path[i].children.length === 0) {
if (i > 0) {
siblings = path[i - 1].children;
siblings.splice(siblings.indexOf(path[i]), 1);
} else this.clear();
} else calcBBox(path[i], this.toBBox);
}
},
_initFormat: function (format) {
// data format (minX, minY, maxX, maxY accessors)
// uses eval-type function compilation instead of just accepting a toBBox function
// because the algorithms are very sensitive to sorting functions performance,
// so they should be dead simple and without inner calls
var compareArr = ['return a', ' - b', ';'];
this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
this.toBBox = new Function('a',
'return {minX: a' + format[0] +
', minY: a' + format[1] +
', maxX: a' + format[2] +
', maxY: a' + format[3] + '};');
}
};
function findItem(item, items, equalsFn) {
if (!equalsFn) return items.indexOf(item);
for (var i = 0; i < items.length; i++) {
if (equalsFn(item, items[i])) return i;
}
return -1;
}
// calculate node's bbox from bboxes of its children
function calcBBox(node, toBBox) {
distBBox(node, 0, node.children.length, toBBox, node);
}
// min bounding rectangle of node children from k to p-1
function distBBox(node, k, p, toBBox, destNode) {
if (!destNode) destNode = createNode(null);
destNode.minX = Infinity;
destNode.minY = Infinity;
destNode.maxX = -Infinity;
destNode.maxY = -Infinity;
for (var i = k, child; i < p; i++) {
child = node.children[i];
extend(destNode, node.leaf ? toBBox(child) : child);
}
return destNode;
}
function extend(a, b) {
a.minX = Math.min(a.minX, b.minX);
a.minY = Math.min(a.minY, b.minY);
a.maxX = Math.max(a.maxX, b.maxX);
a.maxY = Math.max(a.maxY, b.maxY);
return a;
}
function compareNodeMinX(a, b) { return a.minX - b.minX; }
function compareNodeMinY(a, b) { return a.minY - b.minY; }
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
function enlargedArea(a, b) {
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
}
function intersectionArea(a, b) {
var minX = Math.max(a.minX, b.minX),
minY = Math.max(a.minY, b.minY),
maxX = Math.min(a.maxX, b.maxX),
maxY = Math.min(a.maxY, b.maxY);
return Math.max(0, maxX - minX) *
Math.max(0, maxY - minY);
}
function contains(a, b) {
return a.minX <= b.minX &&
a.minY <= b.minY &&
b.maxX <= a.maxX &&
b.maxY <= a.maxY;
}
function intersects$1(a, b) {
return b.minX <= a.maxX &&
b.minY <= a.maxY &&
b.maxX >= a.minX &&
b.maxY >= a.minY;
}
function createNode(children) {
return {
children: children,
height: 1,
leaf: true,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
}
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
// combines selection algorithm with binary divide & conquer approach
function multiSelect(arr, left, right, n, compare) {
var stack = [left, right],
mid;
while (stack.length) {
right = stack.pop();
left = stack.pop();
if (right - left <= n) continue;
mid = left + Math.ceil((right - left) / n / 2) * n;
quickselect(arr, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var twoProduct_1 = twoProduct;
var SPLITTER = +(Math.pow(2, 27) + 1.0);
function twoProduct(a, b, result) {
var x = a * b;
var c = SPLITTER * a;
var abig = c - a;
var ahi = c - abig;
var alo = a - ahi;
var d = SPLITTER * b;
var bbig = d - b;
var bhi = d - bbig;
var blo = b - bhi;
var err1 = x - (ahi * bhi);
var err2 = err1 - (alo * bhi);
var err3 = err2 - (ahi * blo);
var y = alo * blo - err3;
if(result) {
result[0] = y;
result[1] = x;
return result
}
return [ y, x ]
}
var robustSum = linearExpansionSum;
//Easy case: Add two scalars
function scalarScalar(a, b) {
var x = a + b;
var bv = x - a;
var av = x - bv;
var br = b - bv;
var ar = a - av;
var y = ar + br;
if(y) {
return [y, x]
}
return [x]
}
function linearExpansionSum(e, f) {
var ne = e.length|0;
var nf = f.length|0;
if(ne === 1 && nf === 1) {
return scalarScalar(e[0], f[0])
}
var n = ne + nf;
var g = new Array(n);
var count = 0;
var eptr = 0;
var fptr = 0;
var abs = Math.abs;
var ei = e[eptr];
var ea = abs(ei);
var fi = f[fptr];
var fa = abs(fi);
var a, b;
if(ea < fa) {
b = ei;
eptr += 1;
if(eptr < ne) {
ei = e[eptr];
ea = abs(ei);
}
} else {
b = fi;
fptr += 1;
if(fptr < nf) {
fi = f[fptr];
fa = abs(fi);
}
}
if((eptr < ne && ea < fa) || (fptr >= nf)) {
a = ei;
eptr += 1;
if(eptr < ne) {
ei = e[eptr];
ea = abs(ei);
}
} else {
a = fi;
fptr += 1;
if(fptr < nf) {
fi = f[fptr];
fa = abs(fi);
}
}
var x = a + b;
var bv = x - a;
var y = b - bv;
var q0 = y;
var q1 = x;
var _x, _bv, _av, _br, _ar;
while(eptr < ne && fptr < nf) {
if(ea < fa) {
a = ei;
eptr += 1;
if(eptr < ne) {
ei = e[eptr];
ea = abs(ei);
}
} else {
a = fi;
fptr += 1;
if(fptr < nf) {
fi = f[fptr];
fa = abs(fi);
}
}
b = q0;
x = a + b;
bv = x - a;
y = b - bv;
if(y) {
g[count++] = y;
}
_x = q1 + x;
_bv = _x - q1;
_av = _x - _bv;
_br = x - _bv;
_ar = q1 - _av;
q0 = _ar + _br;
q1 = _x;
}
while(eptr < ne) {
a = ei;
b = q0;
x = a + b;
bv = x - a;
y = b - bv;
if(y) {
g[count++] = y;
}
_x = q1 + x;
_bv = _x - q1;
_av = _x - _bv;
_br = x - _bv;
_ar = q1 - _av;
q0 = _ar + _br;
q1 = _x;
eptr += 1;
if(eptr < ne) {
ei = e[eptr];
}
}
while(fptr < nf) {
a = fi;
b = q0;
x = a + b;
bv = x - a;
y = b - bv;
if(y) {
g[count++] = y;
}
_x = q1 + x;
_bv = _x - q1;
_av = _x - _bv;
_br = x - _bv;
_ar = q1 - _av;
q0 = _ar + _br;
q1 = _x;
fptr += 1;
if(fptr < nf) {
fi = f[fptr];
}
}
if(q0) {
g[count++] = q0;
}
if(q1) {
g[count++] = q1;
}
if(!count) {
g[count++] = 0.0;
}
g.length = count;
return g
}
var twoSum = fastTwoSum;
function fastTwoSum(a, b, result) {
var x = a + b;
var bv = x - a;
var av = x - bv;
var br = b - bv;
var ar = a - av;
if(result) {
result[0] = ar + br;
result[1] = x;
return result
}
return [ar+br, x]
}
var robustScale = scaleLinearExpansion;
function scaleLinearExpansion(e, scale) {
var n = e.length;
if(n === 1) {
var ts = twoProduct_1(e[0], scale);
if(ts[0]) {
return ts
}
return [ ts[1] ]
}
var g = new Array(2 * n);
var q = [0.1, 0.1];
var t = [0.1, 0.1];
var count = 0;
twoProduct_1(e[0], scale, q);
if(q[0]) {
g[count++] = q[0];
}
for(var i=1; i= nf)) {
a = ei;
eptr += 1;
if(eptr < ne) {
ei = e[eptr];
ea = abs(ei);
}
} else {
a = fi;
fptr += 1;
if(fptr < nf) {
fi = -f[fptr];
fa = abs(fi);
}
}
var x = a + b;
var bv = x - a;
var y = b - bv;
var q0 = y;
var q1 = x;
var _x, _bv, _av, _br, _ar;
while(eptr < ne && fptr < nf) {
if(ea < fa) {
a = ei;
eptr += 1;
if(eptr < ne) {
ei = e[eptr];
ea = abs(ei);
}
} else {
a = fi;
fptr += 1;
if(fptr < nf) {
fi = -f[fptr];
fa = abs(fi);
}
}
b = q0;
x = a + b;
bv = x - a;
y = b - bv;
if(y) {
g[count++] = y;
}
_x = q1 + x;
_bv = _x - q1;
_av = _x - _bv;
_br = x - _bv;
_ar = q1 - _av;
q0 = _ar + _br;
q1 = _x;
}
while(eptr < ne) {
a = ei;
b = q0;
x = a + b;
bv = x - a;
y = b - bv;
if(y) {
g[count++] = y;
}
_x = q1 + x;
_bv = _x - q1;
_av = _x - _bv;
_br = x - _bv;
_ar = q1 - _av;
q0 = _ar + _br;
q1 = _x;
eptr += 1;
if(eptr < ne) {
ei = e[eptr];
}
}
while(fptr < nf) {
a = fi;
b = q0;
x = a + b;
bv = x - a;
y = b - bv;
if(y) {
g[count++] = y;
}
_x = q1 + x;
_bv = _x - q1;
_av = _x - _bv;
_br = x - _bv;
_ar = q1 - _av;
q0 = _ar + _br;
q1 = _x;
fptr += 1;
if(fptr < nf) {
fi = -f[fptr];
}
}
if(q0) {
g[count++] = q0;
}
if(q1) {
g[count++] = q1;
}
if(!count) {
g[count++] = 0.0;
}
g.length = count;
return g
}
var orientation_1 = createCommonjsModule(function (module) {
var NUM_EXPAND = 5;
var EPSILON = 1.1102230246251565e-16;
var ERRBOUND3 = (3.0 + 16.0 * EPSILON) * EPSILON;
var ERRBOUND4 = (7.0 + 56.0 * EPSILON) * EPSILON;
function cofactor(m, c) {
var result = new Array(m.length-1);
for(var i=1; i>1;
return ["sum(", generateSum(expr.slice(0, m)), ",", generateSum(expr.slice(m)), ")"].join("")
}
}
function determinant(m) {
if(m.length === 2) {
return [["sum(prod(", m[0][0], ",", m[1][1], "),prod(-", m[0][1], ",", m[1][0], "))"].join("")]
} else {
var expr = [];
for(var i=0; i 0) {
if(r <= 0) {
return det
} else {
s = l + r;
}
} else if(l < 0) {
if(r >= 0) {
return det
} else {
s = -(l + r);
}
} else {
return det
}
var tol = ERRBOUND3 * s;
if(det >= tol || det <= -tol) {
return det
}
return orientation3Exact(a, b, c)
},
function orientation4(a,b,c,d) {
var adx = a[0] - d[0];
var bdx = b[0] - d[0];
var cdx = c[0] - d[0];
var ady = a[1] - d[1];
var bdy = b[1] - d[1];
var cdy = c[1] - d[1];
var adz = a[2] - d[2];
var bdz = b[2] - d[2];
var cdz = c[2] - d[2];
var bdxcdy = bdx * cdy;
var cdxbdy = cdx * bdy;
var cdxady = cdx * ady;
var adxcdy = adx * cdy;
var adxbdy = adx * bdy;
var bdxady = bdx * ady;
var det = adz * (bdxcdy - cdxbdy)
+ bdz * (cdxady - adxcdy)
+ cdz * (adxbdy - bdxady);
var permanent = (Math.abs(bdxcdy) + Math.abs(cdxbdy)) * Math.abs(adz)
+ (Math.abs(cdxady) + Math.abs(adxcdy)) * Math.abs(bdz)
+ (Math.abs(adxbdy) + Math.abs(bdxady)) * Math.abs(cdz);
var tol = ERRBOUND4 * permanent;
if ((det > tol) || (-det > tol)) {
return det
}
return orientation4Exact(a,b,c,d)
}
];
function slowOrient(args) {
var proc = CACHED[args.length];
if(!proc) {
proc = CACHED[args.length] = orientation(args.length);
}
return proc.apply(undefined, args)
}
function generateOrientationProc() {
while(CACHED.length <= NUM_EXPAND) {
CACHED.push(orientation(CACHED.length));
}
var args = [];
var procArgs = ["slow"];
for(var i=0; i<=NUM_EXPAND; ++i) {
args.push("a" + i);
procArgs.push("o" + i);
}
var code = [
"function getOrientation(", args.join(), "){switch(arguments.length){case 0:case 1:return 0;"
];
for(var i=2; i<=NUM_EXPAND; ++i) {
code.push("case ", i, ":return o", i, "(", args.slice(0, i).join(), ");");
}
code.push("}var s=new Array(arguments.length);for(var i=0;i 1 && orient$1(
points[lower[m-2]],
points[lower[m-1]],
p) <= 0) {
m -= 1;
lower.pop();
}
lower.push(idx);
//Insert into upper list
m = upper.length;
while(m > 1 && orient$1(
points[upper[m-2]],
points[upper[m-1]],
p) >= 0) {
m -= 1;
upper.pop();
}
upper.push(idx);
}
//Merge lists together
var result = new Array(upper.length + lower.length - 2);
var ptr = 0;
for(var i=0, nl=lower.length; i0; --j) {
result[ptr++] = upper[j];
}
//Return result
return result
}
var tinyqueue = TinyQueue;
var default_1$1 = TinyQueue;
function TinyQueue(data, compare) {
if (!(this instanceof TinyQueue)) return new TinyQueue(data, compare);
this.data = data || [];
this.length = this.data.length;
this.compare = compare || defaultCompare$1;
if (this.length > 0) {
for (var i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
}
}
function defaultCompare$1(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
TinyQueue.prototype = {
push: function (item) {
this.data.push(item);
this.length++;
this._up(this.length - 1);
},
pop: function () {
if (this.length === 0) return undefined;
var top = this.data[0];
this.length--;
if (this.length > 0) {
this.data[0] = this.data[this.length];
this._down(0);
}
this.data.pop();
return top;
},
peek: function () {
return this.data[0];
},
_up: function (pos) {
var data = this.data;
var compare = this.compare;
var item = data[pos];
while (pos > 0) {
var parent = (pos - 1) >> 1;
var current = data[parent];
if (compare(item, current) >= 0) break;
data[pos] = current;
pos = parent;
}
data[pos] = item;
},
_down: function (pos) {
var data = this.data;
var compare = this.compare;
var halfLength = this.length >> 1;
var item = data[pos];
while (pos < halfLength) {
var left = (pos << 1) + 1;
var right = left + 1;
var best = data[left];
if (right < this.length && compare(data[right], best) < 0) {
left = right;
best = data[right];
}
if (compare(best, item) >= 0) break;
data[pos] = best;
pos = left;
}
data[pos] = item;
}
};
tinyqueue.default = default_1$1;
var pointInPolygon = function (point, vs) {
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
var x = point[0], y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
var orient = orientation_1[3];
var concaveman_1 = concaveman;
var default_1 = concaveman;
function concaveman(points, concavity, lengthThreshold) {
// a relative measure of concavity; higher value means simpler hull
concavity = Math.max(0, concavity === undefined ? 2 : concavity);
// when a segment goes below this length threshold, it won't be drilled down further
lengthThreshold = lengthThreshold || 0;
// start with a convex hull of the points
var hull = fastConvexHull(points);
// index the points with an R-tree
var tree = rbush_1(16, ['[0]', '[1]', '[0]', '[1]']).load(points);
// turn the convex hull into a linked list and populate the initial edge queue with the nodes
var queue = [];
for (var i = 0, last; i < hull.length; i++) {
var p = hull[i];
tree.remove(p);
last = insertNode(p, last);
queue.push(last);
}
// index the segments with an R-tree (for intersection checks)
var segTree = rbush_1(16);
for (i = 0; i < queue.length; i++) segTree.insert(updateBBox(queue[i]));
var sqConcavity = concavity * concavity;
var sqLenThreshold = lengthThreshold * lengthThreshold;
// process edges one by one
while (queue.length) {
var node = queue.shift();
var a = node.p;
var b = node.next.p;
// skip the edge if it's already short enough
var sqLen = getSqDist(a, b);
if (sqLen < sqLenThreshold) continue;
var maxSqLen = sqLen / sqConcavity;
// find the best connection point for the current edge to flex inward to
p = findCandidate(tree, node.prev.p, a, b, node.next.next.p, maxSqLen, segTree);
// if we found a connection and it satisfies our concavity measure
if (p && Math.min(getSqDist(p, a), getSqDist(p, b)) <= maxSqLen) {
// connect the edge endpoints through this point and add 2 new edges to the queue
queue.push(node);
queue.push(insertNode(p, node));
// update point and segment indexes
tree.remove(p);
segTree.remove(node);
segTree.insert(updateBBox(node));
segTree.insert(updateBBox(node.next));
}
}
// convert the resulting hull linked list to an array of points
node = last;
var concave = [];
do {
concave.push(node.p);
node = node.next;
} while (node !== last);
concave.push(node.p);
return concave;
}
function findCandidate(tree, a, b, c, d, maxDist, segTree) {
var queue = new tinyqueue(null, compareDist);
var node = tree.data;
// search through the point R-tree with a depth-first search using a priority queue
// in the order of distance to the edge (b, c)
while (node) {
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
var dist = node.leaf ? sqSegDist(child, b, c) : sqSegBoxDist(b, c, child);
if (dist > maxDist) continue; // skip the node if it's farther than we ever need
queue.push({
node: child,
dist: dist
});
}
while (queue.length && !queue.peek().node.children) {
var item = queue.pop();
var p = item.node;
// skip all points that are as close to adjacent edges (a,b) and (c,d),
// and points that would introduce self-intersections when connected
var d0 = sqSegDist(p, a, b);
var d1 = sqSegDist(p, c, d);
if (item.dist < d0 && item.dist < d1 &&
noIntersections(b, p, segTree) &&
noIntersections(c, p, segTree)) return p;
}
node = queue.pop();
if (node) node = node.node;
}
return null;
}
function compareDist(a, b) {
return a.dist - b.dist;
}
// square distance from a segment bounding box to the given one
function sqSegBoxDist(a, b, bbox) {
if (inside(a, bbox) || inside(b, bbox)) return 0;
var d1 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.minX, bbox.minY, bbox.maxX, bbox.minY);
if (d1 === 0) return 0;
var d2 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.minX, bbox.minY, bbox.minX, bbox.maxY);
if (d2 === 0) return 0;
var d3 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.maxX, bbox.minY, bbox.maxX, bbox.maxY);
if (d3 === 0) return 0;
var d4 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.minX, bbox.maxY, bbox.maxX, bbox.maxY);
if (d4 === 0) return 0;
return Math.min(d1, d2, d3, d4);
}
function inside(a, bbox) {
return a[0] >= bbox.minX &&
a[0] <= bbox.maxX &&
a[1] >= bbox.minY &&
a[1] <= bbox.maxY;
}
// check if the edge (a,b) doesn't intersect any other edges
function noIntersections(a, b, segTree) {
var minX = Math.min(a[0], b[0]);
var minY = Math.min(a[1], b[1]);
var maxX = Math.max(a[0], b[0]);
var maxY = Math.max(a[1], b[1]);
var edges = segTree.search({minX: minX, minY: minY, maxX: maxX, maxY: maxY});
for (var i = 0; i < edges.length; i++) {
if (intersects(edges[i].p, edges[i].next.p, a, b)) return false;
}
return true;
}
// check if the edges (p1,q1) and (p2,q2) intersect
function intersects(p1, q1, p2, q2) {
return p1 !== q2 && q1 !== p2 &&
orient(p1, q1, p2) > 0 !== orient(p1, q1, q2) > 0 &&
orient(p2, q2, p1) > 0 !== orient(p2, q2, q1) > 0;
}
// update the bounding box of a node's edge
function updateBBox(node) {
var p1 = node.p;
var p2 = node.next.p;
node.minX = Math.min(p1[0], p2[0]);
node.minY = Math.min(p1[1], p2[1]);
node.maxX = Math.max(p1[0], p2[0]);
node.maxY = Math.max(p1[1], p2[1]);
return node;
}
// speed up convex hull by filtering out points inside quadrilateral formed by 4 extreme points
function fastConvexHull(points) {
var left = points[0];
var top = points[0];
var right = points[0];
var bottom = points[0];
// find the leftmost, rightmost, topmost and bottommost points
for (var i = 0; i < points.length; i++) {
var p = points[i];
if (p[0] < left[0]) left = p;
if (p[0] > right[0]) right = p;
if (p[1] < top[1]) top = p;
if (p[1] > bottom[1]) bottom = p;
}
// filter out points that are inside the resulting quadrilateral
var cull = [left, top, right, bottom];
var filtered = cull.slice();
for (i = 0; i < points.length; i++) {
if (!pointInPolygon(points[i], cull)) filtered.push(points[i]);
}
// get convex hull around the filtered points
var indices = monotoneConvexHull2d(filtered);
// return the hull as array of points (rather than indices)
var hull = [];
for (i = 0; i < indices.length; i++) hull.push(filtered[indices[i]]);
return hull;
}
// create a new node in a doubly linked list
function insertNode(p, prev) {
var node = {
p: p,
prev: null,
next: null,
minX: 0,
minY: 0,
maxX: 0,
maxY: 0
};
if (!prev) {
node.prev = node;
node.next = node;
} else {
node.next = prev.next;
node.prev = prev;
prev.next.prev = node;
prev.next = node;
}
return node;
}
// square distance between 2 points
function getSqDist(p1, p2) {
var dx = p1[0] - p2[0],
dy = p1[1] - p2[1];
return dx * dx + dy * dy;
}
// square distance from a point to a segment
function sqSegDist(p, p1, p2) {
var x = p1[0],
y = p1[1],
dx = p2[0] - x,
dy = p2[1] - y;
if (dx !== 0 || dy !== 0) {
var t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = p2[0];
y = p2[1];
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p[0] - x;
dy = p[1] - y;
return dx * dx + dy * dy;
}
// segment to segment distance, ported from http://geomalgorithms.com/a07-_distance.html by Dan Sunday
function sqSegSegDist(x0, y0, x1, y1, x2, y2, x3, y3) {
var ux = x1 - x0;
var uy = y1 - y0;
var vx = x3 - x2;
var vy = y3 - y2;
var wx = x0 - x2;
var wy = y0 - y2;
var a = ux * ux + uy * uy;
var b = ux * vx + uy * vy;
var c = vx * vx + vy * vy;
var d = ux * wx + uy * wy;
var e = vx * wx + vy * wy;
var D = a * c - b * b;
var sc, sN, tc, tN;
var sD = D;
var tD = D;
if (D === 0) {
sN = 0;
sD = 1;
tN = e;
tD = c;
} else {
sN = b * e - c * d;
tN = a * e - b * d;
if (sN < 0) {
sN = 0;
tN = e;
tD = c;
} else if (sN > sD) {
sN = sD;
tN = e + b;
tD = c;
}
}
if (tN < 0.0) {
tN = 0.0;
if (-d < 0.0) sN = 0.0;
else if (-d > a) sN = sD;
else {
sN = -d;
sD = a;
}
} else if (tN > tD) {
tN = tD;
if ((-d + b) < 0.0) sN = 0;
else if (-d + b > a) sN = sD;
else {
sN = -d + b;
sD = a;
}
}
sc = sN === 0 ? 0 : sN / sD;
tc = tN === 0 ? 0 : tN / tD;
var cx = (1 - sc) * x0 + sc * x1;
var cy = (1 - sc) * y0 + sc * y1;
var cx2 = (1 - tc) * x2 + tc * x3;
var cy2 = (1 - tc) * y2 + tc * y3;
var dx = cx2 - cx;
var dy = cy2 - cy;
return dx * dx + dy * dy;
}
concaveman_1.default = default_1;
/**
* Takes a {@link Feature} or a {@link FeatureCollection} and returns a convex hull {@link Polygon}.
*
* Internally this uses
* the [convex-hull](https://github.com/mikolalysenko/convex-hull) module that
* implements a [monotone chain hull](http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain).
*
* @name convex
* @param {GeoJSON} geojson input Feature or FeatureCollection
* @param {Object} [options={}] Optional parameters
* @param {number} [options.concavity=Infinity] 1 - thin shape. Infinity - convex hull.
* @returns {Feature} a convex hull
* @example
* var points = turf.featureCollection([
* turf.point([10.195312, 43.755225]),
* turf.point([10.404052, 43.8424511]),
* turf.point([10.579833, 43.659924]),
* turf.point([10.360107, 43.516688]),
* turf.point([10.14038, 43.588348]),
* turf.point([10.195312, 43.755225])
* ]);
*
* var hull = turf.convex(points);
*
* //addToMap
* var addToMap = [points, hull]
*/
function convex(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var concavity = options.concavity || Infinity;
var points$$1 = [];
// Convert all points to flat 2D coordinate Array
coordEach(geojson, function (coord) {
points$$1.push([coord[0], coord[1]]);
});
if (!points$$1.length) return null;
var convexHull = concaveman_1(points$$1, concavity);
// Convex hull should have at least 3 different vertices in order to create a valid polygon
if (convexHull.length > 3) {
return polygon([convexHull]);
}
return null;
}
// http://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
// modified from: https://github.com/substack/point-in-polygon/blob/master/index.js
// which was modified from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
/**
* Takes a {@link Point} and a {@link Polygon} or {@link MultiPolygon} and determines if the point resides inside the polygon. The polygon can
* be convex or concave. The function accounts for holes.
*
* @name booleanPointInPolygon
* @param {Coord} point input point
* @param {Feature} polygon input polygon or multipolygon
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.ignoreBoundary=false] True if polygon boundary should be ignored when determining if the point is inside the polygon otherwise false.
* @returns {boolean} `true` if the Point is inside the Polygon; `false` if the Point is not inside the Polygon
* @example
* var pt = turf.point([-77, 44]);
* var poly = turf.polygon([[
* [-81, 41],
* [-81, 47],
* [-72, 47],
* [-72, 41],
* [-81, 41]
* ]]);
*
* turf.booleanPointInPolygon(pt, poly);
* //= true
*/
function booleanPointInPolygon(point, polygon, options) {
// Optional parameters
options = options || {};
if (typeof options !== 'object') throw new Error('options is invalid');
var ignoreBoundary = options.ignoreBoundary;
// validation
if (!point) throw new Error('point is required');
if (!polygon) throw new Error('polygon is required');
var pt = getCoord(point);
var polys = getCoords(polygon);
var type = (polygon.geometry) ? polygon.geometry.type : polygon.type;
var bbox = polygon.bbox;
// Quick elimination if point is not inside bbox
if (bbox && inBBox(pt, bbox) === false) return false;
// normalize to multipolygon
if (type === 'Polygon') polys = [polys];
for (var i = 0, insidePoly = false; i < polys.length && !insidePoly; i++) {
// check if it is in the outer ring first
if (inRing(pt, polys[i][0], ignoreBoundary)) {
var inHole = false;
var k = 1;
// check for the point in any of the holes
while (k < polys[i].length && !inHole) {
if (inRing(pt, polys[i][k], !ignoreBoundary)) {
inHole = true;
}
k++;
}
if (!inHole) insidePoly = true;
}
}
return insidePoly;
}
/**
* inRing
*
* @private
* @param {Array} pt [x,y]
* @param {Array>} ring [[x,y], [x,y],..]
* @param {boolean} ignoreBoundary ignoreBoundary
* @returns {boolean} inRing
*/
function inRing(pt, ring, ignoreBoundary) {
var isInside = false;
if (ring[0][0] === ring[ring.length - 1][0] && ring[0][1] === ring[ring.length - 1][1]) ring = ring.slice(0, ring.length - 1);
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
var xi = ring[i][0], yi = ring[i][1];
var xj = ring[j][0], yj = ring[j][1];
var onBoundary = (pt[1] * (xi - xj) + yi * (xj - pt[0]) + yj * (pt[0] - xi) === 0) &&
((xi - pt[0]) * (xj - pt[0]) <= 0) && ((yi - pt[1]) * (yj - pt[1]) <= 0);
if (onBoundary) return !ignoreBoundary;
var intersect = ((yi > pt[1]) !== (yj > pt[1])) &&
(pt[0] < (xj - xi) * (pt[1] - yi) / (yj - yi) + xi);
if (intersect) isInside = !isInside;
}
return isInside;
}
/**
* inBBox
*
* @private
* @param {Position} pt point [x,y]
* @param {BBox} bbox BBox [west, south, east, north]
* @returns {boolean} true/false if point is inside BBox
*/
function inBBox(pt, bbox) {
return bbox[0] <= pt[0] &&
bbox[1] <= pt[1] &&
bbox[2] >= pt[0] &&
bbox[3] >= pt[1];
}
/**
* Finds {@link Points} that fall within {@link (Multi)Polygon(s)}.
*
* @name pointsWithinPolygon
* @param {Feauture|FeatureCollection} points Points as input search
* @param {FeatureCollection|Geoemtry|Feature} polygons Points must be within these (Multi)Polygon(s)
* @returns {FeatureCollection} points that land within at least one polygon
* @example
* var points = turf.points([
* [-46.6318, -23.5523],
* [-46.6246, -23.5325],
* [-46.6062, -23.5513],
* [-46.663, -23.554],
* [-46.643, -23.557]
* ]);
*
* var searchWithin = turf.polygon([[
* [-46.653,-23.543],
* [-46.634,-23.5346],
* [-46.613,-23.543],
* [-46.614,-23.559],
* [-46.631,-23.567],
* [-46.653,-23.560],
* [-46.653,-23.543]
* ]]);
*
* var ptsWithin = turf.pointsWithinPolygon(points, searchWithin);
*
* //addToMap
* var addToMap = [points, searchWithin, ptsWithin]
* turf.featureEach(ptsWithin, function (currentFeature) {
* currentFeature.properties['marker-size'] = 'large';
* currentFeature.properties['marker-color'] = '#000';
* });
*/
function pointsWithinPolygon(points$$1, polygons$$1) {
var results = [];
geomEach(polygons$$1, function (polygon$$1) {
featureEach(points$$1, function (point$$1) {
if (booleanPointInPolygon(point$$1, polygon$$1)) results.push(point$$1);
});
});
return featureCollection(results);
}
//http://en.wikipedia.org/wiki/Delaunay_triangulation
//https://github.com/ironwallaby/delaunay
/**
* Takes a set of {@link Point|points} and creates a
* [Triangulated Irregular Network](http://en.wikipedia.org/wiki/Triangulated_irregular_network),
* or a TIN for short, returned as a collection of Polygons. These are often used
* for developing elevation contour maps or stepped heat visualizations.
*
* If an optional z-value property is provided then it is added as properties called `a`, `b`,
* and `c` representing its value at each of the points that represent the corners of the
* triangle.
*
* @name tin
* @param {FeatureCollection} points input points
* @param {String} [z] name of the property from which to pull z values
* This is optional: if not given, then there will be no extra data added to the derived triangles.
* @returns {FeatureCollection} TIN output
* @example
* // generate some random point data
* var points = turf.randomPoint(30, {bbox: [50, 30, 70, 50]});
*
* // add a random property to each point between 0 and 9
* for (var i = 0; i < points.features.length; i++) {
* points.features[i].properties.z = ~~(Math.random() * 9);
* }
* var tin = turf.tin(points, 'z');
*
* //addToMap
* var addToMap = [tin, points]
* for (var i = 0; i < tin.features.length; i++) {
* var properties = tin.features[i].properties;
* properties.fill = '#' + properties.a + properties.b + properties.c;
* }
*/
function tin(points$$1, z) {
if (points$$1.type !== 'FeatureCollection') throw new Error('points must be a FeatureCollection');
//break down points
var isPointZ = false;
return featureCollection(triangulate(points$$1.features.map(function (p) {
var point$$1 = {
x: p.geometry.coordinates[0],
y: p.geometry.coordinates[1]
};
if (z) {
point$$1.z = p.properties[z];
} else if (p.geometry.coordinates.length === 3) {
isPointZ = true;
point$$1.z = p.geometry.coordinates[2];
}
return point$$1;
})).map(function (triangle) {
var a = [triangle.a.x, triangle.a.y];
var b = [triangle.b.x, triangle.b.y];
var c = [triangle.c.x, triangle.c.y];
var properties = {};
// Add z coordinates to triangle points if user passed
// them in that way otherwise add it as a property.
if (isPointZ) {
a.push(triangle.a.z);
b.push(triangle.b.z);
c.push(triangle.c.z);
} else {
properties = {
a: triangle.a.z,
b: triangle.b.z,
c: triangle.c.z
};
}
return polygon([[a, b, c, a]], properties);
}));
}
function Triangle(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
var A = b.x - a.x,
B = b.y - a.y,
C = c.x - a.x,
D = c.y - a.y,
E = A * (a.x + b.x) + B * (a.y + b.y),
F = C * (a.x + c.x) + D * (a.y + c.y),
G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)),
dx, dy;
// If the points of the triangle are collinear, then just find the
// extremes and use the midpoint as the center of the circumcircle.
this.x = (D * E - B * F) / G;
this.y = (A * F - C * E) / G;
dx = this.x - a.x;
dy = this.y - a.y;
this.r = dx * dx + dy * dy;
}
function byX(a, b) {
return b.x - a.x;
}
function dedup(edges) {
var j = edges.length,
a, b, i, m, n;
outer:
while (j) {
b = edges[--j];
a = edges[--j];
i = j;
while (i) {
n = edges[--i];
m = edges[--i];
if ((a === m && b === n) || (a === n && b === m)) {
edges.splice(j, 2);
edges.splice(i, 2);
j -= 2;
continue outer;
}
}
}
}
function triangulate(vertices) {
// Bail if there aren't enough vertices to form any triangles.
if (vertices.length < 3)
return [];
// Ensure the vertex array is in order of descending X coordinate
// (which is needed to ensure a subquadratic runtime), and then find
// the bounding box around the points.
vertices.sort(byX);
var i = vertices.length - 1,
xmin = vertices[i].x,
xmax = vertices[0].x,
ymin = vertices[i].y,
ymax = ymin,
epsilon = 1e-12;
var a,
b,
c,
A,
B,
G;
while (i--) {
if (vertices[i].y < ymin)
ymin = vertices[i].y;
if (vertices[i].y > ymax)
ymax = vertices[i].y;
}
//Find a supertriangle, which is a triangle that surrounds all the
//vertices. This is used like something of a sentinel value to remove
//cases in the main algorithm, and is removed before we return any
// results.
// Once found, put it in the "open" list. (The "open" list is for
// triangles who may still need to be considered; the "closed" list is
// for triangles which do not.)
var dx = xmax - xmin,
dy = ymax - ymin,
dmax = (dx > dy) ? dx : dy,
xmid = (xmax + xmin) * 0.5,
ymid = (ymax + ymin) * 0.5,
open = [
new Triangle({
x: xmid - 20 * dmax,
y: ymid - dmax,
__sentinel: true
}, {
x: xmid,
y: ymid + 20 * dmax,
__sentinel: true
}, {
x: xmid + 20 * dmax,
y: ymid - dmax,
__sentinel: true
}
)],
closed = [],
edges = [],
j;
// Incrementally add each vertex to the mesh.
i = vertices.length;
while (i--) {
// For each open triangle, check to see if the current point is
// inside it's circumcircle. If it is, remove the triangle and add
// it's edges to an edge list.
edges.length = 0;
j = open.length;
while (j--) {
// If this point is to the right of this triangle's circumcircle,
// then this triangle should never get checked again. Remove it
// from the open list, add it to the closed list, and skip.
dx = vertices[i].x - open[j].x;
if (dx > 0 && dx * dx > open[j].r) {
closed.push(open[j]);
open.splice(j, 1);
continue;
}
// If not, skip this triangle.
dy = vertices[i].y - open[j].y;
if (dx * dx + dy * dy > open[j].r)
continue;
// Remove the triangle and add it's edges to the edge list.
edges.push(
open[j].a, open[j].b,
open[j].b, open[j].c,
open[j].c, open[j].a
);
open.splice(j, 1);
}
// Remove any doubled edges.
dedup(edges);
// Add a new triangle for each edge.
j = edges.length;
while (j) {
b = edges[--j];
a = edges[--j];
c = vertices[i];
// Avoid adding colinear triangles (which have error-prone
// circumcircles)
A = b.x - a.x;
B = b.y - a.y;
G = 2 * (A * (c.y - b.y) - B * (c.x - b.x));
if (Math.abs(G) > epsilon) {
open.push(new Triangle(a, b, c));
}
}
}
// Copy any remaining open triangles to the closed list, and then
// remove any triangles that share a vertex with the supertriangle.
Array.prototype.push.apply(closed, open);
i = closed.length;
while (i--)
if (closed[i].a.__sentinel ||
closed[i].b.__sentinel ||
closed[i].c.__sentinel)
closed.splice(i, 1);
return closed;
}
//http://en.wikipedia.org/wiki/Haversine_formula
//http://www.movable-type.co.uk/scripts/latlong.html
/**
* Calculates the distance between two {@link Point|points} in degrees, radians,
* miles, or kilometers. This uses the
* [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula)
* to account for global curvature.
*
* @name distance
* @param {Coord} from origin point
* @param {Coord} to destination point
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @returns {number} distance between the two points
* @example
* var from = turf.point([-75.343, 39.984]);
* var to = turf.point([-75.534, 39.123]);
* var options = {units: 'miles'};
*
* var distance = turf.distance(from, to, options);
*
* //addToMap
* var addToMap = [from, to];
* from.properties.distance = distance;
* to.properties.distance = distance;
*/
function distance(from, to, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var units = options.units;
var coordinates1 = getCoord(from);
var coordinates2 = getCoord(to);
var dLat = degreesToRadians((coordinates2[1] - coordinates1[1]));
var dLon = degreesToRadians((coordinates2[0] - coordinates1[0]));
var lat1 = degreesToRadians(coordinates1[1]);
var lat2 = degreesToRadians(coordinates2[1]);
var a = Math.pow(Math.sin(dLat / 2), 2) +
Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
return radiansToLength(2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)), units);
}
/**
* Returns a cloned copy of the passed GeoJSON Object, including possible 'Foreign Members'.
* ~3-5x faster than the common JSON.parse + JSON.stringify combo method.
*
* @name clone
* @param {GeoJSON} geojson GeoJSON Object
* @returns {GeoJSON} cloned GeoJSON Object
* @example
* var line = turf.lineString([[-74, 40], [-78, 42], [-82, 35]], {color: 'red'});
*
* var lineCloned = turf.clone(line);
*/
function clone(geojson) {
if (!geojson) throw new Error('geojson is required');
switch (geojson.type) {
case 'Feature':
return cloneFeature(geojson);
case 'FeatureCollection':
return cloneFeatureCollection(geojson);
case 'Point':
case 'LineString':
case 'Polygon':
case 'MultiPoint':
case 'MultiLineString':
case 'MultiPolygon':
case 'GeometryCollection':
return cloneGeometry(geojson);
default:
throw new Error('unknown GeoJSON type');
}
}
/**
* Clone Feature
*
* @private
* @param {Feature} geojson GeoJSON Feature
* @returns {Feature} cloned Feature
*/
function cloneFeature(geojson) {
var cloned = {type: 'Feature'};
// Preserve Foreign Members
Object.keys(geojson).forEach(function (key) {
switch (key) {
case 'type':
case 'properties':
case 'geometry':
return;
default:
cloned[key] = geojson[key];
}
});
// Add properties & geometry last
cloned.properties = cloneProperties(geojson.properties);
cloned.geometry = cloneGeometry(geojson.geometry);
return cloned;
}
/**
* Clone Properties
*
* @private
* @param {Object} properties GeoJSON Properties
* @returns {Object} cloned Properties
*/
function cloneProperties(properties) {
var cloned = {};
if (!properties) return cloned;
Object.keys(properties).forEach(function (key) {
var value = properties[key];
if (typeof value === 'object') {
if (value === null) {
// handle null
cloned[key] = null;
} else if (value.length) {
// handle Array
cloned[key] = value.map(function (item) {
return item;
});
} else {
// handle generic Object
cloned[key] = cloneProperties(value);
}
} else cloned[key] = value;
});
return cloned;
}
/**
* Clone Feature Collection
*
* @private
* @param {FeatureCollection} geojson GeoJSON Feature Collection
* @returns {FeatureCollection} cloned Feature Collection
*/
function cloneFeatureCollection(geojson) {
var cloned = {type: 'FeatureCollection'};
// Preserve Foreign Members
Object.keys(geojson).forEach(function (key) {
switch (key) {
case 'type':
case 'features':
return;
default:
cloned[key] = geojson[key];
}
});
// Add features
cloned.features = geojson.features.map(function (feature) {
return cloneFeature(feature);
});
return cloned;
}
/**
* Clone Geometry
*
* @private
* @param {Geometry} geometry GeoJSON Geometry
* @returns {Geometry} cloned Geometry
*/
function cloneGeometry(geometry) {
var geom = {type: geometry.type};
if (geometry.bbox) geom.bbox = geometry.bbox;
if (geometry.type === 'GeometryCollection') {
geom.geometries = geometry.geometries.map(function (geom) {
return cloneGeometry(geom);
});
return geom;
}
geom.coordinates = deepSlice(geometry.coordinates);
return geom;
}
/**
* Deep Slice coordinates
*
* @private
* @param {Coordinates} coords Coordinates
* @returns {Coordinates} all coordinates sliced
*/
function deepSlice(coords) {
if (typeof coords[0] !== 'object') { return coords.slice(); }
return coords.map(function (coord) {
return deepSlice(coord);
});
}
var identity = function(x) {
return x;
};
var transform = function(transform) {
if (transform == null) return identity;
var x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(input, i) {
if (!i) x0 = y0 = 0;
var j = 2, n = input.length, output = new Array(n);
output[0] = (x0 += input[0]) * kx + dx;
output[1] = (y0 += input[1]) * ky + dy;
while (j < n) output[j] = input[j], ++j;
return output;
};
};
var reverse = function(array, n) {
var t, j = array.length, i = j - n;
while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
};
function object(topology, o) {
var transformPoint = transform(topology.transform),
arcs = topology.arcs;
function arc(i, points) {
if (points.length) points.pop();
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length; k < n; ++k) {
points.push(transformPoint(a[k], k));
}
if (i < 0) reverse(points, n);
}
function point(p) {
return transformPoint(p);
}
function line(arcs) {
var points = [];
for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
if (points.length < 2) points.push(points[0]); // This should never happen per the specification.
return points;
}
function ring(arcs) {
var points = line(arcs);
while (points.length < 4) points.push(points[0]); // This may happen if an arc has only two points.
return points;
}
function polygon(arcs) {
return arcs.map(ring);
}
function geometry(o) {
var type = o.type, coordinates;
switch (type) {
case "GeometryCollection": return {type: type, geometries: o.geometries.map(geometry)};
case "Point": coordinates = point(o.coordinates); break;
case "MultiPoint": coordinates = o.coordinates.map(point); break;
case "LineString": coordinates = line(o.arcs); break;
case "MultiLineString": coordinates = o.arcs.map(line); break;
case "Polygon": coordinates = polygon(o.arcs); break;
case "MultiPolygon": coordinates = o.arcs.map(polygon); break;
default: return null;
}
return {type: type, coordinates: coordinates};
}
return geometry(o);
}
var stitch = function(topology, arcs) {
var stitchedArcs = {},
fragmentByStart = {},
fragmentByEnd = {},
fragments = [],
emptyIndex = -1;
// Stitch empty arcs first, since they may be subsumed by other arcs.
arcs.forEach(function(i, j) {
var arc = topology.arcs[i < 0 ? ~i : i], t;
if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
}
});
arcs.forEach(function(i) {
var e = ends(i),
start = e[0],
end = e[1],
f, g;
if (f = fragmentByEnd[start]) {
delete fragmentByEnd[f.end];
f.push(i);
f.end = end;
if (g = fragmentByStart[end]) {
delete fragmentByStart[g.start];
var fg = g === f ? f : f.concat(g);
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByStart[end]) {
delete fragmentByStart[f.start];
f.unshift(i);
f.start = start;
if (g = fragmentByEnd[start]) {
delete fragmentByEnd[g.end];
var gf = g === f ? f : g.concat(f);
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else {
f = [i];
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
}
});
function ends(i) {
var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;
if (topology.transform) p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; });
else p1 = arc[arc.length - 1];
return i < 0 ? [p1, p0] : [p0, p1];
}
function flush(fragmentByEnd, fragmentByStart) {
for (var k in fragmentByEnd) {
var f = fragmentByEnd[k];
delete fragmentByStart[f.start];
delete f.start;
delete f.end;
f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });
fragments.push(f);
}
}
flush(fragmentByEnd, fragmentByStart);
flush(fragmentByStart, fragmentByEnd);
arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); });
return fragments;
};
function planarRingArea(ring) {
var i = -1, n = ring.length, a, b = ring[n - 1], area = 0;
while (++i < n) a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0];
return Math.abs(area); // Note: doubled area!
}
var merge = function(topology) {
return object(topology, mergeArcs.apply(this, arguments));
};
function mergeArcs(topology, objects) {
var polygonsByArc = {},
polygons = [],
groups = [];
objects.forEach(geometry);
function geometry(o) {
switch (o.type) {
case "GeometryCollection": o.geometries.forEach(geometry); break;
case "Polygon": extract(o.arcs); break;
case "MultiPolygon": o.arcs.forEach(extract); break;
}
}
function extract(polygon) {
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
(polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
});
});
polygons.push(polygon);
}
function area(ring) {
return planarRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]);
}
polygons.forEach(function(polygon) {
if (!polygon._) {
var group = [],
neighbors = [polygon];
polygon._ = 1;
groups.push(group);
while (polygon = neighbors.pop()) {
group.push(polygon);
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
polygonsByArc[arc < 0 ? ~arc : arc].forEach(function(polygon) {
if (!polygon._) {
polygon._ = 1;
neighbors.push(polygon);
}
});
});
});
}
}
});
polygons.forEach(function(polygon) {
delete polygon._;
});
return {
type: "MultiPolygon",
arcs: groups.map(function(polygons) {
var arcs = [], n;
// Extract the exterior (unique) arcs.
polygons.forEach(function(polygon) {
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
if (polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
arcs.push(arc);
}
});
});
});
// Stitch the arcs into one or more rings.
arcs = stitch(topology, arcs);
// If more than one ring is returned,
// at most one of these rings can be the exterior;
// choose the one with the greatest absolute area.
if ((n = arcs.length) > 1) {
for (var i = 1, k = area(arcs[0]), ki, t; i < n; ++i) {
if ((ki = area(arcs[i])) > k) {
t = arcs[0], arcs[0] = arcs[i], arcs[i] = t, k = ki;
}
}
}
return arcs;
})
};
}
// Computes the bounding box of the specified hash of GeoJSON objects.
var bounds = function(objects) {
var x0 = Infinity,
y0 = Infinity,
x1 = -Infinity,
y1 = -Infinity;
function boundGeometry(geometry) {
if (geometry != null && boundGeometryType.hasOwnProperty(geometry.type)) boundGeometryType[geometry.type](geometry);
}
var boundGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(boundGeometry); },
Point: function(o) { boundPoint(o.coordinates); },
MultiPoint: function(o) { o.coordinates.forEach(boundPoint); },
LineString: function(o) { boundLine(o.arcs); },
MultiLineString: function(o) { o.arcs.forEach(boundLine); },
Polygon: function(o) { o.arcs.forEach(boundLine); },
MultiPolygon: function(o) { o.arcs.forEach(boundMultiLine); }
};
function boundPoint(coordinates) {
var x = coordinates[0],
y = coordinates[1];
if (x < x0) x0 = x;
if (x > x1) x1 = x;
if (y < y0) y0 = y;
if (y > y1) y1 = y;
}
function boundLine(coordinates) {
coordinates.forEach(boundPoint);
}
function boundMultiLine(coordinates) {
coordinates.forEach(boundLine);
}
for (var key in objects) {
boundGeometry(objects[key]);
}
return x1 >= x0 && y1 >= y0 ? [x0, y0, x1, y1] : undefined;
};
var hashset = function(size, hash, equal, type, empty) {
if (arguments.length === 3) {
type = Array;
empty = null;
}
var store = new type(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
mask = size - 1;
for (var i = 0; i < size; ++i) {
store[i] = empty;
}
function add(value) {
var index = hash(value) & mask,
match = store[index],
collisions = 0;
while (match != empty) {
if (equal(match, value)) return true;
if (++collisions >= size) throw new Error("full hashset");
match = store[index = (index + 1) & mask];
}
store[index] = value;
return true;
}
function has(value) {
var index = hash(value) & mask,
match = store[index],
collisions = 0;
while (match != empty) {
if (equal(match, value)) return true;
if (++collisions >= size) break;
match = store[index = (index + 1) & mask];
}
return false;
}
function values() {
var values = [];
for (var i = 0, n = store.length; i < n; ++i) {
var match = store[i];
if (match != empty) values.push(match);
}
return values;
}
return {
add: add,
has: has,
values: values
};
};
var hashmap = function(size, hash, equal, keyType, keyEmpty, valueType) {
if (arguments.length === 3) {
keyType = valueType = Array;
keyEmpty = null;
}
var keystore = new keyType(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
valstore = new valueType(size),
mask = size - 1;
for (var i = 0; i < size; ++i) {
keystore[i] = keyEmpty;
}
function set(key, value) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index] = value;
if (++collisions >= size) throw new Error("full hashmap");
matchKey = keystore[index = (index + 1) & mask];
}
keystore[index] = key;
valstore[index] = value;
return value;
}
function maybeSet(key, value) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index];
if (++collisions >= size) throw new Error("full hashmap");
matchKey = keystore[index = (index + 1) & mask];
}
keystore[index] = key;
valstore[index] = value;
return value;
}
function get(key, missingValue) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index];
if (++collisions >= size) break;
matchKey = keystore[index = (index + 1) & mask];
}
return missingValue;
}
function keys() {
var keys = [];
for (var i = 0, n = keystore.length; i < n; ++i) {
var matchKey = keystore[i];
if (matchKey != keyEmpty) keys.push(matchKey);
}
return keys;
}
return {
set: set,
maybeSet: maybeSet, // set if unset
get: get,
keys: keys
};
};
var equalPoint = function(pointA, pointB) {
return pointA[0] === pointB[0] && pointA[1] === pointB[1];
};
// TODO if quantized, use simpler Int32 hashing?
var buffer = new ArrayBuffer(16);
var floats = new Float64Array(buffer);
var uints = new Uint32Array(buffer);
var hashPoint = function(point) {
floats[0] = point[0];
floats[1] = point[1];
var hash = uints[0] ^ uints[1];
hash = hash << 5 ^ hash >> 7 ^ uints[2] ^ uints[3];
return hash & 0x7fffffff;
};
// Given an extracted (pre-)topology, identifies all of the junctions. These are
// the points at which arcs (lines or rings) will need to be cut so that each
// arc is represented uniquely.
//
// A junction is a point where at least one arc deviates from another arc going
// through the same point. For example, consider the point B. If there is a arc
// through ABC and another arc through CBA, then B is not a junction because in
// both cases the adjacent point pairs are {A,C}. However, if there is an
// additional arc ABD, then {A,D} != {A,C}, and thus B becomes a junction.
//
// For a closed ring ABCA, the first point A’s adjacent points are the second
// and last point {B,C}. For a line, the first and last point are always
// considered junctions, even if the line is closed; this ensures that a closed
// line is never rotated.
var join = function(topology) {
var coordinates = topology.coordinates,
lines = topology.lines,
rings = topology.rings,
indexes = index(),
visitedByIndex = new Int32Array(coordinates.length),
leftByIndex = new Int32Array(coordinates.length),
rightByIndex = new Int32Array(coordinates.length),
junctionByIndex = new Int8Array(coordinates.length),
junctionCount = 0, // upper bound on number of junctions
i, n,
previousIndex,
currentIndex,
nextIndex;
for (i = 0, n = coordinates.length; i < n; ++i) {
visitedByIndex[i] = leftByIndex[i] = rightByIndex[i] = -1;
}
for (i = 0, n = lines.length; i < n; ++i) {
var line = lines[i],
lineStart = line[0],
lineEnd = line[1];
currentIndex = indexes[lineStart];
nextIndex = indexes[++lineStart];
++junctionCount, junctionByIndex[currentIndex] = 1; // start
while (++lineStart <= lineEnd) {
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[lineStart]);
}
++junctionCount, junctionByIndex[nextIndex] = 1; // end
}
for (i = 0, n = coordinates.length; i < n; ++i) {
visitedByIndex[i] = -1;
}
for (i = 0, n = rings.length; i < n; ++i) {
var ring = rings[i],
ringStart = ring[0] + 1,
ringEnd = ring[1];
previousIndex = indexes[ringEnd - 1];
currentIndex = indexes[ringStart - 1];
nextIndex = indexes[ringStart];
sequence(i, previousIndex, currentIndex, nextIndex);
while (++ringStart <= ringEnd) {
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[ringStart]);
}
}
function sequence(i, previousIndex, currentIndex, nextIndex) {
if (visitedByIndex[currentIndex] === i) return; // ignore self-intersection
visitedByIndex[currentIndex] = i;
var leftIndex = leftByIndex[currentIndex];
if (leftIndex >= 0) {
var rightIndex = rightByIndex[currentIndex];
if ((leftIndex !== previousIndex || rightIndex !== nextIndex)
&& (leftIndex !== nextIndex || rightIndex !== previousIndex)) {
++junctionCount, junctionByIndex[currentIndex] = 1;
}
} else {
leftByIndex[currentIndex] = previousIndex;
rightByIndex[currentIndex] = nextIndex;
}
}
function index() {
var indexByPoint = hashmap(coordinates.length * 1.4, hashIndex, equalIndex, Int32Array, -1, Int32Array),
indexes = new Int32Array(coordinates.length);
for (var i = 0, n = coordinates.length; i < n; ++i) {
indexes[i] = indexByPoint.maybeSet(i, i);
}
return indexes;
}
function hashIndex(i) {
return hashPoint(coordinates[i]);
}
function equalIndex(i, j) {
return equalPoint(coordinates[i], coordinates[j]);
}
visitedByIndex = leftByIndex = rightByIndex = null;
var junctionByPoint = hashset(junctionCount * 1.4, hashPoint, equalPoint), j;
// Convert back to a standard hashset by point for caller convenience.
for (i = 0, n = coordinates.length; i < n; ++i) {
if (junctionByIndex[j = indexes[i]]) {
junctionByPoint.add(coordinates[j]);
}
}
return junctionByPoint;
};
// Given an extracted (pre-)topology, cuts (or rotates) arcs so that all shared
// point sequences are identified. The topology can then be subsequently deduped
// to remove exact duplicate arcs.
var cut = function(topology) {
var junctions = join(topology),
coordinates = topology.coordinates,
lines = topology.lines,
rings = topology.rings,
next,
i, n;
for (i = 0, n = lines.length; i < n; ++i) {
var line = lines[i],
lineMid = line[0],
lineEnd = line[1];
while (++lineMid < lineEnd) {
if (junctions.has(coordinates[lineMid])) {
next = {0: lineMid, 1: line[1]};
line[1] = lineMid;
line = line.next = next;
}
}
}
for (i = 0, n = rings.length; i < n; ++i) {
var ring = rings[i],
ringStart = ring[0],
ringMid = ringStart,
ringEnd = ring[1],
ringFixed = junctions.has(coordinates[ringStart]);
while (++ringMid < ringEnd) {
if (junctions.has(coordinates[ringMid])) {
if (ringFixed) {
next = {0: ringMid, 1: ring[1]};
ring[1] = ringMid;
ring = ring.next = next;
} else { // For the first junction, we can rotate rather than cut.
rotateArray(coordinates, ringStart, ringEnd, ringEnd - ringMid);
coordinates[ringEnd] = coordinates[ringStart];
ringFixed = true;
ringMid = ringStart; // restart; we may have skipped junctions
}
}
}
}
return topology;
};
function rotateArray(array, start, end, offset) {
reverse$1(array, start, end);
reverse$1(array, start, start + offset);
reverse$1(array, start + offset, end);
}
function reverse$1(array, start, end) {
for (var mid = start + ((end-- - start) >> 1), t; start < mid; ++start, --end) {
t = array[start], array[start] = array[end], array[end] = t;
}
}
// Given a cut topology, combines duplicate arcs.
var dedup$1 = function(topology) {
var coordinates = topology.coordinates,
lines = topology.lines, line,
rings = topology.rings, ring,
arcCount = lines.length + rings.length,
i, n;
delete topology.lines;
delete topology.rings;
// Count the number of (non-unique) arcs to initialize the hashmap safely.
for (i = 0, n = lines.length; i < n; ++i) {
line = lines[i]; while (line = line.next) ++arcCount;
}
for (i = 0, n = rings.length; i < n; ++i) {
ring = rings[i]; while (ring = ring.next) ++arcCount;
}
var arcsByEnd = hashmap(arcCount * 2 * 1.4, hashPoint, equalPoint),
arcs = topology.arcs = [];
for (i = 0, n = lines.length; i < n; ++i) {
line = lines[i];
do {
dedupLine(line);
} while (line = line.next);
}
for (i = 0, n = rings.length; i < n; ++i) {
ring = rings[i];
if (ring.next) { // arc is no longer closed
do {
dedupLine(ring);
} while (ring = ring.next);
} else {
dedupRing(ring);
}
}
function dedupLine(arc) {
var startPoint,
endPoint,
startArcs, startArc,
endArcs, endArc,
i, n;
// Does this arc match an existing arc in order?
if (startArcs = arcsByEnd.get(startPoint = coordinates[arc[0]])) {
for (i = 0, n = startArcs.length; i < n; ++i) {
startArc = startArcs[i];
if (equalLine(startArc, arc)) {
arc[0] = startArc[0];
arc[1] = startArc[1];
return;
}
}
}
// Does this arc match an existing arc in reverse order?
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[1]])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (reverseEqualLine(endArc, arc)) {
arc[1] = endArc[0];
arc[0] = endArc[1];
return;
}
}
}
if (startArcs) startArcs.push(arc); else arcsByEnd.set(startPoint, [arc]);
if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
arcs.push(arc);
}
function dedupRing(arc) {
var endPoint,
endArcs,
endArc,
i, n;
// Does this arc match an existing line in order, or reverse order?
// Rings are closed, so their start point and end point is the same.
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0]])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (equalRing(endArc, arc)) {
arc[0] = endArc[0];
arc[1] = endArc[1];
return;
}
if (reverseEqualRing(endArc, arc)) {
arc[0] = endArc[1];
arc[1] = endArc[0];
return;
}
}
}
// Otherwise, does this arc match an existing ring in order, or reverse order?
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0] + findMinimumOffset(arc)])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (equalRing(endArc, arc)) {
arc[0] = endArc[0];
arc[1] = endArc[1];
return;
}
if (reverseEqualRing(endArc, arc)) {
arc[0] = endArc[1];
arc[1] = endArc[0];
return;
}
}
}
if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
arcs.push(arc);
}
function equalLine(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1];
if (ia - ja !== ib - jb) return false;
for (; ia <= ja; ++ia, ++ib) if (!equalPoint(coordinates[ia], coordinates[ib])) return false;
return true;
}
function reverseEqualLine(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1];
if (ia - ja !== ib - jb) return false;
for (; ia <= ja; ++ia, --jb) if (!equalPoint(coordinates[ia], coordinates[jb])) return false;
return true;
}
function equalRing(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1],
n = ja - ia;
if (n !== jb - ib) return false;
var ka = findMinimumOffset(arcA),
kb = findMinimumOffset(arcB);
for (var i = 0; i < n; ++i) {
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[ib + (i + kb) % n])) return false;
}
return true;
}
function reverseEqualRing(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1],
n = ja - ia;
if (n !== jb - ib) return false;
var ka = findMinimumOffset(arcA),
kb = n - findMinimumOffset(arcB);
for (var i = 0; i < n; ++i) {
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[jb - (i + kb) % n])) return false;
}
return true;
}
// Rings are rotated to a consistent, but arbitrary, start point.
// This is necessary to detect when a ring and a rotated copy are dupes.
function findMinimumOffset(arc) {
var start = arc[0],
end = arc[1],
mid = start,
minimum = mid,
minimumPoint = coordinates[mid];
while (++mid < end) {
var point = coordinates[mid];
if (point[0] < minimumPoint[0] || point[0] === minimumPoint[0] && point[1] < minimumPoint[1]) {
minimum = mid;
minimumPoint = point;
}
}
return minimum - start;
}
return topology;
};
// Given an array of arcs in absolute (but already quantized!) coordinates,
// converts to fixed-point delta encoding.
// This is a destructive operation that modifies the given arcs!
var delta = function(arcs) {
var i = -1,
n = arcs.length;
while (++i < n) {
var arc = arcs[i],
j = 0,
k = 1,
m = arc.length,
point = arc[0],
x0 = point[0],
y0 = point[1],
x1,
y1;
while (++j < m) {
point = arc[j], x1 = point[0], y1 = point[1];
if (x1 !== x0 || y1 !== y0) arc[k++] = [x1 - x0, y1 - y0], x0 = x1, y0 = y1;
}
if (k === 1) arc[k++] = [0, 0]; // Each arc must be an array of two or more positions.
arc.length = k;
}
return arcs;
};
// Extracts the lines and rings from the specified hash of geometry objects.
//
// Returns an object with three properties:
//
// * coordinates - shared buffer of [x, y] coordinates
// * lines - lines extracted from the hash, of the form [start, end]
// * rings - rings extracted from the hash, of the form [start, end]
//
// For each ring or line, start and end represent inclusive indexes into the
// coordinates buffer. For rings (and closed lines), coordinates[start] equals
// coordinates[end].
//
// For each line or polygon geometry in the input hash, including nested
// geometries as in geometry collections, the `coordinates` array is replaced
// with an equivalent `arcs` array that, for each line (for line string
// geometries) or ring (for polygon geometries), points to one of the above
// lines or rings.
var extract = function(objects) {
var index = -1,
lines = [],
rings = [],
coordinates = [];
function extractGeometry(geometry) {
if (geometry && extractGeometryType.hasOwnProperty(geometry.type)) extractGeometryType[geometry.type](geometry);
}
var extractGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(extractGeometry); },
LineString: function(o) { o.arcs = extractLine(o.arcs); },
MultiLineString: function(o) { o.arcs = o.arcs.map(extractLine); },
Polygon: function(o) { o.arcs = o.arcs.map(extractRing); },
MultiPolygon: function(o) { o.arcs = o.arcs.map(extractMultiRing); }
};
function extractLine(line) {
for (var i = 0, n = line.length; i < n; ++i) coordinates[++index] = line[i];
var arc = {0: index - n + 1, 1: index};
lines.push(arc);
return arc;
}
function extractRing(ring) {
for (var i = 0, n = ring.length; i < n; ++i) coordinates[++index] = ring[i];
var arc = {0: index - n + 1, 1: index};
rings.push(arc);
return arc;
}
function extractMultiRing(rings) {
return rings.map(extractRing);
}
for (var key in objects) {
extractGeometry(objects[key]);
}
return {
type: "Topology",
coordinates: coordinates,
lines: lines,
rings: rings,
objects: objects
};
};
// Given a hash of GeoJSON objects, returns a hash of GeoJSON geometry objects.
// Any null input geometry objects are represented as {type: null} in the output.
// Any feature.{id,properties,bbox} are transferred to the output geometry object.
// Each output geometry object is a shallow copy of the input (e.g., properties, coordinates)!
var geometry$1 = function(inputs) {
var outputs = {}, key;
for (key in inputs) outputs[key] = geomifyObject(inputs[key]);
return outputs;
};
function geomifyObject(input) {
return input == null ? {type: null}
: (input.type === "FeatureCollection" ? geomifyFeatureCollection
: input.type === "Feature" ? geomifyFeature
: geomifyGeometry)(input);
}
function geomifyFeatureCollection(input) {
var output = {type: "GeometryCollection", geometries: input.features.map(geomifyFeature)};
if (input.bbox != null) output.bbox = input.bbox;
return output;
}
function geomifyFeature(input) {
var output = geomifyGeometry(input.geometry), key; // eslint-disable-line no-unused-vars
if (input.id != null) output.id = input.id;
if (input.bbox != null) output.bbox = input.bbox;
for (key in input.properties) { output.properties = input.properties; break; }
return output;
}
function geomifyGeometry(input) {
if (input == null) return {type: null};
var output = input.type === "GeometryCollection" ? {type: "GeometryCollection", geometries: input.geometries.map(geomifyGeometry)}
: input.type === "Point" || input.type === "MultiPoint" ? {type: input.type, coordinates: input.coordinates}
: {type: input.type, arcs: input.coordinates}; // TODO Check for unknown types?
if (input.bbox != null) output.bbox = input.bbox;
return output;
}
var prequantize = function(objects, bbox, n) {
var x0 = bbox[0],
y0 = bbox[1],
x1 = bbox[2],
y1 = bbox[3],
kx = x1 - x0 ? (n - 1) / (x1 - x0) : 1,
ky = y1 - y0 ? (n - 1) / (y1 - y0) : 1;
function quantizePoint(input) {
return [Math.round((input[0] - x0) * kx), Math.round((input[1] - y0) * ky)];
}
function quantizePoints(input, m) {
var i = -1,
j = 0,
n = input.length,
output = new Array(n), // pessimistic
pi,
px,
py,
x,
y;
while (++i < n) {
pi = input[i];
x = Math.round((pi[0] - x0) * kx);
y = Math.round((pi[1] - y0) * ky);
if (x !== px || y !== py) output[j++] = [px = x, py = y]; // non-coincident points
}
output.length = j;
while (j < m) j = output.push([output[0][0], output[0][1]]);
return output;
}
function quantizeLine(input) {
return quantizePoints(input, 2);
}
function quantizeRing(input) {
return quantizePoints(input, 4);
}
function quantizePolygon(input) {
return input.map(quantizeRing);
}
function quantizeGeometry(o) {
if (o != null && quantizeGeometryType.hasOwnProperty(o.type)) quantizeGeometryType[o.type](o);
}
var quantizeGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(quantizeGeometry); },
Point: function(o) { o.coordinates = quantizePoint(o.coordinates); },
MultiPoint: function(o) { o.coordinates = o.coordinates.map(quantizePoint); },
LineString: function(o) { o.arcs = quantizeLine(o.arcs); },
MultiLineString: function(o) { o.arcs = o.arcs.map(quantizeLine); },
Polygon: function(o) { o.arcs = quantizePolygon(o.arcs); },
MultiPolygon: function(o) { o.arcs = o.arcs.map(quantizePolygon); }
};
for (var key in objects) {
quantizeGeometry(objects[key]);
}
return {
scale: [1 / kx, 1 / ky],
translate: [x0, y0]
};
};
// Constructs the TopoJSON Topology for the specified hash of features.
// Each object in the specified hash must be a GeoJSON object,
// meaning FeatureCollection, a Feature or a geometry object.
var topology = function(objects, quantization) {
var bbox = bounds(objects = geometry$1(objects)),
transform = quantization > 0 && bbox && prequantize(objects, bbox, quantization),
topology = dedup$1(cut(extract(objects))),
coordinates = topology.coordinates,
indexByArc = hashmap(topology.arcs.length * 1.4, hashArc, equalArc);
objects = topology.objects; // for garbage collection
topology.bbox = bbox;
topology.arcs = topology.arcs.map(function(arc, i) {
indexByArc.set(arc, i);
return coordinates.slice(arc[0], arc[1] + 1);
});
delete topology.coordinates;
coordinates = null;
function indexGeometry(geometry) {
if (geometry && indexGeometryType.hasOwnProperty(geometry.type)) indexGeometryType[geometry.type](geometry);
}
var indexGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(indexGeometry); },
LineString: function(o) { o.arcs = indexArcs(o.arcs); },
MultiLineString: function(o) { o.arcs = o.arcs.map(indexArcs); },
Polygon: function(o) { o.arcs = o.arcs.map(indexArcs); },
MultiPolygon: function(o) { o.arcs = o.arcs.map(indexMultiArcs); }
};
function indexArcs(arc) {
var indexes = [];
do {
var index = indexByArc.get(arc);
indexes.push(arc[0] < arc[1] ? index : ~index);
} while (arc = arc.next);
return indexes;
}
function indexMultiArcs(arcs) {
return arcs.map(indexArcs);
}
for (var key in objects) {
indexGeometry(objects[key]);
}
if (transform) {
topology.transform = transform;
topology.arcs = delta(topology.arcs);
}
return topology;
};
function hashArc(arc) {
var i = arc[0], j = arc[1], t;
if (j < i) t = i, i = j, j = t;
return i + 31 * j;
}
function equalArc(arcA, arcB) {
var ia = arcA[0], ja = arcA[1],
ib = arcB[0], jb = arcB[1], t;
if (ja < ia) t = ia, ia = ja, ja = t;
if (jb < ib) t = ib, ib = jb, jb = t;
return ia === ib && ja === jb;
}
/**
* Merges all connected (non-forking, non-junctioning) line strings into single lineStrings.
* [LineString] -> LineString|MultiLineString
*
* @param {FeatureCollection} geojson Lines to dissolve
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.mutate=false] Prevent input mutation
* @returns {Feature} Dissolved lines
*/
function lineDissolve(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var mutate = options.mutate;
// Validation
if (getType(geojson) !== 'FeatureCollection') throw new Error('geojson must be a FeatureCollection');
if (!geojson.features.length) throw new Error('geojson is empty');
// Clone geojson to avoid side effects
if (mutate === false || mutate === undefined) geojson = clone(geojson);
var result = [];
var lastLine = lineReduce(geojson, function (previousLine, currentLine) {
// Attempt to merge this LineString with the other LineStrings, updating
// the reference as it is merged with others and grows.
var merged = mergeLineStrings(previousLine, currentLine);
// Accumulate the merged LineString
if (merged) return merged;
// Put the unmerged LineString back into the list
else {
result.push(previousLine);
return currentLine;
}
});
// Append the last line
if (lastLine) result.push(lastLine);
// Return null if no lines were dissolved
if (!result.length) return null;
// Return LineString if only 1 line was dissolved
else if (result.length === 1) return result[0];
// Return MultiLineString if multiple lines were dissolved with gaps
else return multiLineString(result.map(function (line) { return line.coordinates; }));
}
// [Number, Number] -> String
function coordId(coord) {
return coord[0].toString() + ',' + coord[1].toString();
}
/**
* LineString, LineString -> LineString
*
* @private
* @param {Feature} a line1
* @param {Feature} b line2
* @returns {Feature|null} Merged LineString
*/
function mergeLineStrings(a, b) {
var coords1 = a.geometry.coordinates;
var coords2 = b.geometry.coordinates;
var s1 = coordId(coords1[0]);
var e1 = coordId(coords1[coords1.length - 1]);
var s2 = coordId(coords2[0]);
var e2 = coordId(coords2[coords2.length - 1]);
// TODO: handle case where more than one of these is true!
var coords;
if (s1 === e2) coords = coords2.concat(coords1.slice(1));
else if (s2 === e1) coords = coords1.concat(coords2.slice(1));
else if (s1 === s2) coords = coords1.slice(1).reverse().concat(coords2);
else if (e1 === e2) coords = coords1.concat(coords2.reverse().slice(1));
else return null;
return lineString(coords);
}
/**
* Dissolves all overlapping (Multi)Polygon
*
* @param {FeatureCollection} geojson Polygons to dissolve
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.mutate=false] Prevent input mutation
* @returns {Feature} Dissolved Polygons
*/
function polygonDissolve(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var mutate = options.mutate;
// Validation
if (getType(geojson) !== 'FeatureCollection') throw new Error('geojson must be a FeatureCollection');
if (!geojson.features.length) throw new Error('geojson is empty');
// Clone geojson to avoid side effects
// Topojson modifies in place, so we need to deep clone first
if (mutate === false || mutate === undefined) geojson = clone(geojson);
var geoms = [];
flattenEach(geojson, function (feature$$1) {
geoms.push(feature$$1.geometry);
});
var topo = topology({geoms: geometryCollection(geoms).geometry});
return merge(topo, topo.objects.geoms.geometries);
}
/**
* Transform function: attempts to dissolve geojson objects where possible
* [GeoJSON] -> GeoJSON geometry
*
* @private
* @param {FeatureCollection} geojson Features to dissolved
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.mutate=false] Prevent input mutation
* @returns {Feature} Dissolved Features
*/
function dissolve(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var mutate = options.mutate;
// Validation
if (getType(geojson) !== 'FeatureCollection') throw new Error('geojson must be a FeatureCollection');
if (!geojson.features.length) throw new Error('geojson is empty');
// Clone geojson to avoid side effects
// Topojson modifies in place, so we need to deep clone first
if (mutate === false || mutate === undefined) geojson = clone(geojson);
// Assert homogenity
var type = getHomogenousType(geojson);
if (!type) throw new Error('geojson must be homogenous');
switch (type) {
case 'LineString':
return lineDissolve(geojson, options);
case 'Polygon':
return polygonDissolve(geojson, options);
default:
throw new Error(type + ' is not supported');
}
}
/**
* Checks if GeoJSON is Homogenous
*
* @private
* @param {GeoJSON} geojson GeoJSON
* @returns {string|null} Homogenous type or null if multiple types
*/
function getHomogenousType(geojson) {
var types = {};
flattenEach(geojson, function (feature$$1) {
types[feature$$1.geometry.type] = true;
});
var keys = Object.keys(types);
if (keys.length === 1) return keys[0];
return null;
}
/**
* Takes a set of {@link Point|points} and returns a concave hull Polygon or MultiPolygon.
* Internally, this uses [turf-tin](https://github.com/Turfjs/turf-tin) to generate geometries.
*
* @name concave
* @param {FeatureCollection} points input points
* @param {Object} [options={}] Optional parameters
* @param {number} [options.maxEdge=Infinity] the length (in 'units') of an edge necessary for part of the hull to become concave.
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @returns {Feature<(Polygon|MultiPolygon)>|null} a concave hull (null value is returned if unable to compute hull)
* @example
* var points = turf.featureCollection([
* turf.point([-63.601226, 44.642643]),
* turf.point([-63.591442, 44.651436]),
* turf.point([-63.580799, 44.648749]),
* turf.point([-63.573589, 44.641788]),
* turf.point([-63.587665, 44.64533]),
* turf.point([-63.595218, 44.64765])
* ]);
* var options = {units: 'miles', maxEdge: 1};
*
* var hull = turf.concave(points, options);
*
* //addToMap
* var addToMap = [points, hull]
*/
function concave(points$$1, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
// validation
if (!points$$1) throw new Error('points is required');
var maxEdge = options.maxEdge || Infinity;
if (!isNumber(maxEdge)) throw new Error('maxEdge is invalid');
var cleaned = removeDuplicates(points$$1);
var tinPolys = tin(cleaned);
// calculate length of all edges and area of all triangles
// and remove triangles that fail the max length test
tinPolys.features = tinPolys.features.filter(function (triangle) {
var pt1 = triangle.geometry.coordinates[0][0];
var pt2 = triangle.geometry.coordinates[0][1];
var pt3 = triangle.geometry.coordinates[0][2];
var dist1 = distance(pt1, pt2, options);
var dist2 = distance(pt2, pt3, options);
var dist3 = distance(pt1, pt3, options);
return (dist1 <= maxEdge && dist2 <= maxEdge && dist3 <= maxEdge);
});
if (tinPolys.features.length < 1) return null;
// merge the adjacent triangles
var dissolved = dissolve(tinPolys, options);
// geojson-dissolve always returns a MultiPolygon
if (dissolved.coordinates.length === 1) {
dissolved.coordinates = dissolved.coordinates[0];
dissolved.type = 'Polygon';
}
return feature(dissolved);
}
/**
* Removes duplicated points in a collection returning a new collection
*
* @private
* @param {FeatureCollection} points to be cleaned
* @returns {FeatureCollection} cleaned set of points
*/
function removeDuplicates(points$$1) {
var cleaned = [];
var existing = {};
featureEach(points$$1, function (pt) {
if (!pt.geometry) return;
var key = pt.geometry.coordinates.join('-');
if (!existing.hasOwnProperty(key)) {
cleaned.push(pt);
existing[key] = true;
}
});
return featureCollection(cleaned);
}
/**
* Merges a specified property from a FeatureCollection of points into a
* FeatureCollection of polygons. Given an `inProperty` on points and an `outProperty`
* for polygons, this finds every point that lies within each polygon, collects the
* `inProperty` values from those points, and adds them as an array to `outProperty`
* on the polygon.
*
* @name collect
* @param {FeatureCollection} polygons polygons with values on which to aggregate
* @param {FeatureCollection} points points to be aggregated
* @param {string} inProperty property to be nested from
* @param {string} outProperty property to be nested into
* @returns {FeatureCollection} polygons with properties listed based on `outField`
* @example
* var poly1 = turf.polygon([[[0,0],[10,0],[10,10],[0,10],[0,0]]]);
* var poly2 = turf.polygon([[[10,0],[20,10],[20,20],[20,0],[10,0]]]);
* var polyFC = turf.featureCollection([poly1, poly2]);
* var pt1 = turf.point([5,5], {population: 200});
* var pt2 = turf.point([1,3], {population: 600});
* var pt3 = turf.point([14,2], {population: 100});
* var pt4 = turf.point([13,1], {population: 200});
* var pt5 = turf.point([19,7], {population: 300});
* var pointFC = turf.featureCollection([pt1, pt2, pt3, pt4, pt5]);
* var collected = turf.collect(polyFC, pointFC, 'population', 'values');
* var values = collected.features[0].properties.values
* //=values => [200, 600]
*
* //addToMap
* var addToMap = [pointFC, collected]
*/
function collect(polygons, points, inProperty, outProperty) {
var rtree = rbush_1(6);
var treeItems = points.features.map(function (item) {
return {
minX: item.geometry.coordinates[0],
minY: item.geometry.coordinates[1],
maxX: item.geometry.coordinates[0],
maxY: item.geometry.coordinates[1],
property: item.properties[inProperty]
};
});
rtree.load(treeItems);
polygons.features.forEach(function (poly) {
if (!poly.properties) {
poly.properties = {};
}
var bbox$$1 = bbox(poly);
var potentialPoints = rtree.search({minX: bbox$$1[0], minY: bbox$$1[1], maxX: bbox$$1[2], maxY: bbox$$1[3]});
var values = [];
potentialPoints.forEach(function (pt) {
if (booleanPointInPolygon([pt.minX, pt.minY], poly)) {
values.push(pt.property);
}
});
poly.properties[outProperty] = values;
});
return polygons;
}
/**
* Takes input features and flips all of their coordinates from `[x, y]` to `[y, x]`.
*
* @name flip
* @param {GeoJSON} geojson input features
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} a feature or set of features of the same type as `input` with flipped coordinates
* @example
* var serbia = turf.point([20.566406, 43.421008]);
*
* var saudiArabia = turf.flip(serbia);
*
* //addToMap
* var addToMap = [serbia, saudiArabia];
*/
function flip(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var mutate = options.mutate;
if (!geojson) throw new Error('geojson is required');
// ensure that we don't modify features in-place and changes to the
// output do not change the previous feature, including changes to nested
// properties.
if (mutate === false || mutate === undefined) geojson = clone(geojson);
coordEach(geojson, function (coord) {
var x = coord[0];
var y = coord[1];
coord[0] = y;
coord[1] = x;
});
return geojson;
}
/**
* Removes redundant coordinates from any GeoJSON Geometry.
*
* @name cleanCoords
* @param {Geometry|Feature} geojson Feature or Geometry
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated
* @returns {Geometry|Feature} the cleaned input Feature/Geometry
* @example
* var line = turf.lineString([[0, 0], [0, 2], [0, 5], [0, 8], [0, 8], [0, 10]]);
* var multiPoint = turf.multiPoint([[0, 0], [0, 0], [2, 2]]);
*
* turf.cleanCoords(line).geometry.coordinates;
* //= [[0, 0], [0, 10]]
*
* turf.cleanCoords(multiPoint).geometry.coordinates;
* //= [[0, 0], [2, 2]]
*/
function cleanCoords(geojson, options) {
// Backwards compatible with v4.0
var mutate = (typeof options === 'object') ? options.mutate : options;
if (!geojson) throw new Error('geojson is required');
var type = getType(geojson);
// Store new "clean" points in this Array
var newCoords = [];
switch (type) {
case 'LineString':
newCoords = cleanLine(geojson);
break;
case 'MultiLineString':
case 'Polygon':
getCoords(geojson).forEach(function (line) {
newCoords.push(cleanLine(line));
});
break;
case 'MultiPolygon':
getCoords(geojson).forEach(function (polygons$$1) {
var polyPoints = [];
polygons$$1.forEach(function (ring) {
polyPoints.push(cleanLine(ring));
});
newCoords.push(polyPoints);
});
break;
case 'Point':
return geojson;
case 'MultiPoint':
var existing = {};
getCoords(geojson).forEach(function (coord) {
var key = coord.join('-');
if (!existing.hasOwnProperty(key)) {
newCoords.push(coord);
existing[key] = true;
}
});
break;
default:
throw new Error(type + ' geometry not supported');
}
// Support input mutation
if (geojson.coordinates) {
if (mutate === true) {
geojson.coordinates = newCoords;
return geojson;
}
return {type: type, coordinates: newCoords};
} else {
if (mutate === true) {
geojson.geometry.coordinates = newCoords;
return geojson;
}
return feature({type: type, coordinates: newCoords}, geojson.properties, geojson.bbox, geojson.id);
}
}
/**
* Clean Coords
*
* @private
* @param {Array|LineString} line Line
* @returns {Array} Cleaned coordinates
*/
function cleanLine(line) {
var points$$1 = getCoords(line);
// handle "clean" segment
if (points$$1.length === 2 && !equals(points$$1[0], points$$1[1])) return points$$1;
var prevPoint, point$$1, nextPoint;
var newPoints = [];
var secondToLast = points$$1.length - 1;
newPoints.push(points$$1[0]);
for (var i = 1; i < secondToLast; i++) {
prevPoint = points$$1[i - 1];
point$$1 = points$$1[i];
nextPoint = points$$1[i + 1];
if (!isPointOnLineSegment(prevPoint, nextPoint, point$$1)) {
newPoints.push(point$$1);
}
}
newPoints.push(nextPoint);
return newPoints;
}
/**
* Compares two points and returns if they are equals
*
* @private
* @param {Position} pt1 point
* @param {Position} pt2 point
* @returns {boolean} true if they are equals
*/
function equals(pt1, pt2) {
return pt1[0] === pt2[0] && pt1[1] === pt2[1];
}
/**
* Returns if `point` is on the segment between `start` and `end`.
* Borrowed from `@turf/boolean-point-on-line` to speed up the evaluation (instead of using the module as dependency)
*
* @private
* @param {Position} start coord pair of start of line
* @param {Position} end coord pair of end of line
* @param {Position} point coord pair of point to check
* @returns {boolean} true/false
*/
function isPointOnLineSegment(start, end, point$$1) {
var x = point$$1[0], y = point$$1[1];
var startX = start[0], startY = start[1];
var endX = end[0], endY = end[1];
var dxc = x - startX;
var dyc = y - startY;
var dxl = endX - startX;
var dyl = endY - startY;
var cross = dxc * dyl - dyc * dxl;
if (cross !== 0) return false;
else if (Math.abs(dxl) >= Math.abs(dyl)) return dxl > 0 ? startX <= x && x <= endX : endX <= x && x <= startX;
else return dyl > 0 ? startY <= y && y <= endY : endY <= y && y <= startY;
}
/*
(c) 2013, Vladimir Agafonkin
Simplify.js, a high-performance JS polyline simplification library
mourner.github.io/simplify-js
*/
// to suit your point format, run search/replace for '.x' and '.y';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)
// square distance between 2 points
function getSqDist$1(p1, p2) {
var dx = p1.x - p2.x,
dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y;
if (dx !== 0 || dy !== 0) {
var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format
// basic distance-based simplification
function simplifyRadialDist(points$$1, sqTolerance) {
var prevPoint = points$$1[0],
newPoints = [prevPoint],
point$$1;
for (var i = 1, len = points$$1.length; i < len; i++) {
point$$1 = points$$1[i];
if (getSqDist$1(point$$1, prevPoint) > sqTolerance) {
newPoints.push(point$$1);
prevPoint = point$$1;
}
}
if (prevPoint !== point$$1) newPoints.push(point$$1);
return newPoints;
}
function simplifyDPStep(points$$1, first, last, sqTolerance, simplified) {
var maxSqDist = sqTolerance,
index;
for (var i = first + 1; i < last; i++) {
var sqDist = getSqSegDist(points$$1[i], points$$1[first], points$$1[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) simplifyDPStep(points$$1, first, index, sqTolerance, simplified);
simplified.push(points$$1[index]);
if (last - index > 1) simplifyDPStep(points$$1, index, last, sqTolerance, simplified);
}
}
// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(points$$1, sqTolerance) {
var last = points$$1.length - 1;
var simplified = [points$$1[0]];
simplifyDPStep(points$$1, 0, last, sqTolerance, simplified);
simplified.push(points$$1[last]);
return simplified;
}
// both algorithms combined for awesome performance
function simplify$2(points$$1, tolerance, highestQuality) {
if (points$$1.length <= 2) return points$$1;
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points$$1 = highestQuality ? points$$1 : simplifyRadialDist(points$$1, sqTolerance);
points$$1 = simplifyDouglasPeucker(points$$1, sqTolerance);
return points$$1;
}
/**
* Takes a {@link GeoJSON} object and returns a simplified version. Internally uses
* [simplify-js](http://mourner.github.io/simplify-js/) to perform simplification using the Ramer-Douglas-Peucker algorithm.
*
* @name simplify
* @param {GeoJSON} geojson object to be simplified
* @param {Object} [options={}] Optional parameters
* @param {number} [options.tolerance=1] simplification tolerance
* @param {boolean} [options.highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} a simplified GeoJSON
* @example
* var geojson = turf.polygon([[
* [-70.603637, -33.399918],
* [-70.614624, -33.395332],
* [-70.639343, -33.392466],
* [-70.659942, -33.394759],
* [-70.683975, -33.404504],
* [-70.697021, -33.419406],
* [-70.701141, -33.434306],
* [-70.700454, -33.446339],
* [-70.694274, -33.458369],
* [-70.682601, -33.465816],
* [-70.668869, -33.472117],
* [-70.646209, -33.473835],
* [-70.624923, -33.472117],
* [-70.609817, -33.468107],
* [-70.595397, -33.458369],
* [-70.587158, -33.442901],
* [-70.587158, -33.426283],
* [-70.590591, -33.414248],
* [-70.594711, -33.406224],
* [-70.603637, -33.399918]
* ]]);
* var options = {tolerance: 0.01, highQuality: false};
* var simplified = turf.simplify(geojson, options);
*
* //addToMap
* var addToMap = [geojson, simplified]
*/
function simplify(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var tolerance = options.tolerance !== undefined ? options.tolerance : 1;
var highQuality = options.highQuality || false;
var mutate = options.mutate || false;
if (!geojson) throw new Error('geojson is required');
if (tolerance && tolerance < 0) throw new Error('invalid tolerance');
// Clone geojson to avoid side effects
if (mutate !== true) geojson = clone(geojson);
geomEach(geojson, function (geom) {
simplifyGeom(geom, tolerance, highQuality);
});
return geojson;
}
/**
* Simplifies a feature's coordinates
*
* @private
* @param {Geometry} geometry to be simplified
* @param {number} [tolerance=1] simplification tolerance
* @param {boolean} [highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
* @returns {Geometry} output
*/
function simplifyGeom(geometry$$1, tolerance, highQuality) {
var type = geometry$$1.type;
// "unsimplyfiable" geometry types
if (type === 'Point' || type === 'MultiPoint') return geometry$$1;
// Remove any extra coordinates
cleanCoords(geometry$$1, true);
var coordinates = geometry$$1.coordinates;
switch (type) {
case 'LineString':
geometry$$1['coordinates'] = simplifyLine(coordinates, tolerance, highQuality);
break;
case 'MultiLineString':
geometry$$1['coordinates'] = coordinates.map(function (lines) {
return simplifyLine(lines, tolerance, highQuality);
});
break;
case 'Polygon':
geometry$$1['coordinates'] = simplifyPolygon(coordinates, tolerance, highQuality);
break;
case 'MultiPolygon':
geometry$$1['coordinates'] = coordinates.map(function (rings) {
return simplifyPolygon(rings, tolerance, highQuality);
});
}
return geometry$$1;
}
/**
* Simplifies the coordinates of a LineString with simplify-js
*
* @private
* @param {Array} coordinates to be processed
* @param {number} tolerance simplification tolerance
* @param {boolean} highQuality whether or not to spend more time to create a higher-quality
* @returns {Array>} simplified coords
*/
function simplifyLine(coordinates, tolerance, highQuality) {
return simplify$2(coordinates.map(function (coord) {
return {x: coord[0], y: coord[1], z: coord[2]};
}), tolerance, highQuality).map(function (coords) {
return (coords.z) ? [coords.x, coords.y, coords.z] : [coords.x, coords.y];
});
}
/**
* Simplifies the coordinates of a Polygon with simplify-js
*
* @private
* @param {Array} coordinates to be processed
* @param {number} tolerance simplification tolerance
* @param {boolean} highQuality whether or not to spend more time to create a higher-quality
* @returns {Array>>} simplified coords
*/
function simplifyPolygon(coordinates, tolerance, highQuality) {
return coordinates.map(function (ring) {
var pts = ring.map(function (coord) {
return {x: coord[0], y: coord[1]};
});
if (pts.length < 4) {
throw new Error('invalid polygon');
}
var simpleRing = simplify$2(pts, tolerance, highQuality).map(function (coords) {
return [coords.x, coords.y];
});
//remove 1 percent of tolerance until enough points to make a triangle
while (!checkValidity(simpleRing)) {
tolerance -= tolerance * 0.01;
simpleRing = simplify$2(pts, tolerance, highQuality).map(function (coords) {
return [coords.x, coords.y];
});
}
if (
(simpleRing[simpleRing.length - 1][0] !== simpleRing[0][0]) ||
(simpleRing[simpleRing.length - 1][1] !== simpleRing[0][1])) {
simpleRing.push(simpleRing[0]);
}
return simpleRing;
});
}
/**
* Returns true if ring has at least 3 coordinates and its first coordinate is the same as its last
*
* @private
* @param {Array} ring coordinates to be checked
* @returns {boolean} true if valid
*/
function checkValidity(ring) {
if (ring.length < 3) return false;
//if the last point is the same as the first, it's not a triangle
return !(ring.length === 3 && ((ring[2][0] === ring[0][0]) && (ring[2][1] === ring[0][1])));
}
/* eslint-disable */
/**
* BezierSpline
* https://github.com/leszekr/bezier-spline-js
*
* @private
* @copyright
* Copyright (c) 2013 Leszek Rybicki
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var Spline = function (options) {
this.points = options.points || [];
this.duration = options.duration || 10000;
this.sharpness = options.sharpness || 0.85;
this.centers = [];
this.controls = [];
this.stepLength = options.stepLength || 60;
this.length = this.points.length;
this.delay = 0;
// this is to ensure compatibility with the 2d version
for (var i = 0; i < this.length; i++) this.points[i].z = this.points[i].z || 0;
for (var i = 0; i < this.length - 1; i++) {
var p1 = this.points[i];
var p2 = this.points[i + 1];
this.centers.push({
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2,
z: (p1.z + p2.z) / 2
});
}
this.controls.push([this.points[0], this.points[0]]);
for (var i = 0; i < this.centers.length - 1; i++) {
var p1 = this.centers[i];
var p2 = this.centers[i + 1];
var dx = this.points[i + 1].x - (this.centers[i].x + this.centers[i + 1].x) / 2;
var dy = this.points[i + 1].y - (this.centers[i].y + this.centers[i + 1].y) / 2;
var dz = this.points[i + 1].z - (this.centers[i].y + this.centers[i + 1].z) / 2;
this.controls.push([{
x: (1.0 - this.sharpness) * this.points[i + 1].x + this.sharpness * (this.centers[i].x + dx),
y: (1.0 - this.sharpness) * this.points[i + 1].y + this.sharpness * (this.centers[i].y + dy),
z: (1.0 - this.sharpness) * this.points[i + 1].z + this.sharpness * (this.centers[i].z + dz)},
{
x: (1.0 - this.sharpness) * this.points[i + 1].x + this.sharpness * (this.centers[i + 1].x + dx),
y: (1.0 - this.sharpness) * this.points[i + 1].y + this.sharpness * (this.centers[i + 1].y + dy),
z: (1.0 - this.sharpness) * this.points[i + 1].z + this.sharpness * (this.centers[i + 1].z + dz)}]);
}
this.controls.push([this.points[this.length - 1], this.points[this.length - 1]]);
this.steps = this.cacheSteps(this.stepLength);
return this;
};
/*
Caches an array of equidistant (more or less) points on the curve.
*/
Spline.prototype.cacheSteps = function (mindist) {
var steps = [];
var laststep = this.pos(0);
steps.push(0);
for (var t = 0; t < this.duration; t += 10) {
var step = this.pos(t);
var dist = Math.sqrt((step.x - laststep.x) * (step.x - laststep.x) + (step.y - laststep.y) * (step.y - laststep.y) + (step.z - laststep.z) * (step.z - laststep.z));
if (dist > mindist) {
steps.push(t);
laststep = step;
}
}
return steps;
};
/*
returns angle and speed in the given point in the curve
*/
Spline.prototype.vector = function (t) {
var p1 = this.pos(t + 10);
var p2 = this.pos(t - 10);
return {
angle:180 * Math.atan2(p1.y - p2.y, p1.x - p2.x) / 3.14,
speed:Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y) + (p2.z - p1.z) * (p2.z - p1.z))
};
};
/*
Gets the position of the point, given time.
WARNING: The speed is not constant. The time it takes between control points is constant.
For constant speed, use Spline.steps[i];
*/
Spline.prototype.pos = function (time) {
function bezier(t, p1, c1, c2, p2) {
var B = function (t) {
var t2 = t * t, t3 = t2 * t;
return [(t3), (3 * t2 * (1 - t)), (3 * t * (1 - t) * (1 - t)), ((1 - t) * (1 - t) * (1 - t))];
};
var b = B(t);
var pos = {
x : p2.x * b[0] + c2.x * b[1] + c1.x * b[2] + p1.x * b[3],
y : p2.y * b[0] + c2.y * b[1] + c1.y * b[2] + p1.y * b[3],
z : p2.z * b[0] + c2.z * b[1] + c1.z * b[2] + p1.z * b[3]
};
return pos;
}
var t = time - this.delay;
if (t < 0) t = 0;
if (t > this.duration) t = this.duration - 1;
//t = t-this.delay;
var t2 = (t) / this.duration;
if (t2 >= 1) return this.points[this.length - 1];
var n = Math.floor((this.points.length - 1) * t2);
var t1 = (this.length - 1) * t2 - n;
return bezier(t1, this.points[n], this.controls[n][1], this.controls[n + 1][0], this.points[n + 1]);
};
/**
* Takes a {@link LineString|line} and returns a curved version
* by applying a [Bezier spline](http://en.wikipedia.org/wiki/B%C3%A9zier_spline)
* algorithm.
*
* The bezier spline implementation is by [Leszek Rybicki](http://leszek.rybicki.cc/).
*
* @name bezierSpline
* @param {Feature} line input LineString
* @param {Object} [options={}] Optional parameters
* @param {number} [options.resolution=10000] time in milliseconds between points
* @param {number} [options.sharpness=0.85] a measure of how curvy the path should be between splines
* @returns {Feature} curved line
* @example
* var line = turf.lineString([
* [-76.091308, 18.427501],
* [-76.695556, 18.729501],
* [-76.552734, 19.40443],
* [-74.61914, 19.134789],
* [-73.652343, 20.07657],
* [-73.157958, 20.210656]
* ]);
*
* var curved = turf.bezierSpline(line);
*
* //addToMap
* var addToMap = [line, curved]
* curved.properties = { stroke: '#0F0' };
*/
function bezier(line, options) {
// Optional params
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var resolution = options.resolution || 10000;
var sharpness = options.sharpness || 0.85;
// validation
if (!line) throw new Error('line is required');
if (!isNumber(resolution)) throw new Error('resolution must be an number');
if (!isNumber(sharpness)) throw new Error('sharpness must be an number');
var coords = [];
var spline = new Spline({
points: getGeom(line).coordinates.map(function (pt) {
return {x: pt[0], y: pt[1]};
}),
duration: resolution,
sharpness: sharpness
});
for (var i = 0; i < spline.duration; i += 10) {
var pos = spline.pos(i);
if (Math.floor(i / 100) % 2 === 0) {
coords.push([pos.x, pos.y]);
}
}
return lineString(coords, line.properties);
}
/**
* Takes a set of {@link Point|points} and a set of {@link Polygon|polygons} and performs a spatial join.
*
* @name tag
* @param {FeatureCollection} points input points
* @param {FeatureCollection} polygons input polygons
* @param {string} field property in `polygons` to add to joined {} features
* @param {string} outField property in `points` in which to store joined property from `polygons`
* @returns {FeatureCollection} points with `containingPolyId` property containing values from `polyId`
* @example
* var pt1 = turf.point([-77, 44]);
* var pt2 = turf.point([-77, 38]);
* var poly1 = turf.polygon([[
* [-81, 41],
* [-81, 47],
* [-72, 47],
* [-72, 41],
* [-81, 41]
* ]], {pop: 3000});
* var poly2 = turf.polygon([[
* [-81, 35],
* [-81, 41],
* [-72, 41],
* [-72, 35],
* [-81, 35]
* ]], {pop: 1000});
*
* var points = turf.featureCollection([pt1, pt2]);
* var polygons = turf.featureCollection([poly1, poly2]);
*
* var tagged = turf.tag(points, polygons, 'pop', 'population');
*
* //addToMap
* var addToMap = [tagged, polygons]
*/
function tag(points, polygons, field, outField) {
// prevent mutations
points = clone(points);
polygons = clone(polygons);
featureEach(points, function (pt) {
if (!pt.properties) pt.properties = {};
featureEach(polygons, function (poly) {
if (pt.properties[outField] === undefined) {
if (booleanPointInPolygon(pt, poly)) pt.properties[outField] = poly.properties[field];
}
});
});
return points;
}
// http://stackoverflow.com/questions/11935175/sampling-a-random-subset-from-an-array
/**
* Takes a {@link FeatureCollection} and returns a FeatureCollection with given number of {@link Feature|features} at random.
*
* @name sample
* @param {FeatureCollection} featurecollection set of input features
* @param {number} num number of features to select
* @returns {FeatureCollection} a FeatureCollection with `n` features
* @example
* var points = turf.randomPoint(100, {bbox: [-80, 30, -60, 60]});
*
* var sample = turf.sample(points, 5);
*
* //addToMap
* var addToMap = [points, sample]
* turf.featureEach(sample, function (currentFeature) {
* currentFeature.properties['marker-size'] = 'large';
* currentFeature.properties['marker-color'] = '#000';
* });
*/
function sample(featurecollection, num) {
if (!featurecollection) throw new Error('featurecollection is required');
if (num === null || num === undefined) throw new Error('num is required');
if (typeof num !== 'number') throw new Error('num must be a number');
var outFC = featureCollection(getRandomSubarray(featurecollection.features, num));
return outFC;
}
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index;
while (i-- > min) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(min);
}
/**
* Takes a bbox and returns an equivalent {@link Polygon|polygon}.
*
* @name bboxPolygon
* @param {BBox} bbox extent in [minX, minY, maxX, maxY] order
* @returns {Feature} a Polygon representation of the bounding box
* @example
* var bbox = [0, 0, 10, 10];
*
* var poly = turf.bboxPolygon(bbox);
*
* //addToMap
* var addToMap = [poly]
*/
function bboxPolygon(bbox) {
validateBBox(bbox);
// Convert BBox positions to Numbers
// No performance loss for including Number()
// https://github.com/Turfjs/turf/issues/1119
var west = Number(bbox[0]);
var south = Number(bbox[1]);
var east = Number(bbox[2]);
var north = Number(bbox[3]);
if (bbox.length === 6) throw new Error('@turf/bbox-polygon does not support BBox with 6 positions');
var lowLeft = [west, south];
var topLeft = [west, north];
var topRight = [east, north];
var lowRight = [east, south];
return polygon([[
lowLeft,
lowRight,
topRight,
topLeft,
lowLeft
]]);
}
/**
* Takes any number of features and returns a rectangular {@link Polygon} that encompasses all vertices.
*
* @name envelope
* @param {GeoJSON} geojson input features
* @returns {Feature} a rectangular Polygon feature that encompasses all vertices
* @example
* var features = turf.featureCollection([
* turf.point([-75.343, 39.984], {"name": "Location A"}),
* turf.point([-75.833, 39.284], {"name": "Location B"}),
* turf.point([-75.534, 39.123], {"name": "Location C"})
* ]);
*
* var enveloped = turf.envelope(features);
*
* //addToMap
* var addToMap = [features, enveloped];
*/
function envelope(geojson) {
return bboxPolygon(bbox(geojson));
}
/**
* Takes a bounding box and calculates the minimum square bounding box that
* would contain the input.
*
* @name square
* @param {BBox} bbox extent in [west, south, east, north] order
* @returns {BBox} a square surrounding `bbox`
* @example
* var bbox = [-20, -20, -15, 0];
* var squared = turf.square(bbox);
*
* //addToMap
* var addToMap = [turf.bboxPolygon(bbox), turf.bboxPolygon(squared)]
*/
function square(bbox) {
var west = bbox[0];
var south = bbox[1];
var east = bbox[2];
var north = bbox[3];
var horizontalDistance = distance(bbox.slice(0, 2), [east, south]);
var verticalDistance = distance(bbox.slice(0, 2), [west, north]);
if (horizontalDistance >= verticalDistance) {
var verticalMidpoint = (south + north) / 2;
return [
west,
verticalMidpoint - ((east - west) / 2),
east,
verticalMidpoint + ((east - west) / 2)
];
} else {
var horizontalMidpoint = (west + east) / 2;
return [
horizontalMidpoint - ((north - south) / 2),
south,
horizontalMidpoint + ((north - south) / 2),
north
];
}
}
//http://en.wikipedia.org/wiki/Haversine_formula
//http://www.movable-type.co.uk/scripts/latlong.html
/**
* Takes a {@link Point} and calculates the location of a destination point given a distance in degrees, radians, miles, or kilometers; and bearing in degrees. This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature.
*
* @name destination
* @param {Coord} origin starting point
* @param {number} distance distance from the origin point
* @param {number} bearing ranging from -180 to 180
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] miles, kilometers, degrees, or radians
* @param {Object} [options.properties={}] Translate properties to Point
* @returns {Feature} destination point
* @example
* var point = turf.point([-75.343, 39.984]);
* var distance = 50;
* var bearing = 90;
* var options = {units: 'miles'};
*
* var destination = turf.destination(point, distance, bearing, options);
*
* //addToMap
* var addToMap = [point, destination]
* destination.properties['marker-color'] = '#f00';
* point.properties['marker-color'] = '#0f0';
*/
function destination(origin, distance, bearing, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var units = options.units;
var properties = options.properties;
// Handle input
var coordinates1 = getCoord(origin);
var longitude1 = degreesToRadians(coordinates1[0]);
var latitude1 = degreesToRadians(coordinates1[1]);
var bearing_rad = degreesToRadians(bearing);
var radians = lengthToRadians(distance, units);
// Main
var latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(radians) +
Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearing_rad));
var longitude2 = longitude1 + Math.atan2(Math.sin(bearing_rad) * Math.sin(radians) * Math.cos(latitude1),
Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2));
var lng = radiansToDegrees(longitude2);
var lat = radiansToDegrees(latitude2);
return point([lng, lat], properties);
}
/**
* Takes a {@link Point} and calculates the circle polygon given a radius in degrees, radians, miles, or kilometers; and steps for precision.
*
* @name circle
* @param {Feature|number[]} center center point
* @param {number} radius radius of the circle
* @param {Object} [options={}] Optional parameters
* @param {number} [options.steps=64] number of steps
* @param {string} [options.units='kilometers'] miles, kilometers, degrees, or radians
* @param {Object} [options.properties={}] properties
* @returns {Feature} circle polygon
* @example
* var center = [-75.343, 39.984];
* var radius = 5;
* var options = {steps: 10, units: 'kilometers', properties: {foo: 'bar'}};
* var circle = turf.circle(center, radius, options);
*
* //addToMap
* var addToMap = [turf.point(center), circle]
*/
function circle(center, radius, options) {
// Optional params
options = options || {};
var steps = options.steps || 64;
var properties = options.properties;
// validation
if (!center) throw new Error('center is required');
if (!radius) throw new Error('radius is required');
if (typeof options !== 'object') throw new Error('options must be an object');
if (typeof steps !== 'number') throw new Error('steps must be a number');
// default params
steps = steps || 64;
properties = properties || center.properties || {};
var coordinates = [];
for (var i = 0; i < steps; i++) {
coordinates.push(destination(center, radius, i * -360 / steps, options).geometry.coordinates);
}
coordinates.push(coordinates[0]);
return polygon([coordinates], properties);
}
//http://en.wikipedia.org/wiki/Haversine_formula
//http://www.movable-type.co.uk/scripts/latlong.html
/**
* Takes two {@link Point|points} and finds the geographic bearing between them,
* i.e. the angle measured in degrees from the north line (0 degrees)
*
* @name bearing
* @param {Coord} start starting Point
* @param {Coord} end ending Point
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.final=false] calculates the final bearing if true
* @returns {number} bearing in decimal degrees, between -180 and 180 degrees (positive clockwise)
* @example
* var point1 = turf.point([-75.343, 39.984]);
* var point2 = turf.point([-75.534, 39.123]);
*
* var bearing = turf.bearing(point1, point2);
*
* //addToMap
* var addToMap = [point1, point2]
* point1.properties['marker-color'] = '#f00'
* point2.properties['marker-color'] = '#0f0'
* point1.properties.bearing = bearing
*/
function bearing(start, end, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var final = options.final;
// Reverse calculation
if (final === true) return calculateFinalBearing(start, end);
var coordinates1 = getCoord(start);
var coordinates2 = getCoord(end);
var lon1 = degreesToRadians(coordinates1[0]);
var lon2 = degreesToRadians(coordinates2[0]);
var lat1 = degreesToRadians(coordinates1[1]);
var lat2 = degreesToRadians(coordinates2[1]);
var a = Math.sin(lon2 - lon1) * Math.cos(lat2);
var b = Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
return radiansToDegrees(Math.atan2(a, b));
}
/**
* Calculates Final Bearing
*
* @private
* @param {Coord} start starting Point
* @param {Coord} end ending Point
* @returns {number} bearing
*/
function calculateFinalBearing(start, end) {
// Swap start & end
var bear = bearing(end, start);
bear = (bear + 180) % 360;
return bear;
}
/**
* Takes two {@link Point|points} and returns a point midway between them.
* The midpoint is calculated geodesically, meaning the curvature of the earth is taken into account.
*
* @name midpoint
* @param {Coord} point1 first point
* @param {Coord} point2 second point
* @returns {Feature} a point midway between `pt1` and `pt2`
* @example
* var point1 = turf.point([144.834823, -37.771257]);
* var point2 = turf.point([145.14244, -37.830937]);
*
* var midpoint = turf.midpoint(point1, point2);
*
* //addToMap
* var addToMap = [point1, point2, midpoint];
* midpoint.properties['marker-color'] = '#f00';
*/
function midpoint(point1, point2) {
var dist = distance(point1, point2);
var heading = bearing(point1, point2);
var midpoint = destination(point1, dist / 2, heading);
return midpoint;
}
/**
* Takes a {@link Feature} or {@link FeatureCollection} and returns the absolute center point of all features.
*
* @name center
* @param {GeoJSON} geojson GeoJSON to be centered
* @param {Object} [options={}] Optional parameters
* @param {Object} [options.properties={}] an Object that is used as the {@link Feature}'s properties
* @returns {Feature} a Point feature at the absolute center point of all input features
* @example
* var features = turf.featureCollection([
* turf.point( [-97.522259, 35.4691]),
* turf.point( [-97.502754, 35.463455]),
* turf.point( [-97.508269, 35.463245])
* ]);
*
* var center = turf.center(features);
*
* //addToMap
* var addToMap = [features, center]
* center.properties['marker-size'] = 'large';
* center.properties['marker-color'] = '#000';
*/
function center(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var properties = options.properties;
// Input validation
if (!geojson) throw new Error('geojson is required');
var ext = bbox(geojson);
var x = (ext[0] + ext[2]) / 2;
var y = (ext[1] + ext[3]) / 2;
return point([x, y], properties);
}
/**
* Takes one or more features and calculates the centroid using the mean of all vertices.
* This lessens the effect of small islands and artifacts when calculating the centroid of a set of polygons.
*
* @name centroid
* @param {GeoJSON} geojson GeoJSON to be centered
* @param {Object} [properties={}] an Object that is used as the {@link Feature}'s properties
* @returns {Feature} the centroid of the input features
* @example
* var polygon = turf.polygon([[[-81, 41], [-88, 36], [-84, 31], [-80, 33], [-77, 39], [-81, 41]]]);
*
* var centroid = turf.centroid(polygon);
*
* //addToMap
* var addToMap = [polygon, centroid]
*/
function centroid(geojson, properties) {
var xSum = 0;
var ySum = 0;
var len = 0;
coordEach(geojson, function (coord) {
xSum += coord[0];
ySum += coord[1];
len++;
}, true);
return point([xSum / len, ySum / len], properties);
}
/**
* Takes any {@link Feature} or a {@link FeatureCollection} and returns its [center of mass](https://en.wikipedia.org/wiki/Center_of_mass) using this formula: [Centroid of Polygon](https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon).
*
* @name centerOfMass
* @param {GeoJSON} geojson GeoJSON to be centered
* @param {Object} [properties={}] an Object that is used as the {@link Feature}'s properties
* @returns {Feature} the center of mass
* @example
* var polygon = turf.polygon([[[-81, 41], [-88, 36], [-84, 31], [-80, 33], [-77, 39], [-81, 41]]]);
*
* var center = turf.centerOfMass(polygon);
*
* //addToMap
* var addToMap = [polygon, center]
*/
function centerOfMass(geojson, properties) {
switch (getType(geojson)) {
case 'Point':
return geojson;
case 'Polygon':
var coords = [];
coordEach(geojson, function (coord) {
coords.push(coord);
});
// First, we neutralize the feature (set it around coordinates [0,0]) to prevent rounding errors
// We take any point to translate all the points around 0
var centre = centroid(geojson, properties);
var translation = centre.geometry.coordinates;
var sx = 0;
var sy = 0;
var sArea = 0;
var i, pi, pj, xi, xj, yi, yj, a;
var neutralizedPoints = coords.map(function (point$$1) {
return [
point$$1[0] - translation[0],
point$$1[1] - translation[1]
];
});
for (i = 0; i < coords.length - 1; i++) {
// pi is the current point
pi = neutralizedPoints[i];
xi = pi[0];
yi = pi[1];
// pj is the next point (pi+1)
pj = neutralizedPoints[i + 1];
xj = pj[0];
yj = pj[1];
// a is the common factor to compute the signed area and the final coordinates
a = xi * yj - xj * yi;
// sArea is the sum used to compute the signed area
sArea += a;
// sx and sy are the sums used to compute the final coordinates
sx += (xi + xj) * a;
sy += (yi + yj) * a;
}
// Shape has no area: fallback on turf.centroid
if (sArea === 0) {
return centre;
} else {
// Compute the signed area, and factorize 1/6A
var area = sArea * 0.5;
var areaFactor = 1 / (6 * area);
// Compute the final coordinates, adding back the values that have been neutralized
return point([
translation[0] + areaFactor * sx,
translation[1] + areaFactor * sy
], properties);
}
default:
// Not a polygon: Compute the convex hull and work with that
var hull = convex(geojson);
if (hull) return centerOfMass(hull, properties);
// Hull is empty: fallback on the centroid
else return centroid(geojson, properties);
}
}
/**
* Combines a {@link FeatureCollection} of {@link Point}, {@link LineString}, or {@link Polygon} features
* into {@link MultiPoint}, {@link MultiLineString}, or {@link MultiPolygon} features.
*
* @name combine
* @param {FeatureCollection} fc a FeatureCollection of any type
* @returns {FeatureCollection} a FeatureCollection of corresponding type to input
* @example
* var fc = turf.featureCollection([
* turf.point([19.026432, 47.49134]),
* turf.point([19.074497, 47.509548])
* ]);
*
* var combined = turf.combine(fc);
*
* //addToMap
* var addToMap = [combined]
*/
function combine(fc) {
var groups = {
MultiPoint: {coordinates: [], properties: []},
MultiLineString: {coordinates: [], properties: []},
MultiPolygon: {coordinates: [], properties: []}
};
var multiMapping = Object.keys(groups).reduce(function (memo, item) {
memo[item.replace('Multi', '')] = item;
return memo;
}, {});
function addToGroup(feature$$1, key, multi) {
if (!multi) {
groups[key].coordinates.push(feature$$1.geometry.coordinates);
} else {
groups[key].coordinates = groups[key].coordinates.concat(feature$$1.geometry.coordinates);
}
groups[key].properties.push(feature$$1.properties);
}
featureEach(fc, function (feature$$1) {
if (!feature$$1.geometry) return;
if (groups[feature$$1.geometry.type]) {
addToGroup(feature$$1, feature$$1.geometry.type, true);
} else if (multiMapping[feature$$1.geometry.type]) {
addToGroup(feature$$1, multiMapping[feature$$1.geometry.type], false);
}
});
return featureCollection(Object.keys(groups)
.filter(function (key) {
return groups[key].coordinates.length;
})
.sort()
.map(function (key) {
var geometry$$1 = { type: key, coordinates: groups[key].coordinates };
var properties = { collectedProperties: groups[key].properties };
return feature(geometry$$1, properties);
}));
}
/**
* Takes a feature or set of features and returns all positions as {@link Point|points}.
*
* @name explode
* @param {GeoJSON} geojson input features
* @returns {FeatureCollection} points representing the exploded input features
* @throws {Error} if it encounters an unknown geometry type
* @example
* var polygon = turf.polygon([[[-81, 41], [-88, 36], [-84, 31], [-80, 33], [-77, 39], [-81, 41]]]);
*
* var explode = turf.explode(polygon);
*
* //addToMap
* var addToMap = [polygon, explode]
*/
function explode(geojson) {
var points$$1 = [];
if (geojson.type === 'FeatureCollection') {
featureEach(geojson, function (feature$$1) {
coordEach(feature$$1, function (coord) {
points$$1.push(point(coord, feature$$1.properties));
});
});
} else {
coordEach(geojson, function (coord) {
points$$1.push(point(coord, geojson.properties));
});
}
return featureCollection(points$$1);
}
var earcut_1 = earcut;
var default_1$2 = earcut;
function earcut(data, holeIndices, dim) {
dim = dim || 2;
var hasHoles = holeIndices && holeIndices.length,
outerLen = hasHoles ? holeIndices[0] * dim : data.length,
outerNode = linkedList(data, 0, outerLen, dim, true),
triangles = [];
if (!outerNode) return triangles;
var minX, minY, maxX, maxY, x, y, invSize;
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if (data.length > 80 * dim) {
minX = maxX = data[0];
minY = maxY = data[1];
for (var i = dim; i < outerLen; i += dim) {
x = data[i];
y = data[i + 1];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
}
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
invSize = Math.max(maxX - minX, maxY - minY);
invSize = invSize !== 0 ? 1 / invSize : 0;
}
earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
return triangles;
}
// create a circular doubly linked list from polygon points in the specified winding order
function linkedList(data, start, end, dim, clockwise) {
var i, last;
if (clockwise === (signedArea(data, start, end, dim) > 0)) {
for (i = start; i < end; i += dim) last = insertNode$1(i, data[i], data[i + 1], last);
} else {
for (i = end - dim; i >= start; i -= dim) last = insertNode$1(i, data[i], data[i + 1], last);
}
if (last && equals$1(last, last.next)) {
removeNode(last);
last = last.next;
}
return last;
}
// eliminate colinear or duplicate points
function filterPoints(start, end) {
if (!start) return start;
if (!end) end = start;
var p = start,
again;
do {
again = false;
if (!p.steiner && (equals$1(p, p.next) || area(p.prev, p, p.next) === 0)) {
removeNode(p);
p = end = p.prev;
if (p === p.next) break;
again = true;
} else {
p = p.next;
}
} while (again || p !== end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
var stop = ear,
prev, next;
// iterate through ears, slicing them one by one
while (ear.prev !== ear.next) {
prev = ear.prev;
next = ear.next;
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
// cut off the triangle
triangles.push(prev.i / dim);
triangles.push(ear.i / dim);
triangles.push(next.i / dim);
removeNode(ear);
// skipping the next vertice leads to less sliver triangles
ear = next.next;
stop = next.next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear === stop) {
// try filtering points and slicing again
if (!pass) {
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
// if this didn't work, try curing all small self-intersections locally
} else if (pass === 1) {
ear = cureLocalIntersections(ear, triangles, dim);
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass === 2) {
splitEarcut(ear, triangles, dim, minX, minY, invSize);
}
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
function isEar(ear) {
var a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
var p = ear.next.next;
while (p !== ear.prev) {
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.next;
}
return true;
}
function isEarHashed(ear, minX, minY, invSize) {
var a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
// z-order range for the current triangle bbox;
var minZ = zOrder(minTX, minTY, minX, minY, invSize),
maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);
// first look for points inside the triangle in increasing z-order
var p = ear.nextZ;
while (p && p.z <= maxZ) {
if (p !== ear.prev && p !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.nextZ;
}
// then look for points in decreasing z-order
p = ear.prevZ;
while (p && p.z >= minZ) {
if (p !== ear.prev && p !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
function cureLocalIntersections(start, triangles, dim) {
var p = start;
do {
var a = p.prev,
b = p.next.next;
if (!equals$1(a, b) && intersects$2(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
triangles.push(a.i / dim);
triangles.push(p.i / dim);
triangles.push(b.i / dim);
// remove two nodes involved
removeNode(p);
removeNode(p.next);
p = start = b;
}
p = p.next;
} while (p !== start);
return p;
}
// try splitting polygon into two and triangulate them independently
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
// look for a valid diagonal that divides the polygon into two
var a = start;
do {
var b = a.next.next;
while (b !== a.prev) {
if (a.i !== b.i && isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
var c = splitPolygon(a, b);
// filter colinear points around the cuts
a = filterPoints(a, a.next);
c = filterPoints(c, c.next);
// run earcut on each half
earcutLinked(a, triangles, dim, minX, minY, invSize);
earcutLinked(c, triangles, dim, minX, minY, invSize);
return;
}
b = b.next;
}
a = a.next;
} while (a !== start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles(data, holeIndices, outerNode, dim) {
var queue = [],
i, len, start, end, list;
for (i = 0, len = holeIndices.length; i < len; i++) {
start = holeIndices[i] * dim;
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
list = linkedList(data, start, end, dim, false);
if (list === list.next) list.steiner = true;
queue.push(getLeftmost(list));
}
queue.sort(compareX);
// process holes from left to right
for (i = 0; i < queue.length; i++) {
eliminateHole(queue[i], outerNode);
outerNode = filterPoints(outerNode, outerNode.next);
}
return outerNode;
}
function compareX(a, b) {
return a.x - b.x;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
function eliminateHole(hole, outerNode) {
outerNode = findHoleBridge(hole, outerNode);
if (outerNode) {
var b = splitPolygon(outerNode, hole);
filterPoints(b, b.next);
}
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
function findHoleBridge(hole, outerNode) {
var p = outerNode,
hx = hole.x,
hy = hole.y,
qx = -Infinity,
m;
// find a segment intersected by a ray from the hole's leftmost point to the left;
// segment's endpoint with lesser x will be potential connection point
do {
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
if (x <= hx && x > qx) {
qx = x;
if (x === hx) {
if (hy === p.y) return p;
if (hy === p.next.y) return p.next;
}
m = p.x < p.next.x ? p : p.next;
}
}
p = p.next;
} while (p !== outerNode);
if (!m) return null;
if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint
// look for points inside the triangle of hole point, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the point of the minimum angle with the ray as connection point
var stop = m,
mx = m.x,
my = m.y,
tanMin = Infinity,
tan;
p = m.next;
while (p !== stop) {
if (hx >= p.x && p.x >= mx && hx !== p.x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {
m = p;
tanMin = tan;
}
}
p = p.next;
}
return m;
}
// interlink polygon nodes in z-order
function indexCurve(start, minX, minY, invSize) {
var p = start;
do {
if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize);
p.prevZ = p.prev;
p.nextZ = p.next;
p = p.next;
} while (p !== start);
p.prevZ.nextZ = null;
p.prevZ = null;
sortLinked(p);
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
function sortLinked(list) {
var i, p, q, e, tail, numMerges, pSize, qSize,
inSize = 1;
do {
p = list;
list = null;
tail = null;
numMerges = 0;
while (p) {
numMerges++;
q = p;
pSize = 0;
for (i = 0; i < inSize; i++) {
pSize++;
q = q.nextZ;
if (!q) break;
}
qSize = inSize;
while (pSize > 0 || (qSize > 0 && q)) {
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
e = p;
p = p.nextZ;
pSize--;
} else {
e = q;
q = q.nextZ;
qSize--;
}
if (tail) tail.nextZ = e;
else list = e;
e.prevZ = tail;
tail = e;
}
p = q;
}
tail.nextZ = null;
inSize *= 2;
} while (numMerges > 1);
return list;
}
// z-order of a point given coords and inverse of the longer side of data bbox
function zOrder(x, y, minX, minY, invSize) {
// coords are transformed into non-negative 15-bit integer range
x = 32767 * (x - minX) * invSize;
y = 32767 * (y - minY) * invSize;
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
// find the leftmost node of a polygon ring
function getLeftmost(start) {
var p = start,
leftmost = start;
do {
if (p.x < leftmost.x) leftmost = p;
p = p.next;
} while (p !== start);
return leftmost;
}
// check if a point lies within a convex triangle
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal(a, b) {
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
}
// signed area of a triangle
function area(p, q, r) {
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
}
// check if two points are equal
function equals$1(p1, p2) {
return p1.x === p2.x && p1.y === p2.y;
}
// check if two segments intersect
function intersects$2(p1, q1, p2, q2) {
if ((equals$1(p1, q1) && equals$1(p2, q2)) ||
(equals$1(p1, q2) && equals$1(p2, q1))) return true;
return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
}
// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon(a, b) {
var p = a;
do {
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
intersects$2(p, p.next, a, b)) return true;
p = p.next;
} while (p !== a);
return false;
}
// check if a polygon diagonal is locally inside the polygon
function locallyInside(a, b) {
return area(a.prev, a, a.next) < 0 ?
area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
}
// check if the middle point of a polygon diagonal is inside the polygon
function middleInside(a, b) {
var p = a,
inside = false,
px = (a.x + b.x) / 2,
py = (a.y + b.y) / 2;
do {
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
inside = !inside;
p = p.next;
} while (p !== a);
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
function splitPolygon(a, b) {
var a2 = new Node(a.i, a.x, a.y),
b2 = new Node(b.i, b.x, b.y),
an = a.next,
bp = b.prev;
a.next = b;
b.prev = a;
a2.next = an;
an.prev = a2;
b2.next = a2;
a2.prev = b2;
bp.next = b2;
b2.prev = bp;
return b2;
}
// create a node and optionally link it with previous one (in a circular doubly linked list)
function insertNode$1(i, x, y, last) {
var p = new Node(i, x, y);
if (!last) {
p.prev = p;
p.next = p;
} else {
p.next = last.next;
p.prev = last;
last.next.prev = p;
last.next = p;
}
return p;
}
function removeNode(p) {
p.next.prev = p.prev;
p.prev.next = p.next;
if (p.prevZ) p.prevZ.nextZ = p.nextZ;
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
}
function Node(i, x, y) {
// vertice index in coordinates array
this.i = i;
// vertex coordinates
this.x = x;
this.y = y;
// previous and next vertice nodes in a polygon ring
this.prev = null;
this.next = null;
// z-order curve value
this.z = null;
// previous and next nodes in z-order
this.prevZ = null;
this.nextZ = null;
// indicates whether this is a steiner point
this.steiner = false;
}
// return a percentage difference between the polygon area and its triangulation area;
// used to verify correctness of triangulation
earcut.deviation = function (data, holeIndices, dim, triangles) {
var hasHoles = holeIndices && holeIndices.length;
var outerLen = hasHoles ? holeIndices[0] * dim : data.length;
var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
if (hasHoles) {
for (var i = 0, len = holeIndices.length; i < len; i++) {
var start = holeIndices[i] * dim;
var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
polygonArea -= Math.abs(signedArea(data, start, end, dim));
}
}
var trianglesArea = 0;
for (i = 0; i < triangles.length; i += 3) {
var a = triangles[i] * dim;
var b = triangles[i + 1] * dim;
var c = triangles[i + 2] * dim;
trianglesArea += Math.abs(
(data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
(data[a] - data[b]) * (data[c + 1] - data[a + 1]));
}
return polygonArea === 0 && trianglesArea === 0 ? 0 :
Math.abs((trianglesArea - polygonArea) / polygonArea);
};
function signedArea(data, start, end, dim) {
var sum = 0;
for (var i = start, j = end - dim; i < end; i += dim) {
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
j = i;
}
return sum;
}
// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
earcut.flatten = function (data) {
var dim = data[0][0].length,
result = {vertices: [], holes: [], dimensions: dim},
holeIndex = 0;
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
}
if (i > 0) {
holeIndex += data[i - 1].length;
result.holes.push(holeIndex);
}
}
return result;
};
earcut_1.default = default_1$2;
/**
* Tesselates a {@link Feature} into a {@link FeatureCollection} of triangles
* using [earcut](https://github.com/mapbox/earcut).
*
* @name tesselate
* @param {Feature} poly the polygon to tesselate
* @returns {FeatureCollection} a geometrycollection feature
* @example
* var poly = turf.polygon([[[11, 0], [22, 4], [31, 0], [31, 11], [21, 15], [11, 11], [11, 0]]]);
* var triangles = turf.tesselate(poly);
*
* //addToMap
* var addToMap = [poly, triangles]
*/
function tesselate(poly) {
if (!poly.geometry || (poly.geometry.type !== 'Polygon' && poly.geometry.type !== 'MultiPolygon')) {
throw new Error('input must be a Polygon or MultiPolygon');
}
var fc = {type: 'FeatureCollection', features: []};
if (poly.geometry.type === 'Polygon') {
fc.features = processPolygon(poly.geometry.coordinates);
} else {
poly.geometry.coordinates.forEach(function (coordinates) {
fc.features = fc.features.concat(processPolygon(coordinates));
});
}
return fc;
}
function processPolygon(coordinates) {
var data = flattenCoords(coordinates);
var dim = 2;
var result = earcut_1(data.vertices, data.holes, dim);
var features = [];
var vertices = [];
result.forEach(function (vert, i) {
var index = result[i];
vertices.push([data.vertices[index * dim], data.vertices[index * dim + 1]]);
});
for (var i = 0; i < vertices.length; i += 3) {
var coords = vertices.slice(i, i + 3);
coords.push(vertices[i]);
features.push(polygon([coords]));
}
return features;
}
function flattenCoords(data) {
var dim = data[0][0].length,
result = {vertices: [], holes: [], dimensions: dim},
holeIndex = 0;
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
}
if (i > 0) {
holeIndex += data[i - 1].length;
result.holes.push(holeIndex);
}
}
return result;
}
/**
* Takes a reference {@link Point|point} and a FeatureCollection of Features
* with Point geometries and returns the
* point from the FeatureCollection closest to the reference. This calculation
* is geodesic.
*
* @name nearestPoint
* @param {Coord} targetPoint the reference point
* @param {FeatureCollection} points against input point set
* @returns {Feature} the closest point in the set to the reference point
* @example
* var targetPoint = turf.point([28.965797, 41.010086], {"marker-color": "#0F0"});
* var points = turf.featureCollection([
* turf.point([28.973865, 41.011122]),
* turf.point([28.948459, 41.024204]),
* turf.point([28.938674, 41.013324])
* ]);
*
* var nearest = turf.nearestPoint(targetPoint, points);
*
* //addToMap
* var addToMap = [targetPoint, points, nearest];
* nearest.properties['marker-color'] = '#F00';
*/
function nearestPoint(targetPoint, points) {
// Input validation
if (!targetPoint) throw new Error('targetPoint is required');
if (!points) throw new Error('points is required');
var nearest;
var minDist = Infinity;
featureEach(points, function (pt, featureIndex) {
var distanceToPoint = distance(targetPoint, pt);
if (distanceToPoint < minDist) {
nearest = clone(pt);
nearest.properties.featureIndex = featureIndex;
nearest.properties.distanceToPoint = distanceToPoint;
minDist = distanceToPoint;
}
});
return nearest;
}
function quickselect$3(arr, k, left, right, compare) {
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare$2);
}
function quickselectStep(arr, k, left, right, compare) {
while (right > left) {
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
quickselectStep(arr, k, newLeft, newRight, compare);
}
var t = arr[k];
var i = left;
var j = right;
swap$1(arr, left, k);
if (compare(arr[right], t) > 0) swap$1(arr, left, right);
while (i < j) {
swap$1(arr, i, j);
i++;
j--;
while (compare(arr[i], t) < 0) i++;
while (compare(arr[j], t) > 0) j--;
}
if (compare(arr[left], t) === 0) swap$1(arr, left, j);
else {
j++;
swap$1(arr, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
function swap$1(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultCompare$2(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
function rbush$4(maxEntries, format) {
if (!(this instanceof rbush$4)) return new rbush$4(maxEntries, format);
// max entries in a node is 9 by default; min node fill is 40% for best performance
this._maxEntries = Math.max(4, maxEntries || 9);
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
if (format) {
this._initFormat(format);
}
this.clear();
}
rbush$4.prototype = {
all: function () {
return this._all(this.data, []);
},
search: function (bbox) {
var node = this.data,
result = [],
toBBox = this.toBBox;
if (!intersects$4(bbox, node)) return result;
var nodesToSearch = [],
i, len, child, childBBox;
while (node) {
for (i = 0, len = node.children.length; i < len; i++) {
child = node.children[i];
childBBox = node.leaf ? toBBox(child) : child;
if (intersects$4(bbox, childBBox)) {
if (node.leaf) result.push(child);
else if (contains$1(bbox, childBBox)) this._all(child, result);
else nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return result;
},
collides: function (bbox) {
var node = this.data,
toBBox = this.toBBox;
if (!intersects$4(bbox, node)) return false;
var nodesToSearch = [],
i, len, child, childBBox;
while (node) {
for (i = 0, len = node.children.length; i < len; i++) {
child = node.children[i];
childBBox = node.leaf ? toBBox(child) : child;
if (intersects$4(bbox, childBBox)) {
if (node.leaf || contains$1(bbox, childBBox)) return true;
nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return false;
},
load: function (data) {
if (!(data && data.length)) return this;
if (data.length < this._minEntries) {
for (var i = 0, len = data.length; i < len; i++) {
this.insert(data[i]);
}
return this;
}
// recursively build the tree with the given data from scratch using OMT algorithm
var node = this._build(data.slice(), 0, data.length - 1, 0);
if (!this.data.children.length) {
// save as is if tree is empty
this.data = node;
} else if (this.data.height === node.height) {
// split root if trees have the same height
this._splitRoot(this.data, node);
} else {
if (this.data.height < node.height) {
// swap trees if inserted one is bigger
var tmpNode = this.data;
this.data = node;
node = tmpNode;
}
// insert the small tree into the large tree at appropriate level
this._insert(node, this.data.height - node.height - 1, true);
}
return this;
},
insert: function (item) {
if (item) this._insert(item, this.data.height - 1);
return this;
},
clear: function () {
this.data = createNode$1([]);
return this;
},
remove: function (item, equalsFn) {
if (!item) return this;
var node = this.data,
bbox = this.toBBox(item),
path = [],
indexes = [],
i, parent, index, goingUp;
// depth-first iterative tree traversal
while (node || path.length) {
if (!node) { // go up
node = path.pop();
parent = path[path.length - 1];
i = indexes.pop();
goingUp = true;
}
if (node.leaf) { // check current node
index = findItem$1(item, node.children, equalsFn);
if (index !== -1) {
// item found, remove the item and condense tree upwards
node.children.splice(index, 1);
path.push(node);
this._condense(path);
return this;
}
}
if (!goingUp && !node.leaf && contains$1(node, bbox)) { // go down
path.push(node);
indexes.push(i);
i = 0;
parent = node;
node = node.children[0];
} else if (parent) { // go right
i++;
node = parent.children[i];
goingUp = false;
} else node = null; // nothing found
}
return this;
},
toBBox: function (item) { return item; },
compareMinX: compareNodeMinX$1,
compareMinY: compareNodeMinY$1,
toJSON: function () { return this.data; },
fromJSON: function (data) {
this.data = data;
return this;
},
_all: function (node, result) {
var nodesToSearch = [];
while (node) {
if (node.leaf) result.push.apply(result, node.children);
else nodesToSearch.push.apply(nodesToSearch, node.children);
node = nodesToSearch.pop();
}
return result;
},
_build: function (items, left, right, height) {
var N = right - left + 1,
M = this._maxEntries,
node;
if (N <= M) {
// reached leaf level; return leaf
node = createNode$1(items.slice(left, right + 1));
calcBBox$1(node, this.toBBox);
return node;
}
if (!height) {
// target height of the bulk-loaded tree
height = Math.ceil(Math.log(N) / Math.log(M));
// target number of root entries to maximize storage utilization
M = Math.ceil(N / Math.pow(M, height - 1));
}
node = createNode$1([]);
node.leaf = false;
node.height = height;
// split the items into M mostly square tiles
var N2 = Math.ceil(N / M),
N1 = N2 * Math.ceil(Math.sqrt(M)),
i, j, right2, right3;
multiSelect$1(items, left, right, N1, this.compareMinX);
for (i = left; i <= right; i += N1) {
right2 = Math.min(i + N1 - 1, right);
multiSelect$1(items, i, right2, N2, this.compareMinY);
for (j = i; j <= right2; j += N2) {
right3 = Math.min(j + N2 - 1, right2);
// pack each entry recursively
node.children.push(this._build(items, j, right3, height - 1));
}
}
calcBBox$1(node, this.toBBox);
return node;
},
_chooseSubtree: function (bbox, node, level, path) {
var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
while (true) {
path.push(node);
if (node.leaf || path.length - 1 === level) break;
minArea = minEnlargement = Infinity;
for (i = 0, len = node.children.length; i < len; i++) {
child = node.children[i];
area = bboxArea$1(child);
enlargement = enlargedArea$1(bbox, child) - area;
// choose entry with the least area enlargement
if (enlargement < minEnlargement) {
minEnlargement = enlargement;
minArea = area < minArea ? area : minArea;
targetNode = child;
} else if (enlargement === minEnlargement) {
// otherwise choose one with the smallest area
if (area < minArea) {
minArea = area;
targetNode = child;
}
}
}
node = targetNode || node.children[0];
}
return node;
},
_insert: function (item, level, isNode) {
var toBBox = this.toBBox,
bbox = isNode ? item : toBBox(item),
insertPath = [];
// find the best node for accommodating the item, saving all nodes along the path too
var node = this._chooseSubtree(bbox, this.data, level, insertPath);
// put the item into the node
node.children.push(item);
extend$1(node, bbox);
// split on node overflow; propagate upwards if necessary
while (level >= 0) {
if (insertPath[level].children.length > this._maxEntries) {
this._split(insertPath, level);
level--;
} else break;
}
// adjust bboxes along the insertion path
this._adjustParentBBoxes(bbox, insertPath, level);
},
// split overflowed node into two
_split: function (insertPath, level) {
var node = insertPath[level],
M = node.children.length,
m = this._minEntries;
this._chooseSplitAxis(node, m, M);
var splitIndex = this._chooseSplitIndex(node, m, M);
var newNode = createNode$1(node.children.splice(splitIndex, node.children.length - splitIndex));
newNode.height = node.height;
newNode.leaf = node.leaf;
calcBBox$1(node, this.toBBox);
calcBBox$1(newNode, this.toBBox);
if (level) insertPath[level - 1].children.push(newNode);
else this._splitRoot(node, newNode);
},
_splitRoot: function (node, newNode) {
// split root node
this.data = createNode$1([node, newNode]);
this.data.height = node.height + 1;
this.data.leaf = false;
calcBBox$1(this.data, this.toBBox);
},
_chooseSplitIndex: function (node, m, M) {
var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
minOverlap = minArea = Infinity;
for (i = m; i <= M - m; i++) {
bbox1 = distBBox$1(node, 0, i, this.toBBox);
bbox2 = distBBox$1(node, i, M, this.toBBox);
overlap = intersectionArea$1(bbox1, bbox2);
area = bboxArea$1(bbox1) + bboxArea$1(bbox2);
// choose distribution with minimum overlap
if (overlap < minOverlap) {
minOverlap = overlap;
index = i;
minArea = area < minArea ? area : minArea;
} else if (overlap === minOverlap) {
// otherwise choose distribution with minimum area
if (area < minArea) {
minArea = area;
index = i;
}
}
}
return index;
},
// sorts node children by the best axis for split
_chooseSplitAxis: function (node, m, M) {
var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX$1,
compareMinY = node.leaf ? this.compareMinY : compareNodeMinY$1,
xMargin = this._allDistMargin(node, m, M, compareMinX),
yMargin = this._allDistMargin(node, m, M, compareMinY);
// if total distributions margin value is minimal for x, sort by minX,
// otherwise it's already sorted by minY
if (xMargin < yMargin) node.children.sort(compareMinX);
},
// total margin of all possible split distributions where each node is at least m full
_allDistMargin: function (node, m, M, compare) {
node.children.sort(compare);
var toBBox = this.toBBox,
leftBBox = distBBox$1(node, 0, m, toBBox),
rightBBox = distBBox$1(node, M - m, M, toBBox),
margin = bboxMargin$1(leftBBox) + bboxMargin$1(rightBBox),
i, child;
for (i = m; i < M - m; i++) {
child = node.children[i];
extend$1(leftBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin$1(leftBBox);
}
for (i = M - m - 1; i >= m; i--) {
child = node.children[i];
extend$1(rightBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin$1(rightBBox);
}
return margin;
},
_adjustParentBBoxes: function (bbox, path, level) {
// adjust bboxes along the given tree path
for (var i = level; i >= 0; i--) {
extend$1(path[i], bbox);
}
},
_condense: function (path) {
// go through the path, removing empty nodes and updating bboxes
for (var i = path.length - 1, siblings; i >= 0; i--) {
if (path[i].children.length === 0) {
if (i > 0) {
siblings = path[i - 1].children;
siblings.splice(siblings.indexOf(path[i]), 1);
} else this.clear();
} else calcBBox$1(path[i], this.toBBox);
}
},
_initFormat: function (format) {
// data format (minX, minY, maxX, maxY accessors)
// uses eval-type function compilation instead of just accepting a toBBox function
// because the algorithms are very sensitive to sorting functions performance,
// so they should be dead simple and without inner calls
var compareArr = ['return a', ' - b', ';'];
this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
this.toBBox = new Function('a',
'return {minX: a' + format[0] +
', minY: a' + format[1] +
', maxX: a' + format[2] +
', maxY: a' + format[3] + '};');
}
};
function findItem$1(item, items, equalsFn) {
if (!equalsFn) return items.indexOf(item);
for (var i = 0; i < items.length; i++) {
if (equalsFn(item, items[i])) return i;
}
return -1;
}
// calculate node's bbox from bboxes of its children
function calcBBox$1(node, toBBox) {
distBBox$1(node, 0, node.children.length, toBBox, node);
}
// min bounding rectangle of node children from k to p-1
function distBBox$1(node, k, p, toBBox, destNode) {
if (!destNode) destNode = createNode$1(null);
destNode.minX = Infinity;
destNode.minY = Infinity;
destNode.maxX = -Infinity;
destNode.maxY = -Infinity;
for (var i = k, child; i < p; i++) {
child = node.children[i];
extend$1(destNode, node.leaf ? toBBox(child) : child);
}
return destNode;
}
function extend$1(a, b) {
a.minX = Math.min(a.minX, b.minX);
a.minY = Math.min(a.minY, b.minY);
a.maxX = Math.max(a.maxX, b.maxX);
a.maxY = Math.max(a.maxY, b.maxY);
return a;
}
function compareNodeMinX$1(a, b) { return a.minX - b.minX; }
function compareNodeMinY$1(a, b) { return a.minY - b.minY; }
function bboxArea$1(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
function bboxMargin$1(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
function enlargedArea$1(a, b) {
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
}
function intersectionArea$1(a, b) {
var minX = Math.max(a.minX, b.minX),
minY = Math.max(a.minY, b.minY),
maxX = Math.min(a.maxX, b.maxX),
maxY = Math.min(a.maxY, b.maxY);
return Math.max(0, maxX - minX) *
Math.max(0, maxY - minY);
}
function contains$1(a, b) {
return a.minX <= b.minX &&
a.minY <= b.minY &&
b.maxX <= a.maxX &&
b.maxY <= a.maxY;
}
function intersects$4(a, b) {
return b.minX <= a.maxX &&
b.minY <= a.maxY &&
b.maxX >= a.minX &&
b.maxY >= a.minY;
}
function createNode$1(children) {
return {
children: children,
height: 1,
leaf: true,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
}
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
// combines selection algorithm with binary divide & conquer approach
function multiSelect$1(arr, left, right, n, compare) {
var stack = [left, right],
mid;
while (stack.length) {
right = stack.pop();
left = stack.pop();
if (right - left <= n) continue;
mid = left + Math.ceil((right - left) / n / 2) * n;
quickselect$3(arr, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}
/**
* GeoJSON implementation of [RBush](https://github.com/mourner/rbush#rbush) spatial index.
*
* @name rbush
* @param {number} [maxEntries=9] defines the maximum number of entries in a tree node. 9 (used by default) is a
* reasonable choice for most applications. Higher value means faster insertion and slower search, and vice versa.
* @returns {RBush} GeoJSON RBush
* @example
* import geojsonRbush from 'geojson-rbush';
* var tree = geojsonRbush();
*/
function geojsonRbush(maxEntries) {
var tree = rbush$4(maxEntries);
/**
* [insert](https://github.com/mourner/rbush#data-format)
*
* @param {Feature} feature insert single GeoJSON Feature
* @returns {RBush} GeoJSON RBush
* @example
* var polygon = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]
* }
* }
* tree.insert(polygon)
*/
tree.insert = function (feature) {
if (Array.isArray(feature)) {
var bbox = feature;
feature = bboxPolygon$2(bbox);
feature.bbox = bbox;
} else {
feature.bbox = feature.bbox ? feature.bbox : turfBBox(feature);
}
return rbush$4.prototype.insert.call(this, feature);
};
/**
* [load](https://github.com/mourner/rbush#bulk-inserting-data)
*
* @param {BBox[]|FeatureCollection} features load entire GeoJSON FeatureCollection
* @returns {RBush} GeoJSON RBush
* @example
* var polygons = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]
* }
* },
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[-93, 32], [-83, 32], [-83, 39], [-93, 39], [-93, 32]]]
* }
* }
* ]
* }
* tree.load(polygons)
*/
tree.load = function (features) {
var load = [];
// Load an Array of BBox
if (Array.isArray(features)) {
features.forEach(function (bbox) {
var feature = bboxPolygon$2(bbox);
feature.bbox = bbox;
load.push(feature);
});
} else {
// Load FeatureCollection
featureEach(features, function (feature) {
feature.bbox = feature.bbox ? feature.bbox : turfBBox(feature);
load.push(feature);
});
}
return rbush$4.prototype.load.call(this, load);
};
/**
* [remove](https://github.com/mourner/rbush#removing-data)
*
* @param {BBox|Feature} feature remove single GeoJSON Feature
* @returns {RBush} GeoJSON RBush
* @example
* var polygon = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]
* }
* }
* tree.remove(polygon)
*/
tree.remove = function (feature) {
if (Array.isArray(feature)) {
var bbox = feature;
feature = bboxPolygon$2(bbox);
feature.bbox = bbox;
}
return rbush$4.prototype.remove.call(this, feature);
};
/**
* [clear](https://github.com/mourner/rbush#removing-data)
*
* @returns {RBush} GeoJSON Rbush
* @example
* tree.clear()
*/
tree.clear = function () {
return rbush$4.prototype.clear.call(this);
};
/**
* [search](https://github.com/mourner/rbush#search)
*
* @param {BBox|FeatureCollection|Feature} geojson search with GeoJSON
* @returns {FeatureCollection} all features that intersects with the given GeoJSON.
* @example
* var polygon = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]
* }
* }
* tree.search(polygon)
*/
tree.search = function (geojson) {
var features = rbush$4.prototype.search.call(this, this.toBBox(geojson));
return {
type: 'FeatureCollection',
features: features
};
};
/**
* [collides](https://github.com/mourner/rbush#collisions)
*
* @param {BBox|FeatureCollection|Feature} geojson collides with GeoJSON
* @returns {boolean} true if there are any items intersecting the given GeoJSON, otherwise false.
* @example
* var polygon = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]
* }
* }
* tree.collides(polygon)
*/
tree.collides = function (geojson) {
return rbush$4.prototype.collides.call(this, this.toBBox(geojson));
};
/**
* [all](https://github.com/mourner/rbush#search)
*
* @returns {FeatureCollection} all the features in RBush
* @example
* tree.all()
* //=FeatureCollection
*/
tree.all = function () {
var features = rbush$4.prototype.all.call(this);
return {
type: 'FeatureCollection',
features: features
};
};
/**
* [toJSON](https://github.com/mourner/rbush#export-and-import)
*
* @returns {any} export data as JSON object
* @example
* var exported = tree.toJSON()
* //=JSON object
*/
tree.toJSON = function () {
return rbush$4.prototype.toJSON.call(this);
};
/**
* [fromJSON](https://github.com/mourner/rbush#export-and-import)
*
* @param {any} json import previously exported data
* @returns {RBush} GeoJSON RBush
* @example
* var exported = {
* "children": [
* {
* "type": "Feature",
* "geometry": {
* "type": "Point",
* "coordinates": [110, 50]
* },
* "properties": {},
* "bbox": [110, 50, 110, 50]
* }
* ],
* "height": 1,
* "leaf": true,
* "minX": 110,
* "minY": 50,
* "maxX": 110,
* "maxY": 50
* }
* tree.fromJSON(exported)
*/
tree.fromJSON = function (json) {
return rbush$4.prototype.fromJSON.call(this, json);
};
/**
* Converts GeoJSON to {minX, minY, maxX, maxY} schema
*
* @private
* @param {BBox|FeatureCollectio|Feature} geojson feature(s) to retrieve BBox from
* @returns {Object} converted to {minX, minY, maxX, maxY}
*/
tree.toBBox = function (geojson) {
var bbox;
if (geojson.bbox) bbox = geojson.bbox;
else if (Array.isArray(geojson) && geojson.length === 4) bbox = geojson;
else bbox = turfBBox(geojson);
return {
minX: bbox[0],
minY: bbox[1],
maxX: bbox[2],
maxY: bbox[3]
};
};
return tree;
}
/**
* Takes a bbox and returns an equivalent {@link Polygon|polygon}.
*
* @private
* @name bboxPolygon
* @param {Array} bbox extent in [minX, minY, maxX, maxY] order
* @returns {Feature} a Polygon representation of the bounding box
* @example
* var bbox = [0, 0, 10, 10];
*
* var poly = turf.bboxPolygon(bbox);
*
* //addToMap
* var addToMap = [poly]
*/
function bboxPolygon$2(bbox) {
var lowLeft = [bbox[0], bbox[1]];
var topLeft = [bbox[0], bbox[3]];
var topRight = [bbox[2], bbox[3]];
var lowRight = [bbox[2], bbox[1]];
var coordinates = [[lowLeft, lowRight, topRight, topLeft, lowLeft]];
return {
type: 'Feature',
bbox: bbox,
properties: {},
geometry: {
type: 'Polygon',
coordinates: coordinates
}
};
}
/**
* Takes a set of features, calculates the bbox of all input features, and returns a bounding box.
*
* @private
* @name bbox
* @param {FeatureCollection|Feature} geojson input features
* @returns {Array} bbox extent in [minX, minY, maxX, maxY] order
* @example
* var line = turf.lineString([[-74, 40], [-78, 42], [-82, 35]]);
* var bbox = turf.bbox(line);
* var bboxPolygon = turf.bboxPolygon(bbox);
*
* //addToMap
* var addToMap = [line, bboxPolygon]
*/
function turfBBox(geojson) {
var bbox = [Infinity, Infinity, -Infinity, -Infinity];
coordEach(geojson, function (coord) {
if (bbox[0] > coord[0]) bbox[0] = coord[0];
if (bbox[1] > coord[1]) bbox[1] = coord[1];
if (bbox[2] < coord[0]) bbox[2] = coord[0];
if (bbox[3] < coord[1]) bbox[3] = coord[1];
});
return bbox;
}
/**
* Creates a {@link FeatureCollection} of 2-vertex {@link LineString} segments from a {@link LineString|(Multi)LineString} or {@link Polygon|(Multi)Polygon}.
*
* @name lineSegment
* @param {Geometry|FeatureCollection|Feature} geojson GeoJSON Polygon or LineString
* @returns {FeatureCollection} 2-vertex line segments
* @example
* var polygon = turf.polygon([[[-50, 5], [-40, -10], [-50, -10], [-40, 5], [-50, 5]]]);
* var segments = turf.lineSegment(polygon);
*
* //addToMap
* var addToMap = [polygon, segments]
*/
function lineSegment(geojson) {
if (!geojson) throw new Error('geojson is required');
var results = [];
flattenEach(geojson, function (feature$$1) {
lineSegmentFeature(feature$$1, results);
});
return featureCollection(results);
}
/**
* Line Segment
*
* @private
* @param {Feature} geojson Line or polygon feature
* @param {Array} results push to results
* @returns {void}
*/
function lineSegmentFeature(geojson, results) {
var coords = [];
var geometry$$1 = geojson.geometry;
switch (geometry$$1.type) {
case 'Polygon':
coords = getCoords(geometry$$1);
break;
case 'LineString':
coords = [getCoords(geometry$$1)];
}
coords.forEach(function (coord) {
var segments = createSegments(coord, geojson.properties);
segments.forEach(function (segment) {
segment.id = results.length;
results.push(segment);
});
});
}
/**
* Create Segments from LineString coordinates
*
* @private
* @param {LineString} coords LineString coordinates
* @param {*} properties GeoJSON properties
* @returns {Array>} line segments
*/
function createSegments(coords, properties) {
var segments = [];
coords.reduce(function (previousCoords, currentCoords) {
var segment = lineString([previousCoords, currentCoords], properties);
segment.bbox = bbox$3(previousCoords, currentCoords);
segments.push(segment);
return currentCoords;
});
return segments;
}
/**
* Create BBox between two coordinates (faster than @turf/bbox)
*
* @private
* @param {Array} coords1 Point coordinate
* @param {Array} coords2 Point coordinate
* @returns {BBox} [west, south, east, north]
*/
function bbox$3(coords1, coords2) {
var x1 = coords1[0];
var y1 = coords1[1];
var x2 = coords2[0];
var y2 = coords2[1];
var west = (x1 < x2) ? x1 : x2;
var south = (y1 < y2) ? y1 : y2;
var east = (x1 > x2) ? x1 : x2;
var north = (y1 > y2) ? y1 : y2;
return [west, south, east, north];
}
/**
* Takes any LineString or Polygon GeoJSON and returns the intersecting point(s).
*
* @name lineIntersect
* @param {Geometry|FeatureCollection|Feature} line1 any LineString or Polygon
* @param {Geometry|FeatureCollection|Feature} line2 any LineString or Polygon
* @returns {FeatureCollection} point(s) that intersect both
* @example
* var line1 = turf.lineString([[126, -11], [129, -21]]);
* var line2 = turf.lineString([[123, -18], [131, -14]]);
* var intersects = turf.lineIntersect(line1, line2);
*
* //addToMap
* var addToMap = [line1, line2, intersects]
*/
function lineIntersect(line1, line2) {
var unique = {};
var results = [];
// First, normalize geometries to features
// Then, handle simple 2-vertex segments
if (line1.type === 'LineString') line1 = feature(line1);
if (line2.type === 'LineString') line2 = feature(line2);
if (line1.type === 'Feature' &&
line2.type === 'Feature' &&
line1.geometry.type === 'LineString' &&
line2.geometry.type === 'LineString' &&
line1.geometry.coordinates.length === 2 &&
line2.geometry.coordinates.length === 2) {
var intersect = intersects$3(line1, line2);
if (intersect) results.push(intersect);
return featureCollection(results);
}
// Handles complex GeoJSON Geometries
var tree = geojsonRbush();
tree.load(lineSegment(line2));
featureEach(lineSegment(line1), function (segment) {
featureEach(tree.search(segment), function (match) {
var intersect = intersects$3(segment, match);
if (intersect) {
// prevent duplicate points https://github.com/Turfjs/turf/issues/688
var key = getCoords(intersect).join(',');
if (!unique[key]) {
unique[key] = true;
results.push(intersect);
}
}
});
});
return featureCollection(results);
}
/**
* Find a point that intersects LineStrings with two coordinates each
*
* @private
* @param {Feature} line1 GeoJSON LineString (Must only contain 2 coordinates)
* @param {Feature} line2 GeoJSON LineString (Must only contain 2 coordinates)
* @returns {Feature} intersecting GeoJSON Point
*/
function intersects$3(line1, line2) {
var coords1 = getCoords(line1);
var coords2 = getCoords(line2);
if (coords1.length !== 2) {
throw new Error(' line1 must only contain 2 coordinates');
}
if (coords2.length !== 2) {
throw new Error(' line2 must only contain 2 coordinates');
}
var x1 = coords1[0][0];
var y1 = coords1[0][1];
var x2 = coords1[1][0];
var y2 = coords1[1][1];
var x3 = coords2[0][0];
var y3 = coords2[0][1];
var x4 = coords2[1][0];
var y4 = coords2[1][1];
var denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
var numeA = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
var numeB = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));
if (denom === 0) {
if (numeA === 0 && numeB === 0) {
return null;
}
return null;
}
var uA = numeA / denom;
var uB = numeB / denom;
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
var x = x1 + (uA * (x2 - x1));
var y = y1 + (uA * (y2 - y1));
return point([x, y]);
}
return null;
}
/**
* Takes a {@link Point} and a {@link LineString} and calculates the closest Point on the (Multi)LineString.
*
* @name nearestPointOnLine
* @param {Geometry|Feature} lines lines to snap to
* @param {Geometry|Feature|number[]} pt point to snap from
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @returns {Feature} closest point on the `line` to `point`. The properties object will contain three values: `index`: closest point was found on nth line part, `dist`: distance between pt and the closest point, `location`: distance along the line between start and the closest point.
* @example
* var line = turf.lineString([
* [-77.031669, 38.878605],
* [-77.029609, 38.881946],
* [-77.020339, 38.884084],
* [-77.025661, 38.885821],
* [-77.021884, 38.889563],
* [-77.019824, 38.892368]
* ]);
* var pt = turf.point([-77.037076, 38.884017]);
*
* var snapped = turf.nearestPointOnLine(line, pt, {units: 'miles'});
*
* //addToMap
* var addToMap = [line, pt, snapped];
* snapped.properties['marker-color'] = '#00f';
*/
function nearestPointOnLine(lines, pt, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
// validation
var type = (lines.geometry) ? lines.geometry.type : lines.type;
if (type !== 'LineString' && type !== 'MultiLineString') {
throw new Error('lines must be LineString or MultiLineString');
}
var closestPt = point([Infinity, Infinity], {
dist: Infinity
});
var length = 0.0;
flattenEach(lines, function (line) {
var coords = getCoords(line);
for (var i = 0; i < coords.length - 1; i++) {
//start
var start = point(coords[i]);
start.properties.dist = distance(pt, start, options);
//stop
var stop = point(coords[i + 1]);
stop.properties.dist = distance(pt, stop, options);
// sectionLength
var sectionLength = distance(start, stop, options);
//perpendicular
var heightDistance = Math.max(start.properties.dist, stop.properties.dist);
var direction = bearing(start, stop);
var perpendicularPt1 = destination(pt, heightDistance, direction + 90, options);
var perpendicularPt2 = destination(pt, heightDistance, direction - 90, options);
var intersect = lineIntersect(
lineString([perpendicularPt1.geometry.coordinates, perpendicularPt2.geometry.coordinates]),
lineString([start.geometry.coordinates, stop.geometry.coordinates])
);
var intersectPt = null;
if (intersect.features.length > 0) {
intersectPt = intersect.features[0];
intersectPt.properties.dist = distance(pt, intersectPt, options);
intersectPt.properties.location = length + distance(start, intersectPt, options);
}
if (start.properties.dist < closestPt.properties.dist) {
closestPt = start;
closestPt.properties.index = i;
closestPt.properties.location = length;
}
if (stop.properties.dist < closestPt.properties.dist) {
closestPt = stop;
closestPt.properties.index = i + 1;
closestPt.properties.location = length + sectionLength;
}
if (intersectPt && intersectPt.properties.dist < closestPt.properties.dist) {
closestPt = intersectPt;
closestPt.properties.index = i;
}
// update length
length += sectionLength;
}
});
return closestPt;
}
// https://en.wikipedia.org/wiki/Rhumb_line
/**
* Takes two {@link Point|points} and finds the bearing angle between them along a Rhumb line
* i.e. the angle measured in degrees start the north line (0 degrees)
*
* @name rhumbBearing
* @param {Coord} start starting Point
* @param {Coord} end ending Point
* @param {Object} [options] Optional parameters
* @param {boolean} [options.final=false] calculates the final bearing if true
* @returns {number} bearing from north in decimal degrees, between -180 and 180 degrees (positive clockwise)
* @example
* var point1 = turf.point([-75.343, 39.984], {"marker-color": "#F00"});
* var point2 = turf.point([-75.534, 39.123], {"marker-color": "#00F"});
*
* var bearing = turf.rhumbBearing(point1, point2);
*
* //addToMap
* var addToMap = [point1, point2];
* point1.properties.bearing = bearing;
* point2.properties.bearing = bearing;
*/
function rhumbBearing(start, end, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var final = options.final;
// validation
if (!start) throw new Error('start point is required');
if (!end) throw new Error('end point is required');
var bear360;
if (final) bear360 = calculateRhumbBearing(getCoord(end), getCoord(start));
else bear360 = calculateRhumbBearing(getCoord(start), getCoord(end));
var bear180 = (bear360 > 180) ? -(360 - bear360) : bear360;
return bear180;
}
/**
* Returns the bearing from ‘this’ point to destination point along a rhumb line.
* Adapted from Geodesy: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js
*
* @private
* @param {Array} from - origin point.
* @param {Array} to - destination point.
* @returns {number} Bearing in degrees from north.
* @example
* var p1 = new LatLon(51.127, 1.338);
* var p2 = new LatLon(50.964, 1.853);
* var d = p1.rhumbBearingTo(p2); // 116.7 m
*/
function calculateRhumbBearing(from, to) {
// φ => phi
// Δλ => deltaLambda
// Δψ => deltaPsi
// θ => theta
var phi1 = degreesToRadians(from[1]);
var phi2 = degreesToRadians(to[1]);
var deltaLambda = degreesToRadians((to[0] - from[0]));
// if deltaLambdaon over 180° take shorter rhumb line across the anti-meridian:
if (deltaLambda > Math.PI) deltaLambda -= 2 * Math.PI;
if (deltaLambda < -Math.PI) deltaLambda += 2 * Math.PI;
var deltaPsi = Math.log(Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4));
var theta = Math.atan2(deltaLambda, deltaPsi);
return (radiansToDegrees(theta) + 360) % 360;
}
// https://en.wikipedia.org/wiki/Rhumb_line
/**
* Calculates the distance along a rhumb line between two {@link Point|points} in degrees, radians,
* miles, or kilometers.
*
* @name rhumbDistance
* @param {Coord} from origin point
* @param {Coord} to destination point
* @param {Object} [options] Optional parameters
* @param {string} [options.units="kilometers"] can be degrees, radians, miles, or kilometers
* @returns {number} distance between the two points
* @example
* var from = turf.point([-75.343, 39.984]);
* var to = turf.point([-75.534, 39.123]);
* var options = {units: 'miles'};
*
* var distance = turf.rhumbDistance(from, to, options);
*
* //addToMap
* var addToMap = [from, to];
* from.properties.distance = distance;
* to.properties.distance = distance;
*/
function rhumbDistance(from, to, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var units = options.units;
// validation
if (!from) throw new Error('from point is required');
if (!to) throw new Error('to point is required');
var origin = getCoord(from);
var destination = getCoord(to);
// compensate the crossing of the 180th meridian (https://macwright.org/2016/09/26/the-180th-meridian.html)
// solution from https://github.com/mapbox/mapbox-gl-js/issues/3250#issuecomment-294887678
destination[0] += (destination[0] - origin[0] > 180) ? -360 : (origin[0] - destination[0] > 180) ? 360 : 0;
var distanceInMeters = calculateRhumbDistance(origin, destination);
var distance = convertLength(distanceInMeters, 'meters', units);
return distance;
}
/**
* Returns the distance travelling from ‘this’ point to destination point along a rhumb line.
* Adapted from Geodesy: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js
*
* @private
* @param {Array} origin point.
* @param {Array} destination point.
* @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres).
* @returns {number} Distance in km between this point and destination point (same units as radius).
*
* @example
* var p1 = new LatLon(51.127, 1.338);
* var p2 = new LatLon(50.964, 1.853);
* var d = p1.distanceTo(p2); // 40.31 km
*/
function calculateRhumbDistance(origin, destination, radius) {
// φ => phi
// λ => lambda
// ψ => psi
// Δ => Delta
// δ => delta
// θ => theta
radius = (radius === undefined) ? earthRadius : Number(radius);
// see www.edwilliams.org/avform.htm#Rhumb
var R = radius;
var phi1 = origin[1] * Math.PI / 180;
var phi2 = destination[1] * Math.PI / 180;
var DeltaPhi = phi2 - phi1;
var DeltaLambda = Math.abs(destination[0] - origin[0]) * Math.PI / 180;
// if dLon over 180° take shorter rhumb line across the anti-meridian:
if (DeltaLambda > Math.PI) DeltaLambda -= 2 * Math.PI;
// on Mercator projection, longitude distances shrink by latitude; q is the 'stretch factor'
// q becomes ill-conditioned along E-W line (0/0); use empirical tolerance to avoid it
var DeltaPsi = Math.log(Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4));
var q = Math.abs(DeltaPsi) > 10e-12 ? DeltaPhi / DeltaPsi : Math.cos(phi1);
// distance is pythagoras on 'stretched' Mercator projection
var delta = Math.sqrt(DeltaPhi * DeltaPhi + q * q * DeltaLambda * DeltaLambda); // angular distance in radians
var dist = delta * R;
return dist;
}
/**
* Converts a WGS84 GeoJSON object into Mercator (EPSG:900913) projection
*
* @name toMercator
* @param {GeoJSON|Position} geojson WGS84 GeoJSON object
* @param {Object} [options] Optional parameters
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} true/false
* @example
* var pt = turf.point([-71,41]);
* var converted = turf.toMercator(pt);
*
* //addToMap
* var addToMap = [pt, converted];
*/
function toMercator(geojson, options) {
return convert(geojson, 'mercator', options);
}
/**
* Converts a Mercator (EPSG:900913) GeoJSON object into WGS84 projection
*
* @name toWgs84
* @param {GeoJSON|Position} geojson Mercator GeoJSON object
* @param {Object} [options] Optional parameters
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} true/false
* @example
* var pt = turf.point([-7903683.846322424, 5012341.663847514]);
* var converted = turf.toWgs84(pt);
*
* //addToMap
* var addToMap = [pt, converted];
*/
function toWgs84(geojson, options) {
return convert(geojson, 'wgs84', options);
}
/**
* Converts a GeoJSON coordinates to the defined `projection`
*
* @private
* @param {GeoJSON} geojson GeoJSON Feature or Geometry
* @param {string} projection defines the projection system to convert the coordinates to
* @param {Object} [options] Optional parameters
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} true/false
*/
function convert(geojson, projection, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var mutate = options.mutate;
// Validation
if (!geojson) throw new Error('geojson is required');
// Handle Position
if (Array.isArray(geojson) && isNumber(geojson[0])) geojson = (projection === 'mercator') ? convertToMercator(geojson) : convertToWgs84(geojson);
// Handle GeoJSON
else {
// Handle possible data mutation
if (mutate !== true) geojson = clone(geojson);
coordEach(geojson, function (coord) {
var newCoord = (projection === 'mercator') ? convertToMercator(coord) : convertToWgs84(coord);
coord[0] = newCoord[0];
coord[1] = newCoord[1];
});
}
return geojson;
}
/**
* Convert lon/lat values to 900913 x/y.
* (from https://github.com/mapbox/sphericalmercator)
*
* @private
* @param {Array} lonLat WGS84 point
* @returns {Array} Mercator [x, y] point
*/
function convertToMercator(lonLat) {
var D2R = Math.PI / 180,
// 900913 properties
A = 6378137.0,
MAXEXTENT = 20037508.342789244;
// compensate longitudes passing the 180th meridian
// from https://github.com/proj4js/proj4js/blob/master/lib/common/adjust_lon.js
var adjusted = (Math.abs(lonLat[0]) <= 180) ? lonLat[0] : (lonLat[0] - (sign(lonLat[0]) * 360));
var xy = [
A * adjusted * D2R,
A * Math.log(Math.tan((Math.PI * 0.25) + (0.5 * lonLat[1] * D2R)))
];
// if xy value is beyond maxextent (e.g. poles), return maxextent
if (xy[0] > MAXEXTENT) xy[0] = MAXEXTENT;
if (xy[0] < -MAXEXTENT) xy[0] = -MAXEXTENT;
if (xy[1] > MAXEXTENT) xy[1] = MAXEXTENT;
if (xy[1] < -MAXEXTENT) xy[1] = -MAXEXTENT;
return xy;
}
/**
* Convert 900913 x/y values to lon/lat.
* (from https://github.com/mapbox/sphericalmercator)
*
* @private
* @param {Array} xy Mercator [x, y] point
* @returns {Array} WGS84 [lon, lat] point
*/
function convertToWgs84(xy) {
// 900913 properties.
var R2D = 180 / Math.PI;
var A = 6378137.0;
return [
(xy[0] * R2D / A),
((Math.PI * 0.5) - 2.0 * Math.atan(Math.exp(-xy[1] / A))) * R2D
];
}
/**
* Returns the sign of the input, or zero
*
* @private
* @param {number} x input
* @returns {number} -1|0|1 output
*/
function sign(x) {
return (x < 0) ? -1 : (x > 0) ? 1 : 0;
}
var main_es$3 = Object.freeze({
toMercator: toMercator,
toWgs84: toWgs84
});
// (logic of computation inspired by:
// https://stackoverflow.com/questions/32771458/distance-from-lat-lng-point-to-minor-arc-segment)
/**
* Returns the minimum distance between a {@link Point} and a {@link LineString}, being the distance from a line the
* minimum distance between the point and any segment of the `LineString`.
*
* @name pointToLineDistance
* @param {Coord} pt Feature or Geometry
* @param {Feature} line GeoJSON Feature or Geometry
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @param {boolean} [options.mercator=false] if distance should be on Mercator or WGS84 projection
* @returns {number} distance between point and line
* @example
* var pt = turf.point([0, 0]);
* var line = turf.lineString([[1, 1],[-1, 1]]);
*
* var distance = turf.pointToLineDistance(pt, line, {units: 'miles'});
* //=69.11854715938406
*/
function pointToLineDistance(pt, line, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
// validation
if (!pt) throw new Error('pt is required');
if (Array.isArray(pt)) pt = point(pt);
else if (pt.type === 'Point') pt = feature(pt);
else featureOf(pt, 'Point', 'point');
if (!line) throw new Error('line is required');
if (Array.isArray(line)) line = lineString(line);
else if (line.type === 'LineString') line = feature(line);
else featureOf(line, 'LineString', 'line');
var distance$$1 = Infinity;
var p = pt.geometry.coordinates;
segmentEach(line, function (segment) {
var a = segment.geometry.coordinates[0];
var b = segment.geometry.coordinates[1];
var d = distanceToSegment(p, a, b, options);
if (distance$$1 > d) distance$$1 = d;
});
return distance$$1;
}
/**
* Returns the distance between a point P on a segment AB.
*
* @private
* @param {Array} p external point
* @param {Array} a first segment point
* @param {Array} b second segment point
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @param {boolean} [options.mercator=false] if distance should be on Mercator or WGS84 projection
* @returns {number} distance
*/
function distanceToSegment(p, a, b, options) {
var mercator = options.mercator;
var distanceAP = (mercator !== true) ? distance(a, p, options) : euclideanDistance(a, p, options);
var azimuthAP = bearingToAzimuth((mercator !== true) ? bearing(a, p) : rhumbBearing(a, p));
var azimuthAB = bearingToAzimuth((mercator !== true) ? bearing(a, b) : rhumbBearing(a, b));
var angleA = Math.abs(azimuthAP - azimuthAB);
// if (angleA > 180) angleA = Math.abs(angleA - 360);
// if the angle PAB is obtuse its projection on the line extending the segment falls outside the segment
// thus return distance between P and the start point A
/*
P__
|\ \____
| \ \____
| \ \____
| \_____________\
H A B
*/
if (angleA > 90) return distanceAP;
var azimuthBA = (azimuthAB + 180) % 360;
var azimuthBP = bearingToAzimuth((mercator !== true) ? bearing(b, p) : rhumbBearing(b, p));
var angleB = Math.abs(azimuthBP - azimuthBA);
if (angleB > 180) angleB = Math.abs(angleB - 360);
// also if the angle ABP is acute the projection of P falls outside the segment, on the other side
// so return the distance between P and the end point B
/*
____P
____/ /|
____/ / |
____/ / |
/______________/ |
A B H
*/
if (angleB > 90) return (mercator !== true) ? distance(p, b, options) : euclideanDistance(p, b, options);
// finally if the projection falls inside the segment
// return the distance between P and the segment
/*
P
__/|\
__/ | \
__/ | \
__/ | \
/____________|____\
A H B
*/
if (mercator !== true) return distanceAP * Math.sin(degreesToRadians(angleA));
return mercatorPH(a, b, p, options);
}
/**
* Returns the distance between a point P on a segment AB, on Mercator projection
*
* @private
* @param {Array} a first segment point
* @param {Array} b second segment point
* @param {Array} p external point
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @returns {number} distance
*/
function mercatorPH(a, b, p, options) {
var delta = 0;
// translate points if any is crossing the 180th meridian
if (Math.abs(a[0]) >= 180 || Math.abs(b[0]) >= 180 || Math.abs(p[0]) >= 180) {
delta = (a[0] > 0 || b[0] > 0 || p[0] > 0) ? -180 : 180;
}
var origin = point(p);
var A = toMercator([a[0] + delta, a[1]]);
var B = toMercator([b[0] + delta, b[1]]);
var P = toMercator([p[0] + delta, p[1]]);
var h = toWgs84(euclideanIntersection(A, B, P));
if (delta !== 0) h[0] -= delta; // translate back to original position
var distancePH = rhumbDistance(origin, h, options);
return distancePH;
}
/**
* Returns the point H projection of a point P on a segment AB, on the euclidean plain
* from https://stackoverflow.com/questions/10301001/perpendicular-on-a-line-segment-from-a-given-point#answer-12499474
* P
* |
* |
* _________|____
* A H B
*
* @private
* @param {Array} a first segment point
* @param {Array} b second segment point
* @param {Array} p external point
* @returns {Array} projected point
*/
function euclideanIntersection(a, b, p) {
var x1 = a[0], y1 = a[1],
x2 = b[0], y2 = b[1],
x3 = p[0], y3 = p[1];
var px = x2 - x1, py = y2 - y1;
var dab = px * px + py * py;
var u = ((x3 - x1) * px + (y3 - y1) * py) / dab;
var x = x1 + u * px, y = y1 + u * py;
return [x, y]; // H
}
/**
* Returns euclidean distance between two points
*
* @private
* @param {Object} from first point
* @param {Object} to second point
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @returns {number} squared distance
*/
function euclideanDistance(from, to, options) {
var units = options.units;
// translate points if any is crossing the 180th meridian
var delta = 0;
if (Math.abs(from[0]) >= 180) {
delta = (from[0] > 0) ? -180 : 180;
}
if (Math.abs(to[0]) >= 180) {
delta = (to[0] > 0) ? -180 : 180;
}
var p1 = toMercator([from[0] + delta, from[1]]);
var p2 = toMercator([to[0] + delta, to[1]]);
var sqr = function (n) { return n * n; };
var squareD = sqr(p1[0] - p2[0]) + sqr(p1[1] - p2[1]);
var d = Math.sqrt(squareD);
return convertLength(d, 'meters', units);
}
/**
* Returns the closest {@link Point|point}, of a {@link FeatureCollection|collection} of points, to a {@link LineString|line}.
* The returned point has a `dist` property indicating its distance to the line.
*
* @name nearestPointToLine
* @param {FeatureCollection|GeometryCollection} points Point Collection
* @param {Feature|Geometry} line Line Feature
* @param {Object} [options] Optional parameters
* @param {string} [options.units='kilometers'] unit of the output distance property, can be degrees, radians, miles, or kilometers
* @param {Object} [options.properties={}] Translate Properties to Point
* @returns {Feature} the closest point
* @example
* var pt1 = turf.point([0, 0]);
* var pt2 = turf.point([0.5, 0.5]);
* var points = turf.featureCollection([pt1, pt2]);
* var line = turf.lineString([[1,1], [-1,1]]);
*
* var nearest = turf.nearestPointToLine(points, line);
*
* //addToMap
* var addToMap = [nearest, line];
*/
function nearestPointToLine(points$$1, line, options) {
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var units = options.units;
var properties = options.properties || {};
// validation
if (!points$$1) throw new Error('points is required');
points$$1 = normalize(points$$1);
if (!points$$1.features.length) throw new Error('points must contain features');
if (!line) throw new Error('line is required');
if (getType(line) !== 'LineString') throw new Error('line must be a LineString');
var dist = Infinity;
var pt = null;
featureEach(points$$1, function (point$$1) {
var d = pointToLineDistance(point$$1, line, { units: units });
if (d < dist) {
dist = d;
pt = point$$1;
}
});
/**
* Translate Properties to final Point, priorities:
* 1. options.properties
* 2. inherent Point properties
* 3. dist custom properties created by NearestPointToLine
*/
if (pt) pt.properties = Object.assign({dist: dist}, pt.properties, properties);
return pt;
}
/**
* Convert Collection to FeatureCollection
*
* @private
* @param {FeatureCollection|GeometryCollection} points Points
* @returns {FeatureCollection} points
*/
function normalize(points$$1) {
var features = [];
var type = points$$1.geometry ? points$$1.geometry.type : points$$1.type;
switch (type) {
case 'GeometryCollection':
geomEach(points$$1, function (geom) {
if (geom.type === 'Point') features.push({type: 'Feature', properties: {}, geometry: geom});
});
return {type: 'FeatureCollection', features: features};
case 'FeatureCollection':
points$$1.features = points$$1.features.filter(function (feature$$1) {
return feature$$1.geometry.type === 'Point';
});
return points$$1;
default:
throw new Error('points must be a Point Collection');
}
}
/**
* Takes a triangular plane as a {@link Polygon}
* and a {@link Point} within that triangle and returns the z-value
* at that point. The Polygon should have properties `a`, `b`, and `c`
* that define the values at its three corners. Alternatively, the z-values
* of each triangle point can be provided by their respective 3rd coordinate
* if their values are not provided as properties.
*
* @name planepoint
* @param {Coord} point the Point for which a z-value will be calculated
* @param {Feature} triangle a Polygon feature with three vertices
* @returns {number} the z-value for `interpolatedPoint`
* @example
* var point = turf.point([-75.3221, 39.529]);
* // "a", "b", and "c" values represent the values of the coordinates in order.
* var triangle = turf.polygon([[
* [-75.1221, 39.57],
* [-75.58, 39.18],
* [-75.97, 39.86],
* [-75.1221, 39.57]
* ]], {
* "a": 11,
* "b": 122,
* "c": 44
* });
*
* var zValue = turf.planepoint(point, triangle);
* point.properties.zValue = zValue;
*
* //addToMap
* var addToMap = [triangle, point];
*/
function planepoint(point, triangle) {
// Normalize input
var coord = getCoord(point);
var geom = getGeom(triangle);
var coords = geom.coordinates;
var outer = coords[0];
if (outer.length < 4) throw new Error('OuterRing of a Polygon must have 4 or more Positions.');
var properties = triangle.properties || {};
var a = properties.a;
var b = properties.b;
var c = properties.c;
// Planepoint
var x = coord[0];
var y = coord[1];
var x1 = outer[0][0];
var y1 = outer[0][1];
var z1 = (a !== undefined ? a : outer[0][2]);
var x2 = outer[1][0];
var y2 = outer[1][1];
var z2 = (b !== undefined ? b : outer[1][2]);
var x3 = outer[2][0];
var y3 = outer[2][1];
var z3 = (c !== undefined ? c : outer[2][2]);
var z = (z3 * (x - x1) * (y - y2) + z1 * (x - x2) * (y - y3) + z2 * (x - x3) * (y - y1) -
z2 * (x - x1) * (y - y3) - z3 * (x - x2) * (y - y1) - z1 * (x - x3) * (y - y2)) /
((x - x1) * (y - y2) + (x - x2) * (y - y3) + (x - x3) * (y - y1) -
(x - x1) * (y - y3) - (x - x2) * (y - y1) - (x - x3) * (y - y2));
return z;
}
/**
* Takes a {@link LineString|linestring}, {@link MultiLineString|multi-linestring}, {@link MultiPolygon|multi-polygon}, or {@link Polygon|polygon} and returns {@link Point|points} at all self-intersections.
*
* @name kinks
* @param {Feature} featureIn input feature
* @returns {FeatureCollection} self-intersections
* @example
* var poly = turf.polygon([[
* [-12.034835, 8.901183],
* [-12.060413, 8.899826],
* [-12.03638, 8.873199],
* [-12.059383, 8.871418],
* [-12.034835, 8.901183]
* ]]);
*
* var kinks = turf.kinks(poly);
*
* //addToMap
* var addToMap = [poly, kinks]
*/
function kinks(featureIn) {
var coordinates;
var feature$$1;
var results = {
type: 'FeatureCollection',
features: []
};
if (featureIn.type === 'Feature') {
feature$$1 = featureIn.geometry;
} else {
feature$$1 = featureIn;
}
if (feature$$1.type === 'LineString') {
coordinates = [feature$$1.coordinates];
} else if (feature$$1.type === 'MultiLineString') {
coordinates = feature$$1.coordinates;
} else if (feature$$1.type === 'MultiPolygon') {
coordinates = [].concat.apply([], feature$$1.coordinates);
} else if (feature$$1.type === 'Polygon') {
coordinates = feature$$1.coordinates;
} else {
throw new Error('Input must be a LineString, MultiLineString, ' +
'Polygon, or MultiPolygon Feature or Geometry');
}
coordinates.forEach(function (line1) {
coordinates.forEach(function (line2) {
for (var i = 0; i < line1.length - 1; i++) {
// start iteration at i, intersections for k < i have already been checked in previous outer loop iterations
for (var k = i; k < line2.length - 1; k++) {
if (line1 === line2) {
// segments are adjacent and always share a vertex, not a kink
if (Math.abs(i - k) === 1) {
continue;
}
// first and last segment in a closed lineString or ring always share a vertex, not a kink
if (
// segments are first and last segment of lineString
i === 0 &&
k === line1.length - 2 &&
// lineString is closed
line1[i][0] === line1[line1.length - 1][0] &&
line1[i][1] === line1[line1.length - 1][1]
) {
continue;
}
}
var intersection = lineIntersects(line1[i][0], line1[i][1], line1[i + 1][0], line1[i + 1][1],
line2[k][0], line2[k][1], line2[k + 1][0], line2[k + 1][1]);
if (intersection) {
results.features.push(point([intersection[0], intersection[1]]));
}
}
}
});
});
return results;
}
// modified from http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
function lineIntersects(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
var denominator, a, b, numerator1, numerator2,
result = {
x: null,
y: null,
onLine1: false,
onLine2: false
};
denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
if (denominator === 0) {
if (result.x !== null && result.y !== null) {
return result;
} else {
return false;
}
}
a = line1StartY - line2StartY;
b = line1StartX - line2StartX;
numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
a = numerator1 / denominator;
b = numerator2 / denominator;
// if we cast these lines infinitely in both directions, they intersect here:
result.x = line1StartX + (a * (line1EndX - line1StartX));
result.y = line1StartY + (a * (line1EndY - line1StartY));
// if line1 is a segment and line2 is infinite, they intersect if:
if (a >= 0 && a <= 1) {
result.onLine1 = true;
}
// if line2 is a segment and line1 is infinite, they intersect if:
if (b >= 0 && b <= 1) {
result.onLine2 = true;
}
// if line1 and line2 are segments, they intersect if both of the above are true
if (result.onLine1 && result.onLine2) {
return [result.x, result.y];
} else {
return false;
}
}
/**
* Takes a Feature or FeatureCollection and returns a {@link Point} guaranteed to be on the surface of the feature.
*
* * Given a {@link Polygon}, the point will be in the area of the polygon
* * Given a {@link LineString}, the point will be along the string
* * Given a {@link Point}, the point will the same as the input
*
* @name pointOnFeature
* @param {GeoJSON} geojson any Feature or FeatureCollection
* @returns {Feature} a point on the surface of `input`
* @example
* var polygon = turf.polygon([[
* [116, -36],
* [131, -32],
* [146, -43],
* [155, -25],
* [133, -9],
* [111, -22],
* [116, -36]
* ]]);
*
* var pointOnPolygon = turf.pointOnFeature(polygon);
*
* //addToMap
* var addToMap = [polygon, pointOnPolygon];
*/
function pointOnFeature(geojson) {
// normalize
var fc = normalize$1(geojson);
// get centroid
var cent = center(fc);
// check to see if centroid is on surface
var onSurface = false;
var i = 0;
while (!onSurface && i < fc.features.length) {
var geom = fc.features[i].geometry;
var x, y, x1, y1, x2, y2, k;
var onLine = false;
if (geom.type === 'Point') {
if (cent.geometry.coordinates[0] === geom.coordinates[0] &&
cent.geometry.coordinates[1] === geom.coordinates[1]) {
onSurface = true;
}
} else if (geom.type === 'MultiPoint') {
var onMultiPoint = false;
k = 0;
while (!onMultiPoint && k < geom.coordinates.length) {
if (cent.geometry.coordinates[0] === geom.coordinates[k][0] &&
cent.geometry.coordinates[1] === geom.coordinates[k][1]) {
onSurface = true;
onMultiPoint = true;
}
k++;
}
} else if (geom.type === 'LineString') {
k = 0;
while (!onLine && k < geom.coordinates.length - 1) {
x = cent.geometry.coordinates[0];
y = cent.geometry.coordinates[1];
x1 = geom.coordinates[k][0];
y1 = geom.coordinates[k][1];
x2 = geom.coordinates[k + 1][0];
y2 = geom.coordinates[k + 1][1];
if (pointOnSegment(x, y, x1, y1, x2, y2)) {
onLine = true;
onSurface = true;
}
k++;
}
} else if (geom.type === 'MultiLineString') {
var j = 0;
while (j < geom.coordinates.length) {
onLine = false;
k = 0;
var line = geom.coordinates[j];
while (!onLine && k < line.length - 1) {
x = cent.geometry.coordinates[0];
y = cent.geometry.coordinates[1];
x1 = line[k][0];
y1 = line[k][1];
x2 = line[k + 1][0];
y2 = line[k + 1][1];
if (pointOnSegment(x, y, x1, y1, x2, y2)) {
onLine = true;
onSurface = true;
}
k++;
}
j++;
}
} else if (geom.type === 'Polygon' || geom.type === 'MultiPolygon') {
if (booleanPointInPolygon(cent, geom)) {
onSurface = true;
}
}
i++;
}
if (onSurface) {
return cent;
} else {
var vertices = featureCollection([]);
for (i = 0; i < fc.features.length; i++) {
vertices.features = vertices.features.concat(explode(fc.features[i]).features);
}
// Remove distanceToPoint properties from nearestPoint()
return point(nearestPoint(cent, vertices).geometry.coordinates);
}
}
/**
* Normalizes any GeoJSON to a FeatureCollection
*
* @private
* @name normalize
* @param {GeoJSON} geojson Any GeoJSON
* @returns {FeatureCollection} FeatureCollection
*/
function normalize$1(geojson) {
if (geojson.type !== 'FeatureCollection') {
if (geojson.type !== 'Feature') {
return featureCollection([feature(geojson)]);
}
return featureCollection([geojson]);
}
return geojson;
}
function pointOnSegment(x, y, x1, y1, x2, y2) {
var ab = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
var ap = Math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
var pb = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y));
return ab === ap + pb;
}
/**
* Takes one or more features and returns their area in square meters.
*
* @name area
* @param {GeoJSON} geojson input GeoJSON feature(s)
* @returns {number} area in square meters
* @example
* var polygon = turf.polygon([[[125, -15], [113, -22], [154, -27], [144, -15], [125, -15]]]);
*
* var area = turf.area(polygon);
*
* //addToMap
* var addToMap = [polygon]
* polygon.properties.area = area
*/
function area$1(geojson) {
return geomReduce(geojson, function (value, geom) {
return value + calculateArea(geom);
}, 0);
}
var RADIUS = 6378137;
// var FLATTENING_DENOM = 298.257223563;
// var FLATTENING = 1 / FLATTENING_DENOM;
// var POLAR_RADIUS = RADIUS * (1 - FLATTENING);
/**
* Calculate Area
*
* @private
* @param {GeoJSON} geojson GeoJSON
* @returns {number} area
*/
function calculateArea(geojson) {
var area = 0, i;
switch (geojson.type) {
case 'Polygon':
return polygonArea(geojson.coordinates);
case 'MultiPolygon':
for (i = 0; i < geojson.coordinates.length; i++) {
area += polygonArea(geojson.coordinates[i]);
}
return area;
case 'Point':
case 'MultiPoint':
case 'LineString':
case 'MultiLineString':
return 0;
case 'GeometryCollection':
for (i = 0; i < geojson.geometries.length; i++) {
area += calculateArea(geojson.geometries[i]);
}
return area;
}
}
function polygonArea(coords) {
var area = 0;
if (coords && coords.length > 0) {
var polygons = gene_polygon(coords[0]);
for (var i = 0; i < polygons.length; i++){
area += Math.abs(ringArea(polygons[i]));
}
for (var i = 1; i < coords.length; i++) {
area -= Math.abs(ringArea(coords[i]));
}
}
return area;
}
function gene_polygon(coords){
var current_points = [];
var sd_polygons = [];
for (var i = 0; i < coords.length; i++){
current_points = add_point_to_current(coords, coords[i], current_points, sd_polygons);
}
sd_polygons.push(current_points);
return sd_polygons;
}
/* 将该点加入到current_points 中,倒序遍历current_points中的点,如果能围成多边形,则将所围成的点弹出, 同时缓存自相交的多边形
:param coords:原始多边形坐标串
:param point:待校验的坐标串
:param current_points:所有未相交的坐标串
:param sd_polygons:所有自相交多边形
:return:
*/
function add_point_to_current(coords, point, current_points, sd_polygons){
var current_points_length = current_points.length;
if (current_points_length <= 2){
current_points.push(point);
return current_points;
}
var cross_point_dict = {}; // 记录线段与其他点的相交点,{0:P1,6:P2}
var l0 = {
p1: {
x: point[0],
y: point[1]
},
p2: {
x:current_points[current_points_length-1][0],
y:current_points[current_points_length-1][1]
}
};
for (var i = 0; i < current_points_length - 1; i++){
var line = {
p1: {
x: current_points[i][0],
y: current_points[i][1]
},
p2:{
x: current_points[i+1][0],
y: current_points[i+1][1]
}
};
var cross_point = get_cross_point(l0, line); //获取相交点
if (is_in_two_segment(cross_point, l0, line)) {// 如果相交点在两个线段上
cross_point_dict[i] = cross_point;
}
}
var flag_dict = {}; //保存剪下点的信息
var cross_points_list = []; //[(3,P),(1,P)]
for (var item in cross_point_dict){
cross_points_list.push([parseInt(item), cross_point_dict[item]]);
}
cross_points_list = cross_points_list.reverse();
for (var i = 0; i < cross_points_list.length; i++){
var cross_point_info = cross_points_list[i];
var cross_i = cross_point_info[0];
var cross_point = cross_point_info[1];
if (JSON.stringify(flag_dict) !== '{}'){//对应需要剪下多个多边形的情形,
var points = current_points.slice(cross_i + 1, flag_dict.index + 1);
points.push([flag_dict.point.x, flag_dict.point.y]);
points.push([cross_point.x, cross_point.y]);
points.push(current_points[cross_i + 1]);
sd_polygons.push(points);
} else{
var points = current_points.slice(cross_i + 1);
points.push([cross_point.x, cross_point.y]);
if (points.length <= 2) {
continue;
}
points.push(current_points[cross_i + 1]);
sd_polygons.push(points); // 将生成的polygon保存
flag_dict = {
index: cross_i,
point: cross_point
}
}
}
if (JSON.stringify(flag_dict) !== '{}'){
current_points = current_points.slice(0,flag_dict.index + 1); // 还未围成多边形的数组
current_points.push([flag_dict.point.x, flag_dict.point.y]); // 加上交点
}
current_points.push(point);
return current_points;
}
/* 得到交点
:param l1: 直线Line
:param l2:
:return: 交点坐标Point
*/
function get_cross_point(l1, l2){
get_line_para(l1);
get_line_para(l2);
var d = l1.a * l2.b - l2.a * l1.b;
var p = {x:-1, y:-1};
if (d == 0){
return p;
}
p.x = (l1.b * l2.c - l2.b * l1.c) / d
p.y = (l1.c * l2.a - l2.c * l1.a) / d
return p;
}
function get_line_para(line){
line.a = line.p1.y - line.p2.y;
line.b = line.p2.x - line.p1.x;
line.c = line.p1.x * line.p2.y - line.p2.x * line.p1.y;
}
/* 交点是否在线段上
:param point:(x,y)
:param point1:[(x1,y1),(x2,y2)]
:param point2:
:return:
*/
function is_in_segment(point, point1, point2){
var minx,miny,maxx,maxy;
if (point1.x > point2.x){
minx = point2.x;
maxx = point1.x;
} else{
minx = point1.x;
maxx = point2.x;
}
if (point1.y > point2.y){
miny = point2.y;
maxy = point1.y;
} else{
miny = point1.y;
maxy = point2.y;
}
if (point.x >= minx && point.x <= maxx && point.y >= miny && point.y <= maxy)
return true;
return false;
}
/* 点 是否在两段 线段中
:param point:
:param l1:
:param l2:
:return: 点在线段中,返回true;不在线段中,返回false
*/
function is_in_two_segment(point, l1, l2){
if (is_in_segment(point, l1.p1, l1.p2) && is_in_segment(point, l2.p1, l2.p2)){
if ((is_same_point(point, l1.p1) || is_same_point(point, l1.p2)) && (
is_same_point(point, l2.p1) || is_same_point(point, l2.p2)))
return false;
return true;
}
return false;
}
/* 判断点是否相同
param p1:
param p2:
return:
*/
function is_same_point(p1, p2){
if (abs(p1.x - p2.x) < 0.000001 && abs(p1.y - p2.y) < 0.000001)
return true;
return false;
}
/**
* @private
* Calculate the approximate area of the polygon were it projected onto the earth.
* Note that this area will be positive if ring is oriented clockwise, otherwise it will be negative.
*
* Reference:
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
* Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
*
* @param {Array>} coords Ring Coordinates
* @returns {number} The approximate signed geodesic area of the polygon in square meters.
*/
function ringArea(coords) {
var p1;
var p2;
var p3;
var lowerIndex;
var middleIndex;
var upperIndex;
var i;
var area = 0;
var coordsLength = coords.length;
if (coordsLength > 2) {
for (i = 0; i < coordsLength; i++) {
if (i === coordsLength - 2) { // i = N-2
lowerIndex = coordsLength - 2;
middleIndex = coordsLength - 1;
upperIndex = 0;
} else if (i === coordsLength - 1) { // i = N-1
lowerIndex = coordsLength - 1;
middleIndex = 0;
upperIndex = 1;
} else { // i = 0 to N-3
lowerIndex = i;
middleIndex = i + 1;
upperIndex = i + 2;
}
p1 = coords[lowerIndex];
p2 = coords[middleIndex];
p3 = coords[upperIndex];
area += (rad(p3[0]) - rad(p1[0])) * Math.sin(rad(p2[1]));
}
area = area * RADIUS * RADIUS / 2;
}
return area;
}
function rad(_) {
return _ * Math.PI / 180;
}
/**
* Takes a {@link LineString} and returns a {@link Point} at a specified distance along the line.
*
* @name along
* @param {Feature} line input line
* @param {number} distance distance along the line
* @param {Object} [options] Optional parameters
* @param {string} [options.units="kilometers"] can be degrees, radians, miles, or kilometers
* @returns {Feature} Point `distance` `units` along the line
* @example
* var line = turf.lineString([[-83, 30], [-84, 36], [-78, 41]]);
* var options = {units: 'miles'};
*
* var along = turf.along(line, 200, options);
*
* //addToMap
* var addToMap = [along, line]
*/
function along(line, distance$$1, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
// Validation
var coords;
if (line.type === 'Feature') coords = line.geometry.coordinates;
else if (line.type === 'LineString') coords = line.coordinates;
else throw new Error('input must be a LineString Feature or Geometry');
if (!isNumber(distance$$1)) throw new Error('distance must be a number');
var travelled = 0;
for (var i = 0; i < coords.length; i++) {
if (distance$$1 >= travelled && i === coords.length - 1) break;
else if (travelled >= distance$$1) {
var overshot = distance$$1 - travelled;
if (!overshot) return point(coords[i]);
else {
var direction = bearing(coords[i], coords[i - 1]) - 180;
var interpolated = destination(coords[i], overshot, direction, options);
return interpolated;
}
} else {
travelled += distance(coords[i], coords[i + 1], options);
}
}
return point(coords[coords.length - 1]);
}
/**
* Takes a {@link GeoJSON} and measures its length in the specified units, {@link (Multi)Point}'s distance are ignored.
*
* @name length
* @param {GeoJSON} geojson GeoJSON to measure
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units=kilometers] can be degrees, radians, miles, or kilometers
* @returns {number} length of GeoJSON
* @example
* var line = turf.lineString([[115, -32], [131, -22], [143, -25], [150, -34]]);
* var length = turf.length(line, {units: 'miles'});
*
* //addToMap
* var addToMap = [line];
* line.properties.distance = length;
*/
function length(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
// Input Validation
if (!geojson) throw new Error('geojson is required');
// Calculate distance from 2-vertex line segements
return segmentReduce(geojson, function (previousValue, segment) {
var coords = segment.geometry.coordinates;
return previousValue + distance(coords[0], coords[1], options);
}, 0);
}
/**
* Takes a {@link LineString|line}, a start {@link Point}, and a stop point
* and returns a subsection of the line in-between those points.
* The start & stop points don't need to fall exactly on the line.
*
* This can be useful for extracting only the part of a route between waypoints.
*
* @name lineSlice
* @param {Coord} startPt starting point
* @param {Coord} stopPt stopping point
* @param {Feature|LineString} line line to slice
* @returns {Feature} sliced line
* @example
* var line = turf.lineString([
* [-77.031669, 38.878605],
* [-77.029609, 38.881946],
* [-77.020339, 38.884084],
* [-77.025661, 38.885821],
* [-77.021884, 38.889563],
* [-77.019824, 38.892368]
* ]);
* var start = turf.point([-77.029609, 38.881946]);
* var stop = turf.point([-77.021884, 38.889563]);
*
* var sliced = turf.lineSlice(start, stop, line);
*
* //addToMap
* var addToMap = [start, stop, line]
*/
function lineSlice(startPt, stopPt, line) {
// Validation
var coords = getCoords(line);
if (getType(line) !== 'LineString') throw new Error('line must be a LineString');
var startVertex = nearestPointOnLine(line, startPt);
var stopVertex = nearestPointOnLine(line, stopPt);
var ends;
if (startVertex.properties.index <= stopVertex.properties.index) {
ends = [startVertex, stopVertex];
} else {
ends = [stopVertex, startVertex];
}
var clipCoords = [ends[0].geometry.coordinates];
for (var i = ends[0].properties.index + 1; i < ends[1].properties.index + 1; i++) {
clipCoords.push(coords[i]);
}
clipCoords.push(ends[1].geometry.coordinates);
return lineString(clipCoords, line.properties);
}
/**
* Takes a {@link LineString|line}, a specified distance along the line to a start {@link Point},
* and a specified distance along the line to a stop point
* and returns a subsection of the line in-between those points.
*
* This can be useful for extracting only the part of a route between two distances.
*
* @name lineSliceAlong
* @param {Feature|LineString} line input line
* @param {number} startDist distance along the line to starting point
* @param {number} stopDist distance along the line to ending point
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @returns {Feature} sliced line
* @example
* var line = turf.lineString([[7, 45], [9, 45], [14, 40], [14, 41]]);
* var start = 12.5;
* var stop = 25;
* var sliced = turf.lineSliceAlong(line, start, stop, {units: 'miles'});
*
* //addToMap
* var addToMap = [line, start, stop, sliced]
*/
function lineSliceAlong(line, startDist, stopDist, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var coords;
var slice = [];
// Validation
if (line.type === 'Feature') coords = line.geometry.coordinates;
else if (line.type === 'LineString') coords = line.coordinates;
else throw new Error('input must be a LineString Feature or Geometry');
var travelled = 0;
var overshot, direction, interpolated;
for (var i = 0; i < coords.length; i++) {
if (startDist >= travelled && i === coords.length - 1) break;
else if (travelled > startDist && slice.length === 0) {
overshot = startDist - travelled;
if (!overshot) {
slice.push(coords[i]);
return lineString(slice);
}
direction = bearing(coords[i], coords[i - 1]) - 180;
interpolated = destination(coords[i], overshot, direction, options);
slice.push(interpolated.geometry.coordinates);
}
if (travelled >= stopDist) {
overshot = stopDist - travelled;
if (!overshot) {
slice.push(coords[i]);
return lineString(slice);
}
direction = bearing(coords[i], coords[i - 1]) - 180;
interpolated = destination(coords[i], overshot, direction, options);
slice.push(interpolated.geometry.coordinates);
return lineString(slice);
}
if (travelled >= startDist) {
slice.push(coords[i]);
}
if (i === coords.length - 1) {
return lineString(slice);
}
travelled += distance(coords[i], coords[i + 1], options);
}
return lineString(coords[coords.length - 1]);
}
/**
* Returns true if a point is on a line. Accepts a optional parameter to ignore the start and end vertices of the linestring.
*
* @name booleanPointOnLine
* @param {Coord} pt GeoJSON Point
* @param {Feature} line GeoJSON LineString
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.ignoreEndVertices=false] whether to ignore the start and end vertices.
* @returns {boolean} true/false
* @example
* var pt = turf.point([0, 0]);
* var line = turf.lineString([[-1, -1],[1, 1],[1.5, 2.2]]);
* var isPointOnLine = turf.booleanPointOnLine(pt, line);
* //=true
*/
function booleanPointOnLine(pt, line, options) {
// Optional parameters
options = options || {};
var ignoreEndVertices = options.ignoreEndVertices;
if (!isObject(options)) throw new Error('invalid options');
// Validate input
if (!pt) throw new Error('pt is required');
if (!line) throw new Error('line is required');
// Normalize inputs
var ptCoords = getCoord(pt);
var lineCoords = getCoords(line);
// Main
for (var i = 0; i < lineCoords.length - 1; i++) {
var ignoreBoundary = false;
if (ignoreEndVertices) {
if (i === 0) ignoreBoundary = 'start';
if (i === lineCoords.length - 2) ignoreBoundary = 'end';
if (i === 0 && i + 1 === lineCoords.length - 1) ignoreBoundary = 'both';
}
if (isPointOnLineSegment$1(lineCoords[i], lineCoords[i + 1], ptCoords, ignoreBoundary)) return true;
}
return false;
}
// See http://stackoverflow.com/a/4833823/1979085
/**
* @private
* @param {Position} lineSegmentStart coord pair of start of line
* @param {Position} lineSegmentEnd coord pair of end of line
* @param {Position} pt coord pair of point to check
* @param {boolean|string} excludeBoundary whether the point is allowed to fall on the line ends. If true which end to ignore.
* @returns {boolean} true/false
*/
function isPointOnLineSegment$1(lineSegmentStart, lineSegmentEnd, pt, excludeBoundary) {
var x = pt[0];
var y = pt[1];
var x1 = lineSegmentStart[0];
var y1 = lineSegmentStart[1];
var x2 = lineSegmentEnd[0];
var y2 = lineSegmentEnd[1];
var dxc = pt[0] - x1;
var dyc = pt[1] - y1;
var dxl = x2 - x1;
var dyl = y2 - y1;
var cross = dxc * dyl - dyc * dxl;
if (cross !== 0) {
return false;
}
if (!excludeBoundary) {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 <= x && x <= x2 : x2 <= x && x <= x1;
}
return dyl > 0 ? y1 <= y && y <= y2 : y2 <= y && y <= y1;
} else if (excludeBoundary === 'start') {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 < x && x <= x2 : x2 <= x && x < x1;
}
return dyl > 0 ? y1 < y && y <= y2 : y2 <= y && y < y1;
} else if (excludeBoundary === 'end') {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 <= x && x < x2 : x2 < x && x <= x1;
}
return dyl > 0 ? y1 <= y && y < y2 : y2 < y && y <= y1;
} else if (excludeBoundary === 'both') {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 < x && x < x2 : x2 < x && x < x1;
}
return dyl > 0 ? y1 < y && y < y2 : y2 < y && y < y1;
}
}
/**
* Boolean-within returns true if the first geometry is completely within the second geometry.
* The interiors of both geometries must intersect and, the interior and boundary of the primary (geometry a)
* must not intersect the exterior of the secondary (geometry b).
* Boolean-within returns the exact opposite result of the `@turf/boolean-contains`.
*
* @name booleanWithin
* @param {Geometry|Feature} feature1 GeoJSON Feature or Geometry
* @param {Geometry|Feature} feature2 GeoJSON Feature or Geometry
* @returns {boolean} true/false
* @example
* var line = turf.lineString([[1, 1], [1, 2], [1, 3], [1, 4]]);
* var point = turf.point([1, 2]);
*
* turf.booleanWithin(point, line);
* //=true
*/
function booleanWithin(feature1, feature2) {
var type1 = getType(feature1);
var type2 = getType(feature2);
var geom1 = getGeom(feature1);
var geom2 = getGeom(feature2);
switch (type1) {
case 'Point':
switch (type2) {
case 'MultiPoint':
return isPointInMultiPoint(geom1, geom2);
case 'LineString':
return booleanPointOnLine(geom1, geom2, {ignoreEndVertices: true});
case 'Polygon':
return booleanPointInPolygon(geom1, geom2, {ignoreBoundary: true});
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
case 'MultiPoint':
switch (type2) {
case 'MultiPoint':
return isMultiPointInMultiPoint(geom1, geom2);
case 'LineString':
return isMultiPointOnLine(geom1, geom2);
case 'Polygon':
return isMultiPointInPoly(geom1, geom2);
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
case 'LineString':
switch (type2) {
case 'LineString':
return isLineOnLine(geom1, geom2);
case 'Polygon':
return isLineInPoly(geom1, geom2);
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
case 'Polygon':
switch (type2) {
case 'Polygon':
return isPolyInPoly(geom1, geom2);
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
default:
throw new Error('feature1 ' + type1 + ' geometry not supported');
}
}
function isPointInMultiPoint(point, multiPoint) {
var i;
var output = false;
for (i = 0; i < multiPoint.coordinates.length; i++) {
if (compareCoords(multiPoint.coordinates[i], point.coordinates)) {
output = true;
break;
}
}
return output;
}
function isMultiPointInMultiPoint(multiPoint1, multiPoint2) {
for (var i = 0; i < multiPoint1.coordinates.length; i++) {
var anyMatch = false;
for (var i2 = 0; i2 < multiPoint2.coordinates.length; i2++) {
if (compareCoords(multiPoint1.coordinates[i], multiPoint2.coordinates[i2])) {
anyMatch = true;
}
}
if (!anyMatch) {
return false;
}
}
return true;
}
function isMultiPointOnLine(multiPoint, lineString) {
var foundInsidePoint = false;
for (var i = 0; i < multiPoint.coordinates.length; i++) {
if (!booleanPointOnLine(multiPoint.coordinates[i], lineString)) {
return false;
}
if (!foundInsidePoint) {
foundInsidePoint = booleanPointOnLine(multiPoint.coordinates[i], lineString, {ignoreEndVertices: true});
}
}
return foundInsidePoint;
}
function isMultiPointInPoly(multiPoint, polygon) {
var output = true;
var oneInside = false;
for (var i = 0; i < multiPoint.coordinates.length; i++) {
var isInside = booleanPointInPolygon(multiPoint.coordinates[1], polygon);
if (!isInside) {
output = false;
break;
}
if (!oneInside) {
isInside = booleanPointInPolygon(multiPoint.coordinates[1], polygon, {ignoreBoundary: true});
}
}
return output && isInside;
}
function isLineOnLine(lineString1, lineString2) {
for (var i = 0; i < lineString1.coordinates.length; i++) {
if (!booleanPointOnLine(lineString1.coordinates[i], lineString2)) {
return false;
}
}
return true;
}
function isLineInPoly(linestring, polygon) {
var polyBbox = bbox(polygon);
var lineBbox = bbox(linestring);
if (!doBBoxOverlap(polyBbox, lineBbox)) {
return false;
}
var foundInsidePoint = false;
for (var i = 0; i < linestring.coordinates.length - 1; i++) {
if (!booleanPointInPolygon(linestring.coordinates[i], polygon)) {
return false;
}
if (!foundInsidePoint) {
foundInsidePoint = booleanPointInPolygon(linestring.coordinates[i], polygon, {ignoreBoundary: true});
}
if (!foundInsidePoint) {
var midpoint = getMidpoint(linestring.coordinates[i], linestring.coordinates[i + 1]);
foundInsidePoint = booleanPointInPolygon(midpoint, polygon, {ignoreBoundary: true});
}
}
return foundInsidePoint;
}
/**
* Is Polygon2 in Polygon1
* Only takes into account outer rings
*
* @private
* @param {Geometry|Feature} feature1 Polygon1
* @param {Geometry|Feature} feature2 Polygon2
* @returns {boolean} true/false
*/
function isPolyInPoly(feature1, feature2) {
var poly1Bbox = bbox(feature1);
var poly2Bbox = bbox(feature2);
if (!doBBoxOverlap(poly2Bbox, poly1Bbox)) {
return false;
}
for (var i = 0; i < feature1.coordinates[0].length; i++) {
if (!booleanPointInPolygon(feature1.coordinates[0][i], feature2)) {
return false;
}
}
return true;
}
function doBBoxOverlap(bbox1, bbox2) {
if (bbox1[0] > bbox2[0]) return false;
if (bbox1[2] < bbox2[2]) return false;
if (bbox1[1] > bbox2[1]) return false;
if (bbox1[3] < bbox2[3]) return false;
return true;
}
/**
* compareCoords
*
* @private
* @param {Position} pair1 point [x,y]
* @param {Position} pair2 point [x,y]
* @returns {boolean} true/false if coord pairs match
*/
function compareCoords(pair1, pair2) {
return pair1[0] === pair2[0] && pair1[1] === pair2[1];
}
/**
* getMidpoint
*
* @private
* @param {Position} pair1 point [x,y]
* @param {Position} pair2 point [x,y]
* @returns {Position} midpoint of pair1 and pair2
*/
function getMidpoint(pair1, pair2) {
return [(pair1[0] + pair2[0]) / 2, (pair1[1] + pair2[1]) / 2];
}
/**
* Creates a {@link Point} grid from a bounding box, {@link FeatureCollection} or {@link Feature}.
*
* @name pointGrid
* @param {Array} bbox extent in [minX, minY, maxX, maxY] order
* @param {number} cellSide the distance between points, in units
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] used in calculating cellSide, can be degrees, radians, miles, or kilometers
* @param {Feature} [options.mask] if passed a Polygon or MultiPolygon, the grid Points will be created only inside it
* @param {Object} [options.properties={}] passed to each point of the grid
* @returns {FeatureCollection} grid of points
* @example
* var extent = [-70.823364, -33.553984, -70.473175, -33.302986];
* var cellSide = 3;
* var options = {units: 'miles'};
*
* var grid = turf.pointGrid(extent, cellSide, options);
*
* //addToMap
* var addToMap = [grid];
*/
function pointGrid(bbox, cellSide, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
// var units = options.units;
var mask = options.mask;
var properties = options.properties;
// Containers
var results = [];
// Input Validation
if (cellSide === null || cellSide === undefined) throw new Error('cellSide is required');
if (!isNumber(cellSide)) throw new Error('cellSide is invalid');
if (!bbox) throw new Error('bbox is required');
if (!Array.isArray(bbox)) throw new Error('bbox must be array');
if (bbox.length !== 4) throw new Error('bbox must contain 4 numbers');
if (mask && ['Polygon', 'MultiPolygon'].indexOf(getType(mask)) === -1) throw new Error('options.mask must be a (Multi)Polygon');
var west = bbox[0];
var south = bbox[1];
var east = bbox[2];
var north = bbox[3];
var xFraction = cellSide / (distance([west, south], [east, south], options));
var cellWidth = xFraction * (east - west);
var yFraction = cellSide / (distance([west, south], [west, north], options));
var cellHeight = yFraction * (north - south);
var bboxWidth = (east - west);
var bboxHeight = (north - south);
var columns = Math.floor(bboxWidth / cellWidth);
var rows = Math.floor(bboxHeight / cellHeight);
// adjust origin of the grid
var deltaX = (bboxWidth - columns * cellWidth) / 2;
var deltaY = (bboxHeight - rows * cellHeight) / 2;
var currentX = west + deltaX;
while (currentX <= east) {
var currentY = south + deltaY;
while (currentY <= north) {
var cellPt = point([currentX, currentY], properties);
if (mask) {
if (booleanWithin(cellPt, mask)) results.push(cellPt);
} else {
results.push(cellPt);
}
currentY += cellHeight;
}
currentX += cellWidth;
}
return featureCollection(results);
}
/**
* Takes a GeoJSON Feature or FeatureCollection and truncates the precision of the geometry.
*
* @name truncate
* @param {GeoJSON} geojson any GeoJSON Feature, FeatureCollection, Geometry or GeometryCollection.
* @param {Object} [options={}] Optional parameters
* @param {number} [options.precision=6] coordinate decimal precision
* @param {number} [options.coordinates=3] maximum number of coordinates (primarly used to remove z coordinates)
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} layer with truncated geometry
* @example
* var point = turf.point([
* 70.46923055566859,
* 58.11088890802906,
* 1508
* ]);
* var options = {precision: 3, coordinates: 2};
* var truncated = turf.truncate(point, options);
* //=truncated.geometry.coordinates => [70.469, 58.111]
*
* //addToMap
* var addToMap = [truncated];
*/
function truncate(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var precision = options.precision;
var coordinates = options.coordinates;
var mutate = options.mutate;
// default params
precision = (precision === undefined || precision === null || isNaN(precision)) ? 6 : precision;
coordinates = (coordinates === undefined || coordinates === null || isNaN(coordinates)) ? 3 : coordinates;
// validation
if (!geojson) throw new Error(' is required');
if (typeof precision !== 'number') throw new Error(' must be a number');
if (typeof coordinates !== 'number') throw new Error(' must be a number');
// prevent input mutation
if (mutate === false || mutate === undefined) geojson = JSON.parse(JSON.stringify(geojson));
var factor = Math.pow(10, precision);
// Truncate Coordinates
coordEach(geojson, function (coords) {
truncateCoords(coords, factor, coordinates);
});
return geojson;
}
/**
* Truncate Coordinates - Mutates coordinates in place
*
* @private
* @param {Array} coords Geometry Coordinates
* @param {number} factor rounding factor for coordinate decimal precision
* @param {number} coordinates maximum number of coordinates (primarly used to remove z coordinates)
* @returns {Array} mutated coordinates
*/
function truncateCoords(coords, factor, coordinates) {
// Remove extra coordinates (usually elevation coordinates and more)
if (coords.length > coordinates) coords.splice(coordinates, coords.length);
// Truncate coordinate decimals
for (var i = 0; i < coords.length; i++) {
coords[i] = Math.round(coords[i] * factor) / factor;
}
return coords;
}
/**
* Flattens any {@link GeoJSON} to a {@link FeatureCollection} inspired by [geojson-flatten](https://github.com/tmcw/geojson-flatten).
*
* @name flatten
* @param {GeoJSON} geojson any valid GeoJSON Object
* @returns {FeatureCollection} all Multi-Geometries are flattened into single Features
* @example
* var multiGeometry = turf.multiPolygon([
* [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],
* [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
* [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]
* ]);
*
* var flatten = turf.flatten(multiGeometry);
*
* //addToMap
* var addToMap = [flatten]
*/
function flatten(geojson) {
if (!geojson) throw new Error('geojson is required');
var results = [];
flattenEach(geojson, function (feature$$1) {
results.push(feature$$1);
});
return featureCollection(results);
}
/**
* Divides a {@link LineString} into chunks of a specified length.
* If the line is shorter than the segment length then the original line is returned.
*
* @name lineChunk
* @param {FeatureCollection|Geometry|Feature} geojson the lines to split
* @param {number} segmentLength how long to make each segment
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] units can be degrees, radians, miles, or kilometers
* @param {boolean} [options.reverse=false] reverses coordinates to start the first chunked segment at the end
* @returns {FeatureCollection} collection of line segments
* @example
* var line = turf.lineString([[-95, 40], [-93, 45], [-85, 50]]);
*
* var chunk = turf.lineChunk(line, 15, {units: 'miles'});
*
* //addToMap
* var addToMap = [chunk];
*/
function lineChunk(geojson, segmentLength, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var units = options.units;
var reverse = options.reverse;
// Validation
if (!geojson) throw new Error('geojson is required');
if (segmentLength <= 0) throw new Error('segmentLength must be greater than 0');
// Container
var results = [];
// Flatten each feature to simple LineString
flattenEach(geojson, function (feature$$1) {
// reverses coordinates to start the first chunked segment at the end
if (reverse) feature$$1.geometry.coordinates = feature$$1.geometry.coordinates.reverse();
sliceLineSegments(feature$$1, segmentLength, units, function (segment) {
results.push(segment);
});
});
return featureCollection(results);
}
/**
* Slice Line Segments
*
* @private
* @param {Feature} line GeoJSON LineString
* @param {number} segmentLength how long to make each segment
* @param {string}[units='kilometers'] units can be degrees, radians, miles, or kilometers
* @param {Function} callback iterate over sliced line segments
* @returns {void}
*/
function sliceLineSegments(line, segmentLength, units, callback) {
var lineLength = length(line, {units: units});
// If the line is shorter than the segment length then the orginal line is returned.
if (lineLength <= segmentLength) return callback(line);
var numberOfSegments = lineLength / segmentLength;
// If numberOfSegments is integer, no need to plus 1
if (!Number.isInteger(numberOfSegments)) {
numberOfSegments = Math.floor(numberOfSegments) + 1;
}
for (var i = 0; i < numberOfSegments; i++) {
var outline = lineSliceAlong(line, segmentLength * i, segmentLength * (i + 1), {units: units});
callback(outline, i);
}
}
// Find self-intersections in geojson polygon (possibly with interior rings)
var isects = function (feature$$1, filterFn, useSpatialIndex) {
if (feature$$1.geometry.type !== 'Polygon') throw new Error('The input feature must be a Polygon');
if (useSpatialIndex === undefined) useSpatialIndex = 1;
var coord = feature$$1.geometry.coordinates;
var output = [];
var seen = {};
if (useSpatialIndex) {
var allEdgesAsRbushTreeItems = [];
for (var ring0 = 0; ring0 < coord.length; ring0++) {
for (var edge0 = 0; edge0 < coord[ring0].length - 1; edge0++) {
allEdgesAsRbushTreeItems.push(rbushTreeItem(ring0, edge0));
}
}
var tree = rbush_1();
tree.load(allEdgesAsRbushTreeItems);
}
for (var ringA = 0; ringA < coord.length; ringA++) {
for (var edgeA = 0; edgeA < coord[ringA].length - 1; edgeA++) {
if (useSpatialIndex) {
var bboxOverlaps = tree.search(rbushTreeItem(ringA, edgeA));
bboxOverlaps.forEach(function (bboxIsect) {
var ring1 = bboxIsect.ring;
var edge1 = bboxIsect.edge;
ifIsectAddToOutput(ringA, edgeA, ring1, edge1);
});
} else {
for (var ring1 = 0; ring1 < coord.length; ring1++) {
for (var edge1 = 0; edge1 < coord[ring1].length - 1; edge1++) {
// TODO: speedup possible if only interested in unique: start last two loops at ringA and edgeA+1
ifIsectAddToOutput(ringA, edgeA, ring1, edge1);
}
}
}
}
}
if (!filterFn) output = {type: 'Feature', geometry: {type: 'MultiPoint', coordinates: output}};
return output;
// Function to check if two edges intersect and add the intersection to the output
function ifIsectAddToOutput(ring0, edge0, ring1, edge1) {
var start0 = coord[ring0][edge0];
var end0 = coord[ring0][edge0 + 1];
var start1 = coord[ring1][edge1];
var end1 = coord[ring1][edge1 + 1];
var isect = intersect(start0, end0, start1, end1);
if (isect === null) return; // discard parallels and coincidence
var frac0;
var frac1;
if (end0[0] !== start0[0]) {
frac0 = (isect[0] - start0[0]) / (end0[0] - start0[0]);
} else {
frac0 = (isect[1] - start0[1]) / (end0[1] - start0[1]);
}
if (end1[0] !== start1[0]) {
frac1 = (isect[0] - start1[0]) / (end1[0] - start1[0]);
} else {
frac1 = (isect[1] - start1[1]) / (end1[1] - start1[1]);
}
if (frac0 >= 1 || frac0 <= 0 || frac1 >= 1 || frac1 <= 0) return; // require segment intersection
var key = isect;
var unique = !seen[key];
if (unique) {
seen[key] = true;
}
if (filterFn) {
output.push(filterFn(isect, ring0, edge0, start0, end0, frac0, ring1, edge1, start1, end1, frac1, unique));
} else {
output.push(isect);
}
}
// Function to return a rbush tree item given an ring and edge number
function rbushTreeItem(ring, edge) {
var start = coord[ring][edge];
var end = coord[ring][edge + 1];
var minX;
var maxX;
var minY;
var maxY;
if (start[0] < end[0]) {
minX = start[0];
maxX = end[0];
} else {
minX = end[0];
maxX = start[0];
}
if (start[1] < end[1]) {
minY = start[1];
maxY = end[1];
} else {
minY = end[1];
maxY = start[1];
}
return {minX: minX, minY: minY, maxX: maxX, maxY: maxY, ring: ring, edge: edge};
}
};
// Function to compute where two lines (not segments) intersect. From https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
function intersect(start0, end0, start1, end1) {
if (equalArrays$1(start0, start1) || equalArrays$1(start0, end1) || equalArrays$1(end0, start1) || equalArrays$1(end1, start1)) return null;
var x0 = start0[0],
y0 = start0[1],
x1 = end0[0],
y1 = end0[1],
x2 = start1[0],
y2 = start1[1],
x3 = end1[0],
y3 = end1[1];
var denom = (x0 - x1) * (y2 - y3) - (y0 - y1) * (x2 - x3);
if (denom === 0) return null;
var x4 = ((x0 * y1 - y0 * x1) * (x2 - x3) - (x0 - x1) * (x2 * y3 - y2 * x3)) / denom;
var y4 = ((x0 * y1 - y0 * x1) * (y2 - y3) - (y0 - y1) * (x2 * y3 - y2 * x3)) / denom;
return [x4, y4];
}
// Function to compare Arrays of numbers. From http://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript
function equalArrays$1(array1, array2) {
// if the other array is a falsy value, return
if (!array1 || !array2)
return false;
// compare lengths - can save a lot of time
if (array1.length !== array2.length)
return false;
for (var i = 0, l = array1.length; i < l; i++) {
// Check if we have nested arrays
if (array1[i] instanceof Array && array2[i] instanceof Array) {
// recurse into the nested arrays
if (!equalArrays$1(array1[i], array2[i]))
return false;
} else if (array1[i] !== array2[i]) {
// Warning - two different object instances will never be equal: {x:20} !== {x:20}
return false;
}
}
return true;
}
/**
* Takes a complex (i.e. self-intersecting) geojson polygon, and breaks it down into its composite simple, non-self-intersecting one-ring polygons.
*
* @module simplepolygon
* @param {Feature} feature Input polygon. This polygon may be unconform the {@link https://en.wikipedia.org/wiki/Simple_Features|Simple Features standard} in the sense that it's inner and outer rings may cross-intersect or self-intersect, that the outer ring must not contain the optional inner rings and that the winding number must not be positive for the outer and negative for the inner rings.
* @return {FeatureCollection} Feature collection containing the simple, non-self-intersecting one-ring polygon features that the complex polygon is composed of. These simple polygons have properties such as their parent polygon, winding number and net winding number.
*
* @example
* var poly = {
* "type": "Feature",
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[0,0],[2,0],[0,2],[2,2],[0,0]]]
* }
* };
*
* var result = simplepolygon(poly);
*
* // =result
* // which will be a featureCollection of two polygons, one with coordinates [[[0,0],[2,0],[1,1],[0,0]]], parent -1, winding 1 and net winding 1, and one with coordinates [[[1,1],[0,2],[2,2],[1,1]]], parent -1, winding -1 and net winding -1
*/
var simplepolygon = function (feature$$1) {
// Check input
if (feature$$1.type != 'Feature') throw new Error('The input must a geojson object of type Feature');
if ((feature$$1.geometry === undefined) || (feature$$1.geometry == null)) throw new Error('The input must a geojson object with a non-empty geometry');
if (feature$$1.geometry.type != 'Polygon') throw new Error('The input must be a geojson Polygon');
// Process input
var numRings = feature$$1.geometry.coordinates.length;
var vertices = [];
for (var i = 0; i < numRings; i++) {
var ring = feature$$1.geometry.coordinates[i];
if (!equalArrays(ring[0], ring[ring.length - 1])) {
ring.push(ring[0]); // Close input ring if it is not
}
vertices.push.apply(vertices, ring.slice(0, ring.length - 1));
}
if (!isUnique(vertices)) throw new Error('The input polygon may not have duplicate vertices (except for the first and last vertex of each ring)');
var numvertices = vertices.length; // number of input ring vertices, with the last closing vertices not counted
// Compute self-intersections
var selfIsectsData = isects(feature$$1, function filterFn(isect, ring0, edge0, start0, end0, frac0, ring1, edge1, start1, end1, frac1, unique) {
return [isect, ring0, edge0, start0, end0, frac0, ring1, edge1, start1, end1, frac1, unique];
});
var numSelfIsect = selfIsectsData.length;
// If no self-intersections are found, the input rings are the output rings. Hence, we must only compute their winding numbers, net winding numbers and (since ohers rings could lie outside the first ring) parents.
if (numSelfIsect == 0) {
var outputFeatureArray = [];
for (var i = 0; i < numRings; i++) {
outputFeatureArray.push(polygon([feature$$1.geometry.coordinates[i]], {parent: -1, winding: windingOfRing(feature$$1.geometry.coordinates[i])}));
}
var output = featureCollection(outputFeatureArray);
determineParents();
setNetWinding();
return output;
}
// If self-intersections are found, we will compute the output rings with the help of two intermediate variables
// First, we build the pseudo vertex list and intersection list
// The Pseudo vertex list is an array with for each ring an array with for each edge an array containing the pseudo-vertices (as made by their constructor) that have this ring and edge as ringAndEdgeIn, sorted for each edge by their fractional distance on this edge. It's length hence equals numRings.
var pseudoVtxListByRingAndEdge = [];
// The intersection list is an array containing intersections (as made by their constructor). First all numvertices ring-vertex-intersections, then all self-intersections (intra- and inter-ring). The order of the latter is not important but is permanent once given.
var isectList = [];
// Adding ring-pseudo-vertices to pseudoVtxListByRingAndEdge and ring-vertex-intersections to isectList
for (var i = 0; i < numRings; i++) {
pseudoVtxListByRingAndEdge.push([]);
for (var j = 0; j < feature$$1.geometry.coordinates[i].length - 1; j++) {
// Each edge will feature one ring-pseudo-vertex in its array, on the last position. i.e. edge j features the ring-pseudo-vertex of the ring vertex j+1, which has ringAndEdgeIn = [i,j], on the last position.
pseudoVtxListByRingAndEdge[i].push([new PseudoVtx(feature$$1.geometry.coordinates[i][(j + 1).modulo(feature$$1.geometry.coordinates[i].length - 1)], 1, [i, j], [i, (j + 1).modulo(feature$$1.geometry.coordinates[i].length - 1)], undefined)]);
// The first numvertices elements in isectList correspond to the ring-vertex-intersections
isectList.push(new Isect(feature$$1.geometry.coordinates[i][j], [i, (j - 1).modulo(feature$$1.geometry.coordinates[i].length - 1)], [i, j], undefined, undefined, false, true));
}
}
// Adding intersection-pseudo-vertices to pseudoVtxListByRingAndEdge and self-intersections to isectList
for (var i = 0; i < numSelfIsect; i++) {
// Adding intersection-pseudo-vertices made using selfIsectsData to pseudoVtxListByRingAndEdge's array corresponding to the incomming ring and edge
pseudoVtxListByRingAndEdge[selfIsectsData[i][1]][selfIsectsData[i][2]].push(new PseudoVtx(selfIsectsData[i][0], selfIsectsData[i][5], [selfIsectsData[i][1], selfIsectsData[i][2]], [selfIsectsData[i][6], selfIsectsData[i][7]], undefined));
// selfIsectsData contains double mentions of each intersection, but we only want to add them once to isectList
if (selfIsectsData[i][11]) isectList.push(new Isect(selfIsectsData[i][0], [selfIsectsData[i][1], selfIsectsData[i][2]], [selfIsectsData[i][6], selfIsectsData[i][7]], undefined, undefined, true, true));
}
var numIsect = isectList.length;
// Sort edge arrays of pseudoVtxListByRingAndEdge by the fractional distance 'param'
for (var i = 0; i < pseudoVtxListByRingAndEdge.length; i++) {
for (var j = 0; j < pseudoVtxListByRingAndEdge[i].length; j++) {
pseudoVtxListByRingAndEdge[i][j].sort(function (a, b) { return (a.param < b.param) ? -1 : 1; });
}
}
// Make a spatial index of intersections, in preperation for the following two steps
var allIsectsAsIsectRbushTreeItem = [];
for (var i = 0; i < numIsect; i++) {
allIsectsAsIsectRbushTreeItem.push({minX: isectList[i].coord[0], minY: isectList[i].coord[1], maxX: isectList[i].coord[0], maxY: isectList[i].coord[1], index: i}); // could pass isect: isectList[i], but not necessary
}
var isectRbushTree = rbush_1();
isectRbushTree.load(allIsectsAsIsectRbushTreeItem);
// Now we will teach each intersection in isectList which is the next intersection along both it's [ring, edge]'s, in two steps.
// First, we find the next intersection for each pseudo-vertex in pseudoVtxListByRingAndEdge:
// For each pseudovertex in pseudoVtxListByRingAndEdge (3 loops) look at the next pseudovertex on that edge and find the corresponding intersection by comparing coordinates
for (var i = 0; i < pseudoVtxListByRingAndEdge.length; i++) {
for (var j = 0; j < pseudoVtxListByRingAndEdge[i].length; j++) {
for (var k = 0; k < pseudoVtxListByRingAndEdge[i][j].length; k++) {
var coordToFind;
if (k == pseudoVtxListByRingAndEdge[i][j].length - 1) { // If it's the last pseudoVertex on that edge, then the next pseudoVertex is the first one on the next edge of that ring.
coordToFind = pseudoVtxListByRingAndEdge[i][(j + 1).modulo(feature$$1.geometry.coordinates[i].length - 1)][0].coord;
} else {
coordToFind = pseudoVtxListByRingAndEdge[i][j][k + 1].coord;
}
var IsectRbushTreeItemFound = isectRbushTree.search({minX: coordToFind[0], minY: coordToFind[1], maxX: coordToFind[0], maxY: coordToFind[1]})[0]; // We can take [0] of the result, because there is only one isect correponding to a pseudo-vertex
pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn = IsectRbushTreeItemFound.index;
}
}
}
// Second, we port this knowledge of the next intersection over to the intersections in isectList, by finding the intersection corresponding to each pseudo-vertex and copying the pseudo-vertex' knownledge of the next-intersection over to the intersection
for (var i = 0; i < pseudoVtxListByRingAndEdge.length; i++) {
for (var j = 0; j < pseudoVtxListByRingAndEdge[i].length; j++) {
for (var k = 0; k < pseudoVtxListByRingAndEdge[i][j].length; k++) {
var coordToFind = pseudoVtxListByRingAndEdge[i][j][k].coord;
var IsectRbushTreeItemFound = isectRbushTree.search({minX: coordToFind[0], minY: coordToFind[1], maxX: coordToFind[0], maxY: coordToFind[1]})[0]; // We can take [0] of the result, because there is only one isect correponding to a pseudo-vertex
var l = IsectRbushTreeItemFound.index;
if (l < numvertices) { // Special treatment at ring-vertices: we correct the misnaming that happened in the previous block, since ringAndEdgeOut = ringAndEdge2 for ring vertices.
isectList[l].nxtIsectAlongRingAndEdge2 = pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn;
} else { // Port the knowledge of the next intersection from the pseudo-vertices to the intersections, depending on how the edges are labeled in the pseudo-vertex and intersection.
if (equalArrays(isectList[l].ringAndEdge1, pseudoVtxListByRingAndEdge[i][j][k].ringAndEdgeIn)) {
isectList[l].nxtIsectAlongRingAndEdge1 = pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn;
} else {
isectList[l].nxtIsectAlongRingAndEdge2 = pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn;
}
}
}
}
}
// This explains why, eventhough when we will walk away from an intersection, we will walk way from the corresponding pseudo-vertex along edgeOut, pseudo-vertices have the property 'nxtIsectAlongEdgeIn' in stead of some propery 'nxtPseudoVtxAlongEdgeOut'. This is because this property (which is easy to find out) is used in the above for nxtIsectAlongRingAndEdge1 and nxtIsectAlongRingAndEdge2!
// Before we start walking over the intersections to build the output rings, we prepare a queue that stores information on intersections we still have to deal with, and put at least one intersection in it.
// This queue will contain information on intersections where we can start walking from once the current walk is finished, and its parent output ring (the smallest output ring it lies within, -1 if no parent or parent unknown yet) and its winding number (which we can already determine).
var queue = [];
// For each output ring, add the ring-vertex-intersection with the smalles x-value (i.e. the left-most) as a start intersection. By choosing such an extremal intersections, we are sure to start at an intersection that is a convex vertex of its output ring. By adding them all to the queue, we are sure that no rings will be forgotten. If due to ring-intersections such an intersection will be encountered while walking, it will be removed from the queue.
var i = 0;
for (var j = 0; j < numRings; j++) {
var leftIsect = i;
for (var k = 0; k < feature$$1.geometry.coordinates[j].length - 1; k++) {
if (isectList[i].coord[0] < isectList[leftIsect].coord[0]) {
leftIsect = i;
}
i++;
}
// Compute winding at this left-most ring-vertex-intersection. We thus this by using our knowledge that this extremal vertex must be a convex vertex.
// We first find the intersection before and after it, and then use them to determine the winding number of the corresponding output ring, since we know that an extremal vertex of a simple, non-self-intersecting ring is always convex, so the only reason it would not be is because the winding number we use to compute it is wrong
var isectAfterLeftIsect = isectList[leftIsect].nxtIsectAlongRingAndEdge2;
for (var k = 0; k < isectList.length; k++) {
if ((isectList[k].nxtIsectAlongRingAndEdge1 == leftIsect) || (isectList[k].nxtIsectAlongRingAndEdge2 == leftIsect)) {
var isectBeforeLeftIsect = k;
break;
}
}
var windingAtIsect = isConvex([isectList[isectBeforeLeftIsect].coord, isectList[leftIsect].coord, isectList[isectAfterLeftIsect].coord], true) ? 1 : -1;
queue.push({isect: leftIsect, parent: -1, winding: windingAtIsect});
}
// Sort the queue by the same criterion used to find the leftIsect: the left-most leftIsect must be last in the queue, such that it will be popped first, such that we will work from out to in regarding input rings. This assumtion is used when predicting the winding number and parent of a new queue member.
queue.sort(function (a, b) { return (isectList[a.isect].coord > isectList[b.isect].coord) ? -1 : 1; });
// Initialise output
var outputFeatureArray = [];
// While the queue is not empty, take the last object (i.e. its intersection) out and start making an output ring by walking in the direction that has not been walked away over yet.
while (queue.length > 0) {
// Get the last object out of the queue
var popped = queue.pop();
var startIsect = popped.isect;
var currentOutputRingParent = popped.parent;
var currentOutputRingWinding = popped.winding;
// Make new output ring and add vertex from starting intersection
var currentOutputRing = outputFeatureArray.length;
var currentOutputRingCoords = [isectList[startIsect].coord];
// Set up the variables used while walking over intersections: 'currentIsect', 'nxtIsect' and 'walkingRingAndEdge'
var currentIsect = startIsect;
if (isectList[startIsect].ringAndEdge1Walkable) {
var walkingRingAndEdge = isectList[startIsect].ringAndEdge1;
var nxtIsect = isectList[startIsect].nxtIsectAlongRingAndEdge1;
} else {
var walkingRingAndEdge = isectList[startIsect].ringAndEdge2;
var nxtIsect = isectList[startIsect].nxtIsectAlongRingAndEdge2;
}
// While we have not arrived back at the same intersection, keep walking
while (!equalArrays(isectList[startIsect].coord, isectList[nxtIsect].coord)) {
currentOutputRingCoords.push(isectList[nxtIsect].coord);
// If the next intersection is queued, we can remove it, because we will go there now.
var nxtIsectInQueue = undefined;
for (var i = 0; i < queue.length; i++) { if (queue[i].isect == nxtIsect) { nxtIsectInQueue = i; break; } }
if (nxtIsectInQueue != undefined) {
queue.splice(nxtIsectInQueue, 1);
}
// Arriving at this new intersection, we know which will be our next walking ring and edge (if we came from 1 we will walk away from 2 and vice versa),
// So we can set it as our new walking ring and intersection and remember that we (will) have walked over it
// If we have never walked away from this new intersection along the other ring and edge then we will soon do, add the intersection (and the parent wand winding number) to the queue
// (We can predict the winding number and parent as follows: if the edge is convex, the other output ring started from there will have the alternate winding and lie outside of the current one, and thus have the same parent ring as the current ring. Otherwise, it will have the same winding number and lie inside of the current ring. We are, however, only sure of this of an output ring started from there does not enclose the current ring. This is why the initial queue's intersections must be sorted such that outer ones come out first.)
// We then update the other two walking variables.
if (equalArrays(walkingRingAndEdge, isectList[nxtIsect].ringAndEdge1)) {
walkingRingAndEdge = isectList[nxtIsect].ringAndEdge2;
isectList[nxtIsect].ringAndEdge2Walkable = false;
if (isectList[nxtIsect].ringAndEdge1Walkable) {
var pushing = {isect: nxtIsect};
if (isConvex([isectList[currentIsect].coord, isectList[nxtIsect].coord, isectList[isectList[nxtIsect].nxtIsectAlongRingAndEdge2].coord], currentOutputRingWinding == 1)) {
pushing.parent = currentOutputRingParent;
pushing.winding = -currentOutputRingWinding;
} else {
pushing.parent = currentOutputRing;
pushing.winding = currentOutputRingWinding;
}
queue.push(pushing);
}
currentIsect = nxtIsect;
nxtIsect = isectList[nxtIsect].nxtIsectAlongRingAndEdge2;
} else {
walkingRingAndEdge = isectList[nxtIsect].ringAndEdge1;
isectList[nxtIsect].ringAndEdge1Walkable = false;
if (isectList[nxtIsect].ringAndEdge2Walkable) {
var pushing = {isect: nxtIsect};
if (isConvex([isectList[currentIsect].coord, isectList[nxtIsect].coord, isectList[isectList[nxtIsect].nxtIsectAlongRingAndEdge1].coord], currentOutputRingWinding == 1)) {
pushing.parent = currentOutputRingParent;
pushing.winding = -currentOutputRingWinding;
} else {
pushing.parent = currentOutputRing;
pushing.winding = currentOutputRingWinding;
}
queue.push(pushing);
}
currentIsect = nxtIsect;
nxtIsect = isectList[nxtIsect].nxtIsectAlongRingAndEdge1;
}
}
// Close output ring
currentOutputRingCoords.push(isectList[nxtIsect].coord);
// Push output ring to output
outputFeatureArray.push(polygon([currentOutputRingCoords], {index: currentOutputRing, parent: currentOutputRingParent, winding: currentOutputRingWinding, netWinding: undefined}));
}
var output = featureCollection(outputFeatureArray);
determineParents();
setNetWinding();
// These functions are also used if no intersections are found
function determineParents() {
var featuresWithoutParent = [];
for (var i = 0; i < output.features.length; i++) {
if (output.features[i].properties.parent == -1) featuresWithoutParent.push(i);
}
if (featuresWithoutParent.length > 1) {
for (var i = 0; i < featuresWithoutParent.length; i++) {
var parent = -1;
var parentArea = Infinity;
for (var j = 0; j < output.features.length; j++) {
if (featuresWithoutParent[i] == j) continue;
if (booleanPointInPolygon(output.features[featuresWithoutParent[i]].geometry.coordinates[0][0], output.features[j], {ignoreBoundary: true})) {
if (area$1(output.features[j]) < parentArea) {
parent = j;
}
}
}
output.features[featuresWithoutParent[i]].properties.parent = parent;
}
}
}
function setNetWinding() {
for (var i = 0; i < output.features.length; i++) {
if (output.features[i].properties.parent == -1) {
var netWinding = output.features[i].properties.winding;
output.features[i].properties.netWinding = netWinding;
setNetWindingOfChildren(i, netWinding);
}
}
}
function setNetWindingOfChildren(parent, ParentNetWinding) {
for (var i = 0; i < output.features.length; i++) {
if (output.features[i].properties.parent == parent) {
var netWinding = ParentNetWinding + output.features[i].properties.winding;
output.features[i].properties.netWinding = netWinding;
setNetWindingOfChildren(i, netWinding);
}
}
}
return output;
};
// Constructor for (ring- or intersection-) pseudo-vertices.
var PseudoVtx = function (coord, param, ringAndEdgeIn, ringAndEdgeOut, nxtIsectAlongEdgeIn) {
this.coord = coord; // [x,y] of this pseudo-vertex
this.param = param; // fractional distance of this intersection on incomming edge
this.ringAndEdgeIn = ringAndEdgeIn; // [ring index, edge index] of incomming edge
this.ringAndEdgeOut = ringAndEdgeOut; // [ring index, edge index] of outgoing edge
this.nxtIsectAlongEdgeIn = nxtIsectAlongEdgeIn; // The next intersection when following the incomming edge (so not when following ringAndEdgeOut!)
};
// Constructor for an intersection. There are two intersection-pseudo-vertices per self-intersection and one ring-pseudo-vertex per ring-vertex-intersection. Their labels 1 and 2 are not assigned a particular meaning but are permanent once given.
var Isect = function (coord, ringAndEdge1, ringAndEdge2, nxtIsectAlongRingAndEdge1, nxtIsectAlongRingAndEdge2, ringAndEdge1Walkable, ringAndEdge2Walkable) {
this.coord = coord; // [x,y] of this intersection
this.ringAndEdge1 = ringAndEdge1; // first edge of this intersection
this.ringAndEdge2 = ringAndEdge2; // second edge of this intersection
this.nxtIsectAlongRingAndEdge1 = nxtIsectAlongRingAndEdge1; // the next intersection when following ringAndEdge1
this.nxtIsectAlongRingAndEdge2 = nxtIsectAlongRingAndEdge2; // the next intersection when following ringAndEdge2
this.ringAndEdge1Walkable = ringAndEdge1Walkable; // May we (still) walk away from this intersection over ringAndEdge1?
this.ringAndEdge2Walkable = ringAndEdge2Walkable; // May we (still) walk away from this intersection over ringAndEdge2?
};
// Function to determine if three consecutive points of a simple, non-self-intersecting ring make up a convex vertex, assuming the ring is right- or lefthanded
function isConvex(pts, righthanded) {
// 'pts' is an [x,y] pair
// 'righthanded' is a boolean
if (typeof (righthanded) === 'undefined') righthanded = true;
if (pts.length != 3) throw new Error('This function requires an array of three points [x,y]');
var d = (pts[1][0] - pts[0][0]) * (pts[2][1] - pts[0][1]) - (pts[1][1] - pts[0][1]) * (pts[2][0] - pts[0][0]);
return (d >= 0) == righthanded;
}
// Function to compute winding of simple, non-self-intersecting ring
function windingOfRing(ring) {
// 'ring' is an array of [x,y] pairs with the last equal to the first
// Compute the winding number based on the vertex with the smallest x-value, it precessor and successor. An extremal vertex of a simple, non-self-intersecting ring is always convex, so the only reason it is not is because the winding number we use to compute it is wrong
var leftVtx = 0;
for (var i = 0; i < ring.length - 1; i++) { if (ring[i][0] < ring[leftVtx][0]) leftVtx = i; }
if (isConvex([ring[(leftVtx - 1).modulo(ring.length - 1)], ring[leftVtx], ring[(leftVtx + 1).modulo(ring.length - 1)]], true)) {
var winding = 1;
} else {
var winding = -1;
}
return winding;
}
// Function to compare Arrays of numbers. From http://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript
function equalArrays(array1, array2) {
// if the other array is a falsy value, return
if (!array1 || !array2)
return false;
// compare lengths - can save a lot of time
if (array1.length != array2.length)
return false;
for (var i = 0, l = array1.length; i < l; i++) {
// Check if we have nested arrays
if (array1[i] instanceof Array && array2[i] instanceof Array) {
// recurse into the nested arrays
if (!equalArrays(array1[i], array2[i]))
return false;
} else if (array1[i] != array2[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}
// Fix Javascript modulo for negative number. From http://stackoverflow.com/questions/4467539/javascript-modulo-not-behaving
Number.prototype.modulo = function (n) {
return ((this % n) + n) % n;
};
// Function to check if array is unique (i.e. all unique elements, i.e. no duplicate elements)
function isUnique(array) {
var u = {};
var isUnique = 1;
for (var i = 0, l = array.length; i < l; ++i) {
if (u.hasOwnProperty(array[i])) {
isUnique = 0;
break;
}
u[array[i]] = 1;
}
return isUnique;
}
/**
* Takes a kinked polygon and returns a feature collection of polygons that have no kinks.
* Uses [simplepolygon](https://github.com/mclaeysb/simplepolygon) internally.
*
* @name unkinkPolygon
* @param {FeatureCollection|Feature} geojson GeoJSON Polygon or MultiPolygon
* @returns {FeatureCollection} Unkinked polygons
* @example
* var poly = turf.polygon([[[0, 0], [2, 0], [0, 2], [2, 2], [0, 0]]]);
*
* var result = turf.unkinkPolygon(poly);
*
* //addToMap
* var addToMap = [poly, result]
*/
function unkinkPolygon(geojson) {
var features = [];
flattenEach(geojson, function (feature$$1) {
if (feature$$1.geometry.type !== 'Polygon') return;
featureEach(simplepolygon(feature$$1), function (poly) {
features.push(polygon(poly.geometry.coordinates, feature$$1.properties));
});
});
return featureCollection(features);
}
var D2R = Math.PI / 180;
var R2D = 180 / Math.PI;
var Coord = function (lon, lat) {
this.lon = lon;
this.lat = lat;
this.x = D2R * lon;
this.y = D2R * lat;
};
Coord.prototype.view = function () {
return String(this.lon).slice(0, 4) + ',' + String(this.lat).slice(0, 4);
};
Coord.prototype.antipode = function () {
var anti_lat = -1 * this.lat;
var anti_lon = (this.lon < 0) ? 180 + this.lon : (180 - this.lon) * -1;
return new Coord(anti_lon, anti_lat);
};
var LineString = function () {
this.coords = [];
this.length = 0;
};
LineString.prototype.move_to = function (coord) {
this.length++;
this.coords.push(coord);
};
var Arc = function (properties) {
this.properties = properties || {};
this.geometries = [];
};
Arc.prototype.json = function () {
if (this.geometries.length <= 0) {
return {'geometry': { 'type': 'LineString', 'coordinates': null },
'type': 'Feature', 'properties': this.properties
};
} else if (this.geometries.length === 1) {
return {'geometry': { 'type': 'LineString', 'coordinates': this.geometries[0].coords },
'type': 'Feature', 'properties': this.properties
};
} else {
var multiline = [];
for (var i = 0; i < this.geometries.length; i++) {
multiline.push(this.geometries[i].coords);
}
return {'geometry': { 'type': 'MultiLineString', 'coordinates': multiline },
'type': 'Feature', 'properties': this.properties
};
}
};
// TODO - output proper multilinestring
Arc.prototype.wkt = function () {
var wkt_string = '';
var wkt = 'LINESTRING(';
var collect = function (c) { wkt += c[0] + ' ' + c[1] + ','; };
for (var i = 0; i < this.geometries.length; i++) {
if (this.geometries[i].coords.length === 0) {
return 'LINESTRING(empty)';
} else {
var coords = this.geometries[i].coords;
coords.forEach(collect);
wkt_string += wkt.substring(0, wkt.length - 1) + ')';
}
}
return wkt_string;
};
/*
* http://en.wikipedia.org/wiki/Great-circle_distance
*
*/
var GreatCircle = function (start, end, properties) {
if (!start || start.x === undefined || start.y === undefined) {
throw new Error('GreatCircle constructor expects two args: start and end objects with x and y properties');
}
if (!end || end.x === undefined || end.y === undefined) {
throw new Error('GreatCircle constructor expects two args: start and end objects with x and y properties');
}
this.start = new Coord(start.x, start.y);
this.end = new Coord(end.x, end.y);
this.properties = properties || {};
var w = this.start.x - this.end.x;
var h = this.start.y - this.end.y;
var z = Math.pow(Math.sin(h / 2.0), 2) +
Math.cos(this.start.y) *
Math.cos(this.end.y) *
Math.pow(Math.sin(w / 2.0), 2);
this.g = 2.0 * Math.asin(Math.sqrt(z));
if (this.g === Math.PI) {
throw new Error('it appears ' + start.view() + ' and ' + end.view() + ' are \'antipodal\', e.g diametrically opposite, thus there is no single route but rather infinite');
} else if (isNaN(this.g)) {
throw new Error('could not calculate great circle between ' + start + ' and ' + end);
}
};
/*
* http://williams.best.vwh.net/avform.htm#Intermediate
*/
GreatCircle.prototype.interpolate = function (f) {
var A = Math.sin((1 - f) * this.g) / Math.sin(this.g);
var B = Math.sin(f * this.g) / Math.sin(this.g);
var x = A * Math.cos(this.start.y) * Math.cos(this.start.x) + B * Math.cos(this.end.y) * Math.cos(this.end.x);
var y = A * Math.cos(this.start.y) * Math.sin(this.start.x) + B * Math.cos(this.end.y) * Math.sin(this.end.x);
var z = A * Math.sin(this.start.y) + B * Math.sin(this.end.y);
var lat = R2D * Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
var lon = R2D * Math.atan2(y, x);
return [lon, lat];
};
/*
* Generate points along the great circle
*/
GreatCircle.prototype.Arc = function (npoints, options) {
var first_pass = [];
if (!npoints || npoints <= 2) {
first_pass.push([this.start.lon, this.start.lat]);
first_pass.push([this.end.lon, this.end.lat]);
} else {
var delta = 1.0 / (npoints - 1);
for (var i = 0; i < npoints; ++i) {
var step = delta * i;
var pair = this.interpolate(step);
first_pass.push(pair);
}
}
/* partial port of dateline handling from:
gdal/ogr/ogrgeometryfactory.cpp
TODO - does not handle all wrapping scenarios yet
*/
var bHasBigDiff = false;
var dfMaxSmallDiffLong = 0;
// from http://www.gdal.org/ogr2ogr.html
// -datelineoffset:
// (starting with GDAL 1.10) offset from dateline in degrees (default long. = +/- 10deg, geometries within 170deg to -170deg will be splited)
var dfDateLineOffset = options && options.offset ? options.offset : 10;
var dfLeftBorderX = 180 - dfDateLineOffset;
var dfRightBorderX = -180 + dfDateLineOffset;
var dfDiffSpace = 360 - dfDateLineOffset;
// https://github.com/OSGeo/gdal/blob/7bfb9c452a59aac958bff0c8386b891edf8154ca/gdal/ogr/ogrgeometryfactory.cpp#L2342
for (var j = 1; j < first_pass.length; ++j) {
var dfPrevX = first_pass[j - 1][0];
var dfX = first_pass[j][0];
var dfDiffLong = Math.abs(dfX - dfPrevX);
if (dfDiffLong > dfDiffSpace &&
((dfX > dfLeftBorderX && dfPrevX < dfRightBorderX) || (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX))) {
bHasBigDiff = true;
} else if (dfDiffLong > dfMaxSmallDiffLong) {
dfMaxSmallDiffLong = dfDiffLong;
}
}
var poMulti = [];
if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset) {
var poNewLS = [];
poMulti.push(poNewLS);
for (var k = 0; k < first_pass.length; ++k) {
var dfX0 = parseFloat(first_pass[k][0]);
if (k > 0 && Math.abs(dfX0 - first_pass[k - 1][0]) > dfDiffSpace) {
var dfX1 = parseFloat(first_pass[k - 1][0]);
var dfY1 = parseFloat(first_pass[k - 1][1]);
var dfX2 = parseFloat(first_pass[k][0]);
var dfY2 = parseFloat(first_pass[k][1]);
if (dfX1 > -180 && dfX1 < dfRightBorderX && dfX2 === 180 &&
k + 1 < first_pass.length &&
first_pass[k - 1][0] > -180 && first_pass[k - 1][0] < dfRightBorderX) {
poNewLS.push([-180, first_pass[k][1]]);
k++;
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
continue;
} else if (dfX1 > dfLeftBorderX && dfX1 < 180 && dfX2 === -180 &&
k + 1 < first_pass.length &&
first_pass[k - 1][0] > dfLeftBorderX && first_pass[k - 1][0] < 180) {
poNewLS.push([180, first_pass[k][1]]);
k++;
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
continue;
}
if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX) {
// swap dfX1, dfX2
var tmpX = dfX1;
dfX1 = dfX2;
dfX2 = tmpX;
// swap dfY1, dfY2
var tmpY = dfY1;
dfY1 = dfY2;
dfY2 = tmpY;
}
if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX) {
dfX2 += 360;
}
if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2) {
var dfRatio = (180 - dfX1) / (dfX2 - dfX1);
var dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
poNewLS.push([first_pass[k - 1][0] > dfLeftBorderX ? 180 : -180, dfY]);
poNewLS = [];
poNewLS.push([first_pass[k - 1][0] > dfLeftBorderX ? -180 : 180, dfY]);
poMulti.push(poNewLS);
} else {
poNewLS = [];
poMulti.push(poNewLS);
}
poNewLS.push([dfX0, first_pass[k][1]]);
} else {
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
}
}
} else {
// add normally
var poNewLS0 = [];
poMulti.push(poNewLS0);
for (var l = 0; l < first_pass.length; ++l) {
poNewLS0.push([first_pass[l][0], first_pass[l][1]]);
}
}
var arc = new Arc(this.properties);
for (var m = 0; m < poMulti.length; ++m) {
var line = new LineString();
arc.geometries.push(line);
var points = poMulti[m];
for (var j0 = 0; j0 < points.length; ++j0) {
line.move_to(points[j0]);
}
}
return arc;
};
/**
* Calculate great circles routes as {@link LineString}
*
* @name greatCircle
* @param {Coord} start source point feature
* @param {Coord} end destination point feature
* @param {Object} [options={}] Optional parameters
* @param {Object} [options.properties={}] line feature properties
* @param {number} [options.npoints=100] number of points
* @param {number} [options.offset=10] offset controls the likelyhood that lines will
* be split which cross the dateline. The higher the number the more likely.
* @returns {Feature} great circle line feature
* @example
* var start = turf.point([-122, 48]);
* var end = turf.point([-77, 39]);
*
* var greatCircle = turf.greatCircle(start, end, {'name': 'Seattle to DC'});
*
* //addToMap
* var addToMap = [start, end, greatCircle]
*/
function greatCircle(start, end, options) {
// Optional parameters
options = options || {};
if (typeof options !== 'object') throw new Error('options is invalid');
var properties = options.properties;
var npoints = options.npoints;
var offset = options.offset;
start = getCoord(start);
end = getCoord(end);
properties = properties || {};
npoints = npoints || 100;
offset = offset || 10;
var generator = new GreatCircle({x: start[0], y: start[1]}, {x: end[0], y: end[1]}, properties);
/* eslint-disable */
var line = generator.Arc(npoints, {offset: offset});
/* eslint-enable */
return line.json();
}
/**
* Split a LineString by another GeoJSON Feature.
*
* @name lineSplit
* @param {Feature} line LineString Feature to split
* @param {Feature} splitter Feature used to split line
* @returns {FeatureCollection} 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} line LineString
* @param {FeatureCollection} splitter Point
* @returns {FeatureCollection} split LineStrings
*/
function splitLineWithPoints(line, splitter) {
var results = [];
var tree = geojsonRbush();
flattenEach(splitter, function (point$$1) {
// Add index/id to features (needed for filter)
results.forEach(function (feature$$1, index) {
feature$$1.id = index;
});
// First Point - doesn't need to handle any previous line results
if (!results.length) {
results = splitLineWithPoint(line, point$$1).features;
// Add Square BBox to each feature for GeoJSON-RBush
results.forEach(function (feature$$1) {
if (!feature$$1.bbox) feature$$1.bbox = square(bbox(feature$$1));
});
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$$1);
if (search.features.length) {
// RBush might return multiple lines - only process the closest line to splitter
var closestLine = findClosestFeature(point$$1, 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$$1) { return feature$$1.id !== closestLine.id; });
tree.remove(closestLine);
// Append the two newly split lines into the results
featureEach(splitLineWithPoint(closestLine, point$$1), function (line) {
results.push(line);
tree.insert(line);
});
}
}
});
return featureCollection(results);
}
/**
* Split LineString with Point
*
* @private
* @param {Feature} line LineString
* @param {Feature} splitter Point
* @returns {FeatureCollection} 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 = geojsonRbush();
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 Feature must be closest to this point
* @param {FeatureCollection} lines Collection of Features
* @returns {Feature} closest LineString
*/
function findClosestFeature(point$$1, 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$$1);
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} pt1 point
* @param {Array} pt2 point
* @returns {boolean} true if they are equals
*/
function pointsEquals(pt1, pt2) {
return pt1[0] === pt2[0] && pt1[1] === pt2[1];
}
/**
* Creates a circular arc, of a circle of the given radius and center point, between bearing1 and bearing2;
* 0 bearing is North of center point, positive clockwise.
*
* @name lineArc
* @param {Coord} center center point
* @param {number} radius radius of the circle
* @param {number} bearing1 angle, in decimal degrees, of the first radius of the arc
* @param {number} bearing2 angle, in decimal degrees, of the second radius of the arc
* @param {Object} [options={}] Optional parameters
* @param {number} [options.steps=64] number of steps
* @param {string} [options.units='kilometers'] miles, kilometers, degrees, or radians
* @returns {Feature} line arc
* @example
* var center = turf.point([-75, 40]);
* var radius = 5;
* var bearing1 = 25;
* var bearing2 = 47;
*
* var arc = turf.lineArc(center, radius, bearing1, bearing2);
*
* //addToMap
* var addToMap = [center, arc]
*/
function lineArc(center, radius, bearing1, bearing2, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var steps = options.steps;
var units = options.units;
// validation
if (!center) throw new Error('center is required');
if (!radius) throw new Error('radius is required');
if (bearing1 === undefined || bearing1 === null) throw new Error('bearing1 is required');
if (bearing2 === undefined || bearing2 === null) throw new Error('bearing2 is required');
if (typeof options !== 'object') throw new Error('options must be an object');
// default params
steps = steps || 64;
var angle1 = convertAngleTo360(bearing1);
var angle2 = convertAngleTo360(bearing2);
var properties = center.properties;
// handle angle parameters
if (angle1 === angle2) {
return lineString(circle(center, radius, options).geometry.coordinates[0], properties);
}
var arcStartDegree = angle1;
var arcEndDegree = (angle1 < angle2) ? angle2 : angle2 + 360;
var alfa = arcStartDegree;
var coordinates = [];
var i = 0;
while (alfa < arcEndDegree) {
coordinates.push(destination(center, radius, alfa, units).geometry.coordinates);
i++;
alfa = arcStartDegree + i * 360 / steps;
}
if (alfa > arcEndDegree) {
coordinates.push(destination(center, radius, arcEndDegree, units).geometry.coordinates);
}
return lineString(coordinates, properties);
}
/**
* Takes any angle in degrees
* and returns a valid angle between 0-360 degrees
*
* @private
* @param {number} alfa angle between -180-180 degrees
* @returns {number} angle between 0-360 degrees
*/
function convertAngleTo360(alfa) {
var beta = alfa % 360;
if (beta < 0) {
beta += 360;
}
return beta;
}
/**
* Converts a {@link Polygon} to {@link LineString|(Multi)LineString} or {@link MultiPolygon} to a {@link FeatureCollection} of {@link LineString|(Multi)LineString}.
*
* @name polygonToLine
* @param {Feature} polygon Feature to convert
* @param {Object} [options={}] Optional parameters
* @param {Object} [options.properties={}] translates GeoJSON properties to Feature
* @returns {FeatureCollection|Feature} converted (Multi)Polygon to (Multi)LineString
* @example
* var poly = turf.polygon([[[125, -30], [145, -30], [145, -20], [125, -20], [125, -30]]]);
*
* var line = turf.polygonToLine(poly);
*
* //addToMap
* var addToMap = [line];
*/
function polygonToLine(polygon$$1, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var properties = options.properties;
// Variables
var geom = getType(polygon$$1);
var coords = getCoords(polygon$$1);
properties = properties || polygon$$1.properties || {};
if (!coords.length) throw new Error('polygon must contain coordinates');
switch (geom) {
case 'Polygon':
return coordsToLine(coords, properties);
case 'MultiPolygon':
var lines = [];
coords.forEach(function (coord) {
lines.push(coordsToLine(coord, properties));
});
return featureCollection(lines);
default:
throw new Error('geom ' + geom + ' not supported');
}
}
function coordsToLine(coords, properties) {
if (coords.length > 1) return multiLineString(coords, properties);
return lineString(coords[0], properties);
}
/**
* Converts (Multi)LineString(s) to Polygon(s).
*
* @name lineToPolygon
* @param {FeatureCollection|Feature} lines Features to convert
* @param {Object} [options={}] Optional parameters
* @param {Object} [options.properties={}] translates GeoJSON properties to Feature
* @param {boolean} [options.autoComplete=true] auto complete linestrings (matches first & last coordinates)
* @param {boolean} [options.orderCoords=true] sorts linestrings to place outer ring at the first position of the coordinates
* @returns {Feature} converted to Polygons
* @example
* var line = turf.lineString([[125, -30], [145, -30], [145, -20], [125, -20], [125, -30]]);
*
* var polygon = turf.lineToPolygon(line);
*
* //addToMap
* var addToMap = [polygon];
*/
function lineToPolygon(lines, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var properties = options.properties;
var autoComplete = options.autoComplete;
var orderCoords = options.orderCoords;
// validation
if (!lines) throw new Error('lines is required');
// default params
autoComplete = (autoComplete !== undefined) ? autoComplete : true;
orderCoords = (orderCoords !== undefined) ? orderCoords : true;
var type = getType(lines);
switch (type) {
case 'FeatureCollection':
case 'GeometryCollection':
var coords = [];
var features = (lines.features) ? lines.features : lines.geometries;
features.forEach(function (line) {
coords.push(getCoords(lineStringToPolygon(line, {}, autoComplete, orderCoords)));
});
return multiPolygon(coords, properties);
}
return lineStringToPolygon(lines, properties, autoComplete, orderCoords);
}
/**
* LineString to Polygon
*
* @private
* @param {Feature} line line
* @param {Object} [properties] translates GeoJSON properties to Feature
* @param {boolean} [autoComplete=true] auto complete linestrings
* @param {boolean} [orderCoords=true] sorts linestrings to place outer ring at the first position of the coordinates
* @returns {Feature} line converted to Polygon
*/
function lineStringToPolygon(line, properties, autoComplete, orderCoords) {
properties = properties || line.properties || {};
var coords = getCoords(line);
var type = getType(line);
if (!coords.length) throw new Error('line must contain coordinates');
switch (type) {
case 'LineString':
if (autoComplete) coords = autoCompleteCoords(coords);
return polygon([coords], properties);
case 'MultiLineString':
var multiCoords = [];
var largestArea = 0;
coords.forEach(function (coord) {
if (autoComplete) coord = autoCompleteCoords(coord);
// Largest LineString to be placed in the first position of the coordinates array
if (orderCoords) {
var area = calculateArea$1(bbox(lineString(coord)));
if (area > largestArea) {
multiCoords.unshift(coord);
largestArea = area;
} else multiCoords.push(coord);
} else {
multiCoords.push(coord);
}
});
return polygon(multiCoords, properties);
default:
throw new Error('geometry type ' + type + ' is not supported');
}
}
/**
* Auto Complete Coords - matches first & last coordinates
*
* @private
* @param {Array>} coords Coordinates
* @returns {Array>} auto completed coordinates
*/
function autoCompleteCoords(coords) {
var first = coords[0];
var x1 = first[0];
var y1 = first[1];
var last = coords[coords.length - 1];
var x2 = last[0];
var y2 = last[1];
if (x1 !== x2 || y1 !== y2) {
coords.push(first);
}
return coords;
}
/**
* area - quick approximate area calculation (used to sort)
*
* @private
* @param {Array} bbox BBox [west, south, east, north]
* @returns {number} very quick area calculation
*/
function calculateArea$1(bbox$$1) {
var west = bbox$$1[0];
var south = bbox$$1[1];
var east = bbox$$1[2];
var north = bbox$$1[3];
return Math.abs(west - east) * Math.abs(south - north);
}
var lineclip_1 = lineclip;
lineclip.polyline = lineclip;
lineclip.polygon = polygonclip;
// Cohen-Sutherland line clippign algorithm, adapted to efficiently
// handle polylines rather than just segments
function lineclip(points, bbox, result) {
var len = points.length,
codeA = bitCode(points[0], bbox),
part = [],
i, a, b, codeB, lastCode;
if (!result) result = [];
for (i = 1; i < len; i++) {
a = points[i - 1];
b = points[i];
codeB = lastCode = bitCode(b, bbox);
while (true) {
if (!(codeA | codeB)) { // accept
part.push(a);
if (codeB !== lastCode) { // segment went outside
part.push(b);
if (i < len - 1) { // start a new line
result.push(part);
part = [];
}
} else if (i === len - 1) {
part.push(b);
}
break;
} else if (codeA & codeB) { // trivial reject
break;
} else if (codeA) { // a outside, intersect with clip edge
a = intersect$1(a, b, codeA, bbox);
codeA = bitCode(a, bbox);
} else { // b outside
b = intersect$1(a, b, codeB, bbox);
codeB = bitCode(b, bbox);
}
}
codeA = lastCode;
}
if (part.length) result.push(part);
return result;
}
// Sutherland-Hodgeman polygon clipping algorithm
function polygonclip(points, bbox) {
var result, edge, prev, prevInside, i, p, inside;
// clip against each side of the clip rectangle
for (edge = 1; edge <= 8; edge *= 2) {
result = [];
prev = points[points.length - 1];
prevInside = !(bitCode(prev, bbox) & edge);
for (i = 0; i < points.length; i++) {
p = points[i];
inside = !(bitCode(p, bbox) & edge);
// if segment goes through the clip window, add an intersection
if (inside !== prevInside) result.push(intersect$1(prev, p, edge, bbox));
if (inside) result.push(p); // add a point if it's inside
prev = p;
prevInside = inside;
}
points = result;
if (!points.length) break;
}
return result;
}
// intersect a segment against one of the 4 lines that make up the bbox
function intersect$1(a, b, edge, bbox) {
return edge & 8 ? [a[0] + (b[0] - a[0]) * (bbox[3] - a[1]) / (b[1] - a[1]), bbox[3]] : // top
edge & 4 ? [a[0] + (b[0] - a[0]) * (bbox[1] - a[1]) / (b[1] - a[1]), bbox[1]] : // bottom
edge & 2 ? [bbox[2], a[1] + (b[1] - a[1]) * (bbox[2] - a[0]) / (b[0] - a[0])] : // right
edge & 1 ? [bbox[0], a[1] + (b[1] - a[1]) * (bbox[0] - a[0]) / (b[0] - a[0])] : // left
null;
}
// bit code reflects the point position relative to the bbox:
// left mid right
// top 1001 1000 1010
// mid 0001 0000 0010
// bottom 0101 0100 0110
function bitCode(p, bbox) {
var code = 0;
if (p[0] < bbox[0]) code |= 1; // left
else if (p[0] > bbox[2]) code |= 2; // right
if (p[1] < bbox[1]) code |= 4; // bottom
else if (p[1] > bbox[3]) code |= 8; // top
return code;
}
/**
* Takes a {@link Feature} and a bbox and clips the feature to the bbox using [lineclip](https://github.com/mapbox/lineclip).
* May result in degenerate edges when clipping Polygons.
*
* @name bboxClip
* @param {Feature} feature feature to clip to the bbox
* @param {BBox} bbox extent in [minX, minY, maxX, maxY] order
* @returns {Feature} clipped Feature
* @example
* var bbox = [0, 0, 10, 10];
* var poly = turf.polygon([[[2, 2], [8, 4], [12, 8], [3, 7], [2, 2]]]);
*
* var clipped = turf.bboxClip(poly, bbox);
*
* //addToMap
* var addToMap = [bbox, poly, clipped]
*/
function bboxClip(feature$$1, bbox) {
var geom = getGeom$1(feature$$1);
var coords = getCoords(feature$$1);
var properties = feature$$1.properties;
switch (geom) {
case 'LineString':
case 'MultiLineString':
var lines = [];
if (geom === 'LineString') coords = [coords];
coords.forEach(function (line) {
lineclip_1(line, bbox, lines);
});
if (lines.length === 1) return lineString(lines[0], properties);
return multiLineString(lines, properties);
case 'Polygon':
return polygon(clipPolygon(coords, bbox), properties);
case 'MultiPolygon':
return multiPolygon(coords.map(function (polygon$$1) {
return clipPolygon(polygon$$1, bbox);
}), properties);
default:
throw new Error('geometry ' + geom + ' not supported');
}
}
function clipPolygon(rings, bbox) {
var outRings = [];
for (var i = 0; i < rings.length; i++) {
var clipped = lineclip_1.polygon(rings[i], bbox);
if (clipped.length > 0) {
if (clipped[0][0] !== clipped[clipped.length - 1][0] || clipped[0][1] !== clipped[clipped.length - 1][1]) {
clipped.push(clipped[0]);
}
if (clipped.length >= 4) {
outRings.push(clipped);
}
}
}
return outRings;
}
function getGeom$1(feature$$1) {
return (feature$$1.geometry) ? feature$$1.geometry.type : feature$$1.type;
}
var pSlice = Array.prototype.slice;
function isArguments(object) {
return Object.prototype.toString.call(object) === '[object Arguments]';
}
function deepEqual(actual, expected, opts) {
if (!opts) opts = {};
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
} else if (actual instanceof Date && expected instanceof Date) {
return actual.getTime() === expected.getTime();
// 7.3. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
} else if (!actual || !expected || typeof actual != 'object' && typeof expected != 'object') {
return opts.strict ? actual === expected : actual === expected;
// 7.4. For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected, opts);
}
}
function isUndefinedOrNull(value) {
return value === null || value === undefined;
}
function isBuffer(x) {
if (!x || typeof x !== 'object' || typeof x.length !== 'number') return false;
if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {
return false;
}
if (x.length > 0 && typeof x[0] !== 'number') return false;
return true;
}
function objEquiv(a, b, opts) {
var i, key;
if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
return false;
// an identical 'prototype' property.
if (a.prototype !== b.prototype) return false;
//~~~I've managed to break Object.keys through screwy arguments passing.
// Converting to array solves the problem.
if (isArguments(a)) {
if (!isArguments(b)) {
return false;
}
a = pSlice.call(a);
b = pSlice.call(b);
return deepEqual(a, b, opts);
}
if (isBuffer(a)) {
if (!isBuffer(b)) {
return false;
}
if (a.length !== b.length) return false;
for (i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
try {
var ka = Object.keys(a),
kb = Object.keys(b);
} catch (e) { //happens when one is a string literal and the other isn't
return false;
}
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length !== kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] !== kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!deepEqual(a[key], b[key], opts)) return false;
}
return typeof a === typeof b;
}
/**
* Takes any LineString or Polygon and returns the overlapping lines between both features.
*
* @name lineOverlap
* @param {Geometry|Feature} line1 any LineString or Polygon
* @param {Geometry|Feature} line2 any LineString or Polygon
* @param {Object} [options={}] Optional parameters
* @param {number} [options.tolerance=0] Tolerance distance to match overlapping line segments (in kilometers)
* @returns {FeatureCollection} lines(s) that are overlapping between both features
* @example
* var line1 = turf.lineString([[115, -35], [125, -30], [135, -30], [145, -35]]);
* var line2 = turf.lineString([[115, -25], [125, -30], [135, -30], [145, -25]]);
*
* var overlapping = turf.lineOverlap(line1, line2);
*
* //addToMap
* var addToMap = [line1, line2, overlapping]
*/
function lineOverlap(line1, line2, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
var tolerance = options.tolerance || 0;
// Containers
var features = [];
// Create Spatial Index
var tree = geojsonRbush();
tree.load(lineSegment(line1));
var overlapSegment;
// Line Intersection
// Iterate over line segments
segmentEach(line2, function (segment) {
var doesOverlaps = false;
// Iterate over each segments which falls within the same bounds
featureEach(tree.search(segment), function (match) {
if (doesOverlaps === false) {
var coordsSegment = getCoords(segment).sort();
var coordsMatch = getCoords(match).sort();
// Segment overlaps feature
if (deepEqual(coordsSegment, coordsMatch)) {
doesOverlaps = true;
// Overlaps already exists - only append last coordinate of segment
if (overlapSegment) overlapSegment = concatSegment(overlapSegment, segment);
else overlapSegment = segment;
// Match segments which don't share nodes (Issue #901)
} else if (
(tolerance === 0) ?
booleanPointOnLine(coordsSegment[0], match) && booleanPointOnLine(coordsSegment[1], match) :
nearestPointOnLine(match, coordsSegment[0]).properties.dist <= tolerance &&
nearestPointOnLine(match, coordsSegment[1]).properties.dist <= tolerance) {
doesOverlaps = true;
if (overlapSegment) overlapSegment = concatSegment(overlapSegment, segment);
else overlapSegment = segment;
} else if (
(tolerance === 0) ?
booleanPointOnLine(coordsMatch[0], segment) && booleanPointOnLine(coordsMatch[1], segment) :
nearestPointOnLine(segment, coordsMatch[0]).properties.dist <= tolerance &&
nearestPointOnLine(segment, coordsMatch[1]).properties.dist <= tolerance) {
// Do not define (doesOverlap = true) since more matches can occur within the same segment
// doesOverlaps = true;
if (overlapSegment) overlapSegment = concatSegment(overlapSegment, match);
else overlapSegment = match;
}
}
});
// Segment doesn't overlap - add overlaps to results & reset
if (doesOverlaps === false && overlapSegment) {
features.push(overlapSegment);
overlapSegment = undefined;
}
});
// Add last segment if exists
if (overlapSegment) features.push(overlapSegment);
return featureCollection(features);
}
/**
* Concat Segment
*
* @private
* @param {Feature} line LineString
* @param {Feature