bruce-cesium 7.2.0 → 7.2.2
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 +2237 -336
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +2230 -333
- 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/internal/image-utils.js +106 -1
- package/dist/lib/internal/image-utils.js.map +1 -1
- package/dist/lib/rendering/displaced-surface-primitive.js +41 -8
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +211 -13
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/historic-utils.js +471 -0
- package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -0
- package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +14 -124
- package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +16 -13
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +31 -109
- package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +23 -7
- package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +621 -28
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +687 -0
- package/dist/lib/rendering/texture-value-labels.js.map +1 -0
- package/dist/lib/rendering/tileset-styler.js +28 -24
- package/dist/lib/rendering/tileset-styler.js.map +1 -1
- package/dist/lib/rendering/visuals-register.js +8 -3
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +2 -1
- package/dist/types/internal/image-utils.d.ts +25 -0
- package/dist/types/rendering/displaced-surface-primitive.d.ts +5 -2
- package/dist/types/rendering/render-managers/common/historic-utils.d.ts +159 -0
- package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +1 -0
- package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +5 -0
- package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +1 -0
- package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +2 -0
- package/dist/types/rendering/texture-frame-series-animator.d.ts +172 -1
- package/dist/types/rendering/texture-value-labels.d.ts +139 -0
- package/dist/types/rendering/tileset-styler.d.ts +2 -0
- package/dist/types/rendering/visuals-register.d.ts +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TextureValueLabels = void 0;
|
|
4
|
+
const Cesium = require("cesium");
|
|
5
|
+
const texture_frame_series_animator_1 = require("./texture-frame-series-animator");
|
|
6
|
+
/**
|
|
7
|
+
* Prints a value texture's own numbers over the cells they belong to.
|
|
8
|
+
*/
|
|
9
|
+
var TextureValueLabels;
|
|
10
|
+
(function (TextureValueLabels) {
|
|
11
|
+
// Off by default: a caller can still set a floor, but thinning is the mechanism that keeps a
|
|
12
|
+
// distant view readable.
|
|
13
|
+
const DEFAULT_MIN_CELL_PIXELS = 0;
|
|
14
|
+
const MIN_VISIBLE_PIXELS = 60;
|
|
15
|
+
const MAX_STRIDE = 512;
|
|
16
|
+
const STRIDE_HYSTERESIS = 1.4;
|
|
17
|
+
const DEFAULT_MIN_SPACING_PIXELS = 78;
|
|
18
|
+
const DEFAULT_MAX_LABELS = 240;
|
|
19
|
+
const LABEL_RADIUS = 4;
|
|
20
|
+
const LABEL_FONT = "600 11px ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
21
|
+
// Clear space either side of a label box, so neighbours read as separate.
|
|
22
|
+
const LABEL_GAP_PIXELS = 16;
|
|
23
|
+
const LABEL_ANCHOR_COLOR = "rgba(255,255,255,0.85)";
|
|
24
|
+
// 8 rather than 5: below this a cell reads as texture rather than as a boundary, and it also
|
|
25
|
+
// bounds the worst case, since a full screen of 8 px cells fits inside MAX_GRID_CELLS.
|
|
26
|
+
const DEFAULT_MIN_GRID_CELL_PIXELS = 8;
|
|
27
|
+
const DEFAULT_GRID_WIDTH_PIXELS = 1;
|
|
28
|
+
// Enough for a full screen at the minimum legible cell size, with headroom for an oblique view
|
|
29
|
+
// whose texel window is larger than the screen. The budget is shared across tiles, so sizing it
|
|
30
|
+
// too tightly made the grid depend on which tile happened to be walked first.
|
|
31
|
+
const MAX_GRID_CELLS = 40000;
|
|
32
|
+
// Probes per side when hunting a labellable cell inside one lattice block.
|
|
33
|
+
const MAX_LABEL_PROBES_PER_SIDE = 8;
|
|
34
|
+
const TILE_OUTLINE_ALPHA = 0.45;
|
|
35
|
+
// Cells per grid square at the coarsest. Beyond this a tile is outlined instead.
|
|
36
|
+
const MAX_GRID_STRIDE = 256;
|
|
37
|
+
const LINE_HEIGHT = 13;
|
|
38
|
+
/**
|
|
39
|
+
* A clip outline from a projected ring, or null when the ring cannot be drawn.
|
|
40
|
+
* @param projected one entry per ring vertex, null where the vertex has no window coordinate
|
|
41
|
+
*/
|
|
42
|
+
function ClipOutline(projected) {
|
|
43
|
+
if (projected.length < 3) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const at of projected) {
|
|
48
|
+
if (!at) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
out.push(at);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
TextureValueLabels.ClipOutline = ClipOutline;
|
|
56
|
+
/**
|
|
57
|
+
* How many cells apart labels should sit, as a power of two.
|
|
58
|
+
* @param options cellPixels is one cell's size on screen, held is the stride already in use
|
|
59
|
+
*/
|
|
60
|
+
function ChooseLabelStride(options) {
|
|
61
|
+
const { cellPixels, visibleCols, visibleRows, maxLabels, minSpacingPixels } = options;
|
|
62
|
+
const fits = (s, slack) => cellPixels * s >= minSpacingPixels / slack
|
|
63
|
+
&& Math.ceil(visibleCols / s) * Math.ceil(visibleRows / s) <= maxLabels * slack;
|
|
64
|
+
let stride = 1;
|
|
65
|
+
while (stride < MAX_STRIDE && !fits(stride, 1)) {
|
|
66
|
+
stride *= 2;
|
|
67
|
+
}
|
|
68
|
+
const held = options.held;
|
|
69
|
+
if (held && fits(held, STRIDE_HYSTERESIS)) {
|
|
70
|
+
if (held === stride / 2 && cellPixels * held >= minSpacingPixels / STRIDE_HYSTERESIS) {
|
|
71
|
+
return held;
|
|
72
|
+
}
|
|
73
|
+
if (held === stride * 2 && cellPixels * stride < minSpacingPixels * STRIDE_HYSTERESIS) {
|
|
74
|
+
return held;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return stride;
|
|
78
|
+
}
|
|
79
|
+
TextureValueLabels.ChooseLabelStride = ChooseLabelStride;
|
|
80
|
+
class Labels {
|
|
81
|
+
constructor(options) {
|
|
82
|
+
this.source = null;
|
|
83
|
+
this.painted = null;
|
|
84
|
+
this.floor = null;
|
|
85
|
+
this.ceiling = null;
|
|
86
|
+
this.disposed = false;
|
|
87
|
+
this.drawn = 0;
|
|
88
|
+
this.stride = 0;
|
|
89
|
+
this.viewer = options.viewer;
|
|
90
|
+
this.extent = options.extent;
|
|
91
|
+
this.settings = options.settings || {};
|
|
92
|
+
this.metadata = options.metadata;
|
|
93
|
+
this.tiles = options.tiles;
|
|
94
|
+
this.clipRing = options.clipRing;
|
|
95
|
+
this.canvas = document.createElement("canvas");
|
|
96
|
+
this.canvas.style.position = "absolute";
|
|
97
|
+
this.canvas.style.inset = "0";
|
|
98
|
+
this.canvas.style.pointerEvents = "none";
|
|
99
|
+
const parent = this.viewer.canvas.parentElement;
|
|
100
|
+
if (parent) {
|
|
101
|
+
parent.appendChild(this.canvas);
|
|
102
|
+
}
|
|
103
|
+
this.ctx = this.canvas.getContext("2d");
|
|
104
|
+
}
|
|
105
|
+
SetExtent(extent) {
|
|
106
|
+
this.extent = extent;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The current frame's decoded values, as the animator's value canvas already provides them.
|
|
110
|
+
* @param source value pixels, one texel per cell
|
|
111
|
+
*/
|
|
112
|
+
SetSource(source) {
|
|
113
|
+
this.source = source;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The tinted pixels of the same frame, so the grid can skip cells the ramp painted nothing in.
|
|
117
|
+
* @param painted RGBA of the presented frame, or null to fall back to coverage alone
|
|
118
|
+
*/
|
|
119
|
+
SetPainted(painted) {
|
|
120
|
+
this.painted = painted;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The archive's per-cell extremes, when it publishes them. Without these a label can still
|
|
124
|
+
* print the current reading, just not the range behind it.
|
|
125
|
+
*/
|
|
126
|
+
SetExtremes(floor, ceiling) {
|
|
127
|
+
this.floor = floor;
|
|
128
|
+
this.ceiling = ceiling;
|
|
129
|
+
}
|
|
130
|
+
GetDrawnCount() {
|
|
131
|
+
return this.drawn;
|
|
132
|
+
}
|
|
133
|
+
Clear() {
|
|
134
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
135
|
+
this.drawn = 0;
|
|
136
|
+
}
|
|
137
|
+
Dispose() {
|
|
138
|
+
this.disposed = true;
|
|
139
|
+
if (this.canvas.parentElement) {
|
|
140
|
+
this.canvas.parentElement.removeChild(this.canvas);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Redraws every visible label. Cheap enough to call per rendered frame, because the cell
|
|
145
|
+
* count it considers is bounded by the view rather than by the texture.
|
|
146
|
+
*/
|
|
147
|
+
Render() {
|
|
148
|
+
var _a, _b, _c;
|
|
149
|
+
if (this.disposed || !this.source) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const view = this.viewer.canvas;
|
|
153
|
+
if (this.canvas.width !== view.clientWidth || this.canvas.height !== view.clientHeight) {
|
|
154
|
+
this.canvas.width = view.clientWidth;
|
|
155
|
+
this.canvas.height = view.clientHeight;
|
|
156
|
+
}
|
|
157
|
+
this.Clear();
|
|
158
|
+
const cols = this.source.width;
|
|
159
|
+
const rows = this.source.height;
|
|
160
|
+
const window = this.visibleTexels(cols, rows);
|
|
161
|
+
if (!window) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const midCol = (window.col0 + window.col1) / 2;
|
|
165
|
+
const midRow = (window.row0 + window.row1) / 2;
|
|
166
|
+
const a = this.project(this.lonAt(midCol, cols), this.latAt(midRow, rows));
|
|
167
|
+
const b = this.project(this.lonAt(midCol + 1, cols), this.latAt(midRow + 1, rows));
|
|
168
|
+
if (!a || !b) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const cellPixels = Math.min(Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
172
|
+
// Ahead of the label threshold on purpose: a grid still reads at a cell size where a
|
|
173
|
+
// number no longer fits.
|
|
174
|
+
this.drawGrid(window, cols, rows, cellPixels);
|
|
175
|
+
if (cellPixels < ((_a = this.settings.minCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_CELL_PIXELS)) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const visibleWidth = cellPixels * (window.col1 - window.col0 + 1);
|
|
179
|
+
const visibleHeight = cellPixels * (window.row1 - window.row0 + 1);
|
|
180
|
+
if (Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
184
|
+
this.ctx.font = LABEL_FONT;
|
|
185
|
+
this.ctx.textAlign = "center";
|
|
186
|
+
const maxLabels = (_b = this.settings.maxLabels) !== null && _b !== void 0 ? _b : DEFAULT_MAX_LABELS;
|
|
187
|
+
this.stride = ChooseLabelStride({
|
|
188
|
+
cellPixels,
|
|
189
|
+
visibleCols: window.col1 - window.col0 + 1,
|
|
190
|
+
visibleRows: window.row1 - window.row0 + 1,
|
|
191
|
+
maxLabels,
|
|
192
|
+
// Measured, not assumed: the spacing that keeps labels apart is whatever the widest
|
|
193
|
+
// one is, so changing what a label says cannot quietly start overlapping them.
|
|
194
|
+
minSpacingPixels: Math.max((_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS, this.widestLabel(parts)),
|
|
195
|
+
held: this.stride
|
|
196
|
+
});
|
|
197
|
+
const stride = this.stride;
|
|
198
|
+
const firstRow = Math.floor(window.row0 / stride) * stride;
|
|
199
|
+
const firstCol = Math.floor(window.col0 / stride) * stride;
|
|
200
|
+
for (let row = firstRow; row <= window.row1 && this.drawn < maxLabels; row += stride) {
|
|
201
|
+
for (let col = firstCol; col <= window.col1 && this.drawn < maxLabels; col += stride) {
|
|
202
|
+
const found = this.readNear(col, row, cols, rows, stride);
|
|
203
|
+
if (!found) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const point = this.project(this.lonAt(found.col, cols), this.latAt(found.row, rows));
|
|
207
|
+
if (!point || point.x < 0 || point.y < 0
|
|
208
|
+
|| point.x > this.canvas.width || point.y > this.canvas.height) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
this.drawLabel(point, found.reading, parts, this.cellOutline(found.col, found.row, cols, rows));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/*
|
|
216
|
+
* Outlines each covered cell, from a projected lattice of its corners.
|
|
217
|
+
*/
|
|
218
|
+
drawGrid(window, cols, rows, cellPixels) {
|
|
219
|
+
var _a, _b;
|
|
220
|
+
const color = this.settings.gridColor;
|
|
221
|
+
if (!color) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (this.tiles && this.tiles.length) {
|
|
225
|
+
this.ctx.save();
|
|
226
|
+
this.applyRingClip();
|
|
227
|
+
this.drawTileGrid(color);
|
|
228
|
+
this.ctx.restore();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (cellPixels < ((_a = this.settings.minGridCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_GRID_CELL_PIXELS)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Corners are shared by four cells, so projecting the lattice once rather than per cell
|
|
235
|
+
// cuts the work by about four and keeps neighbouring outlines exactly coincident.
|
|
236
|
+
const wide = window.col1 - window.col0 + 1;
|
|
237
|
+
const high = window.row1 - window.row0 + 1;
|
|
238
|
+
if (wide * high > MAX_GRID_CELLS) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const lattice = new Array((wide + 1) * (high + 1));
|
|
242
|
+
for (let r = 0; r <= high; r++) {
|
|
243
|
+
for (let c = 0; c <= wide; c++) {
|
|
244
|
+
// Corners sit half a texel out from the centres lonAt/latAt return.
|
|
245
|
+
const lon = this.lonAt(window.col0 + c - 0.5, cols);
|
|
246
|
+
const lat = this.latAt(window.row0 + r - 0.5, rows);
|
|
247
|
+
lattice[r * (wide + 1) + c] = this.project(lon, lat);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const path = new Path2D();
|
|
251
|
+
let drew = false;
|
|
252
|
+
for (let r = 0; r < high; r++) {
|
|
253
|
+
for (let c = 0; c < wide; c++) {
|
|
254
|
+
if (!this.isCovered(window.col0 + c, window.row0 + r, cols)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const tl = lattice[r * (wide + 1) + c];
|
|
258
|
+
const tr = lattice[r * (wide + 1) + c + 1];
|
|
259
|
+
const bl = lattice[(r + 1) * (wide + 1) + c];
|
|
260
|
+
const br = lattice[(r + 1) * (wide + 1) + c + 1];
|
|
261
|
+
if (!tl || !tr || !bl || !br) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
path.moveTo(tl.x, tl.y);
|
|
265
|
+
path.lineTo(tr.x, tr.y);
|
|
266
|
+
path.lineTo(br.x, br.y);
|
|
267
|
+
path.lineTo(bl.x, bl.y);
|
|
268
|
+
path.closePath();
|
|
269
|
+
drew = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!drew) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
this.ctx.strokeStyle = color;
|
|
276
|
+
this.ctx.lineWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
277
|
+
this.ctx.stroke(path);
|
|
278
|
+
+" alpha" + this.ctx.globalAlpha;
|
|
279
|
+
}
|
|
280
|
+
/*
|
|
281
|
+
* Restricts drawing to the polygon, so nothing is ruled over ground it does not cover.
|
|
282
|
+
*/
|
|
283
|
+
applyRingClip() {
|
|
284
|
+
const ring = this.clipRing;
|
|
285
|
+
if (!ring || ring.length < 3) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const outline = ClipOutline(ring.map(point => this.project(point.lon, point.lat)));
|
|
289
|
+
if (!outline) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const path = new Path2D();
|
|
293
|
+
path.moveTo(outline[0].x, outline[0].y);
|
|
294
|
+
for (let i = 1; i < outline.length; i++) {
|
|
295
|
+
path.lineTo(outline[i].x, outline[i].y);
|
|
296
|
+
}
|
|
297
|
+
path.closePath();
|
|
298
|
+
this.ctx.clip(path);
|
|
299
|
+
}
|
|
300
|
+
/*
|
|
301
|
+
* The cell of whichever tile covers a point, as screen corners.
|
|
302
|
+
*/
|
|
303
|
+
tileCellOutline(lon, lat) {
|
|
304
|
+
for (const tile of this.tiles) {
|
|
305
|
+
if (lon < tile.West || lon > tile.East || lat < tile.South || lat > tile.North) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const c = (Math.min(tile.ResolutionX - 1, Math.max(0, Math.floor((lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX))));
|
|
309
|
+
const r = (Math.min(tile.ResolutionY - 1, Math.max(0, Math.floor((tile.North - lat) / (tile.North - tile.South) * tile.ResolutionY))));
|
|
310
|
+
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
311
|
+
const w1 = tile.West + ((c + 1) / tile.ResolutionX) * (tile.East - tile.West);
|
|
312
|
+
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
313
|
+
const n1 = tile.North - ((r + 1) / tile.ResolutionY) * (tile.North - tile.South);
|
|
314
|
+
const corners = [
|
|
315
|
+
this.project(w0, n0), this.project(w1, n0),
|
|
316
|
+
this.project(w1, n1), this.project(w0, n1)
|
|
317
|
+
];
|
|
318
|
+
return corners.every((q) => q) ? corners : null;
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
/*
|
|
323
|
+
* Outlines each tile's own cells, so cell size still shows where the mesh is refined.
|
|
324
|
+
*/
|
|
325
|
+
drawTileGrid(color) {
|
|
326
|
+
var _a, _b, _c;
|
|
327
|
+
const view = this.viewer.camera.computeViewRectangle(Cesium.Ellipsoid.WGS84);
|
|
328
|
+
if (!view) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const west = Cesium.Math.toDegrees(view.west);
|
|
332
|
+
const east = Cesium.Math.toDegrees(view.east);
|
|
333
|
+
const south = Cesium.Math.toDegrees(view.south);
|
|
334
|
+
const north = Cesium.Math.toDegrees(view.north);
|
|
335
|
+
const minCell = (_a = this.settings.minGridCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_GRID_CELL_PIXELS;
|
|
336
|
+
const focus = this.groundAtScreenCentre();
|
|
337
|
+
const path = new Path2D();
|
|
338
|
+
// A tile too fine to rule, or one the budget cannot afford, contributes its own outline
|
|
339
|
+
// instead of vanishing. Dropping such tiles entirely is what made whole regions blink out.
|
|
340
|
+
const coarse = new Path2D();
|
|
341
|
+
let drew = false;
|
|
342
|
+
let drewCoarse = false;
|
|
343
|
+
const outlineTile = (tile) => {
|
|
344
|
+
const tl = this.project(tile.West, tile.North);
|
|
345
|
+
const tr = this.project(tile.East, tile.North);
|
|
346
|
+
const br = this.project(tile.East, tile.South);
|
|
347
|
+
const bl = this.project(tile.West, tile.South);
|
|
348
|
+
if (!tl || !tr || !br || !bl) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
coarse.moveTo(tl.x, tl.y);
|
|
352
|
+
coarse.lineTo(tr.x, tr.y);
|
|
353
|
+
coarse.lineTo(br.x, br.y);
|
|
354
|
+
coarse.lineTo(bl.x, bl.y);
|
|
355
|
+
coarse.closePath();
|
|
356
|
+
drewCoarse = true;
|
|
357
|
+
};
|
|
358
|
+
// Planned for every visible tile BEFORE any is drawn.
|
|
359
|
+
const planned = [];
|
|
360
|
+
for (const tile of this.tiles) {
|
|
361
|
+
if (tile.East < west || tile.West > east || tile.North < south || tile.South > north) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const c0 = Math.max(0, Math.floor((Math.max(west, tile.West) - tile.West)
|
|
365
|
+
/ (tile.East - tile.West) * tile.ResolutionX));
|
|
366
|
+
const c1 = Math.min(tile.ResolutionX, Math.ceil((Math.min(east, tile.East) - tile.West)
|
|
367
|
+
/ (tile.East - tile.West) * tile.ResolutionX));
|
|
368
|
+
const r0 = Math.max(0, Math.floor((tile.North - Math.min(north, tile.North))
|
|
369
|
+
/ (tile.North - tile.South) * tile.ResolutionY));
|
|
370
|
+
const r1 = Math.min(tile.ResolutionY, Math.ceil((tile.North - Math.max(south, tile.South))
|
|
371
|
+
/ (tile.North - tile.South) * tile.ResolutionY));
|
|
372
|
+
if (c1 <= c0 || r1 <= r0) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const focusC = focus
|
|
376
|
+
? Math.round((focus.lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX)
|
|
377
|
+
: Math.floor((c0 + c1) / 2);
|
|
378
|
+
const focusR = focus
|
|
379
|
+
? Math.round((tile.North - focus.lat) / (tile.North - tile.South) * tile.ResolutionY)
|
|
380
|
+
: Math.floor((r0 + r1) / 2);
|
|
381
|
+
const midC = Math.min(c1 - 1, Math.max(c0, focusC));
|
|
382
|
+
const midR = Math.min(r1 - 1, Math.max(r0, focusR));
|
|
383
|
+
const cellLon = (tile.East - tile.West) / tile.ResolutionX;
|
|
384
|
+
const cellLat = (tile.North - tile.South) / tile.ResolutionY;
|
|
385
|
+
const at = this.project(tile.West + midC * cellLon, tile.North - midR * cellLat);
|
|
386
|
+
const next = (this.project(tile.West + (midC + 1) * cellLon, tile.North - (midR + 1) * cellLat));
|
|
387
|
+
if (!at || !next) {
|
|
388
|
+
outlineTile(tile);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const onScreen = Math.min(Math.abs(next.x - at.x), Math.abs(next.y - at.y));
|
|
392
|
+
let step = 1;
|
|
393
|
+
while (step < MAX_GRID_STRIDE && onScreen * step < minCell) {
|
|
394
|
+
step *= 2;
|
|
395
|
+
}
|
|
396
|
+
if (onScreen * step < minCell) {
|
|
397
|
+
outlineTile(tile);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
planned.push({ tile, c0, c1, r0, r1, step });
|
|
401
|
+
}
|
|
402
|
+
// Coarsened together rather than dropped one by one, so a busy view thins out evenly.
|
|
403
|
+
const blocksOf = (p) => Math.ceil((p.c1 - p.c0) / p.step) * Math.ceil((p.r1 - p.r0) / p.step);
|
|
404
|
+
let total = planned.reduce((sum, p) => sum + blocksOf(p), 0);
|
|
405
|
+
while (total > MAX_GRID_CELLS && planned.some((p) => p.step < MAX_GRID_STRIDE)) {
|
|
406
|
+
for (const p of planned) {
|
|
407
|
+
p.step = Math.min(MAX_GRID_STRIDE, p.step * 2);
|
|
408
|
+
}
|
|
409
|
+
total = planned.reduce((sum, p) => sum + blocksOf(p), 0);
|
|
410
|
+
}
|
|
411
|
+
for (const { tile, c0, c1, r0, r1, step } of planned) {
|
|
412
|
+
// Anchored to the tile's own origin so lines stay on the same boundaries as the camera
|
|
413
|
+
// moves, and so each step up is a subset of the one below it.
|
|
414
|
+
const firstR = Math.floor(r0 / step) * step;
|
|
415
|
+
const firstC = Math.floor(c0 / step) * step;
|
|
416
|
+
for (let r = firstR; r < r1; r += step) {
|
|
417
|
+
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
418
|
+
const n1 = tile.North
|
|
419
|
+
- (Math.min(r + step, tile.ResolutionY) / tile.ResolutionY) * (tile.North - tile.South);
|
|
420
|
+
for (let c = firstC; c < c1; c += step) {
|
|
421
|
+
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
422
|
+
const w1 = tile.West
|
|
423
|
+
+ (Math.min(c + step, tile.ResolutionX) / tile.ResolutionX) * (tile.East - tile.West);
|
|
424
|
+
if (!this.coveredAt((w0 + w1) / 2, (n0 + n1) / 2)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const tl = this.project(w0, n0);
|
|
428
|
+
const tr = this.project(w1, n0);
|
|
429
|
+
const br = this.project(w1, n1);
|
|
430
|
+
const bl = this.project(w0, n1);
|
|
431
|
+
if (!tl || !tr || !br || !bl) {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
path.moveTo(tl.x, tl.y);
|
|
435
|
+
path.lineTo(tr.x, tr.y);
|
|
436
|
+
path.lineTo(br.x, br.y);
|
|
437
|
+
path.lineTo(bl.x, bl.y);
|
|
438
|
+
path.closePath();
|
|
439
|
+
drew = true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const gridWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
444
|
+
if (drewCoarse) {
|
|
445
|
+
this.ctx.save();
|
|
446
|
+
// Dimmer than a cell edge, so a tile boundary reads as "there is finer data here"
|
|
447
|
+
// rather than as a cell in its own right.
|
|
448
|
+
this.ctx.globalAlpha = TILE_OUTLINE_ALPHA;
|
|
449
|
+
this.ctx.strokeStyle = color;
|
|
450
|
+
this.ctx.lineWidth = gridWidth;
|
|
451
|
+
this.ctx.stroke(coarse);
|
|
452
|
+
this.ctx.restore();
|
|
453
|
+
}
|
|
454
|
+
if (!drew) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
this.ctx.strokeStyle = color;
|
|
458
|
+
this.ctx.lineWidth = (_c = this.settings.gridWidthPixels) !== null && _c !== void 0 ? _c : DEFAULT_GRID_WIDTH_PIXELS;
|
|
459
|
+
this.ctx.stroke(path);
|
|
460
|
+
}
|
|
461
|
+
/*
|
|
462
|
+
* Lon/lat of the ground at the centre of the view, or null when the centre misses the globe.
|
|
463
|
+
*/
|
|
464
|
+
groundAtScreenCentre() {
|
|
465
|
+
const scene = this.viewer.scene;
|
|
466
|
+
const centre = new Cesium.Cartesian2(this.viewer.canvas.clientWidth / 2, this.viewer.canvas.clientHeight / 2);
|
|
467
|
+
const hit = scene.camera.pickEllipsoid(centre, Cesium.Ellipsoid.WGS84);
|
|
468
|
+
if (!hit) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
const carto = Cesium.Cartographic.fromCartesian(hit);
|
|
472
|
+
return {
|
|
473
|
+
lon: Cesium.Math.toDegrees(carto.longitude),
|
|
474
|
+
lat: Cesium.Math.toDegrees(carto.latitude)
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/*
|
|
478
|
+
* Whether the composited raster holds data at a lon/lat.
|
|
479
|
+
*/
|
|
480
|
+
coveredAt(lon, lat) {
|
|
481
|
+
if (!this.source) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
const cols = this.source.width;
|
|
485
|
+
const rows = this.source.height;
|
|
486
|
+
const col = Math.floor((lon - this.extent.West) / (this.extent.East - this.extent.West) * cols);
|
|
487
|
+
const row = Math.floor((this.extent.North - lat) / (this.extent.North - this.extent.South) * rows);
|
|
488
|
+
if (col < 0 || row < 0 || col >= cols || row >= rows) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
const at = (row * cols + col) * 4 + 3;
|
|
492
|
+
// Painted, not merely covered: a dry cell holds a reading the ramp deliberately hides, and
|
|
493
|
+
// ruling a grid over it draws detail where the picture shows none.
|
|
494
|
+
if (this.painted && this.painted.length === cols * rows * 4) {
|
|
495
|
+
return this.painted[at] > 0;
|
|
496
|
+
}
|
|
497
|
+
return this.source.data[at] >= 128;
|
|
498
|
+
}
|
|
499
|
+
isCovered(col, row, cols) {
|
|
500
|
+
return Boolean(this.source && this.source.data[(row * cols + col) * 4 + 3] >= 128);
|
|
501
|
+
}
|
|
502
|
+
/*
|
|
503
|
+
* Width of the widest label this overlay could draw, in pixels.
|
|
504
|
+
*/
|
|
505
|
+
widestLabel(parts) {
|
|
506
|
+
const sample = [];
|
|
507
|
+
if (parts.indexOf("value") >= 0) {
|
|
508
|
+
sample.push("-000.00");
|
|
509
|
+
}
|
|
510
|
+
const range = [];
|
|
511
|
+
if (parts.indexOf("floor") >= 0) {
|
|
512
|
+
range.push("min: -000.00");
|
|
513
|
+
}
|
|
514
|
+
if (parts.indexOf("ceiling") >= 0) {
|
|
515
|
+
range.push("max: -000.00");
|
|
516
|
+
}
|
|
517
|
+
if (range.length) {
|
|
518
|
+
sample.push(range.join(" - "));
|
|
519
|
+
}
|
|
520
|
+
if (!sample.length) {
|
|
521
|
+
return 0;
|
|
522
|
+
}
|
|
523
|
+
const widest = Math.max(...sample.map((line) => this.ctx.measureText(line).width));
|
|
524
|
+
return widest + LABEL_GAP_PIXELS;
|
|
525
|
+
}
|
|
526
|
+
/*
|
|
527
|
+
* The four screen corners of one cell, or null if any of them will not project.
|
|
528
|
+
*/
|
|
529
|
+
cellOutline(col, row, cols, rows) {
|
|
530
|
+
// On a pyramid the composited texel is not a cell. Outlining it would draw a second,
|
|
531
|
+
// uniform lattice over the tile grid, misaligned with it everywhere.
|
|
532
|
+
if (this.tiles && this.tiles.length) {
|
|
533
|
+
return this.tileCellOutline(this.lonAt(col, cols), this.latAt(row, rows));
|
|
534
|
+
}
|
|
535
|
+
const corners = [
|
|
536
|
+
this.project(this.lonAt(col - 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
537
|
+
this.project(this.lonAt(col + 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
538
|
+
this.project(this.lonAt(col + 0.5, cols), this.latAt(row + 0.5, rows)),
|
|
539
|
+
this.project(this.lonAt(col - 0.5, cols), this.latAt(row + 0.5, rows))
|
|
540
|
+
];
|
|
541
|
+
return corners.every((c) => c) ? corners : null;
|
|
542
|
+
}
|
|
543
|
+
drawLabel(point, reading, parts, outline) {
|
|
544
|
+
var _a;
|
|
545
|
+
const dp = (_a = this.settings.decimals) !== null && _a !== void 0 ? _a : (Math.abs(reading.value) < 10 ? 2 : 1);
|
|
546
|
+
const lines = [];
|
|
547
|
+
if (parts.indexOf("value") >= 0) {
|
|
548
|
+
lines.push(reading.value.toFixed(dp));
|
|
549
|
+
}
|
|
550
|
+
// Named rather than a bare pair: two numbers under a third say nothing about which is
|
|
551
|
+
// the series low and which is its high.
|
|
552
|
+
const range = [];
|
|
553
|
+
if (parts.indexOf("floor") >= 0 && reading.floor !== undefined) {
|
|
554
|
+
range.push(`min: ${reading.floor.toFixed(dp)}`);
|
|
555
|
+
}
|
|
556
|
+
if (parts.indexOf("ceiling") >= 0 && reading.ceiling !== undefined) {
|
|
557
|
+
range.push(`max: ${reading.ceiling.toFixed(dp)}`);
|
|
558
|
+
}
|
|
559
|
+
if (range.length) {
|
|
560
|
+
lines.push(range.join(" - "));
|
|
561
|
+
}
|
|
562
|
+
if (!lines.length) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (outline) {
|
|
566
|
+
this.ctx.strokeStyle = LABEL_ANCHOR_COLOR;
|
|
567
|
+
this.ctx.lineWidth = 1.5;
|
|
568
|
+
this.ctx.beginPath();
|
|
569
|
+
this.ctx.moveTo(outline[0].x, outline[0].y);
|
|
570
|
+
for (let i = 1; i < outline.length; i++) {
|
|
571
|
+
this.ctx.lineTo(outline[i].x, outline[i].y);
|
|
572
|
+
}
|
|
573
|
+
this.ctx.closePath();
|
|
574
|
+
this.ctx.stroke();
|
|
575
|
+
}
|
|
576
|
+
const width = Math.max(...lines.map((l) => this.ctx.measureText(l).width)) + 10;
|
|
577
|
+
const height = lines.length * LINE_HEIGHT + 6;
|
|
578
|
+
const top = point.y - height / 2;
|
|
579
|
+
this.ctx.fillStyle = "rgba(16,16,14,0.72)";
|
|
580
|
+
this.ctx.beginPath();
|
|
581
|
+
this.ctx.roundRect(point.x - width / 2, top, width, height, LABEL_RADIUS);
|
|
582
|
+
this.ctx.fill();
|
|
583
|
+
lines.forEach((line, i) => {
|
|
584
|
+
this.ctx.fillStyle = i === 0 ? "#ffffff" : "#a8a89f";
|
|
585
|
+
this.ctx.fillText(line, point.x, top + LINE_HEIGHT * (i + 1) - 1);
|
|
586
|
+
});
|
|
587
|
+
this.drawn++;
|
|
588
|
+
}
|
|
589
|
+
/*
|
|
590
|
+
* The first cell with a reading at or near a lattice point, searched within its own block.
|
|
591
|
+
*/
|
|
592
|
+
readNear(col, row, cols, rows, stride) {
|
|
593
|
+
const direct = this.readAt(col, row, cols);
|
|
594
|
+
if (direct && this.paintedAt(col, row, cols)) {
|
|
595
|
+
return { col, row, reading: direct };
|
|
596
|
+
}
|
|
597
|
+
if (stride <= 1) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
// Bounded, and stepped rather than exhaustive, so a large stride cannot make this the
|
|
601
|
+
// expensive part of a frame.
|
|
602
|
+
const step = Math.max(1, Math.floor(stride / MAX_LABEL_PROBES_PER_SIDE));
|
|
603
|
+
for (let r = row; r < Math.min(rows, row + stride); r += step) {
|
|
604
|
+
for (let c = col; c < Math.min(cols, col + stride); c += step) {
|
|
605
|
+
const reading = this.readAt(c, r, cols);
|
|
606
|
+
if (reading && this.paintedAt(c, r, cols)) {
|
|
607
|
+
return { col: c, row: r, reading };
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
paintedAt(col, row, cols) {
|
|
614
|
+
if (!this.painted) {
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
const at = (row * cols + col) * 4 + 3;
|
|
618
|
+
return this.painted[at] > 0;
|
|
619
|
+
}
|
|
620
|
+
// Decodes one cell, skipping anything the texture marks as having no data.
|
|
621
|
+
readAt(col, row, cols) {
|
|
622
|
+
var _a, _b;
|
|
623
|
+
const i = (row * cols + col) * 4;
|
|
624
|
+
if (!this.source || this.source.data[i + 3] < 128) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
const lo = this.metadata && typeof this.metadata.ValueMin === "number" ? this.metadata.ValueMin : 0;
|
|
628
|
+
const hi = this.metadata && typeof this.metadata.ValueMax === "number" ? this.metadata.ValueMax : 1;
|
|
629
|
+
const value = lo + (this.source.data[i] / 255) * (hi - lo);
|
|
630
|
+
const reading = { value };
|
|
631
|
+
const eLo = (_a = this.metadata) === null || _a === void 0 ? void 0 : _a.ExtremesValueMin;
|
|
632
|
+
const eHi = (_b = this.metadata) === null || _b === void 0 ? void 0 : _b.ExtremesValueMax;
|
|
633
|
+
if (this.floor && typeof eLo === "number" && typeof eHi === "number") {
|
|
634
|
+
reading.floor = eLo + decode16(this.floor.data, i) * (eHi - eLo);
|
|
635
|
+
// An anomaly archive's frames are already floor-relative, so the reading a user
|
|
636
|
+
// wants is the absolute one. This is the whole reason the floor is published.
|
|
637
|
+
if (texture_frame_series_animator_1.TextureFrameSeriesAnimator.IsAnomalyMeasure(this.metadata)) {
|
|
638
|
+
reading.value += reading.floor;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (this.ceiling && typeof eLo === "number" && typeof eHi === "number") {
|
|
642
|
+
reading.ceiling = eLo + decode16(this.ceiling.data, i) * (eHi - eLo);
|
|
643
|
+
}
|
|
644
|
+
return reading;
|
|
645
|
+
}
|
|
646
|
+
lonAt(col, cols) {
|
|
647
|
+
return this.extent.West + ((col + 0.5) / cols) * (this.extent.East - this.extent.West);
|
|
648
|
+
}
|
|
649
|
+
latAt(row, rows) {
|
|
650
|
+
return this.extent.North - ((row + 0.5) / rows) * (this.extent.North - this.extent.South);
|
|
651
|
+
}
|
|
652
|
+
// The texel range overlapping the camera's view, so an off-screen texture costs nothing.
|
|
653
|
+
visibleTexels(cols, rows) {
|
|
654
|
+
const view = this.viewer.camera.computeViewRectangle(Cesium.Ellipsoid.WGS84);
|
|
655
|
+
if (!view) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
const west = Math.max(this.extent.West, Cesium.Math.toDegrees(view.west));
|
|
659
|
+
const east = Math.min(this.extent.East, Cesium.Math.toDegrees(view.east));
|
|
660
|
+
const south = Math.max(this.extent.South, Cesium.Math.toDegrees(view.south));
|
|
661
|
+
const north = Math.min(this.extent.North, Cesium.Math.toDegrees(view.north));
|
|
662
|
+
if (west >= east || south >= north) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
const spanLon = this.extent.East - this.extent.West;
|
|
666
|
+
const spanLat = this.extent.North - this.extent.South;
|
|
667
|
+
return {
|
|
668
|
+
col0: Math.max(0, Math.floor((west - this.extent.West) / spanLon * cols) - 1),
|
|
669
|
+
col1: Math.min(cols - 1, Math.ceil((east - this.extent.West) / spanLon * cols) + 1),
|
|
670
|
+
row0: Math.max(0, Math.floor((this.extent.North - north) / spanLat * rows) - 1),
|
|
671
|
+
row1: Math.min(rows - 1, Math.ceil((this.extent.North - south) / spanLat * rows) + 1)
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
project(lon, lat) {
|
|
675
|
+
const world = Cesium.Cartesian3.fromDegrees(lon, lat, 0);
|
|
676
|
+
const transforms = Cesium.SceneTransforms;
|
|
677
|
+
// Renamed in newer Cesium, and this library runs on whatever the host app supplies.
|
|
678
|
+
const fn = transforms.worldToWindowCoordinates || transforms.wgs84ToWindowCoordinates;
|
|
679
|
+
return fn ? fn.call(transforms, this.viewer.scene, world) : null;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
TextureValueLabels.Labels = Labels;
|
|
683
|
+
function decode16(data, i) {
|
|
684
|
+
return (data[i] * 256 + data[i + 1]) / 65535;
|
|
685
|
+
}
|
|
686
|
+
})(TextureValueLabels = exports.TextureValueLabels || (exports.TextureValueLabels = {}));
|
|
687
|
+
//# sourceMappingURL=texture-value-labels.js.map
|