gridviz 2.1.17 → 3.0.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.
Files changed (67) hide show
  1. package/README.md +2 -3
  2. package/dist/gridviz.js +8659 -9294
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +3 -2
  5. package/src/button/Button.js +14 -15
  6. package/src/button/FullscreenButton.js +14 -14
  7. package/src/button/ZoomButtons.js +2 -2
  8. package/src/{DatasetComponent.js → core/Dataset.js} +33 -13
  9. package/src/core/Drawable.js +60 -0
  10. package/src/{GeoCanvas.js → core/GeoCanvas.js} +65 -155
  11. package/src/core/Layer.js +24 -0
  12. package/src/{Legend.js → core/Legend.js} +24 -1
  13. package/src/core/Map.js +474 -0
  14. package/src/core/MultiResolutionDataset.js +96 -0
  15. package/src/core/Style.js +84 -0
  16. package/src/dataset/COGeo.js +5 -5
  17. package/src/dataset/CSVGrid.js +43 -41
  18. package/src/dataset/GeoTIFF.js +9 -14
  19. package/src/dataset/{LGrid.js → JSGrid.js} +9 -11
  20. package/src/dataset/TiledGrid.js +68 -45
  21. package/src/index.js +22 -29
  22. package/src/{BackgroundLayer.js → layer/BackgroundLayer.js} +27 -52
  23. package/src/{BackgroundLayerWMS.js → layer/BackgroundLayerWMS.js} +21 -45
  24. package/src/{LineLayer.js → layer/GeoJSONLayer.js} +22 -23
  25. package/src/layer/GridLayer.js +160 -0
  26. package/src/{LabelLayer.js → layer/LabelLayer.js} +25 -23
  27. package/src/legend/ColorCategoryLegend.js +9 -24
  28. package/src/legend/ColorDiscreteLegend.js +20 -24
  29. package/src/legend/ColorLegend.js +21 -33
  30. package/src/legend/OrientationLegend.js +90 -0
  31. package/src/legend/SizeLegend.js +153 -72
  32. package/src/style/CompositionStyle.js +71 -84
  33. package/src/style/ContourStyle.js +2 -36
  34. package/src/style/DotDensityStyle.js +50 -73
  35. package/src/style/IsoFenceStyle.js +59 -70
  36. package/src/style/JoyPlotStyle.js +41 -48
  37. package/src/style/LegoStyle.js +4 -4
  38. package/src/style/MosaicStyle.js +30 -39
  39. package/src/style/NinjaStarStyle.js +45 -68
  40. package/src/style/PillarStyle.js +70 -93
  41. package/src/style/SegmentStyle.js +37 -75
  42. package/src/style/ShapeColorSizeStyle.js +49 -98
  43. package/src/style/SideCategoryStyle.js +112 -0
  44. package/src/style/SideStyle.js +99 -133
  45. package/src/style/{SquareColorCatWGLStyle.js → SquareColorCategoryWebGLStyle.js} +28 -41
  46. package/src/style/{SquareColorWGLStyle.js → SquareColorWebGLStyle.js} +28 -49
  47. package/src/style/StrokeStyle.js +43 -70
  48. package/src/style/TanakaStyle.js +34 -132
  49. package/src/style/TextStyle.js +43 -64
  50. package/src/style/TimeSeriesStyle.js +44 -75
  51. package/src/utils/WebGLSquareColoringAdvanced.js +4 -4
  52. package/src/utils/scale.js +217 -0
  53. package/src/utils/stretching.js +86 -129
  54. package/src/utils/{Utils.js → utils.js} +15 -0
  55. package/src/utils/webGLUtils.js +4 -5
  56. package/src/utils/zoomUtils.js +30 -30
  57. package/src/App.js +0 -760
  58. package/src/Dataset.js +0 -76
  59. package/src/Layer.js +0 -103
  60. package/src/Style.js +0 -131
  61. package/src/dataset/GridTile.js +0 -41
  62. package/src/legend/SegmentOrientationLegend.js +0 -87
  63. package/src/legend/SegmentWidthLegend.js +0 -101
  64. package/src/style/CompositionStyle_.js +0 -293
  65. package/src/style/ShapeColorSizeStyle_.js +0 -108
  66. package/src/style/SideCatStyle.js +0 -172
  67. /package/src/{Tooltip.js → core/Tooltip.js} +0 -0
@@ -0,0 +1,24 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Drawable } from "./Drawable.js"
5
+
6
+ /**
7
+ * @abstract
8
+ * @author Joseph Davies, Julien Gaffuri
9
+ */
10
+ export class Layer extends Drawable {
11
+
12
+ /**
13
+ * Draw layer.
14
+ *
15
+ * @param {import("./GeoCanvas").GeoCanvas} geoCanvas The canvas where to draw the layer.
16
+ * @param {object} legend
17
+ * @returns {void}
18
+ * @abstract
19
+ */
20
+ draw(geoCanvas, legend = undefined) {
21
+ throw new Error('Method draw not implemented.')
22
+ }
23
+
24
+ }
@@ -63,8 +63,31 @@ export class Legend {
63
63
 
64
64
  if (this.width) this.div.style('width', this.width)
65
65
  if (this.height) this.div.style('height', this.height)
66
+
67
+
68
+ //title
69
+ this.title = opts.title
70
+ this.titleFontSize = opts.titleFontSize || '0.8em'
71
+ this.titleFontWeight = opts.titleFontWeight || 'bold'
72
+
73
+ //label
74
+ this.labelFontSize = opts.labelFontSize || '0.8em'
75
+ this.labelUnitText = opts.labelUnitText || ''
76
+ this.labelFormat = opts.labelFormat
66
77
  }
67
78
 
79
+
80
+ makeTitle() {
81
+ if (!this.title) return
82
+ this.div
83
+ .append('div')
84
+ .style('font-size', this.titleFontSize)
85
+ .style('font-weight', this.titleFontWeight)
86
+ .style('margin-bottom', '7px')
87
+ .text(this.title)
88
+ }
89
+
90
+
68
91
  /**
69
92
  * Apply a style to the legend div.
70
93
  * @param {string} k
@@ -80,7 +103,7 @@ export class Legend {
80
103
  * @param {Object} opts
81
104
  * @abstract
82
105
  */
83
- update(opts) {
106
+ update(opts = {}) {
84
107
  console.error('Legend update not implemented yet.')
85
108
  }
86
109
  }
@@ -0,0 +1,474 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ // internal imports
5
+ import { GeoCanvas } from './GeoCanvas.js'
6
+ import { Tooltip } from './Tooltip.js'
7
+ import { ZoomButtons } from '../button/ZoomButtons.js'
8
+ import { FullscreenButton } from '../button/FullscreenButton.js'
9
+
10
+ // external imports
11
+ import { select } from 'd3-selection'
12
+
13
+ /**
14
+ * A gridviz application.
15
+ *
16
+ * @author Joseph Davies, Julien Gaffuri
17
+ */
18
+ export class Map {
19
+ /**
20
+ * @param {HTMLDivElement} container
21
+ * @param {object} opts
22
+ */
23
+ constructor(container, opts) {
24
+ opts = opts || {}
25
+
26
+ /**
27
+ * The layers.
28
+ * @type {Array.<import("./Layer.js").Layer>}
29
+ * */
30
+ this.layers = opts.layers || []
31
+
32
+ //get container element
33
+ this.container = container || document.getElementById('gridviz')
34
+ if (!this.container) {
35
+ console.error('Cannot find gridviz container element.')
36
+ return
37
+ }
38
+
39
+ //https://css-tricks.com/absolute-positioning-inside-relative-positioning/
40
+ this.container.style.position = "relative"; // container element must have relative positioning
41
+
42
+ //set dimensions
43
+ /** @type {number} */
44
+ this.w = opts.w || this.container.offsetWidth
45
+ /** @type {number} */
46
+ this.h = opts.h || this.container.offsetHeight
47
+
48
+ //create canvas element if not specified
49
+ /** @type {HTMLCanvasElement} */
50
+ this._canvas = opts.canvas || this.initialiseCanvas()
51
+
52
+ /** Make geo canvas
53
+ * @type {GeoCanvas}
54
+ * @private */
55
+ this.geoCanvas = new GeoCanvas(this._canvas, opts.x, opts.y, opts.z, opts)
56
+ this.geoCanvas.redraw = () => { this.redraw() }
57
+
58
+ // legend div
59
+ this.legendDivId = opts.legendDivId || 'gvizLegend'
60
+ this.legend = select('#' + this.legendDivId)
61
+ if (this.legend.empty()) this.initialiseLegend()
62
+
63
+
64
+ //tooltip
65
+
66
+ // set App container as default parent element for tooltip
67
+ if (!opts.tooltip) opts.tooltip = {}
68
+ if (!opts.tooltip.parentElement) opts.tooltip.parentElement = this.container
69
+
70
+ /**
71
+ * @private
72
+ * @type {Tooltip} */
73
+ this.tooltip = new Tooltip(opts.tooltip)
74
+
75
+ // add event listeners to container
76
+ this.mouseOverHandler = (e) => this.focusCell(e)
77
+ this.mouseMoveHandler = (e) => this.focusCell(e)
78
+ this.mouseOutHandler = (e) => this.tooltip.hide()
79
+ this.geoCanvas.canvas.addEventListener('mouseover', this.mouseOverHandler)
80
+ this.geoCanvas.canvas.addEventListener('mousemove', this.mouseMoveHandler)
81
+ this.geoCanvas.canvas.addEventListener('mouseout', this.mouseOutHandler)
82
+
83
+ // add extra logic to onZoomStartFun
84
+ this.geoCanvas.onZoomStartFun = (e) => {
85
+ if (opts.onZoomStartFun) opts.onZoomStartFun(e)
86
+ this.tooltip.hide()
87
+ }
88
+
89
+ //for mouse over
90
+ /**
91
+ * @private
92
+ * @type {HTMLCanvasElement|null} */
93
+ this.canvasSave = null
94
+
95
+ this.selectionRectangleColor = opts.selectionRectangleColor || 'red'
96
+ this.selectionRectangleWidthPix = opts.selectionRectangleWidthPix || (() => 4) //(r,z) => {}
97
+
98
+ // transparent background (e.g. leaflet) 'red painting' fix
99
+ this.transparentBackground = opts.transparentBackground
100
+
101
+ //set default globalCompositeOperation
102
+ this.defaultGlobalCompositeOperation =
103
+ opts.defaultGlobalCompositeOperation || this.geoCanvas.ctx.globalCompositeOperation
104
+ }
105
+
106
+ /**
107
+ * @protected
108
+ * @returns {HTMLCanvasElement}
109
+ */
110
+ initialiseCanvas() {
111
+ const canvas = document.createElement('canvas')
112
+ canvas.setAttribute('width', '' + this.w)
113
+ canvas.setAttribute('height', '' + this.h)
114
+ this.container.appendChild(canvas)
115
+ return canvas
116
+ }
117
+
118
+
119
+ initialiseLegend() {
120
+ this.legend = select(this.container.id && this.container.id != '' ? '#' + this.container.id : 'body')
121
+ .append('div')
122
+ .attr('id', this.legendDivId)
123
+ .style('position', 'absolute')
124
+ .style('width', 'auto')
125
+ .style('height', 'auto')
126
+ .style('background', '#FFFFFF')
127
+ //.style("padding", this.padding)
128
+ .style('border', '0px')
129
+ //.style('border-radius', '5px')
130
+ .style('box-shadow', '3px 3px 3px grey, -3px -3px 3px #ddd')
131
+ .style('font-family', 'Helvetica, Arial, sans-serif')
132
+ .style('bottom', '15px')
133
+ .style('right', '15px')
134
+ //hide
135
+ //.style("visibility", "hidden")
136
+ }
137
+
138
+ /**
139
+ * Set/get layer stack.
140
+ *
141
+ * @param {undefined|import("./Layer.js").Layer|import("./Layer.js").Layer[]} layers
142
+ * @returns { this | import("./Layer.js").Layer[] }
143
+ */
144
+ layers_(layers) {
145
+ if (arguments.length === 0) return this.layers
146
+ if (arguments.length === 1)
147
+ if (Array.isArray(layers)) this.layers = layers
148
+ else this.layers = [layers]
149
+ else this.layers = arguments
150
+ return this
151
+ }
152
+
153
+ /** @returns {this} */
154
+ redraw() {
155
+ //remove legend elements
156
+ if (this.legend) this.legend.selectAll('*').remove()
157
+
158
+ //clear
159
+ this.geoCanvas.initCanvasTransform()
160
+ this.geoCanvas.clear(this.geoCanvas.backgroundColor)
161
+
162
+ const z = this.geoCanvas.view.z
163
+ this.updateExtentGeo()
164
+
165
+ //go through the layers
166
+ for (const layer of this.layers) {
167
+
168
+ //check if layer is visible
169
+ if (layer.visible && !layer.visible(z)) continue
170
+
171
+ //set layer alpha and blend mode
172
+ this.geoCanvas.ctx.globalAlpha = layer.alpha ? layer.alpha(z) : 1.0
173
+ this.geoCanvas.ctx.globalCompositeOperation = layer.blendOperation(z)
174
+
175
+ //set affin transform to draw with geographical coordinates
176
+ this.geoCanvas.setCanvasTransform()
177
+
178
+ //draw layer
179
+ layer.draw(this.geoCanvas, this.legend)
180
+
181
+ //draw layer filter
182
+ if (layer.filterColor)
183
+ layer.drawFilter(this.geoCanvas)
184
+
185
+ //restore default alpha and blend operation
186
+ this.geoCanvas.ctx.globalAlpha = 1.0
187
+ this.geoCanvas.ctx.globalCompositeOperation = this.defaultGlobalCompositeOperation
188
+ }
189
+
190
+ //
191
+ this.canvasSave = null
192
+
193
+ // listen for resize events on the App's container and handle them
194
+ this.defineResizeObserver(this.container, this._canvas)
195
+
196
+ return this
197
+ }
198
+
199
+
200
+ /**
201
+ * @param {number} marginPx
202
+ * @returns {import('./GeoCanvas.js').Envelope}
203
+ * @public
204
+ */
205
+ updateExtentGeo(marginPx = 20) {
206
+ return this.geoCanvas.updateExtentGeo(marginPx)
207
+ }
208
+
209
+
210
+
211
+ /** @param {MouseEvent} e */
212
+ focusCell(e) {
213
+ //compute mouse geo position
214
+ const mousePositionGeo = {
215
+ x: this.geoCanvas.pixToGeoX(e.offsetX + this.tooltip.xMouseOffset),
216
+ y: this.geoCanvas.pixToGeoY(e.offsetY + this.tooltip.yMouseOffset),
217
+ }
218
+ /** @type {{cell:import('./Dataset.js').Cell,html:string,resolution:number} | undefined} */
219
+ const focus = this.getCellFocusInfo(mousePositionGeo)
220
+
221
+ // transparent background (e.g. leaflet) 'red painting' fix
222
+ if (this.transparentBackground) {
223
+ if (focus) {
224
+ this.tooltip.html(focus.html)
225
+ this.tooltip.setPosition(e)
226
+ this.tooltip.show()
227
+ } else {
228
+ this.tooltip.hide()
229
+ }
230
+ this.canvasSave = document.createElement('canvas')
231
+ this.canvasSave.setAttribute('width', '' + this.w)
232
+ this.canvasSave.setAttribute('height', '' + this.h)
233
+ this.canvasSave.getContext('2d')?.drawImage(this.geoCanvas.canvas, 0, 0)
234
+ this.geoCanvas.initCanvasTransform()
235
+ return
236
+ }
237
+
238
+ if (focus) {
239
+ this.tooltip.html(focus.html)
240
+ this.tooltip.setPosition(e)
241
+ this.tooltip.show()
242
+
243
+ //show cell position as a rectangle
244
+ if (!this.canvasSave) {
245
+ this.canvasSave = document.createElement('canvas')
246
+ this.canvasSave.setAttribute('width', '' + this.w)
247
+ this.canvasSave.setAttribute('height', '' + this.h)
248
+ this.canvasSave.getContext('2d')?.drawImage(this.geoCanvas.canvas, 0, 0)
249
+ } else {
250
+ this.geoCanvas.ctx.drawImage(this.canvasSave, 0, 0)
251
+ }
252
+
253
+ //draw image saved + draw rectangle
254
+ const rectWPix = this.selectionRectangleWidthPix
255
+ ? this.selectionRectangleWidthPix(focus.resolution, this.geoCanvas.view.z)
256
+ : 4
257
+ this.geoCanvas.initCanvasTransform()
258
+ this.geoCanvas.ctx.strokeStyle = this.selectionRectangleColor
259
+ this.geoCanvas.ctx.lineWidth = rectWPix
260
+ this.geoCanvas.ctx.beginPath()
261
+
262
+ this.geoCanvas.ctx.rect(
263
+ this.geoCanvas.geoToPixX(focus.cell.x) - rectWPix / 2,
264
+ this.geoCanvas.geoToPixY(focus.cell.y) + rectWPix / 2,
265
+ focus.resolution / this.geoCanvas.view.z + rectWPix,
266
+ -focus.resolution / this.geoCanvas.view.z - rectWPix
267
+ )
268
+ this.geoCanvas.ctx.stroke()
269
+ } else {
270
+ this.tooltip.hide()
271
+ if (this.canvasSave) this.geoCanvas.ctx.drawImage(this.canvasSave, 0, 0)
272
+ }
273
+ }
274
+
275
+
276
+ /**
277
+ * Return the cell HTML info at a given geo position.
278
+ * This is usefull for user interactions, to show this info where the user clicks for example.
279
+ *
280
+ * @param {{x:number,y:number}} posGeo
281
+ * @returns {{cell:import('./Dataset.js').Cell,html:string,resolution:number} | undefined}
282
+ * @protected
283
+ */
284
+ getCellFocusInfo(posGeo) {
285
+ //go through the layers, starting from top
286
+ const z = this.geoCanvas.view.z
287
+ for (let i = this.layers.length - 1; i >= 0; i--) {
288
+ /** @type {import("./Layer.js").Layer} */
289
+ const layer = this.layers[i]
290
+ if (layer.visible && !layer.visible(z)) continue
291
+ if (!layer.cellInfoHTML) continue
292
+ //if (layer.cellInfoHTML === 'none') continue
293
+ if (!layer.getDataset) continue
294
+ const dsc = layer.getDataset(z)
295
+ if (!dsc) continue
296
+
297
+ //get cell at mouse position
298
+ /** @type {import('./Dataset.js').Cell|undefined} */
299
+ const cell = dsc.getCellFromPosition(posGeo, dsc.getViewCache())
300
+ //console.log(cell, dsc.resolution)
301
+ if (!cell) return undefined
302
+ const html = layer.cellInfoHTML(cell, dsc.getResolution())
303
+ if (!html) return undefined
304
+ return { cell: cell, html: html, resolution: dsc.getResolution() }
305
+ }
306
+ }
307
+
308
+
309
+ /**
310
+ * @param {number} x
311
+ * @param {number} y
312
+ * @param {number|undefined} z
313
+ */
314
+ setView(x, y, z = undefined) {
315
+ this.geoCanvas.view.x = x
316
+ this.geoCanvas.view.y = y
317
+ if (z != undefined) this.geoCanvas.view.z = z
318
+ return this
319
+ }
320
+
321
+ /** @returns {import('./GeoCanvas.js').View} */
322
+ getView() { return this.geoCanvas.view }
323
+
324
+ /** @returns {number} */
325
+ getZoom() {
326
+ return this.geoCanvas.view.z
327
+ }
328
+ /** @param {number} z @returns {this} */
329
+ setZoom(z) {
330
+ this.geoCanvas.view.z = z
331
+ return this
332
+ }
333
+
334
+
335
+ /** @returns {Array.<number>} */
336
+ getZoomExtent() {
337
+ return this.geoCanvas.getZoomExtent()
338
+ }
339
+ /** @param {Array.<number>} val @returns {this} */
340
+ setZoomExtent(val) {
341
+ this.geoCanvas.setZoomExtent(val)
342
+ return this
343
+ }
344
+
345
+
346
+
347
+ /** @returns {string} */
348
+ getBackgroundColor() {
349
+ return this.geoCanvas.backgroundColor
350
+ }
351
+ /** @param {string} val @returns {this} */
352
+ setBackgroundColor(val) {
353
+ this.geoCanvas.backgroundColor = val
354
+ return this
355
+ }
356
+
357
+ /**
358
+ * Adds a set of zoom buttons to the map
359
+ *
360
+ * @param {object} opts
361
+ * @returns {this}
362
+ */
363
+ addZoomButtons(opts) {
364
+ // * opts.id
365
+ // * opts.onZoom - custom event handler function
366
+ // * opts.x
367
+ // * opts.y
368
+ // * opts.delta - zoom delta applied on each click
369
+
370
+ this.zoomButtons = new ZoomButtons({
371
+ map: this,
372
+ id: opts?.id || 'gridviz-zoom-buttons',
373
+ class: opts?.class,
374
+ x: opts?.x,
375
+ y: opts?.y,
376
+ onZoom: opts?.onZoom,
377
+ delta: opts?.delta || 0.2
378
+ })
379
+
380
+ return this
381
+ }
382
+
383
+ /**
384
+ * Adds a fullscreen toggle button to the app
385
+ *
386
+ * @param {object} opts
387
+ * @returns {this}
388
+ */
389
+ addFullscreenButton(opts) {
390
+ // * opts.map - the gridviz map
391
+ // * opts.id
392
+ // * opts.x
393
+ // * opts.y
394
+
395
+ this.fullscreenButton = new FullscreenButton({
396
+ map: this,
397
+ id: opts?.id || 'gridviz-fullscreen-button',
398
+ class: opts?.class,
399
+ x: opts?.x,
400
+ y: opts?.y
401
+ })
402
+
403
+ return this
404
+ }
405
+
406
+ /** @returns {this} */
407
+ setViewFromURL() {
408
+ this.geoCanvas.setViewFromURL()
409
+ return this
410
+ }
411
+
412
+ /**
413
+ * @description Add a resize event observer to the Apps container and update the canvas accordingly
414
+ * @param {HTMLDivElement} container The App's container element
415
+ * @param {HTMLCanvasElement} canvas The App canvas element
416
+ * @memberof App
417
+ */
418
+ defineResizeObserver(container, canvas) {
419
+ // listen to resize events
420
+ const resizeObserver = new ResizeObserver((entries) => {
421
+ // make sure canvas has been built
422
+ if (container.clientWidth > 0 && container.clientHeight > 0) {
423
+ // make sure we dont exceed loop limit first
424
+ // see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
425
+ window.requestAnimationFrame(() => {
426
+ if (!Array.isArray(entries) || !entries.length) {
427
+ return
428
+ }
429
+ // update the map and canvas size
430
+ if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
431
+ this.h = container.clientHeight
432
+ this.w = container.clientWidth
433
+ this.geoCanvas.h = container.clientHeight
434
+ this.geoCanvas.w = container.clientWidth
435
+ canvas.setAttribute('width', '' + this.w)
436
+ canvas.setAttribute('height', '' + this.h)
437
+ this.redraw()
438
+
439
+ //update button positions
440
+ // if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px'
441
+ // if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px'
442
+ }
443
+ })
444
+ }
445
+ })
446
+
447
+ resizeObserver.observe(container)
448
+ }
449
+
450
+ /**
451
+ * @description Destroy the map and it's event listeners
452
+ * This should significantly reduce the memory used when creating and destroying gridviz map instances (for example in leaflet-gridviz)
453
+ * @memberof App
454
+ */
455
+ destroy() {
456
+ // clear layers
457
+ this.layers = []
458
+ this.bgLayers = []
459
+
460
+ // remove event listeners from container
461
+ this.container.removeEventListener('mouseover', this.mouseOverHandler)
462
+ this.container.removeEventListener('mousemove', this.mouseMoveHandler)
463
+ this.container.removeEventListener('mouseout', this.mouseOutHandler)
464
+
465
+ // remove canvas
466
+ this.geoCanvas.canvas.remove()
467
+
468
+ // remove legend
469
+ this.legend?.remove()
470
+
471
+ // remove tooltip
472
+ this.tooltip.tooltip?.remove()
473
+ }
474
+ }
@@ -0,0 +1,96 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /**
5
+ * A multi resolution dataset of grid cells.
6
+ * It consists of different {@link Dataset}s for each resolution.
7
+ *
8
+ * @abstract
9
+ *
10
+ * @author Joseph Davies, Julien Gaffuri
11
+ */
12
+ export class MultiResolutionDataset {
13
+ /**
14
+ * @param {Array.<number>} resolutions The resolutions of the datasets, in CRS geographical unit.
15
+ * @param {Array.<import("./Dataset").Dataset>|function(number):import("./Dataset").Dataset} datasets The datasets list, one per resolution. Or a function that returns a dataset from a resolution value.
16
+ * @param { {preprocess?:function(import("./Dataset").Cell):boolean} } opts Options. preprocess: A function to apply on each dataset cell to prepare its values. Can be used also to select cells to keep.
17
+ */
18
+ constructor(resolutions, datasets, opts = {}) {
19
+ opts = opts || {}
20
+
21
+ /** The resolutions of the datasets, in CRS geographical unit.
22
+ * @type {Array.<number>} */
23
+ this.resolutions = resolutions
24
+
25
+ /** The datasets. If the list is not explictely defined, build it from the resolution list with the function
26
+ * @type {Array.<import("./Dataset").Dataset>} */
27
+ this.datasets = (typeof datasets === 'function') ? this.resolutions.map(datasets) : datasets
28
+
29
+ //there must be as many datasets as resolutions
30
+ if (this.datasets.length > 1 && this.datasets.length != this.resolutions.length)
31
+ throw new Error(
32
+ 'Uncompatible number of datasets and resolutions: ' +
33
+ this.datasets.length +
34
+ ' ' +
35
+ this.resolutions.length
36
+ )
37
+
38
+ //set dataset preprocesses if specified
39
+ if (opts.preprocess) this.setPrepocesses(opts.preprocess)
40
+ }
41
+
42
+
43
+ /**
44
+ * Return the relevant dataset for a specified zoom.
45
+ * @param {number} z
46
+ * @param {number} minPixelsPerCell
47
+ * @returns {import("./Dataset").Dataset|undefined}
48
+ * */
49
+ getDataset(z, minPixelsPerCell) {
50
+
51
+ //special case whith single dataset
52
+ if (this.datasets.length == 1) return this.datasets[0]
53
+
54
+ const rs = this.resolutions
55
+ let i = 0
56
+ let z_ = rs[i] / minPixelsPerCell
57
+ while (z_ < z && i < rs.length) {
58
+ i++
59
+ z_ = rs[i] / minPixelsPerCell
60
+ }
61
+ //if (i == 0) return this.dataset.datasets[0];
62
+ //return this.dataset.datasets[i - 1];
63
+ if (i == rs.length) return this.datasets[rs.length - 1]
64
+ return this.datasets[i]
65
+ }
66
+
67
+
68
+
69
+ /**
70
+ * Set a preprocess function for all datasets.
71
+ * This is a function applied on each cell after it has been loaded.
72
+ *
73
+ * @param {function(import("./Dataset").Cell):boolean} preprocess
74
+ * @returns {this}
75
+ */
76
+ setPrepocesses(preprocess) {
77
+ for (let ds of this.datasets) ds.preprocess = preprocess
78
+ return this
79
+ }
80
+
81
+ /**
82
+ * A function to ease the creation of multi resolution datasets.
83
+ *
84
+ * @param {Array.<number>} resolutions The resolutions of the datasets, in CRS geographical unit
85
+ * @param {function(number):import("./Dataset").Dataset} resToDataset Function returning a dataset from a resolution
86
+ * @param { {preprocess?:function(import("./Dataset").Cell):boolean} } opts Options. preprocess: A function to apply on each dataset cell to prepare its values
87
+ * @returns {MultiResolutionDataset}
88
+ */
89
+ /*static make(resolutions, resToDataset, opts) {
90
+ //make datasets
91
+ const dsc = []
92
+ for (const res of resolutions) dsc.push(resToDataset(res))
93
+ //make multi resolution dataset
94
+ return new MultiResolutionDataset(dsc, resolutions, opts)
95
+ }*/
96
+ }