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/GeoCanvas.js CHANGED
@@ -1,419 +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
- }
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
+ }