bruce-cesium 7.2.2 → 7.2.4
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 +437 -377
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +441 -382
- 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 +30 -39
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +279 -267
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +71 -25
- 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 +166 -66
- package/dist/types/rendering/texture-value-labels.d.ts +22 -0
- package/package.json +2 -2
|
@@ -6,38 +6,229 @@ 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
|
+
* Whether anything was applied to the values on the way out of the source.
|
|
173
|
+
* @param metadata the archive's Data.generation
|
|
174
|
+
*/
|
|
175
|
+
function IsTransformed(metadata) {
|
|
176
|
+
return Boolean(metadata && metadata.Transform);
|
|
177
|
+
}
|
|
178
|
+
TextureFrameSeriesAnimator.IsTransformed = IsTransformed;
|
|
179
|
+
/**
|
|
180
|
+
* A delivered value in the source's own terms, or unchanged when it cannot be turned back.
|
|
181
|
+
* @param map from SourceMap(), null when the archive does not say enough
|
|
182
|
+
*/
|
|
183
|
+
function ToSource(map, value) {
|
|
184
|
+
return map ? value * map.scale + map.offset : value;
|
|
185
|
+
}
|
|
186
|
+
TextureFrameSeriesAnimator.ToSource = ToSource;
|
|
187
|
+
/**
|
|
188
|
+
* Whether the archive's frames carry a 16 bit value packed across R and G.
|
|
16
189
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
190
|
+
* Every consumer that reads a texel has to ask: taking R alone from a packed frame yields a
|
|
191
|
+
* plausible surface quantised to 255 steps of the full range rather than an obvious failure.
|
|
192
|
+
* @param metadata the archive's Data.generation
|
|
193
|
+
*/
|
|
194
|
+
function IsPackedValue(metadata) {
|
|
195
|
+
return Boolean(metadata && metadata.Format === TextureFrameSeriesAnimator.FORMAT_RG16);
|
|
196
|
+
}
|
|
197
|
+
TextureFrameSeriesAnimator.IsPackedValue = IsPackedValue;
|
|
198
|
+
/**
|
|
199
|
+
* The normalised 0 to 1 value at a texel, from whichever format the archive packed it in.
|
|
19
200
|
* @param metadata the archive's Data.generation
|
|
201
|
+
* @param pixels untinted RGBA value pixels
|
|
202
|
+
* @param at index of the texel's red channel
|
|
20
203
|
*/
|
|
21
|
-
function
|
|
22
|
-
if (
|
|
23
|
-
return 1;
|
|
204
|
+
function NormalisedAt(metadata, pixels, at) {
|
|
205
|
+
if (IsPackedValue(metadata)) {
|
|
206
|
+
return (pixels[at] * 256 + pixels[at + 1]) / 65535;
|
|
24
207
|
}
|
|
25
|
-
return
|
|
208
|
+
return pixels[at] / 255;
|
|
26
209
|
}
|
|
27
|
-
TextureFrameSeriesAnimator.
|
|
210
|
+
TextureFrameSeriesAnimator.NormalisedAt = NormalisedAt;
|
|
211
|
+
// Values already sit on the ellipsoid Cesium measures against, so nothing is added to place them.
|
|
212
|
+
TextureFrameSeriesAnimator.DATUM_ELLIPSOIDAL = "WGS84";
|
|
28
213
|
/**
|
|
29
|
-
* Whether
|
|
214
|
+
* Whether the values are heights against a vertical datum rather than a thickness.
|
|
215
|
+
*
|
|
216
|
+
* A thickness rises from wherever the polygon sits and a height already knows where it belongs, so
|
|
217
|
+
* this is what decides whether the polygon's own altitude may be added underneath it.
|
|
30
218
|
* @param metadata the archive's Data.generation
|
|
31
219
|
*/
|
|
32
|
-
function
|
|
33
|
-
return
|
|
34
|
-
metadata.Tiles && metadata.Tiles.length > 0));
|
|
220
|
+
function IsDatumHeight(metadata) {
|
|
221
|
+
return Boolean(metadata && metadata.SourceVerticalDatum);
|
|
35
222
|
}
|
|
36
|
-
TextureFrameSeriesAnimator.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
223
|
+
TextureFrameSeriesAnimator.IsDatumHeight = IsDatumHeight;
|
|
224
|
+
/**
|
|
225
|
+
* The archive's tiles, which Adapt() guarantees are present.
|
|
226
|
+
* @param metadata the archive's Data.generation
|
|
227
|
+
*/
|
|
228
|
+
function TilesOf(metadata) {
|
|
229
|
+
return (metadata && metadata.Tiles) || [];
|
|
230
|
+
}
|
|
231
|
+
TextureFrameSeriesAnimator.TilesOf = TilesOf;
|
|
41
232
|
/**
|
|
42
233
|
* Turns a value in the attribute's own units into the 0 to 1 position the ramp is indexed by.
|
|
43
234
|
*
|
|
@@ -56,44 +247,18 @@ var TextureFrameSeriesAnimator;
|
|
|
56
247
|
return Math.min(1, Math.max(0, (value - lo) / span));
|
|
57
248
|
}
|
|
58
249
|
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
250
|
/**
|
|
91
251
|
* Detects whether a ClientFile's `Data.generation` metadata describes a frame archive rather than a single static image,
|
|
92
252
|
* so a caller can decide whether to construct an Animator or fall back to the existing static-texture path.
|
|
93
|
-
* @param data ClientFile.Data
|
|
253
|
+
* @param data ClientFile.Data
|
|
94
254
|
*/
|
|
95
255
|
function IsFrameArchiveMetadata(data) {
|
|
96
|
-
|
|
256
|
+
const generation = data && data.generation;
|
|
257
|
+
if (!generation) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return (typeof generation.Version === "number" ||
|
|
261
|
+
(Array.isArray(generation.Frames) && generation.Frames.length > 0));
|
|
97
262
|
}
|
|
98
263
|
TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
|
|
99
264
|
// First resolvable static number out of a calculator field list, since that is all a border needs.
|
|
@@ -139,10 +304,27 @@ var TextureFrameSeriesAnimator;
|
|
|
139
304
|
TextureFrameSeriesAnimator.AppearanceSignature = AppearanceSignature;
|
|
140
305
|
const MAX_COMPOSITE_TEXELS = 2048;
|
|
141
306
|
const VALUE_DILATE_PASSES = 2;
|
|
307
|
+
/*
|
|
308
|
+
* Rewrites a packed 16 bit value as the 8 bit grey the colour ramps index on.
|
|
309
|
+
*
|
|
310
|
+
* All three channels, since a ramp is free to read any of them and a leftover low byte in G would
|
|
311
|
+
* show up as a green cast on the one that reads them all.
|
|
312
|
+
*/
|
|
313
|
+
function flattenPacked(pixels, packed) {
|
|
314
|
+
if (!packed) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
318
|
+
const grey = Math.round((pixels[i] * 256 + pixels[i + 1]) / 65535 * 255);
|
|
319
|
+
pixels[i] = grey;
|
|
320
|
+
pixels[i + 1] = grey;
|
|
321
|
+
pixels[i + 2] = grey;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
142
324
|
/*
|
|
143
325
|
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
144
326
|
*/
|
|
145
|
-
function dilateValues(value, width, height, passes) {
|
|
327
|
+
function dilateValues(value, width, height, passes, packed) {
|
|
146
328
|
const texels = width * height;
|
|
147
329
|
const filled = new Uint8Array(texels);
|
|
148
330
|
for (let p = 0; p < texels; p++) {
|
|
@@ -166,15 +348,25 @@ var TextureFrameSeriesAnimator;
|
|
|
166
348
|
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !wasFilled[ny * width + nx]) {
|
|
167
349
|
continue;
|
|
168
350
|
}
|
|
169
|
-
|
|
351
|
+
const at = (ny * width + nx) * 4;
|
|
352
|
+
// Averaged as a value rather than per byte: averaging a packed low byte
|
|
353
|
+
// on its own wraps at every 256th step and speckles the bled edge.
|
|
354
|
+
sum += packed ? source[at] * 256 + source[at + 1] : source[at];
|
|
170
355
|
hits++;
|
|
171
356
|
}
|
|
172
357
|
}
|
|
173
358
|
if (hits > 0) {
|
|
174
359
|
const v = Math.round(sum / hits);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
360
|
+
if (packed) {
|
|
361
|
+
value[p * 4] = (v >> 8) & 255;
|
|
362
|
+
value[p * 4 + 1] = v & 255;
|
|
363
|
+
value[p * 4 + 2] = 0;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
value[p * 4] = v;
|
|
367
|
+
value[p * 4 + 1] = v;
|
|
368
|
+
value[p * 4 + 2] = v;
|
|
369
|
+
}
|
|
178
370
|
filled[p] = 1;
|
|
179
371
|
}
|
|
180
372
|
}
|
|
@@ -212,7 +404,6 @@ var TextureFrameSeriesAnimator;
|
|
|
212
404
|
// forcing Cesium to re-upload the texture only when something changed.
|
|
213
405
|
this.pool = [document.createElement("canvas"), document.createElement("canvas")];
|
|
214
406
|
this.poolIdx = 0;
|
|
215
|
-
this.scratch = null;
|
|
216
407
|
this.valueDims = null;
|
|
217
408
|
this.presentVersion = 0;
|
|
218
409
|
this.extremesLoad = null;
|
|
@@ -235,7 +426,7 @@ var TextureFrameSeriesAnimator;
|
|
|
235
426
|
this.highColor = (mask && bruce_models_1.Color.ColorFromStr(mask.maxColor)) || DEFAULT_HIGH_COLOR;
|
|
236
427
|
// Positions are authored in the attribute's units and normalised once here, so the
|
|
237
428
|
// per-pixel loop stays a comparison against 0 to 1 like the two-colour path.
|
|
238
|
-
const points = mask
|
|
429
|
+
const points = mask && mask.points;
|
|
239
430
|
this.rampStops = (points && points.length > 0)
|
|
240
431
|
? points.map((p) => ({
|
|
241
432
|
position: NormalisePosition(options.metadata, p.position),
|
|
@@ -261,9 +452,8 @@ var TextureFrameSeriesAnimator;
|
|
|
261
452
|
this.produceValueCanvas = Boolean(options.produceValueCanvas);
|
|
262
453
|
this.metadata = options.metadata;
|
|
263
454
|
this.drapeExtent = options.drapeExtent;
|
|
264
|
-
this.tiles =
|
|
265
|
-
this.
|
|
266
|
-
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : Boolean(options.baselineMask);
|
|
455
|
+
this.tiles = TilesOf(options.metadata);
|
|
456
|
+
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : this.tiles.some((tile) => Boolean(tile.BaselineMask));
|
|
267
457
|
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
268
458
|
this.driveMaterial = options.driveMaterial !== false;
|
|
269
459
|
this.originalMaterial = options.entity.polygon.material;
|
|
@@ -439,32 +629,20 @@ var TextureFrameSeriesAnimator;
|
|
|
439
629
|
}
|
|
440
630
|
async fetchAndTint(idx) {
|
|
441
631
|
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);
|
|
632
|
+
// Every tile's frame for one timestep sits contiguously, because the generator writes
|
|
633
|
+
// frame major. So a tiled frame is still ONE range request, not one per tile.
|
|
634
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
635
|
+
let hi = 0;
|
|
636
|
+
for (const tile of tiles) {
|
|
637
|
+
const at = tile.Frames[idx];
|
|
638
|
+
if (!at) {
|
|
639
|
+
continue;
|
|
456
640
|
}
|
|
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();
|
|
641
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
642
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
467
643
|
}
|
|
644
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
645
|
+
const buffer = await response.arrayBuffer();
|
|
468
646
|
if (this.disposed) {
|
|
469
647
|
return;
|
|
470
648
|
}
|
|
@@ -474,9 +652,7 @@ var TextureFrameSeriesAnimator;
|
|
|
474
652
|
if (this.disposed) {
|
|
475
653
|
return;
|
|
476
654
|
}
|
|
477
|
-
const decoded = tiles
|
|
478
|
-
? await this.composeTiles(tiles, idx, buffer, span)
|
|
479
|
-
: await this.decodeAndTint(buffer);
|
|
655
|
+
const decoded = await this.composeTiles(tiles, idx, buffer, lo);
|
|
480
656
|
if (this.disposed) {
|
|
481
657
|
return;
|
|
482
658
|
}
|
|
@@ -487,26 +663,6 @@ var TextureFrameSeriesAnimator;
|
|
|
487
663
|
this.beginCrossfadeTo(idx);
|
|
488
664
|
}
|
|
489
665
|
}
|
|
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
666
|
/*
|
|
511
667
|
* Draws every tile of one frame into a single raster covering the drape extent.
|
|
512
668
|
*/
|
|
@@ -579,6 +735,10 @@ var TextureFrameSeriesAnimator;
|
|
|
579
735
|
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
580
736
|
// recovered from the tinted pixels afterwards.
|
|
581
737
|
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
738
|
+
// The ramp indexes on R as an 8 bit grey, so a packed frame is flattened to one here
|
|
739
|
+
// rather than teaching every ramp function a second format.
|
|
740
|
+
// The copy above keeps the packed pair, which is what displacement and labels read.
|
|
741
|
+
flattenPacked(imageData.data, IsPackedValue(this.metadata));
|
|
582
742
|
// Stops win when supplied: they can express a band and a hidden floor, which a two
|
|
583
743
|
// colour ramp cannot. The pair stays the fallback so an older style still draws.
|
|
584
744
|
if (this.rampStops && this.rampStops.length > 0) {
|
|
@@ -592,72 +752,20 @@ var TextureFrameSeriesAnimator;
|
|
|
592
752
|
}
|
|
593
753
|
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
594
754
|
if (valuePixels) {
|
|
595
|
-
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES);
|
|
755
|
+
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
596
756
|
}
|
|
597
757
|
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
598
758
|
}
|
|
599
759
|
/*
|
|
600
|
-
* Paints one frame's pixels onto a canvas
|
|
760
|
+
* Paints one frame's composited pixels onto a canvas.
|
|
601
761
|
*/
|
|
602
762
|
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
763
|
canvas.width = dims.width;
|
|
623
764
|
canvas.height = dims.height;
|
|
624
765
|
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 };
|
|
766
|
+
const image = ctx.createImageData(dims.width, dims.height);
|
|
767
|
+
image.data.set(pixels);
|
|
768
|
+
ctx.putImageData(image, 0, 0);
|
|
661
769
|
}
|
|
662
770
|
/**
|
|
663
771
|
* The archive's per-cell floor and ceiling rasters, once EnsureExtremes has resolved.
|
|
@@ -669,45 +777,16 @@ var TextureFrameSeriesAnimator;
|
|
|
669
777
|
* Fetches and decodes the floor and ceiling rasters, at most once.
|
|
670
778
|
*/
|
|
671
779
|
EnsureExtremes() {
|
|
672
|
-
if (this.extremesLoad) {
|
|
673
|
-
return this.extremesLoad;
|
|
674
|
-
}
|
|
675
|
-
if (this.tiles) {
|
|
780
|
+
if (!this.extremesLoad) {
|
|
676
781
|
this.extremesLoad = this.loadTiledExtremes();
|
|
677
|
-
return this.extremesLoad;
|
|
678
|
-
}
|
|
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
782
|
}
|
|
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
783
|
return this.extremesLoad;
|
|
699
784
|
}
|
|
700
785
|
/*
|
|
701
786
|
* 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
787
|
*/
|
|
706
788
|
async loadTiledBaselineMask() {
|
|
707
789
|
const tiles = this.tiles;
|
|
708
|
-
if (!tiles) {
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
790
|
const entries = tiles.map((tile) => tile.BaselineMask);
|
|
712
791
|
let lo = Number.MAX_SAFE_INTEGER;
|
|
713
792
|
let hi = 0;
|
|
@@ -761,14 +840,11 @@ var TextureFrameSeriesAnimator;
|
|
|
761
840
|
/*
|
|
762
841
|
* Composites each tile's floor and ceiling into rasters matching the composited frames.
|
|
763
842
|
*
|
|
764
|
-
* A
|
|
765
|
-
*
|
|
843
|
+
* A label indexes them by the same texel as the value canvas, so they have to be laid out the
|
|
844
|
+
* same way rather than fetched as whole-extent rasters.
|
|
766
845
|
*/
|
|
767
846
|
async loadTiledExtremes() {
|
|
768
847
|
const tiles = this.tiles;
|
|
769
|
-
if (!tiles) {
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
848
|
const target = this.compositeExtent();
|
|
773
849
|
const size = this.compositeSize(tiles, target);
|
|
774
850
|
// One request for the whole extremes block. They sit contiguously after the frames, so
|
|
@@ -863,70 +939,6 @@ var TextureFrameSeriesAnimator;
|
|
|
863
939
|
}
|
|
864
940
|
}));
|
|
865
941
|
}
|
|
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
942
|
/**
|
|
931
943
|
* Fetches and decodes the archive's static baseline mask, at most once.
|
|
932
944
|
*/
|