gridviz 2.0.38 → 2.0.40

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 (58) hide show
  1. package/README.md +119 -119
  2. package/dist/gridviz.js +7945 -8887
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +77 -78
  5. package/src/App.js +698 -698
  6. package/src/BackgroundLayer.js +182 -182
  7. package/src/BackgroundLayerWMS.js +131 -131
  8. package/src/Dataset.js +76 -76
  9. package/src/DatasetComponent.js +103 -103
  10. package/src/GeoCanvas.js +419 -419
  11. package/src/LabelLayer.js +190 -190
  12. package/src/Layer.js +92 -92
  13. package/src/Legend.js +86 -86
  14. package/src/LineLayer.js +150 -150
  15. package/src/Style.js +114 -114
  16. package/src/Tooltip.js +168 -168
  17. package/src/dataset/COGeo.js +24 -24
  18. package/src/dataset/CSVGrid.js +104 -104
  19. package/src/dataset/GeoTIFF.js +127 -127
  20. package/src/dataset/GridTile.js +41 -41
  21. package/src/dataset/ParquetGrid.js +122 -122
  22. package/src/dataset/TiledGrid.js +296 -296
  23. package/src/index.js +174 -177
  24. package/src/legend/ColorCategoryLegend.js +124 -124
  25. package/src/legend/ColorDiscreteLegend.js +105 -105
  26. package/src/legend/ColorLegend.js +125 -125
  27. package/src/legend/SegmentOrientationLegend.js +87 -87
  28. package/src/legend/SegmentWidthLegend.js +97 -97
  29. package/src/legend/SizeLegend.js +141 -141
  30. package/src/style/CompositionStyle.js +300 -300
  31. package/src/style/ContourStyle.js +54 -54
  32. package/src/style/DotDensityStyle.js +156 -156
  33. package/src/style/JoyPlotStyle.js +135 -135
  34. package/src/style/LegoStyle.js +112 -112
  35. package/src/style/MosaicStyle.js +107 -107
  36. package/src/style/NinjaStarStyle.js +124 -124
  37. package/src/style/PillarStyle.js +230 -230
  38. package/src/style/SegmentStyle.js +142 -142
  39. package/src/style/ShapeColorSizeStyle.js +126 -126
  40. package/src/style/SideCatStyle.js +172 -172
  41. package/src/style/SideStyle.js +218 -218
  42. package/src/style/SquareColorCatWGLStyle.js +120 -120
  43. package/src/style/SquareColorWGLStyle.js +146 -146
  44. package/src/style/StrokeStyle.js +131 -131
  45. package/src/style/TanakaStyle.js +160 -160
  46. package/src/style/TextStyle.js +132 -132
  47. package/src/utils/Utils.js +35 -35
  48. package/src/utils/WebGLRectangleColoring.js +106 -106
  49. package/src/utils/WebGLSquareColoring.js +118 -118
  50. package/src/utils/WebGLSquareColoringAdvanced.js +230 -230
  51. package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -133
  52. package/src/utils/WebGLSquareColoringKS.js +153 -153
  53. package/src/utils/stretching.js +131 -131
  54. package/src/utils/webGLUtils.js +71 -71
  55. package/src/utils/zoomUtils.js +142 -142
  56. package/src/style/KernelSmoothingOldStyle.js +0 -221
  57. package/src/style/KernelSmoothingStyle.js +0 -115
  58. package/src/style/KernelSmoothingWGLStyle.js +0 -130
package/src/App.js CHANGED
@@ -1,698 +1,698 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- // internal imports
5
- import { GeoCanvas } from './GeoCanvas.js'
6
- import { Layer } from './Layer.js'
7
- import { Dataset } from './Dataset.js'
8
- import { Tooltip } from './Tooltip.js'
9
- import { CSVGrid } from './dataset/CSVGrid.js'
10
- //import { ParquetGrid } from './dataset/ParquetGrid';
11
- import { TiledGrid } from './dataset/TiledGrid.js'
12
- import { BackgroundLayer } from './BackgroundLayer.js'
13
- import { BackgroundLayerWMS } from './BackgroundLayerWMS.js'
14
- import { LabelLayer } from './LabelLayer.js'
15
- import { LineLayer } from './LineLayer.js'
16
- import { monitor, monitorDuration } from './utils/Utils.js'
17
-
18
- // external imports
19
- import { select } from 'd3-selection'
20
-
21
- /**
22
- * A gridviz application.
23
- *
24
- * @author Joseph Davies, Julien Gaffuri
25
- */
26
- export class App {
27
- /**
28
- * @param {HTMLDivElement} container
29
- * @param {object} opts
30
- */
31
- constructor(container, opts) {
32
- opts = opts || {}
33
-
34
- /**
35
- * The layers.
36
- * @type {Array.<Layer>}
37
- * */
38
- this.layers = []
39
-
40
- //get container element
41
- container = container || document.getElementById('gridviz')
42
- if (!container) {
43
- console.error('Cannot find gridviz container element.')
44
- return
45
- }
46
-
47
- //set dimensions
48
- /** @type {number} */
49
- this.w = opts.w || container.offsetWidth
50
- /** @type {number} */
51
- this.h = opts.h || container.offsetHeight
52
-
53
- //create canvas element if user doesnt specify one
54
- /** @type {HTMLCanvasElement} */
55
- let canvas = opts.canvas || null
56
- if (!canvas) {
57
- canvas = document.createElement('canvas')
58
- canvas.setAttribute('width', '' + this.w)
59
- canvas.setAttribute('height', '' + this.h)
60
- container.appendChild(canvas)
61
- }
62
-
63
- /** Make geo canvas
64
- * @type {GeoCanvas}
65
- * @private */
66
- this.cg = new GeoCanvas(canvas, undefined, 1, opts)
67
- this.cg.redraw = (strong = true) => {
68
- if (monitor) monitorDuration('Start redraw')
69
- //console.log("?x=" + this.cg.getCenter().x + "&y=" + this.cg.getCenter().y + "&z=" + this.cg.getZf())
70
-
71
- //remove legend elements
72
- if (this.legend && strong) this.legend.selectAll('*').remove()
73
-
74
- //clear
75
- this.cg.initCanvasTransform()
76
- this.cg.clear(this.cg.backgroundColor)
77
-
78
- const zf = this.getZoomFactor()
79
- this.updateExtentGeo()
80
-
81
- //go through the background layers
82
- for (const layer of this.bgLayers) {
83
- //check if layer is visible
84
- if (!layer.visible) continue
85
- if (zf > layer.maxZoom) continue
86
- if (zf < layer.minZoom) continue
87
-
88
- //draw layer
89
- layer.draw(this.cg)
90
- }
91
-
92
- //go through the layers
93
- for (const layer of this.layers) {
94
- //check if layer is visible
95
- if (!layer.visible) continue
96
- if (zf > layer.maxZoom) continue
97
- if (zf < layer.minZoom) continue
98
-
99
- //get layer dataset component
100
- /** @type {import('./DatasetComponent').DatasetComponent|undefined} */
101
- const dsc = layer.getDatasetComponent(zf)
102
- if (!dsc) continue
103
-
104
- //launch data download, if necessary
105
- if (strong)
106
- dsc.getData(this.cg.extGeo, () => {
107
- this.cg.redraw()
108
- })
109
-
110
- //update dataset view cache
111
- if (strong) dsc.updateViewCache(this.cg.extGeo)
112
-
113
- //draw cells, style by style
114
- if (strong)
115
- for (const s of layer.styles) {
116
- if (zf > s.maxZoom) continue
117
- if (zf < s.minZoom) continue
118
- s.draw(dsc.getViewCache(), dsc.getResolution(), this.cg)
119
- }
120
-
121
- //add legend element
122
- if (this.legend && strong)
123
- for (const s of layer.styles) {
124
- if (zf > s.maxZoom) continue
125
- if (zf < s.minZoom) continue
126
- for (const lg of s.legends) {
127
- //console.log(s, lg)
128
- //this.legend.append(lg.div)
129
- //s1.node().appendChild(s2.node())
130
- this.legend.node().append(lg.div.node())
131
- }
132
-
133
- //case for styles of styles, like kernel smoothing
134
- //TODO do better
135
- if (s['styles']) {
136
- for (const s2 of s.styles) {
137
- if (zf > s2.maxZoom) continue
138
- if (zf < s2.minZoom) continue
139
- for (const lg of s2.legends) {
140
- //console.log(s, lg)
141
- //this.legend.append(lg.div)
142
- //s1.node().appendChild(s2.node())
143
- this.legend.node().append(lg.div.node())
144
- }
145
- }
146
- }
147
- }
148
- }
149
-
150
- //draw boundary layer
151
- //if (strong)
152
- if (this.showBoundaries && this.boundaryLayer) this.boundaryLayer.draw(this.cg)
153
-
154
- //draw label layer
155
- //if (strong)
156
- if (this.showLabels && this.labelLayer) this.labelLayer.draw(this.cg)
157
-
158
- //
159
- this.canvasSave = null
160
-
161
- if (monitor) monitorDuration('End redraw')
162
-
163
- // listen for resize events on the App's container and handle them
164
- this.defineResizeObserver(container, canvas)
165
-
166
- return this
167
- }
168
-
169
- /** @type {Array.<BackgroundLayer|BackgroundLayerWMS>} */
170
- this.bgLayers = []
171
- /** @type {boolean} */
172
- this.showBgLayers = true
173
-
174
- /** @type {LabelLayer | undefined} */
175
- this.labelLayer = undefined
176
- /** @type {boolean} */
177
- this.showLabels = true
178
-
179
- /** @type {LineLayer | undefined} */
180
- this.boundaryLayer = undefined
181
- /** @type {boolean} */
182
- this.showBoundaries = true
183
-
184
- //legend div
185
-
186
- this.legendDivId = opts.legendDivId || 'gvizLegend'
187
- this.legend = select('#' + this.legendDivId)
188
- if (this.legend.empty()) {
189
- this.legend = select('#' + container.id)
190
- .append('div')
191
- .attr('id', this.legendDivId)
192
- .style('position', 'absolute')
193
- .style('width', 'auto')
194
- .style('height', 'auto')
195
- .style('background', '#FFFFFFCC')
196
- //.style("padding", this.padding)
197
- .style('border', '0px')
198
- .style('border-radius', '5px')
199
- .style('box-shadow', '3px 3px 3px grey, -3px -3px 3px #ddd')
200
- .style('font-family', 'Helvetica, Arial, sans-serif')
201
- .style('top', '20px')
202
- .style('right', '20px')
203
- //hide
204
- //.style("visibility", "hidden")
205
- }
206
-
207
- //tooltip
208
-
209
- // set App container as default parent element for tooltip
210
- if (!opts.tooltip) opts.tooltip = {}
211
- if (!opts.tooltip.parentElement) opts.tooltip.parentElement = container
212
-
213
- /**
214
- * @private
215
- * @type {Tooltip} */
216
- this.tooltip = new Tooltip(opts.tooltip)
217
-
218
- /** @param {MouseEvent} e */
219
- const focusCell = (e) => {
220
- //compute mouse geo position
221
- const mousePositionGeo = {
222
- x: this.cg.pixToGeoX(e.offsetX + this.tooltip.xMouseOffset),
223
- y: this.cg.pixToGeoY(e.offsetY + this.tooltip.yMouseOffset),
224
- }
225
- /** @type {{cell:import('./Dataset').Cell,html:string,resolution:number} | undefined} */
226
- const focus = this.getCellFocusInfo(mousePositionGeo)
227
-
228
- // transparent background (e.g. leaflet) 'red painting' fix
229
- if (opts.transparentBackground) {
230
- if (focus) {
231
- this.tooltip.setPosition(e)
232
- this.tooltip.show()
233
- this.tooltip.html(focus.html)
234
- } else {
235
- this.tooltip.hide()
236
- }
237
- this.canvasSave = document.createElement('canvas')
238
- this.canvasSave.setAttribute('width', '' + this.w)
239
- this.canvasSave.setAttribute('height', '' + this.h)
240
- this.canvasSave.getContext('2d').drawImage(this.cg.canvas, 0, 0)
241
- this.cg.initCanvasTransform()
242
- return
243
- }
244
-
245
- if (focus) {
246
- this.tooltip.setPosition(e)
247
- this.tooltip.show()
248
- this.tooltip.html(focus.html)
249
-
250
- //show cell position as a rectangle
251
- if (!this.canvasSave) {
252
- this.canvasSave = document.createElement('canvas')
253
- this.canvasSave.setAttribute('width', '' + this.w)
254
- this.canvasSave.setAttribute('height', '' + this.h)
255
- this.canvasSave.getContext('2d').drawImage(this.cg.canvas, 0, 0)
256
- } else {
257
- this.cg.ctx.drawImage(this.canvasSave, 0, 0)
258
- }
259
-
260
- //draw image saved + draw rectangle
261
- const rectWPix = this.selectionRectangleWidthPix
262
- ? this.selectionRectangleWidthPix(focus.resolution, this.getZoomFactor())
263
- : 4
264
- this.cg.initCanvasTransform()
265
- this.cg.ctx.strokeStyle = this.selectionRectangleColor
266
- this.cg.ctx.lineWidth = rectWPix
267
- this.cg.ctx.beginPath()
268
-
269
- this.cg.ctx.rect(
270
- this.cg.geoToPixX(focus.cell.x) - rectWPix / 2,
271
- this.cg.geoToPixY(focus.cell.y) + rectWPix / 2,
272
- focus.resolution / this.getZoomFactor() + rectWPix,
273
- -focus.resolution / this.getZoomFactor() - rectWPix
274
- )
275
- this.cg.ctx.stroke()
276
- } else {
277
- this.tooltip.hide()
278
- if (this.canvasSave) this.cg.ctx.drawImage(this.canvasSave, 0, 0)
279
- }
280
- }
281
- container.addEventListener('mouseover', (e) => {
282
- focusCell(e)
283
- })
284
- container.addEventListener('mousemove', (e) => {
285
- focusCell(e)
286
- })
287
- container.addEventListener('mouseout', () => {
288
- this.tooltip.hide()
289
- })
290
-
291
- // add extra logic to onZoomStartFun
292
- this.cg.onZoomStartFun = (e) => {
293
- if (opts.onZoomStartFun) opts.onZoomStartFun(e)
294
- this.tooltip.hide()
295
- }
296
-
297
- //for mouse over
298
- /**
299
- * @private
300
- * @type {HTMLCanvasElement|null} */
301
- this.canvasSave = null
302
-
303
- this.selectionRectangleColor = opts.selectionRectangleColor || 'red'
304
- this.selectionRectangleWidthPix = opts.selectionRectangleWidthPix || (() => 4) //(r,zf) => {}
305
-
306
- //
307
- //canvas.addEventListener("keydown", e => { console.log(arguments) });
308
- }
309
-
310
- /**
311
- * @param {number} marginPx
312
- * @returns {import('./Dataset').Envelope}
313
- * @public
314
- */
315
- updateExtentGeo(marginPx = 20) {
316
- return this.cg.updateExtentGeo(marginPx)
317
- }
318
-
319
- /**
320
- * Return the cell HTML info at a given geo position.
321
- * This is usefull for user interactions, to show this info where the user clicks for example.
322
- *
323
- * @param {{x:number,y:number}} posGeo
324
- * @returns {{cell:import('./Dataset').Cell,html:string,resolution:number} | undefined}
325
- * @protected
326
- */
327
- getCellFocusInfo(posGeo) {
328
- //go through the layers, starting from top
329
- const zf = this.getZoomFactor()
330
- for (let i = this.layers.length - 1; i >= 0; i--) {
331
- /** @type {Layer} */
332
- const layer = this.layers[i]
333
- if (!layer.visible) continue
334
- if (!layer.cellInfoHTML) continue
335
- if (layer.cellInfoHTML === 'none') continue
336
- const dsc = layer.getDatasetComponent(zf)
337
- if (!dsc) continue
338
-
339
- //get cell at mouse position
340
- /** @type {import('./Dataset').Cell|undefined} */
341
- const cell = dsc.getCellFromPosition(posGeo, dsc.getViewCache())
342
- if (!cell) return undefined
343
- const html = layer.cellInfoHTML(cell, dsc.getResolution())
344
- if (!html) return undefined
345
- return { cell: cell, html: html, resolution: dsc.getResolution() }
346
- }
347
- }
348
-
349
- //getters and setters
350
-
351
- /** @returns {{x:number,y:number}} */
352
- getGeoCenter() {
353
- return this.cg.getCenter()
354
- }
355
- /** @param {{x:number,y:number}} val @returns {this} */
356
- setGeoCenter(val) {
357
- this.cg.setCenter(val)
358
- return this
359
- }
360
-
361
- /** @returns {number} */
362
- getZoomFactor() {
363
- return this.cg.getZf()
364
- }
365
- /** @param {number} val @returns {this} */
366
- setZoomFactor(val) {
367
- this.cg.setZf(val)
368
- return this
369
- }
370
-
371
- /** @returns {Array.<number>} */
372
- getZoomFactorExtent() {
373
- return this.cg.getZfExtent()
374
- }
375
- /** @param {Array.<number>} val @returns {this} */
376
- setZoomFactorExtent(val) {
377
- this.cg.setZfExtent(val)
378
- return this
379
- }
380
-
381
- /** @returns {string} */
382
- getBackgroundColor() {
383
- return this.cg.backgroundColor
384
- }
385
- /** @param {string} val @returns {this} */
386
- setBackgroundColor(val) {
387
- this.cg.backgroundColor = val
388
- return this
389
- }
390
-
391
- /** @returns {LineLayer | undefined} */
392
- getBoundaryLayer() {
393
- return this.boundaryLayer
394
- }
395
- /** @param {object} opts @returns {this} */
396
- setBoundaryLayer(opts) {
397
- this.boundaryLayer = new LineLayer(opts)
398
- return this
399
- }
400
-
401
- /** @returns {LabelLayer | undefined} */
402
- getLabelLayer() {
403
- return this.labelLayer
404
- }
405
- /** @param {object} opts @returns {this} */
406
- setLabelLayer(opts) {
407
- this.labelLayer = new LabelLayer(opts)
408
- return this
409
- }
410
-
411
- /** @returns {this} */
412
- redraw() {
413
- this.cg.redraw()
414
- return this
415
- }
416
-
417
- /**
418
- * Add a layer to the app.
419
- *
420
- * @param {Dataset} dataset The dataset of the layer
421
- * @param {Array.<import('./Style').Style>} styles The styles of the layer
422
- * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string}} opts The layer options.
423
- * @returns {this}
424
- */
425
- addLayerFromDataset(dataset, styles, opts) {
426
- const lay = new Layer(dataset, styles, opts)
427
- this.layers.push(lay)
428
- return this
429
- }
430
-
431
- //dataset creation
432
-
433
- /**
434
- * Make a CSV grid dataset.
435
- *
436
- * @param {string} url The URL of the dataset.
437
- * @param {number} resolution The dataset resolution in geographical unit.
438
- * @param {object=} opts The parameters of the dataset.
439
- * @returns {Dataset}
440
- */
441
- makeCSVGridDataset(url, resolution, opts) {
442
- return new Dataset(
443
- [
444
- new CSVGrid(url, resolution, opts).getData(undefined, () => {
445
- this.cg.redraw()
446
- }),
447
- ],
448
- [],
449
- opts
450
- )
451
- }
452
-
453
- /**
454
- * Make a parquet grid dataset.
455
- *
456
- * @param {string} url The URL of the dataset.
457
- * @param {number} resolution The dataset resolution in geographical unit.
458
- * @param {object=} opts The parameters of the dataset.
459
- * @returns {Dataset}
460
- */
461
- /*makeParquetGridDataset(url, resolution, opts) {
462
- return new Dataset([new ParquetGrid(url, resolution, opts).getData(undefined, () => { this.cg.redraw(); })], [], opts)
463
- }*/
464
-
465
- /**
466
- * Make a tiled grid dataset.
467
- *
468
- * @param {string} url
469
- * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
470
- * @returns {Dataset}
471
- */
472
- makeTiledGridDataset(url, opts) {
473
- return new Dataset(
474
- [
475
- new TiledGrid(url, this, opts).loadInfo(() => {
476
- this.cg.redraw()
477
- }),
478
- ],
479
- [],
480
- opts
481
- )
482
- }
483
-
484
- //multi scale dataset creation
485
-
486
- /**
487
- * Make a multi scale parquet grid dataset.
488
- *
489
- * @param {Array.<number>} resolutions
490
- * @param {function(number):string} resToURL
491
- * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
492
- * @returns {Dataset}
493
- */
494
- /*makeMultiScaleParquetGridDataset(resolutions, resToURL, opts) {
495
- return Dataset.make(
496
- resolutions,
497
- (res) => new ParquetGrid(resToURL(res), res, opts).getData(undefined, () => { this.cg.redraw(); }),
498
- opts
499
- )
500
- }*/
501
-
502
- /**
503
- * Make a multi scale CSV grid dataset.
504
- *
505
- * @param {Array.<number>} resolutions
506
- * @param {function(number):string} resToURL
507
- * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
508
- * @returns {Dataset}
509
- */
510
- makeMultiScaleCSVGridDataset(resolutions, resToURL, opts) {
511
- return Dataset.make(
512
- resolutions,
513
- (res) =>
514
- new CSVGrid(resToURL(res), res, opts).getData(undefined, () => {
515
- this.cg.redraw()
516
- }),
517
- opts
518
- )
519
- }
520
-
521
- //tiled multiscale
522
-
523
- /**
524
- * Make a multi scale tiled grid dataset.
525
- *
526
- * @param {Array.<number>} resolutions
527
- * @param {function(number):string} resToURL
528
- * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
529
- * @returns {Dataset}
530
- */
531
- makeMultiScaleTiledGridDataset(resolutions, resToURL, opts) {
532
- return Dataset.make(
533
- resolutions,
534
- (res) =>
535
- new TiledGrid(resToURL(res), this, opts).loadInfo(() => {
536
- this.cg.redraw()
537
- }),
538
- opts
539
- )
540
- }
541
-
542
- // direct layer creation
543
-
544
- /**
545
- * Add a layer from a CSV grid dataset.
546
- *
547
- * @param {string} url The URL of the dataset.
548
- * @param {number} resolution The dataset resolution in geographical unit.
549
- * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
550
- * @param {object=} opts The parameters of the dataset and layer.
551
- * @returns {this}
552
- */
553
- addCSVGridLayer(url, resolution, styles, opts) {
554
- const ds = this.makeCSVGridDataset(url, resolution, opts)
555
- return this.addLayerFromDataset(ds, styles, opts)
556
- }
557
-
558
- /**
559
- * Add a layer from a parquet grid dataset.
560
- *
561
- * @param {string} url The URL of the dataset.
562
- * @param {number} resolution The dataset resolution in geographical unit.
563
- * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
564
- * @param {object=} opts The parameters of the dataset and layer.
565
- * @returns {this}
566
- */
567
- /*addParquetGridLayer(url, resolution, styles, opts) {
568
- const ds = this.makeParquetGridDataset(url, resolution, opts)
569
- return this.addLayerFromDataset(ds, styles, opts);
570
- }*/
571
-
572
- /**
573
- *
574
- * @param {string} url
575
- * @param {Array.<import('./Style').Style>} styles
576
- * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string, preprocess?:function(import('./Dataset').Cell):boolean}} opts
577
- * @returns {this}
578
- */
579
- addTiledGridLayer(url, styles, opts) {
580
- const ds = this.makeTiledGridDataset(url, opts)
581
- return this.addLayerFromDataset(ds, styles, opts)
582
- }
583
-
584
- /**
585
- * Add a layer from a CSV grid dataset.
586
- *
587
- * @param {Array.<number>} resolutions
588
- * @param {function(number):string} resToURL
589
- * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
590
- * @param {object=} opts The parameters of the dataset and layer.
591
- * @returns {this}
592
- */
593
- addMultiScaleCSVGridLayer(resolutions, resToURL, styles, opts) {
594
- const ds = this.makeMultiScaleCSVGridDataset(resolutions, resToURL, opts)
595
- return this.addLayerFromDataset(ds, styles, opts)
596
- }
597
-
598
- /**
599
- * Add a layer from a parquet grid dataset.
600
- *
601
- * @param {Array.<number>} resolutions
602
- * @param {function(number):string} resToURL
603
- * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
604
- * @param {object=} opts The parameters of the dataset and layer.
605
- * @returns {this}
606
- */
607
- /*addMultiScaleParquetGridLayer(resolutions, resToURL, styles, opts) {
608
- const ds = this.makeMultiScaleParquetGridDataset(resolutions, resToURL, opts)
609
- return this.addLayerFromDataset(ds, styles, opts);
610
- }*/
611
-
612
- /**
613
- * @param {Array.<number>} resolutions
614
- * @param {function(number):string} resToURL
615
- * @param {Array.<import('./Style').Style>} styles
616
- * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string, preprocess?:function(import('./Dataset').Cell):boolean}} opts
617
- * @returns {this}
618
- */
619
- addMultiScaleTiledGridLayer(resolutions, resToURL, styles, opts) {
620
- const ds = this.makeMultiScaleTiledGridDataset(resolutions, resToURL, opts)
621
- return this.addLayerFromDataset(ds, styles, opts)
622
- }
623
-
624
- /**
625
- * Add a background layer to the app.
626
- *
627
- * @param {object} opts
628
- * @returns {this}
629
- */
630
- addBackgroundLayer(opts) {
631
- this.bgLayers.push(new BackgroundLayer(opts))
632
- this.redraw()
633
- return this
634
- }
635
-
636
- /**
637
- * Add a WMS background layer to the app.
638
- *
639
- * @param {object} opts
640
- * @returns {this}
641
- */
642
- addBackgroundLayerWMS(opts) {
643
- this.bgLayers.push(new BackgroundLayerWMS(opts))
644
- this.redraw()
645
- return this
646
- }
647
-
648
- /**
649
- *
650
- * @param {string} id
651
- * @param {object} opts
652
- * @returns {this}
653
- */
654
- addZoomSlider(id, opts) {
655
- this.cg.addZoomSlider(id, opts)
656
- return this
657
- }
658
-
659
- /** @returns {this} */
660
- setViewFromURL() {
661
- this.cg.setViewFromURL()
662
- return this
663
- }
664
-
665
- /**
666
- * @description Add a resize event observer to the Apps container and update the canvas accordingly
667
- * @param {HTMLDivElement} container The App's container element
668
- * @param {HTMLCanvasElement} canvas The App canvas element
669
- * @memberof App
670
- */
671
- defineResizeObserver(container, canvas) {
672
- // listen to resize events
673
- const resizeObserver = new ResizeObserver((entries) => {
674
- // make sure canvas has been built
675
- if (container.clientWidth > 0 && container.clientHeight > 0) {
676
- // make sure we dont exceed loop limit first
677
- // see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
678
- window.requestAnimationFrame(() => {
679
- if (!Array.isArray(entries) || !entries.length) {
680
- return
681
- }
682
- // update the app and canvas size
683
- if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
684
- this.h = container.clientHeight
685
- this.w = container.clientWidth
686
- this.cg.h = container.clientHeight
687
- this.cg.w = container.clientWidth
688
- canvas.setAttribute('width', '' + this.w)
689
- canvas.setAttribute('height', '' + this.h)
690
- this.redraw()
691
- }
692
- })
693
- }
694
- })
695
-
696
- resizeObserver.observe(container)
697
- }
698
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ // internal imports
5
+ import { GeoCanvas } from './GeoCanvas.js'
6
+ import { Layer } from './Layer.js'
7
+ import { Dataset } from './Dataset.js'
8
+ import { Tooltip } from './Tooltip.js'
9
+ import { CSVGrid } from './dataset/CSVGrid.js'
10
+ //import { ParquetGrid } from './dataset/ParquetGrid';
11
+ import { TiledGrid } from './dataset/TiledGrid.js'
12
+ import { BackgroundLayer } from './BackgroundLayer.js'
13
+ import { BackgroundLayerWMS } from './BackgroundLayerWMS.js'
14
+ import { LabelLayer } from './LabelLayer.js'
15
+ import { LineLayer } from './LineLayer.js'
16
+ import { monitor, monitorDuration } from './utils/Utils.js'
17
+
18
+ // external imports
19
+ import { select } from 'd3-selection'
20
+
21
+ /**
22
+ * A gridviz application.
23
+ *
24
+ * @author Joseph Davies, Julien Gaffuri
25
+ */
26
+ export class App {
27
+ /**
28
+ * @param {HTMLDivElement} container
29
+ * @param {object} opts
30
+ */
31
+ constructor(container, opts) {
32
+ opts = opts || {}
33
+
34
+ /**
35
+ * The layers.
36
+ * @type {Array.<Layer>}
37
+ * */
38
+ this.layers = []
39
+
40
+ //get container element
41
+ container = container || document.getElementById('gridviz')
42
+ if (!container) {
43
+ console.error('Cannot find gridviz container element.')
44
+ return
45
+ }
46
+
47
+ //set dimensions
48
+ /** @type {number} */
49
+ this.w = opts.w || container.offsetWidth
50
+ /** @type {number} */
51
+ this.h = opts.h || container.offsetHeight
52
+
53
+ //create canvas element if user doesnt specify one
54
+ /** @type {HTMLCanvasElement} */
55
+ let canvas = opts.canvas || null
56
+ if (!canvas) {
57
+ canvas = document.createElement('canvas')
58
+ canvas.setAttribute('width', '' + this.w)
59
+ canvas.setAttribute('height', '' + this.h)
60
+ container.appendChild(canvas)
61
+ }
62
+
63
+ /** Make geo canvas
64
+ * @type {GeoCanvas}
65
+ * @private */
66
+ this.cg = new GeoCanvas(canvas, undefined, 1, opts)
67
+ this.cg.redraw = (strong = true) => {
68
+ if (monitor) monitorDuration('Start redraw')
69
+ //console.log("?x=" + this.cg.getCenter().x + "&y=" + this.cg.getCenter().y + "&z=" + this.cg.getZf())
70
+
71
+ //remove legend elements
72
+ if (this.legend && strong) this.legend.selectAll('*').remove()
73
+
74
+ //clear
75
+ this.cg.initCanvasTransform()
76
+ this.cg.clear(this.cg.backgroundColor)
77
+
78
+ const zf = this.getZoomFactor()
79
+ this.updateExtentGeo()
80
+
81
+ //go through the background layers
82
+ for (const layer of this.bgLayers) {
83
+ //check if layer is visible
84
+ if (!layer.visible) continue
85
+ if (zf > layer.maxZoom) continue
86
+ if (zf < layer.minZoom) continue
87
+
88
+ //draw layer
89
+ layer.draw(this.cg)
90
+ }
91
+
92
+ //go through the layers
93
+ for (const layer of this.layers) {
94
+ //check if layer is visible
95
+ if (!layer.visible) continue
96
+ if (zf > layer.maxZoom) continue
97
+ if (zf < layer.minZoom) continue
98
+
99
+ //get layer dataset component
100
+ /** @type {import('./DatasetComponent').DatasetComponent|undefined} */
101
+ const dsc = layer.getDatasetComponent(zf)
102
+ if (!dsc) continue
103
+
104
+ //launch data download, if necessary
105
+ if (strong)
106
+ dsc.getData(this.cg.extGeo, () => {
107
+ this.cg.redraw()
108
+ })
109
+
110
+ //update dataset view cache
111
+ if (strong) dsc.updateViewCache(this.cg.extGeo)
112
+
113
+ //draw cells, style by style
114
+ if (strong)
115
+ for (const s of layer.styles) {
116
+ if (zf > s.maxZoom) continue
117
+ if (zf < s.minZoom) continue
118
+ s.draw(dsc.getViewCache(), dsc.getResolution(), this.cg)
119
+ }
120
+
121
+ //add legend element
122
+ if (this.legend && strong)
123
+ for (const s of layer.styles) {
124
+ if (zf > s.maxZoom) continue
125
+ if (zf < s.minZoom) continue
126
+ for (const lg of s.legends) {
127
+ //console.log(s, lg)
128
+ //this.legend.append(lg.div)
129
+ //s1.node().appendChild(s2.node())
130
+ this.legend.node().append(lg.div.node())
131
+ }
132
+
133
+ //case for styles of styles, like kernel smoothing
134
+ //TODO do better
135
+ if (s['styles']) {
136
+ for (const s2 of s.styles) {
137
+ if (zf > s2.maxZoom) continue
138
+ if (zf < s2.minZoom) continue
139
+ for (const lg of s2.legends) {
140
+ //console.log(s, lg)
141
+ //this.legend.append(lg.div)
142
+ //s1.node().appendChild(s2.node())
143
+ this.legend.node().append(lg.div.node())
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ //draw boundary layer
151
+ //if (strong)
152
+ if (this.showBoundaries && this.boundaryLayer) this.boundaryLayer.draw(this.cg)
153
+
154
+ //draw label layer
155
+ //if (strong)
156
+ if (this.showLabels && this.labelLayer) this.labelLayer.draw(this.cg)
157
+
158
+ //
159
+ this.canvasSave = null
160
+
161
+ if (monitor) monitorDuration('End redraw')
162
+
163
+ // listen for resize events on the App's container and handle them
164
+ this.defineResizeObserver(container, canvas)
165
+
166
+ return this
167
+ }
168
+
169
+ /** @type {Array.<BackgroundLayer|BackgroundLayerWMS>} */
170
+ this.bgLayers = []
171
+ /** @type {boolean} */
172
+ this.showBgLayers = true
173
+
174
+ /** @type {LabelLayer | undefined} */
175
+ this.labelLayer = undefined
176
+ /** @type {boolean} */
177
+ this.showLabels = true
178
+
179
+ /** @type {LineLayer | undefined} */
180
+ this.boundaryLayer = undefined
181
+ /** @type {boolean} */
182
+ this.showBoundaries = true
183
+
184
+ //legend div
185
+
186
+ this.legendDivId = opts.legendDivId || 'gvizLegend'
187
+ this.legend = select('#' + this.legendDivId)
188
+ if (this.legend.empty()) {
189
+ this.legend = select('#' + container.id)
190
+ .append('div')
191
+ .attr('id', this.legendDivId)
192
+ .style('position', 'absolute')
193
+ .style('width', 'auto')
194
+ .style('height', 'auto')
195
+ .style('background', '#FFFFFFCC')
196
+ //.style("padding", this.padding)
197
+ .style('border', '0px')
198
+ .style('border-radius', '5px')
199
+ .style('box-shadow', '3px 3px 3px grey, -3px -3px 3px #ddd')
200
+ .style('font-family', 'Helvetica, Arial, sans-serif')
201
+ .style('top', '20px')
202
+ .style('right', '20px')
203
+ //hide
204
+ //.style("visibility", "hidden")
205
+ }
206
+
207
+ //tooltip
208
+
209
+ // set App container as default parent element for tooltip
210
+ if (!opts.tooltip) opts.tooltip = {}
211
+ if (!opts.tooltip.parentElement) opts.tooltip.parentElement = container
212
+
213
+ /**
214
+ * @private
215
+ * @type {Tooltip} */
216
+ this.tooltip = new Tooltip(opts.tooltip)
217
+
218
+ /** @param {MouseEvent} e */
219
+ const focusCell = (e) => {
220
+ //compute mouse geo position
221
+ const mousePositionGeo = {
222
+ x: this.cg.pixToGeoX(e.offsetX + this.tooltip.xMouseOffset),
223
+ y: this.cg.pixToGeoY(e.offsetY + this.tooltip.yMouseOffset),
224
+ }
225
+ /** @type {{cell:import('./Dataset').Cell,html:string,resolution:number} | undefined} */
226
+ const focus = this.getCellFocusInfo(mousePositionGeo)
227
+
228
+ // transparent background (e.g. leaflet) 'red painting' fix
229
+ if (opts.transparentBackground) {
230
+ if (focus) {
231
+ this.tooltip.setPosition(e)
232
+ this.tooltip.show()
233
+ this.tooltip.html(focus.html)
234
+ } else {
235
+ this.tooltip.hide()
236
+ }
237
+ this.canvasSave = document.createElement('canvas')
238
+ this.canvasSave.setAttribute('width', '' + this.w)
239
+ this.canvasSave.setAttribute('height', '' + this.h)
240
+ this.canvasSave.getContext('2d').drawImage(this.cg.canvas, 0, 0)
241
+ this.cg.initCanvasTransform()
242
+ return
243
+ }
244
+
245
+ if (focus) {
246
+ this.tooltip.setPosition(e)
247
+ this.tooltip.show()
248
+ this.tooltip.html(focus.html)
249
+
250
+ //show cell position as a rectangle
251
+ if (!this.canvasSave) {
252
+ this.canvasSave = document.createElement('canvas')
253
+ this.canvasSave.setAttribute('width', '' + this.w)
254
+ this.canvasSave.setAttribute('height', '' + this.h)
255
+ this.canvasSave.getContext('2d').drawImage(this.cg.canvas, 0, 0)
256
+ } else {
257
+ this.cg.ctx.drawImage(this.canvasSave, 0, 0)
258
+ }
259
+
260
+ //draw image saved + draw rectangle
261
+ const rectWPix = this.selectionRectangleWidthPix
262
+ ? this.selectionRectangleWidthPix(focus.resolution, this.getZoomFactor())
263
+ : 4
264
+ this.cg.initCanvasTransform()
265
+ this.cg.ctx.strokeStyle = this.selectionRectangleColor
266
+ this.cg.ctx.lineWidth = rectWPix
267
+ this.cg.ctx.beginPath()
268
+
269
+ this.cg.ctx.rect(
270
+ this.cg.geoToPixX(focus.cell.x) - rectWPix / 2,
271
+ this.cg.geoToPixY(focus.cell.y) + rectWPix / 2,
272
+ focus.resolution / this.getZoomFactor() + rectWPix,
273
+ -focus.resolution / this.getZoomFactor() - rectWPix
274
+ )
275
+ this.cg.ctx.stroke()
276
+ } else {
277
+ this.tooltip.hide()
278
+ if (this.canvasSave) this.cg.ctx.drawImage(this.canvasSave, 0, 0)
279
+ }
280
+ }
281
+ container.addEventListener('mouseover', (e) => {
282
+ focusCell(e)
283
+ })
284
+ container.addEventListener('mousemove', (e) => {
285
+ focusCell(e)
286
+ })
287
+ container.addEventListener('mouseout', () => {
288
+ this.tooltip.hide()
289
+ })
290
+
291
+ // add extra logic to onZoomStartFun
292
+ this.cg.onZoomStartFun = (e) => {
293
+ if (opts.onZoomStartFun) opts.onZoomStartFun(e)
294
+ this.tooltip.hide()
295
+ }
296
+
297
+ //for mouse over
298
+ /**
299
+ * @private
300
+ * @type {HTMLCanvasElement|null} */
301
+ this.canvasSave = null
302
+
303
+ this.selectionRectangleColor = opts.selectionRectangleColor || 'red'
304
+ this.selectionRectangleWidthPix = opts.selectionRectangleWidthPix || (() => 4) //(r,zf) => {}
305
+
306
+ //
307
+ //canvas.addEventListener("keydown", e => { console.log(arguments) });
308
+ }
309
+
310
+ /**
311
+ * @param {number} marginPx
312
+ * @returns {import('./Dataset').Envelope}
313
+ * @public
314
+ */
315
+ updateExtentGeo(marginPx = 20) {
316
+ return this.cg.updateExtentGeo(marginPx)
317
+ }
318
+
319
+ /**
320
+ * Return the cell HTML info at a given geo position.
321
+ * This is usefull for user interactions, to show this info where the user clicks for example.
322
+ *
323
+ * @param {{x:number,y:number}} posGeo
324
+ * @returns {{cell:import('./Dataset').Cell,html:string,resolution:number} | undefined}
325
+ * @protected
326
+ */
327
+ getCellFocusInfo(posGeo) {
328
+ //go through the layers, starting from top
329
+ const zf = this.getZoomFactor()
330
+ for (let i = this.layers.length - 1; i >= 0; i--) {
331
+ /** @type {Layer} */
332
+ const layer = this.layers[i]
333
+ if (!layer.visible) continue
334
+ if (!layer.cellInfoHTML) continue
335
+ if (layer.cellInfoHTML === 'none') continue
336
+ const dsc = layer.getDatasetComponent(zf)
337
+ if (!dsc) continue
338
+
339
+ //get cell at mouse position
340
+ /** @type {import('./Dataset').Cell|undefined} */
341
+ const cell = dsc.getCellFromPosition(posGeo, dsc.getViewCache())
342
+ if (!cell) return undefined
343
+ const html = layer.cellInfoHTML(cell, dsc.getResolution())
344
+ if (!html) return undefined
345
+ return { cell: cell, html: html, resolution: dsc.getResolution() }
346
+ }
347
+ }
348
+
349
+ //getters and setters
350
+
351
+ /** @returns {{x:number,y:number}} */
352
+ getGeoCenter() {
353
+ return this.cg.getCenter()
354
+ }
355
+ /** @param {{x:number,y:number}} val @returns {this} */
356
+ setGeoCenter(val) {
357
+ this.cg.setCenter(val)
358
+ return this
359
+ }
360
+
361
+ /** @returns {number} */
362
+ getZoomFactor() {
363
+ return this.cg.getZf()
364
+ }
365
+ /** @param {number} val @returns {this} */
366
+ setZoomFactor(val) {
367
+ this.cg.setZf(val)
368
+ return this
369
+ }
370
+
371
+ /** @returns {Array.<number>} */
372
+ getZoomFactorExtent() {
373
+ return this.cg.getZfExtent()
374
+ }
375
+ /** @param {Array.<number>} val @returns {this} */
376
+ setZoomFactorExtent(val) {
377
+ this.cg.setZfExtent(val)
378
+ return this
379
+ }
380
+
381
+ /** @returns {string} */
382
+ getBackgroundColor() {
383
+ return this.cg.backgroundColor
384
+ }
385
+ /** @param {string} val @returns {this} */
386
+ setBackgroundColor(val) {
387
+ this.cg.backgroundColor = val
388
+ return this
389
+ }
390
+
391
+ /** @returns {LineLayer | undefined} */
392
+ getBoundaryLayer() {
393
+ return this.boundaryLayer
394
+ }
395
+ /** @param {object} opts @returns {this} */
396
+ setBoundaryLayer(opts) {
397
+ this.boundaryLayer = new LineLayer(opts)
398
+ return this
399
+ }
400
+
401
+ /** @returns {LabelLayer | undefined} */
402
+ getLabelLayer() {
403
+ return this.labelLayer
404
+ }
405
+ /** @param {object} opts @returns {this} */
406
+ setLabelLayer(opts) {
407
+ this.labelLayer = new LabelLayer(opts)
408
+ return this
409
+ }
410
+
411
+ /** @returns {this} */
412
+ redraw() {
413
+ this.cg.redraw()
414
+ return this
415
+ }
416
+
417
+ /**
418
+ * Add a layer to the app.
419
+ *
420
+ * @param {Dataset} dataset The dataset of the layer
421
+ * @param {Array.<import('./Style').Style>} styles The styles of the layer
422
+ * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string}} opts The layer options.
423
+ * @returns {this}
424
+ */
425
+ addLayerFromDataset(dataset, styles, opts) {
426
+ const lay = new Layer(dataset, styles, opts)
427
+ this.layers.push(lay)
428
+ return this
429
+ }
430
+
431
+ //dataset creation
432
+
433
+ /**
434
+ * Make a CSV grid dataset.
435
+ *
436
+ * @param {string} url The URL of the dataset.
437
+ * @param {number} resolution The dataset resolution in geographical unit.
438
+ * @param {object=} opts The parameters of the dataset.
439
+ * @returns {Dataset}
440
+ */
441
+ makeCSVGridDataset(url, resolution, opts) {
442
+ return new Dataset(
443
+ [
444
+ new CSVGrid(url, resolution, opts).getData(undefined, () => {
445
+ this.cg.redraw()
446
+ }),
447
+ ],
448
+ [],
449
+ opts
450
+ )
451
+ }
452
+
453
+ /**
454
+ * Make a parquet grid dataset.
455
+ *
456
+ * @param {string} url The URL of the dataset.
457
+ * @param {number} resolution The dataset resolution in geographical unit.
458
+ * @param {object=} opts The parameters of the dataset.
459
+ * @returns {Dataset}
460
+ */
461
+ /*makeParquetGridDataset(url, resolution, opts) {
462
+ return new Dataset([new ParquetGrid(url, resolution, opts).getData(undefined, () => { this.cg.redraw(); })], [], opts)
463
+ }*/
464
+
465
+ /**
466
+ * Make a tiled grid dataset.
467
+ *
468
+ * @param {string} url
469
+ * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
470
+ * @returns {Dataset}
471
+ */
472
+ makeTiledGridDataset(url, opts) {
473
+ return new Dataset(
474
+ [
475
+ new TiledGrid(url, this, opts).loadInfo(() => {
476
+ this.cg.redraw()
477
+ }),
478
+ ],
479
+ [],
480
+ opts
481
+ )
482
+ }
483
+
484
+ //multi scale dataset creation
485
+
486
+ /**
487
+ * Make a multi scale parquet grid dataset.
488
+ *
489
+ * @param {Array.<number>} resolutions
490
+ * @param {function(number):string} resToURL
491
+ * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
492
+ * @returns {Dataset}
493
+ */
494
+ /*makeMultiScaleParquetGridDataset(resolutions, resToURL, opts) {
495
+ return Dataset.make(
496
+ resolutions,
497
+ (res) => new ParquetGrid(resToURL(res), res, opts).getData(undefined, () => { this.cg.redraw(); }),
498
+ opts
499
+ )
500
+ }*/
501
+
502
+ /**
503
+ * Make a multi scale CSV grid dataset.
504
+ *
505
+ * @param {Array.<number>} resolutions
506
+ * @param {function(number):string} resToURL
507
+ * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
508
+ * @returns {Dataset}
509
+ */
510
+ makeMultiScaleCSVGridDataset(resolutions, resToURL, opts) {
511
+ return Dataset.make(
512
+ resolutions,
513
+ (res) =>
514
+ new CSVGrid(resToURL(res), res, opts).getData(undefined, () => {
515
+ this.cg.redraw()
516
+ }),
517
+ opts
518
+ )
519
+ }
520
+
521
+ //tiled multiscale
522
+
523
+ /**
524
+ * Make a multi scale tiled grid dataset.
525
+ *
526
+ * @param {Array.<number>} resolutions
527
+ * @param {function(number):string} resToURL
528
+ * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
529
+ * @returns {Dataset}
530
+ */
531
+ makeMultiScaleTiledGridDataset(resolutions, resToURL, opts) {
532
+ return Dataset.make(
533
+ resolutions,
534
+ (res) =>
535
+ new TiledGrid(resToURL(res), this, opts).loadInfo(() => {
536
+ this.cg.redraw()
537
+ }),
538
+ opts
539
+ )
540
+ }
541
+
542
+ // direct layer creation
543
+
544
+ /**
545
+ * Add a layer from a CSV grid dataset.
546
+ *
547
+ * @param {string} url The URL of the dataset.
548
+ * @param {number} resolution The dataset resolution in geographical unit.
549
+ * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
550
+ * @param {object=} opts The parameters of the dataset and layer.
551
+ * @returns {this}
552
+ */
553
+ addCSVGridLayer(url, resolution, styles, opts) {
554
+ const ds = this.makeCSVGridDataset(url, resolution, opts)
555
+ return this.addLayerFromDataset(ds, styles, opts)
556
+ }
557
+
558
+ /**
559
+ * Add a layer from a parquet grid dataset.
560
+ *
561
+ * @param {string} url The URL of the dataset.
562
+ * @param {number} resolution The dataset resolution in geographical unit.
563
+ * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
564
+ * @param {object=} opts The parameters of the dataset and layer.
565
+ * @returns {this}
566
+ */
567
+ /*addParquetGridLayer(url, resolution, styles, opts) {
568
+ const ds = this.makeParquetGridDataset(url, resolution, opts)
569
+ return this.addLayerFromDataset(ds, styles, opts);
570
+ }*/
571
+
572
+ /**
573
+ *
574
+ * @param {string} url
575
+ * @param {Array.<import('./Style').Style>} styles
576
+ * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string, preprocess?:function(import('./Dataset').Cell):boolean}} opts
577
+ * @returns {this}
578
+ */
579
+ addTiledGridLayer(url, styles, opts) {
580
+ const ds = this.makeTiledGridDataset(url, opts)
581
+ return this.addLayerFromDataset(ds, styles, opts)
582
+ }
583
+
584
+ /**
585
+ * Add a layer from a CSV grid dataset.
586
+ *
587
+ * @param {Array.<number>} resolutions
588
+ * @param {function(number):string} resToURL
589
+ * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
590
+ * @param {object=} opts The parameters of the dataset and layer.
591
+ * @returns {this}
592
+ */
593
+ addMultiScaleCSVGridLayer(resolutions, resToURL, styles, opts) {
594
+ const ds = this.makeMultiScaleCSVGridDataset(resolutions, resToURL, opts)
595
+ return this.addLayerFromDataset(ds, styles, opts)
596
+ }
597
+
598
+ /**
599
+ * Add a layer from a parquet grid dataset.
600
+ *
601
+ * @param {Array.<number>} resolutions
602
+ * @param {function(number):string} resToURL
603
+ * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
604
+ * @param {object=} opts The parameters of the dataset and layer.
605
+ * @returns {this}
606
+ */
607
+ /*addMultiScaleParquetGridLayer(resolutions, resToURL, styles, opts) {
608
+ const ds = this.makeMultiScaleParquetGridDataset(resolutions, resToURL, opts)
609
+ return this.addLayerFromDataset(ds, styles, opts);
610
+ }*/
611
+
612
+ /**
613
+ * @param {Array.<number>} resolutions
614
+ * @param {function(number):string} resToURL
615
+ * @param {Array.<import('./Style').Style>} styles
616
+ * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string, preprocess?:function(import('./Dataset').Cell):boolean}} opts
617
+ * @returns {this}
618
+ */
619
+ addMultiScaleTiledGridLayer(resolutions, resToURL, styles, opts) {
620
+ const ds = this.makeMultiScaleTiledGridDataset(resolutions, resToURL, opts)
621
+ return this.addLayerFromDataset(ds, styles, opts)
622
+ }
623
+
624
+ /**
625
+ * Add a background layer to the app.
626
+ *
627
+ * @param {object} opts
628
+ * @returns {this}
629
+ */
630
+ addBackgroundLayer(opts) {
631
+ this.bgLayers.push(new BackgroundLayer(opts))
632
+ this.redraw()
633
+ return this
634
+ }
635
+
636
+ /**
637
+ * Add a WMS background layer to the app.
638
+ *
639
+ * @param {object} opts
640
+ * @returns {this}
641
+ */
642
+ addBackgroundLayerWMS(opts) {
643
+ this.bgLayers.push(new BackgroundLayerWMS(opts))
644
+ this.redraw()
645
+ return this
646
+ }
647
+
648
+ /**
649
+ *
650
+ * @param {string} id
651
+ * @param {object} opts
652
+ * @returns {this}
653
+ */
654
+ addZoomSlider(id, opts) {
655
+ this.cg.addZoomSlider(id, opts)
656
+ return this
657
+ }
658
+
659
+ /** @returns {this} */
660
+ setViewFromURL() {
661
+ this.cg.setViewFromURL()
662
+ return this
663
+ }
664
+
665
+ /**
666
+ * @description Add a resize event observer to the Apps container and update the canvas accordingly
667
+ * @param {HTMLDivElement} container The App's container element
668
+ * @param {HTMLCanvasElement} canvas The App canvas element
669
+ * @memberof App
670
+ */
671
+ defineResizeObserver(container, canvas) {
672
+ // listen to resize events
673
+ const resizeObserver = new ResizeObserver((entries) => {
674
+ // make sure canvas has been built
675
+ if (container.clientWidth > 0 && container.clientHeight > 0) {
676
+ // make sure we dont exceed loop limit first
677
+ // see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
678
+ window.requestAnimationFrame(() => {
679
+ if (!Array.isArray(entries) || !entries.length) {
680
+ return
681
+ }
682
+ // update the app and canvas size
683
+ if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
684
+ this.h = container.clientHeight
685
+ this.w = container.clientWidth
686
+ this.cg.h = container.clientHeight
687
+ this.cg.w = container.clientWidth
688
+ canvas.setAttribute('width', '' + this.w)
689
+ canvas.setAttribute('height', '' + this.h)
690
+ this.redraw()
691
+ }
692
+ })
693
+ }
694
+ })
695
+
696
+ resizeObserver.observe(container)
697
+ }
698
+ }