gridviz 2.0.27 → 2.0.29

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 (56) hide show
  1. package/dist/gridviz.js +34315 -0
  2. package/package.json +4 -2
  3. package/src/App.js +651 -0
  4. package/src/BackgroundLayer.js +182 -0
  5. package/src/BackgroundLayerWMS.js +131 -0
  6. package/src/Dataset.js +76 -0
  7. package/src/DatasetComponent.js +103 -0
  8. package/src/GeoCanvas.js +419 -0
  9. package/src/LabelLayer.js +190 -0
  10. package/src/Layer.js +92 -0
  11. package/src/Legend.js +86 -0
  12. package/src/LineLayer.js +150 -0
  13. package/src/Style.js +114 -0
  14. package/src/Tooltip.js +150 -0
  15. package/src/dataset/COGeo.js +24 -0
  16. package/src/dataset/CSVGrid.js +105 -0
  17. package/src/dataset/GeoTIFF.js +127 -0
  18. package/src/dataset/GridTile.js +41 -0
  19. package/src/dataset/ParquetGrid.js +123 -0
  20. package/src/dataset/TiledGrid.js +294 -0
  21. package/src/index.js +176 -0
  22. package/src/legend/ColorCategoryLegend.js +124 -0
  23. package/src/legend/ColorDiscreteLegend.js +105 -0
  24. package/src/legend/ColorLegend.js +125 -0
  25. package/src/legend/SegmentOrientationLegend.js +87 -0
  26. package/src/legend/SegmentWidthLegend.js +97 -0
  27. package/src/legend/SizeLegend.js +141 -0
  28. package/src/style/CompositionStyle.js +294 -0
  29. package/src/style/ContourStyle.js +54 -0
  30. package/src/style/DotDensityStyle.js +156 -0
  31. package/src/style/JoyPlotStyle.js +135 -0
  32. package/src/style/KernelSmoothingOldStyle.js +221 -0
  33. package/src/style/KernelSmoothingStyle.js +115 -0
  34. package/src/style/KernelSmoothingWGLStyle.js +130 -0
  35. package/src/style/LegoStyle.js +112 -0
  36. package/src/style/MosaicStyle.js +107 -0
  37. package/src/style/NinjaStarStyle.js +124 -0
  38. package/src/style/PillarStyle.js +230 -0
  39. package/src/style/SegmentStyle.js +142 -0
  40. package/src/style/ShapeColorSizeStyle.js +126 -0
  41. package/src/style/SideCatStyle.js +172 -0
  42. package/src/style/SideStyle.js +218 -0
  43. package/src/style/SquareColorCatWGLStyle.js +120 -0
  44. package/src/style/SquareColorWGLStyle.js +146 -0
  45. package/src/style/StrokeStyle.js +131 -0
  46. package/src/style/TanakaStyle.js +160 -0
  47. package/src/style/TextStyle.js +132 -0
  48. package/src/utils/Utils.js +35 -0
  49. package/src/utils/WebGLRectangleColoring.js +106 -0
  50. package/src/utils/WebGLSquareColoring.js +118 -0
  51. package/src/utils/WebGLSquareColoringAdvanced.js +230 -0
  52. package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -0
  53. package/src/utils/WebGLSquareColoringKS.js +153 -0
  54. package/src/utils/stretching.js +131 -0
  55. package/src/utils/webGLUtils.js +71 -0
  56. package/src/utils/zoomUtils.js +142 -0
@@ -0,0 +1,127 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { DatasetComponent } from '../DatasetComponent'
5
+ //import { fromUrl } from "geotiff"
6
+
7
+ /**
8
+ * @todo
9
+ * A dataset composed of a single GeoTiff file.
10
+ *
11
+ * @author Joseph Davies, Julien Gaffuri
12
+ */
13
+ export class GeoTIFF extends DatasetComponent {
14
+ /**
15
+ * @param {string} url The URL of the dataset.
16
+ * @param {number} resolution The dataset resolution in geographical unit.
17
+ * @param {{preprocess?:(function(import("../Dataset").Cell):boolean)}} opts
18
+ */
19
+ constructor(url, resolution, opts = {}) {
20
+ super(url, resolution, opts)
21
+
22
+ /**
23
+ * @private
24
+ * @type {Array.<import("../Dataset").Cell>} */
25
+ this.cells = []
26
+
27
+ /**
28
+ * @type {string}
29
+ * @private */
30
+ this.infoLoadingStatus = 'notLoaded'
31
+ }
32
+
33
+ /**
34
+ * Request data within a geographic envelope.
35
+ *
36
+ * @param {import("../Dataset").Envelope|undefined} e
37
+ * @param {function():void} redraw
38
+ */
39
+ getData(e, redraw) {
40
+ //check if data already loaded
41
+ if (this.infoLoadingStatus != 'notLoaded') return this
42
+
43
+ //load data
44
+ this.infoLoadingStatus = 'loading'
45
+
46
+ /*
47
+
48
+ fromUrl(this.url)
49
+ .then(tiff => {
50
+ console.log(tiff);
51
+
52
+ (async function () {
53
+ const image = await tiff.getImage();
54
+ const width = image.getWidth();
55
+ const height = image.getHeight();
56
+ const tileWidth = image.getTileWidth();
57
+ const tileHeight = image.getTileHeight();
58
+ const samplesPerPixel = image.getSamplesPerPixel();
59
+
60
+ //for geotiff
61
+ const origin = image.getOrigin();
62
+ const resolution = image.getResolution();
63
+ const bbox = image.getBoundingBox();
64
+
65
+ console.log(image)
66
+ console.log(width, height)
67
+ console.log(tileWidth, tileHeight)
68
+ console.log(samplesPerPixel)
69
+ console.log(origin)
70
+ console.log(resolution)
71
+ console.log(bbox)
72
+
73
+ console.log("-------")
74
+
75
+ const data = await image.readRasters();
76
+ console.log(data)
77
+
78
+ })()
79
+
80
+ //convert coordinates in numbers
81
+ //for (const c of data) { c.x = +c.x; c.y = +c.y; }
82
+
83
+ //this.cells = data;
84
+
85
+ //execute preprocess, if any
86
+ //if (this.preprocess) for (const c of this.cells) this.preprocess(c);
87
+
88
+ //TODO check if redraw is necessary
89
+ //that is if the dataset belongs to a layer which is visible at the current zoom level
90
+
91
+ //execute the callback, usually a draw function
92
+ if (redraw) redraw()
93
+
94
+ this.infoLoadingStatus = "loaded";
95
+
96
+
97
+ })
98
+ .catch(() => {
99
+ //mark as failed
100
+ this.infoLoadingStatus = "failed";
101
+ this.cells = []
102
+ });
103
+ */
104
+
105
+ return this
106
+ }
107
+
108
+ /**
109
+ * Fill the view cache with all cells which are within a geographical envelope.
110
+ *
111
+ * @param {import("../Dataset").Envelope} extGeo
112
+ * @returns {void}
113
+ */
114
+ updateViewCache(extGeo) {
115
+ //data not loaded yet
116
+ if (!this.cells) return
117
+
118
+ this.cellsViewCache = []
119
+ for (const cell of this.cells) {
120
+ if (+cell.x + this.resolution < extGeo.xMin) continue
121
+ if (+cell.x - this.resolution > extGeo.xMax) continue
122
+ if (+cell.y + this.resolution < extGeo.yMin) continue
123
+ if (+cell.y - this.resolution > extGeo.yMax) continue
124
+ this.cellsViewCache.push(cell)
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,41 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /**
5
+ * A grid tile.
6
+ *
7
+ * @author Joseph Davies, Julien Gaffuri
8
+ */
9
+ export class GridTile {
10
+ /**
11
+ * @param {Array.<import("../Dataset").Cell>} cells The tile cells.
12
+ * @param {number} xT The X position of the tile.
13
+ * @param {number} yT The Y position of the tile.
14
+ * @param {import("./CSVGrid").GridInfo} gridInfo The grid info object.
15
+ */
16
+ constructor(cells, xT, yT, gridInfo) {
17
+ /** @type {Array.<import("../Dataset").Cell>} */
18
+ this.cells = cells
19
+ /** @type {number} */
20
+ this.x = xT
21
+ /** @type {number} */
22
+ this.y = yT
23
+
24
+ const r = gridInfo.resolutionGeo
25
+ const s = gridInfo.tileSizeCell
26
+
27
+ /** @type {import("../Dataset").Envelope} */
28
+ this.extGeo = {
29
+ xMin: gridInfo.originPoint.x + r * s * this.x,
30
+ xMax: gridInfo.originPoint.x + r * s * (this.x + 1),
31
+ yMin: gridInfo.originPoint.y + r * s * this.y,
32
+ yMax: gridInfo.originPoint.y + r * s * (this.y + 1),
33
+ }
34
+
35
+ //convert cell coordinates into geographical coordinates
36
+ for (let cell of this.cells) {
37
+ cell.x = this.extGeo.xMin + cell.x * r
38
+ cell.y = this.extGeo.yMin + cell.y * r
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,123 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { DatasetComponent } from '../DatasetComponent'
5
+ import { tableFromIPC } from 'apache-arrow'
6
+ //import { readParquet } from "parquet-wasm"
7
+
8
+ /**
9
+ * A dataset composed of a single parquet file (not tiled).
10
+ *
11
+ * @author Julien Gaffuri
12
+ */
13
+ export class ParquetGrid extends DatasetComponent {
14
+ /**
15
+ * @param {string} url The URL of the dataset.
16
+ * @param {number} resolution The dataset resolution in geographical unit.
17
+ * @param {{preprocess?:(function(import("../Dataset").Cell):boolean), readParquetFun?:Function}} opts
18
+ */
19
+ constructor(url, resolution, opts = {}) {
20
+ super(url, resolution, opts)
21
+
22
+ /**
23
+ * @private
24
+ * @type {Array.<import("../Dataset").Cell>} */
25
+ this.cells = []
26
+
27
+ /**
28
+ * @type {string}
29
+ * @private */
30
+ this.infoLoadingStatus = 'notLoaded'
31
+
32
+ if (!opts.readParquetFun) throw new Error('readParquet function needed for parquet dataset')
33
+
34
+ /**
35
+ * @type {Function}
36
+ * @private */
37
+ this.readParquetFun = opts.readParquetFun
38
+ }
39
+
40
+ /**
41
+ * Request data within a geographic envelope.
42
+ *
43
+ * @param {import("../Dataset").Envelope|undefined} e
44
+ * @param {function():void} redraw
45
+ */
46
+ getData(e, redraw) {
47
+ //check if data already loaded
48
+ if (this.infoLoadingStatus != 'notLoaded') return this
49
+
50
+ //load data
51
+ this.infoLoadingStatus = 'loading'
52
+
53
+ ;(async () => {
54
+ try {
55
+ const resp = await fetch(this.url)
56
+ const parquetUint8Array = new Uint8Array(await resp.arrayBuffer())
57
+ const arrowUint8Array = this.readParquetFun(parquetUint8Array)
58
+
59
+ const t = tableFromIPC(arrowUint8Array)
60
+ //see https://arrow.apache.org/docs/js/
61
+ //https://loaders.gl/arrowjs/docs/developer-guide/tables#record-tojson-and-toarray
62
+
63
+ this.cells = []
64
+ for (const e of t) {
65
+ //get cell
66
+ const c = e.toJSON()
67
+
68
+ //preprocess/filter
69
+ if (this.preprocess) {
70
+ const b = this.preprocess(c)
71
+ if (b == false) continue
72
+ this.cells.push(c)
73
+ } else {
74
+ this.cells.push(c)
75
+ }
76
+ }
77
+
78
+ //TODO check if redraw is necessary
79
+ //that is if the dataset belongs to a layer which is visible at the current zoom level
80
+
81
+ //execute the callback, usually a draw function
82
+ if (redraw) redraw()
83
+
84
+ this.infoLoadingStatus = 'loaded'
85
+ } catch (error) {
86
+ //mark as failed
87
+ this.infoLoadingStatus = 'failed'
88
+ this.cells = []
89
+ }
90
+ })()
91
+
92
+ return this
93
+ }
94
+
95
+ /**
96
+ * Fill the view cache with all cells which are within a geographical envelope.
97
+ *
98
+ * @param {import("../Dataset").Envelope} extGeo
99
+ * @returns {void}
100
+ */
101
+ updateViewCache(extGeo) {
102
+ //data not loaded yet
103
+ if (!this.cells) return
104
+
105
+ this.cellsViewCache = []
106
+ for (const cell of this.cells) {
107
+ if (+cell.x + this.resolution < extGeo.xMin) continue
108
+ if (+cell.x - this.resolution > extGeo.xMax) continue
109
+ if (+cell.y + this.resolution < extGeo.yMin) continue
110
+ if (+cell.y - this.resolution > extGeo.yMax) continue
111
+ this.cellsViewCache.push(cell)
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Retrieve parquet wasm decoder
118
+ */
119
+ const getReadParquet = async () => {
120
+ //const parquetModule = await import("https://unpkg.com/parquet-wasm@0.4.0-beta.5/esm/arrow2.js");
121
+ //await parquetModule.default();
122
+ //return parquetModule.readParquet;
123
+ }
@@ -0,0 +1,294 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /** @typedef {{ dims: object, crs: string, tileSizeCell: number, originPoint: {x:number,y:number}, resolutionGeo: number, tilingBounds:import("../Dataset").Envelope, format:import("../DatasetComponent").Format }} GridInfo */
5
+
6
+ import { json, csv } from 'd3-fetch'
7
+ import { GridTile } from './GridTile'
8
+ import { App } from '../App'
9
+ import { DatasetComponent } from '../DatasetComponent'
10
+ import { monitor, monitorDuration } from '../utils/Utils'
11
+ import { tableFromIPC } from 'apache-arrow'
12
+
13
+ /**
14
+ * A tiled dataset, composed of CSV (or parquet) tiles.
15
+ *
16
+ * @author Joseph Davies, Julien Gaffuri
17
+ */
18
+ export class TiledGrid extends DatasetComponent {
19
+ /**
20
+ * @param {string} url The URL of the dataset.
21
+ * @param {App} app The application.
22
+ * @param {{preprocess?:(function(import("../Dataset").Cell):boolean), readParquetFun?:Function }} opts
23
+ */
24
+ constructor(url, app, opts = {}) {
25
+ super(url, 0, opts)
26
+
27
+ /**
28
+ * The app being used.
29
+ * @type {App}
30
+ */
31
+ this.app = app
32
+
33
+ /**
34
+ * The grid info object, from the info.json file.
35
+ * @type {GridInfo | undefined}
36
+ * @private
37
+ * */
38
+ this.info = undefined
39
+
40
+ /**
41
+ * @type {string}
42
+ * @private */
43
+ this.infoLoadingStatus = 'notLoaded'
44
+
45
+ /**
46
+ * The cache of the loaded tiles. It is double indexed: by xT and then yT.
47
+ * Example: this.cache[xT][yT] returns the tile at [xT][yT] location.
48
+ *
49
+ * @type {object}
50
+ * */
51
+ this.cache = {}
52
+
53
+ /**
54
+ * @type {Function|undefined}
55
+ * @private */
56
+ this.readParquetFun = opts.readParquetFun
57
+ }
58
+
59
+ /**
60
+ * Load the info.json from the url.
61
+ *
62
+ * @param {function():void} callback
63
+ * @returns this
64
+ */
65
+ loadInfo(callback) {
66
+ if (!this.info && this.infoLoadingStatus === 'notLoaded') {
67
+ ;(async () => {
68
+ try {
69
+ const data = await json(this.url + 'info.json')
70
+ this.info = data
71
+ this.resolution = data.resolutionGeo
72
+ this.infoLoadingStatus = 'loaded'
73
+ if (callback) callback()
74
+ } catch (error) {
75
+ //mark as failed
76
+ this.infoLoadingStatus = 'failed'
77
+ }
78
+ })()
79
+ } else if (callback && (this.infoLoadingStatus === 'loaded' || this.infoLoadingStatus === 'failed'))
80
+ callback()
81
+ return this
82
+ }
83
+
84
+ /**
85
+ * Compute a tiling envelope from a geographical envelope.
86
+ * This is the function to use to know which tiles to download for a geographical view.
87
+ *
88
+ * @param {import("../Dataset").Envelope} e
89
+ * @returns {import("../Dataset").Envelope|undefined}
90
+ */
91
+ getTilingEnvelope(e) {
92
+ if (!this.info) {
93
+ this.loadInfo(() => {})
94
+ return
95
+ }
96
+
97
+ const po = this.info.originPoint,
98
+ r = this.info.resolutionGeo,
99
+ s = this.info.tileSizeCell
100
+
101
+ return {
102
+ xMin: Math.floor((e.xMin - po.x) / (r * s)),
103
+ xMax: Math.floor((e.xMax - po.x) / (r * s)),
104
+ yMin: Math.floor((e.yMin - po.y) / (r * s)),
105
+ yMax: Math.floor((e.yMax - po.y) / (r * s)),
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Request data within a geographic envelope.
111
+ *
112
+ * @param {import("../Dataset").Envelope} extGeo
113
+ * @param {function():void} redrawFun
114
+ * @returns {this}
115
+ */
116
+ getData(extGeo, redrawFun) {
117
+ //TODO empty cache when it gets too big ?
118
+
119
+ //check if info has been loaded
120
+ if (!this.info) return this
121
+
122
+ //tiles within the scope
123
+ /** @type {import("../Dataset").Envelope|undefined} */
124
+ const tb = this.getTilingEnvelope(extGeo)
125
+ if (!tb) return this
126
+
127
+ //grid bounds
128
+ /** @type {import("../Dataset").Envelope} */
129
+ const gb = this.info.tilingBounds
130
+
131
+ const format = this.info.format
132
+
133
+ for (let xT = Math.max(tb.xMin, gb.xMin); xT <= Math.min(tb.xMax, gb.xMax); xT++) {
134
+ for (let yT = Math.max(tb.yMin, gb.yMin); yT <= Math.min(tb.yMax, gb.yMax); yT++) {
135
+ //prepare cache
136
+ if (!this.cache[xT]) this.cache[xT] = {}
137
+
138
+ //check if tile exists in the cache
139
+ /** @type {GridTile} */
140
+ let tile = this.cache[xT][yT]
141
+ if (tile) continue
142
+
143
+ //mark tile as loading
144
+ this.cache[xT][yT] = 'loading'
145
+
146
+ ;(async () => {
147
+ //request tile
148
+ /** @type {Array.<import("../Dataset").Cell>} */
149
+ let cells
150
+
151
+ try {
152
+ if (!format || format === 'CSV') {
153
+ /** @type {Array.<import("../Dataset").Cell>} */
154
+ // @ts-ignore
155
+ const data = await csv(this.url + xT + '/' + yT + '.csv')
156
+
157
+ if (monitor) monitorDuration('*** TiledGrid parse start')
158
+
159
+ //preprocess/filter
160
+ if (this.preprocess) {
161
+ cells = []
162
+ for (const c of data) {
163
+ const b = this.preprocess(c)
164
+ if (b == false) continue
165
+ cells.push(c)
166
+ }
167
+ } else {
168
+ cells = data
169
+ }
170
+
171
+ if (monitor) monitorDuration('preprocess / filter')
172
+ } else if (format === 'PARQUET') {
173
+ if (!this.readParquetFun)
174
+ throw new Error('readParquet function needed for parquet dataset')
175
+
176
+ const resp = await fetch(this.url + xT + '/' + yT + '.parquet')
177
+ const parquetUint8Array = new Uint8Array(await resp.arrayBuffer())
178
+ const arrowUint8Array = this.readParquetFun(parquetUint8Array)
179
+ const t = tableFromIPC(arrowUint8Array)
180
+
181
+ cells = []
182
+ for (const e of t) {
183
+ //get cell
184
+ const c = e.toJSON()
185
+
186
+ //preprocess/filter
187
+ if (this.preprocess) {
188
+ const b = this.preprocess(c)
189
+ if (b == false) continue
190
+ cells.push(c)
191
+ } else {
192
+ cells.push(c)
193
+ }
194
+ }
195
+ } else throw new Error('Tiled format not supported: ' + format)
196
+ } catch (error) {
197
+ //mark as failed
198
+ this.cache[xT][yT] = 'failed'
199
+ return
200
+ }
201
+
202
+ //store tile in cache
203
+ if (!this.info) {
204
+ console.error('Tile info inknown')
205
+ return
206
+ }
207
+ const tile_ = new GridTile(cells, xT, yT, this.info)
208
+ this.cache[xT][yT] = tile_
209
+
210
+ if (monitor) monitorDuration('storage')
211
+
212
+ //if no redraw is specified, then leave
213
+ if (!redrawFun) return
214
+
215
+ //check if redraw is really needed, that is if:
216
+
217
+ // 1. the dataset belongs to a layer which is visible at the current zoom level
218
+ let redraw = false
219
+ //go through the layers
220
+ const zf = this.app.getZoomFactor()
221
+ for (const lay of this.app.layers) {
222
+ if (!lay.visible) continue
223
+ if (lay.getDatasetComponent(zf) != this) continue
224
+ //found one layer. No need to seek more.
225
+ redraw = true
226
+ break
227
+ }
228
+ if (monitor) monitorDuration('check redraw 1')
229
+
230
+ if (!redraw) return
231
+
232
+ // 2. the tile is within the view, that is its geo envelope intersects the viewer geo envelope.
233
+ const env = this.app.updateExtentGeo()
234
+ const envT = tile_.extGeo
235
+ if (env.xMax <= envT.xMin) return
236
+ if (env.xMin >= envT.xMax) return
237
+ if (env.yMax <= envT.yMin) return
238
+ if (env.yMin >= envT.yMax) return
239
+
240
+ if (monitor) monitorDuration('check redraw 2')
241
+ if (monitor) monitorDuration('*** TiledGrid parse end')
242
+
243
+ //redraw
244
+ redrawFun()
245
+ })()
246
+ }
247
+ }
248
+ return this
249
+ }
250
+
251
+ /**
252
+ * Fill the view cache with all cells which are within a geographical envelope.
253
+ * @abstract
254
+ * @param {import("../Dataset").Envelope} extGeo
255
+ * @returns {void}
256
+ */
257
+ updateViewCache(extGeo) {
258
+ //
259
+ this.cellsViewCache = []
260
+
261
+ //check if info has been loaded
262
+ if (!this.info) return
263
+
264
+ //tiles within the scope
265
+ /** @type {import("../Dataset").Envelope|undefined} */
266
+ const tb = this.getTilingEnvelope(extGeo)
267
+ if (!tb) return
268
+
269
+ //grid bounds
270
+ /** @type {import("../Dataset").Envelope} */
271
+ const gb = this.info.tilingBounds
272
+
273
+ for (let xT = Math.max(tb.xMin, gb.xMin); xT <= Math.min(tb.xMax, gb.xMax); xT++) {
274
+ if (!this.cache[xT]) continue
275
+ for (let yT = Math.max(tb.yMin, gb.yMin); yT <= Math.min(tb.yMax, gb.yMax); yT++) {
276
+ //get tile
277
+ /** @type {GridTile} */
278
+ const tile = this.cache[xT][yT]
279
+ if (!tile || typeof tile === 'string') continue
280
+
281
+ //get cells
282
+ //this.cellsViewCache = this.cellsViewCache.concat(tile.cells)
283
+
284
+ for (const cell of tile.cells) {
285
+ if (+cell.x + this.resolution < extGeo.xMin) continue
286
+ if (+cell.x - this.resolution > extGeo.xMax) continue
287
+ if (+cell.y + this.resolution < extGeo.yMin) continue
288
+ if (+cell.y - this.resolution > extGeo.yMax) continue
289
+ this.cellsViewCache.push(cell)
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }