gridviz 2.0.28 → 2.0.31

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 (57) hide show
  1. package/dist/gridviz.js +34368 -0
  2. package/dist/gridviz.min.js +1 -1
  3. package/package.json +10 -6
  4. package/src/App.js +654 -0
  5. package/src/BackgroundLayer.js +182 -0
  6. package/src/BackgroundLayerWMS.js +131 -0
  7. package/src/Dataset.js +76 -0
  8. package/src/DatasetComponent.js +103 -0
  9. package/src/GeoCanvas.js +419 -0
  10. package/src/LabelLayer.js +190 -0
  11. package/src/Layer.js +92 -0
  12. package/src/Legend.js +86 -0
  13. package/src/LineLayer.js +150 -0
  14. package/src/Style.js +114 -0
  15. package/src/Tooltip.js +150 -0
  16. package/src/dataset/COGeo.js +24 -0
  17. package/src/dataset/CSVGrid.js +104 -0
  18. package/src/dataset/GeoTIFF.js +127 -0
  19. package/src/dataset/GridTile.js +41 -0
  20. package/src/dataset/ParquetGrid.js +122 -0
  21. package/src/dataset/TiledGrid.js +296 -0
  22. package/src/index.js +177 -0
  23. package/src/legend/ColorCategoryLegend.js +124 -0
  24. package/src/legend/ColorDiscreteLegend.js +105 -0
  25. package/src/legend/ColorLegend.js +125 -0
  26. package/src/legend/SegmentOrientationLegend.js +87 -0
  27. package/src/legend/SegmentWidthLegend.js +97 -0
  28. package/src/legend/SizeLegend.js +141 -0
  29. package/src/style/CompositionStyle.js +300 -0
  30. package/src/style/ContourStyle.js +54 -0
  31. package/src/style/DotDensityStyle.js +156 -0
  32. package/src/style/JoyPlotStyle.js +135 -0
  33. package/src/style/KernelSmoothingOldStyle.js +221 -0
  34. package/src/style/KernelSmoothingStyle.js +115 -0
  35. package/src/style/KernelSmoothingWGLStyle.js +130 -0
  36. package/src/style/LegoStyle.js +112 -0
  37. package/src/style/MosaicStyle.js +107 -0
  38. package/src/style/NinjaStarStyle.js +124 -0
  39. package/src/style/PillarStyle.js +230 -0
  40. package/src/style/SegmentStyle.js +142 -0
  41. package/src/style/ShapeColorSizeStyle.js +126 -0
  42. package/src/style/SideCatStyle.js +172 -0
  43. package/src/style/SideStyle.js +218 -0
  44. package/src/style/SquareColorCatWGLStyle.js +120 -0
  45. package/src/style/SquareColorWGLStyle.js +146 -0
  46. package/src/style/StrokeStyle.js +131 -0
  47. package/src/style/TanakaStyle.js +160 -0
  48. package/src/style/TextStyle.js +132 -0
  49. package/src/utils/Utils.js +35 -0
  50. package/src/utils/WebGLRectangleColoring.js +106 -0
  51. package/src/utils/WebGLSquareColoring.js +118 -0
  52. package/src/utils/WebGLSquareColoringAdvanced.js +230 -0
  53. package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -0
  54. package/src/utils/WebGLSquareColoringKS.js +153 -0
  55. package/src/utils/stretching.js +131 -0
  56. package/src/utils/webGLUtils.js +71 -0
  57. package/src/utils/zoomUtils.js +142 -0
@@ -0,0 +1,419 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /** @typedef { {xMin: number, xMax: number, yMin: number, yMax: number} } Envelope */
5
+
6
+ import { select } from 'd3-selection'
7
+ import { zoom, zoomIdentity } from 'd3-zoom'
8
+
9
+ /**
10
+ * A HTML canvas for geo data display, enhanced with zoom and pan capabilities.
11
+ *
12
+ * @author Joseph Davies, Julien Gaffuri
13
+ */
14
+ export class GeoCanvas {
15
+ /**
16
+ * @constructor
17
+ * @param {HTMLCanvasElement} canvas
18
+ * @param {object} center Geographical coordinates of the center
19
+ * @param {number} zf The zoom factor (pixel size, in ground m)
20
+ * @param {object} opts
21
+ */
22
+ constructor(canvas, center, zf, opts) {
23
+ this.opts = opts || {}
24
+
25
+ /** @type {HTMLCanvasElement} */
26
+ this.canvas = canvas
27
+
28
+ /** @type {number} */
29
+ this.w = this.canvas.offsetWidth
30
+ /** @type {number} */
31
+ this.h = this.canvas.offsetHeight
32
+
33
+ this.canvas.width = this.w
34
+ this.canvas.height = this.h
35
+
36
+ const ctx = this.canvas.getContext('2d')
37
+ if (!ctx) throw 'Impossible to create canvas 2D context'
38
+ /**@type {CanvasRenderingContext2D} */
39
+ this.ctx = ctx
40
+
41
+ // set geo coordinates of the center
42
+ this.center = center || { x: this.w * 0.5, y: this.h * 0.5 }
43
+
44
+ // zoom factor: pixel size, in m/pix
45
+ /** @type {number} */
46
+ this.zf = zf
47
+
48
+ /** Background color.
49
+ * @type {string} */
50
+ this.backgroundColor = opts.backgroundColor || 'white'
51
+
52
+ /** @type {function():void} */
53
+ this.onZoomStartFun = opts.onZoomStartFun
54
+
55
+ /** @type {function():void} */
56
+ this.onZoomEndFun = opts.onZoomEndFun
57
+
58
+ /** @type {function():void} */
59
+ this.onZoomFun = opts.onZoomFun
60
+
61
+ //current extent
62
+ /** @type {Envelope} */
63
+ this.extGeo = { xMin: NaN, xMax: NaN, yMin: NaN, yMax: NaN }
64
+ this.updateExtentGeo()
65
+
66
+ //rely on d3 zoom for pan/zoom
67
+ if (!opts.disableZoom) {
68
+ let tP = zoomIdentity
69
+ const z = zoom()
70
+ //to make the zooming a bit faster
71
+ .wheelDelta((e) => -e.deltaY * (e.deltaMode === 1 ? 0.07 : e.deltaMode ? 1 : 0.004))
72
+ .on('zoom', (e) => {
73
+ const t = e.transform
74
+ const f = tP.k / t.k
75
+ if (f == 1) {
76
+ //pan
77
+ const dx = tP.x - t.x
78
+ const dy = tP.y - t.y
79
+ this.pan(dx * this.getZf(), -dy * this.getZf())
80
+ } else {
81
+ const se = e.sourceEvent
82
+ if (se instanceof WheelEvent) {
83
+ //zoom at the mouse position
84
+ this.zoom(
85
+ f,
86
+ this.pixToGeoX(e.sourceEvent.offsetX),
87
+ this.pixToGeoY(e.sourceEvent.offsetY)
88
+ )
89
+ } else if (se instanceof TouchEvent) {
90
+ //compute average position of the touches
91
+ let tx = 0,
92
+ ty = 0
93
+ for (let tt of se.targetTouches) {
94
+ tx += tt.clientX
95
+ ty += tt.clientY
96
+ }
97
+ tx /= se.targetTouches.length
98
+ ty /= se.targetTouches.length
99
+ //zoom at this average position
100
+ this.zoom(f, this.pixToGeoX(tx), this.pixToGeoY(ty))
101
+ }
102
+ }
103
+ tP = t
104
+
105
+ if (this.onZoomFun) this.onZoomFun(e)
106
+ })
107
+ .on('start', (e) => {
108
+ this.canvasSave.c = document.createElement('canvas')
109
+ this.canvasSave.c.setAttribute('width', '' + this.w)
110
+ this.canvasSave.c.setAttribute('height', '' + this.h)
111
+ this.canvasSave.c.getContext('2d').drawImage(this.canvas, 0, 0)
112
+ this.canvasSave.dx = 0
113
+ this.canvasSave.dy = 0
114
+ this.canvasSave.f = 1
115
+
116
+ if (this.onZoomStartFun) this.onZoomStartFun(e)
117
+ })
118
+ .on('end', (e) => {
119
+ this.redraw(true)
120
+ this.canvasSave = { c: null, dx: 0, dy: 0, f: 1 }
121
+
122
+ if (this.onZoomEndFun) this.onZoomEndFun(e)
123
+ })
124
+ z(select(this.canvas))
125
+ }
126
+ //select(this.canvas).call(z);
127
+
128
+ /** Zoom extent, to limit zoom in and out
129
+ * @type {Array.<number>} */
130
+ this.zfExtent = [0, Infinity]
131
+
132
+ /** Canvas state, to be used to avoid unnecessary redraws on zoom/pan
133
+ * @type {{c:HTMLCanvasElement|null,dx:number,dy:number,f:number}} */
134
+ this.canvasSave = { c: null, dx: 0, dy: 0, f: 1 }
135
+ }
136
+
137
+ /** @param {{x:number,y:number}} v Geographical coordinates of the center */
138
+ setCenter(v) {
139
+ this.center = v
140
+ }
141
+ /** @returns {{x:number,y:number}} Geographical coordinates of the center */
142
+ getCenter() {
143
+ return this.center
144
+ }
145
+
146
+ /** @param {number} v The zoom factor (pixel size, in ground m) */
147
+ setZf(v) {
148
+ this.zf = v
149
+ if (this.slider) this.slider.attr('value', +this.zf)
150
+ }
151
+ /** @returns {number} The zoom factor (pixel size, in ground m) */
152
+ getZf() {
153
+ return this.zf
154
+ }
155
+
156
+ /** @param {Array.<number>} v */
157
+ setZfExtent(v) {
158
+ this.zfExtent = v
159
+ }
160
+ /** @returns {Array.<number>} */
161
+ getZfExtent() {
162
+ return this.zfExtent
163
+ }
164
+
165
+ /** Initialise canvas transform with identity transformation. */
166
+ initCanvasTransform() {
167
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0)
168
+ }
169
+
170
+ /** Initialise canvas transform with geo to screen transformation, so that geo objects can be drawn directly in geo coordinates. */
171
+ setCanvasTransform() {
172
+ const k = 1 / this.getZf()
173
+ const tx = -this.center.x / this.getZf() + this.w * 0.5
174
+ const ty = this.center.y / this.getZf() + this.h * 0.5
175
+ this.ctx.setTransform(k, 0, 0, -k, tx, ty)
176
+ }
177
+
178
+ /** Get the transformation matrix to webGL screen coordinates, within [-1,1]*[-1,1] */
179
+ getWebGLTransform() {
180
+ const kx = 2.0 / (this.w * this.getZf())
181
+ const ky = 2.0 / (this.h * this.getZf())
182
+ return [kx, 0.0, 0.0, 0.0, ky, 0.0, -kx * this.center.x, -ky * this.center.y, 1.0]
183
+ }
184
+
185
+ /** The function specifying how to draw the map.
186
+ * @param {boolean} strong */
187
+ redraw(strong = true) {
188
+ throw new Error('Method redraw not implemented.')
189
+ }
190
+
191
+ /**
192
+ * Clear. To be used before a redraw for example.
193
+ * @param {string} color
194
+ */
195
+ clear(color = 'white') {
196
+ if (this.opts.transparentBackground) {
197
+ this.ctx.clearRect(0, 0, this.w, this.h)
198
+ } else {
199
+ if (this.ctx) this.ctx.fillStyle = color
200
+ this.ctx.fillRect(0, 0, this.w, this.h)
201
+ }
202
+ }
203
+
204
+ /**
205
+ * @param {number} dxGeo
206
+ * @param {number} dyGeo
207
+ */
208
+ pan(dxGeo = 0, dyGeo = 0) {
209
+ //TODO force extend to remain
210
+ this.center.x += dxGeo
211
+ this.center.y += dyGeo
212
+ this.updateExtentGeo()
213
+
214
+ if (this.canvasSave.c) {
215
+ this.canvasSave.dx -= dxGeo / this.getZf()
216
+ this.canvasSave.dy += dyGeo / this.getZf()
217
+ this.clear(this.backgroundColor)
218
+ this.ctx.drawImage(this.canvasSave.c, this.canvasSave.dx, this.canvasSave.dy)
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Zoom.
224
+ * @param {number} f The zoom factor, within ]0, Infinity]. 1 is for no change. <1 to zoom-in, >1 to zoom-out.
225
+ * @param {number} xGeo The x geo position fixed in the screen.
226
+ * @param {number} yGeo The y geo position fixed in the screen.
227
+ */
228
+ zoom(f = 1, xGeo = this.center.x, yGeo = this.center.y) {
229
+ //TODO force geo extend to remain
230
+
231
+ //trying to zoom in/out beyond limit
232
+ if (this.zfExtent[0] == this.getZf() && f <= 1) return
233
+ if (this.zfExtent[1] == this.getZf() && f >= 1) return
234
+
235
+ //ensure zoom extent preserved
236
+ const newZf = f * this.getZf()
237
+ if (newZf < this.zfExtent[0]) f = this.zfExtent[0] / this.getZf()
238
+ if (newZf > this.zfExtent[1]) f = this.zfExtent[1] / this.getZf()
239
+
240
+ this.setZf(f * this.getZf())
241
+ const dxGeo = (xGeo - this.center.x) * (1 - f)
242
+ this.center.x += dxGeo
243
+ const dyGeo = (yGeo - this.center.y) * (1 - f)
244
+ this.center.y += dyGeo
245
+ this.updateExtentGeo()
246
+
247
+ //TODO
248
+ //this.redraw(false)
249
+ if (this.canvasSave.c) {
250
+ this.clear(this.backgroundColor)
251
+ this.canvasSave.f /= f
252
+ this.canvasSave.dx = this.geoToPixX(xGeo) * (1 - this.canvasSave.f)
253
+ this.canvasSave.dy = this.geoToPixY(yGeo) * (1 - this.canvasSave.f)
254
+ this.clear(this.backgroundColor)
255
+ this.ctx.drawImage(
256
+ this.canvasSave.c,
257
+ this.canvasSave.dx,
258
+ this.canvasSave.dy,
259
+ this.canvasSave.f * this.canvasSave.c.width,
260
+ this.canvasSave.f * this.canvasSave.c.height
261
+ )
262
+ }
263
+ }
264
+
265
+ /**
266
+ * @param {number} marginPx
267
+ * @returns {Envelope} The envelope of the view, in geo coordinates.
268
+ */
269
+ updateExtentGeo(marginPx = 20) {
270
+ this.extGeo = {
271
+ xMin: this.pixToGeoX(-marginPx),
272
+ xMax: this.pixToGeoX(this.w + marginPx),
273
+ yMin: this.pixToGeoY(this.h + marginPx),
274
+ yMax: this.pixToGeoY(-marginPx),
275
+ }
276
+ return this.extGeo
277
+ }
278
+
279
+ /**
280
+ * Check if the object has to be drawn
281
+ *
282
+ * @param {{x:number,y:number}} obj
283
+ */
284
+ toDraw(obj) {
285
+ if (obj.x < this.extGeo.xMin) return false
286
+ if (obj.x > this.extGeo.xMax) return false
287
+ if (obj.y < this.extGeo.yMin) return false
288
+ if (obj.y > this.extGeo.yMax) return false
289
+ return true
290
+ }
291
+
292
+ //conversion functions
293
+ /**
294
+ * @param {number} xGeo Geo x coordinate, in m.
295
+ * @returns {number} Screen x coordinate, in pix.
296
+ */
297
+ geoToPixX(xGeo) {
298
+ return (xGeo - this.center.x) / this.getZf() + this.w * 0.5
299
+ }
300
+ /**
301
+ * @param {number} yGeo Geo y coordinate, in m.
302
+ * @returns {number} Screen y coordinate, in pix.
303
+ */
304
+ geoToPixY(yGeo) {
305
+ return -(yGeo - this.center.y) / this.getZf() + this.h * 0.5
306
+ }
307
+ /**
308
+ * @param {number} x Screen x coordinate, in pix.
309
+ * @returns {number} Geo x coordinate, in m.
310
+ */
311
+ pixToGeoX(x) {
312
+ return (x - this.w * 0.5) * this.getZf() + this.center.x
313
+ }
314
+ /**
315
+ * @param {number} y Screen y coordinate, in pix.
316
+ * @returns {number} Geo y coordinate, in m.
317
+ */
318
+ pixToGeoY(y) {
319
+ return -(y - this.h * 0.5) * this.getZf() + this.center.y
320
+ }
321
+
322
+ /** Get x,y,z elements from URL and assign them to the view center and zoom level. */
323
+ setViewFromURL() {
324
+ const x = GeoCanvas.getParameterByName('x'),
325
+ y = GeoCanvas.getParameterByName('y'),
326
+ z = GeoCanvas.getParameterByName('z')
327
+ const c = this.getCenter()
328
+ if (x != null && x != undefined && !isNaN(+x)) c.x = +x
329
+ if (y != null && y != undefined && !isNaN(+y)) c.y = +y
330
+ if (z != null && z != undefined && !isNaN(+z)) this.setZf(+z)
331
+ }
332
+
333
+ /**
334
+ *
335
+ * @param {string} id
336
+ * @param {object} opts
337
+ * @returns {this}
338
+ */
339
+ addZoomSlider(id, opts) {
340
+ opts = opts || {}
341
+ opts.width = opts.width || '30px'
342
+ opts.height = opts.height || '300px'
343
+
344
+ //the div element
345
+ const div = select('#' + id)
346
+ if (div.empty()) {
347
+ console.error('Could not find div element to build zoom slider. Id: ' + id)
348
+ return this
349
+ }
350
+
351
+ const th = this
352
+ /** */
353
+ this.slider = div
354
+ .append('input')
355
+ .attr('type', 'range')
356
+ .attr('min', this.getZfExtent()[0])
357
+ .attr('max', this.getZfExtent()[1])
358
+ .attr('value', this.getZf())
359
+ .on('input', function (d) {
360
+ if (!this || !this.value) return
361
+ const v = +this.value
362
+ select(this).attr('value', v)
363
+ th.setZf(v)
364
+ //redraw
365
+ th.redraw()
366
+ })
367
+ .style('width', opts.width)
368
+ .style('height', opts.height)
369
+ .style('opacity', 0.7)
370
+ .on('mouseover', function (d) {
371
+ select(this).style('opacity', 1)
372
+ })
373
+ .on('mouseout', function (d) {
374
+ select(this).style('opacity', 0.7)
375
+ })
376
+ .style('-webkit-appearance', 'slider-vertical') //for chrome
377
+ .style('writing-mode', 'bt-lr') //for IE/edge
378
+ .attr('orient', 'vertical') //for firefox
379
+ .style('background', 'lightgray')
380
+ .style('outline', 'none')
381
+ .style('-webkit-transition', '.2s')
382
+ .style('transition', 'opacity .2s')
383
+
384
+ //TODO
385
+ /*select(".slider::-webkit-slider-thumb")
386
+ .style("-webkit-appearance", "none")
387
+ .style("appearance", "none")
388
+ .style("width", "30px")
389
+ .style("height", "40px")
390
+ .style("background", "black")
391
+ .style("cursor", "pointer")*/
392
+
393
+ /*select("slider::-moz-range-thumb")
394
+ .style("-webkit-appearance", "none")
395
+ .style("width", "30px")
396
+ .style("height", "40px")
397
+ .style("background", "#04AA6D")
398
+ .style("cursor", "pointer")*/
399
+ /*
400
+ .slider::ms-thumb,
401
+ .slider::-moz-range-thumb {
402
+ }*/
403
+
404
+ return this
405
+ }
406
+
407
+ /**
408
+ * Get a URL parameter by name.
409
+ *
410
+ * @param {string} name
411
+ * @returns {string | null}
412
+ */
413
+ static getParameterByName(name) {
414
+ name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]')
415
+ var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'),
416
+ results = regex.exec(location.search)
417
+ return !results ? null : decodeURIComponent(results[1].replace(/\+/g, ' '))
418
+ }
419
+ }
@@ -0,0 +1,190 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { csv } from 'd3-fetch'
5
+
6
+ /** A label. The name is the text to show. (x,y) are the coordinates in the same CRS as the grid.
7
+ * @typedef {{name: string, x:number, y:number }} Label */
8
+
9
+ /**
10
+ * A (generic) layer for placename labels, to be shown on top of the grid layers.
11
+ * The input is a CSV file with the position (x, y) of the labels and name + some other info on the label importance.
12
+ * If the label data is not in the expected format or in the same CRS as the grid, it can be corrected with the "preprocess" function.
13
+ * The selection of the label, their style (font, weight, etc.) and color can be specified depending on their importance and the zoom level.
14
+ *
15
+ * @author Joseph Davies, Julien Gaffuri
16
+ */
17
+ export class LabelLayer {
18
+ /**
19
+ * @param {object} opts
20
+ */
21
+ constructor(opts) {
22
+ opts = opts || {}
23
+
24
+ /**
25
+ * The URL of the label data, as CSV file.
26
+ * The file should contain the information for each label such as the text, the position and other information for the display of the label according to the zoom level.
27
+ * If necessary, this data can be reformated with the 'preprocess' parameter.
28
+ * @private
29
+ * @type {string} */
30
+ this.url = opts.url
31
+
32
+ /** Specify if and how a label should be drawn, depending on its importance and the zoom level.
33
+ * @private
34
+ * @type {function(Label,number):string} */
35
+ this.style = opts.style || (() => 'bold 1em Arial')
36
+
37
+ /** Specify the label color, depending on its importance and the zoom level.
38
+ * @private
39
+ * @type {function(Label,number):string} */
40
+ this.color = opts.color || (opts.dark ? () => '#ddd' : () => '#222')
41
+
42
+ /** Specify the label halo color, depending on its importance and the zoom level.
43
+ * @private
44
+ * @type {function(Label,number):string} */
45
+ this.haloColor = opts.haloColor || (opts.dark ? () => '#000000BB' : () => '#FFFFFFBB')
46
+
47
+ /** Specify the label halo width, depending on its importance and the zoom level.
48
+ * @private
49
+ * @type {function(Label,number):number} */
50
+ this.haloWidth = opts.haloWidth || (() => 4)
51
+
52
+ /** The anchor where to draw the text, from label position. See HTML-canvas textAlign property.
53
+ * "left" || "right" || "center" || "start" || "end"
54
+ * @private
55
+ * @type {CanvasTextAlign} */
56
+ this.textAlign = opts.textAlign || 'start'
57
+
58
+ /**
59
+ * @private
60
+ * @type {Array.<number>} */
61
+ this.offsetPix = opts.offsetPix || [5, 5]
62
+
63
+ /**
64
+ * A preprocess to run on each label after loading.
65
+ * It can be used to apply some specific treatment before, format the label data, project coordinates, etc.
66
+ * Return false if the label should not be kept.
67
+ * @private
68
+ * @type {function(Label):boolean} */
69
+ this.preprocess = opts.preprocess
70
+
71
+ /**
72
+ * @private
73
+ * @type {Array.<Label> | undefined} */
74
+ this.labels = undefined
75
+
76
+ /**
77
+ * @private
78
+ * @type {string} */
79
+ this.loadingStatus = 'notLoaded'
80
+ }
81
+
82
+ /**
83
+ * Draw the label layer.
84
+ *
85
+ * @param {import("./GeoCanvas").GeoCanvas} cg The canvas where to draw the layer.
86
+ * @returns {void}
87
+ */
88
+ draw(cg) {
89
+ //load labels, if not done yet.
90
+ if (!this.labels) {
91
+ this.load(cg.redraw)
92
+ return
93
+ }
94
+
95
+ //zoom factor
96
+ const zf = cg.getZf()
97
+
98
+ //text align
99
+ cg.ctx.textAlign = this.textAlign || 'start'
100
+
101
+ //line join and cap
102
+ cg.ctx.lineJoin = 'bevel' //|| "round" || "miter";
103
+ cg.ctx.lineCap = 'butt' //|| "round" || "square";
104
+
105
+ //draw in pix coordinates
106
+ cg.initCanvasTransform()
107
+
108
+ //draw labels, one by one
109
+ for (const lb of this.labels) {
110
+ //get label style
111
+ const st = this.style(lb, zf)
112
+ if (!st) continue
113
+ cg.ctx.font = st
114
+
115
+ //check label within the view, to be drawn
116
+ if (!cg.toDraw(lb)) continue
117
+
118
+ //position
119
+ const xP = cg.geoToPixX(lb.x) + this.offsetPix[0]
120
+ const yP = cg.geoToPixY(lb.y) - this.offsetPix[1]
121
+
122
+ //label stroke, for the halo
123
+ if (this.haloColor && this.haloWidth) {
124
+ const hc = this.haloColor(lb, zf)
125
+ const hw = this.haloWidth(lb, zf)
126
+ if (hc && hw && hw > 0) {
127
+ cg.ctx.strokeStyle = hc
128
+ cg.ctx.lineWidth = hw
129
+ cg.ctx.strokeText(lb.name, xP, yP)
130
+ }
131
+ }
132
+
133
+ //label fill
134
+ if (this.color) {
135
+ const col = this.color(lb, zf)
136
+ if (col) {
137
+ cg.ctx.fillStyle = col
138
+ cg.ctx.fillText(lb.name, xP, yP)
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Load data for labels, from URL this.url
146
+ * @param {function():void} callback
147
+ * @private
148
+ */
149
+ async load(callback) {
150
+ if (!this.url) {
151
+ console.log('Failed loading labels: No URL specified. ' + this.url)
152
+ this.loadingStatus = 'failed'
153
+ this.labels = []
154
+ return
155
+ }
156
+
157
+ //check if data already loaded
158
+ if (this.loadingStatus != 'notLoaded') return
159
+
160
+ //load data
161
+ this.loadingStatus = 'loading'
162
+
163
+ try {
164
+ /** @type { Array.<Label> } */
165
+ const data = await csv(this.url)
166
+
167
+ //preprocess/filter
168
+ if (this.preprocess) {
169
+ this.labels = []
170
+ for (const c of data) {
171
+ const b = this.preprocess(c)
172
+ if (b == false) continue
173
+ this.labels.push(c)
174
+ }
175
+ } else {
176
+ //store labels
177
+ this.labels = data
178
+ }
179
+
180
+ this.loadingStatus = 'loaded'
181
+
182
+ //redraw
183
+ if (callback) callback()
184
+ } catch (error) {
185
+ console.log('Failed loading labels from ' + this.url)
186
+ this.labels = []
187
+ this.loadingStatus = 'failed'
188
+ }
189
+ }
190
+ }
package/src/Layer.js ADDED
@@ -0,0 +1,92 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /**
5
+ * A layer, which specifies a dataset to be shown with specified styles.
6
+ *
7
+ * @author Joseph Davies, Julien Gaffuri
8
+ */
9
+ export class Layer {
10
+ /**
11
+ * @param {import("./Dataset").Dataset} dataset The multi resolution dataset to show.
12
+ * @param {Array.<import("./Style").Style>} styles The styles, ordered in drawing order.
13
+ * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import("./Dataset").Cell):string}} opts
14
+ * minZoom: The minimum zoom level when to show the layer. maxZoom: The maximum zoom level when to show the layer
15
+ */
16
+ constructor(dataset, styles, opts = {}) {
17
+ opts = opts || {}
18
+
19
+ /** @type {import("./Dataset").Dataset} */
20
+ this.dataset = dataset
21
+ /** @type {Array.<import("./Style").Style>} */
22
+ this.styles = styles
23
+
24
+ /** An attribute to specify if a layer should be drawn or not
25
+ * @type {boolean} */
26
+ this.visible = opts.visible == false ? false : true
27
+
28
+ /** The minimum zoom factor: Below this level, the layer is not shown.
29
+ * @type {number} */
30
+ this.minZoom = opts.minZoom || 0
31
+
32
+ /** The maximum zoom factor: Above this level, the layer is not shown.
33
+ * @type {number} */
34
+ this.maxZoom = opts.maxZoom || Infinity
35
+
36
+ //ensure acceptable values for the zoom limits.
37
+ if (this.minZoom >= this.maxZoom)
38
+ throw new Error('Unexpected zoom limits for layer. Zoom min should be smaller than zoom max.')
39
+
40
+ /** Unit: number of pixels
41
+ * @type {number} */
42
+ this.pixNb = opts.pixNb || 3
43
+
44
+ /**
45
+ * The function returning cell information as HTML.
46
+ * This is typically used for tooltip information.
47
+ * @type {function(import("./Dataset").Cell, number):string} */
48
+ this.cellInfoHTML = opts.cellInfoHTML || Layer.defaultCellInfoHTML
49
+ }
50
+
51
+ /**
52
+ * Return the relevant dataset component for a specified zoom factor.
53
+ *
54
+ * @param {number} zf
55
+ * @returns {import("./DatasetComponent").DatasetComponent|undefined}
56
+ * */
57
+ getDatasetComponent(zf) {
58
+ if (zf < this.minZoom || zf > this.maxZoom) return
59
+
60
+ //special case whith single component dataset
61
+ if (this.dataset.datasetComponents.length == 1) return this.dataset.datasetComponents[0]
62
+
63
+ const rs = this.dataset.resolutions
64
+ let i = 0
65
+ let z = rs[i] / this.pixNb
66
+ while (z < zf && i < rs.length) {
67
+ i++
68
+ z = rs[i] / this.pixNb
69
+ }
70
+ //if (i == 0) return this.dataset.datasetComponents[0];
71
+ //return this.dataset.datasetComponents[i - 1];
72
+ if (i == rs.length) return this.dataset.datasetComponents[rs.length - 1]
73
+ return this.dataset.datasetComponents[i]
74
+ }
75
+
76
+ /**
77
+ * The default function returning cell information as HTML.
78
+ * This is typically used for tooltip information.
79
+ *
80
+ * @param {import("./Dataset").Cell} cell
81
+ * @returns {string}
82
+ */
83
+ static defaultCellInfoHTML(cell) {
84
+ const buf = []
85
+ for (const key of Object.keys(cell)) {
86
+ if (key === 'x') continue
87
+ if (key === 'y') continue
88
+ buf.push('<b>', key, '</b>', ' : ', cell[key], '<br>')
89
+ }
90
+ return buf.join('')
91
+ }
92
+ }