/** * Callback for coordEach * * @private * @callback coordEachCallback * @param {[number, number]} currentCoords The current coordinates being processed. * @param {number} currentIndex The index of the current element being processed in the * array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise. */ /** * Iterate over coordinates in any GeoJSON object, similar to Array.forEach() * * @name coordEach * @param {Object} layer any GeoJSON object * @param {Function} callback a method that takes (currentCoords, currentIndex) * @param {boolean} [excludeWrapCoord=false] whether or not to include * the final coordinate of LinearRings that wraps the ring in its iteration. * @example * var features = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.coordEach(features, function (currentCoords, currentIndex) { * //=currentCoords * //=currentIndex * }); */ function coordEach(layer, callback, excludeWrapCoord) { var i, j, k, g, l, geometry, stopG, coords, geometryMaybeCollection, wrapShrink = 0, currentIndex = 0, isGeometryCollection, isFeatureCollection = layer.type === 'FeatureCollection', isFeature = layer.type === 'Feature', stop = isFeatureCollection ? layer.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 ? layer.features[i].geometry : (isFeature ? layer.geometry : layer)); isGeometryCollection = geometryMaybeCollection.type === 'GeometryCollection'; stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1; for (g = 0; g < stopG; g++) { geometry = isGeometryCollection ? geometryMaybeCollection.geometries[g] : geometryMaybeCollection; coords = geometry.coordinates; wrapShrink = (excludeWrapCoord && (geometry.type === 'Polygon' || geometry.type === 'MultiPolygon')) ? 1 : 0; if (geometry.type === 'Point') { callback(coords, currentIndex); currentIndex++; } else if (geometry.type === 'LineString' || geometry.type === 'MultiPoint') { for (j = 0; j < coords.length; j++) { callback(coords[j], currentIndex); currentIndex++; } } else if (geometry.type === 'Polygon' || geometry.type === 'MultiLineString') { for (j = 0; j < coords.length; j++) for (k = 0; k < coords[j].length - wrapShrink; k++) { callback(coords[j][k], currentIndex); currentIndex++; } } else if (geometry.type === 'MultiPolygon') { for (j = 0; j < coords.length; j++) for (k = 0; k < coords[j].length; k++) for (l = 0; l < coords[j][k].length - wrapShrink; l++) { callback(coords[j][k][l], currentIndex); currentIndex++; } } else if (geometry.type === 'GeometryCollection') { for (j = 0; j < geometry.geometries.length; j++) coordEach(geometry.geometries[j], callback, excludeWrapCoord); } else { throw new Error('Unknown Geometry Type'); } } } } module.exports.coordEach = coordEach; /** * 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. * * @private * @callback coordReduceCallback * @param {*} previousValue The accumulated value previously returned in the last invocation * of the callback, or initialValue, if supplied. * @param {[number, number]} currentCoords The current coordinate being processed. * @param {number} currentIndex The index of the current element being processed in the * array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise. */ /** * Reduce coordinates in any GeoJSON object, similar to Array.reduce() * * @name coordReduce * @param {Object} layer any GeoJSON object * @param {Function} callback a method that takes (previousValue, currentCoords, currentIndex) * @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 = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.coordReduce(features, function (previousValue, currentCoords, currentIndex) { * //=previousValue * //=currentCoords * //=currentIndex * return currentCoords; * }); */ function coordReduce(layer, callback, initialValue, excludeWrapCoord) { var previousValue = initialValue; coordEach(layer, function (currentCoords, currentIndex) { if (currentIndex === 0 && initialValue === undefined) { previousValue = currentCoords; } else { previousValue = callback(previousValue, currentCoords, currentIndex); } }, excludeWrapCoord); return previousValue; } module.exports.coordReduce = coordReduce; /** * Callback for propEach * * @private * @callback propEachCallback * @param {*} currentProperties The current properties being processed. * @param {number} currentIndex The index of the current element being processed in the * array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise. */ /** * Iterate over properties in any GeoJSON object, similar to Array.forEach() * * @name propEach * @param {Object} layer any GeoJSON object * @param {Function} callback a method that takes (currentProperties, currentIndex) * @example * var features = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {"foo": "bar"}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {"hello": "world"}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.propEach(features, function (currentProperties, currentIndex) { * //=currentProperties * //=currentIndex * }); */ function propEach(layer, callback) { var i; switch (layer.type) { case 'FeatureCollection': for (i = 0; i < layer.features.length; i++) { callback(layer.features[i].properties, i); } break; case 'Feature': callback(layer.properties, 0); break; } } module.exports.propEach = propEach; /** * 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. * * @private * @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} currentIndex The index of the current element being processed in the * array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise. */ /** * 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 {Object} layer any GeoJSON object * @param {Function} callback a method that takes (previousValue, currentProperties, currentIndex) * @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 = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {"foo": "bar"}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {"hello": "world"}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.propReduce(features, function (previousValue, currentProperties, currentIndex) { * //=previousValue * //=currentProperties * //=currentIndex * return currentProperties * }); */ function propReduce(layer, callback, initialValue) { var previousValue = initialValue; propEach(layer, function (currentProperties, currentIndex) { if (currentIndex === 0 && initialValue === undefined) { previousValue = currentProperties; } else { previousValue = callback(previousValue, currentProperties, currentIndex); } }); return previousValue; } module.exports.propReduce = propReduce; /** * Callback for featureEach * * @private * @callback featureEachCallback * @param {Feature} currentFeature The current feature being processed. * @param {number} currentIndex The index of the current element being processed in the * array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise. */ /** * Iterate over features in any GeoJSON object, similar to * Array.forEach. * * @name featureEach * @param {Object} layer any GeoJSON object * @param {Function} callback a method that takes (currentFeature, currentIndex) * @example * var features = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.featureEach(features, function (currentFeature, currentIndex) { * //=currentFeature * //=currentIndex * }); */ function featureEach(layer, callback) { if (layer.type === 'Feature') { callback(layer, 0); } else if (layer.type === 'FeatureCollection') { for (var i = 0; i < layer.features.length; i++) { callback(layer.features[i], i); } } } module.exports.featureEach = featureEach; /** * 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. * * @private * @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} currentIndex The index of the current element being processed in the * array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise. */ /** * Reduce features in any GeoJSON object, similar to Array.reduce(). * * @name featureReduce * @param {Object} layer any GeoJSON object * @param {Function} callback a method that takes (previousValue, currentFeature, currentIndex) * @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 = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {"foo": "bar"}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {"hello": "world"}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.featureReduce(features, function (previousValue, currentFeature, currentIndex) { * //=previousValue * //=currentFeature * //=currentIndex * return currentFeature * }); */ function featureReduce(layer, callback, initialValue) { var previousValue = initialValue; featureEach(layer, function (currentFeature, currentIndex) { if (currentIndex === 0 && initialValue === undefined) { previousValue = currentFeature; } else { previousValue = callback(previousValue, currentFeature, currentIndex); } }); return previousValue; } module.exports.featureReduce = featureReduce; /** * Get all coordinates from any GeoJSON object. * * @name coordAll * @param {Object} layer any GeoJSON object * @returns {Array>} coordinate position array * @example * var features = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * var coords = turf.coordAll(features); * //=coords */ function coordAll(layer) { var coords = []; coordEach(layer, function (coord) { coords.push(coord); }); return coords; } module.exports.coordAll = coordAll; /** * Iterate over each geometry in any GeoJSON object, similar to Array.forEach() * * @name geomEach * @param {Object} layer any GeoJSON object * @param {Function} callback a method that takes (currentGeometry, currentIndex) * @example * var features = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.geomEach(features, function (currentGeometry, currentIndex) { * //=currentGeometry * //=currentIndex * }); */ function geomEach(layer, callback) { var i, j, g, geometry, stopG, geometryMaybeCollection, isGeometryCollection, currentIndex = 0, isFeatureCollection = layer.type === 'FeatureCollection', isFeature = layer.type === 'Feature', stop = isFeatureCollection ? layer.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 ? layer.features[i].geometry : (isFeature ? layer.geometry : layer)); isGeometryCollection = geometryMaybeCollection.type === 'GeometryCollection'; stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1; for (g = 0; g < stopG; g++) { geometry = isGeometryCollection ? geometryMaybeCollection.geometries[g] : geometryMaybeCollection; if (geometry.type === 'Point' || geometry.type === 'LineString' || geometry.type === 'MultiPoint' || geometry.type === 'Polygon' || geometry.type === 'MultiLineString' || geometry.type === 'MultiPolygon') { callback(geometry, currentIndex); currentIndex++; } else if (geometry.type === 'GeometryCollection') { for (j = 0; j < geometry.geometries.length; j++) { callback(geometry.geometries[j], currentIndex); currentIndex++; } } else { throw new Error('Unknown Geometry Type'); } } } } module.exports.geomEach = geomEach; /** * 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. * * @private * @callback geomReduceCallback * @param {*} previousValue The accumulated value previously returned in the last invocation * of the callback, or initialValue, if supplied. * @param {*} currentGeometry The current Feature being processed. * @param {number} currentIndex The index of the current element being processed in the * array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise. */ /** * Reduce geometry in any GeoJSON object, similar to Array.reduce(). * * @name geomReduce * @param {Object} layer any GeoJSON object * @param {Function} callback a method that takes (previousValue, currentGeometry, currentIndex) * @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 = { * "type": "FeatureCollection", * "features": [ * { * "type": "Feature", * "properties": {"foo": "bar"}, * "geometry": { * "type": "Point", * "coordinates": [26, 37] * } * }, * { * "type": "Feature", * "properties": {"hello": "world"}, * "geometry": { * "type": "Point", * "coordinates": [36, 53] * } * } * ] * }; * turf.geomReduce(features, function (previousValue, currentGeometry, currentIndex) { * //=previousValue * //=currentGeometry * //=currentIndex * return currentGeometry * }); */ function geomReduce(layer, callback, initialValue) { var previousValue = initialValue; geomEach(layer, function (currentGeometry, currentIndex) { if (currentIndex === 0 && initialValue === undefined) { previousValue = currentGeometry; } else { previousValue = callback(previousValue, currentGeometry, currentIndex); } }); return previousValue; } module.exports.geomReduce = geomReduce;