gridviz 3.0.26 → 3.0.27
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 +128 -128
- package/dist/gridviz.js +8630 -8620
- package/dist/gridviz.min.js +1 -1
- package/package.json +73 -73
- 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 +154 -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 +544 -544
- 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 +302 -302
- package/src/dataset/worker.js +18 -18
- package/src/index.js +77 -77
- 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 +224 -214
- package/src/layer/GridLayer.js +148 -148
- 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/ContourStyle.js +21 -21
- package/src/style/DotDensityStyle.js +133 -133
- package/src/style/ImageStyle.js +100 -100
- package/src/style/IsoFenceStyle.js +217 -217
- package/src/style/JoyPlotStyle.js +127 -127
- package/src/style/LegoStyle.js +110 -110
- 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/ShapeColorSizeStyle.js +147 -147
- package/src/style/SideCategoryStyle.js +112 -112
- package/src/style/SideStyle.js +212 -212
- package/src/style/SquareColorCategoryWebGLStyle.js +107 -107
- package/src/style/SquareColorWebGLStyle.js +126 -126
- package/src/style/StrokeStyle.js +135 -135
- package/src/style/TanakaStyle.js +69 -69
- 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 +118 -118
- package/src/utils/WebGLSquareColoringAdvanced.js +230 -230
- package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -133
- package/src/utils/WebGLSquareColoringKS.js +153 -153
- package/src/utils/scale.js +218 -218
- package/src/utils/stretching.js +120 -120
- package/src/utils/ternary.js +208 -208
- package/src/utils/utils.js +58 -58
- package/src/utils/webGLUtils.js +72 -72
- package/src/utils/zoomUtils.js +142 -142
package/src/dataset/TiledGrid.js
CHANGED
|
@@ -1,302 +1,302 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
/** @typedef {{ dims: object, crs: string, tileSizeCell: number, originPoint: {x:number,y:number}, resolutionGeo: number, tilingBounds:import("../core/GeoCanvas.js").Envelope }} GridInfo */
|
|
5
|
-
|
|
6
|
-
// internal
|
|
7
|
-
import { Dataset } from '../core/Dataset.js'
|
|
8
|
-
//import { monitor, monitorDuration } from '../utils/Utils.js'
|
|
9
|
-
|
|
10
|
-
// external
|
|
11
|
-
import { json, csv } from 'd3-fetch'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A tiled dataset, composed of CSV tiles.
|
|
15
|
-
*
|
|
16
|
-
* @module dataset
|
|
17
|
-
* @author Joseph Davies, Julien Gaffuri
|
|
18
|
-
*/
|
|
19
|
-
export class TiledGrid extends Dataset {
|
|
20
|
-
/**
|
|
21
|
-
* @param {import("../core/Map.js").Map} map The map.
|
|
22
|
-
* @param {string} url The URL of the dataset.
|
|
23
|
-
* @param {{preprocess?:(function(import("../core/Dataset.js").Cell):boolean), onlyDrawWhenAllTilesReady:boolean}} opts
|
|
24
|
-
*/
|
|
25
|
-
constructor(map, url, opts = {}) {
|
|
26
|
-
super(map, url, 0, opts)
|
|
27
|
-
this.onlyDrawWhenAllTilesReady = opts.onlyDrawWhenAllTilesReady || false
|
|
28
|
-
/**
|
|
29
|
-
* The grid info object, from the info.json file.
|
|
30
|
-
* @type {GridInfo | undefined}
|
|
31
|
-
* @private
|
|
32
|
-
* */
|
|
33
|
-
this.info = undefined
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @type {string}
|
|
37
|
-
* @private */
|
|
38
|
-
this.infoLoadingStatus = 'notLoaded'
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The cache of the loaded tiles. It is double indexed: by xT and then yT.
|
|
42
|
-
* Example: this.cache[xT][yT] returns the tile at [xT][yT] location.
|
|
43
|
-
*
|
|
44
|
-
* @type {object}
|
|
45
|
-
* */
|
|
46
|
-
this.cache = {}
|
|
47
|
-
|
|
48
|
-
//launch loading
|
|
49
|
-
this.loadInfo()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Load the info.json from the url.
|
|
54
|
-
* @returns this
|
|
55
|
-
*/
|
|
56
|
-
loadInfo() {
|
|
57
|
-
if (!this.info && this.infoLoadingStatus === 'notLoaded') {
|
|
58
|
-
;(async () => {
|
|
59
|
-
try {
|
|
60
|
-
const data = await json(this.url + 'info.json')
|
|
61
|
-
this.info = data
|
|
62
|
-
this.resolution = data.resolutionGeo
|
|
63
|
-
this.infoLoadingStatus = 'loaded'
|
|
64
|
-
this.map.redraw()
|
|
65
|
-
} catch (error) {
|
|
66
|
-
//mark as failed
|
|
67
|
-
this.infoLoadingStatus = 'failed'
|
|
68
|
-
}
|
|
69
|
-
})()
|
|
70
|
-
} else if (this.infoLoadingStatus === 'loaded' || this.infoLoadingStatus === 'failed') this.map.redraw()
|
|
71
|
-
return this
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Compute a tiling envelope from a geographical envelope.
|
|
76
|
-
* This is the function to use to know which tiles to download for a geographical view.
|
|
77
|
-
*
|
|
78
|
-
* @param {import("../core/GeoCanvas.js").Envelope} e
|
|
79
|
-
* @returns {import("../core/GeoCanvas.js").Envelope|undefined}
|
|
80
|
-
*/
|
|
81
|
-
getTilingEnvelope(e) {
|
|
82
|
-
if (!this.info) {
|
|
83
|
-
this.loadInfo()
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const po = this.info.originPoint,
|
|
88
|
-
r = this.info.resolutionGeo,
|
|
89
|
-
s = this.info.tileSizeCell
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
xMin: Math.floor((e.xMin - po.x) / (r * s)),
|
|
93
|
-
xMax: Math.floor((e.xMax - po.x) / (r * s)),
|
|
94
|
-
yMin: Math.floor((e.yMin - po.y) / (r * s)),
|
|
95
|
-
yMax: Math.floor((e.yMax - po.y) / (r * s)),
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Request data within a geographic envelope.
|
|
101
|
-
*
|
|
102
|
-
* @param {import('../core/GeoCanvas.js').Envelope} extGeo
|
|
103
|
-
* @returns {this}
|
|
104
|
-
*/
|
|
105
|
-
async getData(extGeo) {
|
|
106
|
-
if (!this.info) return this
|
|
107
|
-
|
|
108
|
-
// Create an AbortController for the current data request
|
|
109
|
-
this.abortController = new AbortController()
|
|
110
|
-
const signal = this.abortController.signal
|
|
111
|
-
|
|
112
|
-
// Get the tiling envelope and check bounds
|
|
113
|
-
const tb = this.getTilingEnvelope(extGeo)
|
|
114
|
-
if (!tb) return this
|
|
115
|
-
|
|
116
|
-
const { xMin: gbXMin, xMax: gbXMax, yMin: gbYMin, yMax: gbYMax } = this.info.tilingBounds
|
|
117
|
-
|
|
118
|
-
const xMin = Math.max(tb.xMin, gbXMin)
|
|
119
|
-
const xMax = Math.min(tb.xMax, gbXMax)
|
|
120
|
-
const yMin = Math.max(tb.yMin, gbYMin)
|
|
121
|
-
const yMax = Math.min(tb.yMax, gbYMax)
|
|
122
|
-
|
|
123
|
-
const totalTiles = (xMax - xMin + 1) * (yMax - yMin + 1)
|
|
124
|
-
let processedTiles = 0
|
|
125
|
-
const tilePromises = []
|
|
126
|
-
|
|
127
|
-
// Iterate over tiles within bounds
|
|
128
|
-
for (let xT = xMin; xT <= xMax; xT++) {
|
|
129
|
-
for (let yT = yMin; yT <= yMax; yT++) {
|
|
130
|
-
if (!this.cache[xT]) this.cache[xT] = {}
|
|
131
|
-
|
|
132
|
-
// Skip already loaded tiles or retry failed ones
|
|
133
|
-
if (this.cache[xT][yT] && this.cache[xT][yT] !== 'failed') {
|
|
134
|
-
++processedTiles
|
|
135
|
-
continue
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Mark tile as loading
|
|
139
|
-
this.cache[xT][yT] = 'loading'
|
|
140
|
-
|
|
141
|
-
tilePromises.push(
|
|
142
|
-
this.loadTile(xT, yT, signal)
|
|
143
|
-
.then((tile) => {
|
|
144
|
-
this.cache[xT][yT] = tile
|
|
145
|
-
|
|
146
|
-
// Check if this is the last tile
|
|
147
|
-
const isLastTile = ++processedTiles === totalTiles
|
|
148
|
-
this.checkAndRedraw(tile, isLastTile)
|
|
149
|
-
})
|
|
150
|
-
.catch(() => {
|
|
151
|
-
this.cache[xT][yT] = 'failed'
|
|
152
|
-
++processedTiles
|
|
153
|
-
})
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
await Promise.allSettled(tilePromises)
|
|
159
|
-
return this
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Load a tile.
|
|
164
|
-
*
|
|
165
|
-
* @param {number} xT
|
|
166
|
-
* @param {number} yT
|
|
167
|
-
* @param {AbortSignal} signal
|
|
168
|
-
* @returns {Promise<any>}
|
|
169
|
-
*/
|
|
170
|
-
async loadTile(xT, yT, signal) {
|
|
171
|
-
try {
|
|
172
|
-
const data = await csv(`${this.url}${xT}/${yT}.csv`, { signal })
|
|
173
|
-
|
|
174
|
-
const cells = this.preprocess ? data.filter((cell) => this.preprocess(cell) !== false) : data
|
|
175
|
-
|
|
176
|
-
if (!this.info) throw new Error('Tile info unknown')
|
|
177
|
-
|
|
178
|
-
return getGridTile(cells, xT, yT, this.info)
|
|
179
|
-
} catch (error) {
|
|
180
|
-
if (error.name === 'AbortError') {
|
|
181
|
-
console.warn(`Tile request for ${xT}, ${yT} was aborted.`)
|
|
182
|
-
}
|
|
183
|
-
throw error
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Cancel ongoing data requests when zoom level changes.
|
|
189
|
-
*/
|
|
190
|
-
cancelCurrentRequests() {
|
|
191
|
-
if (this.abortController) {
|
|
192
|
-
this.abortController.abort()
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
checkAndRedraw(tile, isLastTile) {
|
|
197
|
-
// Check if any visible layer depends on this dataset
|
|
198
|
-
// check if redraw is really needed, that is if:
|
|
199
|
-
// 1. the dataset belongs to a layer which is visible at the current zoom level
|
|
200
|
-
let needsRedraw = false
|
|
201
|
-
//go through the layers
|
|
202
|
-
const z = this.map.getZoom()
|
|
203
|
-
for (const lay of this.map.layers) {
|
|
204
|
-
if (lay.visible && !lay.visible(z)) continue
|
|
205
|
-
if (!lay.getDataset) continue
|
|
206
|
-
if (lay.getDataset(z) != this) continue
|
|
207
|
-
//found one layer. No need to seek more.
|
|
208
|
-
needsRedraw = true
|
|
209
|
-
break
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (!needsRedraw) return
|
|
213
|
-
|
|
214
|
-
// Check if tile intersects the current view
|
|
215
|
-
const env = this.map.updateExtentGeo()
|
|
216
|
-
const { xMin, xMax, yMin, yMax } = tile.extGeo
|
|
217
|
-
if (env.xMax <= xMin || env.xMin >= xMax || env.yMax <= yMin || env.yMin >= yMax) return
|
|
218
|
-
|
|
219
|
-
// Trigger redraw
|
|
220
|
-
if (this.onlyDrawWhenAllTilesReady) {
|
|
221
|
-
if (isLastTile) {
|
|
222
|
-
this.map.redraw()
|
|
223
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
this.map.redraw()
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Fill the view cache with all cells which are within a geographical envelope.
|
|
231
|
-
* @abstract
|
|
232
|
-
* @param {import("../core/GeoCanvas.js").Envelope} extGeo
|
|
233
|
-
* @returns {void}
|
|
234
|
-
*/
|
|
235
|
-
updateViewCache(extGeo) {
|
|
236
|
-
//
|
|
237
|
-
this.cellsViewCache = []
|
|
238
|
-
|
|
239
|
-
//check if info has been loaded
|
|
240
|
-
if (!this.info) return
|
|
241
|
-
|
|
242
|
-
//tiles within the scope
|
|
243
|
-
/** @type {import("../core/GeoCanvas.js").Envelope|undefined} */
|
|
244
|
-
const tb = this.getTilingEnvelope(extGeo)
|
|
245
|
-
if (!tb) return
|
|
246
|
-
|
|
247
|
-
//grid bounds
|
|
248
|
-
/** @type {import("../core/GeoCanvas.js").Envelope} */
|
|
249
|
-
const gb = this.info.tilingBounds
|
|
250
|
-
|
|
251
|
-
for (let xT = Math.max(tb.xMin, gb.xMin); xT <= Math.min(tb.xMax, gb.xMax); xT++) {
|
|
252
|
-
if (!this.cache[xT]) continue
|
|
253
|
-
for (let yT = Math.max(tb.yMin, gb.yMin); yT <= Math.min(tb.yMax, gb.yMax); yT++) {
|
|
254
|
-
//get tile
|
|
255
|
-
/** @type {object} */
|
|
256
|
-
const tile = this.cache[xT][yT]
|
|
257
|
-
if (!tile || typeof tile === 'string') continue
|
|
258
|
-
|
|
259
|
-
//get cells
|
|
260
|
-
//this.cellsViewCache = this.cellsViewCache.concat(tile.cells)
|
|
261
|
-
|
|
262
|
-
for (const cell of tile.cells) {
|
|
263
|
-
if (+cell.x + this.resolution < extGeo.xMin) continue
|
|
264
|
-
if (+cell.x - this.resolution > extGeo.xMax) continue
|
|
265
|
-
if (+cell.y + this.resolution < extGeo.yMin) continue
|
|
266
|
-
if (+cell.y - this.resolution > extGeo.yMax) continue
|
|
267
|
-
this.cellsViewCache.push(cell)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function getGridTile(cells, xT, yT, gridInfo) {
|
|
275
|
-
const tile = {}
|
|
276
|
-
|
|
277
|
-
/** @type {Array.<import("../core/Dataset").Cell>} */
|
|
278
|
-
tile.cells = cells
|
|
279
|
-
/** @type {number} */
|
|
280
|
-
tile.x = xT
|
|
281
|
-
/** @type {number} */
|
|
282
|
-
tile.y = yT
|
|
283
|
-
|
|
284
|
-
const r = gridInfo.resolutionGeo
|
|
285
|
-
const s = gridInfo.tileSizeCell
|
|
286
|
-
|
|
287
|
-
/** @type {import("../core/GeoCanvas").Envelope} */
|
|
288
|
-
tile.extGeo = {
|
|
289
|
-
xMin: gridInfo.originPoint.x + r * s * tile.x,
|
|
290
|
-
xMax: gridInfo.originPoint.x + r * s * (tile.x + 1),
|
|
291
|
-
yMin: gridInfo.originPoint.y + r * s * tile.y,
|
|
292
|
-
yMax: gridInfo.originPoint.y + r * s * (tile.y + 1),
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
//convert cell coordinates into geographical coordinates
|
|
296
|
-
for (let cell of tile.cells) {
|
|
297
|
-
cell.x = tile.extGeo.xMin + cell.x * r
|
|
298
|
-
cell.y = tile.extGeo.yMin + cell.y * r
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return tile
|
|
302
|
-
}
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
/** @typedef {{ dims: object, crs: string, tileSizeCell: number, originPoint: {x:number,y:number}, resolutionGeo: number, tilingBounds:import("../core/GeoCanvas.js").Envelope }} GridInfo */
|
|
5
|
+
|
|
6
|
+
// internal
|
|
7
|
+
import { Dataset } from '../core/Dataset.js'
|
|
8
|
+
//import { monitor, monitorDuration } from '../utils/Utils.js'
|
|
9
|
+
|
|
10
|
+
// external
|
|
11
|
+
import { json, csv } from 'd3-fetch'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A tiled dataset, composed of CSV tiles.
|
|
15
|
+
*
|
|
16
|
+
* @module dataset
|
|
17
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
18
|
+
*/
|
|
19
|
+
export class TiledGrid extends Dataset {
|
|
20
|
+
/**
|
|
21
|
+
* @param {import("../core/Map.js").Map} map The map.
|
|
22
|
+
* @param {string} url The URL of the dataset.
|
|
23
|
+
* @param {{preprocess?:(function(import("../core/Dataset.js").Cell):boolean), onlyDrawWhenAllTilesReady:boolean}} opts
|
|
24
|
+
*/
|
|
25
|
+
constructor(map, url, opts = {}) {
|
|
26
|
+
super(map, url, 0, opts)
|
|
27
|
+
this.onlyDrawWhenAllTilesReady = opts.onlyDrawWhenAllTilesReady || false
|
|
28
|
+
/**
|
|
29
|
+
* The grid info object, from the info.json file.
|
|
30
|
+
* @type {GridInfo | undefined}
|
|
31
|
+
* @private
|
|
32
|
+
* */
|
|
33
|
+
this.info = undefined
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @type {string}
|
|
37
|
+
* @private */
|
|
38
|
+
this.infoLoadingStatus = 'notLoaded'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The cache of the loaded tiles. It is double indexed: by xT and then yT.
|
|
42
|
+
* Example: this.cache[xT][yT] returns the tile at [xT][yT] location.
|
|
43
|
+
*
|
|
44
|
+
* @type {object}
|
|
45
|
+
* */
|
|
46
|
+
this.cache = {}
|
|
47
|
+
|
|
48
|
+
//launch loading
|
|
49
|
+
this.loadInfo()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load the info.json from the url.
|
|
54
|
+
* @returns this
|
|
55
|
+
*/
|
|
56
|
+
loadInfo() {
|
|
57
|
+
if (!this.info && this.infoLoadingStatus === 'notLoaded') {
|
|
58
|
+
;(async () => {
|
|
59
|
+
try {
|
|
60
|
+
const data = await json(this.url + 'info.json')
|
|
61
|
+
this.info = data
|
|
62
|
+
this.resolution = data.resolutionGeo
|
|
63
|
+
this.infoLoadingStatus = 'loaded'
|
|
64
|
+
this.map.redraw()
|
|
65
|
+
} catch (error) {
|
|
66
|
+
//mark as failed
|
|
67
|
+
this.infoLoadingStatus = 'failed'
|
|
68
|
+
}
|
|
69
|
+
})()
|
|
70
|
+
} else if (this.infoLoadingStatus === 'loaded' || this.infoLoadingStatus === 'failed') this.map.redraw()
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Compute a tiling envelope from a geographical envelope.
|
|
76
|
+
* This is the function to use to know which tiles to download for a geographical view.
|
|
77
|
+
*
|
|
78
|
+
* @param {import("../core/GeoCanvas.js").Envelope} e
|
|
79
|
+
* @returns {import("../core/GeoCanvas.js").Envelope|undefined}
|
|
80
|
+
*/
|
|
81
|
+
getTilingEnvelope(e) {
|
|
82
|
+
if (!this.info) {
|
|
83
|
+
this.loadInfo()
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const po = this.info.originPoint,
|
|
88
|
+
r = this.info.resolutionGeo,
|
|
89
|
+
s = this.info.tileSizeCell
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
xMin: Math.floor((e.xMin - po.x) / (r * s)),
|
|
93
|
+
xMax: Math.floor((e.xMax - po.x) / (r * s)),
|
|
94
|
+
yMin: Math.floor((e.yMin - po.y) / (r * s)),
|
|
95
|
+
yMax: Math.floor((e.yMax - po.y) / (r * s)),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Request data within a geographic envelope.
|
|
101
|
+
*
|
|
102
|
+
* @param {import('../core/GeoCanvas.js').Envelope} extGeo
|
|
103
|
+
* @returns {this}
|
|
104
|
+
*/
|
|
105
|
+
async getData(extGeo) {
|
|
106
|
+
if (!this.info) return this
|
|
107
|
+
|
|
108
|
+
// Create an AbortController for the current data request
|
|
109
|
+
this.abortController = new AbortController()
|
|
110
|
+
const signal = this.abortController.signal
|
|
111
|
+
|
|
112
|
+
// Get the tiling envelope and check bounds
|
|
113
|
+
const tb = this.getTilingEnvelope(extGeo)
|
|
114
|
+
if (!tb) return this
|
|
115
|
+
|
|
116
|
+
const { xMin: gbXMin, xMax: gbXMax, yMin: gbYMin, yMax: gbYMax } = this.info.tilingBounds
|
|
117
|
+
|
|
118
|
+
const xMin = Math.max(tb.xMin, gbXMin)
|
|
119
|
+
const xMax = Math.min(tb.xMax, gbXMax)
|
|
120
|
+
const yMin = Math.max(tb.yMin, gbYMin)
|
|
121
|
+
const yMax = Math.min(tb.yMax, gbYMax)
|
|
122
|
+
|
|
123
|
+
const totalTiles = (xMax - xMin + 1) * (yMax - yMin + 1)
|
|
124
|
+
let processedTiles = 0
|
|
125
|
+
const tilePromises = []
|
|
126
|
+
|
|
127
|
+
// Iterate over tiles within bounds
|
|
128
|
+
for (let xT = xMin; xT <= xMax; xT++) {
|
|
129
|
+
for (let yT = yMin; yT <= yMax; yT++) {
|
|
130
|
+
if (!this.cache[xT]) this.cache[xT] = {}
|
|
131
|
+
|
|
132
|
+
// Skip already loaded tiles or retry failed ones
|
|
133
|
+
if (this.cache[xT][yT] && this.cache[xT][yT] !== 'failed') {
|
|
134
|
+
++processedTiles
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Mark tile as loading
|
|
139
|
+
this.cache[xT][yT] = 'loading'
|
|
140
|
+
|
|
141
|
+
tilePromises.push(
|
|
142
|
+
this.loadTile(xT, yT, signal)
|
|
143
|
+
.then((tile) => {
|
|
144
|
+
this.cache[xT][yT] = tile
|
|
145
|
+
|
|
146
|
+
// Check if this is the last tile
|
|
147
|
+
const isLastTile = ++processedTiles === totalTiles
|
|
148
|
+
this.checkAndRedraw(tile, isLastTile)
|
|
149
|
+
})
|
|
150
|
+
.catch(() => {
|
|
151
|
+
this.cache[xT][yT] = 'failed'
|
|
152
|
+
++processedTiles
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await Promise.allSettled(tilePromises)
|
|
159
|
+
return this
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Load a tile.
|
|
164
|
+
*
|
|
165
|
+
* @param {number} xT
|
|
166
|
+
* @param {number} yT
|
|
167
|
+
* @param {AbortSignal} signal
|
|
168
|
+
* @returns {Promise<any>}
|
|
169
|
+
*/
|
|
170
|
+
async loadTile(xT, yT, signal) {
|
|
171
|
+
try {
|
|
172
|
+
const data = await csv(`${this.url}${xT}/${yT}.csv`, { signal })
|
|
173
|
+
|
|
174
|
+
const cells = this.preprocess ? data.filter((cell) => this.preprocess(cell) !== false) : data
|
|
175
|
+
|
|
176
|
+
if (!this.info) throw new Error('Tile info unknown')
|
|
177
|
+
|
|
178
|
+
return getGridTile(cells, xT, yT, this.info)
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (error.name === 'AbortError') {
|
|
181
|
+
console.warn(`Tile request for ${xT}, ${yT} was aborted.`)
|
|
182
|
+
}
|
|
183
|
+
throw error
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Cancel ongoing data requests when zoom level changes.
|
|
189
|
+
*/
|
|
190
|
+
cancelCurrentRequests() {
|
|
191
|
+
if (this.abortController) {
|
|
192
|
+
this.abortController.abort()
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
checkAndRedraw(tile, isLastTile) {
|
|
197
|
+
// Check if any visible layer depends on this dataset
|
|
198
|
+
// check if redraw is really needed, that is if:
|
|
199
|
+
// 1. the dataset belongs to a layer which is visible at the current zoom level
|
|
200
|
+
let needsRedraw = false
|
|
201
|
+
//go through the layers
|
|
202
|
+
const z = this.map.getZoom()
|
|
203
|
+
for (const lay of this.map.layers) {
|
|
204
|
+
if (lay.visible && !lay.visible(z)) continue
|
|
205
|
+
if (!lay.getDataset) continue
|
|
206
|
+
if (lay.getDataset(z) != this) continue
|
|
207
|
+
//found one layer. No need to seek more.
|
|
208
|
+
needsRedraw = true
|
|
209
|
+
break
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!needsRedraw) return
|
|
213
|
+
|
|
214
|
+
// Check if tile intersects the current view
|
|
215
|
+
const env = this.map.updateExtentGeo()
|
|
216
|
+
const { xMin, xMax, yMin, yMax } = tile.extGeo
|
|
217
|
+
if (env.xMax <= xMin || env.xMin >= xMax || env.yMax <= yMin || env.yMin >= yMax) return
|
|
218
|
+
|
|
219
|
+
// Trigger redraw
|
|
220
|
+
if (this.onlyDrawWhenAllTilesReady) {
|
|
221
|
+
if (isLastTile) {
|
|
222
|
+
this.map.redraw()
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
this.map.redraw()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Fill the view cache with all cells which are within a geographical envelope.
|
|
231
|
+
* @abstract
|
|
232
|
+
* @param {import("../core/GeoCanvas.js").Envelope} extGeo
|
|
233
|
+
* @returns {void}
|
|
234
|
+
*/
|
|
235
|
+
updateViewCache(extGeo) {
|
|
236
|
+
//
|
|
237
|
+
this.cellsViewCache = []
|
|
238
|
+
|
|
239
|
+
//check if info has been loaded
|
|
240
|
+
if (!this.info) return
|
|
241
|
+
|
|
242
|
+
//tiles within the scope
|
|
243
|
+
/** @type {import("../core/GeoCanvas.js").Envelope|undefined} */
|
|
244
|
+
const tb = this.getTilingEnvelope(extGeo)
|
|
245
|
+
if (!tb) return
|
|
246
|
+
|
|
247
|
+
//grid bounds
|
|
248
|
+
/** @type {import("../core/GeoCanvas.js").Envelope} */
|
|
249
|
+
const gb = this.info.tilingBounds
|
|
250
|
+
|
|
251
|
+
for (let xT = Math.max(tb.xMin, gb.xMin); xT <= Math.min(tb.xMax, gb.xMax); xT++) {
|
|
252
|
+
if (!this.cache[xT]) continue
|
|
253
|
+
for (let yT = Math.max(tb.yMin, gb.yMin); yT <= Math.min(tb.yMax, gb.yMax); yT++) {
|
|
254
|
+
//get tile
|
|
255
|
+
/** @type {object} */
|
|
256
|
+
const tile = this.cache[xT][yT]
|
|
257
|
+
if (!tile || typeof tile === 'string') continue
|
|
258
|
+
|
|
259
|
+
//get cells
|
|
260
|
+
//this.cellsViewCache = this.cellsViewCache.concat(tile.cells)
|
|
261
|
+
|
|
262
|
+
for (const cell of tile.cells) {
|
|
263
|
+
if (+cell.x + this.resolution < extGeo.xMin) continue
|
|
264
|
+
if (+cell.x - this.resolution > extGeo.xMax) continue
|
|
265
|
+
if (+cell.y + this.resolution < extGeo.yMin) continue
|
|
266
|
+
if (+cell.y - this.resolution > extGeo.yMax) continue
|
|
267
|
+
this.cellsViewCache.push(cell)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function getGridTile(cells, xT, yT, gridInfo) {
|
|
275
|
+
const tile = {}
|
|
276
|
+
|
|
277
|
+
/** @type {Array.<import("../core/Dataset").Cell>} */
|
|
278
|
+
tile.cells = cells
|
|
279
|
+
/** @type {number} */
|
|
280
|
+
tile.x = xT
|
|
281
|
+
/** @type {number} */
|
|
282
|
+
tile.y = yT
|
|
283
|
+
|
|
284
|
+
const r = gridInfo.resolutionGeo
|
|
285
|
+
const s = gridInfo.tileSizeCell
|
|
286
|
+
|
|
287
|
+
/** @type {import("../core/GeoCanvas").Envelope} */
|
|
288
|
+
tile.extGeo = {
|
|
289
|
+
xMin: gridInfo.originPoint.x + r * s * tile.x,
|
|
290
|
+
xMax: gridInfo.originPoint.x + r * s * (tile.x + 1),
|
|
291
|
+
yMin: gridInfo.originPoint.y + r * s * tile.y,
|
|
292
|
+
yMax: gridInfo.originPoint.y + r * s * (tile.y + 1),
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
//convert cell coordinates into geographical coordinates
|
|
296
|
+
for (let cell of tile.cells) {
|
|
297
|
+
cell.x = tile.extGeo.xMin + cell.x * r
|
|
298
|
+
cell.y = tile.extGeo.yMin + cell.y * r
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return tile
|
|
302
|
+
}
|
package/src/dataset/worker.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
self.onmessage = function (e) {
|
|
2
|
-
const { type, cells, preprocess } = e.data
|
|
3
|
-
if (type === 'processCells') {
|
|
4
|
-
const result = processCells(cells, preprocess)
|
|
5
|
-
self.postMessage({ type: 'processedCells', result })
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function processCells(cells, preprocess) {
|
|
10
|
-
// If no preprocess function is provided, return the cells as they are
|
|
11
|
-
if (!preprocess) {
|
|
12
|
-
return cells
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Apply preprocess function to each cell if available
|
|
16
|
-
const preprocessedCells = cells.map((cell) => preprocess(cell)).filter((cell) => cell !== false)
|
|
17
|
-
return preprocessedCells
|
|
18
|
-
}
|
|
1
|
+
self.onmessage = function (e) {
|
|
2
|
+
const { type, cells, preprocess } = e.data
|
|
3
|
+
if (type === 'processCells') {
|
|
4
|
+
const result = processCells(cells, preprocess)
|
|
5
|
+
self.postMessage({ type: 'processedCells', result })
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function processCells(cells, preprocess) {
|
|
10
|
+
// If no preprocess function is provided, return the cells as they are
|
|
11
|
+
if (!preprocess) {
|
|
12
|
+
return cells
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Apply preprocess function to each cell if available
|
|
16
|
+
const preprocessedCells = cells.map((cell) => preprocess(cell)).filter((cell) => cell !== false)
|
|
17
|
+
return preprocessedCells
|
|
18
|
+
}
|