gridviz 3.0.35 → 3.0.37

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 (67) hide show
  1. package/README.md +129 -129
  2. package/dist/gridviz.js +21056 -17487
  3. package/dist/gridviz.min.js +2 -2
  4. package/package.json +74 -74
  5. package/src/button/Button.js +67 -67
  6. package/src/button/FullscreenButton.js +121 -121
  7. package/src/button/ZoomButtons.js +103 -103
  8. package/src/core/Dataset.js +232 -154
  9. package/src/core/Drawable.js +78 -78
  10. package/src/core/GeoCanvas.js +443 -443
  11. package/src/core/Layer.js +23 -23
  12. package/src/core/Legend.js +108 -108
  13. package/src/core/Map.js +650 -568
  14. package/src/core/MultiResolutionDataset.js +93 -93
  15. package/src/core/Style.js +98 -98
  16. package/src/core/Tooltip.js +182 -182
  17. package/src/dataset/COGeo.js +25 -25
  18. package/src/dataset/CSVGrid.js +112 -112
  19. package/src/dataset/GeoTIFF.js +122 -122
  20. package/src/dataset/JSGrid.js +55 -55
  21. package/src/dataset/TiledGrid.js +311 -302
  22. package/src/dataset/worker.js +18 -18
  23. package/src/index.js +79 -79
  24. package/src/layer/BackgroundLayer.js +173 -173
  25. package/src/layer/BackgroundLayerImage.js +81 -81
  26. package/src/layer/BackgroundLayerWMS.js +104 -104
  27. package/src/layer/GeoJSONLayer.js +276 -276
  28. package/src/layer/GridLayer.js +166 -166
  29. package/src/layer/LabelLayer.js +194 -194
  30. package/src/legend/ColorCategoryLegend.js +109 -109
  31. package/src/legend/ColorDiscreteLegend.js +100 -100
  32. package/src/legend/ColorLegend.js +115 -115
  33. package/src/legend/OrientationLegend.js +89 -89
  34. package/src/legend/SizeLegend.js +224 -224
  35. package/src/legend/TernaryLegend.js +302 -302
  36. package/src/style/CompositionStyle.js +291 -291
  37. package/src/style/DotDensityStyle.js +133 -133
  38. package/src/style/ImageStyle.js +100 -100
  39. package/src/style/Interpolator.js +258 -258
  40. package/src/style/IsoFenceStyle.js +217 -217
  41. package/src/style/JoyPlotStyle.js +180 -180
  42. package/src/style/LegoStyle.js +132 -132
  43. package/src/style/MosaicStyle.js +99 -99
  44. package/src/style/NinjaStarStyle.js +102 -102
  45. package/src/style/PillarStyle.js +207 -207
  46. package/src/style/SegmentStyle.js +105 -105
  47. package/src/style/ShadingRayStyle.js +280 -280
  48. package/src/style/ShadingStyle.js +72 -72
  49. package/src/style/ShapeColorSizeStyle.js +147 -147
  50. package/src/style/SideCategoryStyle.js +112 -112
  51. package/src/style/SideStyle.js +217 -217
  52. package/src/style/SideTanakaStyle.js +154 -154
  53. package/src/style/SquareColorCategoryWebGLStyle.js +311 -294
  54. package/src/style/SquareColorWebGLStyle.js +126 -126
  55. package/src/style/StrokeStyle.js +135 -135
  56. package/src/style/TextStyle.js +110 -110
  57. package/src/style/TimeSeriesStyle.js +222 -222
  58. package/src/utils/WebGLRectangleColoring.js +106 -106
  59. package/src/utils/WebGLSquareColoring.js +114 -118
  60. package/src/utils/WebGLSquareColoringAdvanced.js +223 -227
  61. package/src/utils/WebGLSquareColoringKS.js +153 -153
  62. package/src/utils/scale.js +255 -255
  63. package/src/utils/stretching.js +120 -120
  64. package/src/utils/ternary.js +208 -208
  65. package/src/utils/utils.js +125 -125
  66. package/src/utils/webGLUtils.js +178 -73
  67. package/src/utils/zoomUtils.js +142 -142
@@ -1,103 +1,103 @@
1
- import { Button } from './Button.js'
2
-
3
- /**
4
- * Button for toggling fullscreen mode
5
- *
6
- * @module button
7
- * @author Joseph Davies, Julien Gaffuri
8
- */
9
- export class ZoomButtons extends Button {
10
- /**
11
- * @param {Object} opts
12
- */
13
- constructor(opts) {
14
- super(opts)
15
-
16
- this.onZoom = opts.onZoom // custom user event handler
17
- this.delta = opts.delta || 0.2
18
-
19
- // Create zoom in button
20
- this.zoomInBtn = document.createElement('a')
21
- this.zoomInBtn.id = 'zoom-in'
22
- this.zoomInBtn.className = 'gridviz-zoom-button'
23
- this.zoomInBtn.title = 'Zoom in'
24
- this.zoomInBtn.textContent = '+'
25
- this.zoomInBtn.addEventListener('click', (e) => {
26
- this.zoomIn(e)
27
- })
28
- this.zoomInBtn.addEventListener('mouseover', () => {
29
- this.zoomInBtn.style.backgroundColor = 'lightgrey'
30
- })
31
- this.zoomInBtn.addEventListener('mouseout', () => {
32
- this.zoomInBtn.style.backgroundColor = '#ffffff'
33
- })
34
-
35
- // Create zoom out button
36
- this.zoomOutBtn = document.createElement('a')
37
- this.zoomOutBtn.id = 'zoom-out'
38
- this.zoomOutBtn.className = 'gridviz-zoom-button'
39
- this.zoomOutBtn.title = 'Zoom out'
40
- this.zoomOutBtn.textContent = '-'
41
- this.zoomOutBtn.addEventListener('click', (e) => {
42
- this.zoomOut(e)
43
- })
44
- this.zoomOutBtn.addEventListener('mouseover', () => {
45
- this.zoomOutBtn.style.backgroundColor = 'lightgrey'
46
- })
47
- this.zoomOutBtn.addEventListener('mouseout', () => {
48
- this.zoomOutBtn.style.backgroundColor = '#ffffff'
49
- })
50
-
51
- // Set common styles for buttons
52
- const buttons = [this.zoomInBtn, this.zoomOutBtn]
53
- buttons.forEach((btn, index) => {
54
- btn.style.alignItems = 'center'
55
- btn.style.justifyContent = 'center'
56
- btn.style.display = 'flex'
57
- btn.style.border = 'none'
58
- btn.style.color = 'black'
59
- btn.style.textAlign = 'center'
60
- btn.style.textDecoration = 'none'
61
- btn.style.padding = '4px'
62
- btn.style.fontSize = '24px'
63
- btn.style.fontWeight = 'bold'
64
- btn.style.userSelect = 'none'
65
- btn.style.backgroundColor = '#ffffff'
66
- if (index === 0) btn.style.borderBottom = '1px solid grey' // Zoom in button only
67
- })
68
-
69
- // Unset parent class height and display for dual buttons
70
- this.style('height', 'unset')
71
- this.style('display', 'unset')
72
-
73
- // Set position
74
- if (opts.x) {
75
- this.style('left', opts.x + 'px')
76
- } else {
77
- this.style('right', '10px')
78
- }
79
- if (opts.y) {
80
- this.style('top', opts.y + 'px')
81
- } else {
82
- this.style('top', '10px')
83
- }
84
-
85
- // Append buttons to the container
86
- this.div.node().appendChild(this.zoomInBtn)
87
- this.div.node().appendChild(this.zoomOutBtn)
88
- }
89
-
90
- /* Zoom in */
91
- zoomIn(e) {
92
- this.map.setZoom(this.map.getZoom() * (1 - this.delta)).redraw()
93
- if (this.onZoom) this.onZoom(e)
94
- if (this.map.geoCanvas.onZoomFun) this.map.geoCanvas.onZoomFun(e)
95
- }
96
-
97
- /* Zoom out */
98
- zoomOut(e) {
99
- this.map.setZoom(this.map.getZoom() * (1 + this.delta)).redraw()
100
- if (this.onZoom) this.onZoom(e)
101
- if (this.map.geoCanvas.onZoomFun) this.map.geoCanvas.onZoomFun(e)
102
- }
103
- }
1
+ import { Button } from './Button.js'
2
+
3
+ /**
4
+ * Button for toggling fullscreen mode
5
+ *
6
+ * @module button
7
+ * @author Joseph Davies, Julien Gaffuri
8
+ */
9
+ export class ZoomButtons extends Button {
10
+ /**
11
+ * @param {Object} opts
12
+ */
13
+ constructor(opts) {
14
+ super(opts)
15
+
16
+ this.onZoom = opts.onZoom // custom user event handler
17
+ this.delta = opts.delta || 0.2
18
+
19
+ // Create zoom in button
20
+ this.zoomInBtn = document.createElement('a')
21
+ this.zoomInBtn.id = 'zoom-in'
22
+ this.zoomInBtn.className = 'gridviz-zoom-button'
23
+ this.zoomInBtn.title = 'Zoom in'
24
+ this.zoomInBtn.textContent = '+'
25
+ this.zoomInBtn.addEventListener('click', (e) => {
26
+ this.zoomIn(e)
27
+ })
28
+ this.zoomInBtn.addEventListener('mouseover', () => {
29
+ this.zoomInBtn.style.backgroundColor = 'lightgrey'
30
+ })
31
+ this.zoomInBtn.addEventListener('mouseout', () => {
32
+ this.zoomInBtn.style.backgroundColor = '#ffffff'
33
+ })
34
+
35
+ // Create zoom out button
36
+ this.zoomOutBtn = document.createElement('a')
37
+ this.zoomOutBtn.id = 'zoom-out'
38
+ this.zoomOutBtn.className = 'gridviz-zoom-button'
39
+ this.zoomOutBtn.title = 'Zoom out'
40
+ this.zoomOutBtn.textContent = '-'
41
+ this.zoomOutBtn.addEventListener('click', (e) => {
42
+ this.zoomOut(e)
43
+ })
44
+ this.zoomOutBtn.addEventListener('mouseover', () => {
45
+ this.zoomOutBtn.style.backgroundColor = 'lightgrey'
46
+ })
47
+ this.zoomOutBtn.addEventListener('mouseout', () => {
48
+ this.zoomOutBtn.style.backgroundColor = '#ffffff'
49
+ })
50
+
51
+ // Set common styles for buttons
52
+ const buttons = [this.zoomInBtn, this.zoomOutBtn]
53
+ buttons.forEach((btn, index) => {
54
+ btn.style.alignItems = 'center'
55
+ btn.style.justifyContent = 'center'
56
+ btn.style.display = 'flex'
57
+ btn.style.border = 'none'
58
+ btn.style.color = 'black'
59
+ btn.style.textAlign = 'center'
60
+ btn.style.textDecoration = 'none'
61
+ btn.style.padding = '4px'
62
+ btn.style.fontSize = '24px'
63
+ btn.style.fontWeight = 'bold'
64
+ btn.style.userSelect = 'none'
65
+ btn.style.backgroundColor = '#ffffff'
66
+ if (index === 0) btn.style.borderBottom = '1px solid grey' // Zoom in button only
67
+ })
68
+
69
+ // Unset parent class height and display for dual buttons
70
+ this.style('height', 'unset')
71
+ this.style('display', 'unset')
72
+
73
+ // Set position
74
+ if (opts.x) {
75
+ this.style('left', opts.x + 'px')
76
+ } else {
77
+ this.style('right', '10px')
78
+ }
79
+ if (opts.y) {
80
+ this.style('top', opts.y + 'px')
81
+ } else {
82
+ this.style('top', '10px')
83
+ }
84
+
85
+ // Append buttons to the container
86
+ this.div.node().appendChild(this.zoomInBtn)
87
+ this.div.node().appendChild(this.zoomOutBtn)
88
+ }
89
+
90
+ /* Zoom in */
91
+ zoomIn(e) {
92
+ this.map.setZoom(this.map.getZoom() * (1 - this.delta)).redraw()
93
+ if (this.onZoom) this.onZoom(e)
94
+ if (this.map.geoCanvas.onZoomFun) this.map.geoCanvas.onZoomFun(e)
95
+ }
96
+
97
+ /* Zoom out */
98
+ zoomOut(e) {
99
+ this.map.setZoom(this.map.getZoom() * (1 + this.delta)).redraw()
100
+ if (this.onZoom) this.onZoom(e)
101
+ if (this.map.geoCanvas.onZoomFun) this.map.geoCanvas.onZoomFun(e)
102
+ }
103
+ }
@@ -1,154 +1,232 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- /**
5
- * A grid cell.
6
- * @typedef {{x: number, y: number}} Cell */
7
-
8
- /**
9
- * A dataset component, of grid cells.
10
- * @abstract
11
- *
12
- * @module core
13
- * @author Joseph Davies, Julien Gaffuri
14
- */
15
- export class Dataset {
16
- /**
17
- * @param {import("./Map.js").Map} map The map.
18
- * @param {string} url The URL of the dataset.
19
- * @param {number} resolution The dataset resolution, in the CRS geographical unit.
20
- * @param {{preprocess?:function(Cell):boolean, mixedResolution?:function(Cell):number}} opts
21
- * @abstract
22
- */
23
- constructor(map, url, resolution, opts = {}) {
24
- /**
25
- * The map.
26
- * @protected
27
- * @type {import("./Map.js").Map} */
28
- this.map = map
29
-
30
- /**
31
- * The url of the dataset.
32
- * @protected
33
- * @type {string} */
34
- this.url = url
35
-
36
- /**
37
- * The dataset resolution in geographical unit.
38
- * @protected
39
- * @type {number} */
40
- this.resolution = resolution
41
-
42
- /**
43
- * In case the dataset is a dataset with cells having different resolution,
44
- * this is the function returning the resolution of each cell.
45
- * @protected
46
- * @type {(function(Cell):number )| undefined } */
47
- this.mixedResolution = opts.mixedResolution
48
-
49
- /**
50
- * A preprocess to run on each cell after loading. It can be used to apply some specific treatment before or compute a new column. And also to determine which cells to keep after loading.
51
- * @type {(function(Cell):boolean )| undefined } */
52
- this.preprocess = opts.preprocess || undefined
53
-
54
- /** The cells within the view
55
- * @protected
56
- * @type {Array.<Cell>} */
57
- this.cellsViewCache = []
58
- }
59
-
60
- /**
61
- * Request data within a geographic envelope.
62
- *
63
- * @abstract
64
- * @param {import("./GeoCanvas").Envelope|undefined} extGeo
65
- * @returns {this}
66
- */
67
- getData(extGeo = undefined) {
68
- throw new Error('Method getData not implemented.')
69
- }
70
-
71
- /**
72
- * Fill the view cache with all cells which are within a geographical envelope.
73
- * @abstract
74
- * @param {import("./GeoCanvas").Envelope} extGeo The view geographical envelope.
75
- * @returns {void}
76
- */
77
- updateViewCache(extGeo) {
78
- throw new Error('Method updateViewCache not implemented.')
79
- }
80
-
81
- /**
82
- * Get a cell under a given position, if any.
83
- *
84
- * @param {{x:number,y:number}} posGeo
85
- * @param {Array.<Cell>} cells Some cells from the dataset (a subset if necessary, usually the view cache).
86
- * @returns {Cell|undefined}
87
- */
88
- getCellFromPosition(posGeo, cells) {
89
- //compute candidate cell position
90
- /** @type {number} */
91
- //const r = this.getResolution()
92
- /** @type {number} */
93
- //const cellX = r * Math.floor(posGeo.x / r)
94
- /** @type {number} */
95
- //const cellY = r * Math.floor(posGeo.y / r)
96
-
97
- /*/get cell
98
- for (const cell of cells) {
99
- if (cell.x != cellX) continue
100
- if (cell.y != cellY) continue
101
- return cell
102
- }
103
- return undefined*/
104
-
105
- //rare case of mixed resolution dataset
106
- if (this.mixedResolution) {
107
- for (const c of cells) {
108
- /** @type {number} */
109
- const r = +this.mixedResolution(c)
110
- if (posGeo.x < c.x) continue
111
- else if (c.x + r < posGeo.x) continue
112
- else if (posGeo.y < c.y) continue
113
- else if (c.y + r < posGeo.y) continue
114
- else return c
115
- }
116
- return undefined
117
- }
118
-
119
- //common case
120
-
121
- /** @type {number} */
122
- const r = this.getResolution()
123
- for (const cell of cells) {
124
- if (posGeo.x < cell.x) continue
125
- else if (cell.x + r < posGeo.x) continue
126
- else if (posGeo.y < cell.y) continue
127
- else if (cell.y + r < posGeo.y) continue
128
- else return cell
129
- }
130
- return undefined
131
- }
132
-
133
- //getters and setters
134
-
135
- /** @returns {number} */
136
- getResolution() {
137
- return this.resolution
138
- }
139
-
140
- /** @returns {Array.<Cell>} */
141
- getViewCache() {
142
- return this.cellsViewCache
143
- }
144
-
145
- /**
146
- * Return the relevant dataset for a specified zoom.
147
- * @param {number} z
148
- * @param {number} minPixelsPerCell
149
- * @returns {Dataset|undefined}
150
- * */
151
- getDataset(z, minPixelsPerCell) {
152
- return this
153
- }
154
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /**
5
+ * A grid cell.
6
+ * @typedef {{x: number, y: number}} Cell */
7
+
8
+ /**
9
+ * A dataset component, of grid cells.
10
+ * @abstract
11
+ *
12
+ * @module core
13
+ * @author Joseph Davies, Julien Gaffuri
14
+ */
15
+ export class Dataset {
16
+ /**
17
+ * @param {import("./Map.js").Map} map The map.
18
+ * @param {string} url The URL of the dataset.
19
+ * @param {number} resolution The dataset resolution, in the CRS geographical unit.
20
+ * @param {{preprocess?:function(Cell):boolean, mixedResolution?:function(Cell):number}} opts
21
+ * @abstract
22
+ */
23
+ constructor(map, url, resolution, opts = {}) {
24
+ /**
25
+ * The map.
26
+ * @protected
27
+ * @type {import("./Map.js").Map} */
28
+ this.map = map
29
+
30
+ /**
31
+ * The url of the dataset.
32
+ * @protected
33
+ * @type {string} */
34
+ this.url = url
35
+
36
+ /**
37
+ * The dataset resolution in geographical unit.
38
+ * @protected
39
+ * @type {number} */
40
+ this.resolution = resolution
41
+
42
+ /**
43
+ * In case the dataset is a dataset with cells having different resolution,
44
+ * this is the function returning the resolution of each cell.
45
+ * @protected
46
+ * @type {(function(Cell):number )| undefined } */
47
+ this.mixedResolution = opts.mixedResolution
48
+
49
+ /**
50
+ * A preprocess to run on each cell after loading. It can be used to apply some specific treatment before or compute a new column. And also to determine which cells to keep after loading.
51
+ * @type {(function(Cell):boolean )| undefined } */
52
+ this.preprocess = opts.preprocess || undefined
53
+
54
+ /** The cells within the view
55
+ * @protected
56
+ * @type {Array.<Cell>} */
57
+ this.cellsViewCache = []
58
+ }
59
+
60
+ /**
61
+ * Request data within a geographic envelope.
62
+ *
63
+ * @abstract
64
+ * @param {import("./GeoCanvas").Envelope|undefined} extGeo
65
+ * @returns {this}
66
+ */
67
+ getData(extGeo = undefined) {
68
+ throw new Error('Method getData not implemented.')
69
+ }
70
+
71
+ /**
72
+ * Fill the view cache with all cells which are within a geographical envelope.
73
+ * @abstract
74
+ * @param {import("./GeoCanvas").Envelope} extGeo The view geographical envelope.
75
+ * @returns {void}
76
+ */
77
+ updateViewCache(extGeo) {
78
+ throw new Error('Method updateViewCache not implemented.')
79
+ }
80
+
81
+ /**
82
+ * Get a cell under a given position, if any.
83
+ *
84
+ * @param {{x:number,y:number}} posGeo
85
+ * @param {Array.<Cell>} cells Some cells from the dataset (a subset if necessary, usually the view cache).
86
+ * @returns {Cell|undefined}
87
+ */
88
+ getCellFromPosition(posGeo, cells) {
89
+ //compute candidate cell position
90
+ /** @type {number} */
91
+ //const r = this.getResolution()
92
+ /** @type {number} */
93
+ //const cellX = r * Math.floor(posGeo.x / r)
94
+ /** @type {number} */
95
+ //const cellY = r * Math.floor(posGeo.y / r)
96
+
97
+ /*/get cell
98
+ for (const cell of cells) {
99
+ if (cell.x != cellX) continue
100
+ if (cell.y != cellY) continue
101
+ return cell
102
+ }
103
+ return undefined*/
104
+
105
+ //rare case of mixed resolution dataset
106
+ if (this.mixedResolution) {
107
+ for (const c of cells) {
108
+ /** @type {number} */
109
+ const r = +this.mixedResolution(c)
110
+ if (posGeo.x < c.x) continue
111
+ else if (c.x + r < posGeo.x) continue
112
+ else if (posGeo.y < c.y) continue
113
+ else if (c.y + r < posGeo.y) continue
114
+ else return c
115
+ }
116
+ return undefined
117
+ }
118
+
119
+ //common case
120
+
121
+ /** @type {number} */
122
+ const r = this.getResolution()
123
+
124
+ // Look the cell up in an index of the cells by grid position, built once per cell
125
+ // list (the view cache is a new array after each redraw) instead of scanning every
126
+ // cell on every mouse move.
127
+ const index = this.cellIndex(cells, r)
128
+ if (index) {
129
+ const fx = (posGeo.x - index.ox) / r
130
+ const fy = (posGeo.y - index.oy) / r
131
+ const ix = Math.floor(fx)
132
+ const iy = Math.floor(fy)
133
+ // a position on a cell's right/top edge also belongs to that cell (inclusive
134
+ // bounds, as in the scan below): check the cells left of and below it too
135
+ for (const [dx, dy] of [
136
+ [0, 0],
137
+ [-1, 0],
138
+ [0, -1],
139
+ [-1, -1],
140
+ ]) {
141
+ if (dx && fx !== ix) continue
142
+ if (dy && fy !== iy) continue
143
+ const cell = index.cells.get(cellKey(ix + dx, iy + dy))
144
+ if (cell) return cell
145
+ }
146
+ return undefined
147
+ }
148
+
149
+ for (const cell of cells) {
150
+ if (posGeo.x < cell.x) continue
151
+ else if (cell.x + r < posGeo.x) continue
152
+ else if (posGeo.y < cell.y) continue
153
+ else if (cell.y + r < posGeo.y) continue
154
+ else return cell
155
+ }
156
+ return undefined
157
+ }
158
+
159
+ /**
160
+ * An index of cells by their position on the grid of resolution r, or null when some
161
+ * cell isn't on a common grid (then the caller scans the cells). Cached for the last
162
+ * cell list given.
163
+ *
164
+ * @param {Array.<Cell>} cells
165
+ * @param {number} r
166
+ * @returns {{cells: Map<number, Cell>, ox: number, oy: number} | null}
167
+ * @protected
168
+ */
169
+ cellIndex(cells, r) {
170
+ if (this._index && this._indexCells === cells && this._indexLength === cells.length && this._indexR === r)
171
+ return this._index
172
+ let index = null
173
+ if (cells.length && r > 0) {
174
+ const ox = +cells[0].x
175
+ const oy = +cells[0].y
176
+ const map = new Map()
177
+ index = { cells: map, ox, oy }
178
+ for (const cell of cells) {
179
+ const fx = (+cell.x - ox) / r
180
+ const fy = (+cell.y - oy) / r
181
+ const ix = Math.round(fx)
182
+ const iy = Math.round(fy)
183
+ // not on the grid: no index, scan instead
184
+ if (Math.abs(fx - ix) > 1e-6 || Math.abs(fy - iy) > 1e-6) {
185
+ index = null
186
+ break
187
+ }
188
+ const key = cellKey(ix, iy)
189
+ // the first cell at a position wins, as in the scan
190
+ if (!map.has(key)) map.set(key, cell)
191
+ }
192
+ }
193
+ this._index = index
194
+ this._indexCells = cells
195
+ this._indexLength = cells.length
196
+ this._indexR = r
197
+ return index
198
+ }
199
+
200
+ //getters and setters
201
+
202
+ /** @returns {number} */
203
+ getResolution() {
204
+ return this.resolution
205
+ }
206
+
207
+ /** @returns {Array.<Cell>} */
208
+ getViewCache() {
209
+ return this.cellsViewCache
210
+ }
211
+
212
+ /**
213
+ * Return the relevant dataset for a specified zoom.
214
+ * @param {number} z
215
+ * @param {number} minPixelsPerCell
216
+ * @returns {Dataset|undefined}
217
+ * */
218
+ getDataset(z, minPixelsPerCell) {
219
+ return this
220
+ }
221
+ }
222
+
223
+ /**
224
+ * One number per grid position: cell positions relative to the first cell stay well
225
+ * within +-2^21, so x * 2^22 + y is exact.
226
+ * @param {number} ix
227
+ * @param {number} iy
228
+ * @returns {number}
229
+ */
230
+ function cellKey(ix, iy) {
231
+ return ix * 4194304 + iy
232
+ }