/** * Takes a {@link Point} grid and returns a correspondent matrix {Array<Array<number>>} * of the 'property' values * * @name gridToMatrix * @param {FeatureCollection<Point>} 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<Array<number>>} 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] * ] */ export default 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 = pointRow[c]; // Check if zProperty exist if (point.properties[zProperty]) row.push(point.properties[zProperty]); else row.push(0); // add flags if (flags === true) point.properties.matrixPosition = [r, c]; } matrix.push(row); } return matrix; }
/** * 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<LineString>} 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 = 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 > d) distance = d; }); return distance; }
/** * 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<Point>} points Point Collection * @param {Feature|Geometry<LineString>} 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<Point>} 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, line, options) { options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var units = options.units; var properties = options.properties || {}; // validation if (!points) throw new Error('points is required'); points = normalize(points); if (!points.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, function (point) { var d = pointToLineDistance(point, line, { units: units }); if (d < dist) { dist = d; pt = point; } }); /** * Translate Properties to final Point, priorities: * 1. options.properties * 2. inherent Point properties * 3. dist custom properties created by NearestPointToLine */ if (pt) pt.properties = objectAssign({dist: dist}, pt.properties, properties); return pt; }
/** * 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; }
/** * Creates a circular sector of a circle of given radius and center {@link Point}, * between (clockwise) bearing1 and bearing2; 0 bearing is North of center point, positive clockwise. * * @name sector * @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 sector * @param {number} bearing2 angle, in decimal degrees, of the second radius of the sector * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] miles, kilometers, degrees, or radians * @param {number} [options.steps=64] number of steps * @param {Properties} [options.properties={}] Translate properties to Feature Polygon * @returns {Feature<Polygon>} sector polygon * @example * var center = turf.point([-75, 40]); * var radius = 5; * var bearing1 = 25; * var bearing2 = 45; * * var sector = turf.sector(center, radius, bearing1, bearing2); * * //addToMap * var addToMap = [center, sector]; */ function sector(center, radius, bearing1, bearing2, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var properties = options.properties; // validation if (!center) throw new Error('center 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 (!radius) throw new Error('radius is required'); if (typeof options !== 'object') throw new Error('options must be an object'); if (convertAngleTo360(bearing1) === convertAngleTo360(bearing2)) { return circle(center, radius, options); } var coords = getCoords(center); var arc = lineArc(center, radius, bearing1, bearing2, options); var sliceCoords = [[coords]]; coordEach(arc, function (currentCoords) { sliceCoords[0].push(currentCoords); }); sliceCoords[0].push(coords); return polygon(sliceCoords, properties); }
export function randomPoint(count, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var bbox = options.bbox; if (count === undefined || count === null) count = 1; var features = []; for (var i = 0; i < count; i++) { features.push(point(randomPosition(bbox))); } return featureCollection(features); }
var multipolygons = contours.map(function (contour, index) { if (breaksProperties[index] && !isObject(breaksProperties[index])) { throw new Error('Each mappedProperty is required to be an Object'); } // collect all properties var contourProperties = objectAssign( {}, commonProperties, breaksProperties[index] ); contourProperties[zProperty] = contour[zProperty]; var multiP = multiPolygon(contour.groupedRings, contourProperties); return multiP; });
/** * 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 square or rectangular grid {@link FeatureCollection} of {@link Point} features with z-values and an array of * value breaks and generates filled contour isobands. * * @name isobands * @param {FeatureCollection<Point>} pointGrid input points - must be square or rectangular * @param {Array<number>} breaks where to draw contours * @param {Object} [options={}] options on output * @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 isobands * @param {Array<Object>} [options.breaksProperties=[]] GeoJSON properties passed, in order, to the correspondent isoband (order defined by breaks) * @returns {FeatureCollection<MultiPolygon>} a FeatureCollection of {@link MultiPolygon} features representing isobands */ function isobands(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 || []; // Validation collectionOf(pointGrid, 'Point', 'Input must contain Points'); if (!breaks) throw new Error('breaks is required'); if (!Array.isArray(breaks)) throw new Error('breaks is not an Array'); if (!isObject(commonProperties)) throw new Error('commonProperties is not an Object'); if (!Array.isArray(breaksProperties)) throw new Error('breaksProperties is not an Array'); // Isoband methods var matrix = gridToMatrix(pointGrid, {zProperty: zProperty, flip: true}); var contours = createContourLines(matrix, breaks, zProperty); contours = rescaleContours(contours, matrix, pointGrid); var multipolygons = contours.map(function (contour, index) { if (breaksProperties[index] && !isObject(breaksProperties[index])) { throw new Error('Each mappedProperty is required to be an Object'); } // collect all properties var contourProperties = objectAssign( {}, commonProperties, breaksProperties[index] ); contourProperties[zProperty] = contour[zProperty]; var multiP = multiPolygon(contour.groupedRings, contourProperties); return multiP; }); return featureCollection(multipolygons); }
/** * Takes a {@link Point} grid and returns a correspondent matrix {Array<Array<number>>} * of the 'property' values * * @name matrixToGrid * @param {Array<Array<number>>} matrix of numbers * @param {Point|Array<number>} origin position of the first bottom-left (South-West) point of the grid * @param {number} cellSize the distance across each cell * @param {Object} [options={}] optional parameters * @param {string} [options.zProperty='elevation'] the grid points property name associated with the matrix value * @param {Object} [options.properties={}] GeoJSON properties passed to all the points * @param {string} [options.units='kilometers'] used in calculating cellSize, can be miles, or kilometers * @returns {FeatureCollection<Point>} grid of points * * @example * var matrixToGrid = require('matrix-to-grid'); * var matrix = [ * [ 1, 13, 20, 9, 10, 13, 18], * [34, 8, 0, 4, 5, 8, 13], * [10, 5, 2, 1, 2, 5, 24], * [ 0, 4, 56, 19, 0, 4, 9], * [10, 5, 2, 12, 2, 5, 10], * [57, 8, 5, 4, 5, 0, 57], * [ 3, 13, 0, 9, 5, 13, 35], * [18, 13, 10, 9, 78, 13, 18] * ]; * var origin = [-70.823364, -33.553984] * matrixToGrid(matrix, origin, 10); * //= pointGrid */ export default function matrixToGrid(matrix, origin, cellSize, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var zProperty = options.zProperty || 'elevation'; var properties = options.properties; var units = options.units; // validation if (!matrix || !Array.isArray(matrix)) throw new Error('matrix is required'); if (!origin) throw new Error('origin is required'); if (Array.isArray(origin)) { origin = point(origin); // Convert coordinates array to point } // all matrix array have to be of the same size var matrixCols = matrix[0].length; var matrixRows = matrix.length; for (var row = 1; row < matrixRows; row++) { if (matrix[row].length !== matrixCols) throw new Error('matrix requires all rows of equal size'); } var points = []; for (var r = 0; r < matrixRows; r++) { // create first point in the row var first = rhumbDestination(origin, cellSize * r, 0, { units: units }); first.properties[zProperty] = matrix[matrixRows - 1 - r][0]; for (var prop in properties) { first.properties[prop] = properties[prop]; } points.push(first); for (var c = 1; c < matrixCols; c++) { // create the other points in the same row var pt = rhumbDestination(first, cellSize * c, 90, { units: units }); for (var prop2 in properties) { pt.properties[prop2] = properties[prop2]; } // add matrix property var val = matrix[matrixRows - 1 - r][c]; pt.properties[zProperty] = val; points.push(pt); } } var grid = featureCollection(points); return grid; }
//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); }
export function randomPolygon(count, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var bbox = options.bbox; var num_vertices = options.num_vertices; var max_radial_length = options.max_radial_length; if (count === undefined || count === null) count = 1; // Validation if (!isNumber(num_vertices)) num_vertices = 10; if (!isNumber(max_radial_length)) max_radial_length = 10; var features = []; for (var i = 0; i < count; i++) { var vertices = [], circle_offsets = Array.apply(null, new Array(num_vertices + 1)).map(Math.random); circle_offsets.forEach(sumOffsets); circle_offsets.forEach(scaleOffsets); vertices[vertices.length - 1] = vertices[0]; // close the ring // center the polygon around something vertices = vertices.map(vertexToCoordinate(randomPosition(bbox))); features.push(polygon([vertices])); } function sumOffsets(cur, index, arr) { arr[index] = (index > 0) ? cur + arr[index - 1] : cur; } function scaleOffsets(cur) { cur = cur * 2 * Math.PI / circle_offsets[circle_offsets.length - 1]; var radial_scaler = Math.random(); vertices.push([ radial_scaler * max_radial_length * Math.sin(cur), radial_scaler * max_radial_length * Math.cos(cur) ]); } return featureCollection(features); }
/** * 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<LineString>} 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 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; }
/** * 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 destination {@link Point} having travelled the given distance along a Rhumb line from the * origin Point with the (varant) given bearing. * * @name rhumbDestination * @param {Coord} origin starting point * @param {number} distance distance from the starting point * @param {number} bearing varant bearing angle ranging from -180 to 180 degrees from north * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers * @param {Object} [options.properties={}] translate properties to destination point * @returns {Feature<Point>} Destination point. * @example * var pt = turf.point([-75.343, 39.984], {"marker-color": "F00"}); * var distance = 50; * var bearing = 90; * var options = {units: 'miles'}; * * var destination = turf.rhumbDestination(pt, distance, bearing, options); * * //addToMap * var addToMap = [pt, destination] * destination.properties['marker-color'] = '#00F'; */ function rhumbDestination(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; // validation if (!origin) throw new Error('origin is required'); if (distance === undefined || distance === null) throw new Error('distance is required'); if (bearing === undefined || bearing === null) throw new Error('bearing is required'); if (!(distance >= 0)) throw new Error('distance must be greater than 0'); var distanceInMeters = convertLength(distance, units, 'meters'); var coords = getCoord(origin); var destination = calculateRhumbDestination(coords, distanceInMeters, bearing); // 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] - coords[0] > 180) ? -360 : (coords[0] - destination[0] > 180) ? 360 : 0; return point(destination, properties); }
export function randomLineString(count, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var bbox = options.bbox; var num_vertices = options.num_vertices; var max_length = options.max_length; var max_rotation = options.max_rotation; if (count === undefined || count === null) count = 1; // Default parameters if (!isNumber(num_vertices) || num_vertices < 2) num_vertices = 10; if (!isNumber(max_length)) max_length = 0.0001; if (!isNumber(max_rotation)) max_rotation = Math.PI / 8; var features = []; for (var i = 0; i < count; i++) { var startingPoint = randomPosition(bbox); var vertices = [startingPoint]; for (var j = 0; j < num_vertices - 1; j++) { var priorAngle = (j === 0) ? Math.random() * 2 * Math.PI : Math.tan( (vertices[j][1] - vertices[j - 1][1]) / (vertices[j][0] - vertices[j - 1][0]) ); var angle = priorAngle + (Math.random() - 0.5) * max_rotation * 2; var distance = Math.random() * max_length; vertices.push([ vertices[j][0] + distance * Math.cos(angle), vertices[j][1] + distance * Math.sin(angle) ]); } features.push(lineString(vertices)); } return featureCollection(features); }
/** * Takes a {@link FeatureCollection} of points and calculates the median center, * algorithimically. The median center is understood as the point that is * requires the least total travel from all other points. * * Turfjs has four different functions for calculating the center of a set of * data. Each is useful depending on circumstance. * * `@turf/center` finds the simple center of a dataset, by finding the * midpoint between the extents of the data. That is, it divides in half the * farthest east and farthest west point as well as the farthest north and * farthest south. * * `@turf/center-of-mass` imagines that the dataset is a sheet of paper. * The center of mass is where the sheet would balance on a fingertip. * * `@turf/center-mean` takes the averages of all the coordinates and * produces a value that respects that. Unlike `@turf/center`, it is * sensitive to clusters and outliers. It lands in the statistical middle of a * dataset, not the geographical. It can also be weighted, meaning certain * points are more important than others. * * `@turf/center-median` takes the mean center and tries to find, iteratively, * a new point that requires the least amount of travel from all the points in * the dataset. It is not as sensitive to outliers as `@turf/center`, but it is * attracted to clustered data. It, too, can be weighted. * * **Bibliography** * * Harold W. Kuhn and Robert E. Kuenne, βAn Efficient Algorithm for the * Numerical Solution of the Generalized Weber Problem in Spatial * Economics,β _Journal of Regional Science_ 4, no. 2 (1962): 21β33, * doi:{@link https://doi.org/10.1111/j.1467-9787.1962.tb00902.x}. * * James E. Burt, Gerald M. Barber, and David L. Rigby, _Elementary * Statistics for Geographers_, 3rd ed., New York: The Guilford * Press, 2009, 150β151. * * @name centerMedian * @param {FeatureCollection<any>} features Any GeoJSON Feature Collection * @param {Object} [options={}] Optional parameters * @param {string} [options.weight] the property name used to weight the center * @param {number} [options.tolerance=0.001] the difference in distance between candidate medians at which point the algorighim stops iterating. * @param {number} [options.counter=10] how many attempts to find the median, should the tolerance be insufficient. * @returns {Feature<Point>} The median center of the collection * @example * var points = turf.points([[0, 0], [1, 0], [0, 1], [5, 8]]); * var medianCenter = turf.centerMedian(points); * * //addToMap * var addToMap = [points, medianCenter] */ function centerMedian(features, options) { // Optional params options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var counter = options.counter || 10; if (!isNumber(counter)) throw new Error('counter must be a number'); var weightTerm = options.weight; // Calculate mean center: var meanCenter = centerMean(features, {weight: options.weight}); // Calculate center of every feature: var centroids = featureCollection([]); featureEach(features, function (feature) { centroids.features.push(centroid(feature, {weight: feature.properties[weightTerm]})); }); centroids.properties = { tolerance: options.tolerance, medianCandidates: [] }; return findMedian(meanCenter.geometry.coordinates, [0, 0], centroids, counter); }
/** * Moves any geojson Feature or Geometry of a specified distance along a Rhumb Line * on the provided direction angle. * * @name transformTranslate * @param {GeoJSON} geojson object to be translated * @param {number} distance length of the motion; negative values determine motion in opposite direction * @param {number} direction of the motion; angle from North in decimal degrees, positive clockwise * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] in which `distance` will be express; miles, kilometers, degrees, or radians * @param {number} [options.zTranslation=0] length of the vertical motion, same unit of distance * @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true) * @returns {GeoJSON} the translated GeoJSON object * @example * var poly = turf.polygon([[[0,29],[3.5,29],[2.5,32],[0,29]]]); * var translatedPoly = turf.transformTranslate(poly, 100, 35); * * //addToMap * var addToMap = [poly, translatedPoly]; * translatedPoly.properties = {stroke: '#F00', 'stroke-width': 4}; */ function transformTranslate(geojson, distance, direction, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); var units = options.units; var zTranslation = options.zTranslation; var mutate = options.mutate; // Input validation if (!geojson) throw new Error('geojson is required'); if (distance === undefined || distance === null || isNaN(distance)) throw new Error('distance is required'); if (zTranslation && typeof zTranslation !== 'number' && isNaN(zTranslation)) throw new Error('zTranslation is not a number'); // Shortcut no-motion zTranslation = (zTranslation !== undefined) ? zTranslation : 0; if (distance === 0 && zTranslation === 0) return geojson; if (direction === undefined || direction === null || isNaN(direction)) throw new Error('direction is required'); // Invert with negative distances if (distance < 0) { distance = -distance; direction = -direction; } // Clone geojson to avoid side effects if (mutate === false || mutate === undefined) geojson = clone(geojson); // Translate each coordinate coordEach(geojson, function (pointCoords) { var newCoords = getCoords(rhumbDestination(pointCoords, distance, direction, {units: units})); pointCoords[0] = newCoords[0]; pointCoords[1] = newCoords[1]; if (zTranslation && pointCoords.length === 3) pointCoords[2] += zTranslation; }); return geojson; }
/** * 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<Point>} 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); }
export function randomPosition(bbox) { if (isObject(bbox)) bbox = bbox.bbox; if (bbox && !Array.isArray(bbox)) throw new Error('bbox is invalid'); if (bbox) return coordInBBox(bbox); else return [lon(), lat()]; }
/** * Takes a bounding box and a cell depth and returns a set of triangular {@link Polygon|polygons} in a grid. * * @name triangleGrid * @param {Array<number>} bbox extent in [minX, minY, maxX, maxY] order * @param {number} cellSide dimension of each cell * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] used in calculating cellSide, can be degrees, radians, miles, or kilometers * @param {Feature<Polygon|MultiPolygon>} [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<Polygon>} grid of polygons * @example * var bbox = [-95, 30 ,-85, 40]; * var cellSide = 50; * var options = {units: 'miles'}; * * var triangleGrid = turf.triangleGrid(bbox, cellSide, options); * * //addToMap * var addToMap = [triangleGrid]; */ function triangleGrid(bbox, cellSide, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); // var units = options.units; var properties = options.properties; var mask = options.mask; // 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'); // Main var xFraction = cellSide / (distance([bbox[0], bbox[1]], [bbox[2], bbox[1]], options)); var cellWidth = xFraction * (bbox[2] - bbox[0]); var yFraction = cellSide / (distance([bbox[0], bbox[1]], [bbox[0], bbox[3]], options)); var cellHeight = yFraction * (bbox[3] - bbox[1]); var xi = 0; var currentX = bbox[0]; while (currentX <= bbox[2]) { var yi = 0; var currentY = bbox[1]; while (currentY <= bbox[3]) { var cellTriangle1 = null; var cellTriangle2 = null; if (xi % 2 === 0 && yi % 2 === 0) { cellTriangle1 = polygon([[ [currentX, currentY], [currentX, currentY + cellHeight], [currentX + cellWidth, currentY], [currentX, currentY] ]], properties); cellTriangle2 = polygon([[ [currentX, currentY + cellHeight], [currentX + cellWidth, currentY + cellHeight], [currentX + cellWidth, currentY], [currentX, currentY + cellHeight] ]], properties); } else if (xi % 2 === 0 && yi % 2 === 1) { cellTriangle1 = polygon([[ [currentX, currentY], [currentX + cellWidth, currentY + cellHeight], [currentX + cellWidth, currentY], [currentX, currentY] ]], properties); cellTriangle2 = polygon([[ [currentX, currentY], [currentX, currentY + cellHeight], [currentX + cellWidth, currentY + cellHeight], [currentX, currentY] ]], properties); } else if (yi % 2 === 0 && xi % 2 === 1) { cellTriangle1 = polygon([[ [currentX, currentY], [currentX, currentY + cellHeight], [currentX + cellWidth, currentY + cellHeight], [currentX, currentY] ]], properties); cellTriangle2 = polygon([[ [currentX, currentY], [currentX + cellWidth, currentY + cellHeight], [currentX + cellWidth, currentY], [currentX, currentY] ]], properties); } else if (yi % 2 === 1 && xi % 2 === 1) { cellTriangle1 = polygon([[ [currentX, currentY], [currentX, currentY + cellHeight], [currentX + cellWidth, currentY], [currentX, currentY] ]], properties); cellTriangle2 = polygon([[ [currentX, currentY + cellHeight], [currentX + cellWidth, currentY + cellHeight], [currentX + cellWidth, currentY], [currentX, currentY + cellHeight] ]], properties); } if (mask) { if (intersect(mask, cellTriangle1)) results.push(cellTriangle1); if (intersect(mask, cellTriangle2)) results.push(cellTriangle2); } else { results.push(cellTriangle1); results.push(cellTriangle2); } currentY += cellHeight; yi++; } xi++; currentX += cellWidth; } return featureCollection(results); }
/** * Takes a bounding box and the diameter of the cell and returns a {@link FeatureCollection} of flat-topped * hexagons or triangles ({@link Polygon} features) aligned in an "odd-q" vertical grid as * described in [Hexagonal Grids](http://www.redblobgames.com/grids/hexagons/). * * @name hexGrid * @param {BBox} bbox extent in [minX, minY, maxX, maxY] order * @param {number} cellSide length of the side of the the hexagons or triangles, in units. It will also coincide with the * radius of the circumcircle of the hexagons. * @param {Object} [options={}] Optional parameters * @param {string} [options.units='kilometers'] used in calculating cell size, can be degrees, radians, miles, or kilometers * @param {Object} [options.properties={}] passed to each hexagon or triangle of the grid * @param {Feature<Polygon|MultiPolygon>} [options.mask] if passed a Polygon or MultiPolygon, the grid Points will be created only inside it * @param {boolean} [options.triangles=false] whether to return as triangles instead of hexagons * @returns {FeatureCollection<Polygon>} a hexagonal grid * @example * var bbox = [-96,31,-84,40]; * var cellSide = 50; * var options = {units: 'miles'}; * * var hexgrid = turf.hexGrid(bbox, cellSide, options); * * //addToMap * var addToMap = [hexgrid]; */ function hexGrid(bbox, cellSide, options) { // Optional parameters options = options || {}; if (!isObject(options)) throw new Error('options is invalid'); // var units = options.units; var properties = options.properties || {}; var triangles = options.triangles; var mask = options.mask; // 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 centerY = (south + north) / 2; var centerX = (west + east) / 2; // https://github.com/Turfjs/turf/issues/758 var xFraction = cellSide * 2 / (distance([west, centerY], [east, centerY], options)); var cellWidth = xFraction * (east - west); var yFraction = cellSide * 2 / (distance([centerX, south], [centerX, north], options)); var cellHeight = yFraction * (north - south); var radius = cellWidth / 2; var hex_width = radius * 2; var hex_height = Math.sqrt(3) / 2 * cellHeight; var box_width = east - west; var box_height = north - south; var x_interval = 3 / 4 * hex_width; var y_interval = hex_height; // adjust box_width so all hexagons will be inside the bbox var x_span = (box_width - hex_width) / (hex_width - radius / 2); var x_count = Math.floor(x_span); var x_adjust = ((x_count * x_interval - radius / 2) - box_width) / 2 - radius / 2 + x_interval / 2; // adjust box_height so all hexagons will be inside the bbox var y_count = Math.floor((box_height - hex_height) / hex_height); var y_adjust = (box_height - y_count * hex_height) / 2; var hasOffsetY = y_count * hex_height - box_height > hex_height / 2; if (hasOffsetY) { y_adjust -= hex_height / 4; } // Precompute cosines and sines of angles used in hexagon creation for performance gain var cosines = []; var sines = []; for (var i = 0; i < 6; i++) { var angle = 2 * Math.PI / 6 * i; cosines.push(Math.cos(angle)); sines.push(Math.sin(angle)); } var results = []; for (var x = 0; x <= x_count; x++) { for (var y = 0; y <= y_count; y++) { var isOdd = x % 2 === 1; if (y === 0 && isOdd) continue; if (y === 0 && hasOffsetY) continue; var center_x = x * x_interval + west - x_adjust; var center_y = y * y_interval + south + y_adjust; if (isOdd) { center_y -= hex_height / 2; } if (triangles === true) { hexTriangles( [center_x, center_y], cellWidth / 2, cellHeight / 2, properties, cosines, sines).forEach(function (triangle) { if (mask) { if (intersect(mask, triangle)) results.push(triangle); } else { results.push(triangle); } }); } else { var hex = hexagon( [center_x, center_y], cellWidth / 2, cellHeight / 2, properties, cosines, sines ); if (mask) { if (intersect(mask, hex)) results.push(hex); } else { results.push(hex); } } } } return featureCollection(results); }