gridviz 2.1.16 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/gridviz.js +4971 -5654
- 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 -12
- 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 +460 -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 -10
- 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 +140 -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 +202 -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
|
@@ -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
|
}
|
package/src/core/Map.js
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
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
|
+
/** @returns {this} */
|
|
140
|
+
redraw() {
|
|
141
|
+
//remove legend elements
|
|
142
|
+
if (this.legend) this.legend.selectAll('*').remove()
|
|
143
|
+
|
|
144
|
+
//clear
|
|
145
|
+
this.geoCanvas.initCanvasTransform()
|
|
146
|
+
this.geoCanvas.clear(this.geoCanvas.backgroundColor)
|
|
147
|
+
|
|
148
|
+
const z = this.geoCanvas.view.z
|
|
149
|
+
this.updateExtentGeo()
|
|
150
|
+
|
|
151
|
+
//go through the layers
|
|
152
|
+
for (const layer of this.layers) {
|
|
153
|
+
|
|
154
|
+
//check if layer is visible
|
|
155
|
+
if (layer.visible && !layer.visible(z)) continue
|
|
156
|
+
|
|
157
|
+
//set layer alpha and blend mode
|
|
158
|
+
this.geoCanvas.ctx.globalAlpha = layer.alpha ? layer.alpha(z) : 1.0
|
|
159
|
+
this.geoCanvas.ctx.globalCompositeOperation = layer.blendOperation(z)
|
|
160
|
+
|
|
161
|
+
//set affin transform to draw with geographical coordinates
|
|
162
|
+
this.geoCanvas.setCanvasTransform()
|
|
163
|
+
|
|
164
|
+
//draw layer
|
|
165
|
+
layer.draw(this.geoCanvas, this.legend)
|
|
166
|
+
|
|
167
|
+
//draw layer filter
|
|
168
|
+
if (layer.filterColor)
|
|
169
|
+
layer.drawFilter(this.geoCanvas)
|
|
170
|
+
|
|
171
|
+
//restore default alpha and blend operation
|
|
172
|
+
this.geoCanvas.ctx.globalAlpha = 1.0
|
|
173
|
+
this.geoCanvas.ctx.globalCompositeOperation = this.defaultGlobalCompositeOperation
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//
|
|
177
|
+
this.canvasSave = null
|
|
178
|
+
|
|
179
|
+
// listen for resize events on the App's container and handle them
|
|
180
|
+
this.defineResizeObserver(this.container, this._canvas)
|
|
181
|
+
|
|
182
|
+
return this
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {number} marginPx
|
|
188
|
+
* @returns {import('./GeoCanvas.js').Envelope}
|
|
189
|
+
* @public
|
|
190
|
+
*/
|
|
191
|
+
updateExtentGeo(marginPx = 20) {
|
|
192
|
+
return this.geoCanvas.updateExtentGeo(marginPx)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
/** @param {MouseEvent} e */
|
|
198
|
+
focusCell(e) {
|
|
199
|
+
//compute mouse geo position
|
|
200
|
+
const mousePositionGeo = {
|
|
201
|
+
x: this.geoCanvas.pixToGeoX(e.offsetX + this.tooltip.xMouseOffset),
|
|
202
|
+
y: this.geoCanvas.pixToGeoY(e.offsetY + this.tooltip.yMouseOffset),
|
|
203
|
+
}
|
|
204
|
+
/** @type {{cell:import('./Dataset.js').Cell,html:string,resolution:number} | undefined} */
|
|
205
|
+
const focus = this.getCellFocusInfo(mousePositionGeo)
|
|
206
|
+
|
|
207
|
+
// transparent background (e.g. leaflet) 'red painting' fix
|
|
208
|
+
if (this.transparentBackground) {
|
|
209
|
+
if (focus) {
|
|
210
|
+
this.tooltip.html(focus.html)
|
|
211
|
+
this.tooltip.setPosition(e)
|
|
212
|
+
this.tooltip.show()
|
|
213
|
+
} else {
|
|
214
|
+
this.tooltip.hide()
|
|
215
|
+
}
|
|
216
|
+
this.canvasSave = document.createElement('canvas')
|
|
217
|
+
this.canvasSave.setAttribute('width', '' + this.w)
|
|
218
|
+
this.canvasSave.setAttribute('height', '' + this.h)
|
|
219
|
+
this.canvasSave.getContext('2d')?.drawImage(this.geoCanvas.canvas, 0, 0)
|
|
220
|
+
this.geoCanvas.initCanvasTransform()
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (focus) {
|
|
225
|
+
this.tooltip.html(focus.html)
|
|
226
|
+
this.tooltip.setPosition(e)
|
|
227
|
+
this.tooltip.show()
|
|
228
|
+
|
|
229
|
+
//show cell position as a rectangle
|
|
230
|
+
if (!this.canvasSave) {
|
|
231
|
+
this.canvasSave = document.createElement('canvas')
|
|
232
|
+
this.canvasSave.setAttribute('width', '' + this.w)
|
|
233
|
+
this.canvasSave.setAttribute('height', '' + this.h)
|
|
234
|
+
this.canvasSave.getContext('2d')?.drawImage(this.geoCanvas.canvas, 0, 0)
|
|
235
|
+
} else {
|
|
236
|
+
this.geoCanvas.ctx.drawImage(this.canvasSave, 0, 0)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
//draw image saved + draw rectangle
|
|
240
|
+
const rectWPix = this.selectionRectangleWidthPix
|
|
241
|
+
? this.selectionRectangleWidthPix(focus.resolution, this.geoCanvas.view.z)
|
|
242
|
+
: 4
|
|
243
|
+
this.geoCanvas.initCanvasTransform()
|
|
244
|
+
this.geoCanvas.ctx.strokeStyle = this.selectionRectangleColor
|
|
245
|
+
this.geoCanvas.ctx.lineWidth = rectWPix
|
|
246
|
+
this.geoCanvas.ctx.beginPath()
|
|
247
|
+
|
|
248
|
+
this.geoCanvas.ctx.rect(
|
|
249
|
+
this.geoCanvas.geoToPixX(focus.cell.x) - rectWPix / 2,
|
|
250
|
+
this.geoCanvas.geoToPixY(focus.cell.y) + rectWPix / 2,
|
|
251
|
+
focus.resolution / this.geoCanvas.view.z + rectWPix,
|
|
252
|
+
-focus.resolution / this.geoCanvas.view.z - rectWPix
|
|
253
|
+
)
|
|
254
|
+
this.geoCanvas.ctx.stroke()
|
|
255
|
+
} else {
|
|
256
|
+
this.tooltip.hide()
|
|
257
|
+
if (this.canvasSave) this.geoCanvas.ctx.drawImage(this.canvasSave, 0, 0)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Return the cell HTML info at a given geo position.
|
|
264
|
+
* This is usefull for user interactions, to show this info where the user clicks for example.
|
|
265
|
+
*
|
|
266
|
+
* @param {{x:number,y:number}} posGeo
|
|
267
|
+
* @returns {{cell:import('./Dataset.js').Cell,html:string,resolution:number} | undefined}
|
|
268
|
+
* @protected
|
|
269
|
+
*/
|
|
270
|
+
getCellFocusInfo(posGeo) {
|
|
271
|
+
//go through the layers, starting from top
|
|
272
|
+
const z = this.geoCanvas.view.z
|
|
273
|
+
for (let i = this.layers.length - 1; i >= 0; i--) {
|
|
274
|
+
/** @type {import("./Layer.js").Layer} */
|
|
275
|
+
const layer = this.layers[i]
|
|
276
|
+
if (layer.visible && !layer.visible(z)) continue
|
|
277
|
+
if (!layer.cellInfoHTML) continue
|
|
278
|
+
//if (layer.cellInfoHTML === 'none') continue
|
|
279
|
+
if (!layer.getDataset) continue
|
|
280
|
+
const dsc = layer.getDataset(z)
|
|
281
|
+
if (!dsc) continue
|
|
282
|
+
|
|
283
|
+
//get cell at mouse position
|
|
284
|
+
/** @type {import('./Dataset.js').Cell|undefined} */
|
|
285
|
+
const cell = dsc.getCellFromPosition(posGeo, dsc.getViewCache())
|
|
286
|
+
//console.log(cell, dsc.resolution)
|
|
287
|
+
if (!cell) return undefined
|
|
288
|
+
const html = layer.cellInfoHTML(cell, dsc.getResolution())
|
|
289
|
+
if (!html) return undefined
|
|
290
|
+
return { cell: cell, html: html, resolution: dsc.getResolution() }
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @param {number} x
|
|
297
|
+
* @param {number} y
|
|
298
|
+
* @param {number|undefined} z
|
|
299
|
+
*/
|
|
300
|
+
setView(x, y, z = undefined) {
|
|
301
|
+
this.geoCanvas.view.x = x
|
|
302
|
+
this.geoCanvas.view.y = y
|
|
303
|
+
if (z != undefined) this.geoCanvas.view.z = z
|
|
304
|
+
return this
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** @returns {import('./GeoCanvas.js').View} */
|
|
308
|
+
getView() { return this.geoCanvas.view }
|
|
309
|
+
|
|
310
|
+
/** @returns {number} */
|
|
311
|
+
getZoom() {
|
|
312
|
+
return this.geoCanvas.view.z
|
|
313
|
+
}
|
|
314
|
+
/** @param {number} z @returns {this} */
|
|
315
|
+
setZoom(z) {
|
|
316
|
+
this.geoCanvas.view.z = z
|
|
317
|
+
return this
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
/** @returns {Array.<number>} */
|
|
322
|
+
getZoomExtent() {
|
|
323
|
+
return this.geoCanvas.getZoomExtent()
|
|
324
|
+
}
|
|
325
|
+
/** @param {Array.<number>} val @returns {this} */
|
|
326
|
+
setZoomExtent(val) {
|
|
327
|
+
this.geoCanvas.setZoomExtent(val)
|
|
328
|
+
return this
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
/** @returns {string} */
|
|
334
|
+
getBackgroundColor() {
|
|
335
|
+
return this.geoCanvas.backgroundColor
|
|
336
|
+
}
|
|
337
|
+
/** @param {string} val @returns {this} */
|
|
338
|
+
setBackgroundColor(val) {
|
|
339
|
+
this.geoCanvas.backgroundColor = val
|
|
340
|
+
return this
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Adds a set of zoom buttons to the map
|
|
345
|
+
*
|
|
346
|
+
* @param {object} opts
|
|
347
|
+
* @returns {this}
|
|
348
|
+
*/
|
|
349
|
+
addZoomButtons(opts) {
|
|
350
|
+
// * opts.id
|
|
351
|
+
// * opts.onZoom - custom event handler function
|
|
352
|
+
// * opts.x
|
|
353
|
+
// * opts.y
|
|
354
|
+
// * opts.delta - zoom delta applied on each click
|
|
355
|
+
|
|
356
|
+
this.zoomButtons = new ZoomButtons({
|
|
357
|
+
map: this,
|
|
358
|
+
id: opts?.id || 'gridviz-zoom-buttons',
|
|
359
|
+
class: opts?.class,
|
|
360
|
+
x: opts?.x,
|
|
361
|
+
y: opts?.y,
|
|
362
|
+
onZoom: opts?.onZoom,
|
|
363
|
+
delta: opts?.delta || 0.2
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
return this
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Adds a fullscreen toggle button to the app
|
|
371
|
+
*
|
|
372
|
+
* @param {object} opts
|
|
373
|
+
* @returns {this}
|
|
374
|
+
*/
|
|
375
|
+
addFullscreenButton(opts) {
|
|
376
|
+
// * opts.map - the gridviz map
|
|
377
|
+
// * opts.id
|
|
378
|
+
// * opts.x
|
|
379
|
+
// * opts.y
|
|
380
|
+
|
|
381
|
+
this.fullscreenButton = new FullscreenButton({
|
|
382
|
+
map: this,
|
|
383
|
+
id: opts?.id || 'gridviz-fullscreen-button',
|
|
384
|
+
class: opts?.class,
|
|
385
|
+
x: opts?.x,
|
|
386
|
+
y: opts?.y
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
return this
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** @returns {this} */
|
|
393
|
+
setViewFromURL() {
|
|
394
|
+
this.geoCanvas.setViewFromURL()
|
|
395
|
+
return this
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @description Add a resize event observer to the Apps container and update the canvas accordingly
|
|
400
|
+
* @param {HTMLDivElement} container The App's container element
|
|
401
|
+
* @param {HTMLCanvasElement} canvas The App canvas element
|
|
402
|
+
* @memberof App
|
|
403
|
+
*/
|
|
404
|
+
defineResizeObserver(container, canvas) {
|
|
405
|
+
// listen to resize events
|
|
406
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
407
|
+
// make sure canvas has been built
|
|
408
|
+
if (container.clientWidth > 0 && container.clientHeight > 0) {
|
|
409
|
+
// make sure we dont exceed loop limit first
|
|
410
|
+
// see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
|
|
411
|
+
window.requestAnimationFrame(() => {
|
|
412
|
+
if (!Array.isArray(entries) || !entries.length) {
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
// update the map and canvas size
|
|
416
|
+
if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
|
|
417
|
+
this.h = container.clientHeight
|
|
418
|
+
this.w = container.clientWidth
|
|
419
|
+
this.geoCanvas.h = container.clientHeight
|
|
420
|
+
this.geoCanvas.w = container.clientWidth
|
|
421
|
+
canvas.setAttribute('width', '' + this.w)
|
|
422
|
+
canvas.setAttribute('height', '' + this.h)
|
|
423
|
+
this.redraw()
|
|
424
|
+
|
|
425
|
+
//update button positions
|
|
426
|
+
// if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px'
|
|
427
|
+
// if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px'
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
resizeObserver.observe(container)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* @description Destroy the map and it's event listeners
|
|
438
|
+
* This should significantly reduce the memory used when creating and destroying gridviz map instances (for example in leaflet-gridviz)
|
|
439
|
+
* @memberof App
|
|
440
|
+
*/
|
|
441
|
+
destroy() {
|
|
442
|
+
// clear layers
|
|
443
|
+
this.layers = []
|
|
444
|
+
this.bgLayers = []
|
|
445
|
+
|
|
446
|
+
// remove event listeners from container
|
|
447
|
+
this.container.removeEventListener('mouseover', this.mouseOverHandler)
|
|
448
|
+
this.container.removeEventListener('mousemove', this.mouseMoveHandler)
|
|
449
|
+
this.container.removeEventListener('mouseout', this.mouseOutHandler)
|
|
450
|
+
|
|
451
|
+
// remove canvas
|
|
452
|
+
this.geoCanvas.canvas.remove()
|
|
453
|
+
|
|
454
|
+
// remove legend
|
|
455
|
+
this.legend?.remove()
|
|
456
|
+
|
|
457
|
+
// remove tooltip
|
|
458
|
+
this.tooltip.tooltip?.remove()
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -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
|
+
}
|