Example #1
0
/**
 * 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;
}
Example #2
0
/**
 * 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;
}
Example #3
0
/**
 * 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;
}
Example #4
0
/**
 * 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;
}
Example #5
0
/**
 * 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);
}
Example #6
0
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);
}
Example #7
0
 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;
 });
Example #8
0
/**
 * 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);
}
Example #9
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);
}
Example #10
0
/**
 * 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;
}
Example #11
0
//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);
}
Example #12
0
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);
}
Example #13
0
/**
 * 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);
}
Example #14
0
/**
 * 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;
}
Example #15
0
/**
 * 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;
}
Example #16
0
/**
 * 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);
}
Example #17
0
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);
}
Example #18
0
/**
 * 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);
}
Example #19
0
/**
 * 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;
}
Example #20
0
/**
 * 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);
}
Example #21
0
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()];
}
Example #22
0
/**
 * 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);
}
Example #23
0
/**
 * 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);
}