gridviz 3.0.32 → 3.0.34
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/dist/gridviz.js +1929 -1052
- package/dist/gridviz.min.js +1 -1
- package/package.json +1 -1
- package/src/core/Drawable.js +2 -2
- package/src/core/Map.js +8 -4
- package/src/index.js +3 -1
- package/src/layer/GeoJSONLayer.js +132 -80
- package/src/layer/GridLayer.js +21 -4
- package/src/legend/TernaryLegend.js +1 -1
- package/src/style/Interpolator.js +258 -0
- package/src/style/JoyPlotStyle.js +63 -10
- package/src/style/ShadingRayStyle.js +280 -0
- package/src/style/SquareColorCategoryWebGLStyle.js +2 -2
- package/src/utils/utils.js +67 -0
package/package.json
CHANGED
package/src/core/Drawable.js
CHANGED
|
@@ -30,7 +30,7 @@ export class Drawable {
|
|
|
30
30
|
* The function parameter is the zoom level.
|
|
31
31
|
* (see CanvasRenderingContext2D: globalCompositeOperation property)
|
|
32
32
|
* @type {function(number):GlobalCompositeOperation} */
|
|
33
|
-
this.blendOperation = opts.blendOperation
|
|
33
|
+
this.blendOperation = opts.blendOperation //|| ((z) => 'source-over')
|
|
34
34
|
|
|
35
35
|
/** @type {(function(number):string)|undefined} */
|
|
36
36
|
this.filterColor = opts.filterColor // (z) => "#eee7"
|
|
@@ -39,7 +39,7 @@ export class Drawable {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Draw
|
|
42
|
+
* Draw filter.
|
|
43
43
|
*
|
|
44
44
|
* @param {import("./GeoCanvas.js").GeoCanvas} geoCanvas The canvas where to draw the layer.
|
|
45
45
|
* @returns {void}
|
package/src/core/Map.js
CHANGED
|
@@ -182,14 +182,19 @@ export class Map {
|
|
|
182
182
|
const z = this.geoCanvas.view.z
|
|
183
183
|
this.updateExtentGeo()
|
|
184
184
|
|
|
185
|
+
const ctx = this.geoCanvas.offscreenCtx
|
|
186
|
+
|
|
185
187
|
//go through the layers
|
|
186
188
|
for (const layer of this.layers) {
|
|
187
189
|
//check if layer is visible
|
|
188
190
|
if (layer.visible && !layer.visible(z)) continue
|
|
189
191
|
|
|
190
192
|
//set layer alpha and blend mode
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
if (layer.alpha || layer.blendOperation) {
|
|
194
|
+
ctx.save()
|
|
195
|
+
if (layer.alpha) ctx.globalAlpha = layer.alpha(z)
|
|
196
|
+
if (layer.blendOperation) ctx.globalCompositeOperation = layer.blendOperation(z)
|
|
197
|
+
}
|
|
193
198
|
|
|
194
199
|
//set affin transform to draw with geographical coordinates
|
|
195
200
|
this.geoCanvas.setCanvasTransform()
|
|
@@ -201,8 +206,7 @@ export class Map {
|
|
|
201
206
|
if (layer.filterColor) layer.drawFilter(this.geoCanvas)
|
|
202
207
|
|
|
203
208
|
//restore default alpha and blend operation
|
|
204
|
-
|
|
205
|
-
this.geoCanvas.offscreenCtx.globalCompositeOperation = this.defaultGlobalCompositeOperation
|
|
209
|
+
if (layer.alpha || layer.blendOperation) ctx.restore()
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
// one drawImage call: draw the offscreen canvas to the main canvas
|
package/src/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export { SegmentStyle } from './style/SegmentStyle.js'
|
|
|
23
23
|
export { TextStyle } from './style/TextStyle.js'
|
|
24
24
|
export { PillarStyle } from './style/PillarStyle.js'
|
|
25
25
|
export { SideStyle } from './style/SideStyle.js'
|
|
26
|
+
export { ShadingRayStyle } from './style/ShadingRayStyle.js'
|
|
26
27
|
export { ShadingStyle } from './style/ShadingStyle.js'
|
|
27
28
|
export { SideCategoryStyle } from './style/SideCategoryStyle.js'
|
|
28
29
|
export { DotDensityStyle } from './style/DotDensityStyle.js'
|
|
@@ -35,6 +36,7 @@ export { NinjaStarStyle } from './style/NinjaStarStyle.js'
|
|
|
35
36
|
export { TimeSeriesStyle } from './style/TimeSeriesStyle.js'
|
|
36
37
|
export { IsoFenceStyle } from './style/IsoFenceStyle.js'
|
|
37
38
|
export { ImageStyle } from './style/ImageStyle.js'
|
|
39
|
+
export { Interpolator } from './style/Interpolator.js'
|
|
38
40
|
|
|
39
41
|
// export additional layers
|
|
40
42
|
export { GridLayer } from './layer/GridLayer.js'
|
|
@@ -62,7 +64,7 @@ export { TernaryLegend } from './legend/TernaryLegend.js'
|
|
|
62
64
|
export * from './utils/stretching.js'
|
|
63
65
|
export * from './utils/scale.js'
|
|
64
66
|
export * from './utils/ternary.js'
|
|
65
|
-
export { nice } from './utils/utils.js'
|
|
67
|
+
export { nice, cellsToMatrix } from './utils/utils.js'
|
|
66
68
|
|
|
67
69
|
import { GeoCanvas } from './core/GeoCanvas.js'
|
|
68
70
|
export const getParameterByName = GeoCanvas.getParameterByName
|
|
@@ -30,6 +30,7 @@ export class GeoJSONLayer extends Layer {
|
|
|
30
30
|
this.preprocess = opts.preprocess
|
|
31
31
|
|
|
32
32
|
//for points
|
|
33
|
+
|
|
33
34
|
/**
|
|
34
35
|
* @private
|
|
35
36
|
* @type {function(object,number):string} */
|
|
@@ -39,36 +40,32 @@ export class GeoJSONLayer extends Layer {
|
|
|
39
40
|
* @private
|
|
40
41
|
* @type {function(object,number):number} */
|
|
41
42
|
this.size = opts.size || ((f, z) => 10)
|
|
42
|
-
/**
|
|
43
|
-
* @private
|
|
44
|
-
* @type {function(object,number):string} */
|
|
45
|
-
this.strokeStyle = opts.strokeStyle || ((f, z) => 'red')
|
|
46
|
-
/**
|
|
47
|
-
* @private
|
|
48
|
-
* @type {function(object,number):string} */
|
|
49
|
-
this.fillStyle = opts.fillStyle || ((f, z) => 'black')
|
|
50
|
-
/**
|
|
51
|
-
* In pixel
|
|
52
|
-
* @private
|
|
53
|
-
* @type {function(object,number):number} */
|
|
54
|
-
this.lineWidth = opts.lineWidth || ((f, z) => 2)
|
|
55
43
|
|
|
56
|
-
//for lines
|
|
44
|
+
// for lines and polygon outlines
|
|
57
45
|
|
|
58
46
|
/**
|
|
59
47
|
* @private
|
|
60
48
|
* @type {function(object,number):string} */
|
|
61
|
-
this.
|
|
49
|
+
this.strokeStyle = opts.strokeStyle || opts.color || ((f, z) => 'gray')
|
|
62
50
|
/**
|
|
63
51
|
* In pixel
|
|
64
52
|
* @private
|
|
65
53
|
* @type {function(object,number):number} */
|
|
66
|
-
this.
|
|
54
|
+
this.lineWidth = opts.lineWidth || opts.width || ((f, z) => 2)
|
|
67
55
|
/**
|
|
68
56
|
* @private
|
|
69
57
|
* @type {function(object,number):Array.<number>|undefined} */
|
|
70
58
|
this.lineDash = opts.lineDash || ((f, z) => undefined)
|
|
71
59
|
|
|
60
|
+
|
|
61
|
+
// for polygon filling and point symbol filling
|
|
62
|
+
/**
|
|
63
|
+
* @private
|
|
64
|
+
* @type {function(object,number):string} */
|
|
65
|
+
this.fillStyle = opts.fillStyle || ((f, z) => 'black')
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
72
69
|
/**
|
|
73
70
|
* @private
|
|
74
71
|
* @type {Array.<object> | undefined} */
|
|
@@ -96,81 +93,136 @@ export class GeoJSONLayer extends Layer {
|
|
|
96
93
|
const z = geoCanvas.view.z
|
|
97
94
|
const ctx = geoCanvas.offscreenCtx
|
|
98
95
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
96
|
+
// draw each feature
|
|
97
|
+
for (const f of this.fs)
|
|
98
|
+
this.drawGeometry(f, f.geometry, f.geometry.type, z, ctx)
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @private
|
|
104
|
+
* Draw a geometry on the canvas, according to its type and the layer style.
|
|
105
|
+
* @param {object} f The feature to draw
|
|
106
|
+
* @param {object} geom The geometry of the feature to draw
|
|
107
|
+
* @param {string} gt The geometry type (e.g. 'Point', 'LineString', etc.)
|
|
108
|
+
* @param {number} z The current zoom level of the map
|
|
109
|
+
* @param {CanvasRenderingContext2D} ctx The canvas context to draw on
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
drawGeometry(f, geom, gt, z, ctx) {
|
|
113
|
+
if (gt == 'Point' || gt == 'MultiPoint') {
|
|
114
|
+
|
|
115
|
+
//get style parameters for the point feature
|
|
116
|
+
const shape = this.shape(f, z)
|
|
117
|
+
if (!shape || shape == 'none') return
|
|
118
|
+
const size = this.size(f, z) * z
|
|
119
|
+
if (!size) return
|
|
120
|
+
|
|
121
|
+
const strokeStyle = this.strokeStyle(f, z)
|
|
122
|
+
const fillStyle = this.fillStyle(f, z)
|
|
123
|
+
const lineWidth = this.lineWidth(f, z) * z
|
|
124
|
+
|
|
125
|
+
//set canvas drawing parameters
|
|
126
|
+
ctx.save()
|
|
127
|
+
if (strokeStyle) ctx.strokeStyle = strokeStyle
|
|
128
|
+
if (fillStyle) ctx.fillStyle = fillStyle
|
|
129
|
+
if (lineWidth) ctx.lineWidth = lineWidth
|
|
130
|
+
|
|
131
|
+
let cs = geom.coordinates
|
|
132
|
+
if (gt == 'Point') cs = [cs]
|
|
133
|
+
|
|
134
|
+
if (shape == 'circle') {
|
|
135
|
+
//draw circle - fill and stroke
|
|
136
|
+
for (const c of cs) {
|
|
137
|
+
ctx.beginPath()
|
|
138
|
+
ctx.arc(c[0], c[1], size / 2, 0, 2 * Math.PI, false)
|
|
139
|
+
if (fillStyle) ctx.fill()
|
|
140
|
+
if (strokeStyle && lineWidth) ctx.stroke()
|
|
141
|
+
}
|
|
142
|
+
} else if (shape == 'square') {
|
|
143
|
+
//draw square - fill and stroke
|
|
144
|
+
for (const c of cs) {
|
|
145
|
+
ctx.beginPath()
|
|
146
|
+
ctx.rect(c[0] - size / 2, c[1] - size / 2, size, size)
|
|
147
|
+
if (fillStyle) ctx.fill()
|
|
148
|
+
if (strokeStyle && lineWidth) ctx.stroke()
|
|
139
149
|
}
|
|
140
|
-
} else
|
|
150
|
+
} else {
|
|
151
|
+
console.error('Unexpected shape for point GeoJSON: ' + shape)
|
|
152
|
+
}
|
|
153
|
+
ctx.restore()
|
|
154
|
+
|
|
155
|
+
} else if (gt == 'LineString' || gt == 'MultiLineString') {
|
|
141
156
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!col || col == 'none') continue
|
|
145
|
-
ctx.strokeStyle = col
|
|
157
|
+
const col = this.strokeStyle(f, z)
|
|
158
|
+
if (!col || col === 'none') return
|
|
146
159
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!wP || wP < 0) continue
|
|
150
|
-
ctx.lineWidth = wP * z
|
|
160
|
+
const wP = this.lineWidth(f, z)
|
|
161
|
+
if (!wP || wP < 0) return
|
|
151
162
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
ctx.save()
|
|
164
|
+
ctx.strokeStyle = col
|
|
165
|
+
ctx.lineWidth = wP * z
|
|
166
|
+
ctx.setLineDash(this.lineDash(f, z) || [])
|
|
167
|
+
|
|
168
|
+
//
|
|
169
|
+
const css = gt === 'LineString' ? [geom.coordinates] : geom.coordinates
|
|
170
|
+
|
|
171
|
+
for (const cs of css) {
|
|
172
|
+
if (cs.length < 2) continue
|
|
173
|
+
ctx.beginPath()
|
|
174
|
+
ctx.moveTo(cs[0][0], cs[0][1])
|
|
175
|
+
for (let i = 1; i < cs.length; i++) ctx.lineTo(cs[i][0], cs[i][1])
|
|
176
|
+
ctx.stroke()
|
|
177
|
+
}
|
|
155
178
|
|
|
156
|
-
|
|
157
|
-
if (gt == 'LineString') css = [css]
|
|
179
|
+
ctx.restore()
|
|
158
180
|
|
|
159
|
-
|
|
160
|
-
|
|
181
|
+
} else if (gt == 'Polygon' || gt == 'MultiPolygon') {
|
|
182
|
+
ctx.save()
|
|
183
|
+
|
|
184
|
+
// A Polygon is [ outerRing, ...holes ]. A MultiPolygon is [ [ outerRing, ...holes ], ... ]
|
|
185
|
+
const polygons = gt === 'Polygon' ? [geom.coordinates] : geom.coordinates
|
|
186
|
+
|
|
187
|
+
ctx.beginPath()
|
|
188
|
+
|
|
189
|
+
for (const rings of polygons) {
|
|
190
|
+
for (const cs of rings) {
|
|
161
191
|
if (cs.length < 2) continue
|
|
162
|
-
ctx.beginPath()
|
|
163
192
|
ctx.moveTo(cs[0][0], cs[0][1])
|
|
164
193
|
for (let i = 1; i < cs.length; i++) ctx.lineTo(cs[i][0], cs[i][1])
|
|
194
|
+
ctx.closePath()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fill
|
|
199
|
+
const fillStyle = this.fillStyle(f, z)
|
|
200
|
+
if (fillStyle && fillStyle !== 'none') {
|
|
201
|
+
ctx.fillStyle = fillStyle
|
|
202
|
+
ctx.fill('evenodd') // even-odd punches holes correctly regardless of ring winding order
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Stroke (optional, only if style provides a width)
|
|
206
|
+
const wP = this.lineWidth(f, z)
|
|
207
|
+
if (wP && wP > 0) {
|
|
208
|
+
const strokeStyle = this.strokeStyle?.(f, z) ?? fillStyle
|
|
209
|
+
if (strokeStyle && strokeStyle !== 'none') {
|
|
210
|
+
ctx.strokeStyle = strokeStyle
|
|
211
|
+
ctx.lineWidth = wP * z
|
|
212
|
+
ctx.setLineDash(this.lineDash?.(f, z) || [])
|
|
165
213
|
ctx.stroke()
|
|
166
214
|
}
|
|
167
|
-
} else {
|
|
168
|
-
console.log('Unsupported geometry type in GeoJSONLayer: ' + gt)
|
|
169
215
|
}
|
|
170
|
-
|
|
216
|
+
ctx.restore()
|
|
217
|
+
|
|
218
|
+
} else if (gt == 'GeometryCollection') {
|
|
219
|
+
//recursive call for each geometry in the collection
|
|
220
|
+
for (const g of geom.geometries)
|
|
221
|
+
this.drawGeometry(f, g, g.type, z, ctx)
|
|
171
222
|
|
|
172
|
-
|
|
173
|
-
|
|
223
|
+
} else {
|
|
224
|
+
console.log('Unexpected geometry type in GeoJSONLayer: ' + gt)
|
|
225
|
+
}
|
|
174
226
|
}
|
|
175
227
|
|
|
176
228
|
/**
|
|
@@ -180,7 +232,7 @@ export class GeoJSONLayer extends Layer {
|
|
|
180
232
|
*/
|
|
181
233
|
async load(callback) {
|
|
182
234
|
if (!this.url) {
|
|
183
|
-
console.log('Failed loading
|
|
235
|
+
console.log('Failed loading GeoJSON data: No URL specified. ' + this.url)
|
|
184
236
|
this.loadingStatus = 'failed'
|
|
185
237
|
this.labels = []
|
|
186
238
|
return
|
|
@@ -216,7 +268,7 @@ export class GeoJSONLayer extends Layer {
|
|
|
216
268
|
//redraw
|
|
217
269
|
if (callback) callback()
|
|
218
270
|
} catch (error) {
|
|
219
|
-
console.log('Failed loading
|
|
271
|
+
console.log('Failed loading GeoJSON from ' + this.url)
|
|
220
272
|
this.fs = []
|
|
221
273
|
this.loadingStatus = 'failed'
|
|
222
274
|
}
|
package/src/layer/GridLayer.js
CHANGED
|
@@ -60,15 +60,26 @@ export class GridLayer extends Layer {
|
|
|
60
60
|
//update dataset view cache
|
|
61
61
|
dsc.updateViewCache(geoCanvas.extGeo)
|
|
62
62
|
|
|
63
|
+
|
|
64
|
+
//set layer alpha and blend mode
|
|
65
|
+
if (this.alpha || this.blendOperation) {
|
|
66
|
+
ctx.save()
|
|
67
|
+
if (this.alpha) ctx.globalAlpha = this.alpha(z)
|
|
68
|
+
if (this.blendOperation) ctx.globalCompositeOperation = this.blendOperation(z)
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
//draw cells, style by style
|
|
64
72
|
for (const s of this.styles) {
|
|
65
73
|
//check if style is visible
|
|
66
74
|
if (s.visible && !s.visible(z)) continue
|
|
67
75
|
|
|
68
|
-
//set style alpha and blend mode
|
|
69
|
-
//TODO: multiply by layer alpha ?
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
// set style alpha and blend mode
|
|
77
|
+
//TODO: multiply style alpha by layer alpha ?
|
|
78
|
+
if (s.alpha || s.blendOperation) {
|
|
79
|
+
ctx.save()
|
|
80
|
+
if (s.alpha) ctx.globalAlpha = s.alpha(z)
|
|
81
|
+
if (s.blendOperation) ctx.globalCompositeOperation = s.blendOperation(z)
|
|
82
|
+
}
|
|
72
83
|
|
|
73
84
|
//set affin transform to draw with geographical coordinates
|
|
74
85
|
geoCanvas.setCanvasTransform()
|
|
@@ -78,8 +89,14 @@ export class GridLayer extends Layer {
|
|
|
78
89
|
|
|
79
90
|
//draw style filter
|
|
80
91
|
if (s.filterColor) s.drawFilter(geoCanvas)
|
|
92
|
+
|
|
93
|
+
//restore default alpha and blend operation - before style change
|
|
94
|
+
if (s.alpha || s.blendOperation) ctx.restore()
|
|
81
95
|
}
|
|
82
96
|
|
|
97
|
+
//restore default alpha and blend operation - before layer change
|
|
98
|
+
if (this.alpha || this.blendOperation) ctx.restore()
|
|
99
|
+
|
|
83
100
|
//add legend element
|
|
84
101
|
if (legend) {
|
|
85
102
|
for (const s of this.styles) {
|
|
@@ -19,7 +19,7 @@ export class TernaryLegend extends Legend {
|
|
|
19
19
|
this.classifier = opts.classifier
|
|
20
20
|
|
|
21
21
|
this.width = opts.width || 150
|
|
22
|
-
this.selectionColor =
|
|
22
|
+
this.selectionColor = opts.selectionColor || 'red'
|
|
23
23
|
this.tooltip = opts.tooltip
|
|
24
24
|
this.texts = opts.texts
|
|
25
25
|
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Style } from '../core/Style.js'
|
|
5
|
+
import { cellsToMatrix } from '../utils/utils.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @module style
|
|
9
|
+
* @author Julien Gaffuri
|
|
10
|
+
*/
|
|
11
|
+
export class Interpolator extends Style {
|
|
12
|
+
/** @param {object} opts */
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
super(opts)
|
|
15
|
+
opts = opts || {}
|
|
16
|
+
|
|
17
|
+
/** The cell value to interpolate
|
|
18
|
+
* @type { function } } */
|
|
19
|
+
this.value = opts.value //(c) => elevation
|
|
20
|
+
|
|
21
|
+
/** The target resolution. As a function (resolution, z) => targetResolution
|
|
22
|
+
* NB: Try to make sure that resolution/targetResolution is an integer.
|
|
23
|
+
* @type { function } } */
|
|
24
|
+
this.targetResolution = opts.targetResolution || ((r, z) => r / 5)
|
|
25
|
+
|
|
26
|
+
/** the property name to store the interpolated value in the cell
|
|
27
|
+
* @type { string } } */
|
|
28
|
+
this.interpolatedProperty = opts.interpolatedProperty || 'value'
|
|
29
|
+
|
|
30
|
+
// the interpolation method: currently only 'bilinear' is supported
|
|
31
|
+
//this.method = opts.method || 'bilinear' // 'nearest', 'bilinear'
|
|
32
|
+
|
|
33
|
+
/** when a neighbor cell has no value, take zero value or average of surrounding cells
|
|
34
|
+
* @type { string } } */
|
|
35
|
+
this.nodataHandling = opts.nodataHandling || "zero" // 'zero', 'average'
|
|
36
|
+
|
|
37
|
+
/** The styles to represent the interpolated grid.
|
|
38
|
+
* @type {Array.<Style>} */
|
|
39
|
+
this.styles = opts.styles || []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* @param {Array.<import("../core/Dataset.js").Cell>} cells
|
|
46
|
+
* @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
|
|
47
|
+
* @param {number} resolution
|
|
48
|
+
*/
|
|
49
|
+
draw(cells, geoCanvas, resolution) {
|
|
50
|
+
|
|
51
|
+
//filter cells
|
|
52
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
53
|
+
if (cells.length == 0) return;
|
|
54
|
+
|
|
55
|
+
//
|
|
56
|
+
const z = geoCanvas.view.z
|
|
57
|
+
|
|
58
|
+
//get view scale
|
|
59
|
+
//const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
|
|
60
|
+
|
|
61
|
+
//index cells by y and x
|
|
62
|
+
let m = cellsToMatrix(cells, resolution, c => this.value(c))
|
|
63
|
+
const x0 = m.x0, y0 = m.y0
|
|
64
|
+
|
|
65
|
+
//get target resolution and scale factor
|
|
66
|
+
let targetResolution = this.targetResolution(resolution, z)
|
|
67
|
+
const scaleFactor = Math.round(resolution / targetResolution)
|
|
68
|
+
targetResolution = resolution / scaleFactor
|
|
69
|
+
|
|
70
|
+
// compute ray casting shadow
|
|
71
|
+
m = bilinearInterpolator(m, scaleFactor, this.nodataHandling);
|
|
72
|
+
|
|
73
|
+
// make cells
|
|
74
|
+
cells = []
|
|
75
|
+
const ra = (resolution - targetResolution) / 2
|
|
76
|
+
for (let i = 0; i < m.length; i++) {
|
|
77
|
+
const row = m[i]
|
|
78
|
+
const y = y0 - i * targetResolution + ra
|
|
79
|
+
for (let j = 0; j < row.length; j++) {
|
|
80
|
+
const v = row[j]
|
|
81
|
+
if (!v) continue
|
|
82
|
+
const c = { x: x0 + j * targetResolution + ra, y: y }
|
|
83
|
+
c[this.interpolatedProperty] = v
|
|
84
|
+
cells.push(c)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//draw smoothed cells from styles
|
|
89
|
+
const ctx = geoCanvas.offscreenCtx
|
|
90
|
+
for (let s of this.styles) {
|
|
91
|
+
|
|
92
|
+
//check if style is visible
|
|
93
|
+
if (s.visible && !s.visible(z)) continue
|
|
94
|
+
|
|
95
|
+
//set style alpha and blend mode
|
|
96
|
+
//TODO: multiply by layer alpha ?
|
|
97
|
+
if (s.alpha || s.blendOperation) {
|
|
98
|
+
ctx.save()
|
|
99
|
+
if (s.alpha) ctx.globalAlpha = s.alpha(z)
|
|
100
|
+
if (s.blendOperation) ctx.globalCompositeOperation = s.blendOperation(z)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//set affin transform to draw with geographical coordinates
|
|
104
|
+
geoCanvas.setCanvasTransform()
|
|
105
|
+
|
|
106
|
+
//draw with style
|
|
107
|
+
s.draw(cells, geoCanvas, targetResolution)
|
|
108
|
+
|
|
109
|
+
//draw style filter
|
|
110
|
+
if (s.filterColor) s.drawFilter(geoCanvas)
|
|
111
|
+
|
|
112
|
+
//restore ctx
|
|
113
|
+
if (s.alpha || s.blendOperation) ctx.restore()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
const interp = (topLeft, topRight, bottomLeft, bottomRight, xRatio, yRatio) => {
|
|
121
|
+
const top = topLeft + (topRight - topLeft) * xRatio;
|
|
122
|
+
const bottom = bottomLeft + (bottomRight - bottomLeft) * xRatio;
|
|
123
|
+
return top + (bottom - top) * yRatio;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
function bilinearInterpolator(grid, scaleFactor = 5, nodataHandling = "zero") {
|
|
128
|
+
|
|
129
|
+
if (scaleFactor === 1) return grid;
|
|
130
|
+
|
|
131
|
+
// input grid dimensions
|
|
132
|
+
const rows = grid.length;
|
|
133
|
+
if (rows === 0) return [];
|
|
134
|
+
const cols = grid[0].length;
|
|
135
|
+
|
|
136
|
+
// output grid dimensions
|
|
137
|
+
const fineRows = rows * scaleFactor;
|
|
138
|
+
const fineCols = cols * scaleFactor;
|
|
139
|
+
const fineGrid = Array(fineRows).fill().map(() => Array(fineCols).fill(undefined));
|
|
140
|
+
|
|
141
|
+
// check if scale factor is even
|
|
142
|
+
const sfIsEven = scaleFactor % 2 === 0
|
|
143
|
+
|
|
144
|
+
//compute output grid values
|
|
145
|
+
for (let i = 0; i < fineRows; i++) {
|
|
146
|
+
|
|
147
|
+
const coarseI = Math.min(Math.floor(i / scaleFactor), rows - 2);
|
|
148
|
+
const yRatio = (i % scaleFactor) / scaleFactor;
|
|
149
|
+
const i_ = coarseI * scaleFactor + (scaleFactor / 2)
|
|
150
|
+
|
|
151
|
+
for (let j = 0; j < fineCols; j++) {
|
|
152
|
+
const coarseJ = Math.min(Math.floor(j / scaleFactor), cols - 2);
|
|
153
|
+
const xRatio = (j % scaleFactor) / scaleFactor;
|
|
154
|
+
const j_ = coarseJ * scaleFactor + (scaleFactor / 2)
|
|
155
|
+
|
|
156
|
+
// get four corner values
|
|
157
|
+
const topLeft = grid[coarseI][coarseJ];
|
|
158
|
+
const topRight = grid[coarseI][coarseJ + 1];
|
|
159
|
+
const bottomLeft = grid[coarseI + 1][coarseJ];
|
|
160
|
+
const bottomRight = grid[coarseI + 1][coarseJ + 1];
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if (sfIsEven) {
|
|
164
|
+
if (topLeft === undefined && i <= i_ && j <= j_) continue
|
|
165
|
+
if (topRight === undefined && i <= i_ && j >= j_) continue
|
|
166
|
+
if (bottomLeft === undefined && i >= i_ && j <= j_) continue
|
|
167
|
+
if (bottomRight === undefined && i >= i_ && j >= j_) continue
|
|
168
|
+
} else {
|
|
169
|
+
if (topLeft === undefined && i < i_ && j < j_) continue
|
|
170
|
+
if (topRight === undefined && i < i_ && j > j_) continue
|
|
171
|
+
if (bottomLeft === undefined && i > i_ && j < j_) continue
|
|
172
|
+
if (bottomRight === undefined && i > i_ && j > j_) continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if(nodataHandling === "zero") {
|
|
176
|
+
// replace undefined values by zero
|
|
177
|
+
fineGrid[i][j] = interp(topLeft || 0, topRight || 0, bottomLeft || 0, bottomRight || 0, xRatio, yRatio)
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// general case: bilinear interpolation
|
|
182
|
+
// If all four are defined, interpolate normally
|
|
183
|
+
if (topLeft !== undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight !== undefined) {
|
|
184
|
+
fineGrid[i][j] = interp(topLeft, topRight, bottomLeft, bottomRight, xRatio, yRatio)
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// If only one is not defined, use average value of 2 adjacents and interpolate
|
|
189
|
+
if (topLeft === undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight !== undefined) {
|
|
190
|
+
const v = topRight + bottomLeft
|
|
191
|
+
fineGrid[i][j] = interp(v / 2, topRight, bottomLeft, bottomRight, xRatio, yRatio)
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
if (topLeft !== undefined && topRight === undefined && bottomLeft !== undefined && bottomRight !== undefined) {
|
|
195
|
+
const v = topLeft + bottomRight
|
|
196
|
+
fineGrid[i][j] = interp(topLeft, v / 2, bottomLeft, bottomRight, xRatio, yRatio)
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
if (topLeft !== undefined && topRight !== undefined && bottomLeft === undefined && bottomRight !== undefined) {
|
|
200
|
+
const v = topLeft + bottomRight
|
|
201
|
+
fineGrid[i][j] = interp(topLeft, topRight, v / 2, bottomRight, xRatio, yRatio)
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
if (topLeft !== undefined && topRight !== undefined && bottomLeft !== undefined && bottomRight === undefined) {
|
|
205
|
+
const v = topRight + bottomLeft
|
|
206
|
+
fineGrid[i][j] = interp(topLeft, topRight, bottomLeft, v / 2, xRatio, yRatio)
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// If only two diagonally opposite points are defined, interpolate along diagonal
|
|
211
|
+
if (topLeft !== undefined && bottomRight !== undefined) {
|
|
212
|
+
const t = (xRatio + yRatio) * Math.SQRT1_2
|
|
213
|
+
fineGrid[i][j] = topLeft * (1 - t) + bottomRight * t;
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
216
|
+
if (topRight !== undefined && bottomLeft !== undefined) {
|
|
217
|
+
const t = (1 - xRatio + yRatio) * Math.SQRT1_2
|
|
218
|
+
fineGrid[i][j] = topRight * (1 - t) + bottomLeft * t;
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If only two adjacent points are defined, interpolate between them
|
|
223
|
+
if (topLeft !== undefined && bottomLeft !== undefined) {
|
|
224
|
+
fineGrid[i][j] = bottomLeft * yRatio + topLeft * (1 - yRatio);
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
if (topRight !== undefined && bottomRight !== undefined) {
|
|
228
|
+
fineGrid[i][j] = bottomRight * yRatio + topRight * (1 - yRatio);
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
if (topLeft !== undefined && topRight !== undefined) {
|
|
232
|
+
fineGrid[i][j] = topRight * xRatio + topLeft * (1 - xRatio);
|
|
233
|
+
}
|
|
234
|
+
if (bottomLeft !== undefined && bottomRight !== undefined) {
|
|
235
|
+
fineGrid[i][j] = bottomRight * xRatio + bottomLeft * (1 - xRatio);
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If only one point is defined, use that value
|
|
240
|
+
if (topLeft !== undefined) {
|
|
241
|
+
fineGrid[i][j] = topLeft;
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
if (topRight !== undefined) {
|
|
245
|
+
fineGrid[i][j] = topRight;
|
|
246
|
+
continue
|
|
247
|
+
}
|
|
248
|
+
if (bottomLeft !== undefined) {
|
|
249
|
+
fineGrid[i][j] = bottomLeft;
|
|
250
|
+
continue
|
|
251
|
+
}
|
|
252
|
+
if (bottomRight !== undefined) {
|
|
253
|
+
fineGrid[i][j] = bottomRight;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return fineGrid;
|
|
258
|
+
}
|