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