bruce-cesium 7.2.2 → 7.2.3
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/bruce-cesium.es5.js +419 -372
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +423 -377
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +2 -1
- package/dist/lib/bruce-cesium.js.map +1 -1
- package/dist/lib/rendering/displaced-surface-primitive.js +51 -43
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +26 -36
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +271 -267
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +65 -23
- package/dist/lib/rendering/texture-value-labels.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +2 -1
- package/dist/types/rendering/displaced-surface-primitive.d.ts +2 -10
- package/dist/types/rendering/texture-frame-series-animator.d.ts +161 -66
- package/dist/types/rendering/texture-value-labels.d.ts +22 -0
- package/package.json +2 -2
|
@@ -6,38 +6,221 @@ const Cesium = require("cesium");
|
|
|
6
6
|
const image_utils_1 = require("../internal/image-utils");
|
|
7
7
|
var TextureFrameSeriesAnimator;
|
|
8
8
|
(function (TextureFrameSeriesAnimator) {
|
|
9
|
-
// Values are measured up from the ground beneath them.
|
|
10
|
-
TextureFrameSeriesAnimator.QUANTITY_THICKNESS = "thickness";
|
|
11
|
-
// Values are heights against a vertical datum, so they need that datum to be placed.
|
|
12
|
-
TextureFrameSeriesAnimator.QUANTITY_ELEVATION = "elevation";
|
|
13
9
|
TextureFrameSeriesAnimator.LAYOUT_TILES = "tiles";
|
|
10
|
+
// The only generation this renderer draws. Anything else is refused rather than half read: the
|
|
11
|
+
// shapes differ enough that a partial reading produces a plausible wrong surface, which is worse
|
|
12
|
+
// than an empty one.
|
|
13
|
+
TextureFrameSeriesAnimator.SUPPORTED_VERSION = 3;
|
|
14
|
+
// Value in R at 8 bits, or high in R and low in G at 16.
|
|
15
|
+
TextureFrameSeriesAnimator.FORMAT_U8 = "u8";
|
|
16
|
+
TextureFrameSeriesAnimator.FORMAT_RG16 = "rg16";
|
|
17
|
+
// Declared as a field with one value, so a second mode can be added later without an absent
|
|
18
|
+
// Encoding having to mean anything.
|
|
19
|
+
TextureFrameSeriesAnimator.ENCODING_ABSOLUTE = "absolute";
|
|
20
|
+
TextureFrameSeriesAnimator.LAYER_VALUE = "Value";
|
|
21
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_MIN = "ValueMin";
|
|
22
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_MAX = "ValueMax";
|
|
23
|
+
TextureFrameSeriesAnimator.LAYER_BASELINE = "Baseline";
|
|
24
|
+
// The per-texel correction that was applied, published only on an archive that was transformed.
|
|
25
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_SHIFT = "ValueShift";
|
|
14
26
|
/**
|
|
15
|
-
*
|
|
27
|
+
* Turns a published value map into the flat shape this renderer works in, or null when it cannot be drawn.
|
|
28
|
+
* @param published the archive's Data.generation
|
|
29
|
+
*/
|
|
30
|
+
function Adapt(published) {
|
|
31
|
+
if (!published || published.Version !== TextureFrameSeriesAnimator.SUPPORTED_VERSION) {
|
|
32
|
+
const found = published && published.Version != null ? published.Version : "none";
|
|
33
|
+
const at = published && published.Identity ? published.Identity.Generated : undefined;
|
|
34
|
+
// In theory should be no files.
|
|
35
|
+
console.warn("bruce-cesium: value map version " + found + " is not supported (this build reads "
|
|
36
|
+
+ TextureFrameSeriesAnimator.SUPPORTED_VERSION + "), so nothing will be drawn for it."
|
|
37
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const geometry = published.Geometry || {};
|
|
41
|
+
const tiles = published.Tiles || [];
|
|
42
|
+
if (geometry.Layout !== TextureFrameSeriesAnimator.LAYOUT_TILES || tiles.length === 0) {
|
|
43
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
44
|
+
console.warn("bruce-cesium: value map declares no tiles, so nothing will be drawn for it."
|
|
45
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const value = published.Value || {};
|
|
49
|
+
if (value.Encoding && value.Encoding !== TextureFrameSeriesAnimator.ENCODING_ABSOLUTE) {
|
|
50
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
51
|
+
console.warn("bruce-cesium: value map encoding " + value.Encoding + " is not supported"
|
|
52
|
+
+ " (this build reads " + TextureFrameSeriesAnimator.ENCODING_ABSOLUTE + "), so nothing will be drawn for it."
|
|
53
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const series = published.Series || {};
|
|
57
|
+
const layerRange = declaredRange(published, TextureFrameSeriesAnimator.LAYER_VALUE_MIN);
|
|
58
|
+
const flat = {
|
|
59
|
+
Frames: [],
|
|
60
|
+
ValueMin: value.Min,
|
|
61
|
+
ValueMax: value.Max,
|
|
62
|
+
Format: value.Format || TextureFrameSeriesAnimator.FORMAT_U8,
|
|
63
|
+
Encoding: value.Encoding || TextureFrameSeriesAnimator.ENCODING_ABSOLUTE,
|
|
64
|
+
Units: value.Units,
|
|
65
|
+
West: geometry.West,
|
|
66
|
+
East: geometry.East,
|
|
67
|
+
South: geometry.South,
|
|
68
|
+
North: geometry.North,
|
|
69
|
+
ResolutionX: geometry.ResolutionX,
|
|
70
|
+
ResolutionY: geometry.ResolutionY,
|
|
71
|
+
PixelPlacement: geometry.PixelPlacement,
|
|
72
|
+
Times: series.Times,
|
|
73
|
+
ExtremesValueMin: layerRange ? layerRange.min : undefined,
|
|
74
|
+
ExtremesValueMax: layerRange ? layerRange.max : undefined,
|
|
75
|
+
// The delivered values are what gets drawn. What the source called them, and any shift
|
|
76
|
+
// applied on the way, travel for labels and analysis rather than for placement.
|
|
77
|
+
SourceVerticalDatum: published.ValueSource ? published.ValueSource.VerticalDatum : undefined,
|
|
78
|
+
Transform: published.ValueSource ? published.ValueSource.Transform : undefined,
|
|
79
|
+
SourceUnits: published.ValueSource ? published.ValueSource.Units : undefined,
|
|
80
|
+
SourceValueMin: published.ValueSource ? published.ValueSource.Min : undefined,
|
|
81
|
+
SourceValueMax: published.ValueSource ? published.ValueSource.Max : undefined
|
|
82
|
+
};
|
|
83
|
+
flat.Tiles = tiles.map((tile) => adaptTile(tile, series.Times));
|
|
84
|
+
// The coarsest tile covers the whole extent, so it is what a caller wanting one time list reads.
|
|
85
|
+
flat.Frames = flat.Tiles[0].Frames;
|
|
86
|
+
warnIfUnshifted(published, flat);
|
|
87
|
+
return flat;
|
|
88
|
+
}
|
|
89
|
+
TextureFrameSeriesAnimator.Adapt = Adapt;
|
|
90
|
+
/*
|
|
91
|
+
* Says so when an archive's heights are not on the ellipsoid they will be drawn against.
|
|
92
|
+
*/
|
|
93
|
+
function warnIfUnshifted(published, flat) {
|
|
94
|
+
const datum = flat.SourceVerticalDatum;
|
|
95
|
+
if (!datum || datum === TextureFrameSeriesAnimator.DATUM_ELLIPSOIDAL || flat.Transform) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
99
|
+
console.warn("bruce-cesium: value map heights are in " + datum + " and report no transform, so"
|
|
100
|
+
+ " they are not ellipsoidal and will draw at the wrong height when placed absolutely."
|
|
101
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating with the shift"
|
|
102
|
+
+ " applied." : ""));
|
|
103
|
+
}
|
|
104
|
+
/*
|
|
105
|
+
* The Min and Max a named layer declares, since a statistics layer is encoded over the true data
|
|
106
|
+
* range rather than over the frames' own.
|
|
107
|
+
*/
|
|
108
|
+
function declaredRange(published, name) {
|
|
109
|
+
const layers = published.Layers || [];
|
|
110
|
+
for (const layer of layers) {
|
|
111
|
+
if (layer.Name === name && typeof layer.Min === "number" && typeof layer.Max === "number") {
|
|
112
|
+
return { min: layer.Min, max: layer.Max };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/*
|
|
118
|
+
* One tile, with its per-time value rasters lined up against the shared time list.
|
|
119
|
+
*/
|
|
120
|
+
function adaptTile(tile, times) {
|
|
121
|
+
const layers = tile.Layers || {};
|
|
122
|
+
const perTime = layers[TextureFrameSeriesAnimator.LAYER_VALUE] || [];
|
|
123
|
+
const frames = perTime.map((entry, index) => ({
|
|
124
|
+
Timestamp: times && times[index] ? times[index] : "",
|
|
125
|
+
ByteOffset: entry.Bytes.Offset,
|
|
126
|
+
ByteLength: entry.Bytes.Length
|
|
127
|
+
}));
|
|
128
|
+
return {
|
|
129
|
+
Level: tile.Level,
|
|
130
|
+
X: tile.X,
|
|
131
|
+
Y: tile.Y,
|
|
132
|
+
West: tile.West,
|
|
133
|
+
East: tile.East,
|
|
134
|
+
South: tile.South,
|
|
135
|
+
North: tile.North,
|
|
136
|
+
TexelMetres: tile.TexelMetres,
|
|
137
|
+
ResolutionX: tile.ResolutionX,
|
|
138
|
+
ResolutionY: tile.ResolutionY,
|
|
139
|
+
Frames: frames,
|
|
140
|
+
Floor: single(layers[TextureFrameSeriesAnimator.LAYER_VALUE_MIN]),
|
|
141
|
+
Ceiling: single(layers[TextureFrameSeriesAnimator.LAYER_VALUE_MAX]),
|
|
142
|
+
BaselineMask: single(layers[TextureFrameSeriesAnimator.LAYER_BASELINE])
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function single(layer) {
|
|
146
|
+
if (!layer || !layer.Bytes) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return { ByteOffset: layer.Bytes.Offset, ByteLength: layer.Bytes.Length };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* How to turn a delivered value into the number the source published, or null when the archive
|
|
153
|
+
* does not say enough to do it.
|
|
154
|
+
* @param metadata the archive's Data.generation
|
|
155
|
+
*/
|
|
156
|
+
function SourceMap(metadata) {
|
|
157
|
+
if (!metadata) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const { ValueMin, ValueMax, SourceValueMin, SourceValueMax } = metadata;
|
|
161
|
+
if (typeof ValueMin !== "number" || typeof ValueMax !== "number"
|
|
162
|
+
|| typeof SourceValueMin !== "number" || typeof SourceValueMax !== "number") {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const delivered = ValueMax - ValueMin;
|
|
166
|
+
// A single delivered value describes no line, so all that can be recovered is where it sits.
|
|
167
|
+
const scale = delivered !== 0 ? (SourceValueMax - SourceValueMin) / delivered : 1;
|
|
168
|
+
return { scale, offset: SourceValueMin - ValueMin * scale };
|
|
169
|
+
}
|
|
170
|
+
TextureFrameSeriesAnimator.SourceMap = SourceMap;
|
|
171
|
+
/**
|
|
172
|
+
* A delivered value in the source's own terms, or unchanged when it cannot be turned back.
|
|
173
|
+
* @param map from SourceMap(), null when the archive does not say enough
|
|
174
|
+
*/
|
|
175
|
+
function ToSource(map, value) {
|
|
176
|
+
return map ? value * map.scale + map.offset : value;
|
|
177
|
+
}
|
|
178
|
+
TextureFrameSeriesAnimator.ToSource = ToSource;
|
|
179
|
+
/**
|
|
180
|
+
* Whether the archive's frames carry a 16 bit value packed across R and G.
|
|
16
181
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
182
|
+
* Every consumer that reads a texel has to ask: taking R alone from a packed frame yields a
|
|
183
|
+
* plausible surface quantised to 255 steps of the full range rather than an obvious failure.
|
|
19
184
|
* @param metadata the archive's Data.generation
|
|
20
185
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
186
|
+
function IsPackedValue(metadata) {
|
|
187
|
+
return Boolean(metadata && metadata.Format === TextureFrameSeriesAnimator.FORMAT_RG16);
|
|
188
|
+
}
|
|
189
|
+
TextureFrameSeriesAnimator.IsPackedValue = IsPackedValue;
|
|
190
|
+
/**
|
|
191
|
+
* The normalised 0 to 1 value at a texel, from whichever format the archive packed it in.
|
|
192
|
+
* @param metadata the archive's Data.generation
|
|
193
|
+
* @param pixels untinted RGBA value pixels
|
|
194
|
+
* @param at index of the texel's red channel
|
|
195
|
+
*/
|
|
196
|
+
function NormalisedAt(metadata, pixels, at) {
|
|
197
|
+
if (IsPackedValue(metadata)) {
|
|
198
|
+
return (pixels[at] * 256 + pixels[at + 1]) / 65535;
|
|
24
199
|
}
|
|
25
|
-
return
|
|
200
|
+
return pixels[at] / 255;
|
|
201
|
+
}
|
|
202
|
+
TextureFrameSeriesAnimator.NormalisedAt = NormalisedAt;
|
|
203
|
+
// Values already sit on the ellipsoid Cesium measures against, so nothing is added to place them.
|
|
204
|
+
TextureFrameSeriesAnimator.DATUM_ELLIPSOIDAL = "WGS84";
|
|
205
|
+
/**
|
|
206
|
+
* Whether the values are heights against a vertical datum rather than a thickness.
|
|
207
|
+
*
|
|
208
|
+
* A thickness rises from wherever the polygon sits and a height already knows where it belongs, so
|
|
209
|
+
* this is what decides whether the polygon's own altitude may be added underneath it.
|
|
210
|
+
* @param metadata the archive's Data.generation
|
|
211
|
+
*/
|
|
212
|
+
function IsDatumHeight(metadata) {
|
|
213
|
+
return Boolean(metadata && metadata.SourceVerticalDatum);
|
|
26
214
|
}
|
|
27
|
-
TextureFrameSeriesAnimator.
|
|
215
|
+
TextureFrameSeriesAnimator.IsDatumHeight = IsDatumHeight;
|
|
28
216
|
/**
|
|
29
|
-
*
|
|
217
|
+
* The archive's tiles, which Adapt() guarantees are present.
|
|
30
218
|
* @param metadata the archive's Data.generation
|
|
31
219
|
*/
|
|
32
|
-
function
|
|
33
|
-
return (
|
|
34
|
-
metadata.Tiles && metadata.Tiles.length > 0));
|
|
220
|
+
function TilesOf(metadata) {
|
|
221
|
+
return (metadata && metadata.Tiles) || [];
|
|
35
222
|
}
|
|
36
|
-
TextureFrameSeriesAnimator.
|
|
37
|
-
// Frames carry the value as the source reported it.
|
|
38
|
-
TextureFrameSeriesAnimator.MEASURE_ABSOLUTE = "absolute";
|
|
39
|
-
// Frames carry the value minus each cell's own minimum over the series.
|
|
40
|
-
TextureFrameSeriesAnimator.MEASURE_ANOMALY = "anomaly";
|
|
223
|
+
TextureFrameSeriesAnimator.TilesOf = TilesOf;
|
|
41
224
|
/**
|
|
42
225
|
* Turns a value in the attribute's own units into the 0 to 1 position the ramp is indexed by.
|
|
43
226
|
*
|
|
@@ -56,44 +239,18 @@ var TextureFrameSeriesAnimator;
|
|
|
56
239
|
return Math.min(1, Math.max(0, (value - lo) / span));
|
|
57
240
|
}
|
|
58
241
|
TextureFrameSeriesAnimator.NormalisePosition = NormalisePosition;
|
|
59
|
-
/**
|
|
60
|
-
* Whether the frames are a departure from each cell's own normal rather than a raw reading.
|
|
61
|
-
*
|
|
62
|
-
* Worth asking before labelling anything: the two measures need different words for the same
|
|
63
|
-
* number, and calling an anomaly "water depth" is a worse error than the coarse ramp it replaced.
|
|
64
|
-
* @param metadata the archive's Data.generation
|
|
65
|
-
*/
|
|
66
|
-
function IsAnomalyMeasure(metadata) {
|
|
67
|
-
return Boolean(metadata && metadata.Measure === TextureFrameSeriesAnimator.MEASURE_ANOMALY);
|
|
68
|
-
}
|
|
69
|
-
TextureFrameSeriesAnimator.IsAnomalyMeasure = IsAnomalyMeasure;
|
|
70
|
-
/**
|
|
71
|
-
* How much the value moves anywhere in the series, in source units.
|
|
72
|
-
*
|
|
73
|
-
* Falls back to the published range, which is the right answer for an anomaly archive (its range
|
|
74
|
-
* IS the movement) and the only available one for an archive predating AnomalyMax.
|
|
75
|
-
* @param metadata the archive's Data.generation
|
|
76
|
-
*/
|
|
77
|
-
function MovingRange(metadata) {
|
|
78
|
-
if (!metadata) {
|
|
79
|
-
return undefined;
|
|
80
|
-
}
|
|
81
|
-
if (typeof metadata.AnomalyMax === "number" && metadata.AnomalyMax > 0) {
|
|
82
|
-
return metadata.AnomalyMax;
|
|
83
|
-
}
|
|
84
|
-
if (typeof metadata.ValueMin === "number" && typeof metadata.ValueMax === "number") {
|
|
85
|
-
return Math.abs(metadata.ValueMax - metadata.ValueMin);
|
|
86
|
-
}
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
TextureFrameSeriesAnimator.MovingRange = MovingRange;
|
|
90
242
|
/**
|
|
91
243
|
* Detects whether a ClientFile's `Data.generation` metadata describes a frame archive rather than a single static image,
|
|
92
244
|
* so a caller can decide whether to construct an Animator or fall back to the existing static-texture path.
|
|
93
|
-
* @param data ClientFile.Data
|
|
245
|
+
* @param data ClientFile.Data
|
|
94
246
|
*/
|
|
95
247
|
function IsFrameArchiveMetadata(data) {
|
|
96
|
-
|
|
248
|
+
const generation = data && data.generation;
|
|
249
|
+
if (!generation) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return (typeof generation.Version === "number" ||
|
|
253
|
+
(Array.isArray(generation.Frames) && generation.Frames.length > 0));
|
|
97
254
|
}
|
|
98
255
|
TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
|
|
99
256
|
// First resolvable static number out of a calculator field list, since that is all a border needs.
|
|
@@ -139,10 +296,27 @@ var TextureFrameSeriesAnimator;
|
|
|
139
296
|
TextureFrameSeriesAnimator.AppearanceSignature = AppearanceSignature;
|
|
140
297
|
const MAX_COMPOSITE_TEXELS = 2048;
|
|
141
298
|
const VALUE_DILATE_PASSES = 2;
|
|
299
|
+
/*
|
|
300
|
+
* Rewrites a packed 16 bit value as the 8 bit grey the colour ramps index on.
|
|
301
|
+
*
|
|
302
|
+
* All three channels, since a ramp is free to read any of them and a leftover low byte in G would
|
|
303
|
+
* show up as a green cast on the one that reads them all.
|
|
304
|
+
*/
|
|
305
|
+
function flattenPacked(pixels, packed) {
|
|
306
|
+
if (!packed) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
310
|
+
const grey = Math.round((pixels[i] * 256 + pixels[i + 1]) / 65535 * 255);
|
|
311
|
+
pixels[i] = grey;
|
|
312
|
+
pixels[i + 1] = grey;
|
|
313
|
+
pixels[i + 2] = grey;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
142
316
|
/*
|
|
143
317
|
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
144
318
|
*/
|
|
145
|
-
function dilateValues(value, width, height, passes) {
|
|
319
|
+
function dilateValues(value, width, height, passes, packed) {
|
|
146
320
|
const texels = width * height;
|
|
147
321
|
const filled = new Uint8Array(texels);
|
|
148
322
|
for (let p = 0; p < texels; p++) {
|
|
@@ -166,15 +340,25 @@ var TextureFrameSeriesAnimator;
|
|
|
166
340
|
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !wasFilled[ny * width + nx]) {
|
|
167
341
|
continue;
|
|
168
342
|
}
|
|
169
|
-
|
|
343
|
+
const at = (ny * width + nx) * 4;
|
|
344
|
+
// Averaged as a value rather than per byte: averaging a packed low byte
|
|
345
|
+
// on its own wraps at every 256th step and speckles the bled edge.
|
|
346
|
+
sum += packed ? source[at] * 256 + source[at + 1] : source[at];
|
|
170
347
|
hits++;
|
|
171
348
|
}
|
|
172
349
|
}
|
|
173
350
|
if (hits > 0) {
|
|
174
351
|
const v = Math.round(sum / hits);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
352
|
+
if (packed) {
|
|
353
|
+
value[p * 4] = (v >> 8) & 255;
|
|
354
|
+
value[p * 4 + 1] = v & 255;
|
|
355
|
+
value[p * 4 + 2] = 0;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
value[p * 4] = v;
|
|
359
|
+
value[p * 4 + 1] = v;
|
|
360
|
+
value[p * 4 + 2] = v;
|
|
361
|
+
}
|
|
178
362
|
filled[p] = 1;
|
|
179
363
|
}
|
|
180
364
|
}
|
|
@@ -212,7 +396,6 @@ var TextureFrameSeriesAnimator;
|
|
|
212
396
|
// forcing Cesium to re-upload the texture only when something changed.
|
|
213
397
|
this.pool = [document.createElement("canvas"), document.createElement("canvas")];
|
|
214
398
|
this.poolIdx = 0;
|
|
215
|
-
this.scratch = null;
|
|
216
399
|
this.valueDims = null;
|
|
217
400
|
this.presentVersion = 0;
|
|
218
401
|
this.extremesLoad = null;
|
|
@@ -235,7 +418,7 @@ var TextureFrameSeriesAnimator;
|
|
|
235
418
|
this.highColor = (mask && bruce_models_1.Color.ColorFromStr(mask.maxColor)) || DEFAULT_HIGH_COLOR;
|
|
236
419
|
// Positions are authored in the attribute's units and normalised once here, so the
|
|
237
420
|
// per-pixel loop stays a comparison against 0 to 1 like the two-colour path.
|
|
238
|
-
const points = mask
|
|
421
|
+
const points = mask && mask.points;
|
|
239
422
|
this.rampStops = (points && points.length > 0)
|
|
240
423
|
? points.map((p) => ({
|
|
241
424
|
position: NormalisePosition(options.metadata, p.position),
|
|
@@ -261,9 +444,8 @@ var TextureFrameSeriesAnimator;
|
|
|
261
444
|
this.produceValueCanvas = Boolean(options.produceValueCanvas);
|
|
262
445
|
this.metadata = options.metadata;
|
|
263
446
|
this.drapeExtent = options.drapeExtent;
|
|
264
|
-
this.tiles =
|
|
265
|
-
this.
|
|
266
|
-
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : Boolean(options.baselineMask);
|
|
447
|
+
this.tiles = TilesOf(options.metadata);
|
|
448
|
+
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : this.tiles.some((tile) => Boolean(tile.BaselineMask));
|
|
267
449
|
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
268
450
|
this.driveMaterial = options.driveMaterial !== false;
|
|
269
451
|
this.originalMaterial = options.entity.polygon.material;
|
|
@@ -439,32 +621,20 @@ var TextureFrameSeriesAnimator;
|
|
|
439
621
|
}
|
|
440
622
|
async fetchAndTint(idx) {
|
|
441
623
|
const tiles = this.tiles;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const at = tile.Frames[idx];
|
|
451
|
-
if (!at) {
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
lo = Math.min(lo, at.ByteOffset);
|
|
455
|
-
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
624
|
+
// Every tile's frame for one timestep sits contiguously, because the generator writes
|
|
625
|
+
// frame major. So a tiled frame is still ONE range request, not one per tile.
|
|
626
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
627
|
+
let hi = 0;
|
|
628
|
+
for (const tile of tiles) {
|
|
629
|
+
const at = tile.Frames[idx];
|
|
630
|
+
if (!at) {
|
|
631
|
+
continue;
|
|
456
632
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
buffer = await response.arrayBuffer();
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
const entry = this.frames[idx];
|
|
463
|
-
const start = entry.ByteOffset;
|
|
464
|
-
const end = entry.ByteOffset + entry.ByteLength - 1;
|
|
465
|
-
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${start}-${end}` } });
|
|
466
|
-
buffer = await response.arrayBuffer();
|
|
633
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
634
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
467
635
|
}
|
|
636
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
637
|
+
const buffer = await response.arrayBuffer();
|
|
468
638
|
if (this.disposed) {
|
|
469
639
|
return;
|
|
470
640
|
}
|
|
@@ -474,9 +644,7 @@ var TextureFrameSeriesAnimator;
|
|
|
474
644
|
if (this.disposed) {
|
|
475
645
|
return;
|
|
476
646
|
}
|
|
477
|
-
const decoded = tiles
|
|
478
|
-
? await this.composeTiles(tiles, idx, buffer, span)
|
|
479
|
-
: await this.decodeAndTint(buffer);
|
|
647
|
+
const decoded = await this.composeTiles(tiles, idx, buffer, lo);
|
|
480
648
|
if (this.disposed) {
|
|
481
649
|
return;
|
|
482
650
|
}
|
|
@@ -487,26 +655,6 @@ var TextureFrameSeriesAnimator;
|
|
|
487
655
|
this.beginCrossfadeTo(idx);
|
|
488
656
|
}
|
|
489
657
|
}
|
|
490
|
-
/**
|
|
491
|
-
* Decodes one frame's raw PNG bytes (as returned by a Range GET) and applies the grayscale color mask.
|
|
492
|
-
*/
|
|
493
|
-
async decodeAndTint(buffer) {
|
|
494
|
-
const blob = new Blob([buffer], { type: "image/png" });
|
|
495
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
496
|
-
let image;
|
|
497
|
-
try {
|
|
498
|
-
image = await (0, image_utils_1.loadImage)(objectUrl);
|
|
499
|
-
}
|
|
500
|
-
finally {
|
|
501
|
-
URL.revokeObjectURL(objectUrl);
|
|
502
|
-
}
|
|
503
|
-
const canvas = document.createElement("canvas");
|
|
504
|
-
canvas.width = image.width;
|
|
505
|
-
canvas.height = image.height;
|
|
506
|
-
const ctx = canvas.getContext("2d");
|
|
507
|
-
ctx.drawImage(image, 0, 0);
|
|
508
|
-
return this.tint(ctx.getImageData(0, 0, canvas.width, canvas.height));
|
|
509
|
-
}
|
|
510
658
|
/*
|
|
511
659
|
* Draws every tile of one frame into a single raster covering the drape extent.
|
|
512
660
|
*/
|
|
@@ -579,6 +727,10 @@ var TextureFrameSeriesAnimator;
|
|
|
579
727
|
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
580
728
|
// recovered from the tinted pixels afterwards.
|
|
581
729
|
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
730
|
+
// The ramp indexes on R as an 8 bit grey, so a packed frame is flattened to one here
|
|
731
|
+
// rather than teaching every ramp function a second format.
|
|
732
|
+
// The copy above keeps the packed pair, which is what displacement and labels read.
|
|
733
|
+
flattenPacked(imageData.data, IsPackedValue(this.metadata));
|
|
582
734
|
// Stops win when supplied: they can express a band and a hidden floor, which a two
|
|
583
735
|
// colour ramp cannot. The pair stays the fallback so an older style still draws.
|
|
584
736
|
if (this.rampStops && this.rampStops.length > 0) {
|
|
@@ -592,72 +744,20 @@ var TextureFrameSeriesAnimator;
|
|
|
592
744
|
}
|
|
593
745
|
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
594
746
|
if (valuePixels) {
|
|
595
|
-
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES);
|
|
747
|
+
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
596
748
|
}
|
|
597
749
|
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
598
750
|
}
|
|
599
751
|
/*
|
|
600
|
-
* Paints one frame's pixels onto a canvas
|
|
752
|
+
* Paints one frame's composited pixels onto a canvas.
|
|
601
753
|
*/
|
|
602
754
|
writeInto(canvas, pixels, dims) {
|
|
603
|
-
const rect = this.sourceRect(dims);
|
|
604
|
-
if (!rect) {
|
|
605
|
-
canvas.width = dims.width;
|
|
606
|
-
canvas.height = dims.height;
|
|
607
|
-
const direct = canvas.getContext("2d");
|
|
608
|
-
const image = direct.createImageData(dims.width, dims.height);
|
|
609
|
-
image.data.set(pixels);
|
|
610
|
-
direct.putImageData(image, 0, 0);
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
if (!this.scratch) {
|
|
614
|
-
this.scratch = document.createElement("canvas");
|
|
615
|
-
}
|
|
616
|
-
this.scratch.width = dims.width;
|
|
617
|
-
this.scratch.height = dims.height;
|
|
618
|
-
const scratchCtx = this.scratch.getContext("2d");
|
|
619
|
-
const image = scratchCtx.createImageData(dims.width, dims.height);
|
|
620
|
-
image.data.set(pixels);
|
|
621
|
-
scratchCtx.putImageData(image, 0, 0);
|
|
622
755
|
canvas.width = dims.width;
|
|
623
756
|
canvas.height = dims.height;
|
|
624
757
|
const ctx = canvas.getContext("2d");
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
ctx.
|
|
628
|
-
ctx.clearRect(0, 0, dims.width, dims.height);
|
|
629
|
-
ctx.drawImage(this.scratch, rect.x, rect.y, rect.width, rect.height, 0, 0, dims.width, dims.height);
|
|
630
|
-
}
|
|
631
|
-
/*
|
|
632
|
-
* The part of the archive raster that the drape extent covers, in source pixels.
|
|
633
|
-
*/
|
|
634
|
-
sourceRect(dims) {
|
|
635
|
-
const drape = this.drapeExtent;
|
|
636
|
-
const m = this.metadata;
|
|
637
|
-
// A composited frame was drawn into the drape extent already, so resampling it again
|
|
638
|
-
// would apply the same correction twice.
|
|
639
|
-
if (this.tiles) {
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
if (!drape || !m || m.West == null || m.East == null || m.South == null || m.North == null) {
|
|
643
|
-
return null;
|
|
644
|
-
}
|
|
645
|
-
const spanLon = m.East - m.West;
|
|
646
|
-
const spanLat = m.North - m.South;
|
|
647
|
-
if (!(spanLon > 0) || !(spanLat > 0)) {
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
const x = (drape.West - m.West) / spanLon * dims.width;
|
|
651
|
-
const width = (drape.East - drape.West) / spanLon * dims.width;
|
|
652
|
-
const y = (m.North - drape.North) / spanLat * dims.height;
|
|
653
|
-
const height = (drape.North - drape.South) / spanLat * dims.height;
|
|
654
|
-
// A drape that already matches the archive is the common case and must not pay for a
|
|
655
|
-
// resample, nor lose a half pixel to rounding.
|
|
656
|
-
if (Math.abs(x) < 0.01 && Math.abs(y) < 0.01
|
|
657
|
-
&& Math.abs(width - dims.width) < 0.01 && Math.abs(height - dims.height) < 0.01) {
|
|
658
|
-
return null;
|
|
659
|
-
}
|
|
660
|
-
return { x, y, width, height };
|
|
758
|
+
const image = ctx.createImageData(dims.width, dims.height);
|
|
759
|
+
image.data.set(pixels);
|
|
760
|
+
ctx.putImageData(image, 0, 0);
|
|
661
761
|
}
|
|
662
762
|
/**
|
|
663
763
|
* The archive's per-cell floor and ceiling rasters, once EnsureExtremes has resolved.
|
|
@@ -669,45 +769,16 @@ var TextureFrameSeriesAnimator;
|
|
|
669
769
|
* Fetches and decodes the floor and ceiling rasters, at most once.
|
|
670
770
|
*/
|
|
671
771
|
EnsureExtremes() {
|
|
672
|
-
if (this.extremesLoad) {
|
|
673
|
-
return this.extremesLoad;
|
|
674
|
-
}
|
|
675
|
-
if (this.tiles) {
|
|
772
|
+
if (!this.extremesLoad) {
|
|
676
773
|
this.extremesLoad = this.loadTiledExtremes();
|
|
677
|
-
return this.extremesLoad;
|
|
678
774
|
}
|
|
679
|
-
const floor = this.metadata && this.metadata.Floor;
|
|
680
|
-
const ceiling = this.metadata && this.metadata.Ceiling;
|
|
681
|
-
if (!floor && !ceiling) {
|
|
682
|
-
this.extremesLoad = Promise.resolve();
|
|
683
|
-
return this.extremesLoad;
|
|
684
|
-
}
|
|
685
|
-
this.extremesLoad = Promise.all([
|
|
686
|
-
floor ? this.loadImageDataAt(floor) : Promise.resolve(null),
|
|
687
|
-
ceiling ? this.loadImageDataAt(ceiling) : Promise.resolve(null)
|
|
688
|
-
]).then(([f, c]) => {
|
|
689
|
-
if (this.disposed) {
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
this.floorPixels = f;
|
|
693
|
-
this.ceilingPixels = c;
|
|
694
|
-
}).catch((e) => {
|
|
695
|
-
// Missing extremes cost a label its range, and must not take the animation with them.
|
|
696
|
-
console.warn("TextureFrameSeriesAnimator: could not load the extremes rasters.", e);
|
|
697
|
-
});
|
|
698
775
|
return this.extremesLoad;
|
|
699
776
|
}
|
|
700
777
|
/*
|
|
701
778
|
* Composites each tile's baseline mask into one covering the composited frames.
|
|
702
|
-
*
|
|
703
|
-
* A tiled archive publishes the mask per tile, so reading the archive level BaselineMask
|
|
704
|
-
* finds nothing and hiding dry cells silently does nothing at all.
|
|
705
779
|
*/
|
|
706
780
|
async loadTiledBaselineMask() {
|
|
707
781
|
const tiles = this.tiles;
|
|
708
|
-
if (!tiles) {
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
782
|
const entries = tiles.map((tile) => tile.BaselineMask);
|
|
712
783
|
let lo = Number.MAX_SAFE_INTEGER;
|
|
713
784
|
let hi = 0;
|
|
@@ -761,14 +832,11 @@ var TextureFrameSeriesAnimator;
|
|
|
761
832
|
/*
|
|
762
833
|
* Composites each tile's floor and ceiling into rasters matching the composited frames.
|
|
763
834
|
*
|
|
764
|
-
* A
|
|
765
|
-
*
|
|
835
|
+
* A label indexes them by the same texel as the value canvas, so they have to be laid out the
|
|
836
|
+
* same way rather than fetched as whole-extent rasters.
|
|
766
837
|
*/
|
|
767
838
|
async loadTiledExtremes() {
|
|
768
839
|
const tiles = this.tiles;
|
|
769
|
-
if (!tiles) {
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
840
|
const target = this.compositeExtent();
|
|
773
841
|
const size = this.compositeSize(tiles, target);
|
|
774
842
|
// One request for the whole extremes block. They sit contiguously after the frames, so
|
|
@@ -863,70 +931,6 @@ var TextureFrameSeriesAnimator;
|
|
|
863
931
|
}
|
|
864
932
|
}));
|
|
865
933
|
}
|
|
866
|
-
/*
|
|
867
|
-
* Range GETs one entry and decodes it to an image.
|
|
868
|
-
*/
|
|
869
|
-
async loadImageAt(entry) {
|
|
870
|
-
const start = entry.ByteOffset;
|
|
871
|
-
const end = entry.ByteOffset + entry.ByteLength - 1;
|
|
872
|
-
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${start}-${end}` } });
|
|
873
|
-
const buffer = await response.arrayBuffer();
|
|
874
|
-
const objectUrl = URL.createObjectURL(new Blob([buffer], { type: "image/png" }));
|
|
875
|
-
try {
|
|
876
|
-
return await (0, image_utils_1.loadImage)(objectUrl);
|
|
877
|
-
}
|
|
878
|
-
finally {
|
|
879
|
-
URL.revokeObjectURL(objectUrl);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
/*
|
|
883
|
-
* Puts a static raster through the same resample the frames get.
|
|
884
|
-
*/
|
|
885
|
-
remap(image) {
|
|
886
|
-
if (!image) {
|
|
887
|
-
return null;
|
|
888
|
-
}
|
|
889
|
-
const rect = this.sourceRect({ width: image.width, height: image.height });
|
|
890
|
-
if (!rect) {
|
|
891
|
-
return image;
|
|
892
|
-
}
|
|
893
|
-
const from = document.createElement("canvas");
|
|
894
|
-
from.width = image.width;
|
|
895
|
-
from.height = image.height;
|
|
896
|
-
from.getContext("2d").putImageData(image, 0, 0);
|
|
897
|
-
const to = document.createElement("canvas");
|
|
898
|
-
to.width = image.width;
|
|
899
|
-
to.height = image.height;
|
|
900
|
-
const ctx = to.getContext("2d");
|
|
901
|
-
ctx.imageSmoothingEnabled = false;
|
|
902
|
-
ctx.clearRect(0, 0, image.width, image.height);
|
|
903
|
-
ctx.drawImage(from, rect.x, rect.y, rect.width, rect.height, 0, 0, image.width, image.height);
|
|
904
|
-
return ctx.getImageData(0, 0, image.width, image.height);
|
|
905
|
-
}
|
|
906
|
-
/*
|
|
907
|
-
* Range GETs one entry out of the blob and decodes it to pixels.
|
|
908
|
-
*/
|
|
909
|
-
async loadImageDataAt(entry) {
|
|
910
|
-
const start = entry.ByteOffset;
|
|
911
|
-
const end = entry.ByteOffset + entry.ByteLength - 1;
|
|
912
|
-
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${start}-${end}` } });
|
|
913
|
-
const buffer = await response.arrayBuffer();
|
|
914
|
-
const blob = new Blob([buffer], { type: "image/png" });
|
|
915
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
916
|
-
let image;
|
|
917
|
-
try {
|
|
918
|
-
image = await (0, image_utils_1.loadImage)(objectUrl);
|
|
919
|
-
}
|
|
920
|
-
finally {
|
|
921
|
-
URL.revokeObjectURL(objectUrl);
|
|
922
|
-
}
|
|
923
|
-
const canvas = document.createElement("canvas");
|
|
924
|
-
canvas.width = image.width;
|
|
925
|
-
canvas.height = image.height;
|
|
926
|
-
const ctx = canvas.getContext("2d");
|
|
927
|
-
ctx.drawImage(image, 0, 0);
|
|
928
|
-
return ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
929
|
-
}
|
|
930
934
|
/**
|
|
931
935
|
* Fetches and decodes the archive's static baseline mask, at most once.
|
|
932
936
|
*/
|