@spatial/turf
Version:
a JavaScript library for performing geospatial operations with GeoJSON
1,240 lines (1,153 loc) • 4.31 MB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.turf = {}));
}(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<number>} [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');
const bbox = options.bbox;
const 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
const 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<number>} coordinates Coordinates
* @param {Object} [options={}] Optional Parameters
* @param {Array<number>} [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');
const 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
let 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<number>} 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<number>} [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<Point>} 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
}, properties, options);
}
/**
* Creates a {@link Point} {@link FeatureCollection} from an Array of Point coordinates.
*
* @name points
* @param {Array<Array<number>>} coordinates an array of Points
* @param {Object} [properties={}] Translate these properties to each Feature
* @param {Object} [options={}] Optional Parameters
* @param {Array<number>} [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>} 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(coords => point(coords, properties)), options);
}
/**
* Creates a {@link Polygon} {@link Feature} from an Array of LinearRings.
*
* @name polygon
* @param {Array<Array<Array<number>>>} 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<number>} [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>} 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 (let i = 0; i < coordinates.length; i++) {
const ring = coordinates[i];
if (ring.length < 4) {
throw new Error('Each LinearRing of a Polygon must have 4 or more Positions.');
}
for (let 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
}, properties, options);
}
/**
* Creates a {@link Polygon} {@link FeatureCollection} from an Array of Polygon coordinates.
*
* @name polygons
* @param {Array<Array<Array<Array<number>>>>} 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<number>} [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>} 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(coords => polygon(coords, properties)), options);
}
/**
* Creates a {@link LineString} {@link Feature} from an Array of Positions.
*
* @name lineString
* @param {Array<Array<number>>} 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<number>} [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>} 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
}, properties, options);
}
/**
* Creates a {@link LineString} {@link FeatureCollection} from an Array of LineString coordinates.
*
* @name lineStrings
* @param {Array<Array<number>>} 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<number>} [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>} 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(coords => 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<number>} [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');
const bbox = options.bbox;
const 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
const fc = {type: 'FeatureCollection'};
if (id) fc.id = id;
if (bbox) fc.bbox = bbox;
fc.features = features;
return fc;
}
/**
* Creates a {@link Feature<MultiLineString>} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiLineString
* @param {Array<Array<Array<number>>>} 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<number>} [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<MultiLineString>} 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
}, properties, options);
}
/**
* Creates a {@link Feature<MultiPoint>} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiPoint
* @param {Array<Array<number>>} 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<number>} [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<MultiPoint>} 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
}, properties, options);
}
/**
* Creates a {@link Feature<MultiPolygon>} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiPolygon
* @param {Array<Array<Array<Array<number>>>>} 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<number>} [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<MultiPolygon>} 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
}, properties, options);
}
/**
* Creates a {@link Feature<GeometryCollection>} based on a
* coordinate array. Properties can be added optionally.
*
* @name geometryCollection
* @param {Array<Geometry>} 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<number>} [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<GeometryCollection>} 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
}, 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');
const 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');
const 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');
const 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');
let 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');
const 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');
const 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');
const startFactor = areaFactors[originalUnit || 'meters'];
if (!startFactor) throw new Error('invalid original units');
const 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<number>} 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((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 = /*#__PURE__*/Object.freeze({
areaFactors: areaFactors,
bearingToAngle: bearingToAngle,
bearingToAzimuth: bearingToAzimuth,
convertArea: convertArea,
convertDistance: convertDistance,
convertLength: convertLength,
degrees2radians: degrees2radians,
degreesToRadians: degreesToRadians,
distanceToDegrees: distanceToDegrees,
distanceToRadians: distanceToRadians,
earthRadius: earthRadius,
factors: factors,
feature: feature,
featureCollection: featureCollection,
geometry: geometry,
geometryCollection: geometryCollection,
isNumber: isNumber,
isObject: isObject,
lengthToDegrees: lengthToDegrees,
lengthToRadians: lengthToRadians,
lineString: lineString,
lineStrings: lineStrings,
multiLineString: multiLineString,
multiPoint: multiPoint,
multiPolygon: multiPolygon,
point: point,
points: points,
polygon: polygon,
polygons: polygons,
radians2degrees: radians2degrees,
radiansToDegrees: radiansToDegrees,
radiansToDistance: radiansToDistance,
radiansToLength: radiansToLength,
round: round,
unitsFactors: unitsFactors,
validateBBox: validateBBox,
validateId: validateId
});
/**
* Callback for coordEach
*
* @callback coordEachCallback
* @param {Array<number>} 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;
let j, k, l, geometry, 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 (let 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 (let geomIndex = 0; geomIndex < stopG; geomIndex++) {
let multiFeatureIndex = 0;
let geometryIndex = 0;
geometry = isGeometryCollection ?
geometryMaybeCollection.geometries[geomIndex] : geometryMaybeCollection;
// Handles null Geometry -- Skips this geometry
if (geometry === null) continue;
coords = geometry.coordinates;
const geomType = geometry.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.geometries.length; j++)
coordEach(geometry.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<number>} 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) {
let previousValue = initialValue;
coordEach(geojson, (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) {
let 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) {
let previousValue = initialValue;
propEach(geojson, (currentProperties, featureIndex) => {
if (featureIndex === 0 && initialValue === undefined) previousValue = currentProperties;
else previousValue = callback(previousValue, currentProperties, featureIndex);
});
return previousValue;
}
/**
* Callback for featureEach
*
* @callback featureEachCallback
* @param {Feature<any>} 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 (let 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) {
let previousValue = initialValue;
featureEach(geojson, (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<Array<number>>} 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) {
const coords = [];
coordEach(geojson, (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<number>} 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) {
let i, j, g, geometry, 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 ? geojso