gridviz 3.0.41 → 3.1.0

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.
@@ -1,258 +1,259 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- import { Style } from '../core/Style.js'
5
- import { cellsToMatrix } from '../utils/utils.js'
6
-
7
- /**
8
- * @module style
9
- * @author Julien Gaffuri
10
- */
11
- export class Interpolator extends Style {
12
- /** @param {object} opts */
13
- constructor(opts) {
14
- super(opts)
15
- opts = opts || {}
16
-
17
- /** The cell value to interpolate
18
- * @type { function } } */
19
- this.value = opts.value //(c) => elevation
20
-
21
- /** The target resolution. As a function (resolution, z) => targetResolution
22
- * NB: Try to make sure that resolution/targetResolution is an integer.
23
- * @type { function } } */
24
- this.targetResolution = opts.targetResolution || ((r, z) => r / 5)
25
-
26
- /** the property name to store the interpolated value in the cell
27
- * @type { string } } */
28
- this.interpolatedProperty = opts.interpolatedProperty || 'value'
29
-
30
- // the interpolation method: currently only 'bilinear' is supported
31
- //this.method = opts.method || 'bilinear' // 'nearest', 'bilinear'
32
-
33
- /** when a neighbor cell has no value, take zero value or average of surrounding cells
34
- * @type { string } } */
35
- this.nodataHandling = opts.nodataHandling || "zero" // 'zero', 'average'
36
-
37
- /** The styles to represent the interpolated grid.
38
- * @type {Array.<Style>} */
39
- this.styles = opts.styles || []
40
- }
41
-
42
-
43
- /**
44
- *
45
- * @param {Array.<import("../core/Dataset.js").Cell>} cells
46
- * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
47
- * @param {number} resolution
48
- */
49
- draw(cells, geoCanvas, resolution) {
50
-
51
- //filter cells
52
- if (this.filter) cells = cells.filter(this.filter)
53
- if (cells.length == 0) return;
54
-
55
- //
56
- const z = geoCanvas.view.z
57
-
58
- //get view scale
59
- //const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
60
-
61
- //index cells by y and x
62
- let m = cellsToMatrix(cells, resolution, c => this.value(c))
63
- const x0 = m.x0, y0 = m.y0
64
-
65
- //get target resolution and scale factor
66
- let targetResolution = this.targetResolution(resolution, z)
67
- const scaleFactor = Math.round(resolution / targetResolution)
68
- targetResolution = resolution / scaleFactor
69
-
70
- // compute ray casting shadow
71
- m = bilinearInterpolator(m, scaleFactor, this.nodataHandling);
72
-
73
- // make cells
74
- cells = []
75
- const ra = (resolution - targetResolution) / 2
76
- for (let i = 0; i < m.length; i++) {
77
- const row = m[i]
78
- const y = y0 - i * targetResolution + ra
79
- for (let j = 0; j < row.length; j++) {
80
- const v = row[j]
81
- if (!v) continue
82
- const c = { x: x0 + j * targetResolution + ra, y: y }
83
- c[this.interpolatedProperty] = v
84
- cells.push(c)
85
- }
86
- }
87
-
88
- //draw smoothed cells from styles
89
- const ctx = geoCanvas.offscreenCtx
90
- for (let s of this.styles) {
91
-
92
- //check if style is visible
93
- if (s.visible && !s.visible(z)) continue
94
-
95
- //set style alpha and blend mode
96
- //TODO: multiply by layer alpha ?
97
- if (s.alpha || s.blendOperation) {
98
- ctx.save()
99
- if (s.alpha) ctx.globalAlpha = s.alpha(z)
100
- if (s.blendOperation) ctx.globalCompositeOperation = s.blendOperation(z)
101
- }
102
-
103
- //set affin transform to draw with geographical coordinates
104
- geoCanvas.setCanvasTransform()
105
-
106
- //draw with style
107
- s.draw(cells, geoCanvas, targetResolution)
108
-
109
- //draw style filter
110
- if (s.filterColor) s.drawFilter(geoCanvas)
111
-
112
- //restore ctx
113
- if (s.alpha || s.blendOperation) ctx.restore()
114
- }
115
-
116
- }
117
- }
118
-
119
-
120
- const interp = (topLeft, topRight, bottomLeft, bottomRight, xRatio, yRatio) => {
121
- const top = topLeft + (topRight - topLeft) * xRatio;
122
- const bottom = bottomLeft + (bottomRight - bottomLeft) * xRatio;
123
- return top + (bottom - top) * yRatio;
124
- }
125
-
126
-
127
- function bilinearInterpolator(grid, scaleFactor = 5, nodataHandling = "zero") {
128
-
129
- if (scaleFactor === 1) return grid;
130
-
131
- // input grid dimensions
132
- const rows = grid.length;
133
- if (rows === 0) return [];
134
- const cols = grid[0].length;
135
-
136
- // output grid dimensions
137
- const fineRows = rows * scaleFactor;
138
- const fineCols = cols * scaleFactor;
139
- const fineGrid = Array(fineRows).fill().map(() => Array(fineCols).fill(undefined));
140
-
141
- // check if scale factor is even
142
- const sfIsEven = scaleFactor % 2 === 0
143
-
144
- //compute output grid values
145
- for (let i = 0; i < fineRows; i++) {
146
-
147
- const coarseI = Math.min(Math.floor(i / scaleFactor), rows - 2);
148
- const yRatio = (i % scaleFactor) / scaleFactor;
149
- const i_ = coarseI * scaleFactor + (scaleFactor / 2)
150
-
151
- for (let j = 0; j < fineCols; j++) {
152
- const coarseJ = Math.min(Math.floor(j / scaleFactor), cols - 2);
153
- const xRatio = (j % scaleFactor) / scaleFactor;
154
- const j_ = coarseJ * scaleFactor + (scaleFactor / 2)
155
-
156
- // get four corner values
157
- const topLeft = grid[coarseI][coarseJ];
158
- const topRight = grid[coarseI][coarseJ + 1];
159
- const bottomLeft = grid[coarseI + 1][coarseJ];
160
- const bottomRight = grid[coarseI + 1][coarseJ + 1];
161
-
162
-
163
- if (sfIsEven) {
164
- if (topLeft === undefined && i <= i_ && j <= j_) continue
165
- if (topRight === undefined && i <= i_ && j >= j_) continue
166
- if (bottomLeft === undefined && i >= i_ && j <= j_) continue
167
- if (bottomRight === undefined && i >= i_ && j >= j_) continue
168
- } else {
169
- if (topLeft === undefined && i < i_ && j < j_) continue
170
- if (topRight === undefined && i < i_ && j > j_) continue
171
- if (bottomLeft === undefined && i > i_ && j < j_) continue
172
- if (bottomRight === undefined && i > i_ && j > j_) continue
173
- }
174
-
175
- if(nodataHandling === "zero") {
176
- // replace undefined values by zero
177
- fineGrid[i][j] = interp(topLeft || 0, topRight || 0, bottomLeft || 0, bottomRight || 0, xRatio, yRatio)
178
- continue
179
- }
180
-
181
- // general case: bilinear interpolation
182
- // If all four are defined, interpolate normally
183
- if (topLeft !== undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight !== undefined) {
184
- fineGrid[i][j] = interp(topLeft, topRight, bottomLeft, bottomRight, xRatio, yRatio)
185
- continue
186
- }
187
-
188
- // If only one is not defined, use average value of 2 adjacents and interpolate
189
- if (topLeft === undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight !== undefined) {
190
- const v = topRight + bottomLeft
191
- fineGrid[i][j] = interp(v / 2, topRight, bottomLeft, bottomRight, xRatio, yRatio)
192
- continue
193
- }
194
- if (topLeft !== undefined && topRight === undefined && bottomLeft !== undefined && bottomRight !== undefined) {
195
- const v = topLeft + bottomRight
196
- fineGrid[i][j] = interp(topLeft, v / 2, bottomLeft, bottomRight, xRatio, yRatio)
197
- continue
198
- }
199
- if (topLeft !== undefined && topRight !== undefined && bottomLeft === undefined && bottomRight !== undefined) {
200
- const v = topLeft + bottomRight
201
- fineGrid[i][j] = interp(topLeft, topRight, v / 2, bottomRight, xRatio, yRatio)
202
- continue
203
- }
204
- if (topLeft !== undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight === undefined) {
205
- const v = topRight + bottomLeft
206
- fineGrid[i][j] = interp(topLeft, topRight, bottomLeft, v / 2, xRatio, yRatio)
207
- continue
208
- }
209
-
210
- // If only two diagonally opposite points are defined, interpolate along diagonal
211
- if (topLeft !== undefined && bottomRight !== undefined) {
212
- const t = (xRatio + yRatio) * Math.SQRT1_2
213
- fineGrid[i][j] = topLeft * (1 - t) + bottomRight * t;
214
- continue
215
- }
216
- if (topRight !== undefined && bottomLeft !== undefined) {
217
- const t = (1 - xRatio + yRatio) * Math.SQRT1_2
218
- fineGrid[i][j] = topRight * (1 - t) + bottomLeft * t;
219
- continue
220
- }
221
-
222
- // If only two adjacent points are defined, interpolate between them
223
- if (topLeft !== undefined && bottomLeft !== undefined) {
224
- fineGrid[i][j] = bottomLeft * yRatio + topLeft * (1 - yRatio);
225
- continue
226
- }
227
- if (topRight !== undefined && bottomRight !== undefined) {
228
- fineGrid[i][j] = bottomRight * yRatio + topRight * (1 - yRatio);
229
- continue
230
- }
231
- if (topLeft !== undefined && topRight !== undefined) {
232
- fineGrid[i][j] = topRight * xRatio + topLeft * (1 - xRatio);
233
- }
234
- if (bottomLeft !== undefined && bottomRight !== undefined) {
235
- fineGrid[i][j] = bottomRight * xRatio + bottomLeft * (1 - xRatio);
236
- continue
237
- }
238
-
239
- // If only one point is defined, use that value
240
- if (topLeft !== undefined) {
241
- fineGrid[i][j] = topLeft;
242
- continue
243
- }
244
- if (topRight !== undefined) {
245
- fineGrid[i][j] = topRight;
246
- continue
247
- }
248
- if (bottomLeft !== undefined) {
249
- fineGrid[i][j] = bottomLeft;
250
- continue
251
- }
252
- if (bottomRight !== undefined) {
253
- fineGrid[i][j] = bottomRight;
254
- }
255
- }
256
- }
257
- return fineGrid;
258
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../core/Style.js'
5
+ import { isVisibleAt } from '../core/Drawable.js'
6
+ import { cellsToMatrix } from '../utils/utils.js'
7
+
8
+ /**
9
+ * @module style
10
+ * @author Julien Gaffuri
11
+ */
12
+ export class Interpolator extends Style {
13
+ /** @param {object} opts */
14
+ constructor(opts) {
15
+ super(opts)
16
+ opts = opts || {}
17
+
18
+ /** The cell value to interpolate
19
+ * @type { function } } */
20
+ this.value = opts.value //(c) => elevation
21
+
22
+ /** The target resolution. As a function (resolution, z) => targetResolution
23
+ * NB: Try to make sure that resolution/targetResolution is an integer.
24
+ * @type { function } } */
25
+ this.targetResolution = opts.targetResolution || ((r, z) => r / 5)
26
+
27
+ /** the property name to store the interpolated value in the cell
28
+ * @type { string } } */
29
+ this.interpolatedProperty = opts.interpolatedProperty || 'value'
30
+
31
+ // the interpolation method: currently only 'bilinear' is supported
32
+ //this.method = opts.method || 'bilinear' // 'nearest', 'bilinear'
33
+
34
+ /** when a neighbor cell has no value, take zero value or average of surrounding cells
35
+ * @type { string } } */
36
+ this.nodataHandling = opts.nodataHandling || "zero" // 'zero', 'average'
37
+
38
+ /** The styles to represent the interpolated grid.
39
+ * @type {Array.<Style>} */
40
+ this.styles = opts.styles || []
41
+ }
42
+
43
+
44
+ /**
45
+ *
46
+ * @param {Array.<import("../core/Dataset.js").Cell>} cells
47
+ * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
48
+ * @param {number} resolution
49
+ */
50
+ draw(cells, geoCanvas, resolution) {
51
+
52
+ //filter cells
53
+ if (this.filter) cells = cells.filter(this.filter)
54
+ if (cells.length == 0) return;
55
+
56
+ //
57
+ const z = geoCanvas.view.z
58
+
59
+ //get view scale
60
+ //const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
61
+
62
+ //index cells by y and x
63
+ let m = cellsToMatrix(cells, resolution, c => this.value(c))
64
+ const x0 = m.x0, y0 = m.y0
65
+
66
+ //get target resolution and scale factor
67
+ let targetResolution = this.targetResolution(resolution, z)
68
+ const scaleFactor = Math.round(resolution / targetResolution)
69
+ targetResolution = resolution / scaleFactor
70
+
71
+ // compute ray casting shadow
72
+ m = bilinearInterpolator(m, scaleFactor, this.nodataHandling);
73
+
74
+ // make cells
75
+ cells = []
76
+ const ra = (resolution - targetResolution) / 2
77
+ for (let i = 0; i < m.length; i++) {
78
+ const row = m[i]
79
+ const y = y0 - i * targetResolution + ra
80
+ for (let j = 0; j < row.length; j++) {
81
+ const v = row[j]
82
+ if (!v) continue
83
+ const c = { x: x0 + j * targetResolution + ra, y: y }
84
+ c[this.interpolatedProperty] = v
85
+ cells.push(c)
86
+ }
87
+ }
88
+
89
+ //draw smoothed cells from styles
90
+ const ctx = geoCanvas.offscreenCtx
91
+ for (let s of this.styles) {
92
+
93
+ //check if style is visible
94
+ if (!isVisibleAt(s, z)) continue
95
+
96
+ //set style alpha and blend mode
97
+ //TODO: multiply by layer alpha ?
98
+ if (s.alpha || s.blendOperation) {
99
+ ctx.save()
100
+ if (s.alpha) ctx.globalAlpha = s.alpha(z)
101
+ if (s.blendOperation) ctx.globalCompositeOperation = s.blendOperation(z)
102
+ }
103
+
104
+ //set affin transform to draw with geographical coordinates
105
+ geoCanvas.setCanvasTransform()
106
+
107
+ //draw with style
108
+ s.draw(cells, geoCanvas, targetResolution)
109
+
110
+ //draw style filter
111
+ if (s.filterColor) s.drawFilter(geoCanvas)
112
+
113
+ //restore ctx
114
+ if (s.alpha || s.blendOperation) ctx.restore()
115
+ }
116
+
117
+ }
118
+ }
119
+
120
+
121
+ const interp = (topLeft, topRight, bottomLeft, bottomRight, xRatio, yRatio) => {
122
+ const top = topLeft + (topRight - topLeft) * xRatio;
123
+ const bottom = bottomLeft + (bottomRight - bottomLeft) * xRatio;
124
+ return top + (bottom - top) * yRatio;
125
+ }
126
+
127
+
128
+ function bilinearInterpolator(grid, scaleFactor = 5, nodataHandling = "zero") {
129
+
130
+ if (scaleFactor === 1) return grid;
131
+
132
+ // input grid dimensions
133
+ const rows = grid.length;
134
+ if (rows === 0) return [];
135
+ const cols = grid[0].length;
136
+
137
+ // output grid dimensions
138
+ const fineRows = rows * scaleFactor;
139
+ const fineCols = cols * scaleFactor;
140
+ const fineGrid = Array(fineRows).fill().map(() => Array(fineCols).fill(undefined));
141
+
142
+ // check if scale factor is even
143
+ const sfIsEven = scaleFactor % 2 === 0
144
+
145
+ //compute output grid values
146
+ for (let i = 0; i < fineRows; i++) {
147
+
148
+ const coarseI = Math.min(Math.floor(i / scaleFactor), rows - 2);
149
+ const yRatio = (i % scaleFactor) / scaleFactor;
150
+ const i_ = coarseI * scaleFactor + (scaleFactor / 2)
151
+
152
+ for (let j = 0; j < fineCols; j++) {
153
+ const coarseJ = Math.min(Math.floor(j / scaleFactor), cols - 2);
154
+ const xRatio = (j % scaleFactor) / scaleFactor;
155
+ const j_ = coarseJ * scaleFactor + (scaleFactor / 2)
156
+
157
+ // get four corner values
158
+ const topLeft = grid[coarseI][coarseJ];
159
+ const topRight = grid[coarseI][coarseJ + 1];
160
+ const bottomLeft = grid[coarseI + 1][coarseJ];
161
+ const bottomRight = grid[coarseI + 1][coarseJ + 1];
162
+
163
+
164
+ if (sfIsEven) {
165
+ if (topLeft === undefined && i <= i_ && j <= j_) continue
166
+ if (topRight === undefined && i <= i_ && j >= j_) continue
167
+ if (bottomLeft === undefined && i >= i_ && j <= j_) continue
168
+ if (bottomRight === undefined && i >= i_ && j >= j_) continue
169
+ } else {
170
+ if (topLeft === undefined && i < i_ && j < j_) continue
171
+ if (topRight === undefined && i < i_ && j > j_) continue
172
+ if (bottomLeft === undefined && i > i_ && j < j_) continue
173
+ if (bottomRight === undefined && i > i_ && j > j_) continue
174
+ }
175
+
176
+ if(nodataHandling === "zero") {
177
+ // replace undefined values by zero
178
+ fineGrid[i][j] = interp(topLeft || 0, topRight || 0, bottomLeft || 0, bottomRight || 0, xRatio, yRatio)
179
+ continue
180
+ }
181
+
182
+ // general case: bilinear interpolation
183
+ // If all four are defined, interpolate normally
184
+ if (topLeft !== undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight !== undefined) {
185
+ fineGrid[i][j] = interp(topLeft, topRight, bottomLeft, bottomRight, xRatio, yRatio)
186
+ continue
187
+ }
188
+
189
+ // If only one is not defined, use average value of 2 adjacents and interpolate
190
+ if (topLeft === undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight !== undefined) {
191
+ const v = topRight + bottomLeft
192
+ fineGrid[i][j] = interp(v / 2, topRight, bottomLeft, bottomRight, xRatio, yRatio)
193
+ continue
194
+ }
195
+ if (topLeft !== undefined && topRight === undefined && bottomLeft !== undefined && bottomRight !== undefined) {
196
+ const v = topLeft + bottomRight
197
+ fineGrid[i][j] = interp(topLeft, v / 2, bottomLeft, bottomRight, xRatio, yRatio)
198
+ continue
199
+ }
200
+ if (topLeft !== undefined && topRight !== undefined && bottomLeft === undefined && bottomRight !== undefined) {
201
+ const v = topLeft + bottomRight
202
+ fineGrid[i][j] = interp(topLeft, topRight, v / 2, bottomRight, xRatio, yRatio)
203
+ continue
204
+ }
205
+ if (topLeft !== undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight === undefined) {
206
+ const v = topRight + bottomLeft
207
+ fineGrid[i][j] = interp(topLeft, topRight, bottomLeft, v / 2, xRatio, yRatio)
208
+ continue
209
+ }
210
+
211
+ // If only two diagonally opposite points are defined, interpolate along diagonal
212
+ if (topLeft !== undefined && bottomRight !== undefined) {
213
+ const t = (xRatio + yRatio) * Math.SQRT1_2
214
+ fineGrid[i][j] = topLeft * (1 - t) + bottomRight * t;
215
+ continue
216
+ }
217
+ if (topRight !== undefined && bottomLeft !== undefined) {
218
+ const t = (1 - xRatio + yRatio) * Math.SQRT1_2
219
+ fineGrid[i][j] = topRight * (1 - t) + bottomLeft * t;
220
+ continue
221
+ }
222
+
223
+ // If only two adjacent points are defined, interpolate between them
224
+ if (topLeft !== undefined && bottomLeft !== undefined) {
225
+ fineGrid[i][j] = bottomLeft * yRatio + topLeft * (1 - yRatio);
226
+ continue
227
+ }
228
+ if (topRight !== undefined && bottomRight !== undefined) {
229
+ fineGrid[i][j] = bottomRight * yRatio + topRight * (1 - yRatio);
230
+ continue
231
+ }
232
+ if (topLeft !== undefined && topRight !== undefined) {
233
+ fineGrid[i][j] = topRight * xRatio + topLeft * (1 - xRatio);
234
+ }
235
+ if (bottomLeft !== undefined && bottomRight !== undefined) {
236
+ fineGrid[i][j] = bottomRight * xRatio + bottomLeft * (1 - xRatio);
237
+ continue
238
+ }
239
+
240
+ // If only one point is defined, use that value
241
+ if (topLeft !== undefined) {
242
+ fineGrid[i][j] = topLeft;
243
+ continue
244
+ }
245
+ if (topRight !== undefined) {
246
+ fineGrid[i][j] = topRight;
247
+ continue
248
+ }
249
+ if (bottomLeft !== undefined) {
250
+ fineGrid[i][j] = bottomLeft;
251
+ continue
252
+ }
253
+ if (bottomRight !== undefined) {
254
+ fineGrid[i][j] = bottomRight;
255
+ }
256
+ }
257
+ }
258
+ return fineGrid;
259
+ }