bruce-cesium 7.5.8 → 7.5.9
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 +938 -274
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +936 -272
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/rendering/displaced-surface-primitive.js +187 -36
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +20 -9
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js +2 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +220 -20
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +478 -194
- package/dist/lib/rendering/texture-value-labels.js.map +1 -1
- package/dist/lib/rendering/visuals-register.js +27 -9
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/rendering/displaced-surface-primitive.d.ts +32 -0
- package/dist/types/rendering/entity-render-engine-polygon.d.ts +1 -0
- package/dist/types/rendering/texture-frame-series-animator.d.ts +42 -0
- package/dist/types/rendering/texture-value-labels.d.ts +59 -11
- package/dist/types/rendering/visuals-register.d.ts +6 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TextureValueLabels = void 0;
|
|
4
4
|
const Cesium = require("cesium");
|
|
5
5
|
const texture_frame_series_animator_1 = require("./texture-frame-series-animator");
|
|
6
|
+
const entity_label_1 = require("./render-managers/common/entity-label");
|
|
6
7
|
/**
|
|
7
8
|
* Prints a value texture's own numbers over the cells they belong to.
|
|
8
9
|
*/
|
|
@@ -14,13 +15,15 @@ var TextureValueLabels;
|
|
|
14
15
|
const MIN_VISIBLE_PIXELS = 60;
|
|
15
16
|
const MAX_STRIDE = 512;
|
|
16
17
|
const STRIDE_HYSTERESIS = 1.4;
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
18
|
+
// Scene labels carry a leader and are wider than a value, so they want room and not many of them.
|
|
19
|
+
const DEFAULT_MIN_SPACING_PIXELS = 140;
|
|
20
|
+
const DEFAULT_MAX_LABELS = 60;
|
|
21
|
+
// How far past the edge of the view an anchor may sit before its label is dropped.
|
|
22
|
+
const LABEL_EDGE_MARGIN_PIXELS = 120;
|
|
23
|
+
// Distance discount for a label already up when the cap forces a choice.
|
|
24
|
+
const LABEL_KEEP_BIAS = 0.6;
|
|
25
|
+
// How long a label outlives its cell leaving the wanted set, so one back a frame later does not blink.
|
|
26
|
+
const LABEL_LINGER_MS = 300;
|
|
24
27
|
// 8 rather than 5: below this a cell reads as texture rather than as a boundary, and it also
|
|
25
28
|
// bounds the worst case, since a full screen of 8 px cells fits inside MAX_GRID_CELLS.
|
|
26
29
|
const DEFAULT_MIN_GRID_CELL_PIXELS = 8;
|
|
@@ -34,7 +37,8 @@ var TextureValueLabels;
|
|
|
34
37
|
const TILE_OUTLINE_ALPHA = 0.45;
|
|
35
38
|
// Cells per grid square at the coarsest. Beyond this a tile is outlined instead.
|
|
36
39
|
const MAX_GRID_STRIDE = 256;
|
|
37
|
-
|
|
40
|
+
// Times the pointer's ground hit is moved onto the sheet's own height before reading a cell.
|
|
41
|
+
const HOVER_REFINE_STEPS = 3;
|
|
38
42
|
/**
|
|
39
43
|
* A clip outline from a projected ring, or null when the ring cannot be drawn.
|
|
40
44
|
* @param projected one entry per ring vertex, null where the vertex has no window coordinate
|
|
@@ -128,11 +132,25 @@ var TextureValueLabels;
|
|
|
128
132
|
this.sourceMap = null;
|
|
129
133
|
this.source = null;
|
|
130
134
|
this.painted = null;
|
|
135
|
+
this.paintedRamp = null;
|
|
131
136
|
this.floor = null;
|
|
132
137
|
this.ceiling = null;
|
|
133
138
|
this.disposed = false;
|
|
134
|
-
this.drawn = 0;
|
|
135
139
|
this.stride = 0;
|
|
140
|
+
this.cellLabels = new Map();
|
|
141
|
+
this.lingerTimer = null;
|
|
142
|
+
// What the last draw was made from, so a frame that changes none of it draws nothing.
|
|
143
|
+
this.drawnView = null;
|
|
144
|
+
this.drawnInputs = [];
|
|
145
|
+
this.scratchWorld = new Cesium.Cartesian3();
|
|
146
|
+
this.scratchWindow = new Cesium.Cartesian2();
|
|
147
|
+
this.scratchClip = new Cesium.Cartesian4();
|
|
148
|
+
// The camera's view and projection as one matrix, rebuilt per draw, or null outside 3D.
|
|
149
|
+
this.viewProjection = null;
|
|
150
|
+
this.viewProjectionScratch = new Cesium.Matrix4();
|
|
151
|
+
this.hovering = false;
|
|
152
|
+
this.hoverFrame = 0;
|
|
153
|
+
this.hoverAt = null;
|
|
136
154
|
this.viewer = options.viewer;
|
|
137
155
|
this.extent = options.extent;
|
|
138
156
|
this.settings = options.settings || {};
|
|
@@ -142,6 +160,9 @@ var TextureValueLabels;
|
|
|
142
160
|
this.sourceMap = texture_frame_series_animator_1.TextureFrameSeriesAnimator.SourceMap(options.metadata);
|
|
143
161
|
this.tiles = options.tiles;
|
|
144
162
|
this.clipRing = options.clipRing;
|
|
163
|
+
this.heightAt = options.heightAt;
|
|
164
|
+
this.heightKey = options.heightKey;
|
|
165
|
+
this.hover = options.hover;
|
|
145
166
|
this.canvas = document.createElement("canvas");
|
|
146
167
|
this.canvas.style.position = "absolute";
|
|
147
168
|
this.canvas.style.inset = "0";
|
|
@@ -151,12 +172,31 @@ var TextureValueLabels;
|
|
|
151
172
|
parent.appendChild(this.canvas);
|
|
152
173
|
}
|
|
153
174
|
this.ctx = this.canvas.getContext("2d");
|
|
175
|
+
this.onPointerMove = (event) => {
|
|
176
|
+
const bounds = this.viewer.canvas.getBoundingClientRect();
|
|
177
|
+
this.hoverAt = { x: event.clientX - bounds.left, y: event.clientY - bounds.top };
|
|
178
|
+
// At most once a frame, since a pointer reports far faster than anything is drawn.
|
|
179
|
+
if (!this.hoverFrame) {
|
|
180
|
+
this.hoverFrame = requestAnimationFrame(() => {
|
|
181
|
+
this.hoverFrame = 0;
|
|
182
|
+
this.updateHover();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
this.onPointerLeave = () => {
|
|
187
|
+
this.hoverAt = null;
|
|
188
|
+
this.releaseHover();
|
|
189
|
+
};
|
|
190
|
+
if (this.hover && this.hover.register) {
|
|
191
|
+
this.viewer.canvas.addEventListener("pointermove", this.onPointerMove, { passive: true });
|
|
192
|
+
this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
|
|
193
|
+
}
|
|
154
194
|
}
|
|
155
195
|
SetExtent(extent) {
|
|
156
196
|
this.extent = extent;
|
|
157
197
|
}
|
|
158
198
|
/**
|
|
159
|
-
* The current frame's decoded values
|
|
199
|
+
* The current frame's decoded values.
|
|
160
200
|
* @param source value pixels, one texel per cell
|
|
161
201
|
*/
|
|
162
202
|
SetSource(source) {
|
|
@@ -164,11 +204,17 @@ var TextureValueLabels;
|
|
|
164
204
|
}
|
|
165
205
|
/**
|
|
166
206
|
* The tinted pixels of the same frame, so the grid can skip cells the ramp painted nothing in.
|
|
167
|
-
* @param painted RGBA of the presented frame, or null to fall back to coverage alone
|
|
207
|
+
* @param painted RGBA of the presented frame, or null to fall back to the ramp or coverage alone
|
|
168
208
|
*/
|
|
169
209
|
SetPainted(painted) {
|
|
170
210
|
this.painted = painted;
|
|
171
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* The surface's 256 step RGBA ramp, for telling whether a cell was painted without a tinted frame.
|
|
214
|
+
*/
|
|
215
|
+
SetPaintedRamp(ramp) {
|
|
216
|
+
this.paintedRamp = ramp;
|
|
217
|
+
}
|
|
172
218
|
/**
|
|
173
219
|
* The archive's per-cell extremes, when it publishes them. Without these a label can still
|
|
174
220
|
* print the current reading, just not the range behind it.
|
|
@@ -178,21 +224,28 @@ var TextureValueLabels;
|
|
|
178
224
|
this.ceiling = ceiling;
|
|
179
225
|
}
|
|
180
226
|
GetDrawnCount() {
|
|
181
|
-
return this.
|
|
227
|
+
return this.cellLabels.size;
|
|
182
228
|
}
|
|
183
229
|
Clear() {
|
|
184
230
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
185
|
-
this.
|
|
231
|
+
this.drawnView = null;
|
|
186
232
|
}
|
|
187
233
|
Dispose() {
|
|
188
234
|
this.disposed = true;
|
|
235
|
+
this.clearCellLabels();
|
|
236
|
+
this.releaseHover();
|
|
237
|
+
if (this.hoverFrame) {
|
|
238
|
+
cancelAnimationFrame(this.hoverFrame);
|
|
239
|
+
this.hoverFrame = 0;
|
|
240
|
+
}
|
|
241
|
+
this.viewer.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
242
|
+
this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
|
|
189
243
|
if (this.canvas.parentElement) {
|
|
190
244
|
this.canvas.parentElement.removeChild(this.canvas);
|
|
191
245
|
}
|
|
192
246
|
}
|
|
193
247
|
/**
|
|
194
|
-
* Redraws
|
|
195
|
-
* count it considers is bounded by the view rather than by the texture.
|
|
248
|
+
* Redraws the grid and syncs the value labels, when anything they are drawn from has changed.
|
|
196
249
|
*/
|
|
197
250
|
Render() {
|
|
198
251
|
var _a, _b, _c;
|
|
@@ -200,67 +253,296 @@ var TextureValueLabels;
|
|
|
200
253
|
return;
|
|
201
254
|
}
|
|
202
255
|
const view = this.viewer.canvas;
|
|
256
|
+
const inputs = [this.source, this.painted, this.paintedRamp, this.floor, this.ceiling,
|
|
257
|
+
view.clientWidth, view.clientHeight, this.heightKey ? this.heightKey() : null];
|
|
258
|
+
const camera = this.viewer.camera.viewMatrix;
|
|
259
|
+
if (this.drawnView &&
|
|
260
|
+
Cesium.Matrix4.equals(this.drawnView, camera) &&
|
|
261
|
+
inputs.every((input, i) => input === this.drawnInputs[i])) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
203
264
|
if (this.canvas.width !== view.clientWidth || this.canvas.height !== view.clientHeight) {
|
|
204
265
|
this.canvas.width = view.clientWidth;
|
|
205
266
|
this.canvas.height = view.clientHeight;
|
|
206
267
|
}
|
|
207
268
|
this.Clear();
|
|
269
|
+
this.drawnView = Cesium.Matrix4.clone(camera, this.drawnView || undefined);
|
|
270
|
+
this.drawnInputs = inputs;
|
|
271
|
+
const projection = this.viewer.camera.frustum.projectionMatrix;
|
|
272
|
+
this.viewProjection = this.viewer.scene.mode === Cesium.SceneMode.SCENE3D
|
|
273
|
+
? Cesium.Matrix4.multiply(projection, camera, this.viewProjectionScratch)
|
|
274
|
+
: null;
|
|
208
275
|
const cols = this.source.width;
|
|
209
276
|
const rows = this.source.height;
|
|
210
277
|
const window = this.visibleTexels(cols, rows);
|
|
211
278
|
if (!window) {
|
|
279
|
+
this.syncCellLabels([]);
|
|
212
280
|
return;
|
|
213
281
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
const
|
|
282
|
+
// Measured where the view is looking, since in an oblique view up close the middle of the visible
|
|
283
|
+
// texels sits out toward the horizon, where cells are a fraction of the size.
|
|
284
|
+
const focus = this.groundAtScreenCentre();
|
|
285
|
+
const at = focus ? this.texelAt(focus.lon, focus.lat) : null;
|
|
286
|
+
const focused = (Boolean(at) &&
|
|
287
|
+
at.col >= window.col0 &&
|
|
288
|
+
at.col <= window.col1 &&
|
|
289
|
+
at.row >= window.row0 &&
|
|
290
|
+
at.row <= window.row1);
|
|
291
|
+
const midCol = focused ? at.col : (window.col0 + window.col1) / 2;
|
|
292
|
+
const midRow = focused ? at.row : (window.row0 + window.row1) / 2;
|
|
293
|
+
const a = this.projectCell(midCol, midRow, cols, rows);
|
|
294
|
+
const b = this.projectCell(midCol + 1, midRow + 1, cols, rows);
|
|
218
295
|
if (!a || !b) {
|
|
296
|
+
// Left as they are, since one frame where the focus will not project is not the data leaving.
|
|
219
297
|
return;
|
|
220
298
|
}
|
|
221
299
|
const cellPixels = Math.min(Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
222
300
|
// Ahead of the label threshold on purpose: a grid still reads at a cell size where a
|
|
223
301
|
// number no longer fits.
|
|
224
302
|
this.drawGrid(window, cols, rows, cellPixels);
|
|
225
|
-
|
|
303
|
+
// By area rather than the shorter side, which an oblique view foreshortens to a fraction of the other.
|
|
304
|
+
const across = this.projectCell(midCol + 1, midRow, cols, rows);
|
|
305
|
+
const down = this.projectCell(midCol, midRow + 1, cols, rows);
|
|
306
|
+
const cellSize = across && down
|
|
307
|
+
? Math.sqrt(Math.abs((across.x - a.x) * (down.y - a.y) - (across.y - a.y) * (down.x - a.x)))
|
|
308
|
+
: cellPixels;
|
|
309
|
+
const visibleWidth = cellSize * (window.col1 - window.col0 + 1);
|
|
310
|
+
const visibleHeight = cellSize * (window.row1 - window.row0 + 1);
|
|
311
|
+
if (cellSize < ((_a = this.settings.minCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_CELL_PIXELS) ||
|
|
312
|
+
Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
313
|
+
this.syncCellLabels([]);
|
|
226
314
|
return;
|
|
227
315
|
}
|
|
228
|
-
const visibleWidth = cellPixels * (window.col1 - window.col0 + 1);
|
|
229
|
-
const visibleHeight = cellPixels * (window.row1 - window.row0 + 1);
|
|
230
|
-
if (Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
234
|
-
this.ctx.font = LABEL_FONT;
|
|
235
|
-
this.ctx.textAlign = "center";
|
|
236
316
|
const maxLabels = (_b = this.settings.maxLabels) !== null && _b !== void 0 ? _b : DEFAULT_MAX_LABELS;
|
|
237
317
|
this.stride = ChooseLabelStride({
|
|
238
|
-
cellPixels,
|
|
318
|
+
cellPixels: cellSize,
|
|
239
319
|
visibleCols: window.col1 - window.col0 + 1,
|
|
240
320
|
visibleRows: window.row1 - window.row0 + 1,
|
|
241
321
|
maxLabels,
|
|
242
|
-
|
|
243
|
-
// one is, so changing what a label says cannot quietly start overlapping them.
|
|
244
|
-
minSpacingPixels: Math.max((_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS, this.widestLabel(parts)),
|
|
322
|
+
minSpacingPixels: (_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS,
|
|
245
323
|
held: this.stride
|
|
246
324
|
});
|
|
247
|
-
const
|
|
325
|
+
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
326
|
+
this.syncCellLabels(this.labelCells(window, cols, rows, this.stride, maxLabels, parts));
|
|
327
|
+
}
|
|
328
|
+
/*
|
|
329
|
+
* One labelled cell per lattice block, keyed by the block's origin so it survives frame and stride changes.
|
|
330
|
+
*/
|
|
331
|
+
labelCells(window, cols, rows, stride, max, parts) {
|
|
332
|
+
const width = this.canvas.width;
|
|
333
|
+
const height = this.canvas.height;
|
|
248
334
|
const firstRow = Math.floor(window.row0 / stride) * stride;
|
|
249
335
|
const firstCol = Math.floor(window.col0 / stride) * stride;
|
|
250
|
-
|
|
251
|
-
|
|
336
|
+
const candidates = [];
|
|
337
|
+
for (let row = firstRow; row <= window.row1; row += stride) {
|
|
338
|
+
for (let col = firstCol; col <= window.col1; col += stride) {
|
|
252
339
|
const found = this.readNear(col, row, cols, rows, stride);
|
|
253
340
|
if (!found) {
|
|
254
341
|
continue;
|
|
255
342
|
}
|
|
256
|
-
const point = this.
|
|
257
|
-
|
|
258
|
-
|
|
343
|
+
const point = this.projectCell(found.col, found.row, cols, rows);
|
|
344
|
+
// A margin past the edge, so a label does not drop the moment its anchor does.
|
|
345
|
+
const margin = LABEL_EDGE_MARGIN_PIXELS;
|
|
346
|
+
if (!point ||
|
|
347
|
+
point.x < -margin ||
|
|
348
|
+
point.y < -margin ||
|
|
349
|
+
point.x > width + margin ||
|
|
350
|
+
point.y > height + margin) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const text = this.labelLines(found.reading, parts).join("\n");
|
|
354
|
+
if (!text) {
|
|
259
355
|
continue;
|
|
260
356
|
}
|
|
261
|
-
|
|
357
|
+
const key = col + "," + row;
|
|
358
|
+
const distance = Math.hypot(point.x - width / 2, point.y - height / 2);
|
|
359
|
+
candidates.push({
|
|
360
|
+
key,
|
|
361
|
+
lon: this.lonAt(found.col, cols),
|
|
362
|
+
lat: this.latAt(found.row, rows),
|
|
363
|
+
text,
|
|
364
|
+
// Labels already up outrank new ones, so the capped set does not shuffle as the view pans.
|
|
365
|
+
rank: this.cellLabels.has(key) ? distance * LABEL_KEEP_BIAS : distance
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (candidates.length > max) {
|
|
370
|
+
candidates.sort((x, y) => x.rank - y.rank);
|
|
371
|
+
candidates.length = max;
|
|
372
|
+
}
|
|
373
|
+
return candidates;
|
|
374
|
+
}
|
|
375
|
+
/*
|
|
376
|
+
* Keeps one scene label per wanted cell, reusing those that are still wanted.
|
|
377
|
+
*/
|
|
378
|
+
syncCellLabels(wanted) {
|
|
379
|
+
var _a;
|
|
380
|
+
const keep = new Set(wanted.map((w) => w.key));
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
let lingering = false;
|
|
383
|
+
for (const [key, entry] of this.cellLabels) {
|
|
384
|
+
if (keep.has(key)) {
|
|
385
|
+
entry.missingSince = undefined;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
entry.missingSince = (_a = entry.missingSince) !== null && _a !== void 0 ? _a : now;
|
|
389
|
+
if (now - entry.missingSince >= LABEL_LINGER_MS) {
|
|
390
|
+
entry.label.Dispose();
|
|
391
|
+
this.cellLabels.delete(key);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
lingering = true;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// A still camera draws nothing more, so a lingering label is given its own later look.
|
|
398
|
+
if (lingering && !this.lingerTimer) {
|
|
399
|
+
this.lingerTimer = setTimeout(() => {
|
|
400
|
+
this.lingerTimer = null;
|
|
401
|
+
if (!this.disposed) {
|
|
402
|
+
this.drawnView = null;
|
|
403
|
+
this.viewer.scene.requestRender();
|
|
404
|
+
}
|
|
405
|
+
}, LABEL_LINGER_MS);
|
|
406
|
+
}
|
|
407
|
+
for (const want of wanted) {
|
|
408
|
+
const pos3d = Cesium.Cartesian3.fromDegrees(want.lon, want.lat, this.surfaceHeight(want.lon, want.lat));
|
|
409
|
+
const options = {
|
|
410
|
+
presentation: "text",
|
|
411
|
+
text: want.text,
|
|
412
|
+
leader: true,
|
|
413
|
+
selectOnClick: false
|
|
414
|
+
};
|
|
415
|
+
const existing = this.cellLabels.get(want.key);
|
|
416
|
+
if (existing) {
|
|
417
|
+
if (!Cesium.Cartesian3.equalsEpsilon(existing.label.pos3d, pos3d, 0, 0.01)) {
|
|
418
|
+
existing.label.pos3d = pos3d;
|
|
419
|
+
}
|
|
420
|
+
if (existing.text !== want.text) {
|
|
421
|
+
existing.label.Configure(options);
|
|
422
|
+
existing.text = want.text;
|
|
423
|
+
}
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const id = (this.hover ? this.hover.entityId : "texture-value") + "#cell:" + want.key;
|
|
427
|
+
const label = new entity_label_1.EntityLabel.Label({
|
|
428
|
+
rego: {
|
|
429
|
+
entityId: id,
|
|
430
|
+
menuItemId: this.hover ? this.hover.menuItemId : undefined,
|
|
431
|
+
name: want.text,
|
|
432
|
+
priority: 0,
|
|
433
|
+
// No graphic, since a point or billboard visual pulls a label back to itself on rebuild.
|
|
434
|
+
visual: new Cesium.Entity({ position: pos3d })
|
|
435
|
+
},
|
|
436
|
+
viewer: this.viewer,
|
|
437
|
+
pos3d,
|
|
438
|
+
options,
|
|
439
|
+
persistent: true,
|
|
440
|
+
visible: () => !this.disposed
|
|
441
|
+
});
|
|
442
|
+
this.cellLabels.set(want.key, { label, text: want.text });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
clearCellLabels() {
|
|
446
|
+
if (this.lingerTimer) {
|
|
447
|
+
clearTimeout(this.lingerTimer);
|
|
448
|
+
this.lingerTimer = null;
|
|
449
|
+
}
|
|
450
|
+
for (const entry of this.cellLabels.values()) {
|
|
451
|
+
entry.label.Dispose();
|
|
452
|
+
}
|
|
453
|
+
this.cellLabels.clear();
|
|
454
|
+
}
|
|
455
|
+
/*
|
|
456
|
+
* Shows the cell under the pointer through the register's hover label, unless something else holds it.
|
|
457
|
+
*/
|
|
458
|
+
updateHover() {
|
|
459
|
+
const hover = this.hover;
|
|
460
|
+
if (this.disposed || !hover || !hover.register || !this.source || !this.hoverAt) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const register = hover.register;
|
|
464
|
+
const target = register.GetLabelHoverTarget();
|
|
465
|
+
const ours = Boolean(target) && register.IsLabelHovered(hover.entityId, hover.menuItemId);
|
|
466
|
+
if (target && !ours) {
|
|
467
|
+
this.hovering = false;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const under = this.readingUnder(this.hoverAt.x, this.hoverAt.y);
|
|
471
|
+
if (!under) {
|
|
472
|
+
this.releaseHover();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
register.SetLabelHover({
|
|
476
|
+
entityId: hover.entityId,
|
|
477
|
+
menuItemId: hover.menuItemId,
|
|
478
|
+
position: { x: this.hoverAt.x, y: this.hoverAt.y },
|
|
479
|
+
text: under.text,
|
|
480
|
+
pos3d: under.pos3d
|
|
481
|
+
});
|
|
482
|
+
this.hovering = true;
|
|
483
|
+
}
|
|
484
|
+
releaseHover() {
|
|
485
|
+
const hover = this.hover;
|
|
486
|
+
if (!this.hovering || !hover || !hover.register) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
this.hovering = false;
|
|
490
|
+
if (hover.register.IsLabelHovered(hover.entityId, hover.menuItemId)) {
|
|
491
|
+
hover.register.ClearLabelHover();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/*
|
|
495
|
+
* The text for the painted cell under a window position and its centre on the sheet, or null.
|
|
496
|
+
*/
|
|
497
|
+
readingUnder(x, y) {
|
|
498
|
+
const at = this.cellUnder(x, y);
|
|
499
|
+
if (!at) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
const cols = this.source.width;
|
|
503
|
+
const found = this.readAt(at.col, at.row, cols);
|
|
504
|
+
if (!found || !this.paintedAt(at.col, at.row, cols)) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
const text = this.labelLines(found, this.settings.parts || ["value", "floor", "ceiling"]).join("\n");
|
|
508
|
+
if (!text) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const lon = this.lonAt(at.col, cols);
|
|
512
|
+
const lat = this.latAt(at.row, this.source.height);
|
|
513
|
+
return { text, pos3d: Cesium.Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat)) };
|
|
514
|
+
}
|
|
515
|
+
/*
|
|
516
|
+
* The cell under a window position, found on the sheet rather than on the ground beneath it.
|
|
517
|
+
*/
|
|
518
|
+
cellUnder(x, y) {
|
|
519
|
+
const scene = this.viewer.scene;
|
|
520
|
+
const camera = this.viewer.camera;
|
|
521
|
+
const screen = new Cesium.Cartesian2(x, y);
|
|
522
|
+
const ray = camera.getPickRay(screen);
|
|
523
|
+
if (!ray) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
let hit = (scene.globe && scene.globe.pick(ray, scene)) || camera.pickEllipsoid(screen);
|
|
527
|
+
if (!hit) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
const radii = Cesium.Ellipsoid.WGS84.radii;
|
|
531
|
+
for (let i = 0; i < HOVER_REFINE_STEPS && this.heightAt; i++) {
|
|
532
|
+
const carto = Cesium.Cartographic.fromCartesian(hit);
|
|
533
|
+
const height = this.heightAt(Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude));
|
|
534
|
+
if (height == null) {
|
|
535
|
+
break;
|
|
262
536
|
}
|
|
537
|
+
const inflated = new Cesium.Ellipsoid(radii.x + height, radii.y + height, radii.z + height);
|
|
538
|
+
const interval = Cesium.IntersectionTests.rayEllipsoid(ray, inflated);
|
|
539
|
+
if (!interval) {
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
hit = Cesium.Ray.getPoint(ray, interval.start);
|
|
263
543
|
}
|
|
544
|
+
const carto = Cesium.Cartographic.fromCartesian(hit);
|
|
545
|
+
return this.texelAt(Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude));
|
|
264
546
|
}
|
|
265
547
|
/*
|
|
266
548
|
* Outlines each covered cell, from a projected lattice of its corners.
|
|
@@ -288,44 +570,25 @@ var TextureValueLabels;
|
|
|
288
570
|
if (wide * high > MAX_GRID_CELLS) {
|
|
289
571
|
return;
|
|
290
572
|
}
|
|
291
|
-
const lattice = new
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
const path = new Path2D();
|
|
301
|
-
let drew = false;
|
|
573
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => {
|
|
574
|
+
// Corners sit half a texel out from the centres lonAt/latAt return.
|
|
575
|
+
const lon = this.lonAt(window.col0 + c - 0.5, cols);
|
|
576
|
+
const lat = this.latAt(window.row0 + r - 0.5, rows);
|
|
577
|
+
return this.projectInto(lon, lat);
|
|
578
|
+
});
|
|
579
|
+
const covered = new Uint8Array(wide * high);
|
|
302
580
|
for (let r = 0; r < high; r++) {
|
|
303
581
|
for (let c = 0; c < wide; c++) {
|
|
304
|
-
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
const tl = lattice[r * (wide + 1) + c];
|
|
308
|
-
const tr = lattice[r * (wide + 1) + c + 1];
|
|
309
|
-
const bl = lattice[(r + 1) * (wide + 1) + c];
|
|
310
|
-
const br = lattice[(r + 1) * (wide + 1) + c + 1];
|
|
311
|
-
if (!tl || !tr || !bl || !br) {
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
path.moveTo(tl.x, tl.y);
|
|
315
|
-
path.lineTo(tr.x, tr.y);
|
|
316
|
-
path.lineTo(br.x, br.y);
|
|
317
|
-
path.lineTo(bl.x, bl.y);
|
|
318
|
-
path.closePath();
|
|
319
|
-
drew = true;
|
|
582
|
+
covered[r * wide + c] = this.isCovered(window.col0 + c, window.row0 + r, cols) ? 1 : 0;
|
|
320
583
|
}
|
|
321
584
|
}
|
|
322
|
-
|
|
585
|
+
const path = new Path2D();
|
|
586
|
+
if (!lattice.Trace(path, covered, wide, high)) {
|
|
323
587
|
return;
|
|
324
588
|
}
|
|
325
589
|
this.ctx.strokeStyle = color;
|
|
326
590
|
this.ctx.lineWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
327
591
|
this.ctx.stroke(path);
|
|
328
|
-
+" alpha" + this.ctx.globalAlpha;
|
|
329
592
|
}
|
|
330
593
|
/*
|
|
331
594
|
* Restricts drawing to the polygon, so nothing is ruled over ground it does not cover.
|
|
@@ -347,33 +610,11 @@ var TextureValueLabels;
|
|
|
347
610
|
path.closePath();
|
|
348
611
|
this.ctx.clip(path);
|
|
349
612
|
}
|
|
350
|
-
/*
|
|
351
|
-
* The cell of whichever tile covers a point, as screen corners.
|
|
352
|
-
*/
|
|
353
|
-
tileCellOutline(lon, lat) {
|
|
354
|
-
for (const tile of this.tiles) {
|
|
355
|
-
if (lon < tile.West || lon > tile.East || lat < tile.South || lat > tile.North) {
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
const c = (Math.min(tile.ResolutionX - 1, Math.max(0, Math.floor((lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX))));
|
|
359
|
-
const r = (Math.min(tile.ResolutionY - 1, Math.max(0, Math.floor((tile.North - lat) / (tile.North - tile.South) * tile.ResolutionY))));
|
|
360
|
-
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
361
|
-
const w1 = tile.West + ((c + 1) / tile.ResolutionX) * (tile.East - tile.West);
|
|
362
|
-
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
363
|
-
const n1 = tile.North - ((r + 1) / tile.ResolutionY) * (tile.North - tile.South);
|
|
364
|
-
const corners = [
|
|
365
|
-
this.project(w0, n0), this.project(w1, n0),
|
|
366
|
-
this.project(w1, n1), this.project(w0, n1)
|
|
367
|
-
];
|
|
368
|
-
return corners.every((q) => q) ? corners : null;
|
|
369
|
-
}
|
|
370
|
-
return null;
|
|
371
|
-
}
|
|
372
613
|
/*
|
|
373
614
|
* Outlines each tile's own cells, so cell size still shows where the mesh is refined.
|
|
374
615
|
*/
|
|
375
616
|
drawTileGrid(color) {
|
|
376
|
-
var _a, _b
|
|
617
|
+
var _a, _b;
|
|
377
618
|
const view = this.viewer.camera.computeViewRectangle(Cesium.Ellipsoid.WGS84);
|
|
378
619
|
if (!view) {
|
|
379
620
|
return;
|
|
@@ -402,7 +643,7 @@ var TextureValueLabels;
|
|
|
402
643
|
coarse.lineTo(tr.x, tr.y);
|
|
403
644
|
coarse.lineTo(br.x, br.y);
|
|
404
645
|
coarse.lineTo(bl.x, bl.y);
|
|
405
|
-
coarse.
|
|
646
|
+
coarse.lineTo(tl.x, tl.y);
|
|
406
647
|
drewCoarse = true;
|
|
407
648
|
};
|
|
408
649
|
// Planned for every visible tile BEFORE any is drawn.
|
|
@@ -463,32 +704,30 @@ var TextureValueLabels;
|
|
|
463
704
|
// moves, and so each step up is a subset of the one below it.
|
|
464
705
|
const firstR = Math.floor(r0 / step) * step;
|
|
465
706
|
const firstC = Math.floor(c0 / step) * step;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const br = this.project(w1, n1);
|
|
480
|
-
const bl = this.project(w0, n1);
|
|
481
|
-
if (!tl || !tr || !br || !bl) {
|
|
482
|
-
continue;
|
|
707
|
+
const wide = Math.ceil((c1 - firstC) / step);
|
|
708
|
+
const high = Math.ceil((r1 - firstR) / step);
|
|
709
|
+
const lonOf = (c) => tile.West
|
|
710
|
+
+ (Math.min(firstC + c * step, tile.ResolutionX) / tile.ResolutionX) * (tile.East - tile.West);
|
|
711
|
+
const latOf = (r) => tile.North
|
|
712
|
+
- (Math.min(firstR + r * step, tile.ResolutionY) / tile.ResolutionY) * (tile.North - tile.South);
|
|
713
|
+
const covered = new Uint8Array(wide * high);
|
|
714
|
+
let any = false;
|
|
715
|
+
for (let r = 0; r < high; r++) {
|
|
716
|
+
for (let c = 0; c < wide; c++) {
|
|
717
|
+
if (this.coveredAt((lonOf(c) + lonOf(c + 1)) / 2, (latOf(r) + latOf(r + 1)) / 2)) {
|
|
718
|
+
covered[r * wide + c] = 1;
|
|
719
|
+
any = true;
|
|
483
720
|
}
|
|
484
|
-
path.moveTo(tl.x, tl.y);
|
|
485
|
-
path.lineTo(tr.x, tr.y);
|
|
486
|
-
path.lineTo(br.x, br.y);
|
|
487
|
-
path.lineTo(bl.x, bl.y);
|
|
488
|
-
path.closePath();
|
|
489
|
-
drew = true;
|
|
490
721
|
}
|
|
491
722
|
}
|
|
723
|
+
if (!any) {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
// Projected on demand, since a sparse raster leaves most corners belonging to no cell.
|
|
727
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => this.projectInto(lonOf(c), latOf(r)));
|
|
728
|
+
if (lattice.Trace(path, covered, wide, high)) {
|
|
729
|
+
drew = true;
|
|
730
|
+
}
|
|
492
731
|
}
|
|
493
732
|
const gridWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
494
733
|
if (drewCoarse) {
|
|
@@ -505,7 +744,7 @@ var TextureValueLabels;
|
|
|
505
744
|
return;
|
|
506
745
|
}
|
|
507
746
|
this.ctx.strokeStyle = color;
|
|
508
|
-
this.ctx.lineWidth =
|
|
747
|
+
this.ctx.lineWidth = gridWidth;
|
|
509
748
|
this.ctx.stroke(path);
|
|
510
749
|
}
|
|
511
750
|
/*
|
|
@@ -525,7 +764,7 @@ var TextureValueLabels;
|
|
|
525
764
|
};
|
|
526
765
|
}
|
|
527
766
|
/*
|
|
528
|
-
* Whether the composited raster holds data at a lon/lat.
|
|
767
|
+
* Whether the composited raster holds painted data at a lon/lat.
|
|
529
768
|
*/
|
|
530
769
|
coveredAt(lon, lat) {
|
|
531
770
|
if (!this.source) {
|
|
@@ -538,60 +777,20 @@ var TextureValueLabels;
|
|
|
538
777
|
if (col < 0 || row < 0 || col >= cols || row >= rows) {
|
|
539
778
|
return false;
|
|
540
779
|
}
|
|
541
|
-
const at = (row * cols + col) * 4 + 3;
|
|
542
780
|
// Painted, not merely covered: a dry cell holds a reading the ramp deliberately hides, and
|
|
543
781
|
// ruling a grid over it draws detail where the picture shows none.
|
|
544
|
-
if (this.painted
|
|
545
|
-
return this.
|
|
782
|
+
if (this.painted || this.paintedRamp) {
|
|
783
|
+
return this.paintedAt(col, row, cols);
|
|
546
784
|
}
|
|
547
|
-
return this.source.data[
|
|
785
|
+
return this.source.data[(row * cols + col) * 4 + 3] >= 128;
|
|
548
786
|
}
|
|
549
787
|
isCovered(col, row, cols) {
|
|
550
788
|
return Boolean(this.source && this.source.data[(row * cols + col) * 4 + 3] >= 128);
|
|
551
789
|
}
|
|
552
790
|
/*
|
|
553
|
-
*
|
|
791
|
+
* What a label says about one reading, a line at a time.
|
|
554
792
|
*/
|
|
555
|
-
|
|
556
|
-
const sample = [];
|
|
557
|
-
if (parts.indexOf("value") >= 0) {
|
|
558
|
-
sample.push(valueLine("-000.00", LabelUnits(this.metadata)));
|
|
559
|
-
}
|
|
560
|
-
const range = [];
|
|
561
|
-
const showsRange = !texture_frame_series_animator_1.TextureFrameSeriesAnimator.IsTransformed(this.metadata);
|
|
562
|
-
if (showsRange && parts.indexOf("floor") >= 0) {
|
|
563
|
-
range.push("min: -000.00");
|
|
564
|
-
}
|
|
565
|
-
if (showsRange && parts.indexOf("ceiling") >= 0) {
|
|
566
|
-
range.push("max: -000.00");
|
|
567
|
-
}
|
|
568
|
-
if (range.length) {
|
|
569
|
-
sample.push(range.join(" - "));
|
|
570
|
-
}
|
|
571
|
-
if (!sample.length) {
|
|
572
|
-
return 0;
|
|
573
|
-
}
|
|
574
|
-
const widest = Math.max(...sample.map((line) => this.ctx.measureText(line).width));
|
|
575
|
-
return widest + LABEL_GAP_PIXELS;
|
|
576
|
-
}
|
|
577
|
-
/*
|
|
578
|
-
* The four screen corners of one cell, or null if any of them will not project.
|
|
579
|
-
*/
|
|
580
|
-
cellOutline(col, row, cols, rows) {
|
|
581
|
-
// On a pyramid the composited texel is not a cell. Outlining it would draw a second,
|
|
582
|
-
// uniform lattice over the tile grid, misaligned with it everywhere.
|
|
583
|
-
if (this.tiles && this.tiles.length) {
|
|
584
|
-
return this.tileCellOutline(this.lonAt(col, cols), this.latAt(row, rows));
|
|
585
|
-
}
|
|
586
|
-
const corners = [
|
|
587
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
588
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
589
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row + 0.5, rows)),
|
|
590
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row + 0.5, rows))
|
|
591
|
-
];
|
|
592
|
-
return corners.every((c) => c) ? corners : null;
|
|
593
|
-
}
|
|
594
|
-
drawLabel(point, reading, parts, outline) {
|
|
793
|
+
labelLines(reading, parts) {
|
|
595
794
|
var _a;
|
|
596
795
|
const dp = (_a = this.settings.decimals) !== null && _a !== void 0 ? _a : (Math.abs(reading.value) < 10 ? 2 : 1);
|
|
597
796
|
const lines = [];
|
|
@@ -610,32 +809,7 @@ var TextureValueLabels;
|
|
|
610
809
|
if (range.length) {
|
|
611
810
|
lines.push(range.join(" - "));
|
|
612
811
|
}
|
|
613
|
-
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
if (outline) {
|
|
617
|
-
this.ctx.strokeStyle = LABEL_ANCHOR_COLOR;
|
|
618
|
-
this.ctx.lineWidth = 1.5;
|
|
619
|
-
this.ctx.beginPath();
|
|
620
|
-
this.ctx.moveTo(outline[0].x, outline[0].y);
|
|
621
|
-
for (let i = 1; i < outline.length; i++) {
|
|
622
|
-
this.ctx.lineTo(outline[i].x, outline[i].y);
|
|
623
|
-
}
|
|
624
|
-
this.ctx.closePath();
|
|
625
|
-
this.ctx.stroke();
|
|
626
|
-
}
|
|
627
|
-
const width = Math.max(...lines.map((l) => this.ctx.measureText(l).width)) + 10;
|
|
628
|
-
const height = lines.length * LINE_HEIGHT + 6;
|
|
629
|
-
const top = point.y - height / 2;
|
|
630
|
-
this.ctx.fillStyle = "rgba(16,16,14,0.72)";
|
|
631
|
-
this.ctx.beginPath();
|
|
632
|
-
this.ctx.roundRect(point.x - width / 2, top, width, height, LABEL_RADIUS);
|
|
633
|
-
this.ctx.fill();
|
|
634
|
-
lines.forEach((line, i) => {
|
|
635
|
-
this.ctx.fillStyle = i === 0 ? "#ffffff" : "#a8a89f";
|
|
636
|
-
this.ctx.fillText(line, point.x, top + LINE_HEIGHT * (i + 1) - 1);
|
|
637
|
-
});
|
|
638
|
-
this.drawn++;
|
|
812
|
+
return lines;
|
|
639
813
|
}
|
|
640
814
|
/*
|
|
641
815
|
* The first cell with a reading at or near a lattice point, searched within its own block.
|
|
@@ -662,11 +836,24 @@ var TextureValueLabels;
|
|
|
662
836
|
return null;
|
|
663
837
|
}
|
|
664
838
|
paintedAt(col, row, cols) {
|
|
665
|
-
|
|
666
|
-
|
|
839
|
+
const at = (row * cols + col) * 4;
|
|
840
|
+
if (this.painted && this.painted.length === this.source.data.length) {
|
|
841
|
+
return this.painted[at + 3] > 0;
|
|
842
|
+
}
|
|
843
|
+
if (this.paintedRamp) {
|
|
844
|
+
const data = this.source.data;
|
|
845
|
+
if (data[at + 3] === 0) {
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
// The 8 bit grey the ramp is indexed on, which is what a tinted frame would have used.
|
|
849
|
+
const grey = texture_frame_series_animator_1.TextureFrameSeriesAnimator.IsPackedValue(this.metadata)
|
|
850
|
+
? Math.round((data[at] * 256 + data[at + 1]) / 65535 * 255)
|
|
851
|
+
: data[at];
|
|
852
|
+
const steps = this.paintedRamp.length / 4;
|
|
853
|
+
const index = Math.min(steps - 1, Math.round(grey / 255 * (steps - 1)));
|
|
854
|
+
return this.paintedRamp[index * 4 + 3] > 0;
|
|
667
855
|
}
|
|
668
|
-
|
|
669
|
-
return this.painted[at] > 0;
|
|
856
|
+
return true;
|
|
670
857
|
}
|
|
671
858
|
// Decodes one cell, skipping anything the texture marks as having no data.
|
|
672
859
|
readAt(col, row, cols) {
|
|
@@ -682,6 +869,16 @@ var TextureValueLabels;
|
|
|
682
869
|
sourceMap: this.sourceMap
|
|
683
870
|
});
|
|
684
871
|
}
|
|
872
|
+
/*
|
|
873
|
+
* The texel holding a lon/lat, or null outside the raster.
|
|
874
|
+
*/
|
|
875
|
+
texelAt(lon, lat) {
|
|
876
|
+
const cols = this.source.width;
|
|
877
|
+
const rows = this.source.height;
|
|
878
|
+
const col = Math.floor((lon - this.extent.West) / (this.extent.East - this.extent.West) * cols);
|
|
879
|
+
const row = Math.floor((this.extent.North - lat) / (this.extent.North - this.extent.South) * rows);
|
|
880
|
+
return col < 0 || row < 0 || col >= cols || row >= rows ? null : { col, row };
|
|
881
|
+
}
|
|
685
882
|
lonAt(col, cols) {
|
|
686
883
|
return this.extent.West + ((col + 0.5) / cols) * (this.extent.East - this.extent.West);
|
|
687
884
|
}
|
|
@@ -710,15 +907,102 @@ var TextureValueLabels;
|
|
|
710
907
|
row1: Math.min(rows - 1, Math.ceil((this.extent.North - south) / spanLat * rows) + 1)
|
|
711
908
|
};
|
|
712
909
|
}
|
|
910
|
+
// Metres the sheet sits at over a point, or the ellipsoid when nothing says otherwise.
|
|
911
|
+
surfaceHeight(lon, lat) {
|
|
912
|
+
const height = this.heightAt ? this.heightAt(lon, lat) : null;
|
|
913
|
+
return height == null || !isFinite(height) ? 0 : height;
|
|
914
|
+
}
|
|
915
|
+
projectCell(col, row, cols, rows) {
|
|
916
|
+
return this.project(this.lonAt(col, cols), this.latAt(row, rows));
|
|
917
|
+
}
|
|
918
|
+
// Projected at the sheet's own height, since up close the ellipsoid is metres from where it is drawn.
|
|
713
919
|
project(lon, lat) {
|
|
714
|
-
|
|
920
|
+
return this.projectInto(lon, lat) ? { x: this.scratchWindow.x, y: this.scratchWindow.y } : null;
|
|
921
|
+
}
|
|
922
|
+
/*
|
|
923
|
+
* Projects into the shared scratch window position, returning whether it landed anywhere.
|
|
924
|
+
*/
|
|
925
|
+
projectInto(lon, lat) {
|
|
926
|
+
const world = Cesium.Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat), Cesium.Ellipsoid.WGS84, this.scratchWorld);
|
|
927
|
+
// One matrix multiply in 3D, where the general path rederives the viewport and frustum per point.
|
|
928
|
+
if (this.viewProjection) {
|
|
929
|
+
const clip = Cesium.Matrix4.multiplyByVector(this.viewProjection, Cesium.Cartesian4.fromElements(world.x, world.y, world.z, 1, this.scratchClip), this.scratchClip);
|
|
930
|
+
if (!(clip.w > 0)) {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
const view = this.viewer.canvas;
|
|
934
|
+
this.scratchWindow.x = (clip.x / clip.w * 0.5 + 0.5) * view.clientWidth;
|
|
935
|
+
this.scratchWindow.y = (0.5 - clip.y / clip.w * 0.5) * view.clientHeight;
|
|
936
|
+
return this.scratchWindow;
|
|
937
|
+
}
|
|
715
938
|
const transforms = Cesium.SceneTransforms;
|
|
716
939
|
// Renamed in newer Cesium, and this library runs on whatever the host app supplies.
|
|
717
940
|
const fn = transforms.worldToWindowCoordinates || transforms.wgs84ToWindowCoordinates;
|
|
718
|
-
return fn ? fn.call(transforms, this.viewer.scene, world) : null;
|
|
941
|
+
return fn ? fn.call(transforms, this.viewer.scene, world, this.scratchWindow) || null : null;
|
|
719
942
|
}
|
|
720
943
|
}
|
|
721
944
|
TextureValueLabels.Labels = Labels;
|
|
945
|
+
/*
|
|
946
|
+
* A grid of window positions projected at most once each, and the cell edges traced through them.
|
|
947
|
+
*/
|
|
948
|
+
class Lattice {
|
|
949
|
+
constructor(wide, high, projectAt) {
|
|
950
|
+
this.wide = wide;
|
|
951
|
+
this.projectAt = projectAt;
|
|
952
|
+
this.xs = new Float64Array(wide * high);
|
|
953
|
+
this.ys = new Float64Array(wide * high);
|
|
954
|
+
this.state = new Uint8Array(wide * high);
|
|
955
|
+
}
|
|
956
|
+
at(c, r) {
|
|
957
|
+
const i = r * this.wide + c;
|
|
958
|
+
if (this.state[i] === 0) {
|
|
959
|
+
const p = this.projectAt(c, r);
|
|
960
|
+
if (p) {
|
|
961
|
+
this.xs[i] = p.x;
|
|
962
|
+
this.ys[i] = p.y;
|
|
963
|
+
this.state[i] = 1;
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
this.state[i] = 2;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return this.state[i] === 1 ? i : -1;
|
|
970
|
+
}
|
|
971
|
+
edge(path, a, b) {
|
|
972
|
+
if (a < 0 || b < 0) {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
path.moveTo(this.xs[a], this.ys[a]);
|
|
976
|
+
path.lineTo(this.xs[b], this.ys[b]);
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
/*
|
|
980
|
+
* Adds each covered cell's edges to a path, a shared edge once, and says whether any were added.
|
|
981
|
+
*/
|
|
982
|
+
Trace(path, covered, cellsWide, cellsHigh) {
|
|
983
|
+
let drew = false;
|
|
984
|
+
for (let r = 0; r < cellsHigh; r++) {
|
|
985
|
+
for (let c = 0; c < cellsWide; c++) {
|
|
986
|
+
if (!covered[r * cellsWide + c]) {
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const tl = this.at(c, r);
|
|
990
|
+
const tr = this.at(c + 1, r);
|
|
991
|
+
const bl = this.at(c, r + 1);
|
|
992
|
+
if (!(r > 0 && covered[(r - 1) * cellsWide + c])) {
|
|
993
|
+
drew = this.edge(path, tl, tr) || drew;
|
|
994
|
+
}
|
|
995
|
+
if (!(c > 0 && covered[r * cellsWide + c - 1])) {
|
|
996
|
+
drew = this.edge(path, tl, bl) || drew;
|
|
997
|
+
}
|
|
998
|
+
const br = this.at(c + 1, r + 1);
|
|
999
|
+
drew = this.edge(path, tr, br) || drew;
|
|
1000
|
+
drew = this.edge(path, bl, br) || drew;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return drew;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
722
1006
|
/*
|
|
723
1007
|
* The value line. Units go on it alone: repeating them on the range underneath doubles the widest
|
|
724
1008
|
* label to say nothing, and the two lines are plainly the same quantity.
|