UNPKG

@turf/isolines

Version:

Generate contour lines from a grid of data.

316 lines (313 loc) 9.72 kB
var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; // index.ts import { bbox } from "@turf/bbox"; import { coordEach } from "@turf/meta"; import { collectionOf as collectionOf2 } from "@turf/invariant"; import { multiLineString, featureCollection, isObject as isObject2 } from "@turf/helpers"; // lib/grid-to-matrix.ts import { getCoords, collectionOf } from "@turf/invariant"; import { featureEach } from "@turf/meta"; import { isObject } from "@turf/helpers"; function gridToMatrix(grid, options = {}) { if (!isObject(options)) throw new Error("options is invalid"); const { zProperty = "elevation", flip = false, flags = false } = options; collectionOf(grid, "Point", "input must contain Points"); var pointsMatrix = sortPointsByLatLng(grid, flip); var matrix = []; 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]; if (point.properties == null) { point.properties = {}; } if (point.properties[zProperty]) row.push(point.properties[zProperty]); else row.push(0); if (flags === true) point.properties.matrixPosition = [r, c]; } matrix.push(row); } return matrix; } function sortPointsByLatLng(points, flip) { var pointsByLatitude = {}; featureEach(points, (point) => { var lat = getCoords(point)[1]; if (!pointsByLatitude[lat]) pointsByLatitude[lat] = []; pointsByLatitude[lat].push(point); }); const pointMatrix = []; for (const row of Object.values(pointsByLatitude)) { pointMatrix.push(row.sort((a, b) => getCoords(a)[0] - getCoords(b)[0])); } pointMatrix.sort( flip ? (a, b) => getCoords(a[0])[1] - getCoords(b[0])[1] : (a, b) => getCoords(b[0])[1] - getCoords(a[0])[1] ); return pointMatrix; } // index.ts function isolines(pointGrid, breaks, options) { options = options || {}; if (!isObject2(options)) throw new Error("options is invalid"); const zProperty = options.zProperty || "elevation"; const commonProperties = options.commonProperties || {}; const breaksProperties = options.breaksProperties || []; collectionOf2(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 (!isObject2(commonProperties)) throw new Error("commonProperties must be an Object"); if (!Array.isArray(breaksProperties)) throw new Error("breaksProperties must be an Array"); const matrix = gridToMatrix(pointGrid, { zProperty, flip: true }); const dx = matrix[0].length; if (matrix.length < 2 || dx < 2) { throw new Error("Matrix of points must be at least 2x2"); } for (let i = 1; i < matrix.length; i++) { if (matrix[i].length !== dx) { throw new Error("Matrix of points is not uniform in the x dimension"); } } const createdIsoLines = createIsoLines( matrix, breaks, zProperty, commonProperties, breaksProperties ); const scaledIsolines = rescaleIsolines(createdIsoLines, matrix, pointGrid); return featureCollection(scaledIsolines); } function createIsoLines(matrix, breaks, zProperty, commonProperties, breaksProperties) { const results = []; for (let i = 0; i < breaks.length; i++) { const threshold = +breaks[i]; const properties = __spreadValues(__spreadValues({}, commonProperties), breaksProperties[i]); properties[zProperty] = threshold; const isoline = multiLineString(isoContours(matrix, threshold), properties); results.push(isoline); } return results; } function isoContours(matrix, threshold) { const segments = []; const dy = matrix.length; const dx = matrix[0].length; for (let y = 0; y < dy - 1; y++) { for (let x = 0; x < dx - 1; x++) { const tr = matrix[y + 1][x + 1]; const br = matrix[y][x + 1]; const bl = matrix[y][x]; const tl = matrix[y + 1][x]; let grid = (tl >= threshold ? 8 : 0) | (tr >= threshold ? 4 : 0) | (br >= threshold ? 2 : 0) | (bl >= threshold ? 1 : 0); switch (grid) { case 0: continue; case 1: segments.push([ [x + frac(bl, br), y], [x, y + frac(bl, tl)] ]); break; case 2: segments.push([ [x + 1, y + frac(br, tr)], [x + frac(bl, br), y] ]); break; case 3: segments.push([ [x + 1, y + frac(br, tr)], [x, y + frac(bl, tl)] ]); break; case 4: segments.push([ [x + frac(tl, tr), y + 1], [x + 1, y + frac(br, tr)] ]); break; case 5: { const avg = (tl + tr + br + bl) / 4; const above = avg >= threshold; if (above) { segments.push( [ [x + frac(tl, tr), y + 1], [x, y + frac(bl, tl)] ], [ [x + frac(bl, br), y], [x + 1, y + frac(br, tr)] ] ); } else { segments.push( [ [x + frac(tl, tr), y + 1], [x + 1, y + frac(br, tr)] ], [ [x + frac(bl, br), y], [x, y + frac(bl, tl)] ] ); } break; } case 6: segments.push([ [x + frac(tl, tr), y + 1], [x + frac(bl, br), y] ]); break; case 7: segments.push([ [x + frac(tl, tr), y + 1], [x, y + frac(bl, tl)] ]); break; case 8: segments.push([ [x, y + frac(bl, tl)], [x + frac(tl, tr), y + 1] ]); break; case 9: segments.push([ [x + frac(bl, br), y], [x + frac(tl, tr), y + 1] ]); break; case 10: { const avg = (tl + tr + br + bl) / 4; const above = avg >= threshold; if (above) { segments.push( [ [x, y + frac(bl, tl)], [x + frac(bl, br), y] ], [ [x + 1, y + frac(br, tr)], [x + frac(tl, tr), y + 1] ] ); } else { segments.push( [ [x, y + frac(bl, tl)], [x + frac(tl, tr), y + 1] ], [ [x + 1, y + frac(br, tr)], [x + frac(bl, br), y] ] ); } break; } case 11: segments.push([ [x + 1, y + frac(br, tr)], [x + frac(tl, tr), y + 1] ]); break; case 12: segments.push([ [x, y + frac(bl, tl)], [x + 1, y + frac(br, tr)] ]); break; case 13: segments.push([ [x + frac(bl, br), y], [x + 1, y + frac(br, tr)] ]); break; case 14: segments.push([ [x, y + frac(bl, tl)], [x + frac(bl, br), y] ]); break; case 15: continue; } } } const contours = []; while (segments.length > 0) { const contour = [...segments.shift()]; contours.push(contour); let found; do { found = false; for (let i = 0; i < segments.length; i++) { const segment = segments[i]; if (segment[0][0] === contour[contour.length - 1][0] && segment[0][1] === contour[contour.length - 1][1]) { found = true; contour.push(segment[1]); segments.splice(i, 1); break; } if (segment[1][0] === contour[0][0] && segment[1][1] === contour[0][1]) { found = true; contour.unshift(segment[0]); segments.splice(i, 1); break; } } } while (found); } return contours; function frac(z0, z1) { if (z0 === z1) { return 0.5; } let t = (threshold - z0) / (z1 - z0); return t > 1 ? 1 : t < 0 ? 0 : t; } } function rescaleIsolines(createdIsoLines, matrix, points) { const gridBbox = bbox(points); const originalWidth = gridBbox[2] - gridBbox[0]; const originalHeigth = gridBbox[3] - gridBbox[1]; const x0 = gridBbox[0]; const y0 = gridBbox[1]; const matrixWidth = matrix[0].length - 1; const matrixHeight = matrix.length - 1; const scaleX = originalWidth / matrixWidth; const scaleY = originalHeigth / matrixHeight; const resize = (point) => { point[0] = point[0] * scaleX + x0; point[1] = point[1] * scaleY + y0; }; createdIsoLines.forEach((isoline) => { coordEach(isoline, resize); }); return createdIsoLines; } var index_default = isolines; export { index_default as default, isolines }; //# sourceMappingURL=index.js.map