gridviz 3.0.32 → 3.0.34

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.
@@ -2,6 +2,7 @@
2
2
  'use strict'
3
3
 
4
4
  import { Style } from '../core/Style.js'
5
+ import { cellsToGrid } from '../utils/utils.js'
5
6
 
6
7
  /**
7
8
  * @module style
@@ -47,15 +48,70 @@ export class JoyPlotStyle extends Style {
47
48
 
48
49
  //index cells by y and x
49
50
  /** @type {object} */
50
- const ind = {}
51
- for (const cell of cells) {
52
- let row = ind[cell.y]
53
- if (!row) {
54
- row = {}
55
- ind[cell.y] = row
51
+ const ind = cellsToGrid(cells, cell => this.height(cell, resolution, z, viewScale))
52
+
53
+
54
+ //make grid
55
+ /** @type {object} */
56
+ /*const grid = cellsToGrid2(cells, resolution, cell => this.height(cell, resolution, z, viewScale))
57
+ if (!grid.maxI || !grid.maxJ) return
58
+
59
+ //TODO
60
+ const ys = { min: grid.y0 + resolution * grid.minI, max: grid.y0 + resolution * grid.maxI }
61
+
62
+ //draw lines, row by row, stating from the top
63
+ ctx.lineJoin = 'round'
64
+ for (let i = grid.minI; i <= grid.maxI; i++) {
65
+
66
+ //Get row data
67
+ const row = grid[i]
68
+ if (!row) continue
69
+
70
+ // y of the row
71
+ const y = grid.y0 - i * resolution
72
+
73
+ //store the previous height
74
+ let hG_
75
+
76
+ for (let j = grid.minJ; j <= grid.maxJ; j++) {
77
+ //get column value
78
+ let hG = row[j]
79
+ if (!hG) hG = 0
80
+
81
+ // x of the cell
82
+ const x = grid.x0 + j * resolution
83
+
84
+ if (hG || hG_) {
85
+ //draw line only when at least one of both values is non-null
86
+ //TODO test bezierCurveTo
87
+ ctx.lineTo(x + resolution / 2, y + hG)
88
+ } else {
89
+ //else move the point
90
+ ctx.moveTo(x + resolution / 2, y)
91
+ }
92
+ //store the previous value
93
+ hG_ = hG
94
+ }
95
+ //last point
96
+ if (hG_) ctx.lineTo(grid.x0 + resolution * grid.maxJ + resolution / 2, y)
97
+
98
+ //draw fill
99
+ const fc = this.fillColor(y, ys, resolution, z)
100
+ if (fc && fc != 'none') {
101
+ ctx.fillStyle = fc
102
+ ctx.fill()
103
+ }
104
+
105
+ //draw line
106
+ const lc = this.lineColor(y, ys, resolution, z)
107
+ const lw = this.lineWidth(y, ys, resolution, z)
108
+ if (lc && lc != 'none' && lw > 0) {
109
+ ctx.strokeStyle = lc
110
+ ctx.lineWidth = lw
111
+ ctx.stroke()
56
112
  }
57
- row[cell.x] = this.height(cell, resolution, z, viewScale)
58
113
  }
114
+ */
59
115
 
60
116
  //compute extent
61
117
  const e = geoCanvas.extGeo
@@ -65,7 +121,6 @@ export class JoyPlotStyle extends Style {
65
121
  const yMin = Math.floor(e.yMin / resolution) * resolution
66
122
  const yMax = Math.floor(e.yMax / resolution) * resolution
67
123
 
68
- /** @type {{min:number, max:number}} */
69
124
  const ys = { min: yMin, max: yMax }
70
125
 
71
126
  //draw lines, row by row, stating from the top
@@ -82,13 +137,11 @@ export class JoyPlotStyle extends Style {
82
137
  ctx.moveTo(xMin - resolution / 2, y)
83
138
 
84
139
  //store the previous height
85
- /** @type {number|undefined} */
86
140
  let hG_
87
141
 
88
142
  //go through the line cells
89
143
  for (let x = xMin; x <= xMax; x += resolution) {
90
144
  //get column value
91
- /** @type {number} */
92
145
  let hG = row[x]
93
146
  if (!hG) hG = 0
94
147
 
@@ -0,0 +1,280 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../core/Style.js'
5
+ import { cellsToGrid, cellsToMatrix } from '../utils/utils.js'
6
+ import { SquareColorCategoryWebGLStyle } from './SquareColorCategoryWebGLStyle.js'
7
+ //import { SquareColorWebGLStyle } from './SquareColorWebGLStyle.js'
8
+ import { extent } from 'd3-array'
9
+
10
+
11
+ //TODO:
12
+ // make faster algo by setting shades by sun ray
13
+ // diffusion effect ? based on distance to light (altitude - sun ray height)
14
+ // style with SquareColorWebGLStyle, improved version !
15
+
16
+ /**
17
+ * @module style
18
+ * @author Julien Gaffuri
19
+ */
20
+ export class ShadingRayStyle extends Style {
21
+ /** @param {object} opts */
22
+ constructor(opts) {
23
+ super(opts)
24
+ opts = opts || {}
25
+
26
+ this.elevation = opts.elevation //(c) => elevation
27
+
28
+ this.sunAzimuth = opts.sunAzimuth || (() => 2.356) //(r,z,vs)=>
29
+ this.sunAltitude = opts.sunAltitude || (() => 0.2) //(r,z,vs)=>
30
+
31
+ //this.alpha = opts.alpha || (() => 0.33) //(z,vs)
32
+ this.color = opts.color || (() => 'black') //(r,z,vs) => {}
33
+
34
+ this.undefinedValue = opts.undefinedValue || 0
35
+
36
+ //this.version = opts.version || 1
37
+ // used only for v2
38
+ //this.shadowProperty = opts.shadowProperty || "shadow"
39
+ }
40
+
41
+ /**
42
+ *
43
+ * @param {Array.<import("../core/Dataset.js").Cell>} cells
44
+ * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
45
+ * @param {number} resolution
46
+ */
47
+ draw(cells, geoCanvas, resolution) {
48
+ //filter
49
+ if (this.filter) cells = cells.filter(this.filter)
50
+ if (cells.length == 0) return;
51
+
52
+ //
53
+ const z = geoCanvas.view.z
54
+
55
+ //get view scale
56
+ const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
57
+
58
+ //if (this.version == "1") {
59
+ //index cells by y and x
60
+ let m = cellsToMatrix(cells, resolution, c => this.elevation(c))
61
+ const x0 = m.x0, y0 = m.y0
62
+
63
+ // compute ray casting shadow
64
+ m = referenceShadow(
65
+ m,
66
+ resolution,
67
+ this.sunAzimuth(resolution, z, viewScale),
68
+ this.sunAltitude(resolution, z, viewScale),
69
+ this.undefinedValue);
70
+
71
+ // make cells
72
+ cells = []
73
+ for (let i = 0; i < m.length; i++) {
74
+ const row = m[i]
75
+ const y = y0 - i * resolution
76
+ for (let j = 0; j < row.length; j++) {
77
+ const sh = row[j]
78
+ if (!sh) continue
79
+ const c = { x: x0 + j * resolution, y: y }
80
+ //c[this.shadowProperty] = sh
81
+ cells.push(c)
82
+ }
83
+ }
84
+ //} else {
85
+ //clean previsouly computed shadows
86
+ /*for (let c of cells) delete c[this.shadowProperty]
87
+
88
+ //compute shading
89
+ referenceShadowV2(
90
+ cells,
91
+ resolution,
92
+ (c) => this.elevation(c),
93
+ this.shadowProperty,
94
+ this.sunAzimuth(resolution, z, viewScale),
95
+ this.sunAltitude(resolution, z, viewScale));
96
+
97
+ //keep only those with shadow
98
+ cells = cells.filter(c => c.shadow)*/
99
+ //}
100
+
101
+ //set style alpha and blend mode
102
+ //TODO: multiply by layer alpha ?
103
+ //geoCanvas.ctx.globalAlpha = s.alpha ? s.alpha(z) : 1.0
104
+ //if (s.blendOperation) geoCanvas.ctx.globalCompositeOperation = s.blendOperation(z)
105
+
106
+ //set affin transform to draw with geographical coordinates
107
+ //geoCanvas.setCanvasTransform()
108
+
109
+ //draw shadowed cells with webgl style
110
+ new SquareColorCategoryWebGLStyle({
111
+ code: () => "a",
112
+ color: { 'a': this.color(resolution, z, viewScale) },
113
+ //alpha: () => this.alpha(resolution, z, viewScale),
114
+ }).draw(cells, geoCanvas, resolution)
115
+
116
+ /*new SquareColorWebGLStyle({
117
+ filter: (c => c.shadow),
118
+ tFun: (c, r) => c.shadow, //1-Math.min(1, c.shadow / 10000),//c.shadow, //,
119
+ color: (t => "rgba(0,0,0," + t + ")"),
120
+ alpha: () => this.alpha(resolution, z, viewScale),
121
+ }).draw(cells, geoCanvas, resolution)*/
122
+
123
+ //draw style filter
124
+ //if (s.filterColor) s.drawFilter(geoCanvas)
125
+
126
+ //update legends
127
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
128
+ }
129
+ }
130
+
131
+
132
+
133
+
134
+ /**
135
+ * Ground-truth terrain shadow algorithm (ray-based)
136
+ * Trig-free with undefined handling
137
+ *
138
+ * @param {number[][]} elevation - DEM [row][col], may contain undefined
139
+ * @param {number} resolution
140
+ * @param {number} sunAzimuth - radians, clockwise from north (+Y)
141
+ * @param {number} sunAltitude - radians - solar elevation angle above the local horizontal plane
142
+ * @param {number|undefined} undefinedValue - The value to set for cells with no elevation value
143
+ * @returns {(number|undefined)[][]} shade. Height above ground where ray light can be reached.
144
+ */
145
+ function referenceShadow(
146
+ elevation,
147
+ resolution = 1000,
148
+ sunAzimuth = 2.356, // 2PI/3
149
+ sunAltitude = 0.15,
150
+ undefinedValue = undefined,
151
+ ) {
152
+ const rows = elevation.length;
153
+ const cols = elevation[0].length;
154
+
155
+ // Sun direction TOWARD the sun
156
+ const ux = Math.sin(sunAzimuth);
157
+ const uy = -Math.cos(sunAzimuth);
158
+ const tanAlt = Math.tan(sunAltitude);
159
+
160
+ //const diffAngle = 40 * Math.PI / 180 // 0.5 deg
161
+ //const tanTop = Math.tan(sunAltitude + diffAngle)
162
+ //const tanBottom = Math.tan(Math.max(sunAltitude - diffAngle, 0))
163
+ //const tanBottom = Math.tan(sunAltitude - diffAngle)
164
+
165
+ const shade = Array.from({ length: rows }, () => new Array(cols).fill(undefined));
166
+
167
+ for (let y0 = 0; y0 < rows; y0++) {
168
+ for (let x0 = 0; x0 < cols; x0++) {
169
+
170
+ const z0raw = elevation[y0][x0] || undefinedValue;
171
+ if (z0raw === undefined) continue;
172
+
173
+ // cast ray
174
+ let t = resolution;
175
+ while (true) {
176
+ const x = x0 - ux * t / resolution;
177
+ const y = y0 - uy * t / resolution;
178
+
179
+ const ix = Math.round(x);
180
+ const iy = Math.round(y);
181
+
182
+ if (ix < 0 || iy < 0 || ix >= cols || iy >= rows) break;
183
+
184
+ const zqraw = elevation[iy][ix] || undefinedValue;
185
+ if (zqraw === undefined) break; // transparent gap
186
+
187
+ //const deltaBottom = zqraw - z0raw - tanBottom * t;
188
+ //if (deltaBottom > 0) {
189
+ const delta = zqraw - z0raw - tanAlt * t;
190
+ if (delta > 0) {
191
+ shade[y0][x0] = 1 //Math.min(3000 / t, 1)
192
+ break;
193
+ /*}
194
+ //const deltaTop = zqraw - z0raw - tanTop * t;
195
+ //if (deltaTop > 0) {
196
+ // shade[y0][x0] = 0
197
+ // break;
198
+ //} else {
199
+ let r = 1 + delta / deltaBottom
200
+ if (r < 0) r = 0
201
+ if (r < 0 || r > 1) console.log(r)
202
+ shade[y0][x0] = shade[y0][x0] ? Math.max(shade[y0][x0], r) : r
203
+ //}*/
204
+ }
205
+ t += resolution;
206
+ }
207
+ }
208
+ }
209
+ return shade;
210
+ }
211
+
212
+
213
+
214
+
215
+ /**
216
+ * Ground-truth terrain shadow algorithm (ray-based)
217
+ * Trig-free with undefined handling
218
+ *
219
+ * @param {Array.<Cell>} cells - DEM [row][col], may contain undefined
220
+ * @param {number} resolution
221
+ * @param {Function} elevationFun
222
+ * @param {string} shadowProperty
223
+ * @param {number} sunAzimuth - radians, clockwise from north (+Y)
224
+ * @param {number} sunAltitude - radians - solar elevation angle above the local horizontal plane
225
+ */
226
+ function referenceShadowV2(
227
+ cells,
228
+ resolution = 1000,
229
+ elevationFun,
230
+ shadowProperty = "shadow",
231
+ sunAzimuth = 2.356, // 2PI/3
232
+ sunAltitude = 0.15
233
+ ) {
234
+
235
+ //get geo extent
236
+ const [minx, maxx] = extent(cells, c => c.x)
237
+ const [miny, maxy] = extent(cells, c => c.y)
238
+
239
+ //index cells by y and x
240
+ const ind = cellsToGrid(cells)
241
+
242
+ // Sun direction TOWARD the sun
243
+ const ux = Math.sin(sunAzimuth);
244
+ const uy = Math.cos(sunAzimuth);
245
+ const tanAlt = Math.tan(sunAltitude);
246
+
247
+ for (let y0 = miny; y0 <= maxy; y0 += resolution) {
248
+ const row = ind[y0]
249
+ if (!row) continue
250
+ for (let x0 = minx; x0 <= maxx; x0 += resolution) {
251
+ const cell0 = row[x0]
252
+ if (!cell0) continue
253
+ const z0raw = elevationFun(cell0);
254
+ if (z0raw === undefined) continue;
255
+
256
+ // ray
257
+ let t = resolution;
258
+ while (true) {
259
+ const y = y0 - Math.round(uy * t / resolution) * resolution;
260
+ if (!ind[y]) break; // transparent gap
261
+
262
+ const x = x0 + Math.round(ux * t / resolution) * resolution;
263
+ const cellq = ind[y][x];
264
+ if (!cellq) break; // transparent gap
265
+
266
+ const zqraw = elevationFun(cellq);
267
+ if (zqraw === undefined) break; // transparent gap
268
+
269
+ // the height above the ground where light ray can be reached
270
+ const delta = zqraw - z0raw - tanAlt * t;
271
+ if (delta > 0) {
272
+ cell0[shadowProperty] = delta
273
+ break;
274
+ }
275
+ t += resolution;
276
+ }
277
+ }
278
+ }
279
+ }
280
+
@@ -141,7 +141,7 @@ export class SquareColorCategoryWebGLStyle extends Style {
141
141
  c = cells[i]
142
142
  const cat = this.code(c, resolution, z, viewScale)
143
143
  if (cat == undefined) {
144
- console.log('Unexpected category: ' + cat)
144
+ //console.log('Unexpected category: ' + cat)
145
145
  continue
146
146
  }
147
147
  const i_ = this.catToI[cat]
@@ -236,7 +236,7 @@ export class SquareColorCategoryWebGLStyle extends Style {
236
236
  geoCanvas.offscreenCtx.drawImage(canvas, 0, 0)
237
237
 
238
238
  //update legends
239
- this.updateLegends({ style: this, resolution: resolution, z: z })
239
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
240
240
  }
241
241
  }
242
242
 
@@ -1,6 +1,8 @@
1
1
  //@ts-check
2
2
  'use strict'
3
3
 
4
+ import { min, max, extent } from 'd3-array'
5
+
4
6
  /**
5
7
  * Get the class id from a value and class break values
6
8
  *
@@ -25,6 +27,71 @@ export function nice(v, multiples = [8, 6, 5, 4, 2.5, 2]) {
25
27
  return v_
26
28
  }
27
29
 
30
+ /**
31
+ * A grid cell.
32
+ * @typedef {{x: number, y: number}} Cell */
33
+
34
+ /**
35
+ * Index cells by y and x
36
+ * @param {Array.<Cell>} cells
37
+ * @param {Function} [fun]
38
+ * @returns {Object}
39
+ */
40
+ export function cellsToGrid(cells, fun) {
41
+ /** @type {Object} */
42
+ const ind = {}
43
+ for (const cell of cells) {
44
+ let row = ind[cell.y]
45
+ if (!row) {
46
+ row = {}
47
+ ind[cell.y] = row
48
+ }
49
+ row[cell.x] = fun ? fun(cell) : cell
50
+ }
51
+ return ind
52
+ }
53
+
54
+
55
+
56
+ /**
57
+ * Cells to grid.
58
+ *
59
+ * @param {Array.<Cell>} cells
60
+ * @param {number} resolution
61
+ * @param {Function} [fun]
62
+ * @returns {Object}
63
+ * Grid[i][j] is the value for line i and column j.
64
+ * Numbered from top to bottom, from left to right.
65
+ * Properties x0 and y0 are the geo coordinates of the lower left corner of the top left cell.
66
+ * Properties minI, maxI, minJ, maxJ are the extent of the grid.
67
+ */
68
+ export function cellsToMatrix(cells, resolution, fun) {
69
+
70
+ // get cells extent
71
+ const [minx, maxx] = extent(cells, c => c.x)
72
+ const [miny, maxy] = extent(cells, c => c.y)
73
+
74
+ // get grid dimension
75
+ const rows = Math.ceil((maxy - miny) / resolution) + 1
76
+ const cols = Math.ceil((maxx - minx) / resolution) + 1
77
+
78
+ // make empty grid
79
+ const grid = Array.from({ length: rows }, () => new Array(cols).fill(undefined));
80
+ grid.x0 = minx
81
+ grid.y0 = maxy
82
+ grid.resolution = resolution
83
+
84
+ for (const c of cells) {
85
+ const i = Math.floor((maxy - c.y) / resolution)
86
+ const j = Math.floor((c.x - minx) / resolution)
87
+ const v = fun ? fun(c) : c
88
+ grid[i][j] = v
89
+ }
90
+ return grid
91
+ }
92
+
93
+
94
+
28
95
  /*
29
96
  //no longer used
30
97
  export function loadImage(src) {