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.
- package/README.md +129 -129
- package/dist/gridviz.js +21056 -17487
- package/dist/gridviz.min.js +2 -2
- package/package.json +74 -74
- package/src/button/Button.js +67 -67
- package/src/button/FullscreenButton.js +121 -121
- package/src/button/ZoomButtons.js +103 -103
- package/src/core/Dataset.js +232 -154
- package/src/core/Drawable.js +78 -78
- package/src/core/GeoCanvas.js +443 -443
- package/src/core/Layer.js +23 -23
- package/src/core/Legend.js +108 -108
- package/src/core/Map.js +650 -568
- package/src/core/MultiResolutionDataset.js +93 -93
- package/src/core/Style.js +98 -98
- package/src/core/Tooltip.js +182 -182
- package/src/dataset/COGeo.js +25 -25
- package/src/dataset/CSVGrid.js +112 -112
- package/src/dataset/GeoTIFF.js +122 -122
- package/src/dataset/JSGrid.js +55 -55
- package/src/dataset/TiledGrid.js +311 -302
- package/src/dataset/worker.js +18 -18
- package/src/index.js +79 -79
- package/src/layer/BackgroundLayer.js +173 -173
- package/src/layer/BackgroundLayerImage.js +81 -81
- package/src/layer/BackgroundLayerWMS.js +104 -104
- package/src/layer/GeoJSONLayer.js +276 -276
- package/src/layer/GridLayer.js +166 -166
- package/src/layer/LabelLayer.js +194 -194
- package/src/legend/ColorCategoryLegend.js +109 -109
- package/src/legend/ColorDiscreteLegend.js +100 -100
- package/src/legend/ColorLegend.js +115 -115
- package/src/legend/OrientationLegend.js +89 -89
- package/src/legend/SizeLegend.js +224 -224
- package/src/legend/TernaryLegend.js +302 -302
- package/src/style/CompositionStyle.js +291 -291
- package/src/style/DotDensityStyle.js +133 -133
- package/src/style/ImageStyle.js +100 -100
- package/src/style/Interpolator.js +258 -258
- package/src/style/IsoFenceStyle.js +217 -217
- package/src/style/JoyPlotStyle.js +180 -180
- package/src/style/LegoStyle.js +132 -132
- package/src/style/MosaicStyle.js +99 -99
- package/src/style/NinjaStarStyle.js +102 -102
- package/src/style/PillarStyle.js +207 -207
- package/src/style/SegmentStyle.js +105 -105
- package/src/style/ShadingRayStyle.js +280 -280
- package/src/style/ShadingStyle.js +72 -72
- package/src/style/ShapeColorSizeStyle.js +147 -147
- package/src/style/SideCategoryStyle.js +112 -112
- package/src/style/SideStyle.js +217 -217
- package/src/style/SideTanakaStyle.js +154 -154
- package/src/style/SquareColorCategoryWebGLStyle.js +311 -294
- package/src/style/SquareColorWebGLStyle.js +126 -126
- package/src/style/StrokeStyle.js +135 -135
- package/src/style/TextStyle.js +110 -110
- package/src/style/TimeSeriesStyle.js +222 -222
- package/src/utils/WebGLRectangleColoring.js +106 -106
- package/src/utils/WebGLSquareColoring.js +114 -118
- package/src/utils/WebGLSquareColoringAdvanced.js +223 -227
- package/src/utils/WebGLSquareColoringKS.js +153 -153
- package/src/utils/scale.js +255 -255
- package/src/utils/stretching.js +120 -120
- package/src/utils/ternary.js +208 -208
- package/src/utils/utils.js +125 -125
- package/src/utils/webGLUtils.js +178 -73
- 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
|
+
}
|
package/src/core/Dataset.js
CHANGED
|
@@ -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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
+
}
|