@spatial/isolines
Version:
turf isolines module
134 lines (118 loc) • 5.57 kB
JavaScript
import bbox from '@spatial/bbox';
import { coordEach } from '@spatial/meta';
import { collectionOf } from '@spatial/invariant';
import { multiLineString, featureCollection, isObject } from '@spatial/helpers';
import isoContours from './lib/marchingsquares-isocontours';
import gridToMatrix from './lib/grid-to-matrix';
/**
* Takes a grid {@link FeatureCollection} of {@link Point} features with z-values and an array of
* value breaks and generates [isolines](http://en.wikipedia.org/wiki/Isoline).
*
* @name isolines
* @param {FeatureCollection<Point>} pointGrid input points
* @param {Array<number>} breaks values of `zProperty` where to draw isolines
* @param {Object} [options={}] Optional parameters
* @param {string} [options.zProperty='elevation'] the property name in `points` from which z-values will be pulled
* @param {Object} [options.commonProperties={}] GeoJSON properties passed to ALL isolines
* @param {Array<Object>} [options.breaksProperties=[]] GeoJSON properties passed, in order, to the correspondent isoline;
* the breaks array will define the order in which the isolines are created
* @returns {FeatureCollection<MultiLineString>} a FeatureCollection of {@link MultiLineString} features representing isolines
* @example
* // create a grid of points with random z-values in their properties
* var extent = [0, 30, 20, 50];
* var cellWidth = 100;
* var pointGrid = turf.pointGrid(extent, cellWidth, {units: 'miles'});
*
* for (var i = 0; i < pointGrid.features.length; i++) {
* pointGrid.features[i].properties.temperature = Math.random() * 10;
* }
* var breaks = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
*
* var lines = turf.isolines(pointGrid, breaks, {zProperty: 'temperature'});
*
* //addToMap
* var addToMap = [lines];
*/
function isolines(pointGrid, breaks, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error('options is invalid');
const zProperty = options.zProperty || 'elevation';
const commonProperties = options.commonProperties || {};
const breaksProperties = options.breaksProperties || [];
// Input validation
collectionOf(pointGrid, 'Point', 'Input must contain Points');
if (!breaks) throw new Error('breaks is required');
if (!Array.isArray(breaks)) throw new Error('breaks must be an Array');
if (!isObject(commonProperties)) throw new Error('commonProperties must be an Object');
if (!Array.isArray(breaksProperties)) throw new Error('breaksProperties must be an Array');
// Isoline methods
const matrix = gridToMatrix(pointGrid, {zProperty, flip: true});
const createdIsoLines = createIsoLines(matrix, breaks, zProperty, commonProperties, breaksProperties);
const scaledIsolines = rescaleIsolines(createdIsoLines, matrix, pointGrid);
return featureCollection(scaledIsolines);
}
/**
* Creates the isolines lines (featuresCollection of MultiLineString features) from the 2D data grid
*
* Marchingsquares process the grid data as a 3D representation of a function on a 2D plane, therefore it
* assumes the points (x-y coordinates) are one 'unit' distance. The result of the isolines function needs to be
* rescaled, with turfjs, to the original area and proportions on the map
*
* @private
* @param {Array<Array<number>>} matrix Grid Data
* @param {Array<number>} breaks Breaks
* @param {string} zProperty name of the z-values property
* @param {Object} [commonProperties={}] GeoJSON properties passed to ALL isolines
* @param {Object} [breaksProperties=[]] GeoJSON properties passed to the correspondent isoline
* @returns {Array<MultiLineString>} isolines
*/
function createIsoLines(matrix, breaks, zProperty, commonProperties, breaksProperties) {
const results = [];
for (let i = 1; i < breaks.length; i++) {
const threshold = +breaks[i]; // make sure it's a number
const properties = Object.assign(
{},
commonProperties,
breaksProperties[i]
);
properties[zProperty] = threshold;
const isoline = multiLineString(isoContours(matrix, threshold), properties);
results.push(isoline);
}
return results;
}
/**
* Translates and scales isolines
*
* @private
* @param {Array<MultiLineString>} createdIsoLines to be rescaled
* @param {Array<Array<number>>} matrix Grid Data
* @param {Object} points Points by Latitude
* @returns {Array<MultiLineString>} isolines
*/
function rescaleIsolines(createdIsoLines, matrix, points) {
// get dimensions (on the map) of the original grid
const gridBbox = bbox(points); // [ minX, minY, maxX, maxY ]
const originalWidth = gridBbox[2] - gridBbox[0];
const originalHeigth = gridBbox[3] - gridBbox[1];
// get origin, which is the first point of the last row on the rectangular data on the map
const x0 = gridBbox[0];
const y0 = gridBbox[1];
// get number of cells per side
const matrixWidth = matrix[0].length - 1;
const matrixHeight = matrix.length - 1;
// calculate the scaling factor between matrix and rectangular grid on the map
const scaleX = originalWidth / matrixWidth;
const scaleY = originalHeigth / matrixHeight;
const resize = function (point) {
point[0] = point[0] * scaleX + x0;
point[1] = point[1] * scaleY + y0;
};
// resize and shift each point/line of the createdIsoLines
createdIsoLines.forEach(function (isoline) {
coordEach(isoline, resize);
});
return createdIsoLines;
}
export default isolines;