diff --git a/packages/turf-isolines/index.ts b/packages/turf-isolines/index.ts index 6ed03bfd02..53098de9a2 100644 --- a/packages/turf-isolines/index.ts +++ b/packages/turf-isolines/index.ts @@ -1,5 +1,4 @@ import bbox from "@turf/bbox"; -import clone from "@turf/clone"; import { coordEach } from "@turf/meta"; import { collectionOf } from "@turf/invariant"; import { multiLineString, featureCollection, isObject } from "@turf/helpers"; @@ -157,35 +156,11 @@ function rescaleIsolines( point[1] = point[1] * scaleY + y0; }; - // Ok. We need to *clone* the returned isolines due to the behaviour of - // the marchingsquare library behaviour. Long story short, if we don't - // do this, the last point in some isoLines has the coordEach function - // run twice, leading to one point in the isoline rendering WAY out of - // position. - const clonedIsoLines: Feature[] = []; - createdIsoLines.forEach((isoline) => { - clonedIsoLines.push(clone(isoline)); - }); - // resize and shift each point/line of the createdIsoLines - clonedIsoLines.forEach((isoline) => { + createdIsoLines.forEach((isoline) => { coordEach(isoline, resize); }); - return clonedIsoLines; + return createdIsoLines; } -/* - * A bit more background on the isoContours behaviour described above. It - * seems like in some cases the last coord of the line references the same - * coord array as the first point. This just seems to be the way they're - * returned from the library: - * [0] -> A - * [1] -> B - * [2] -> C - * [3] -> A - * Running coordEach over this mutates the values in A, then B and C, then - * mutates [3] which happens to also be a reference to A. So the rescale - * transform is run twice on that coord. - */ - export default isolines; diff --git a/packages/turf-isolines/lib/marchingsquares-isocontours.js b/packages/turf-isolines/lib/marchingsquares-isocontours.js deleted file mode 100644 index 22869e863a..0000000000 --- a/packages/turf-isolines/lib/marchingsquares-isocontours.js +++ /dev/null @@ -1,404 +0,0 @@ -/** - * @license GNU Affero General Public License. - * Copyright (c) 2015, 2015 Ronny Lorenz - * v. 1.2.0 - * https://github.com/RaumZeit/MarchingSquares.js - * - * MarchingSquaresJS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MarchingSquaresJS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * As additional permission under GNU Affero General Public License version 3 - * section 7, third-party projects (personal or commercial) may distribute, - * include, or link against UNMODIFIED VERSIONS of MarchingSquaresJS without the - * requirement that said third-party project for that reason alone becomes - * subject to any requirement of the GNU Affero General Public License version 3. - * Any modifications to MarchingSquaresJS, however, must be shared with the public - * and made available. - * - * In summary this: - * - allows you to use MarchingSquaresJS at no cost - * - allows you to use MarchingSquaresJS for both personal and commercial purposes - * - allows you to distribute UNMODIFIED VERSIONS of MarchingSquaresJS under any - * license as long as this license notice is included - * - enables you to keep the source code of your program that uses MarchingSquaresJS - * undisclosed - * - forces you to share any modifications you have made to MarchingSquaresJS, - * e.g. bug-fixes - * - * You should have received a copy of the GNU Affero General Public License - * along with MarchingSquaresJS. If not, see . - */ - -/** - * Compute the isocontour(s) of a scalar 2D field given - * a certain threshold by applying the Marching Squares - * Algorithm. The function returns a list of path coordinates - */ -var defaultSettings = { - successCallback: null, - verbose: false, -}; - -var settings = {}; - -export default function isoContours(data, threshold, options) { - /* process options */ - options = options ? options : {}; - - var optionKeys = Object.keys(defaultSettings); - - for (var i = 0; i < optionKeys.length; i++) { - var key = optionKeys[i]; - var val = options[key]; - val = - typeof val !== "undefined" && val !== null ? val : defaultSettings[key]; - - settings[key] = val; - } - - if (settings.verbose) - console.log( - "MarchingSquaresJS-isoContours: computing isocontour for " + threshold - ); - - var ret = contourGrid2Paths(computeContourGrid(data, threshold)); - - if (typeof settings.successCallback === "function") - settings.successCallback(ret); - - return ret; -} - -/* - Thats all for the public interface, below follows the actual - implementation -*/ - -/* -################################ -Isocontour implementation below -################################ -*/ - -/* assume that x1 == 1 && x0 == 0 */ -function interpolateX(y, y0, y1) { - return (y - y0) / (y1 - y0); -} - -/* compute the isocontour 4-bit grid */ -function computeContourGrid(data, threshold) { - var rows = data.length - 1; - var cols = data[0].length - 1; - var ContourGrid = { rows: rows, cols: cols, cells: [] }; - - for (var j = 0; j < rows; ++j) { - ContourGrid.cells[j] = []; - for (var i = 0; i < cols; ++i) { - /* compose the 4-bit corner representation */ - var cval = 0; - - var tl = data[j + 1][i]; - var tr = data[j + 1][i + 1]; - var br = data[j][i + 1]; - var bl = data[j][i]; - - if (isNaN(tl) || isNaN(tr) || isNaN(br) || isNaN(bl)) { - continue; - } - cval |= tl >= threshold ? 8 : 0; - cval |= tr >= threshold ? 4 : 0; - cval |= br >= threshold ? 2 : 0; - cval |= bl >= threshold ? 1 : 0; - - /* resolve ambiguity for cval == 5 || 10 via averaging */ - var flipped = false; - if (cval === 5 || cval === 10) { - var average = (tl + tr + br + bl) / 4; - if (cval === 5 && average < threshold) { - cval = 10; - flipped = true; - } else if (cval === 10 && average < threshold) { - cval = 5; - flipped = true; - } - } - - /* add cell to ContourGrid if it contains edges */ - if (cval !== 0 && cval !== 15) { - var top, bottom, left, right; - top = bottom = left = right = 0.5; - /* interpolate edges of cell */ - if (cval === 1) { - left = 1 - interpolateX(threshold, tl, bl); - bottom = 1 - interpolateX(threshold, br, bl); - } else if (cval === 2) { - bottom = interpolateX(threshold, bl, br); - right = 1 - interpolateX(threshold, tr, br); - } else if (cval === 3) { - left = 1 - interpolateX(threshold, tl, bl); - right = 1 - interpolateX(threshold, tr, br); - } else if (cval === 4) { - top = interpolateX(threshold, tl, tr); - right = interpolateX(threshold, br, tr); - } else if (cval === 5) { - top = interpolateX(threshold, tl, tr); - right = interpolateX(threshold, br, tr); - bottom = 1 - interpolateX(threshold, br, bl); - left = 1 - interpolateX(threshold, tl, bl); - } else if (cval === 6) { - bottom = interpolateX(threshold, bl, br); - top = interpolateX(threshold, tl, tr); - } else if (cval === 7) { - left = 1 - interpolateX(threshold, tl, bl); - top = interpolateX(threshold, tl, tr); - } else if (cval === 8) { - left = interpolateX(threshold, bl, tl); - top = 1 - interpolateX(threshold, tr, tl); - } else if (cval === 9) { - bottom = 1 - interpolateX(threshold, br, bl); - top = 1 - interpolateX(threshold, tr, tl); - } else if (cval === 10) { - top = 1 - interpolateX(threshold, tr, tl); - right = 1 - interpolateX(threshold, tr, br); - bottom = interpolateX(threshold, bl, br); - left = interpolateX(threshold, bl, tl); - } else if (cval === 11) { - top = 1 - interpolateX(threshold, tr, tl); - right = 1 - interpolateX(threshold, tr, br); - } else if (cval === 12) { - left = interpolateX(threshold, bl, tl); - right = interpolateX(threshold, br, tr); - } else if (cval === 13) { - bottom = 1 - interpolateX(threshold, br, bl); - right = interpolateX(threshold, br, tr); - } else if (cval === 14) { - left = interpolateX(threshold, bl, tl); - bottom = interpolateX(threshold, bl, br); - } else { - console.log( - "MarchingSquaresJS-isoContours: Illegal cval detected: " + cval - ); - } - ContourGrid.cells[j][i] = { - cval: cval, - flipped: flipped, - top: top, - right: right, - bottom: bottom, - left: left, - }; - } - } - } - - return ContourGrid; -} - -function isSaddle(cell) { - return cell.cval === 5 || cell.cval === 10; -} - -function isTrivial(cell) { - return cell.cval === 0 || cell.cval === 15; -} - -function clearCell(cell) { - if (!isTrivial(cell) && cell.cval !== 5 && cell.cval !== 10) { - cell.cval = 15; - } -} - -function getXY(cell, edge) { - if (edge === "top") { - return [cell.top, 1.0]; - } else if (edge === "bottom") { - return [cell.bottom, 0.0]; - } else if (edge === "right") { - return [1.0, cell.right]; - } else if (edge === "left") { - return [0.0, cell.left]; - } -} - -function contourGrid2Paths(grid) { - var paths = []; - var path_idx = 0; - var epsilon = 1e-7; - - grid.cells.forEach(function (g, j) { - g.forEach(function (gg, i) { - if (typeof gg !== "undefined" && !isSaddle(gg) && !isTrivial(gg)) { - var p = tracePath(grid.cells, j, i); - var merged = false; - /* we may try to merge paths at this point */ - if (p.info === "mergeable") { - /* - search backwards through the path array to find an entry - that starts with where the current path ends... - */ - var x = p.path[p.path.length - 1][0], - y = p.path[p.path.length - 1][1]; - - for (var k = path_idx - 1; k >= 0; k--) { - if ( - Math.abs(paths[k][0][0] - x) <= epsilon && - Math.abs(paths[k][0][1] - y) <= epsilon - ) { - for (var l = p.path.length - 2; l >= 0; --l) { - paths[k].unshift(p.path[l]); - } - merged = true; - break; - } - } - } - if (!merged) paths[path_idx++] = p.path; - } - }); - }); - - return paths; -} - -/* - construct consecutive line segments from starting cell by - walking arround the enclosed area clock-wise - */ -function tracePath(grid, j, i) { - var maxj = grid.length; - var p = []; - var dxContour = [0, 0, 1, 1, 0, 0, 0, 0, -1, 0, 1, 1, -1, 0, -1, 0]; - var dyContour = [0, -1, 0, 0, 1, 1, 1, 1, 0, -1, 0, 0, 0, -1, 0, 0]; - var dx, dy; - var startEdge = [ - "none", - "left", - "bottom", - "left", - "right", - "none", - "bottom", - "left", - "top", - "top", - "none", - "top", - "right", - "right", - "bottom", - "none", - ]; - var nextEdge = [ - "none", - "bottom", - "right", - "right", - "top", - "top", - "top", - "top", - "left", - "bottom", - "right", - "right", - "left", - "bottom", - "left", - "none", - ]; - var edge; - - var currentCell = grid[j][i]; - - var cval = currentCell.cval; - var edge = startEdge[cval]; - - var pt = getXY(currentCell, edge); - - /* push initial segment */ - p.push([i + pt[0], j + pt[1]]); - edge = nextEdge[cval]; - pt = getXY(currentCell, edge); - p.push([i + pt[0], j + pt[1]]); - clearCell(currentCell); - - /* now walk arround the enclosed area in clockwise-direction */ - var k = i + dxContour[cval]; - var l = j + dyContour[cval]; - var prev_cval = cval; - - while (k >= 0 && l >= 0 && l < maxj && (k != i || l != j)) { - currentCell = grid[l][k]; - if (typeof currentCell === "undefined") { - /* path ends here */ - //console.log(k + " " + l + " is undefined, stopping path!"); - break; - } - cval = currentCell.cval; - if (cval === 0 || cval === 15) { - return { path: p, info: "mergeable" }; - } - edge = nextEdge[cval]; - dx = dxContour[cval]; - dy = dyContour[cval]; - if (cval === 5 || cval === 10) { - /* select upper or lower band, depending on previous cells cval */ - if (cval === 5) { - if (currentCell.flipped) { - /* this is actually a flipped case 10 */ - if (dyContour[prev_cval] === -1) { - edge = "left"; - dx = -1; - dy = 0; - } else { - edge = "right"; - dx = 1; - dy = 0; - } - } else { - /* real case 5 */ - if (dxContour[prev_cval] === -1) { - edge = "bottom"; - dx = 0; - dy = -1; - } - } - } else if (cval === 10) { - if (currentCell.flipped) { - /* this is actually a flipped case 5 */ - if (dxContour[prev_cval] === -1) { - edge = "top"; - dx = 0; - dy = 1; - } else { - edge = "bottom"; - dx = 0; - dy = -1; - } - } else { - /* real case 10 */ - if (dyContour[prev_cval] === 1) { - edge = "left"; - dx = -1; - dy = 0; - } - } - } - } - pt = getXY(currentCell, edge); - p.push([k + pt[0], l + pt[1]]); - clearCell(currentCell); - k += dx; - l += dy; - prev_cval = cval; - } - - return { path: p, info: "closed" }; -} diff --git a/packages/turf-isolines/test.js b/packages/turf-isolines/test.js index 5f9bf4658e..f90b3acb60 100644 --- a/packages/turf-isolines/test.js +++ b/packages/turf-isolines/test.js @@ -79,7 +79,7 @@ test("isolines - skipping first break, from issue #2129", (t) => { }) ); - // Make sure an isoline is created for each break, and that it's + // Make sure an isoline is created for each break, and that its // geometry isn't empty. t.equal(lines.features[0].properties.name, "break5"); t.assert(lines.features[0].geometry.coordinates[0].length > 1);