@turf/isolines 6.5.0 → 7.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/dist/es/index.js +61 -589
- package/dist/es/lib/grid-to-matrix.js +101 -0
- package/dist/es/lib/marchingsquares-isocontours.js +385 -0
- package/dist/js/index.d.ts +36 -0
- package/dist/js/index.js +65 -598
- package/dist/js/lib/grid-to-matrix.d.ts +37 -0
- package/dist/js/lib/grid-to-matrix.js +104 -0
- package/dist/js/lib/marchingsquares-isocontours.d.ts +1 -0
- package/dist/js/lib/marchingsquares-isocontours.js +388 -0
- package/package.json +25 -20
- package/index.d.ts +0 -19
package/dist/es/index.js
CHANGED
|
@@ -1,515 +1,9 @@
|
|
|
1
|
-
import bbox from
|
|
2
|
-
import {
|
|
3
|
-
import { collectionOf
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @license GNU Affero General Public License.
|
|
9
|
-
* Copyright (c) 2015, 2015 Ronny Lorenz <ronny@tbi.univie.ac.at>
|
|
10
|
-
* v. 1.2.0
|
|
11
|
-
* https://github.com/RaumZeit/MarchingSquares.js
|
|
12
|
-
*
|
|
13
|
-
* MarchingSquaresJS is free software: you can redistribute it and/or modify
|
|
14
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
15
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
16
|
-
* (at your option) any later version.
|
|
17
|
-
*
|
|
18
|
-
* MarchingSquaresJS is distributed in the hope that it will be useful,
|
|
19
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
20
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
21
|
-
* GNU Affero General Public License for more details.
|
|
22
|
-
*
|
|
23
|
-
* As additional permission under GNU Affero General Public License version 3
|
|
24
|
-
* section 7, third-party projects (personal or commercial) may distribute,
|
|
25
|
-
* include, or link against UNMODIFIED VERSIONS of MarchingSquaresJS without the
|
|
26
|
-
* requirement that said third-party project for that reason alone becomes
|
|
27
|
-
* subject to any requirement of the GNU Affero General Public License version 3.
|
|
28
|
-
* Any modifications to MarchingSquaresJS, however, must be shared with the public
|
|
29
|
-
* and made available.
|
|
30
|
-
*
|
|
31
|
-
* In summary this:
|
|
32
|
-
* - allows you to use MarchingSquaresJS at no cost
|
|
33
|
-
* - allows you to use MarchingSquaresJS for both personal and commercial purposes
|
|
34
|
-
* - allows you to distribute UNMODIFIED VERSIONS of MarchingSquaresJS under any
|
|
35
|
-
* license as long as this license notice is included
|
|
36
|
-
* - enables you to keep the source code of your program that uses MarchingSquaresJS
|
|
37
|
-
* undisclosed
|
|
38
|
-
* - forces you to share any modifications you have made to MarchingSquaresJS,
|
|
39
|
-
* e.g. bug-fixes
|
|
40
|
-
*
|
|
41
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
42
|
-
* along with MarchingSquaresJS. If not, see <http://www.gnu.org/licenses/>.
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Compute the isocontour(s) of a scalar 2D field given
|
|
47
|
-
* a certain threshold by applying the Marching Squares
|
|
48
|
-
* Algorithm. The function returns a list of path coordinates
|
|
49
|
-
*/
|
|
50
|
-
var defaultSettings = {
|
|
51
|
-
successCallback: null,
|
|
52
|
-
verbose: false,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
var settings = {};
|
|
56
|
-
|
|
57
|
-
function isoContours(data, threshold, options) {
|
|
58
|
-
/* process options */
|
|
59
|
-
options = options ? options : {};
|
|
60
|
-
|
|
61
|
-
var optionKeys = Object.keys(defaultSettings);
|
|
62
|
-
|
|
63
|
-
for (var i = 0; i < optionKeys.length; i++) {
|
|
64
|
-
var key = optionKeys[i];
|
|
65
|
-
var val = options[key];
|
|
66
|
-
val =
|
|
67
|
-
typeof val !== "undefined" && val !== null ? val : defaultSettings[key];
|
|
68
|
-
|
|
69
|
-
settings[key] = val;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (settings.verbose)
|
|
73
|
-
console.log(
|
|
74
|
-
"MarchingSquaresJS-isoContours: computing isocontour for " + threshold
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
var ret = contourGrid2Paths(computeContourGrid(data, threshold));
|
|
78
|
-
|
|
79
|
-
if (typeof settings.successCallback === "function")
|
|
80
|
-
settings.successCallback(ret);
|
|
81
|
-
|
|
82
|
-
return ret;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/*
|
|
86
|
-
Thats all for the public interface, below follows the actual
|
|
87
|
-
implementation
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
/*
|
|
91
|
-
################################
|
|
92
|
-
Isocontour implementation below
|
|
93
|
-
################################
|
|
94
|
-
*/
|
|
95
|
-
|
|
96
|
-
/* assume that x1 == 1 && x0 == 0 */
|
|
97
|
-
function interpolateX(y, y0, y1) {
|
|
98
|
-
return (y - y0) / (y1 - y0);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* compute the isocontour 4-bit grid */
|
|
102
|
-
function computeContourGrid(data, threshold) {
|
|
103
|
-
var rows = data.length - 1;
|
|
104
|
-
var cols = data[0].length - 1;
|
|
105
|
-
var ContourGrid = { rows: rows, cols: cols, cells: [] };
|
|
106
|
-
|
|
107
|
-
for (var j = 0; j < rows; ++j) {
|
|
108
|
-
ContourGrid.cells[j] = [];
|
|
109
|
-
for (var i = 0; i < cols; ++i) {
|
|
110
|
-
/* compose the 4-bit corner representation */
|
|
111
|
-
var cval = 0;
|
|
112
|
-
|
|
113
|
-
var tl = data[j + 1][i];
|
|
114
|
-
var tr = data[j + 1][i + 1];
|
|
115
|
-
var br = data[j][i + 1];
|
|
116
|
-
var bl = data[j][i];
|
|
117
|
-
|
|
118
|
-
if (isNaN(tl) || isNaN(tr) || isNaN(br) || isNaN(bl)) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
cval |= tl >= threshold ? 8 : 0;
|
|
122
|
-
cval |= tr >= threshold ? 4 : 0;
|
|
123
|
-
cval |= br >= threshold ? 2 : 0;
|
|
124
|
-
cval |= bl >= threshold ? 1 : 0;
|
|
125
|
-
|
|
126
|
-
/* resolve ambiguity for cval == 5 || 10 via averaging */
|
|
127
|
-
var flipped = false;
|
|
128
|
-
if (cval === 5 || cval === 10) {
|
|
129
|
-
var average = (tl + tr + br + bl) / 4;
|
|
130
|
-
if (cval === 5 && average < threshold) {
|
|
131
|
-
cval = 10;
|
|
132
|
-
flipped = true;
|
|
133
|
-
} else if (cval === 10 && average < threshold) {
|
|
134
|
-
cval = 5;
|
|
135
|
-
flipped = true;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/* add cell to ContourGrid if it contains edges */
|
|
140
|
-
if (cval !== 0 && cval !== 15) {
|
|
141
|
-
var top, bottom, left, right;
|
|
142
|
-
top = bottom = left = right = 0.5;
|
|
143
|
-
/* interpolate edges of cell */
|
|
144
|
-
if (cval === 1) {
|
|
145
|
-
left = 1 - interpolateX(threshold, tl, bl);
|
|
146
|
-
bottom = 1 - interpolateX(threshold, br, bl);
|
|
147
|
-
} else if (cval === 2) {
|
|
148
|
-
bottom = interpolateX(threshold, bl, br);
|
|
149
|
-
right = 1 - interpolateX(threshold, tr, br);
|
|
150
|
-
} else if (cval === 3) {
|
|
151
|
-
left = 1 - interpolateX(threshold, tl, bl);
|
|
152
|
-
right = 1 - interpolateX(threshold, tr, br);
|
|
153
|
-
} else if (cval === 4) {
|
|
154
|
-
top = interpolateX(threshold, tl, tr);
|
|
155
|
-
right = interpolateX(threshold, br, tr);
|
|
156
|
-
} else if (cval === 5) {
|
|
157
|
-
top = interpolateX(threshold, tl, tr);
|
|
158
|
-
right = interpolateX(threshold, br, tr);
|
|
159
|
-
bottom = 1 - interpolateX(threshold, br, bl);
|
|
160
|
-
left = 1 - interpolateX(threshold, tl, bl);
|
|
161
|
-
} else if (cval === 6) {
|
|
162
|
-
bottom = interpolateX(threshold, bl, br);
|
|
163
|
-
top = interpolateX(threshold, tl, tr);
|
|
164
|
-
} else if (cval === 7) {
|
|
165
|
-
left = 1 - interpolateX(threshold, tl, bl);
|
|
166
|
-
top = interpolateX(threshold, tl, tr);
|
|
167
|
-
} else if (cval === 8) {
|
|
168
|
-
left = interpolateX(threshold, bl, tl);
|
|
169
|
-
top = 1 - interpolateX(threshold, tr, tl);
|
|
170
|
-
} else if (cval === 9) {
|
|
171
|
-
bottom = 1 - interpolateX(threshold, br, bl);
|
|
172
|
-
top = 1 - interpolateX(threshold, tr, tl);
|
|
173
|
-
} else if (cval === 10) {
|
|
174
|
-
top = 1 - interpolateX(threshold, tr, tl);
|
|
175
|
-
right = 1 - interpolateX(threshold, tr, br);
|
|
176
|
-
bottom = interpolateX(threshold, bl, br);
|
|
177
|
-
left = interpolateX(threshold, bl, tl);
|
|
178
|
-
} else if (cval === 11) {
|
|
179
|
-
top = 1 - interpolateX(threshold, tr, tl);
|
|
180
|
-
right = 1 - interpolateX(threshold, tr, br);
|
|
181
|
-
} else if (cval === 12) {
|
|
182
|
-
left = interpolateX(threshold, bl, tl);
|
|
183
|
-
right = interpolateX(threshold, br, tr);
|
|
184
|
-
} else if (cval === 13) {
|
|
185
|
-
bottom = 1 - interpolateX(threshold, br, bl);
|
|
186
|
-
right = interpolateX(threshold, br, tr);
|
|
187
|
-
} else if (cval === 14) {
|
|
188
|
-
left = interpolateX(threshold, bl, tl);
|
|
189
|
-
bottom = interpolateX(threshold, bl, br);
|
|
190
|
-
} else {
|
|
191
|
-
console.log(
|
|
192
|
-
"MarchingSquaresJS-isoContours: Illegal cval detected: " + cval
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
ContourGrid.cells[j][i] = {
|
|
196
|
-
cval: cval,
|
|
197
|
-
flipped: flipped,
|
|
198
|
-
top: top,
|
|
199
|
-
right: right,
|
|
200
|
-
bottom: bottom,
|
|
201
|
-
left: left,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return ContourGrid;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function isSaddle(cell) {
|
|
211
|
-
return cell.cval === 5 || cell.cval === 10;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function isTrivial(cell) {
|
|
215
|
-
return cell.cval === 0 || cell.cval === 15;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function clearCell(cell) {
|
|
219
|
-
if (!isTrivial(cell) && cell.cval !== 5 && cell.cval !== 10) {
|
|
220
|
-
cell.cval = 15;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function getXY(cell, edge) {
|
|
225
|
-
if (edge === "top") {
|
|
226
|
-
return [cell.top, 1.0];
|
|
227
|
-
} else if (edge === "bottom") {
|
|
228
|
-
return [cell.bottom, 0.0];
|
|
229
|
-
} else if (edge === "right") {
|
|
230
|
-
return [1.0, cell.right];
|
|
231
|
-
} else if (edge === "left") {
|
|
232
|
-
return [0.0, cell.left];
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function contourGrid2Paths(grid) {
|
|
237
|
-
var paths = [];
|
|
238
|
-
var path_idx = 0;
|
|
239
|
-
var epsilon = 1e-7;
|
|
240
|
-
|
|
241
|
-
grid.cells.forEach(function (g, j) {
|
|
242
|
-
g.forEach(function (gg, i) {
|
|
243
|
-
if (typeof gg !== "undefined" && !isSaddle(gg) && !isTrivial(gg)) {
|
|
244
|
-
var p = tracePath(grid.cells, j, i);
|
|
245
|
-
var merged = false;
|
|
246
|
-
/* we may try to merge paths at this point */
|
|
247
|
-
if (p.info === "mergeable") {
|
|
248
|
-
/*
|
|
249
|
-
search backwards through the path array to find an entry
|
|
250
|
-
that starts with where the current path ends...
|
|
251
|
-
*/
|
|
252
|
-
var x = p.path[p.path.length - 1][0],
|
|
253
|
-
y = p.path[p.path.length - 1][1];
|
|
254
|
-
|
|
255
|
-
for (var k = path_idx - 1; k >= 0; k--) {
|
|
256
|
-
if (
|
|
257
|
-
Math.abs(paths[k][0][0] - x) <= epsilon &&
|
|
258
|
-
Math.abs(paths[k][0][1] - y) <= epsilon
|
|
259
|
-
) {
|
|
260
|
-
for (var l = p.path.length - 2; l >= 0; --l) {
|
|
261
|
-
paths[k].unshift(p.path[l]);
|
|
262
|
-
}
|
|
263
|
-
merged = true;
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (!merged) paths[path_idx++] = p.path;
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
return paths;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/*
|
|
277
|
-
construct consecutive line segments from starting cell by
|
|
278
|
-
walking arround the enclosed area clock-wise
|
|
279
|
-
*/
|
|
280
|
-
function tracePath(grid, j, i) {
|
|
281
|
-
var maxj = grid.length;
|
|
282
|
-
var p = [];
|
|
283
|
-
var dxContour = [0, 0, 1, 1, 0, 0, 0, 0, -1, 0, 1, 1, -1, 0, -1, 0];
|
|
284
|
-
var dyContour = [0, -1, 0, 0, 1, 1, 1, 1, 0, -1, 0, 0, 0, -1, 0, 0];
|
|
285
|
-
var dx, dy;
|
|
286
|
-
var startEdge = [
|
|
287
|
-
"none",
|
|
288
|
-
"left",
|
|
289
|
-
"bottom",
|
|
290
|
-
"left",
|
|
291
|
-
"right",
|
|
292
|
-
"none",
|
|
293
|
-
"bottom",
|
|
294
|
-
"left",
|
|
295
|
-
"top",
|
|
296
|
-
"top",
|
|
297
|
-
"none",
|
|
298
|
-
"top",
|
|
299
|
-
"right",
|
|
300
|
-
"right",
|
|
301
|
-
"bottom",
|
|
302
|
-
"none",
|
|
303
|
-
];
|
|
304
|
-
var nextEdge = [
|
|
305
|
-
"none",
|
|
306
|
-
"bottom",
|
|
307
|
-
"right",
|
|
308
|
-
"right",
|
|
309
|
-
"top",
|
|
310
|
-
"top",
|
|
311
|
-
"top",
|
|
312
|
-
"top",
|
|
313
|
-
"left",
|
|
314
|
-
"bottom",
|
|
315
|
-
"right",
|
|
316
|
-
"right",
|
|
317
|
-
"left",
|
|
318
|
-
"bottom",
|
|
319
|
-
"left",
|
|
320
|
-
"none",
|
|
321
|
-
];
|
|
322
|
-
var edge;
|
|
323
|
-
|
|
324
|
-
var currentCell = grid[j][i];
|
|
325
|
-
|
|
326
|
-
var cval = currentCell.cval;
|
|
327
|
-
var edge = startEdge[cval];
|
|
328
|
-
|
|
329
|
-
var pt = getXY(currentCell, edge);
|
|
330
|
-
|
|
331
|
-
/* push initial segment */
|
|
332
|
-
p.push([i + pt[0], j + pt[1]]);
|
|
333
|
-
edge = nextEdge[cval];
|
|
334
|
-
pt = getXY(currentCell, edge);
|
|
335
|
-
p.push([i + pt[0], j + pt[1]]);
|
|
336
|
-
clearCell(currentCell);
|
|
337
|
-
|
|
338
|
-
/* now walk arround the enclosed area in clockwise-direction */
|
|
339
|
-
var k = i + dxContour[cval];
|
|
340
|
-
var l = j + dyContour[cval];
|
|
341
|
-
var prev_cval = cval;
|
|
342
|
-
|
|
343
|
-
while (k >= 0 && l >= 0 && l < maxj && (k != i || l != j)) {
|
|
344
|
-
currentCell = grid[l][k];
|
|
345
|
-
if (typeof currentCell === "undefined") {
|
|
346
|
-
/* path ends here */
|
|
347
|
-
//console.log(k + " " + l + " is undefined, stopping path!");
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
cval = currentCell.cval;
|
|
351
|
-
if (cval === 0 || cval === 15) {
|
|
352
|
-
return { path: p, info: "mergeable" };
|
|
353
|
-
}
|
|
354
|
-
edge = nextEdge[cval];
|
|
355
|
-
dx = dxContour[cval];
|
|
356
|
-
dy = dyContour[cval];
|
|
357
|
-
if (cval === 5 || cval === 10) {
|
|
358
|
-
/* select upper or lower band, depending on previous cells cval */
|
|
359
|
-
if (cval === 5) {
|
|
360
|
-
if (currentCell.flipped) {
|
|
361
|
-
/* this is actually a flipped case 10 */
|
|
362
|
-
if (dyContour[prev_cval] === -1) {
|
|
363
|
-
edge = "left";
|
|
364
|
-
dx = -1;
|
|
365
|
-
dy = 0;
|
|
366
|
-
} else {
|
|
367
|
-
edge = "right";
|
|
368
|
-
dx = 1;
|
|
369
|
-
dy = 0;
|
|
370
|
-
}
|
|
371
|
-
} else {
|
|
372
|
-
/* real case 5 */
|
|
373
|
-
if (dxContour[prev_cval] === -1) {
|
|
374
|
-
edge = "bottom";
|
|
375
|
-
dx = 0;
|
|
376
|
-
dy = -1;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
} else if (cval === 10) {
|
|
380
|
-
if (currentCell.flipped) {
|
|
381
|
-
/* this is actually a flipped case 5 */
|
|
382
|
-
if (dxContour[prev_cval] === -1) {
|
|
383
|
-
edge = "top";
|
|
384
|
-
dx = 0;
|
|
385
|
-
dy = 1;
|
|
386
|
-
} else {
|
|
387
|
-
edge = "bottom";
|
|
388
|
-
dx = 0;
|
|
389
|
-
dy = -1;
|
|
390
|
-
}
|
|
391
|
-
} else {
|
|
392
|
-
/* real case 10 */
|
|
393
|
-
if (dyContour[prev_cval] === 1) {
|
|
394
|
-
edge = "left";
|
|
395
|
-
dx = -1;
|
|
396
|
-
dy = 0;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
pt = getXY(currentCell, edge);
|
|
402
|
-
p.push([k + pt[0], l + pt[1]]);
|
|
403
|
-
clearCell(currentCell);
|
|
404
|
-
k += dx;
|
|
405
|
-
l += dy;
|
|
406
|
-
prev_cval = cval;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return { path: p, info: "closed" };
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Takes a {@link Point} grid and returns a correspondent matrix {Array<Array<number>>}
|
|
414
|
-
* of the 'property' values
|
|
415
|
-
*
|
|
416
|
-
* @name gridToMatrix
|
|
417
|
-
* @param {FeatureCollection<Point>} grid of points
|
|
418
|
-
* @param {Object} [options={}] Optional parameters
|
|
419
|
-
* @param {string} [options.zProperty='elevation'] the property name in `points` from which z-values will be pulled
|
|
420
|
-
* @param {boolean} [options.flip=false] returns the matrix upside-down
|
|
421
|
-
* @param {boolean} [options.flags=false] flags, adding a `matrixPosition` array field ([row, column]) to its properties,
|
|
422
|
-
* the grid points with coordinates on the matrix
|
|
423
|
-
* @returns {Array<Array<number>>} matrix of property values
|
|
424
|
-
* @example
|
|
425
|
-
* var extent = [-70.823364, -33.553984, -70.473175, -33.302986];
|
|
426
|
-
* var cellSize = 3;
|
|
427
|
-
* var grid = turf.pointGrid(extent, cellSize);
|
|
428
|
-
* // add a random property to each point between 0 and 60
|
|
429
|
-
* for (var i = 0; i < grid.features.length; i++) {
|
|
430
|
-
* grid.features[i].properties.elevation = (Math.random() * 60);
|
|
431
|
-
* }
|
|
432
|
-
* gridToMatrix(grid);
|
|
433
|
-
* //= [
|
|
434
|
-
* [ 1, 13, 10, 9, 10, 13, 18],
|
|
435
|
-
* [34, 8, 5, 4, 5, 8, 13],
|
|
436
|
-
* [10, 5, 2, 1, 2, 5, 4],
|
|
437
|
-
* [ 0, 4, 56, 19, 1, 4, 9],
|
|
438
|
-
* [10, 5, 2, 1, 2, 5, 10],
|
|
439
|
-
* [57, 8, 5, 4, 5, 0, 57],
|
|
440
|
-
* [ 3, 13, 10, 9, 5, 13, 18],
|
|
441
|
-
* [18, 13, 10, 9, 78, 13, 18]
|
|
442
|
-
* ]
|
|
443
|
-
*/
|
|
444
|
-
function gridToMatrix(grid, options) {
|
|
445
|
-
// Optional parameters
|
|
446
|
-
options = options || {};
|
|
447
|
-
if (!isObject(options)) throw new Error("options is invalid");
|
|
448
|
-
var zProperty = options.zProperty || "elevation";
|
|
449
|
-
var flip = options.flip;
|
|
450
|
-
var flags = options.flags;
|
|
451
|
-
|
|
452
|
-
// validation
|
|
453
|
-
collectionOf(grid, "Point", "input must contain Points");
|
|
454
|
-
|
|
455
|
-
var pointsMatrix = sortPointsByLatLng(grid, flip);
|
|
456
|
-
|
|
457
|
-
var matrix = [];
|
|
458
|
-
// create property matrix from sorted points
|
|
459
|
-
// looping order matters here
|
|
460
|
-
for (var r = 0; r < pointsMatrix.length; r++) {
|
|
461
|
-
var pointRow = pointsMatrix[r];
|
|
462
|
-
var row = [];
|
|
463
|
-
for (var c = 0; c < pointRow.length; c++) {
|
|
464
|
-
var point = pointRow[c];
|
|
465
|
-
// Check if zProperty exist
|
|
466
|
-
if (point.properties[zProperty]) row.push(point.properties[zProperty]);
|
|
467
|
-
else row.push(0);
|
|
468
|
-
// add flags
|
|
469
|
-
if (flags === true) point.properties.matrixPosition = [r, c];
|
|
470
|
-
}
|
|
471
|
-
matrix.push(row);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return matrix;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Sorts points by latitude and longitude, creating a 2-dimensional array of points
|
|
479
|
-
*
|
|
480
|
-
* @private
|
|
481
|
-
* @param {FeatureCollection<Point>} points GeoJSON Point features
|
|
482
|
-
* @param {boolean} [flip=false] returns the matrix upside-down
|
|
483
|
-
* @returns {Array<Array<Point>>} points ordered by latitude and longitude
|
|
484
|
-
*/
|
|
485
|
-
function sortPointsByLatLng(points, flip) {
|
|
486
|
-
var pointsByLatitude = {};
|
|
487
|
-
|
|
488
|
-
// divide points by rows with the same latitude
|
|
489
|
-
featureEach(points, function (point) {
|
|
490
|
-
var lat = getCoords(point)[1];
|
|
491
|
-
if (!pointsByLatitude[lat]) pointsByLatitude[lat] = [];
|
|
492
|
-
pointsByLatitude[lat].push(point);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// sort points (with the same latitude) by longitude
|
|
496
|
-
var orderedRowsByLatitude = Object.keys(pointsByLatitude).map(function (lat) {
|
|
497
|
-
var row = pointsByLatitude[lat];
|
|
498
|
-
var rowOrderedByLongitude = row.sort(function (a, b) {
|
|
499
|
-
return getCoords(a)[0] - getCoords(b)[0];
|
|
500
|
-
});
|
|
501
|
-
return rowOrderedByLongitude;
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// sort rows (of points with the same latitude) by latitude
|
|
505
|
-
var pointMatrix = orderedRowsByLatitude.sort(function (a, b) {
|
|
506
|
-
if (flip) return getCoords(a[0])[1] - getCoords(b[0])[1];
|
|
507
|
-
else return getCoords(b[0])[1] - getCoords(a[0])[1];
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
return pointMatrix;
|
|
511
|
-
}
|
|
512
|
-
|
|
1
|
+
import bbox from "@turf/bbox";
|
|
2
|
+
import { coordEach } from "@turf/meta";
|
|
3
|
+
import { collectionOf } from "@turf/invariant";
|
|
4
|
+
import { multiLineString, featureCollection, isObject } from "@turf/helpers";
|
|
5
|
+
import isoContours from "./lib/marchingsquares-isocontours.js";
|
|
6
|
+
import gridToMatrix from "./lib/grid-to-matrix.js";
|
|
513
7
|
/**
|
|
514
8
|
* Takes a grid {@link FeatureCollection} of {@link Point} features with z-values and an array of
|
|
515
9
|
* value breaks and generates [isolines](https://en.wikipedia.org/wiki/Contour_line).
|
|
@@ -540,36 +34,29 @@ function sortPointsByLatLng(points, flip) {
|
|
|
540
34
|
* var addToMap = [lines];
|
|
541
35
|
*/
|
|
542
36
|
function isolines(pointGrid, breaks, options) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
matrix,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
commonProperties,
|
|
566
|
-
breaksProperties
|
|
567
|
-
);
|
|
568
|
-
var scaledIsolines = rescaleIsolines(createdIsoLines, matrix, pointGrid);
|
|
569
|
-
|
|
570
|
-
return featureCollection(scaledIsolines);
|
|
37
|
+
// Optional parameters
|
|
38
|
+
options = options || {};
|
|
39
|
+
if (!isObject(options))
|
|
40
|
+
throw new Error("options is invalid");
|
|
41
|
+
const zProperty = options.zProperty || "elevation";
|
|
42
|
+
const commonProperties = options.commonProperties || {};
|
|
43
|
+
const breaksProperties = options.breaksProperties || [];
|
|
44
|
+
// Input validation
|
|
45
|
+
collectionOf(pointGrid, "Point", "Input must contain Points");
|
|
46
|
+
if (!breaks)
|
|
47
|
+
throw new Error("breaks is required");
|
|
48
|
+
if (!Array.isArray(breaks))
|
|
49
|
+
throw new Error("breaks must be an Array");
|
|
50
|
+
if (!isObject(commonProperties))
|
|
51
|
+
throw new Error("commonProperties must be an Object");
|
|
52
|
+
if (!Array.isArray(breaksProperties))
|
|
53
|
+
throw new Error("breaksProperties must be an Array");
|
|
54
|
+
// Isoline methods
|
|
55
|
+
const matrix = gridToMatrix(pointGrid, { zProperty: zProperty, flip: true });
|
|
56
|
+
const createdIsoLines = createIsoLines(matrix, breaks, zProperty, commonProperties, breaksProperties);
|
|
57
|
+
const scaledIsolines = rescaleIsolines(createdIsoLines, matrix, pointGrid);
|
|
58
|
+
return featureCollection(scaledIsolines);
|
|
571
59
|
}
|
|
572
|
-
|
|
573
60
|
/**
|
|
574
61
|
* Creates the isolines lines (featuresCollection of MultiLineString features) from the 2D data grid
|
|
575
62
|
*
|
|
@@ -579,32 +66,23 @@ function isolines(pointGrid, breaks, options) {
|
|
|
579
66
|
*
|
|
580
67
|
* @private
|
|
581
68
|
* @param {Array<Array<number>>} matrix Grid Data
|
|
582
|
-
* @param {Array<number>} breaks
|
|
69
|
+
* @param {Array<number>} breaks BreakProps
|
|
583
70
|
* @param {string} zProperty name of the z-values property
|
|
584
71
|
* @param {Object} [commonProperties={}] GeoJSON properties passed to ALL isolines
|
|
585
72
|
* @param {Object} [breaksProperties=[]] GeoJSON properties passed to the correspondent isoline
|
|
586
73
|
* @returns {Array<MultiLineString>} isolines
|
|
587
74
|
*/
|
|
588
|
-
function createIsoLines(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
)
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
var properties = objectAssign({}, commonProperties, breaksProperties[i]);
|
|
600
|
-
properties[zProperty] = threshold;
|
|
601
|
-
var isoline = multiLineString(isoContours(matrix, threshold), properties);
|
|
602
|
-
|
|
603
|
-
results.push(isoline);
|
|
604
|
-
}
|
|
605
|
-
return results;
|
|
75
|
+
function createIsoLines(matrix, breaks, zProperty, commonProperties, breaksProperties) {
|
|
76
|
+
const results = [];
|
|
77
|
+
for (let i = 1; i < breaks.length; i++) {
|
|
78
|
+
const threshold = +breaks[i]; // make sure it's a number
|
|
79
|
+
const properties = Object.assign(Object.assign({}, commonProperties), breaksProperties[i]);
|
|
80
|
+
properties[zProperty] = threshold;
|
|
81
|
+
const isoline = multiLineString(isoContours(matrix, threshold), properties);
|
|
82
|
+
results.push(isoline);
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
606
85
|
}
|
|
607
|
-
|
|
608
86
|
/**
|
|
609
87
|
* Translates and scales isolines
|
|
610
88
|
*
|
|
@@ -615,33 +93,27 @@ function createIsoLines(
|
|
|
615
93
|
* @returns {Array<MultiLineString>} isolines
|
|
616
94
|
*/
|
|
617
95
|
function rescaleIsolines(createdIsoLines, matrix, points) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
// resize and shift each point/line of the createdIsoLines
|
|
641
|
-
createdIsoLines.forEach(function (isoline) {
|
|
642
|
-
coordEach(isoline, resize);
|
|
643
|
-
});
|
|
644
|
-
return createdIsoLines;
|
|
96
|
+
// get dimensions (on the map) of the original grid
|
|
97
|
+
const gridBbox = bbox(points); // [ minX, minY, maxX, maxY ]
|
|
98
|
+
const originalWidth = gridBbox[2] - gridBbox[0];
|
|
99
|
+
const originalHeigth = gridBbox[3] - gridBbox[1];
|
|
100
|
+
// get origin, which is the first point of the last row on the rectangular data on the map
|
|
101
|
+
const x0 = gridBbox[0];
|
|
102
|
+
const y0 = gridBbox[1];
|
|
103
|
+
// get number of cells per side
|
|
104
|
+
const matrixWidth = matrix[0].length - 1;
|
|
105
|
+
const matrixHeight = matrix.length - 1;
|
|
106
|
+
// calculate the scaling factor between matrix and rectangular grid on the map
|
|
107
|
+
const scaleX = originalWidth / matrixWidth;
|
|
108
|
+
const scaleY = originalHeigth / matrixHeight;
|
|
109
|
+
const resize = (point) => {
|
|
110
|
+
point[0] = point[0] * scaleX + x0;
|
|
111
|
+
point[1] = point[1] * scaleY + y0;
|
|
112
|
+
};
|
|
113
|
+
// resize and shift each point/line of the createdIsoLines
|
|
114
|
+
createdIsoLines.forEach((isoline) => {
|
|
115
|
+
coordEach(isoline, resize);
|
|
116
|
+
});
|
|
117
|
+
return createdIsoLines;
|
|
645
118
|
}
|
|
646
|
-
|
|
647
119
|
export default isolines;
|