bruce-cesium 7.2.1 → 7.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bruce-cesium.es5.js +1784 -130
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +1782 -133
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +3 -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 +87 -46
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +214 -26
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +648 -51
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +729 -0
- package/dist/lib/rendering/texture-value-labels.js.map +1 -0
- 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 +3 -1
- package/dist/types/internal/image-utils.d.ts +25 -0
- package/dist/types/rendering/displaced-surface-primitive.d.ts +7 -12
- package/dist/types/rendering/texture-frame-series-animator.d.ts +280 -14
- package/dist/types/rendering/texture-value-labels.d.ts +161 -0
- package/dist/types/rendering/visuals-register.d.ts +1 -0
- package/package.json +2 -2
package/dist/bruce-cesium.es5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Cesium from 'cesium';
|
|
2
|
-
import { Cartographic,
|
|
3
|
-
import { Cartes, Entity as Entity$1, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry as Geometry$1,
|
|
2
|
+
import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, ShaderSource, Ellipsoid, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Cartesian2, HeightReference, ColorBlendMode, ShadowMode, ClassificationType, Model, DistanceDisplayCondition, ArcType, CornerType, ConstantPositionProperty, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, HorizontalOrigin, VerticalOrigin, SceneTransforms, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, Cesium3DTileset, Intersect, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, OrthographicFrustum, EasingFunction, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, CustomDataSource, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, GeometryInstance, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
|
|
3
|
+
import { Cartes, Entity as Entity$1, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry as Geometry$1, Bounds, Color as Color$1, Api, BruceEvent, EntityCoords, MenuItem, Tileset, ProjectView, ProjectViewBookmark, ZoomControl, DelayQueue, DataLab, AccountConcept, RecordChangeFeed, BruceApi, EntityRelation, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, EntityRelationType, EntityHistoricData } from 'bruce-models';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Ensures a number is returned from a given value.
|
|
@@ -9054,7 +9054,8 @@ var VisualsRegister;
|
|
|
9054
9054
|
entityId: rego.entityId,
|
|
9055
9055
|
menuItemId: rego.menuItemId,
|
|
9056
9056
|
doUpdate: false,
|
|
9057
|
-
requestRender: false
|
|
9057
|
+
requestRender: false,
|
|
9058
|
+
keepVisual: rego.visual
|
|
9058
9059
|
});
|
|
9059
9060
|
}
|
|
9060
9061
|
const entityId = rego.entityId;
|
|
@@ -9347,7 +9348,9 @@ var VisualsRegister;
|
|
|
9347
9348
|
EntityLabel.Detatch({
|
|
9348
9349
|
rego
|
|
9349
9350
|
});
|
|
9350
|
-
|
|
9351
|
+
// Never tear down the graphic the caller is registering: the renderers hand back
|
|
9352
|
+
// the same object when they reuse it, so removing it here destroys what replaces it.
|
|
9353
|
+
if (doRemove != false && rego.visual !== params.keepVisual) {
|
|
9351
9354
|
removeEntity(this.viewer, rego.visual);
|
|
9352
9355
|
}
|
|
9353
9356
|
rego.visual = null;
|
|
@@ -9401,7 +9404,9 @@ var VisualsRegister;
|
|
|
9401
9404
|
EntityLabel.Detatch({
|
|
9402
9405
|
rego
|
|
9403
9406
|
});
|
|
9404
|
-
|
|
9407
|
+
// Never tear down the graphic the caller is registering: the renderers hand back
|
|
9408
|
+
// the same object when they reuse it, so removing it here destroys what replaces it.
|
|
9409
|
+
if (doRemove != false && rego.visual !== params.keepVisual) {
|
|
9405
9410
|
removeEntity(this.viewer, rego.visual);
|
|
9406
9411
|
}
|
|
9407
9412
|
rego.visual = null;
|
|
@@ -40283,6 +40288,107 @@ function ApplyGrayscaleColorMask(imageData, minColor, maxColor) {
|
|
|
40283
40288
|
data[i + 3] = Math.round(srcAlpha * maskAlpha * 255);
|
|
40284
40289
|
}
|
|
40285
40290
|
}
|
|
40291
|
+
/**
|
|
40292
|
+
* The colour a ramp gives a normalised value.
|
|
40293
|
+
* @param sorted stops in ascending position, normalised into the texture's value range
|
|
40294
|
+
*/
|
|
40295
|
+
function SampleRamp(sorted, t) {
|
|
40296
|
+
let upper = 0;
|
|
40297
|
+
while (upper < sorted.length && sorted[upper].position < t) {
|
|
40298
|
+
upper++;
|
|
40299
|
+
}
|
|
40300
|
+
if (upper === 0) {
|
|
40301
|
+
return sorted[0].color;
|
|
40302
|
+
}
|
|
40303
|
+
if (upper >= sorted.length) {
|
|
40304
|
+
return sorted[sorted.length - 1].color;
|
|
40305
|
+
}
|
|
40306
|
+
const a = sorted[upper - 1];
|
|
40307
|
+
const b = sorted[upper];
|
|
40308
|
+
const span = b.position - a.position;
|
|
40309
|
+
// Equal positions are a hard step rather than a division by zero, which is how a band
|
|
40310
|
+
// gets a crisp edge instead of a gradient into its neighbour.
|
|
40311
|
+
const f = span > 0 ? (t - a.position) / span : 1;
|
|
40312
|
+
return {
|
|
40313
|
+
red: a.color.red + (b.color.red - a.color.red) * f,
|
|
40314
|
+
green: a.color.green + (b.color.green - a.color.green) * f,
|
|
40315
|
+
blue: a.color.blue + (b.color.blue - a.color.blue) * f,
|
|
40316
|
+
alpha: a.color.alpha + (b.color.alpha - a.color.alpha) * f
|
|
40317
|
+
};
|
|
40318
|
+
}
|
|
40319
|
+
/**
|
|
40320
|
+
* A ramp baked into RGBA bytes, for a shader that cannot walk stops per fragment.
|
|
40321
|
+
* @param steps entries in the lookup, each covering an equal slice of the value range
|
|
40322
|
+
*/
|
|
40323
|
+
function RampLookup(stops, low, high, steps = 256) {
|
|
40324
|
+
const sorted = stops && stops.length > 0
|
|
40325
|
+
? stops.slice().sort((a, b) => a.position - b.position)
|
|
40326
|
+
: null;
|
|
40327
|
+
const out = new Uint8Array(steps * 4);
|
|
40328
|
+
for (let i = 0; i < steps; i++) {
|
|
40329
|
+
const t = steps > 1 ? i / (steps - 1) : 0;
|
|
40330
|
+
const color = sorted
|
|
40331
|
+
? SampleRamp(sorted, t)
|
|
40332
|
+
: {
|
|
40333
|
+
red: low.red + (high.red - low.red) * t,
|
|
40334
|
+
green: low.green + (high.green - low.green) * t,
|
|
40335
|
+
blue: low.blue + (high.blue - low.blue) * t,
|
|
40336
|
+
alpha: low.alpha + (high.alpha - low.alpha) * t
|
|
40337
|
+
};
|
|
40338
|
+
out[i * 4] = Math.round(color.red);
|
|
40339
|
+
out[i * 4 + 1] = Math.round(color.green);
|
|
40340
|
+
out[i * 4 + 2] = Math.round(color.blue);
|
|
40341
|
+
out[i * 4 + 3] = Math.round(Math.min(1, Math.max(0, color.alpha)) * 255);
|
|
40342
|
+
}
|
|
40343
|
+
return out;
|
|
40344
|
+
}
|
|
40345
|
+
function ApplyGradientStops(imageData, stops) {
|
|
40346
|
+
if (!stops || stops.length === 0) {
|
|
40347
|
+
return;
|
|
40348
|
+
}
|
|
40349
|
+
const sorted = stops.slice().sort((a, b) => a.position - b.position);
|
|
40350
|
+
const data = imageData.data;
|
|
40351
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
40352
|
+
const t = data[i] / 255;
|
|
40353
|
+
const color = SampleRamp(sorted, t);
|
|
40354
|
+
data[i] = Math.round(color.red);
|
|
40355
|
+
data[i + 1] = Math.round(color.green);
|
|
40356
|
+
data[i + 2] = Math.round(color.blue);
|
|
40357
|
+
const srcAlpha = data[i + 3] / 255;
|
|
40358
|
+
data[i + 3] = Math.round(srcAlpha * color.alpha * 255);
|
|
40359
|
+
}
|
|
40360
|
+
}
|
|
40361
|
+
/**
|
|
40362
|
+
* Draws the texture's own cell grid into the tinted pixels.
|
|
40363
|
+
*
|
|
40364
|
+
* Drawn in texture space rather than screen space so it stays with the cells it describes and needs
|
|
40365
|
+
* no per-frame work as the camera moves.
|
|
40366
|
+
*/
|
|
40367
|
+
function ApplyCellBorders(imageData, cellTexels, widthTexels, color) {
|
|
40368
|
+
if (!(cellTexels > 0) || !(widthTexels > 0) || !color || color.alpha <= 0) {
|
|
40369
|
+
return;
|
|
40370
|
+
}
|
|
40371
|
+
const { width, height, data } = imageData;
|
|
40372
|
+
const edge = Math.max(1, Math.round(widthTexels));
|
|
40373
|
+
for (let y = 0; y < height; y++) {
|
|
40374
|
+
const onRow = (y % cellTexels) < edge;
|
|
40375
|
+
for (let x = 0; x < width; x++) {
|
|
40376
|
+
if (!onRow && (x % cellTexels) >= edge) {
|
|
40377
|
+
continue;
|
|
40378
|
+
}
|
|
40379
|
+
const i = (y * width + x) * 4;
|
|
40380
|
+
// Only where something is already drawn, so a border cannot invent coverage the data
|
|
40381
|
+
// does not have.
|
|
40382
|
+
if (data[i + 3] === 0) {
|
|
40383
|
+
continue;
|
|
40384
|
+
}
|
|
40385
|
+
const a = color.alpha;
|
|
40386
|
+
data[i] = Math.round(data[i] * (1 - a) + color.red * a);
|
|
40387
|
+
data[i + 1] = Math.round(data[i + 1] * (1 - a) + color.green * a);
|
|
40388
|
+
data[i + 2] = Math.round(data[i + 2] * (1 - a) + color.blue * a);
|
|
40389
|
+
}
|
|
40390
|
+
}
|
|
40391
|
+
}
|
|
40286
40392
|
function loadImage(src) {
|
|
40287
40393
|
return new Promise((res, rej) => {
|
|
40288
40394
|
const image = new Image();
|
|
@@ -40294,26 +40400,317 @@ function loadImage(src) {
|
|
|
40294
40400
|
|
|
40295
40401
|
var TextureFrameSeriesAnimator;
|
|
40296
40402
|
(function (TextureFrameSeriesAnimator) {
|
|
40403
|
+
TextureFrameSeriesAnimator.LAYOUT_TILES = "tiles";
|
|
40404
|
+
// The only generation this renderer draws. Anything else is refused rather than half read: the
|
|
40405
|
+
// shapes differ enough that a partial reading produces a plausible wrong surface, which is worse
|
|
40406
|
+
// than an empty one.
|
|
40407
|
+
TextureFrameSeriesAnimator.SUPPORTED_VERSION = 3;
|
|
40408
|
+
// Value in R at 8 bits, or high in R and low in G at 16.
|
|
40409
|
+
TextureFrameSeriesAnimator.FORMAT_U8 = "u8";
|
|
40410
|
+
TextureFrameSeriesAnimator.FORMAT_RG16 = "rg16";
|
|
40411
|
+
// Declared as a field with one value, so a second mode can be added later without an absent
|
|
40412
|
+
// Encoding having to mean anything.
|
|
40413
|
+
TextureFrameSeriesAnimator.ENCODING_ABSOLUTE = "absolute";
|
|
40414
|
+
TextureFrameSeriesAnimator.LAYER_VALUE = "Value";
|
|
40415
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_MIN = "ValueMin";
|
|
40416
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_MAX = "ValueMax";
|
|
40417
|
+
TextureFrameSeriesAnimator.LAYER_BASELINE = "Baseline";
|
|
40418
|
+
// The per-texel correction that was applied, published only on an archive that was transformed.
|
|
40419
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_SHIFT = "ValueShift";
|
|
40420
|
+
/**
|
|
40421
|
+
* Turns a published value map into the flat shape this renderer works in, or null when it cannot be drawn.
|
|
40422
|
+
* @param published the archive's Data.generation
|
|
40423
|
+
*/
|
|
40424
|
+
function Adapt(published) {
|
|
40425
|
+
if (!published || published.Version !== TextureFrameSeriesAnimator.SUPPORTED_VERSION) {
|
|
40426
|
+
const found = published && published.Version != null ? published.Version : "none";
|
|
40427
|
+
const at = published && published.Identity ? published.Identity.Generated : undefined;
|
|
40428
|
+
// In theory should be no files.
|
|
40429
|
+
console.warn("bruce-cesium: value map version " + found + " is not supported (this build reads "
|
|
40430
|
+
+ TextureFrameSeriesAnimator.SUPPORTED_VERSION + "), so nothing will be drawn for it."
|
|
40431
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
40432
|
+
return null;
|
|
40433
|
+
}
|
|
40434
|
+
const geometry = published.Geometry || {};
|
|
40435
|
+
const tiles = published.Tiles || [];
|
|
40436
|
+
if (geometry.Layout !== TextureFrameSeriesAnimator.LAYOUT_TILES || tiles.length === 0) {
|
|
40437
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
40438
|
+
console.warn("bruce-cesium: value map declares no tiles, so nothing will be drawn for it."
|
|
40439
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
40440
|
+
return null;
|
|
40441
|
+
}
|
|
40442
|
+
const value = published.Value || {};
|
|
40443
|
+
if (value.Encoding && value.Encoding !== TextureFrameSeriesAnimator.ENCODING_ABSOLUTE) {
|
|
40444
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
40445
|
+
console.warn("bruce-cesium: value map encoding " + value.Encoding + " is not supported"
|
|
40446
|
+
+ " (this build reads " + TextureFrameSeriesAnimator.ENCODING_ABSOLUTE + "), so nothing will be drawn for it."
|
|
40447
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
40448
|
+
return null;
|
|
40449
|
+
}
|
|
40450
|
+
const series = published.Series || {};
|
|
40451
|
+
const layerRange = declaredRange(published, TextureFrameSeriesAnimator.LAYER_VALUE_MIN);
|
|
40452
|
+
const flat = {
|
|
40453
|
+
Frames: [],
|
|
40454
|
+
ValueMin: value.Min,
|
|
40455
|
+
ValueMax: value.Max,
|
|
40456
|
+
Format: value.Format || TextureFrameSeriesAnimator.FORMAT_U8,
|
|
40457
|
+
Encoding: value.Encoding || TextureFrameSeriesAnimator.ENCODING_ABSOLUTE,
|
|
40458
|
+
Units: value.Units,
|
|
40459
|
+
West: geometry.West,
|
|
40460
|
+
East: geometry.East,
|
|
40461
|
+
South: geometry.South,
|
|
40462
|
+
North: geometry.North,
|
|
40463
|
+
ResolutionX: geometry.ResolutionX,
|
|
40464
|
+
ResolutionY: geometry.ResolutionY,
|
|
40465
|
+
PixelPlacement: geometry.PixelPlacement,
|
|
40466
|
+
Times: series.Times,
|
|
40467
|
+
ExtremesValueMin: layerRange ? layerRange.min : undefined,
|
|
40468
|
+
ExtremesValueMax: layerRange ? layerRange.max : undefined,
|
|
40469
|
+
// The delivered values are what gets drawn. What the source called them, and any shift
|
|
40470
|
+
// applied on the way, travel for labels and analysis rather than for placement.
|
|
40471
|
+
SourceVerticalDatum: published.ValueSource ? published.ValueSource.VerticalDatum : undefined,
|
|
40472
|
+
Transform: published.ValueSource ? published.ValueSource.Transform : undefined,
|
|
40473
|
+
SourceUnits: published.ValueSource ? published.ValueSource.Units : undefined,
|
|
40474
|
+
SourceValueMin: published.ValueSource ? published.ValueSource.Min : undefined,
|
|
40475
|
+
SourceValueMax: published.ValueSource ? published.ValueSource.Max : undefined
|
|
40476
|
+
};
|
|
40477
|
+
flat.Tiles = tiles.map((tile) => adaptTile(tile, series.Times));
|
|
40478
|
+
// The coarsest tile covers the whole extent, so it is what a caller wanting one time list reads.
|
|
40479
|
+
flat.Frames = flat.Tiles[0].Frames;
|
|
40480
|
+
warnIfUnshifted(published, flat);
|
|
40481
|
+
return flat;
|
|
40482
|
+
}
|
|
40483
|
+
TextureFrameSeriesAnimator.Adapt = Adapt;
|
|
40484
|
+
/*
|
|
40485
|
+
* Says so when an archive's heights are not on the ellipsoid they will be drawn against.
|
|
40486
|
+
*/
|
|
40487
|
+
function warnIfUnshifted(published, flat) {
|
|
40488
|
+
const datum = flat.SourceVerticalDatum;
|
|
40489
|
+
if (!datum || datum === TextureFrameSeriesAnimator.DATUM_ELLIPSOIDAL || flat.Transform) {
|
|
40490
|
+
return;
|
|
40491
|
+
}
|
|
40492
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
40493
|
+
console.warn("bruce-cesium: value map heights are in " + datum + " and report no transform, so"
|
|
40494
|
+
+ " they are not ellipsoidal and will draw at the wrong height when placed absolutely."
|
|
40495
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating with the shift"
|
|
40496
|
+
+ " applied." : ""));
|
|
40497
|
+
}
|
|
40498
|
+
/*
|
|
40499
|
+
* The Min and Max a named layer declares, since a statistics layer is encoded over the true data
|
|
40500
|
+
* range rather than over the frames' own.
|
|
40501
|
+
*/
|
|
40502
|
+
function declaredRange(published, name) {
|
|
40503
|
+
const layers = published.Layers || [];
|
|
40504
|
+
for (const layer of layers) {
|
|
40505
|
+
if (layer.Name === name && typeof layer.Min === "number" && typeof layer.Max === "number") {
|
|
40506
|
+
return { min: layer.Min, max: layer.Max };
|
|
40507
|
+
}
|
|
40508
|
+
}
|
|
40509
|
+
return null;
|
|
40510
|
+
}
|
|
40511
|
+
/*
|
|
40512
|
+
* One tile, with its per-time value rasters lined up against the shared time list.
|
|
40513
|
+
*/
|
|
40514
|
+
function adaptTile(tile, times) {
|
|
40515
|
+
const layers = tile.Layers || {};
|
|
40516
|
+
const perTime = layers[TextureFrameSeriesAnimator.LAYER_VALUE] || [];
|
|
40517
|
+
const frames = perTime.map((entry, index) => ({
|
|
40518
|
+
Timestamp: times && times[index] ? times[index] : "",
|
|
40519
|
+
ByteOffset: entry.Bytes.Offset,
|
|
40520
|
+
ByteLength: entry.Bytes.Length
|
|
40521
|
+
}));
|
|
40522
|
+
return {
|
|
40523
|
+
Level: tile.Level,
|
|
40524
|
+
X: tile.X,
|
|
40525
|
+
Y: tile.Y,
|
|
40526
|
+
West: tile.West,
|
|
40527
|
+
East: tile.East,
|
|
40528
|
+
South: tile.South,
|
|
40529
|
+
North: tile.North,
|
|
40530
|
+
TexelMetres: tile.TexelMetres,
|
|
40531
|
+
ResolutionX: tile.ResolutionX,
|
|
40532
|
+
ResolutionY: tile.ResolutionY,
|
|
40533
|
+
Frames: frames,
|
|
40534
|
+
Floor: single(layers[TextureFrameSeriesAnimator.LAYER_VALUE_MIN]),
|
|
40535
|
+
Ceiling: single(layers[TextureFrameSeriesAnimator.LAYER_VALUE_MAX]),
|
|
40536
|
+
BaselineMask: single(layers[TextureFrameSeriesAnimator.LAYER_BASELINE])
|
|
40537
|
+
};
|
|
40538
|
+
}
|
|
40539
|
+
function single(layer) {
|
|
40540
|
+
if (!layer || !layer.Bytes) {
|
|
40541
|
+
return undefined;
|
|
40542
|
+
}
|
|
40543
|
+
return { ByteOffset: layer.Bytes.Offset, ByteLength: layer.Bytes.Length };
|
|
40544
|
+
}
|
|
40545
|
+
/**
|
|
40546
|
+
* How to turn a delivered value into the number the source published, or null when the archive
|
|
40547
|
+
* does not say enough to do it.
|
|
40548
|
+
* @param metadata the archive's Data.generation
|
|
40549
|
+
*/
|
|
40550
|
+
function SourceMap(metadata) {
|
|
40551
|
+
if (!metadata) {
|
|
40552
|
+
return null;
|
|
40553
|
+
}
|
|
40554
|
+
const { ValueMin, ValueMax, SourceValueMin, SourceValueMax } = metadata;
|
|
40555
|
+
if (typeof ValueMin !== "number" || typeof ValueMax !== "number"
|
|
40556
|
+
|| typeof SourceValueMin !== "number" || typeof SourceValueMax !== "number") {
|
|
40557
|
+
return null;
|
|
40558
|
+
}
|
|
40559
|
+
const delivered = ValueMax - ValueMin;
|
|
40560
|
+
// A single delivered value describes no line, so all that can be recovered is where it sits.
|
|
40561
|
+
const scale = delivered !== 0 ? (SourceValueMax - SourceValueMin) / delivered : 1;
|
|
40562
|
+
return { scale, offset: SourceValueMin - ValueMin * scale };
|
|
40563
|
+
}
|
|
40564
|
+
TextureFrameSeriesAnimator.SourceMap = SourceMap;
|
|
40565
|
+
/**
|
|
40566
|
+
* A delivered value in the source's own terms, or unchanged when it cannot be turned back.
|
|
40567
|
+
* @param map from SourceMap(), null when the archive does not say enough
|
|
40568
|
+
*/
|
|
40569
|
+
function ToSource(map, value) {
|
|
40570
|
+
return map ? value * map.scale + map.offset : value;
|
|
40571
|
+
}
|
|
40572
|
+
TextureFrameSeriesAnimator.ToSource = ToSource;
|
|
40573
|
+
/**
|
|
40574
|
+
* Whether the archive's frames carry a 16 bit value packed across R and G.
|
|
40575
|
+
*
|
|
40576
|
+
* Every consumer that reads a texel has to ask: taking R alone from a packed frame yields a
|
|
40577
|
+
* plausible surface quantised to 255 steps of the full range rather than an obvious failure.
|
|
40578
|
+
* @param metadata the archive's Data.generation
|
|
40579
|
+
*/
|
|
40580
|
+
function IsPackedValue(metadata) {
|
|
40581
|
+
return Boolean(metadata && metadata.Format === TextureFrameSeriesAnimator.FORMAT_RG16);
|
|
40582
|
+
}
|
|
40583
|
+
TextureFrameSeriesAnimator.IsPackedValue = IsPackedValue;
|
|
40584
|
+
/**
|
|
40585
|
+
* The normalised 0 to 1 value at a texel, from whichever format the archive packed it in.
|
|
40586
|
+
* @param metadata the archive's Data.generation
|
|
40587
|
+
* @param pixels untinted RGBA value pixels
|
|
40588
|
+
* @param at index of the texel's red channel
|
|
40589
|
+
*/
|
|
40590
|
+
function NormalisedAt(metadata, pixels, at) {
|
|
40591
|
+
if (IsPackedValue(metadata)) {
|
|
40592
|
+
return (pixels[at] * 256 + pixels[at + 1]) / 65535;
|
|
40593
|
+
}
|
|
40594
|
+
return pixels[at] / 255;
|
|
40595
|
+
}
|
|
40596
|
+
TextureFrameSeriesAnimator.NormalisedAt = NormalisedAt;
|
|
40597
|
+
// Values already sit on the ellipsoid Cesium measures against, so nothing is added to place them.
|
|
40598
|
+
TextureFrameSeriesAnimator.DATUM_ELLIPSOIDAL = "WGS84";
|
|
40599
|
+
/**
|
|
40600
|
+
* Whether the values are heights against a vertical datum rather than a thickness.
|
|
40601
|
+
*
|
|
40602
|
+
* A thickness rises from wherever the polygon sits and a height already knows where it belongs, so
|
|
40603
|
+
* this is what decides whether the polygon's own altitude may be added underneath it.
|
|
40604
|
+
* @param metadata the archive's Data.generation
|
|
40605
|
+
*/
|
|
40606
|
+
function IsDatumHeight(metadata) {
|
|
40607
|
+
return Boolean(metadata && metadata.SourceVerticalDatum);
|
|
40608
|
+
}
|
|
40609
|
+
TextureFrameSeriesAnimator.IsDatumHeight = IsDatumHeight;
|
|
40610
|
+
/**
|
|
40611
|
+
* The archive's tiles, which Adapt() guarantees are present.
|
|
40612
|
+
* @param metadata the archive's Data.generation
|
|
40613
|
+
*/
|
|
40614
|
+
function TilesOf(metadata) {
|
|
40615
|
+
return (metadata && metadata.Tiles) || [];
|
|
40616
|
+
}
|
|
40617
|
+
TextureFrameSeriesAnimator.TilesOf = TilesOf;
|
|
40618
|
+
/**
|
|
40619
|
+
* Turns a value in the attribute's own units into the 0 to 1 position the ramp is indexed by.
|
|
40620
|
+
*
|
|
40621
|
+
* Stops are authored in real units, since "hide anything under 0.1 m" is the sentence a user
|
|
40622
|
+
* actually has, and the archive's own published range is what makes that expressible.
|
|
40623
|
+
* @param metadata the archive's Data.generation
|
|
40624
|
+
* @param value in the attribute's units
|
|
40625
|
+
*/
|
|
40626
|
+
function NormalisePosition(metadata, value) {
|
|
40627
|
+
const lo = metadata && typeof metadata.ValueMin === "number" ? metadata.ValueMin : 0;
|
|
40628
|
+
const hi = metadata && typeof metadata.ValueMax === "number" ? metadata.ValueMax : 1;
|
|
40629
|
+
const span = hi - lo;
|
|
40630
|
+
if (!(span > 0)) {
|
|
40631
|
+
return 0;
|
|
40632
|
+
}
|
|
40633
|
+
return Math.min(1, Math.max(0, (value - lo) / span));
|
|
40634
|
+
}
|
|
40635
|
+
TextureFrameSeriesAnimator.NormalisePosition = NormalisePosition;
|
|
40297
40636
|
/**
|
|
40298
40637
|
* Detects whether a ClientFile's `Data.generation` metadata describes a frame archive rather than a single static image,
|
|
40299
40638
|
* so a caller can decide whether to construct an Animator or fall back to the existing static-texture path.
|
|
40300
|
-
* @param data ClientFile.Data
|
|
40639
|
+
* @param data ClientFile.Data
|
|
40301
40640
|
*/
|
|
40302
40641
|
function IsFrameArchiveMetadata(data) {
|
|
40303
|
-
|
|
40642
|
+
const generation = data && data.generation;
|
|
40643
|
+
if (!generation) {
|
|
40644
|
+
return false;
|
|
40645
|
+
}
|
|
40646
|
+
return (typeof generation.Version === "number" ||
|
|
40647
|
+
(Array.isArray(generation.Frames) && generation.Frames.length > 0));
|
|
40304
40648
|
}
|
|
40305
40649
|
TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
|
|
40306
|
-
//
|
|
40650
|
+
// First resolvable static number out of a calculator field list, since that is all a border needs.
|
|
40651
|
+
function resolveNumber(fields) {
|
|
40652
|
+
if (!fields) {
|
|
40653
|
+
return 0;
|
|
40654
|
+
}
|
|
40655
|
+
for (const field of fields) {
|
|
40656
|
+
const value = Number(field && field.value);
|
|
40657
|
+
if (Number.isFinite(value)) {
|
|
40658
|
+
return value;
|
|
40659
|
+
}
|
|
40660
|
+
}
|
|
40661
|
+
return 0;
|
|
40662
|
+
}
|
|
40663
|
+
// First resolvable static colour out of a calculator field list.
|
|
40664
|
+
function resolveColor(fields) {
|
|
40665
|
+
if (!fields) {
|
|
40666
|
+
return null;
|
|
40667
|
+
}
|
|
40668
|
+
for (const field of fields) {
|
|
40669
|
+
const parsed = typeof (field === null || field === void 0 ? void 0 : field.value) === "string"
|
|
40670
|
+
? Color$1.ColorFromStr(field.value)
|
|
40671
|
+
: null;
|
|
40672
|
+
if (parsed) {
|
|
40673
|
+
return parsed;
|
|
40674
|
+
}
|
|
40675
|
+
}
|
|
40676
|
+
return null;
|
|
40677
|
+
}
|
|
40678
|
+
/*
|
|
40679
|
+
* Everything about an Animator that a style can change, as a comparable string.
|
|
40680
|
+
*/
|
|
40681
|
+
function AppearanceSignature(options) {
|
|
40682
|
+
var _a, _b, _c;
|
|
40683
|
+
return JSON.stringify([
|
|
40684
|
+
(_a = options.textureColorMask) !== null && _a !== void 0 ? _a : null,
|
|
40685
|
+
(_b = options.cellBorder) !== null && _b !== void 0 ? _b : null,
|
|
40686
|
+
(_c = options.cellTexels) !== null && _c !== void 0 ? _c : null,
|
|
40687
|
+
Boolean(options.maskBaseline)
|
|
40688
|
+
]);
|
|
40689
|
+
}
|
|
40690
|
+
TextureFrameSeriesAnimator.AppearanceSignature = AppearanceSignature;
|
|
40691
|
+
const MAX_COMPOSITE_TEXELS = 2048;
|
|
40307
40692
|
const VALUE_DILATE_PASSES = 2;
|
|
40308
40693
|
/*
|
|
40309
|
-
*
|
|
40694
|
+
* Rewrites a packed 16 bit value as the 8 bit grey the colour ramps index on.
|
|
40310
40695
|
*
|
|
40311
|
-
*
|
|
40312
|
-
*
|
|
40313
|
-
|
|
40314
|
-
|
|
40696
|
+
* All three channels, since a ramp is free to read any of them and a leftover low byte in G would
|
|
40697
|
+
* show up as a green cast on the one that reads them all.
|
|
40698
|
+
*/
|
|
40699
|
+
function flattenPacked(pixels, packed) {
|
|
40700
|
+
if (!packed) {
|
|
40701
|
+
return;
|
|
40702
|
+
}
|
|
40703
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
40704
|
+
const grey = Math.round((pixels[i] * 256 + pixels[i + 1]) / 65535 * 255);
|
|
40705
|
+
pixels[i] = grey;
|
|
40706
|
+
pixels[i + 1] = grey;
|
|
40707
|
+
pixels[i + 2] = grey;
|
|
40708
|
+
}
|
|
40709
|
+
}
|
|
40710
|
+
/*
|
|
40711
|
+
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
40315
40712
|
*/
|
|
40316
|
-
function dilateValues(value, width, height, passes) {
|
|
40713
|
+
function dilateValues(value, width, height, passes, packed) {
|
|
40317
40714
|
const texels = width * height;
|
|
40318
40715
|
const filled = new Uint8Array(texels);
|
|
40319
40716
|
for (let p = 0; p < texels; p++) {
|
|
@@ -40337,15 +40734,25 @@ var TextureFrameSeriesAnimator;
|
|
|
40337
40734
|
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !wasFilled[ny * width + nx]) {
|
|
40338
40735
|
continue;
|
|
40339
40736
|
}
|
|
40340
|
-
|
|
40737
|
+
const at = (ny * width + nx) * 4;
|
|
40738
|
+
// Averaged as a value rather than per byte: averaging a packed low byte
|
|
40739
|
+
// on its own wraps at every 256th step and speckles the bled edge.
|
|
40740
|
+
sum += packed ? source[at] * 256 + source[at + 1] : source[at];
|
|
40341
40741
|
hits++;
|
|
40342
40742
|
}
|
|
40343
40743
|
}
|
|
40344
40744
|
if (hits > 0) {
|
|
40345
40745
|
const v = Math.round(sum / hits);
|
|
40346
|
-
|
|
40347
|
-
|
|
40348
|
-
|
|
40746
|
+
if (packed) {
|
|
40747
|
+
value[p * 4] = (v >> 8) & 255;
|
|
40748
|
+
value[p * 4 + 1] = v & 255;
|
|
40749
|
+
value[p * 4 + 2] = 0;
|
|
40750
|
+
}
|
|
40751
|
+
else {
|
|
40752
|
+
value[p * 4] = v;
|
|
40753
|
+
value[p * 4 + 1] = v;
|
|
40754
|
+
value[p * 4 + 2] = v;
|
|
40755
|
+
}
|
|
40349
40756
|
filled[p] = 1;
|
|
40350
40757
|
}
|
|
40351
40758
|
}
|
|
@@ -40357,7 +40764,7 @@ var TextureFrameSeriesAnimator;
|
|
|
40357
40764
|
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
40358
40765
|
class Animator {
|
|
40359
40766
|
constructor(options) {
|
|
40360
|
-
var _a, _b;
|
|
40767
|
+
var _a, _b, _c;
|
|
40361
40768
|
this.removeOnTick = null;
|
|
40362
40769
|
this.removeCrossfadeTick = null;
|
|
40363
40770
|
this.disposed = false;
|
|
@@ -40385,6 +40792,9 @@ var TextureFrameSeriesAnimator;
|
|
|
40385
40792
|
this.poolIdx = 0;
|
|
40386
40793
|
this.valueDims = null;
|
|
40387
40794
|
this.presentVersion = 0;
|
|
40795
|
+
this.extremesLoad = null;
|
|
40796
|
+
this.floorPixels = null;
|
|
40797
|
+
this.ceilingPixels = null;
|
|
40388
40798
|
if (!options.entity.polygon) {
|
|
40389
40799
|
throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
|
|
40390
40800
|
}
|
|
@@ -40396,16 +40806,40 @@ var TextureFrameSeriesAnimator;
|
|
|
40396
40806
|
this.archiveUrl = options.archiveUrl;
|
|
40397
40807
|
this.frames = options.frames;
|
|
40398
40808
|
this.crossfadeMs = (_a = options.crossfadeMs) !== null && _a !== void 0 ? _a : DEFAULT_CROSSFADE_MS;
|
|
40809
|
+
this.appearance = AppearanceSignature(options);
|
|
40399
40810
|
const mask = options.textureColorMask;
|
|
40400
40811
|
this.lowColor = (mask && Color$1.ColorFromStr(mask.minColor)) || DEFAULT_LOW_COLOR;
|
|
40401
40812
|
this.highColor = (mask && Color$1.ColorFromStr(mask.maxColor)) || DEFAULT_HIGH_COLOR;
|
|
40813
|
+
// Positions are authored in the attribute's units and normalised once here, so the
|
|
40814
|
+
// per-pixel loop stays a comparison against 0 to 1 like the two-colour path.
|
|
40815
|
+
const points = mask && mask.points;
|
|
40816
|
+
this.rampStops = (points && points.length > 0)
|
|
40817
|
+
? points.map((p) => ({
|
|
40818
|
+
position: NormalisePosition(options.metadata, p.position),
|
|
40819
|
+
color: Color$1.ColorFromStr(p.color) || DEFAULT_LOW_COLOR
|
|
40820
|
+
}))
|
|
40821
|
+
: null;
|
|
40822
|
+
const border = options.cellBorder;
|
|
40823
|
+
const borderWidth = border ? resolveNumber(border.width) : 0;
|
|
40824
|
+
const borderColor = border ? resolveColor(border.color) : null;
|
|
40825
|
+
// Zero width or a fully transparent colour means no grid, matching how the polygon's own
|
|
40826
|
+
// outline behaves, so an existing style that wants no border keeps getting none.
|
|
40827
|
+
this.cellBorder = (border && borderWidth > 0 && borderColor && borderColor.alpha > 0)
|
|
40828
|
+
? {
|
|
40829
|
+
cellTexels: Math.max(1, Math.round((_b = options.cellTexels) !== null && _b !== void 0 ? _b : 1)),
|
|
40830
|
+
widthTexels: borderWidth,
|
|
40831
|
+
color: borderColor
|
|
40832
|
+
}
|
|
40833
|
+
: null;
|
|
40402
40834
|
this.frameDates = this.frames.map((f) => JulianDate.fromIso8601(f.Timestamp));
|
|
40403
40835
|
this.frameCache = new Array(this.frames.length).fill(null);
|
|
40404
40836
|
this.frameDims = new Array(this.frames.length).fill(null);
|
|
40405
40837
|
this.valueCache = new Array(this.frames.length).fill(null);
|
|
40406
40838
|
this.produceValueCanvas = Boolean(options.produceValueCanvas);
|
|
40407
|
-
this.
|
|
40408
|
-
this.
|
|
40839
|
+
this.metadata = options.metadata;
|
|
40840
|
+
this.drapeExtent = options.drapeExtent;
|
|
40841
|
+
this.tiles = TilesOf(options.metadata);
|
|
40842
|
+
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : this.tiles.some((tile) => Boolean(tile.BaselineMask));
|
|
40409
40843
|
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
40410
40844
|
this.driveMaterial = options.driveMaterial !== false;
|
|
40411
40845
|
this.originalMaterial = options.entity.polygon.material;
|
|
@@ -40441,6 +40875,10 @@ var TextureFrameSeriesAnimator;
|
|
|
40441
40875
|
IsDisposed() {
|
|
40442
40876
|
return this.disposed;
|
|
40443
40877
|
}
|
|
40878
|
+
// What this Animator was built to look like, for deciding whether it can be reused.
|
|
40879
|
+
GetAppearanceSignature() {
|
|
40880
|
+
return this.appearance;
|
|
40881
|
+
}
|
|
40444
40882
|
/**
|
|
40445
40883
|
* The archive URL this instance was constructed with,
|
|
40446
40884
|
* lets a caller re-rendering the same entity tell whether an existing instance is already correct, without reaching into private state.
|
|
@@ -40455,6 +40893,15 @@ var TextureFrameSeriesAnimator;
|
|
|
40455
40893
|
GetImageProperty() {
|
|
40456
40894
|
return this.imageProperty;
|
|
40457
40895
|
}
|
|
40896
|
+
/**
|
|
40897
|
+
* The presented frame's TINTED pixels, where alpha is what the ramp actually painted.
|
|
40898
|
+
*
|
|
40899
|
+
* Distinct from the value canvas, whose alpha only says a texel has data. A cell can hold a
|
|
40900
|
+
* reading and still be painted nothing, which is exactly what a hidden floor band does.
|
|
40901
|
+
*/
|
|
40902
|
+
GetDisplayedPixels() {
|
|
40903
|
+
return this.displayedPixels;
|
|
40904
|
+
}
|
|
40458
40905
|
/**
|
|
40459
40906
|
* The untinted canvas carrying the presented frame's value in RGB and its coverage in alpha,
|
|
40460
40907
|
* or null unless the instance was constructed with produceValueCanvas.
|
|
@@ -40567,10 +41014,20 @@ var TextureFrameSeriesAnimator;
|
|
|
40567
41014
|
});
|
|
40568
41015
|
}
|
|
40569
41016
|
async fetchAndTint(idx) {
|
|
40570
|
-
const
|
|
40571
|
-
|
|
40572
|
-
|
|
40573
|
-
|
|
41017
|
+
const tiles = this.tiles;
|
|
41018
|
+
// Every tile's frame for one timestep sits contiguously, because the generator writes
|
|
41019
|
+
// frame major. So a tiled frame is still ONE range request, not one per tile.
|
|
41020
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
41021
|
+
let hi = 0;
|
|
41022
|
+
for (const tile of tiles) {
|
|
41023
|
+
const at = tile.Frames[idx];
|
|
41024
|
+
if (!at) {
|
|
41025
|
+
continue;
|
|
41026
|
+
}
|
|
41027
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
41028
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
41029
|
+
}
|
|
41030
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
40574
41031
|
const buffer = await response.arrayBuffer();
|
|
40575
41032
|
if (this.disposed) {
|
|
40576
41033
|
return;
|
|
@@ -40581,7 +41038,7 @@ var TextureFrameSeriesAnimator;
|
|
|
40581
41038
|
if (this.disposed) {
|
|
40582
41039
|
return;
|
|
40583
41040
|
}
|
|
40584
|
-
const decoded = await this.
|
|
41041
|
+
const decoded = await this.composeTiles(tiles, idx, buffer, lo);
|
|
40585
41042
|
if (this.disposed) {
|
|
40586
41043
|
return;
|
|
40587
41044
|
}
|
|
@@ -40592,39 +41049,292 @@ var TextureFrameSeriesAnimator;
|
|
|
40592
41049
|
this.beginCrossfadeTo(idx);
|
|
40593
41050
|
}
|
|
40594
41051
|
}
|
|
40595
|
-
|
|
40596
|
-
*
|
|
41052
|
+
/*
|
|
41053
|
+
* Draws every tile of one frame into a single raster covering the drape extent.
|
|
40597
41054
|
*/
|
|
40598
|
-
async
|
|
40599
|
-
const
|
|
40600
|
-
const
|
|
40601
|
-
let image;
|
|
40602
|
-
try {
|
|
40603
|
-
image = await loadImage(objectUrl);
|
|
40604
|
-
}
|
|
40605
|
-
finally {
|
|
40606
|
-
URL.revokeObjectURL(objectUrl);
|
|
40607
|
-
}
|
|
41055
|
+
async composeTiles(tiles, idx, buffer, spanStart) {
|
|
41056
|
+
const target = this.compositeExtent();
|
|
41057
|
+
const size = this.compositeSize(tiles, target);
|
|
40608
41058
|
const canvas = document.createElement("canvas");
|
|
40609
|
-
canvas.width =
|
|
40610
|
-
canvas.height =
|
|
41059
|
+
canvas.width = size.width;
|
|
41060
|
+
canvas.height = size.height;
|
|
40611
41061
|
const ctx = canvas.getContext("2d");
|
|
40612
|
-
ctx.
|
|
40613
|
-
|
|
41062
|
+
ctx.imageSmoothingEnabled = false;
|
|
41063
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
41064
|
+
const images = await this.decodeSlices(buffer, spanStart, tiles.map((tile) => tile.Frames[idx]));
|
|
41065
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
41066
|
+
const image = images[i];
|
|
41067
|
+
if (!image || this.disposed) {
|
|
41068
|
+
continue;
|
|
41069
|
+
}
|
|
41070
|
+
const at = this.tileRect(tiles[i], target, size);
|
|
41071
|
+
ctx.drawImage(image, at.x, at.y, at.w, at.h);
|
|
41072
|
+
}
|
|
41073
|
+
return this.tint(ctx.getImageData(0, 0, size.width, size.height));
|
|
41074
|
+
}
|
|
41075
|
+
/*
|
|
41076
|
+
* Where a composited frame is draped, which is the polygon's rectangle when there is one.
|
|
41077
|
+
*/
|
|
41078
|
+
compositeExtent() {
|
|
41079
|
+
if (this.drapeExtent) {
|
|
41080
|
+
return this.drapeExtent;
|
|
41081
|
+
}
|
|
41082
|
+
const m = this.metadata;
|
|
41083
|
+
return {
|
|
41084
|
+
West: m && m.West != null ? m.West : -180,
|
|
41085
|
+
East: m && m.East != null ? m.East : 180,
|
|
41086
|
+
South: m && m.South != null ? m.South : -90,
|
|
41087
|
+
North: m && m.North != null ? m.North : 90
|
|
41088
|
+
};
|
|
41089
|
+
}
|
|
41090
|
+
/*
|
|
41091
|
+
* Composite resolution, capped by what can be uploaded as a texture every frame.
|
|
41092
|
+
*/
|
|
41093
|
+
compositeSize(tiles, target) {
|
|
41094
|
+
let finest = Number.MAX_VALUE;
|
|
41095
|
+
for (const tile of tiles) {
|
|
41096
|
+
if (tile.TexelMetres > 0) {
|
|
41097
|
+
finest = Math.min(finest, tile.TexelMetres);
|
|
41098
|
+
}
|
|
41099
|
+
}
|
|
41100
|
+
if (!(finest > 0) || finest === Number.MAX_VALUE) {
|
|
41101
|
+
finest = 1;
|
|
41102
|
+
}
|
|
41103
|
+
const mid = (target.South + target.North) / 2 * Math.PI / 180;
|
|
41104
|
+
const widthM = Math.max((target.East - target.West) * 111320 * Math.cos(mid), 1);
|
|
41105
|
+
const heightM = Math.max((target.North - target.South) * 110574, 1);
|
|
41106
|
+
let width = Math.ceil(widthM / finest);
|
|
41107
|
+
let height = Math.ceil(heightM / finest);
|
|
41108
|
+
const longest = Math.max(width, height);
|
|
41109
|
+
if (longest > MAX_COMPOSITE_TEXELS) {
|
|
41110
|
+
const shrink = MAX_COMPOSITE_TEXELS / longest;
|
|
41111
|
+
width = Math.max(1, Math.round(width * shrink));
|
|
41112
|
+
height = Math.max(1, Math.round(height * shrink));
|
|
41113
|
+
}
|
|
41114
|
+
return { width: Math.max(1, width), height: Math.max(1, height) };
|
|
41115
|
+
}
|
|
41116
|
+
/*
|
|
41117
|
+
* Applies the ramp, borders and baseline mask to raw value pixels, whatever produced them.
|
|
41118
|
+
*/
|
|
41119
|
+
tint(imageData) {
|
|
41120
|
+
const canvas = { width: imageData.width, height: imageData.height };
|
|
40614
41121
|
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
40615
41122
|
// recovered from the tinted pixels afterwards.
|
|
40616
41123
|
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
40617
|
-
|
|
41124
|
+
// The ramp indexes on R as an 8 bit grey, so a packed frame is flattened to one here
|
|
41125
|
+
// rather than teaching every ramp function a second format.
|
|
41126
|
+
// The copy above keeps the packed pair, which is what displacement and labels read.
|
|
41127
|
+
flattenPacked(imageData.data, IsPackedValue(this.metadata));
|
|
41128
|
+
// Stops win when supplied: they can express a band and a hidden floor, which a two
|
|
41129
|
+
// colour ramp cannot. The pair stays the fallback so an older style still draws.
|
|
41130
|
+
if (this.rampStops && this.rampStops.length > 0) {
|
|
41131
|
+
ApplyGradientStops(imageData, this.rampStops);
|
|
41132
|
+
}
|
|
41133
|
+
else {
|
|
41134
|
+
ApplyGrayscaleColorMask(imageData, this.lowColor, this.highColor);
|
|
41135
|
+
}
|
|
41136
|
+
if (this.cellBorder) {
|
|
41137
|
+
ApplyCellBorders(imageData, this.cellBorder.cellTexels, this.cellBorder.widthTexels, this.cellBorder.color);
|
|
41138
|
+
}
|
|
40618
41139
|
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
40619
41140
|
if (valuePixels) {
|
|
40620
|
-
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES);
|
|
41141
|
+
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
40621
41142
|
}
|
|
40622
41143
|
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
40623
41144
|
}
|
|
41145
|
+
/*
|
|
41146
|
+
* Paints one frame's composited pixels onto a canvas.
|
|
41147
|
+
*/
|
|
41148
|
+
writeInto(canvas, pixels, dims) {
|
|
41149
|
+
canvas.width = dims.width;
|
|
41150
|
+
canvas.height = dims.height;
|
|
41151
|
+
const ctx = canvas.getContext("2d");
|
|
41152
|
+
const image = ctx.createImageData(dims.width, dims.height);
|
|
41153
|
+
image.data.set(pixels);
|
|
41154
|
+
ctx.putImageData(image, 0, 0);
|
|
41155
|
+
}
|
|
41156
|
+
/**
|
|
41157
|
+
* The archive's per-cell floor and ceiling rasters, once EnsureExtremes has resolved.
|
|
41158
|
+
*/
|
|
41159
|
+
GetExtremes() {
|
|
41160
|
+
return { floor: this.floorPixels, ceiling: this.ceilingPixels };
|
|
41161
|
+
}
|
|
41162
|
+
/**
|
|
41163
|
+
* Fetches and decodes the floor and ceiling rasters, at most once.
|
|
41164
|
+
*/
|
|
41165
|
+
EnsureExtremes() {
|
|
41166
|
+
if (!this.extremesLoad) {
|
|
41167
|
+
this.extremesLoad = this.loadTiledExtremes();
|
|
41168
|
+
}
|
|
41169
|
+
return this.extremesLoad;
|
|
41170
|
+
}
|
|
41171
|
+
/*
|
|
41172
|
+
* Composites each tile's baseline mask into one covering the composited frames.
|
|
41173
|
+
*/
|
|
41174
|
+
async loadTiledBaselineMask() {
|
|
41175
|
+
const tiles = this.tiles;
|
|
41176
|
+
const entries = tiles.map((tile) => tile.BaselineMask);
|
|
41177
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
41178
|
+
let hi = 0;
|
|
41179
|
+
for (const at of entries) {
|
|
41180
|
+
if (!at) {
|
|
41181
|
+
continue;
|
|
41182
|
+
}
|
|
41183
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
41184
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
41185
|
+
}
|
|
41186
|
+
if (!(hi > lo)) {
|
|
41187
|
+
return;
|
|
41188
|
+
}
|
|
41189
|
+
try {
|
|
41190
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
41191
|
+
const block = await response.arrayBuffer();
|
|
41192
|
+
if (this.disposed) {
|
|
41193
|
+
return;
|
|
41194
|
+
}
|
|
41195
|
+
const images = await this.decodeSlices(block, lo, entries);
|
|
41196
|
+
if (this.disposed) {
|
|
41197
|
+
return;
|
|
41198
|
+
}
|
|
41199
|
+
const target = this.compositeExtent();
|
|
41200
|
+
const size = this.compositeSize(tiles, target);
|
|
41201
|
+
const canvas = document.createElement("canvas");
|
|
41202
|
+
canvas.width = size.width;
|
|
41203
|
+
canvas.height = size.height;
|
|
41204
|
+
const ctx = canvas.getContext("2d");
|
|
41205
|
+
ctx.imageSmoothingEnabled = false;
|
|
41206
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
41207
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
41208
|
+
if (!images[i]) {
|
|
41209
|
+
continue;
|
|
41210
|
+
}
|
|
41211
|
+
const at = this.tileRect(tiles[i], target, size);
|
|
41212
|
+
ctx.drawImage(images[i], at.x, at.y, at.w, at.h);
|
|
41213
|
+
}
|
|
41214
|
+
const pixels = ctx.getImageData(0, 0, size.width, size.height).data;
|
|
41215
|
+
const flags = new Uint8Array(size.width * size.height);
|
|
41216
|
+
for (let p = 0; p < flags.length; p++) {
|
|
41217
|
+
flags[p] = pixels[p * 4 + 3] >= 128 ? 1 : 0;
|
|
41218
|
+
}
|
|
41219
|
+
this.baselineFlags = flags;
|
|
41220
|
+
this.baselineDims = { width: size.width, height: size.height };
|
|
41221
|
+
}
|
|
41222
|
+
catch (e) {
|
|
41223
|
+
console.warn("TextureFrameSeriesAnimator: could not load the tiled baseline mask.", e);
|
|
41224
|
+
}
|
|
41225
|
+
}
|
|
41226
|
+
/*
|
|
41227
|
+
* Composites each tile's floor and ceiling into rasters matching the composited frames.
|
|
41228
|
+
*
|
|
41229
|
+
* A label indexes them by the same texel as the value canvas, so they have to be laid out the
|
|
41230
|
+
* same way rather than fetched as whole-extent rasters.
|
|
41231
|
+
*/
|
|
41232
|
+
async loadTiledExtremes() {
|
|
41233
|
+
const tiles = this.tiles;
|
|
41234
|
+
const target = this.compositeExtent();
|
|
41235
|
+
const size = this.compositeSize(tiles, target);
|
|
41236
|
+
// One request for the whole extremes block. They sit contiguously after the frames, so
|
|
41237
|
+
// fetching per tile would be 164 round trips for an 82 tile pyramid.
|
|
41238
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
41239
|
+
let hi = 0;
|
|
41240
|
+
for (const tile of tiles) {
|
|
41241
|
+
for (const at of [tile.Floor, tile.Ceiling]) {
|
|
41242
|
+
if (!at) {
|
|
41243
|
+
continue;
|
|
41244
|
+
}
|
|
41245
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
41246
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
41247
|
+
}
|
|
41248
|
+
}
|
|
41249
|
+
if (!(hi > lo)) {
|
|
41250
|
+
return;
|
|
41251
|
+
}
|
|
41252
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
41253
|
+
const block = await response.arrayBuffer();
|
|
41254
|
+
if (this.disposed) {
|
|
41255
|
+
return;
|
|
41256
|
+
}
|
|
41257
|
+
const draw = async (pick) => {
|
|
41258
|
+
const images = await this.decodeSlices(block, lo, tiles.map(pick));
|
|
41259
|
+
if (this.disposed) {
|
|
41260
|
+
return null;
|
|
41261
|
+
}
|
|
41262
|
+
const canvas = document.createElement("canvas");
|
|
41263
|
+
canvas.width = size.width;
|
|
41264
|
+
canvas.height = size.height;
|
|
41265
|
+
const ctx = canvas.getContext("2d");
|
|
41266
|
+
ctx.imageSmoothingEnabled = false;
|
|
41267
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
41268
|
+
let drew = false;
|
|
41269
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
41270
|
+
const image = images[i];
|
|
41271
|
+
if (!image) {
|
|
41272
|
+
continue;
|
|
41273
|
+
}
|
|
41274
|
+
const at = this.tileRect(tiles[i], target, size);
|
|
41275
|
+
ctx.drawImage(image, at.x, at.y, at.w, at.h);
|
|
41276
|
+
drew = true;
|
|
41277
|
+
}
|
|
41278
|
+
return drew ? ctx.getImageData(0, 0, size.width, size.height) : null;
|
|
41279
|
+
};
|
|
41280
|
+
try {
|
|
41281
|
+
this.floorPixels = await draw((tile) => tile.Floor);
|
|
41282
|
+
this.ceilingPixels = await draw((tile) => tile.Ceiling);
|
|
41283
|
+
}
|
|
41284
|
+
catch (e) {
|
|
41285
|
+
console.warn("TextureFrameSeriesAnimator: could not load the tiled extremes.", e);
|
|
41286
|
+
}
|
|
41287
|
+
}
|
|
41288
|
+
/*
|
|
41289
|
+
* Where one tile lands on the composite, snapped to whole pixels.
|
|
41290
|
+
*
|
|
41291
|
+
* Rounding each edge rather than the origin and size is what makes neighbours meet exactly:
|
|
41292
|
+
* a tile's right edge and the next tile's left edge round to the same pixel, so no seam of
|
|
41293
|
+
* background shows between them.
|
|
41294
|
+
*/
|
|
41295
|
+
tileRect(tile, target, size) {
|
|
41296
|
+
const spanLon = target.East - target.West;
|
|
41297
|
+
const spanLat = target.North - target.South;
|
|
41298
|
+
const x0 = Math.round((tile.West - target.West) / spanLon * size.width);
|
|
41299
|
+
const x1 = Math.round((tile.East - target.West) / spanLon * size.width);
|
|
41300
|
+
const y0 = Math.round((target.North - tile.North) / spanLat * size.height);
|
|
41301
|
+
const y1 = Math.round((target.North - tile.South) / spanLat * size.height);
|
|
41302
|
+
return { x: x0, y: y0, w: Math.max(1, x1 - x0), h: Math.max(1, y1 - y0) };
|
|
41303
|
+
}
|
|
41304
|
+
/*
|
|
41305
|
+
* Decodes many tile images at once.
|
|
41306
|
+
*
|
|
41307
|
+
* Serially awaiting each decode makes a frame cost as many round trips through the image
|
|
41308
|
+
* decoder as there are tiles, which for an 82 tile pyramid is seconds rather than one.
|
|
41309
|
+
*/
|
|
41310
|
+
decodeSlices(block, base, entries) {
|
|
41311
|
+
return Promise.all(entries.map(async (at) => {
|
|
41312
|
+
if (!at) {
|
|
41313
|
+
return null;
|
|
41314
|
+
}
|
|
41315
|
+
const slice = block.slice(at.ByteOffset - base, at.ByteOffset - base + at.ByteLength);
|
|
41316
|
+
const objectUrl = URL.createObjectURL(new Blob([slice], { type: "image/png" }));
|
|
41317
|
+
try {
|
|
41318
|
+
return await loadImage(objectUrl);
|
|
41319
|
+
}
|
|
41320
|
+
catch {
|
|
41321
|
+
return null;
|
|
41322
|
+
}
|
|
41323
|
+
finally {
|
|
41324
|
+
URL.revokeObjectURL(objectUrl);
|
|
41325
|
+
}
|
|
41326
|
+
}));
|
|
41327
|
+
}
|
|
40624
41328
|
/**
|
|
40625
41329
|
* Fetches and decodes the archive's static baseline mask, at most once.
|
|
40626
41330
|
*/
|
|
40627
41331
|
ensureBaselineMask() {
|
|
41332
|
+
if (this.maskBaseline && this.tiles) {
|
|
41333
|
+
if (!this.baselineLoad) {
|
|
41334
|
+
this.baselineLoad = this.loadTiledBaselineMask();
|
|
41335
|
+
}
|
|
41336
|
+
return this.baselineLoad;
|
|
41337
|
+
}
|
|
40628
41338
|
if (!this.maskBaseline || !this.baselineMaskEntry) {
|
|
40629
41339
|
return Promise.resolve();
|
|
40630
41340
|
}
|
|
@@ -40737,24 +41447,17 @@ var TextureFrameSeriesAnimator;
|
|
|
40737
41447
|
presentPixels(pixels, dims, valuePixels) {
|
|
40738
41448
|
this.poolIdx = 1 - this.poolIdx;
|
|
40739
41449
|
const target = this.pool[this.poolIdx];
|
|
40740
|
-
target
|
|
40741
|
-
target.height = dims.height;
|
|
40742
|
-
const ctx = target.getContext("2d");
|
|
40743
|
-
const imgData = ctx.createImageData(dims.width, dims.height);
|
|
40744
|
-
imgData.data.set(pixels);
|
|
40745
|
-
ctx.putImageData(imgData, 0, 0);
|
|
41450
|
+
this.writeInto(target, pixels, dims);
|
|
40746
41451
|
this.displayedPixels = pixels;
|
|
40747
41452
|
if (this.valueCanvas && valuePixels) {
|
|
40748
|
-
this.valueCanvas
|
|
40749
|
-
this.valueCanvas.height = dims.height;
|
|
40750
|
-
const valueCtx = this.valueCanvas.getContext("2d");
|
|
40751
|
-
const valueImgData = valueCtx.createImageData(dims.width, dims.height);
|
|
40752
|
-
valueImgData.data.set(valuePixels);
|
|
40753
|
-
valueCtx.putImageData(valueImgData, 0, 0);
|
|
41453
|
+
this.writeInto(this.valueCanvas, valuePixels, dims);
|
|
40754
41454
|
this.displayedValuePixels = valuePixels;
|
|
40755
41455
|
this.valueDims = dims;
|
|
40756
41456
|
}
|
|
40757
41457
|
this.presentVersion++;
|
|
41458
|
+
if (this.viewer && this.viewer.scene && this.viewer.scene.requestRenderMode) {
|
|
41459
|
+
this.viewer.scene.requestRender();
|
|
41460
|
+
}
|
|
40758
41461
|
}
|
|
40759
41462
|
}
|
|
40760
41463
|
TextureFrameSeriesAnimator.Animator = Animator;
|
|
@@ -40766,6 +41469,730 @@ var TextureFrameSeriesAnimator;
|
|
|
40766
41469
|
}
|
|
40767
41470
|
})(TextureFrameSeriesAnimator || (TextureFrameSeriesAnimator = {}));
|
|
40768
41471
|
|
|
41472
|
+
/**
|
|
41473
|
+
* Prints a value texture's own numbers over the cells they belong to.
|
|
41474
|
+
*/
|
|
41475
|
+
var TextureValueLabels;
|
|
41476
|
+
(function (TextureValueLabels) {
|
|
41477
|
+
// Off by default: a caller can still set a floor, but thinning is the mechanism that keeps a
|
|
41478
|
+
// distant view readable.
|
|
41479
|
+
const DEFAULT_MIN_CELL_PIXELS = 0;
|
|
41480
|
+
const MIN_VISIBLE_PIXELS = 60;
|
|
41481
|
+
const MAX_STRIDE = 512;
|
|
41482
|
+
const STRIDE_HYSTERESIS = 1.4;
|
|
41483
|
+
const DEFAULT_MIN_SPACING_PIXELS = 78;
|
|
41484
|
+
const DEFAULT_MAX_LABELS = 240;
|
|
41485
|
+
const LABEL_RADIUS = 4;
|
|
41486
|
+
const LABEL_FONT = "600 11px ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
41487
|
+
// Clear space either side of a label box, so neighbours read as separate.
|
|
41488
|
+
const LABEL_GAP_PIXELS = 16;
|
|
41489
|
+
const LABEL_ANCHOR_COLOR = "rgba(255,255,255,0.85)";
|
|
41490
|
+
// 8 rather than 5: below this a cell reads as texture rather than as a boundary, and it also
|
|
41491
|
+
// bounds the worst case, since a full screen of 8 px cells fits inside MAX_GRID_CELLS.
|
|
41492
|
+
const DEFAULT_MIN_GRID_CELL_PIXELS = 8;
|
|
41493
|
+
const DEFAULT_GRID_WIDTH_PIXELS = 1;
|
|
41494
|
+
// Enough for a full screen at the minimum legible cell size, with headroom for an oblique view
|
|
41495
|
+
// whose texel window is larger than the screen. The budget is shared across tiles, so sizing it
|
|
41496
|
+
// too tightly made the grid depend on which tile happened to be walked first.
|
|
41497
|
+
const MAX_GRID_CELLS = 40000;
|
|
41498
|
+
// Probes per side when hunting a labellable cell inside one lattice block.
|
|
41499
|
+
const MAX_LABEL_PROBES_PER_SIDE = 8;
|
|
41500
|
+
const TILE_OUTLINE_ALPHA = 0.45;
|
|
41501
|
+
// Cells per grid square at the coarsest. Beyond this a tile is outlined instead.
|
|
41502
|
+
const MAX_GRID_STRIDE = 256;
|
|
41503
|
+
const LINE_HEIGHT = 13;
|
|
41504
|
+
/**
|
|
41505
|
+
* A clip outline from a projected ring, or null when the ring cannot be drawn.
|
|
41506
|
+
* @param projected one entry per ring vertex, null where the vertex has no window coordinate
|
|
41507
|
+
*/
|
|
41508
|
+
function ClipOutline(projected) {
|
|
41509
|
+
if (projected.length < 3) {
|
|
41510
|
+
return null;
|
|
41511
|
+
}
|
|
41512
|
+
const out = [];
|
|
41513
|
+
for (const at of projected) {
|
|
41514
|
+
if (!at) {
|
|
41515
|
+
return null;
|
|
41516
|
+
}
|
|
41517
|
+
out.push(at);
|
|
41518
|
+
}
|
|
41519
|
+
return out;
|
|
41520
|
+
}
|
|
41521
|
+
TextureValueLabels.ClipOutline = ClipOutline;
|
|
41522
|
+
/**
|
|
41523
|
+
* One cell's numbers as a label should print them, or null where the texture has no data there.
|
|
41524
|
+
* @param params at is the index of the cell's red channel. sourceMap is derived from the metadata
|
|
41525
|
+
* when omitted, which a caller reading many cells should hoist rather than repeat.
|
|
41526
|
+
*/
|
|
41527
|
+
function ReadingAt(params) {
|
|
41528
|
+
const { metadata, source, floor, ceiling, at } = params;
|
|
41529
|
+
if (source.data[at + 3] < 128) {
|
|
41530
|
+
return null;
|
|
41531
|
+
}
|
|
41532
|
+
const lo = metadata && typeof metadata.ValueMin === "number" ? metadata.ValueMin : 0;
|
|
41533
|
+
const hi = metadata && typeof metadata.ValueMax === "number" ? metadata.ValueMax : 1;
|
|
41534
|
+
const normalised = TextureFrameSeriesAnimator.NormalisedAt(metadata, source.data, at);
|
|
41535
|
+
const toSource = params.sourceMap !== undefined
|
|
41536
|
+
? params.sourceMap
|
|
41537
|
+
: TextureFrameSeriesAnimator.SourceMap(metadata);
|
|
41538
|
+
const inSource = (value) => TextureFrameSeriesAnimator.ToSource(toSource, value);
|
|
41539
|
+
const reading = { value: inSource(lo + normalised * (hi - lo)) };
|
|
41540
|
+
const eLo = metadata && metadata.ExtremesValueMin;
|
|
41541
|
+
const eHi = metadata && metadata.ExtremesValueMax;
|
|
41542
|
+
if (typeof eLo === "number" && typeof eHi === "number") {
|
|
41543
|
+
if (floor) {
|
|
41544
|
+
reading.floor = inSource(eLo + decode16(floor.data, at) * (eHi - eLo));
|
|
41545
|
+
}
|
|
41546
|
+
if (ceiling) {
|
|
41547
|
+
reading.ceiling = inSource(eLo + decode16(ceiling.data, at) * (eHi - eLo));
|
|
41548
|
+
}
|
|
41549
|
+
}
|
|
41550
|
+
return reading;
|
|
41551
|
+
}
|
|
41552
|
+
TextureValueLabels.ReadingAt = ReadingAt;
|
|
41553
|
+
/**
|
|
41554
|
+
* The unit a label's value line should name, or undefined when the archive states none.
|
|
41555
|
+
* @param metadata the archive's Data.generation
|
|
41556
|
+
*/
|
|
41557
|
+
function LabelUnits(metadata) {
|
|
41558
|
+
if (!metadata) {
|
|
41559
|
+
return undefined;
|
|
41560
|
+
}
|
|
41561
|
+
const recovered = TextureFrameSeriesAnimator.SourceMap(metadata);
|
|
41562
|
+
return (recovered && metadata.SourceUnits) || metadata.Units;
|
|
41563
|
+
}
|
|
41564
|
+
TextureValueLabels.LabelUnits = LabelUnits;
|
|
41565
|
+
/**
|
|
41566
|
+
* How many cells apart labels should sit, as a power of two.
|
|
41567
|
+
* @param options cellPixels is one cell's size on screen, held is the stride already in use
|
|
41568
|
+
*/
|
|
41569
|
+
function ChooseLabelStride(options) {
|
|
41570
|
+
const { cellPixels, visibleCols, visibleRows, maxLabels, minSpacingPixels } = options;
|
|
41571
|
+
const fits = (s, slack) => cellPixels * s >= minSpacingPixels / slack
|
|
41572
|
+
&& Math.ceil(visibleCols / s) * Math.ceil(visibleRows / s) <= maxLabels * slack;
|
|
41573
|
+
let stride = 1;
|
|
41574
|
+
while (stride < MAX_STRIDE && !fits(stride, 1)) {
|
|
41575
|
+
stride *= 2;
|
|
41576
|
+
}
|
|
41577
|
+
const held = options.held;
|
|
41578
|
+
if (held && fits(held, STRIDE_HYSTERESIS)) {
|
|
41579
|
+
if (held === stride / 2 && cellPixels * held >= minSpacingPixels / STRIDE_HYSTERESIS) {
|
|
41580
|
+
return held;
|
|
41581
|
+
}
|
|
41582
|
+
if (held === stride * 2 && cellPixels * stride < minSpacingPixels * STRIDE_HYSTERESIS) {
|
|
41583
|
+
return held;
|
|
41584
|
+
}
|
|
41585
|
+
}
|
|
41586
|
+
return stride;
|
|
41587
|
+
}
|
|
41588
|
+
TextureValueLabels.ChooseLabelStride = ChooseLabelStride;
|
|
41589
|
+
class Labels {
|
|
41590
|
+
constructor(options) {
|
|
41591
|
+
this.sourceMap = null;
|
|
41592
|
+
this.source = null;
|
|
41593
|
+
this.painted = null;
|
|
41594
|
+
this.floor = null;
|
|
41595
|
+
this.ceiling = null;
|
|
41596
|
+
this.disposed = false;
|
|
41597
|
+
this.drawn = 0;
|
|
41598
|
+
this.stride = 0;
|
|
41599
|
+
this.viewer = options.viewer;
|
|
41600
|
+
this.extent = options.extent;
|
|
41601
|
+
this.settings = options.settings || {};
|
|
41602
|
+
this.metadata = options.metadata;
|
|
41603
|
+
// Once per overlay rather than once per cell: a label pass probes thousands of texels
|
|
41604
|
+
// looking for one worth labelling, and the map cannot change while the metadata does not.
|
|
41605
|
+
this.sourceMap = TextureFrameSeriesAnimator.SourceMap(options.metadata);
|
|
41606
|
+
this.tiles = options.tiles;
|
|
41607
|
+
this.clipRing = options.clipRing;
|
|
41608
|
+
this.canvas = document.createElement("canvas");
|
|
41609
|
+
this.canvas.style.position = "absolute";
|
|
41610
|
+
this.canvas.style.inset = "0";
|
|
41611
|
+
this.canvas.style.pointerEvents = "none";
|
|
41612
|
+
const parent = this.viewer.canvas.parentElement;
|
|
41613
|
+
if (parent) {
|
|
41614
|
+
parent.appendChild(this.canvas);
|
|
41615
|
+
}
|
|
41616
|
+
this.ctx = this.canvas.getContext("2d");
|
|
41617
|
+
}
|
|
41618
|
+
SetExtent(extent) {
|
|
41619
|
+
this.extent = extent;
|
|
41620
|
+
}
|
|
41621
|
+
/**
|
|
41622
|
+
* The current frame's decoded values, as the animator's value canvas already provides them.
|
|
41623
|
+
* @param source value pixels, one texel per cell
|
|
41624
|
+
*/
|
|
41625
|
+
SetSource(source) {
|
|
41626
|
+
this.source = source;
|
|
41627
|
+
}
|
|
41628
|
+
/**
|
|
41629
|
+
* The tinted pixels of the same frame, so the grid can skip cells the ramp painted nothing in.
|
|
41630
|
+
* @param painted RGBA of the presented frame, or null to fall back to coverage alone
|
|
41631
|
+
*/
|
|
41632
|
+
SetPainted(painted) {
|
|
41633
|
+
this.painted = painted;
|
|
41634
|
+
}
|
|
41635
|
+
/**
|
|
41636
|
+
* The archive's per-cell extremes, when it publishes them. Without these a label can still
|
|
41637
|
+
* print the current reading, just not the range behind it.
|
|
41638
|
+
*/
|
|
41639
|
+
SetExtremes(floor, ceiling) {
|
|
41640
|
+
this.floor = floor;
|
|
41641
|
+
this.ceiling = ceiling;
|
|
41642
|
+
}
|
|
41643
|
+
GetDrawnCount() {
|
|
41644
|
+
return this.drawn;
|
|
41645
|
+
}
|
|
41646
|
+
Clear() {
|
|
41647
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
41648
|
+
this.drawn = 0;
|
|
41649
|
+
}
|
|
41650
|
+
Dispose() {
|
|
41651
|
+
this.disposed = true;
|
|
41652
|
+
if (this.canvas.parentElement) {
|
|
41653
|
+
this.canvas.parentElement.removeChild(this.canvas);
|
|
41654
|
+
}
|
|
41655
|
+
}
|
|
41656
|
+
/**
|
|
41657
|
+
* Redraws every visible label. Cheap enough to call per rendered frame, because the cell
|
|
41658
|
+
* count it considers is bounded by the view rather than by the texture.
|
|
41659
|
+
*/
|
|
41660
|
+
Render() {
|
|
41661
|
+
var _a, _b, _c;
|
|
41662
|
+
if (this.disposed || !this.source) {
|
|
41663
|
+
return;
|
|
41664
|
+
}
|
|
41665
|
+
const view = this.viewer.canvas;
|
|
41666
|
+
if (this.canvas.width !== view.clientWidth || this.canvas.height !== view.clientHeight) {
|
|
41667
|
+
this.canvas.width = view.clientWidth;
|
|
41668
|
+
this.canvas.height = view.clientHeight;
|
|
41669
|
+
}
|
|
41670
|
+
this.Clear();
|
|
41671
|
+
const cols = this.source.width;
|
|
41672
|
+
const rows = this.source.height;
|
|
41673
|
+
const window = this.visibleTexels(cols, rows);
|
|
41674
|
+
if (!window) {
|
|
41675
|
+
return;
|
|
41676
|
+
}
|
|
41677
|
+
const midCol = (window.col0 + window.col1) / 2;
|
|
41678
|
+
const midRow = (window.row0 + window.row1) / 2;
|
|
41679
|
+
const a = this.project(this.lonAt(midCol, cols), this.latAt(midRow, rows));
|
|
41680
|
+
const b = this.project(this.lonAt(midCol + 1, cols), this.latAt(midRow + 1, rows));
|
|
41681
|
+
if (!a || !b) {
|
|
41682
|
+
return;
|
|
41683
|
+
}
|
|
41684
|
+
const cellPixels = Math.min(Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
41685
|
+
// Ahead of the label threshold on purpose: a grid still reads at a cell size where a
|
|
41686
|
+
// number no longer fits.
|
|
41687
|
+
this.drawGrid(window, cols, rows, cellPixels);
|
|
41688
|
+
if (cellPixels < ((_a = this.settings.minCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_CELL_PIXELS)) {
|
|
41689
|
+
return;
|
|
41690
|
+
}
|
|
41691
|
+
const visibleWidth = cellPixels * (window.col1 - window.col0 + 1);
|
|
41692
|
+
const visibleHeight = cellPixels * (window.row1 - window.row0 + 1);
|
|
41693
|
+
if (Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
41694
|
+
return;
|
|
41695
|
+
}
|
|
41696
|
+
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
41697
|
+
this.ctx.font = LABEL_FONT;
|
|
41698
|
+
this.ctx.textAlign = "center";
|
|
41699
|
+
const maxLabels = (_b = this.settings.maxLabels) !== null && _b !== void 0 ? _b : DEFAULT_MAX_LABELS;
|
|
41700
|
+
this.stride = ChooseLabelStride({
|
|
41701
|
+
cellPixels,
|
|
41702
|
+
visibleCols: window.col1 - window.col0 + 1,
|
|
41703
|
+
visibleRows: window.row1 - window.row0 + 1,
|
|
41704
|
+
maxLabels,
|
|
41705
|
+
// Measured, not assumed: the spacing that keeps labels apart is whatever the widest
|
|
41706
|
+
// one is, so changing what a label says cannot quietly start overlapping them.
|
|
41707
|
+
minSpacingPixels: Math.max((_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS, this.widestLabel(parts)),
|
|
41708
|
+
held: this.stride
|
|
41709
|
+
});
|
|
41710
|
+
const stride = this.stride;
|
|
41711
|
+
const firstRow = Math.floor(window.row0 / stride) * stride;
|
|
41712
|
+
const firstCol = Math.floor(window.col0 / stride) * stride;
|
|
41713
|
+
for (let row = firstRow; row <= window.row1 && this.drawn < maxLabels; row += stride) {
|
|
41714
|
+
for (let col = firstCol; col <= window.col1 && this.drawn < maxLabels; col += stride) {
|
|
41715
|
+
const found = this.readNear(col, row, cols, rows, stride);
|
|
41716
|
+
if (!found) {
|
|
41717
|
+
continue;
|
|
41718
|
+
}
|
|
41719
|
+
const point = this.project(this.lonAt(found.col, cols), this.latAt(found.row, rows));
|
|
41720
|
+
if (!point || point.x < 0 || point.y < 0
|
|
41721
|
+
|| point.x > this.canvas.width || point.y > this.canvas.height) {
|
|
41722
|
+
continue;
|
|
41723
|
+
}
|
|
41724
|
+
this.drawLabel(point, found.reading, parts, this.cellOutline(found.col, found.row, cols, rows));
|
|
41725
|
+
}
|
|
41726
|
+
}
|
|
41727
|
+
}
|
|
41728
|
+
/*
|
|
41729
|
+
* Outlines each covered cell, from a projected lattice of its corners.
|
|
41730
|
+
*/
|
|
41731
|
+
drawGrid(window, cols, rows, cellPixels) {
|
|
41732
|
+
var _a, _b;
|
|
41733
|
+
const color = this.settings.gridColor;
|
|
41734
|
+
if (!color) {
|
|
41735
|
+
return;
|
|
41736
|
+
}
|
|
41737
|
+
if (this.tiles && this.tiles.length) {
|
|
41738
|
+
this.ctx.save();
|
|
41739
|
+
this.applyRingClip();
|
|
41740
|
+
this.drawTileGrid(color);
|
|
41741
|
+
this.ctx.restore();
|
|
41742
|
+
return;
|
|
41743
|
+
}
|
|
41744
|
+
if (cellPixels < ((_a = this.settings.minGridCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_GRID_CELL_PIXELS)) {
|
|
41745
|
+
return;
|
|
41746
|
+
}
|
|
41747
|
+
// Corners are shared by four cells, so projecting the lattice once rather than per cell
|
|
41748
|
+
// cuts the work by about four and keeps neighbouring outlines exactly coincident.
|
|
41749
|
+
const wide = window.col1 - window.col0 + 1;
|
|
41750
|
+
const high = window.row1 - window.row0 + 1;
|
|
41751
|
+
if (wide * high > MAX_GRID_CELLS) {
|
|
41752
|
+
return;
|
|
41753
|
+
}
|
|
41754
|
+
const lattice = new Array((wide + 1) * (high + 1));
|
|
41755
|
+
for (let r = 0; r <= high; r++) {
|
|
41756
|
+
for (let c = 0; c <= wide; c++) {
|
|
41757
|
+
// Corners sit half a texel out from the centres lonAt/latAt return.
|
|
41758
|
+
const lon = this.lonAt(window.col0 + c - 0.5, cols);
|
|
41759
|
+
const lat = this.latAt(window.row0 + r - 0.5, rows);
|
|
41760
|
+
lattice[r * (wide + 1) + c] = this.project(lon, lat);
|
|
41761
|
+
}
|
|
41762
|
+
}
|
|
41763
|
+
const path = new Path2D();
|
|
41764
|
+
let drew = false;
|
|
41765
|
+
for (let r = 0; r < high; r++) {
|
|
41766
|
+
for (let c = 0; c < wide; c++) {
|
|
41767
|
+
if (!this.isCovered(window.col0 + c, window.row0 + r, cols)) {
|
|
41768
|
+
continue;
|
|
41769
|
+
}
|
|
41770
|
+
const tl = lattice[r * (wide + 1) + c];
|
|
41771
|
+
const tr = lattice[r * (wide + 1) + c + 1];
|
|
41772
|
+
const bl = lattice[(r + 1) * (wide + 1) + c];
|
|
41773
|
+
const br = lattice[(r + 1) * (wide + 1) + c + 1];
|
|
41774
|
+
if (!tl || !tr || !bl || !br) {
|
|
41775
|
+
continue;
|
|
41776
|
+
}
|
|
41777
|
+
path.moveTo(tl.x, tl.y);
|
|
41778
|
+
path.lineTo(tr.x, tr.y);
|
|
41779
|
+
path.lineTo(br.x, br.y);
|
|
41780
|
+
path.lineTo(bl.x, bl.y);
|
|
41781
|
+
path.closePath();
|
|
41782
|
+
drew = true;
|
|
41783
|
+
}
|
|
41784
|
+
}
|
|
41785
|
+
if (!drew) {
|
|
41786
|
+
return;
|
|
41787
|
+
}
|
|
41788
|
+
this.ctx.strokeStyle = color;
|
|
41789
|
+
this.ctx.lineWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
41790
|
+
this.ctx.stroke(path);
|
|
41791
|
+
+" alpha" + this.ctx.globalAlpha;
|
|
41792
|
+
}
|
|
41793
|
+
/*
|
|
41794
|
+
* Restricts drawing to the polygon, so nothing is ruled over ground it does not cover.
|
|
41795
|
+
*/
|
|
41796
|
+
applyRingClip() {
|
|
41797
|
+
const ring = this.clipRing;
|
|
41798
|
+
if (!ring || ring.length < 3) {
|
|
41799
|
+
return;
|
|
41800
|
+
}
|
|
41801
|
+
const outline = ClipOutline(ring.map(point => this.project(point.lon, point.lat)));
|
|
41802
|
+
if (!outline) {
|
|
41803
|
+
return;
|
|
41804
|
+
}
|
|
41805
|
+
const path = new Path2D();
|
|
41806
|
+
path.moveTo(outline[0].x, outline[0].y);
|
|
41807
|
+
for (let i = 1; i < outline.length; i++) {
|
|
41808
|
+
path.lineTo(outline[i].x, outline[i].y);
|
|
41809
|
+
}
|
|
41810
|
+
path.closePath();
|
|
41811
|
+
this.ctx.clip(path);
|
|
41812
|
+
}
|
|
41813
|
+
/*
|
|
41814
|
+
* The cell of whichever tile covers a point, as screen corners.
|
|
41815
|
+
*/
|
|
41816
|
+
tileCellOutline(lon, lat) {
|
|
41817
|
+
for (const tile of this.tiles) {
|
|
41818
|
+
if (lon < tile.West || lon > tile.East || lat < tile.South || lat > tile.North) {
|
|
41819
|
+
continue;
|
|
41820
|
+
}
|
|
41821
|
+
const c = (Math.min(tile.ResolutionX - 1, Math.max(0, Math.floor((lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX))));
|
|
41822
|
+
const r = (Math.min(tile.ResolutionY - 1, Math.max(0, Math.floor((tile.North - lat) / (tile.North - tile.South) * tile.ResolutionY))));
|
|
41823
|
+
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
41824
|
+
const w1 = tile.West + ((c + 1) / tile.ResolutionX) * (tile.East - tile.West);
|
|
41825
|
+
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
41826
|
+
const n1 = tile.North - ((r + 1) / tile.ResolutionY) * (tile.North - tile.South);
|
|
41827
|
+
const corners = [
|
|
41828
|
+
this.project(w0, n0), this.project(w1, n0),
|
|
41829
|
+
this.project(w1, n1), this.project(w0, n1)
|
|
41830
|
+
];
|
|
41831
|
+
return corners.every((q) => q) ? corners : null;
|
|
41832
|
+
}
|
|
41833
|
+
return null;
|
|
41834
|
+
}
|
|
41835
|
+
/*
|
|
41836
|
+
* Outlines each tile's own cells, so cell size still shows where the mesh is refined.
|
|
41837
|
+
*/
|
|
41838
|
+
drawTileGrid(color) {
|
|
41839
|
+
var _a, _b, _c;
|
|
41840
|
+
const view = this.viewer.camera.computeViewRectangle(Ellipsoid.WGS84);
|
|
41841
|
+
if (!view) {
|
|
41842
|
+
return;
|
|
41843
|
+
}
|
|
41844
|
+
const west = Math$1.toDegrees(view.west);
|
|
41845
|
+
const east = Math$1.toDegrees(view.east);
|
|
41846
|
+
const south = Math$1.toDegrees(view.south);
|
|
41847
|
+
const north = Math$1.toDegrees(view.north);
|
|
41848
|
+
const minCell = (_a = this.settings.minGridCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_GRID_CELL_PIXELS;
|
|
41849
|
+
const focus = this.groundAtScreenCentre();
|
|
41850
|
+
const path = new Path2D();
|
|
41851
|
+
// A tile too fine to rule, or one the budget cannot afford, contributes its own outline
|
|
41852
|
+
// instead of vanishing. Dropping such tiles entirely is what made whole regions blink out.
|
|
41853
|
+
const coarse = new Path2D();
|
|
41854
|
+
let drew = false;
|
|
41855
|
+
let drewCoarse = false;
|
|
41856
|
+
const outlineTile = (tile) => {
|
|
41857
|
+
const tl = this.project(tile.West, tile.North);
|
|
41858
|
+
const tr = this.project(tile.East, tile.North);
|
|
41859
|
+
const br = this.project(tile.East, tile.South);
|
|
41860
|
+
const bl = this.project(tile.West, tile.South);
|
|
41861
|
+
if (!tl || !tr || !br || !bl) {
|
|
41862
|
+
return;
|
|
41863
|
+
}
|
|
41864
|
+
coarse.moveTo(tl.x, tl.y);
|
|
41865
|
+
coarse.lineTo(tr.x, tr.y);
|
|
41866
|
+
coarse.lineTo(br.x, br.y);
|
|
41867
|
+
coarse.lineTo(bl.x, bl.y);
|
|
41868
|
+
coarse.closePath();
|
|
41869
|
+
drewCoarse = true;
|
|
41870
|
+
};
|
|
41871
|
+
// Planned for every visible tile BEFORE any is drawn.
|
|
41872
|
+
const planned = [];
|
|
41873
|
+
for (const tile of this.tiles) {
|
|
41874
|
+
if (tile.East < west || tile.West > east || tile.North < south || tile.South > north) {
|
|
41875
|
+
continue;
|
|
41876
|
+
}
|
|
41877
|
+
const c0 = Math.max(0, Math.floor((Math.max(west, tile.West) - tile.West)
|
|
41878
|
+
/ (tile.East - tile.West) * tile.ResolutionX));
|
|
41879
|
+
const c1 = Math.min(tile.ResolutionX, Math.ceil((Math.min(east, tile.East) - tile.West)
|
|
41880
|
+
/ (tile.East - tile.West) * tile.ResolutionX));
|
|
41881
|
+
const r0 = Math.max(0, Math.floor((tile.North - Math.min(north, tile.North))
|
|
41882
|
+
/ (tile.North - tile.South) * tile.ResolutionY));
|
|
41883
|
+
const r1 = Math.min(tile.ResolutionY, Math.ceil((tile.North - Math.max(south, tile.South))
|
|
41884
|
+
/ (tile.North - tile.South) * tile.ResolutionY));
|
|
41885
|
+
if (c1 <= c0 || r1 <= r0) {
|
|
41886
|
+
continue;
|
|
41887
|
+
}
|
|
41888
|
+
const focusC = focus
|
|
41889
|
+
? Math.round((focus.lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX)
|
|
41890
|
+
: Math.floor((c0 + c1) / 2);
|
|
41891
|
+
const focusR = focus
|
|
41892
|
+
? Math.round((tile.North - focus.lat) / (tile.North - tile.South) * tile.ResolutionY)
|
|
41893
|
+
: Math.floor((r0 + r1) / 2);
|
|
41894
|
+
const midC = Math.min(c1 - 1, Math.max(c0, focusC));
|
|
41895
|
+
const midR = Math.min(r1 - 1, Math.max(r0, focusR));
|
|
41896
|
+
const cellLon = (tile.East - tile.West) / tile.ResolutionX;
|
|
41897
|
+
const cellLat = (tile.North - tile.South) / tile.ResolutionY;
|
|
41898
|
+
const at = this.project(tile.West + midC * cellLon, tile.North - midR * cellLat);
|
|
41899
|
+
const next = (this.project(tile.West + (midC + 1) * cellLon, tile.North - (midR + 1) * cellLat));
|
|
41900
|
+
if (!at || !next) {
|
|
41901
|
+
outlineTile(tile);
|
|
41902
|
+
continue;
|
|
41903
|
+
}
|
|
41904
|
+
const onScreen = Math.min(Math.abs(next.x - at.x), Math.abs(next.y - at.y));
|
|
41905
|
+
let step = 1;
|
|
41906
|
+
while (step < MAX_GRID_STRIDE && onScreen * step < minCell) {
|
|
41907
|
+
step *= 2;
|
|
41908
|
+
}
|
|
41909
|
+
if (onScreen * step < minCell) {
|
|
41910
|
+
outlineTile(tile);
|
|
41911
|
+
continue;
|
|
41912
|
+
}
|
|
41913
|
+
planned.push({ tile, c0, c1, r0, r1, step });
|
|
41914
|
+
}
|
|
41915
|
+
// Coarsened together rather than dropped one by one, so a busy view thins out evenly.
|
|
41916
|
+
const blocksOf = (p) => Math.ceil((p.c1 - p.c0) / p.step) * Math.ceil((p.r1 - p.r0) / p.step);
|
|
41917
|
+
let total = planned.reduce((sum, p) => sum + blocksOf(p), 0);
|
|
41918
|
+
while (total > MAX_GRID_CELLS && planned.some((p) => p.step < MAX_GRID_STRIDE)) {
|
|
41919
|
+
for (const p of planned) {
|
|
41920
|
+
p.step = Math.min(MAX_GRID_STRIDE, p.step * 2);
|
|
41921
|
+
}
|
|
41922
|
+
total = planned.reduce((sum, p) => sum + blocksOf(p), 0);
|
|
41923
|
+
}
|
|
41924
|
+
for (const { tile, c0, c1, r0, r1, step } of planned) {
|
|
41925
|
+
// Anchored to the tile's own origin so lines stay on the same boundaries as the camera
|
|
41926
|
+
// moves, and so each step up is a subset of the one below it.
|
|
41927
|
+
const firstR = Math.floor(r0 / step) * step;
|
|
41928
|
+
const firstC = Math.floor(c0 / step) * step;
|
|
41929
|
+
for (let r = firstR; r < r1; r += step) {
|
|
41930
|
+
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
41931
|
+
const n1 = tile.North
|
|
41932
|
+
- (Math.min(r + step, tile.ResolutionY) / tile.ResolutionY) * (tile.North - tile.South);
|
|
41933
|
+
for (let c = firstC; c < c1; c += step) {
|
|
41934
|
+
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
41935
|
+
const w1 = tile.West
|
|
41936
|
+
+ (Math.min(c + step, tile.ResolutionX) / tile.ResolutionX) * (tile.East - tile.West);
|
|
41937
|
+
if (!this.coveredAt((w0 + w1) / 2, (n0 + n1) / 2)) {
|
|
41938
|
+
continue;
|
|
41939
|
+
}
|
|
41940
|
+
const tl = this.project(w0, n0);
|
|
41941
|
+
const tr = this.project(w1, n0);
|
|
41942
|
+
const br = this.project(w1, n1);
|
|
41943
|
+
const bl = this.project(w0, n1);
|
|
41944
|
+
if (!tl || !tr || !br || !bl) {
|
|
41945
|
+
continue;
|
|
41946
|
+
}
|
|
41947
|
+
path.moveTo(tl.x, tl.y);
|
|
41948
|
+
path.lineTo(tr.x, tr.y);
|
|
41949
|
+
path.lineTo(br.x, br.y);
|
|
41950
|
+
path.lineTo(bl.x, bl.y);
|
|
41951
|
+
path.closePath();
|
|
41952
|
+
drew = true;
|
|
41953
|
+
}
|
|
41954
|
+
}
|
|
41955
|
+
}
|
|
41956
|
+
const gridWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
41957
|
+
if (drewCoarse) {
|
|
41958
|
+
this.ctx.save();
|
|
41959
|
+
// Dimmer than a cell edge, so a tile boundary reads as "there is finer data here"
|
|
41960
|
+
// rather than as a cell in its own right.
|
|
41961
|
+
this.ctx.globalAlpha = TILE_OUTLINE_ALPHA;
|
|
41962
|
+
this.ctx.strokeStyle = color;
|
|
41963
|
+
this.ctx.lineWidth = gridWidth;
|
|
41964
|
+
this.ctx.stroke(coarse);
|
|
41965
|
+
this.ctx.restore();
|
|
41966
|
+
}
|
|
41967
|
+
if (!drew) {
|
|
41968
|
+
return;
|
|
41969
|
+
}
|
|
41970
|
+
this.ctx.strokeStyle = color;
|
|
41971
|
+
this.ctx.lineWidth = (_c = this.settings.gridWidthPixels) !== null && _c !== void 0 ? _c : DEFAULT_GRID_WIDTH_PIXELS;
|
|
41972
|
+
this.ctx.stroke(path);
|
|
41973
|
+
}
|
|
41974
|
+
/*
|
|
41975
|
+
* Lon/lat of the ground at the centre of the view, or null when the centre misses the globe.
|
|
41976
|
+
*/
|
|
41977
|
+
groundAtScreenCentre() {
|
|
41978
|
+
const scene = this.viewer.scene;
|
|
41979
|
+
const centre = new Cartesian2(this.viewer.canvas.clientWidth / 2, this.viewer.canvas.clientHeight / 2);
|
|
41980
|
+
const hit = scene.camera.pickEllipsoid(centre, Ellipsoid.WGS84);
|
|
41981
|
+
if (!hit) {
|
|
41982
|
+
return null;
|
|
41983
|
+
}
|
|
41984
|
+
const carto = Cartographic.fromCartesian(hit);
|
|
41985
|
+
return {
|
|
41986
|
+
lon: Math$1.toDegrees(carto.longitude),
|
|
41987
|
+
lat: Math$1.toDegrees(carto.latitude)
|
|
41988
|
+
};
|
|
41989
|
+
}
|
|
41990
|
+
/*
|
|
41991
|
+
* Whether the composited raster holds data at a lon/lat.
|
|
41992
|
+
*/
|
|
41993
|
+
coveredAt(lon, lat) {
|
|
41994
|
+
if (!this.source) {
|
|
41995
|
+
return false;
|
|
41996
|
+
}
|
|
41997
|
+
const cols = this.source.width;
|
|
41998
|
+
const rows = this.source.height;
|
|
41999
|
+
const col = Math.floor((lon - this.extent.West) / (this.extent.East - this.extent.West) * cols);
|
|
42000
|
+
const row = Math.floor((this.extent.North - lat) / (this.extent.North - this.extent.South) * rows);
|
|
42001
|
+
if (col < 0 || row < 0 || col >= cols || row >= rows) {
|
|
42002
|
+
return false;
|
|
42003
|
+
}
|
|
42004
|
+
const at = (row * cols + col) * 4 + 3;
|
|
42005
|
+
// Painted, not merely covered: a dry cell holds a reading the ramp deliberately hides, and
|
|
42006
|
+
// ruling a grid over it draws detail where the picture shows none.
|
|
42007
|
+
if (this.painted && this.painted.length === cols * rows * 4) {
|
|
42008
|
+
return this.painted[at] > 0;
|
|
42009
|
+
}
|
|
42010
|
+
return this.source.data[at] >= 128;
|
|
42011
|
+
}
|
|
42012
|
+
isCovered(col, row, cols) {
|
|
42013
|
+
return Boolean(this.source && this.source.data[(row * cols + col) * 4 + 3] >= 128);
|
|
42014
|
+
}
|
|
42015
|
+
/*
|
|
42016
|
+
* Width of the widest label this overlay could draw, in pixels.
|
|
42017
|
+
*/
|
|
42018
|
+
widestLabel(parts) {
|
|
42019
|
+
const sample = [];
|
|
42020
|
+
if (parts.indexOf("value") >= 0) {
|
|
42021
|
+
sample.push(valueLine("-000.00", LabelUnits(this.metadata)));
|
|
42022
|
+
}
|
|
42023
|
+
const range = [];
|
|
42024
|
+
if (parts.indexOf("floor") >= 0) {
|
|
42025
|
+
range.push("min: -000.00");
|
|
42026
|
+
}
|
|
42027
|
+
if (parts.indexOf("ceiling") >= 0) {
|
|
42028
|
+
range.push("max: -000.00");
|
|
42029
|
+
}
|
|
42030
|
+
if (range.length) {
|
|
42031
|
+
sample.push(range.join(" - "));
|
|
42032
|
+
}
|
|
42033
|
+
if (!sample.length) {
|
|
42034
|
+
return 0;
|
|
42035
|
+
}
|
|
42036
|
+
const widest = Math.max(...sample.map((line) => this.ctx.measureText(line).width));
|
|
42037
|
+
return widest + LABEL_GAP_PIXELS;
|
|
42038
|
+
}
|
|
42039
|
+
/*
|
|
42040
|
+
* The four screen corners of one cell, or null if any of them will not project.
|
|
42041
|
+
*/
|
|
42042
|
+
cellOutline(col, row, cols, rows) {
|
|
42043
|
+
// On a pyramid the composited texel is not a cell. Outlining it would draw a second,
|
|
42044
|
+
// uniform lattice over the tile grid, misaligned with it everywhere.
|
|
42045
|
+
if (this.tiles && this.tiles.length) {
|
|
42046
|
+
return this.tileCellOutline(this.lonAt(col, cols), this.latAt(row, rows));
|
|
42047
|
+
}
|
|
42048
|
+
const corners = [
|
|
42049
|
+
this.project(this.lonAt(col - 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
42050
|
+
this.project(this.lonAt(col + 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
42051
|
+
this.project(this.lonAt(col + 0.5, cols), this.latAt(row + 0.5, rows)),
|
|
42052
|
+
this.project(this.lonAt(col - 0.5, cols), this.latAt(row + 0.5, rows))
|
|
42053
|
+
];
|
|
42054
|
+
return corners.every((c) => c) ? corners : null;
|
|
42055
|
+
}
|
|
42056
|
+
drawLabel(point, reading, parts, outline) {
|
|
42057
|
+
var _a;
|
|
42058
|
+
const dp = (_a = this.settings.decimals) !== null && _a !== void 0 ? _a : (Math.abs(reading.value) < 10 ? 2 : 1);
|
|
42059
|
+
const lines = [];
|
|
42060
|
+
if (parts.indexOf("value") >= 0) {
|
|
42061
|
+
lines.push(valueLine(reading.value.toFixed(dp), LabelUnits(this.metadata)));
|
|
42062
|
+
}
|
|
42063
|
+
// Named rather than a bare pair: two numbers under a third say nothing about which is
|
|
42064
|
+
// the series low and which is its high.
|
|
42065
|
+
const range = [];
|
|
42066
|
+
if (parts.indexOf("floor") >= 0 && reading.floor !== undefined) {
|
|
42067
|
+
range.push(`min: ${reading.floor.toFixed(dp)}`);
|
|
42068
|
+
}
|
|
42069
|
+
if (parts.indexOf("ceiling") >= 0 && reading.ceiling !== undefined) {
|
|
42070
|
+
range.push(`max: ${reading.ceiling.toFixed(dp)}`);
|
|
42071
|
+
}
|
|
42072
|
+
if (range.length) {
|
|
42073
|
+
lines.push(range.join(" - "));
|
|
42074
|
+
}
|
|
42075
|
+
if (!lines.length) {
|
|
42076
|
+
return;
|
|
42077
|
+
}
|
|
42078
|
+
if (outline) {
|
|
42079
|
+
this.ctx.strokeStyle = LABEL_ANCHOR_COLOR;
|
|
42080
|
+
this.ctx.lineWidth = 1.5;
|
|
42081
|
+
this.ctx.beginPath();
|
|
42082
|
+
this.ctx.moveTo(outline[0].x, outline[0].y);
|
|
42083
|
+
for (let i = 1; i < outline.length; i++) {
|
|
42084
|
+
this.ctx.lineTo(outline[i].x, outline[i].y);
|
|
42085
|
+
}
|
|
42086
|
+
this.ctx.closePath();
|
|
42087
|
+
this.ctx.stroke();
|
|
42088
|
+
}
|
|
42089
|
+
const width = Math.max(...lines.map((l) => this.ctx.measureText(l).width)) + 10;
|
|
42090
|
+
const height = lines.length * LINE_HEIGHT + 6;
|
|
42091
|
+
const top = point.y - height / 2;
|
|
42092
|
+
this.ctx.fillStyle = "rgba(16,16,14,0.72)";
|
|
42093
|
+
this.ctx.beginPath();
|
|
42094
|
+
this.ctx.roundRect(point.x - width / 2, top, width, height, LABEL_RADIUS);
|
|
42095
|
+
this.ctx.fill();
|
|
42096
|
+
lines.forEach((line, i) => {
|
|
42097
|
+
this.ctx.fillStyle = i === 0 ? "#ffffff" : "#a8a89f";
|
|
42098
|
+
this.ctx.fillText(line, point.x, top + LINE_HEIGHT * (i + 1) - 1);
|
|
42099
|
+
});
|
|
42100
|
+
this.drawn++;
|
|
42101
|
+
}
|
|
42102
|
+
/*
|
|
42103
|
+
* The first cell with a reading at or near a lattice point, searched within its own block.
|
|
42104
|
+
*/
|
|
42105
|
+
readNear(col, row, cols, rows, stride) {
|
|
42106
|
+
const direct = this.readAt(col, row, cols);
|
|
42107
|
+
if (direct && this.paintedAt(col, row, cols)) {
|
|
42108
|
+
return { col, row, reading: direct };
|
|
42109
|
+
}
|
|
42110
|
+
if (stride <= 1) {
|
|
42111
|
+
return null;
|
|
42112
|
+
}
|
|
42113
|
+
// Bounded, and stepped rather than exhaustive, so a large stride cannot make this the
|
|
42114
|
+
// expensive part of a frame.
|
|
42115
|
+
const step = Math.max(1, Math.floor(stride / MAX_LABEL_PROBES_PER_SIDE));
|
|
42116
|
+
for (let r = row; r < Math.min(rows, row + stride); r += step) {
|
|
42117
|
+
for (let c = col; c < Math.min(cols, col + stride); c += step) {
|
|
42118
|
+
const reading = this.readAt(c, r, cols);
|
|
42119
|
+
if (reading && this.paintedAt(c, r, cols)) {
|
|
42120
|
+
return { col: c, row: r, reading };
|
|
42121
|
+
}
|
|
42122
|
+
}
|
|
42123
|
+
}
|
|
42124
|
+
return null;
|
|
42125
|
+
}
|
|
42126
|
+
paintedAt(col, row, cols) {
|
|
42127
|
+
if (!this.painted) {
|
|
42128
|
+
return true;
|
|
42129
|
+
}
|
|
42130
|
+
const at = (row * cols + col) * 4 + 3;
|
|
42131
|
+
return this.painted[at] > 0;
|
|
42132
|
+
}
|
|
42133
|
+
// Decodes one cell, skipping anything the texture marks as having no data.
|
|
42134
|
+
readAt(col, row, cols) {
|
|
42135
|
+
if (!this.source) {
|
|
42136
|
+
return null;
|
|
42137
|
+
}
|
|
42138
|
+
return ReadingAt({
|
|
42139
|
+
metadata: this.metadata,
|
|
42140
|
+
source: this.source,
|
|
42141
|
+
floor: this.floor,
|
|
42142
|
+
ceiling: this.ceiling,
|
|
42143
|
+
at: (row * cols + col) * 4,
|
|
42144
|
+
sourceMap: this.sourceMap
|
|
42145
|
+
});
|
|
42146
|
+
}
|
|
42147
|
+
lonAt(col, cols) {
|
|
42148
|
+
return this.extent.West + ((col + 0.5) / cols) * (this.extent.East - this.extent.West);
|
|
42149
|
+
}
|
|
42150
|
+
latAt(row, rows) {
|
|
42151
|
+
return this.extent.North - ((row + 0.5) / rows) * (this.extent.North - this.extent.South);
|
|
42152
|
+
}
|
|
42153
|
+
// The texel range overlapping the camera's view, so an off-screen texture costs nothing.
|
|
42154
|
+
visibleTexels(cols, rows) {
|
|
42155
|
+
const view = this.viewer.camera.computeViewRectangle(Ellipsoid.WGS84);
|
|
42156
|
+
if (!view) {
|
|
42157
|
+
return null;
|
|
42158
|
+
}
|
|
42159
|
+
const west = Math.max(this.extent.West, Math$1.toDegrees(view.west));
|
|
42160
|
+
const east = Math.min(this.extent.East, Math$1.toDegrees(view.east));
|
|
42161
|
+
const south = Math.max(this.extent.South, Math$1.toDegrees(view.south));
|
|
42162
|
+
const north = Math.min(this.extent.North, Math$1.toDegrees(view.north));
|
|
42163
|
+
if (west >= east || south >= north) {
|
|
42164
|
+
return null;
|
|
42165
|
+
}
|
|
42166
|
+
const spanLon = this.extent.East - this.extent.West;
|
|
42167
|
+
const spanLat = this.extent.North - this.extent.South;
|
|
42168
|
+
return {
|
|
42169
|
+
col0: Math.max(0, Math.floor((west - this.extent.West) / spanLon * cols) - 1),
|
|
42170
|
+
col1: Math.min(cols - 1, Math.ceil((east - this.extent.West) / spanLon * cols) + 1),
|
|
42171
|
+
row0: Math.max(0, Math.floor((this.extent.North - north) / spanLat * rows) - 1),
|
|
42172
|
+
row1: Math.min(rows - 1, Math.ceil((this.extent.North - south) / spanLat * rows) + 1)
|
|
42173
|
+
};
|
|
42174
|
+
}
|
|
42175
|
+
project(lon, lat) {
|
|
42176
|
+
const world = Cartesian3.fromDegrees(lon, lat, 0);
|
|
42177
|
+
const transforms = SceneTransforms;
|
|
42178
|
+
// Renamed in newer Cesium, and this library runs on whatever the host app supplies.
|
|
42179
|
+
const fn = transforms.worldToWindowCoordinates || transforms.wgs84ToWindowCoordinates;
|
|
42180
|
+
return fn ? fn.call(transforms, this.viewer.scene, world) : null;
|
|
42181
|
+
}
|
|
42182
|
+
}
|
|
42183
|
+
TextureValueLabels.Labels = Labels;
|
|
42184
|
+
/*
|
|
42185
|
+
* The value line. Units go on it alone: repeating them on the range underneath doubles the widest
|
|
42186
|
+
* label to say nothing, and the two lines are plainly the same quantity.
|
|
42187
|
+
*/
|
|
42188
|
+
function valueLine(value, units) {
|
|
42189
|
+
return units ? `${value} (${units})` : value;
|
|
42190
|
+
}
|
|
42191
|
+
function decode16(data, i) {
|
|
42192
|
+
return (data[i] * 256 + data[i + 1]) / 65535;
|
|
42193
|
+
}
|
|
42194
|
+
})(TextureValueLabels || (TextureValueLabels = {}));
|
|
42195
|
+
|
|
40769
42196
|
/**
|
|
40770
42197
|
* Renders a value texture as a displaced, coloured surface.
|
|
40771
42198
|
* The source canvas is expected as RGBA: value in R, coverage in A.
|
|
@@ -40786,8 +42213,6 @@ var DisplacedSurfacePrimitive;
|
|
|
40786
42213
|
const SKIRT_FRACTION = 0.015;
|
|
40787
42214
|
// Alpha below which a texel counts as no-data and is not drawn at all.
|
|
40788
42215
|
const COVERAGE_CUTOFF = 0.35;
|
|
40789
|
-
// With no exaggeration stated, the full value range is drawn as this fraction of the extent's shorter side.
|
|
40790
|
-
const AUTO_RELIEF_FRACTION = 0.02;
|
|
40791
42216
|
// Terrain is sampled on this grid and interpolated between samples.
|
|
40792
42217
|
// Per-texel sampling would be hundreds of thousands of lookups for a surface whose own data is far coarser than that.
|
|
40793
42218
|
const GROUND_SAMPLES_PER_SIDE = 64;
|
|
@@ -40808,6 +42233,7 @@ in float skirt;
|
|
|
40808
42233
|
|
|
40809
42234
|
uniform sampler2D u_valueTexture;
|
|
40810
42235
|
uniform sampler2D u_groundTexture;
|
|
42236
|
+
uniform float u_valuePacked;
|
|
40811
42237
|
uniform float u_valueMin;
|
|
40812
42238
|
uniform float u_valueRange;
|
|
40813
42239
|
uniform float u_exaggeration;
|
|
@@ -40845,18 +42271,37 @@ float sampleGround(vec2 uv) {
|
|
|
40845
42271
|
return mix(mix(h00, h10, f.x), mix(h01, h11, f.x), f.y);
|
|
40846
42272
|
}
|
|
40847
42273
|
|
|
42274
|
+
/*
|
|
42275
|
+
* The texel's normalised value, whichever way the archive packed it.
|
|
42276
|
+
*
|
|
42277
|
+
* A packed frame keeps the high byte in R, so reading R alone would quantise the whole range to 255
|
|
42278
|
+
* steps and draw a plausible wrong surface rather than an obviously broken one.
|
|
42279
|
+
*
|
|
42280
|
+
* Each channel is scaled and then added, rather than reassembled to a 0..65535 integer and divided.
|
|
42281
|
+
* That integer is outside the range mediump guarantees, and a fragment shader only gets highp where
|
|
42282
|
+
* the device offers it, so the obvious form loses the low byte on exactly the older mobile hardware
|
|
42283
|
+
* that Cesium falls back to mediump for.
|
|
42284
|
+
*/
|
|
42285
|
+
float decodeValue(vec4 texel) {
|
|
42286
|
+
float packed16 = texel.r * (255.0 * 256.0 / 65535.0) + texel.g * (255.0 / 65535.0);
|
|
42287
|
+
return mix(texel.r, packed16, u_valuePacked);
|
|
42288
|
+
}
|
|
42289
|
+
|
|
40848
42290
|
void main() {
|
|
40849
42291
|
vec4 texel = texture(u_valueTexture, st);
|
|
40850
42292
|
v_st = st;
|
|
40851
|
-
v_value = texel
|
|
42293
|
+
v_value = decodeValue(texel);
|
|
40852
42294
|
v_coverage = texel.a;
|
|
40853
42295
|
|
|
40854
|
-
|
|
42296
|
+
// Exaggeration stretches the span above the archive's minimum rather than the height itself. On an
|
|
42297
|
+
// elevation the height is measured from the ellipsoid, tens of metres from any of the data, so
|
|
42298
|
+
// scaling that instead translates the whole sheet underground rather than spreading it out.
|
|
42299
|
+
float metres = u_valueMin + v_value * u_valueRange * u_exaggeration;
|
|
40855
42300
|
|
|
40856
42301
|
// Value zero sits at the polygon's own altitude, plus the terrain underneath when the polygon
|
|
40857
42302
|
// is following the ground rather than absolutely positioned.
|
|
40858
42303
|
float ground = sampleGround(st) * u_groundWeight;
|
|
40859
|
-
float displacement = u_baseHeight + ground + metres *
|
|
42304
|
+
float displacement = u_baseHeight + ground + metres * texel.a - skirt * u_skirtDepth;
|
|
40860
42305
|
|
|
40861
42306
|
// Added to the LOW half of the encoded position: a metre-scale offset added to the high half
|
|
40862
42307
|
// would be lost to float32 rounding at an earth radius.
|
|
@@ -40866,10 +42311,10 @@ void main() {
|
|
|
40866
42311
|
`;
|
|
40867
42312
|
const FRAGMENT_SHADER_GLSL300 = `
|
|
40868
42313
|
uniform sampler2D u_valueTexture;
|
|
40869
|
-
uniform
|
|
40870
|
-
uniform vec4 u_highColor;
|
|
42314
|
+
uniform sampler2D u_rampTexture;
|
|
40871
42315
|
uniform float u_coverageCutoff;
|
|
40872
42316
|
uniform vec2 u_texelSize;
|
|
42317
|
+
uniform float u_valuePacked;
|
|
40873
42318
|
uniform float u_valueRange;
|
|
40874
42319
|
uniform float u_exaggeration;
|
|
40875
42320
|
uniform float u_metresPerTexel;
|
|
@@ -40878,18 +42323,34 @@ in vec2 v_st;
|
|
|
40878
42323
|
in float v_value;
|
|
40879
42324
|
in float v_coverage;
|
|
40880
42325
|
|
|
42326
|
+
/*
|
|
42327
|
+
* Scaled per channel for the same reason the vertex shader's decode is: reassembling the pair reaches
|
|
42328
|
+
* 65535, which mediump does not have to represent, and this is the shader that may not get highp.
|
|
42329
|
+
*/
|
|
42330
|
+
float valueAt(vec2 uv) {
|
|
42331
|
+
vec4 texel = texture(u_valueTexture, uv);
|
|
42332
|
+
float packed16 = texel.r * (255.0 * 256.0 / 65535.0) + texel.g * (255.0 / 65535.0);
|
|
42333
|
+
return mix(texel.r, packed16, u_valuePacked);
|
|
42334
|
+
}
|
|
42335
|
+
|
|
40881
42336
|
void main() {
|
|
40882
42337
|
if (v_coverage < u_coverageCutoff) {
|
|
40883
42338
|
discard;
|
|
40884
42339
|
}
|
|
40885
|
-
|
|
42340
|
+
// The ramp is baked to a lookup rather than lerped between two colours, so a banded style with a
|
|
42341
|
+
// hidden floor reads the same here as it does on the flat drape.
|
|
42342
|
+
// Without it this surface painted water the style hides, and the cell grid then correctly refused to outline any of it.
|
|
42343
|
+
vec4 color = texture(u_rampTexture, vec2(clamp(v_value, 0.0, 1.0), 0.5));
|
|
42344
|
+
if (color.a <= 0.0) {
|
|
42345
|
+
discard;
|
|
42346
|
+
}
|
|
40886
42347
|
|
|
40887
42348
|
// Relief shading from the value gradient, so the displacement reads at a distance without
|
|
40888
42349
|
// recomputing vertex normals every time the texture changes.
|
|
40889
|
-
float left =
|
|
40890
|
-
float right =
|
|
40891
|
-
float down =
|
|
40892
|
-
float up =
|
|
42350
|
+
float left = valueAt(v_st - vec2(u_texelSize.x, 0.0));
|
|
42351
|
+
float right = valueAt(v_st + vec2(u_texelSize.x, 0.0));
|
|
42352
|
+
float down = valueAt(v_st - vec2(0.0, u_texelSize.y));
|
|
42353
|
+
float up = valueAt(v_st + vec2(0.0, u_texelSize.y));
|
|
40893
42354
|
float scale = u_valueRange * u_exaggeration;
|
|
40894
42355
|
vec3 n = normalize(vec3((left - right) * scale, (down - up) * scale, 2.0 * u_metresPerTexel));
|
|
40895
42356
|
float lambert = clamp(dot(n, normalize(vec3(-0.5, -0.6, 0.62))), 0.0, 1.0);
|
|
@@ -41081,6 +42542,7 @@ void main() {
|
|
|
41081
42542
|
this.show = true;
|
|
41082
42543
|
this.valueSource = null;
|
|
41083
42544
|
this.lastPresentVersion = -1;
|
|
42545
|
+
this.rampTexture = null;
|
|
41084
42546
|
this.texture = null;
|
|
41085
42547
|
this.textureDirty = true;
|
|
41086
42548
|
this.shaderProgram = null;
|
|
@@ -41098,12 +42560,12 @@ void main() {
|
|
|
41098
42560
|
this.source = options.source || null;
|
|
41099
42561
|
this.valueMin = (_a = options.valueMin) !== null && _a !== void 0 ? _a : 0;
|
|
41100
42562
|
this.valueMax = (_b = options.valueMax) !== null && _b !== void 0 ? _b : 1;
|
|
41101
|
-
this.exaggeration = (_c = options.exaggeration) !== null && _c !== void 0 ? _c :
|
|
42563
|
+
this.exaggeration = (_c = options.exaggeration) !== null && _c !== void 0 ? _c : 1;
|
|
41102
42564
|
this.baseHeight = (_d = options.baseHeight) !== null && _d !== void 0 ? _d : 0;
|
|
42565
|
+
this.packedValue = Boolean(options.packedValue);
|
|
41103
42566
|
this.pixelPlacement = options.pixelPlacement;
|
|
41104
42567
|
this.tileSkirts = Boolean(options.tileSkirts);
|
|
41105
|
-
this.
|
|
41106
|
-
this.highColor = toCesiumColor(options.highColor || DEFAULT_HIGH_COLOR);
|
|
42568
|
+
this.rampPixels = RampLookup(options.rampStops || null, options.lowColor || DEFAULT_LOW_COLOR, options.highColor || DEFAULT_HIGH_COLOR);
|
|
41107
42569
|
this.tiles = buildTiles(hasCoverage);
|
|
41108
42570
|
}
|
|
41109
42571
|
GetFollowsGround() {
|
|
@@ -41162,6 +42624,7 @@ void main() {
|
|
|
41162
42624
|
return;
|
|
41163
42625
|
}
|
|
41164
42626
|
this.syncTexture(context);
|
|
42627
|
+
this.syncRampTexture(context);
|
|
41165
42628
|
this.requestGround();
|
|
41166
42629
|
this.syncGroundTexture(context);
|
|
41167
42630
|
// A tile needs one mesh before it has a bounding sphere to measure against.
|
|
@@ -41200,6 +42663,10 @@ void main() {
|
|
|
41200
42663
|
this.texture.destroy();
|
|
41201
42664
|
this.texture = null;
|
|
41202
42665
|
}
|
|
42666
|
+
if (this.rampTexture) {
|
|
42667
|
+
this.rampTexture.destroy();
|
|
42668
|
+
this.rampTexture = null;
|
|
42669
|
+
}
|
|
41203
42670
|
if (this.groundTexture) {
|
|
41204
42671
|
this.groundTexture.destroy();
|
|
41205
42672
|
this.groundTexture = null;
|
|
@@ -41293,6 +42760,25 @@ void main() {
|
|
|
41293
42760
|
const width = Cartesian3.distance(southWest, southEast);
|
|
41294
42761
|
return Math.max(1, width / Math.max(1, this.source ? this.source.width : 1));
|
|
41295
42762
|
}
|
|
42763
|
+
syncRampTexture(context) {
|
|
42764
|
+
if (this.rampTexture) {
|
|
42765
|
+
return;
|
|
42766
|
+
}
|
|
42767
|
+
this.rampTexture = new Texture({
|
|
42768
|
+
context,
|
|
42769
|
+
pixelFormat: PixelFormat.RGBA,
|
|
42770
|
+
source: { width: this.rampPixels.length / 4, height: 1,
|
|
42771
|
+
arrayBufferView: this.rampPixels },
|
|
42772
|
+
flipY: false,
|
|
42773
|
+
sampler: new Sampler({
|
|
42774
|
+
wrapS: TextureWrap.CLAMP_TO_EDGE,
|
|
42775
|
+
wrapT: TextureWrap.CLAMP_TO_EDGE,
|
|
42776
|
+
// Linear across the ramp, so a gradient between stops stays smooth.
|
|
42777
|
+
minificationFilter: TextureMinificationFilter.LINEAR,
|
|
42778
|
+
magnificationFilter: TextureMagnificationFilter.LINEAR
|
|
42779
|
+
})
|
|
42780
|
+
});
|
|
42781
|
+
}
|
|
41296
42782
|
syncTexture(context) {
|
|
41297
42783
|
const sizeChanged = this.texture
|
|
41298
42784
|
&& (this.texture.width !== this.source.width || this.texture.height !== this.source.height);
|
|
@@ -41300,6 +42786,14 @@ void main() {
|
|
|
41300
42786
|
if (this.texture) {
|
|
41301
42787
|
this.texture.destroy();
|
|
41302
42788
|
}
|
|
42789
|
+
// Nearest for a packed value, because filtering would blend the two bytes of the
|
|
42790
|
+
// packing rather than the values they encode, the same trap sampleGround avoids.
|
|
42791
|
+
const minification = this.packedValue
|
|
42792
|
+
? TextureMinificationFilter.NEAREST
|
|
42793
|
+
: TextureMinificationFilter.LINEAR;
|
|
42794
|
+
const magnification = this.packedValue
|
|
42795
|
+
? TextureMagnificationFilter.NEAREST
|
|
42796
|
+
: TextureMagnificationFilter.LINEAR;
|
|
41303
42797
|
this.texture = new Texture({
|
|
41304
42798
|
context,
|
|
41305
42799
|
source: this.source,
|
|
@@ -41309,8 +42803,8 @@ void main() {
|
|
|
41309
42803
|
sampler: new Sampler({
|
|
41310
42804
|
wrapS: TextureWrap.CLAMP_TO_EDGE,
|
|
41311
42805
|
wrapT: TextureWrap.CLAMP_TO_EDGE,
|
|
41312
|
-
minificationFilter:
|
|
41313
|
-
magnificationFilter:
|
|
42806
|
+
minificationFilter: minification,
|
|
42807
|
+
magnificationFilter: magnification
|
|
41314
42808
|
})
|
|
41315
42809
|
});
|
|
41316
42810
|
this.textureDirty = false;
|
|
@@ -41411,6 +42905,7 @@ void main() {
|
|
|
41411
42905
|
uniformMap: {
|
|
41412
42906
|
u_valueTexture: () => self.texture,
|
|
41413
42907
|
u_groundTexture: () => self.groundTexture,
|
|
42908
|
+
u_valuePacked: () => (self.packedValue ? 1 : 0),
|
|
41414
42909
|
u_groundWeight: () => (self.followGround && self.groundPixels ? 1 : 0),
|
|
41415
42910
|
u_groundRange: () => new Cartesian2(GROUND_MIN_METRES, GROUND_MAX_METRES),
|
|
41416
42911
|
u_groundSize: () => new Cartesian2(self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1, self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1),
|
|
@@ -41423,8 +42918,7 @@ void main() {
|
|
|
41423
42918
|
? Math.max(2, Math.abs(self.valueMax - self.valueMin) * self.exaggeration * SKIRT_FRACTION)
|
|
41424
42919
|
: 0,
|
|
41425
42920
|
u_coverageCutoff: () => COVERAGE_CUTOFF,
|
|
41426
|
-
|
|
41427
|
-
u_highColor: () => self.highColor,
|
|
42921
|
+
u_rampTexture: () => self.rampTexture,
|
|
41428
42922
|
u_texelSize: () => new Cartesian2(1 / self.source.width, 1 / self.source.height)
|
|
41429
42923
|
}
|
|
41430
42924
|
});
|
|
@@ -41464,33 +42958,6 @@ void main() {
|
|
|
41464
42958
|
}
|
|
41465
42959
|
return tiles;
|
|
41466
42960
|
}
|
|
41467
|
-
/**
|
|
41468
|
-
* Exaggeration that makes the full value range stand AUTO_RELIEF_FRACTION of the extent tall.
|
|
41469
|
-
*
|
|
41470
|
-
* Derived from the whole extent rather than per tile: scaling each tile to its own size would
|
|
41471
|
-
* make neighbours disagree along their shared edge and tear the surface apart.
|
|
41472
|
-
* @param extent
|
|
41473
|
-
* @param valueMin
|
|
41474
|
-
* @param valueMax
|
|
41475
|
-
*/
|
|
41476
|
-
function autoExaggeration(extent, valueMin, valueMax) {
|
|
41477
|
-
const range = Math.abs(valueMax - valueMin);
|
|
41478
|
-
if (!(range > 0)) {
|
|
41479
|
-
return 1;
|
|
41480
|
-
}
|
|
41481
|
-
const southWest = Cartesian3.fromDegrees(extent.West, extent.South, 0);
|
|
41482
|
-
const southEast = Cartesian3.fromDegrees(extent.East, extent.South, 0);
|
|
41483
|
-
const northWest = Cartesian3.fromDegrees(extent.West, extent.North, 0);
|
|
41484
|
-
const shorterSide = Math.min(Cartesian3.distance(southWest, southEast), Cartesian3.distance(southWest, northWest));
|
|
41485
|
-
if (!(shorterSide > 0)) {
|
|
41486
|
-
return 1;
|
|
41487
|
-
}
|
|
41488
|
-
return (shorterSide * AUTO_RELIEF_FRACTION) / range;
|
|
41489
|
-
}
|
|
41490
|
-
DisplacedSurfacePrimitive.autoExaggeration = autoExaggeration;
|
|
41491
|
-
function toCesiumColor(color) {
|
|
41492
|
-
return new Color(color.red / 255, color.green / 255, color.blue / 255, color.alpha);
|
|
41493
|
-
}
|
|
41494
42961
|
function now() {
|
|
41495
42962
|
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
41496
42963
|
}
|
|
@@ -41498,11 +42965,12 @@ void main() {
|
|
|
41498
42965
|
|
|
41499
42966
|
const TEXTURE_FRAME_SERIES_ANIMATOR_KEY = "TextureFrameSeriesAnimator.Animator";
|
|
41500
42967
|
const EXTRUSION_ANIMATOR_KEY = "TextureFrameSeriesAnimator.ExtrusionAnimator";
|
|
42968
|
+
const TEXTURE_VALUE_LABELS_KEY = "TextureValueLabels.Overlay";
|
|
41501
42969
|
const DISPLACED_SURFACE_KEY = "DisplacedSurfacePrimitive.Surface";
|
|
41502
42970
|
var EntityRenderEnginePolygon;
|
|
41503
42971
|
(function (EntityRenderEnginePolygon) {
|
|
41504
42972
|
async function Render(params) {
|
|
41505
|
-
var _a, _b, _c, _d;
|
|
42973
|
+
var _a, _b, _c, _d, _e;
|
|
41506
42974
|
const entity = params.entity;
|
|
41507
42975
|
const style = params.style;
|
|
41508
42976
|
const pRings = Entity$1.GetValue({
|
|
@@ -41553,18 +43021,22 @@ var EntityRenderEnginePolygon;
|
|
|
41553
43021
|
console.error(`Polygon.Render: failed to resolve extrusion texture for entity ${((_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.ID) || "<NONE>"}:`, e);
|
|
41554
43022
|
}
|
|
41555
43023
|
}
|
|
43024
|
+
const retained = params.offline
|
|
43025
|
+
? (_c = params.rendered) === null || _c === void 0 ? void 0 : _c[TEXTURE_FRAME_SERIES_ANIMATOR_KEY]
|
|
43026
|
+
: null;
|
|
43027
|
+
const keepsAnimator = Boolean(retained && !retained.IsDisposed());
|
|
41556
43028
|
const drawsDisplacedSurface = Boolean(extrusionArchive);
|
|
41557
43029
|
// The displaced surface IS the entity's fill, drawn as its own primitive at the value's height.
|
|
41558
43030
|
// Leaving the flat polygon filled as well shows a second copy of the parcel on the ground under it.
|
|
41559
43031
|
const hasFill = (drawsDisplacedSurface ? true
|
|
41560
43032
|
: fillType === Style.EPolygonFillType.Texture
|
|
41561
|
-
? Boolean(textureDataUri || frameArchive || cFillColor.alpha > 0)
|
|
43033
|
+
? Boolean(textureDataUri || frameArchive || keepsAnimator || cFillColor.alpha > 0)
|
|
41562
43034
|
: cFillColor.alpha > 0);
|
|
41563
43035
|
const fillMaterial = drawsDisplacedSurface
|
|
41564
43036
|
? Color.WHITE.withAlpha(0)
|
|
41565
43037
|
: textureDataUri
|
|
41566
43038
|
? new ImageMaterialProperty({ image: textureDataUri, transparent: true })
|
|
41567
|
-
: frameArchive
|
|
43039
|
+
: (frameArchive || keepsAnimator)
|
|
41568
43040
|
? Color.WHITE.withAlpha(0)
|
|
41569
43041
|
: cFillColor;
|
|
41570
43042
|
const lineColorTrace = style.lineColor ? Calculator.TraceGetColor(style.lineColor, entity, params.tags) : { value: null, effective: null };
|
|
@@ -41734,7 +43206,7 @@ var EntityRenderEnginePolygon;
|
|
|
41734
43206
|
}
|
|
41735
43207
|
// Must run before any material is baked below: the Animator takes over polygon.material, so a bake
|
|
41736
43208
|
// has to know whether it is baking over a live animation or over a disposed one's restored material.
|
|
41737
|
-
const animator = syncTextureFrameArchive(cEntity, drawsDisplacedSurface ? null : frameArchive, params.viewer, style.textureColorMask, style.maskTextureBaseline);
|
|
43209
|
+
const animator = syncTextureFrameArchive(cEntity, drawsDisplacedSurface ? null : frameArchive, params.viewer, style.textureColorMask, style.maskTextureBaseline, undefined, undefined, wantsLabels(style), params.offline, polygonBbox(posses));
|
|
41738
43210
|
syncDisplacedSurface({
|
|
41739
43211
|
cEntity,
|
|
41740
43212
|
extrusionArchive,
|
|
@@ -41742,7 +43214,30 @@ var EntityRenderEnginePolygon;
|
|
|
41742
43214
|
style,
|
|
41743
43215
|
entity,
|
|
41744
43216
|
heightRef,
|
|
41745
|
-
outerRingPosses: posses
|
|
43217
|
+
outerRingPosses: posses,
|
|
43218
|
+
offline: params.offline
|
|
43219
|
+
});
|
|
43220
|
+
const drapeExtent = polygonBbox(posses);
|
|
43221
|
+
const labelArchive = drawsDisplacedSurface ? extrusionArchive : frameArchive;
|
|
43222
|
+
syncTextureValueLabels({
|
|
43223
|
+
cEntity,
|
|
43224
|
+
viewer: params.viewer,
|
|
43225
|
+
settings: overlaySettings(style, entity, params.tags),
|
|
43226
|
+
extent: labelArchive
|
|
43227
|
+
? (drawsDisplacedSurface ? archiveExtent(labelArchive.metadata, posses) : drapeExtent)
|
|
43228
|
+
: null,
|
|
43229
|
+
animator: drawsDisplacedSurface
|
|
43230
|
+
? cEntity[EXTRUSION_ANIMATOR_KEY]
|
|
43231
|
+
: animator,
|
|
43232
|
+
metadata: labelArchive ? labelArchive.metadata : undefined,
|
|
43233
|
+
offline: params.offline,
|
|
43234
|
+
clipRing: posses.map((pos) => {
|
|
43235
|
+
const carto = Cartographic.fromCartesian(pos);
|
|
43236
|
+
return {
|
|
43237
|
+
lon: Math$1.toDegrees(carto.longitude),
|
|
43238
|
+
lat: Math$1.toDegrees(carto.latitude)
|
|
43239
|
+
};
|
|
43240
|
+
})
|
|
41746
43241
|
});
|
|
41747
43242
|
if (animator) {
|
|
41748
43243
|
CesiumEntityStyler.SetDefaultTextureImage({
|
|
@@ -41803,7 +43298,7 @@ var EntityRenderEnginePolygon;
|
|
|
41803
43298
|
}
|
|
41804
43299
|
}
|
|
41805
43300
|
let borderPosses = posses.map(x => x.clone ? x.clone() : { ...x });
|
|
41806
|
-
let cEntityBorder = (
|
|
43301
|
+
let cEntityBorder = (_e = (_d = params.rendered) === null || _d === void 0 ? void 0 : _d._siblingGraphics) === null || _e === void 0 ? void 0 : _e[0];
|
|
41807
43302
|
cEntity._siblingGraphics = [];
|
|
41808
43303
|
if (!cEntityBorder || ((!cEntityBorder.polyline && units == "px") ||
|
|
41809
43304
|
(!cEntityBorder.corridor && units == "m"))) {
|
|
@@ -42054,6 +43549,11 @@ var EntityRenderEnginePolygon;
|
|
|
42054
43549
|
* Disposes a cEntity's TextureFrameSeriesAnimator.Animator (if any).
|
|
42055
43550
|
*/
|
|
42056
43551
|
function DisposeTextureFrameSeriesAnimator(cEntity, viewer) {
|
|
43552
|
+
const labelOverlay = cEntity === null || cEntity === void 0 ? void 0 : cEntity[TEXTURE_VALUE_LABELS_KEY];
|
|
43553
|
+
if (labelOverlay) {
|
|
43554
|
+
labelOverlay.remove();
|
|
43555
|
+
cEntity[TEXTURE_VALUE_LABELS_KEY] = null;
|
|
43556
|
+
}
|
|
42057
43557
|
const existing = cEntity === null || cEntity === void 0 ? void 0 : cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
|
|
42058
43558
|
if (existing && !existing.IsDisposed()) {
|
|
42059
43559
|
existing.Dispose();
|
|
@@ -42179,10 +43679,13 @@ const _textureCache = new LRUCache(50);
|
|
|
42179
43679
|
* @param params
|
|
42180
43680
|
*/
|
|
42181
43681
|
function syncDisplacedSurface(params) {
|
|
42182
|
-
var _a;
|
|
42183
43682
|
const { cEntity, extrusionArchive, viewer, style, entity, heightRef, outerRingPosses } = params;
|
|
42184
43683
|
const existingAnimator = cEntity[EXTRUSION_ANIMATOR_KEY];
|
|
42185
43684
|
const existingSurface = cEntity[DISPLACED_SURFACE_KEY];
|
|
43685
|
+
// Same reason as the fill animator: an unresolvable archive is not a removed one.
|
|
43686
|
+
if (params.offline && existingSurface && !existingSurface.isDestroyed()) {
|
|
43687
|
+
return;
|
|
43688
|
+
}
|
|
42186
43689
|
// Nothing to draw and nothing left over, which is every polygon that does not use this feature.
|
|
42187
43690
|
if (!extrusionArchive && !existingAnimator && !existingSurface) {
|
|
42188
43691
|
return;
|
|
@@ -42193,12 +43696,12 @@ function syncDisplacedSurface(params) {
|
|
|
42193
43696
|
&& existingAnimator.GetArchiveUrl() === extrusionArchive.url
|
|
42194
43697
|
&& existingSurface
|
|
42195
43698
|
&& !existingSurface.isDestroyed();
|
|
42196
|
-
const placement = surfacePlacement(entity, heightRef);
|
|
43699
|
+
const placement = surfacePlacement(entity, heightRef, extrusionArchive && TextureFrameSeriesAnimator.IsDatumHeight(extrusionArchive.metadata));
|
|
42197
43700
|
// Whether the surface follows terrain is baked into its ground sampling, so a change there has
|
|
42198
43701
|
// to rebuild rather than update in place.
|
|
42199
43702
|
if (reusable && existingSurface.GetFollowsGround() === placement.followGround) {
|
|
42200
43703
|
existingSurface.SetBaseHeight(placement.baseHeight);
|
|
42201
|
-
existingSurface.SetExaggeration((
|
|
43704
|
+
existingSurface.SetExaggeration(exaggerationFor(style));
|
|
42202
43705
|
return;
|
|
42203
43706
|
}
|
|
42204
43707
|
disposeDisplacedSurface(cEntity, viewer);
|
|
@@ -42220,8 +43723,8 @@ function syncDisplacedSurface(params) {
|
|
|
42220
43723
|
textureColorMask: style.textureColorMask,
|
|
42221
43724
|
driveMaterial: false,
|
|
42222
43725
|
produceValueCanvas: true,
|
|
42223
|
-
|
|
42224
|
-
|
|
43726
|
+
metadata: extrusionArchive.metadata,
|
|
43727
|
+
cellTexels: 1,
|
|
42225
43728
|
maskBaseline: style.maskTextureBaseline
|
|
42226
43729
|
});
|
|
42227
43730
|
cEntity[EXTRUSION_ANIMATOR_KEY] = animator;
|
|
@@ -42236,9 +43739,16 @@ function syncDisplacedSurface(params) {
|
|
|
42236
43739
|
terrainProvider: viewer && viewer.terrainProvider,
|
|
42237
43740
|
valueMin: extrusionArchive.metadata.ValueMin,
|
|
42238
43741
|
valueMax: extrusionArchive.metadata.ValueMax,
|
|
42239
|
-
|
|
43742
|
+
packedValue: TextureFrameSeriesAnimator.IsPackedValue(extrusionArchive.metadata),
|
|
43743
|
+
exaggeration: exaggerationFor(style),
|
|
42240
43744
|
lowColor: mask ? Color$1.ColorFromStr(mask.minColor) : null,
|
|
42241
|
-
highColor: mask ? Color$1.ColorFromStr(mask.maxColor) : null
|
|
43745
|
+
highColor: mask ? Color$1.ColorFromStr(mask.maxColor) : null,
|
|
43746
|
+
// Normalised the same way the animator normalises them, so the surface and the flat drape
|
|
43747
|
+
// agree about which values the style hides.
|
|
43748
|
+
rampStops: ((mask && mask.points) || []).map((stop) => ({
|
|
43749
|
+
position: TextureFrameSeriesAnimator.NormalisePosition(extrusionArchive.metadata, stop.position),
|
|
43750
|
+
color: Color$1.ColorFromStr(stop.color)
|
|
43751
|
+
})).filter((stop) => Boolean(stop.color))
|
|
42242
43752
|
});
|
|
42243
43753
|
surface.BindAnimator(animator);
|
|
42244
43754
|
viewer.scene.primitives.add(surface);
|
|
@@ -42251,7 +43761,7 @@ function syncDisplacedSurface(params) {
|
|
|
42251
43761
|
* @param entity
|
|
42252
43762
|
* @param heightRef
|
|
42253
43763
|
*/
|
|
42254
|
-
function surfacePlacement(entity, heightRef) {
|
|
43764
|
+
function surfacePlacement(entity, heightRef, datumHeight) {
|
|
42255
43765
|
const rawAltitude = Entity$1.GetValue({
|
|
42256
43766
|
entity,
|
|
42257
43767
|
path: ["Bruce", "Location", "altitude"]
|
|
@@ -42263,14 +43773,34 @@ function surfacePlacement(entity, heightRef) {
|
|
|
42263
43773
|
if (heightRef === HeightReference.RELATIVE_TO_GROUND) {
|
|
42264
43774
|
return { baseHeight: altitude, followGround: true };
|
|
42265
43775
|
}
|
|
42266
|
-
|
|
43776
|
+
// Values that ARE heights against a datum already say where they belong, so adding the entity's
|
|
43777
|
+
// altitude on top would move a measured surface off the datum it was measured against.
|
|
43778
|
+
return { baseHeight: datumHeight ? 0 : altitude, followGround: false };
|
|
42267
43779
|
}
|
|
42268
|
-
|
|
42269
|
-
* The
|
|
42270
|
-
* generated before the extent was written still render somewhere sensible.
|
|
42271
|
-
* @param metadata
|
|
42272
|
-
* @param posses
|
|
43780
|
+
/*
|
|
43781
|
+
* The factor a displaced surface should use, which is the style's or nothing.
|
|
42273
43782
|
*/
|
|
43783
|
+
function exaggerationFor(style) {
|
|
43784
|
+
if (style.extrusionExaggeration != null) {
|
|
43785
|
+
return style.extrusionExaggeration;
|
|
43786
|
+
}
|
|
43787
|
+
return 1;
|
|
43788
|
+
}
|
|
43789
|
+
/*
|
|
43790
|
+
* The lon/lat bounding rectangle of a ring, which is the frame Cesium drapes an image material in.
|
|
43791
|
+
*/
|
|
43792
|
+
function polygonBbox(posses) {
|
|
43793
|
+
if (!posses || posses.length === 0) {
|
|
43794
|
+
return undefined;
|
|
43795
|
+
}
|
|
43796
|
+
const rectangle = Rectangle.fromCartesianArray(posses);
|
|
43797
|
+
return {
|
|
43798
|
+
West: Math$1.toDegrees(rectangle.west),
|
|
43799
|
+
East: Math$1.toDegrees(rectangle.east),
|
|
43800
|
+
South: Math$1.toDegrees(rectangle.south),
|
|
43801
|
+
North: Math$1.toDegrees(rectangle.north)
|
|
43802
|
+
};
|
|
43803
|
+
}
|
|
42274
43804
|
function archiveExtent(metadata, posses) {
|
|
42275
43805
|
if (metadata.West != null && metadata.East != null && metadata.South != null && metadata.North != null) {
|
|
42276
43806
|
return { West: metadata.West, East: metadata.East, South: metadata.South, North: metadata.North };
|
|
@@ -42321,9 +43851,22 @@ function disposeDisplacedSurface(cEntity, viewer) {
|
|
|
42321
43851
|
* @param viewer
|
|
42322
43852
|
* @param textureColorMask
|
|
42323
43853
|
*/
|
|
42324
|
-
function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask, maskTextureBaseline) {
|
|
43854
|
+
function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask, maskTextureBaseline, cellBorder, cellTexels, produceValueCanvas, offline, drapeExtent) {
|
|
42325
43855
|
const existing = cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
|
|
42326
|
-
|
|
43856
|
+
// Offline says nothing about whether the archive changed, only that it could not be asked.
|
|
43857
|
+
if (offline && existing && !existing.IsDisposed()) {
|
|
43858
|
+
return existing;
|
|
43859
|
+
}
|
|
43860
|
+
// Appearance is part of the reuse test, not just the archive URL. Recolouring a texture-driven
|
|
43861
|
+
// polygon keeps the same archive, so matching on the URL alone kept the old Animator and the
|
|
43862
|
+
// style edit appeared to do nothing at all.
|
|
43863
|
+
const appearance = TextureFrameSeriesAnimator.AppearanceSignature({
|
|
43864
|
+
textureColorMask, cellBorder, cellTexels, maskBaseline: maskTextureBaseline,
|
|
43865
|
+
valueCanvas: produceValueCanvas, drape: drapeExtent
|
|
43866
|
+
});
|
|
43867
|
+
if (frameArchive && existing && !existing.IsDisposed()
|
|
43868
|
+
&& existing.GetArchiveUrl() === frameArchive.url
|
|
43869
|
+
&& existing.GetAppearanceSignature() === appearance) {
|
|
42327
43870
|
return existing;
|
|
42328
43871
|
}
|
|
42329
43872
|
if (existing && !existing.IsDisposed()) {
|
|
@@ -42339,12 +43882,115 @@ function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask
|
|
|
42339
43882
|
archiveUrl: frameArchive.url,
|
|
42340
43883
|
frames: frameArchive.metadata.Frames,
|
|
42341
43884
|
textureColorMask,
|
|
42342
|
-
|
|
42343
|
-
|
|
43885
|
+
metadata: frameArchive.metadata,
|
|
43886
|
+
cellBorder,
|
|
43887
|
+
cellTexels,
|
|
43888
|
+
maskBaseline: maskTextureBaseline,
|
|
43889
|
+
produceValueCanvas,
|
|
43890
|
+
drapeExtent
|
|
42344
43891
|
});
|
|
42345
43892
|
cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = animator;
|
|
42346
43893
|
return animator;
|
|
42347
43894
|
}
|
|
43895
|
+
/**
|
|
43896
|
+
* Folds the cell-border and value-label style into the single overlay that draws both.
|
|
43897
|
+
*
|
|
43898
|
+
* Returns undefined when neither is asked for, which is what keeps the overlay off entirely.
|
|
43899
|
+
*/
|
|
43900
|
+
function wantsLabels(style) {
|
|
43901
|
+
const texture = style.texture;
|
|
43902
|
+
return Boolean(texture && !Array.isArray(texture) && texture.label);
|
|
43903
|
+
}
|
|
43904
|
+
/*
|
|
43905
|
+
* How the cell grid and its values are drawn, which the style does not get a say in.
|
|
43906
|
+
*
|
|
43907
|
+
* A white hairline reads over every ramp colour without competing with it, and the spacing and cap
|
|
43908
|
+
* are what a parcel-sized archive needs to stay readable while the camera moves. Exposing them
|
|
43909
|
+
* bought nothing except thresholds an author could get wrong.
|
|
43910
|
+
*/
|
|
43911
|
+
const LABEL_GRID_COLOR = "rgba(255,255,255,0.55)";
|
|
43912
|
+
const LABEL_GRID_WIDTH_PIXELS = 1;
|
|
43913
|
+
const LABEL_MIN_SPACING_PIXELS = 78;
|
|
43914
|
+
const LABEL_MAX_LABELS = 240;
|
|
43915
|
+
function overlaySettings(style, entity, tags) {
|
|
43916
|
+
if (!wantsLabels(style)) {
|
|
43917
|
+
return undefined;
|
|
43918
|
+
}
|
|
43919
|
+
return {
|
|
43920
|
+
gridColor: LABEL_GRID_COLOR,
|
|
43921
|
+
gridWidthPixels: LABEL_GRID_WIDTH_PIXELS,
|
|
43922
|
+
minSpacingPixels: LABEL_MIN_SPACING_PIXELS,
|
|
43923
|
+
maxLabels: LABEL_MAX_LABELS
|
|
43924
|
+
};
|
|
43925
|
+
}
|
|
43926
|
+
/**
|
|
43927
|
+
* Keeps a value-label overlay in step with whichever animator is driving the polygon.
|
|
43928
|
+
*
|
|
43929
|
+
* Redrawn on preRender rather than on frame changes: labels are screen-space, so panning and zooming
|
|
43930
|
+
* move them even when the data has not advanced at all.
|
|
43931
|
+
*/
|
|
43932
|
+
function syncTextureValueLabels(params) {
|
|
43933
|
+
const { cEntity, viewer, settings, extent, animator, metadata } = params;
|
|
43934
|
+
const existing = cEntity[TEXTURE_VALUE_LABELS_KEY];
|
|
43935
|
+
// Offline has no extent to rebuild the overlay from, so an existing one is left running.
|
|
43936
|
+
if (params.offline && existing) {
|
|
43937
|
+
return;
|
|
43938
|
+
}
|
|
43939
|
+
if (existing) {
|
|
43940
|
+
existing.remove();
|
|
43941
|
+
cEntity[TEXTURE_VALUE_LABELS_KEY] = null;
|
|
43942
|
+
}
|
|
43943
|
+
if (!settings || !extent || !animator) {
|
|
43944
|
+
return;
|
|
43945
|
+
}
|
|
43946
|
+
const labels = new TextureValueLabels.Labels({
|
|
43947
|
+
viewer, extent, settings, metadata,
|
|
43948
|
+
tiles: TextureFrameSeriesAnimator.TilesOf(metadata).map((tile) => ({
|
|
43949
|
+
West: tile.West, East: tile.East, South: tile.South, North: tile.North,
|
|
43950
|
+
ResolutionX: tile.ResolutionX, ResolutionY: tile.ResolutionY
|
|
43951
|
+
})),
|
|
43952
|
+
clipRing: params.clipRing
|
|
43953
|
+
});
|
|
43954
|
+
let lastVersion = -1;
|
|
43955
|
+
let appliedFloor = null;
|
|
43956
|
+
let appliedCeiling = null;
|
|
43957
|
+
// The extremes are what turn an anomaly frame back into a reading, so they are worth two extra
|
|
43958
|
+
// Range GETs here even though nothing else in the render path wants them.
|
|
43959
|
+
animator.EnsureExtremes();
|
|
43960
|
+
const remove = viewer.scene.preRender.addEventListener(() => {
|
|
43961
|
+
if (animator.IsDisposed()) {
|
|
43962
|
+
return;
|
|
43963
|
+
}
|
|
43964
|
+
const canvas = animator.GetValueCanvas();
|
|
43965
|
+
if (!canvas) {
|
|
43966
|
+
return;
|
|
43967
|
+
}
|
|
43968
|
+
const version = animator.GetPresentVersion();
|
|
43969
|
+
if (version !== lastVersion) {
|
|
43970
|
+
lastVersion = version;
|
|
43971
|
+
const ctx = canvas.getContext("2d");
|
|
43972
|
+
labels.SetSource(ctx.getImageData(0, 0, canvas.width, canvas.height));
|
|
43973
|
+
labels.SetPainted(animator.GetDisplayedPixels());
|
|
43974
|
+
}
|
|
43975
|
+
// Tracked by reference rather than latched on the first one to arrive: a tiled archive
|
|
43976
|
+
// composites the floor and the ceiling separately, so latching once would leave every label
|
|
43977
|
+
// showing a minimum and no maximum.
|
|
43978
|
+
const extremes = animator.GetExtremes();
|
|
43979
|
+
if (extremes.floor !== appliedFloor || extremes.ceiling !== appliedCeiling) {
|
|
43980
|
+
appliedFloor = extremes.floor;
|
|
43981
|
+
appliedCeiling = extremes.ceiling;
|
|
43982
|
+
labels.SetExtremes(appliedFloor, appliedCeiling);
|
|
43983
|
+
}
|
|
43984
|
+
labels.Render();
|
|
43985
|
+
});
|
|
43986
|
+
cEntity[TEXTURE_VALUE_LABELS_KEY] = {
|
|
43987
|
+
labels,
|
|
43988
|
+
remove: () => {
|
|
43989
|
+
remove();
|
|
43990
|
+
labels.Dispose();
|
|
43991
|
+
}
|
|
43992
|
+
};
|
|
43993
|
+
}
|
|
42348
43994
|
/**
|
|
42349
43995
|
* Derives a min/max/label time-range segment from a resolved frame archive's Frames metadata,
|
|
42350
43996
|
* for reporting up to whoever owns the menu item this entity belongs to (see IParams.onSeriesDiscovered).
|
|
@@ -42436,10 +44082,14 @@ async function resolveTextureValue(params) {
|
|
|
42436
44082
|
url = clientFile.URL;
|
|
42437
44083
|
const clientFileData = clientFile.Data;
|
|
42438
44084
|
if (TextureFrameSeriesAnimator.IsFrameArchiveMetadata(clientFileData)) {
|
|
44085
|
+
const adapted = TextureFrameSeriesAnimator.Adapt(clientFileData.generation);
|
|
44086
|
+
if (!adapted) {
|
|
44087
|
+
return { dataUri: null, effective: textureTrace.effective };
|
|
44088
|
+
}
|
|
42439
44089
|
return {
|
|
42440
44090
|
dataUri: null,
|
|
42441
44091
|
effective: textureTrace.effective,
|
|
42442
|
-
frameArchive: { url, metadata:
|
|
44092
|
+
frameArchive: { url, metadata: adapted }
|
|
42443
44093
|
};
|
|
42444
44094
|
}
|
|
42445
44095
|
}
|
|
@@ -42468,7 +44118,11 @@ async function resolveTextureCondition(params) {
|
|
|
42468
44118
|
const url = clientFile.URL;
|
|
42469
44119
|
const clientFileData = clientFile.Data;
|
|
42470
44120
|
if (TextureFrameSeriesAnimator.IsFrameArchiveMetadata(clientFileData)) {
|
|
42471
|
-
|
|
44121
|
+
const adapted = TextureFrameSeriesAnimator.Adapt(clientFileData.generation);
|
|
44122
|
+
if (adapted) {
|
|
44123
|
+
return { dataUri: null, effective: null, frameArchive: { url, metadata: adapted } };
|
|
44124
|
+
}
|
|
44125
|
+
return { dataUri: null, effective: null };
|
|
42472
44126
|
}
|
|
42473
44127
|
const cacheKey = "texture-" + url + "-none";
|
|
42474
44128
|
let prom = _textureCache.Get(cacheKey);
|
|
@@ -43952,7 +45606,7 @@ var StyleUtils;
|
|
|
43952
45606
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
43953
45607
|
})(StyleUtils || (StyleUtils = {}));
|
|
43954
45608
|
|
|
43955
|
-
const VERSION = "7.2.
|
|
45609
|
+
const VERSION = "7.2.3";
|
|
43956
45610
|
/**
|
|
43957
45611
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
43958
45612
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|
|
@@ -43969,5 +45623,5 @@ const getENVIRONMENT = () => {
|
|
|
43969
45623
|
return ENVIRONMENT;
|
|
43970
45624
|
};
|
|
43971
45625
|
|
|
43972
|
-
export { VERSION, setENVIRONMENT, getENVIRONMENT, CesiumAnimatedInOut, DisplacedSurfacePrimitive, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, EntityRenderEnginePolygon, EntityRenderEnginePolyline, MenuItemCreator, MenuItemManager, CesiumParabola, EntityLabel, LiveCursor, SharedGetters, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, DEFAULT_LIVE_PADDING_SECONDS, DEFAULT_BRIGHTNESS_SHIFT, DEFAULT_LIGHT_INTENSITY, LIGHTING_SETTINGS_VERSION, LIGHT_INTENSITY_COMPENSATION, MAX_COMPENSATED_LIGHT_INTENSITY, ViewUtils, CesiumViewMonitor, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
|
|
45626
|
+
export { VERSION, setENVIRONMENT, getENVIRONMENT, CesiumAnimatedInOut, DisplacedSurfacePrimitive, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, EntityRenderEnginePolygon, EntityRenderEnginePolyline, TextureFrameSeriesAnimator, TextureValueLabels, MenuItemCreator, MenuItemManager, CesiumParabola, EntityLabel, LiveCursor, SharedGetters, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, DEFAULT_LIVE_PADDING_SECONDS, DEFAULT_BRIGHTNESS_SHIFT, DEFAULT_LIGHT_INTENSITY, LIGHTING_SETTINGS_VERSION, LIGHT_INTENSITY_COMPENSATION, MAX_COMPENSATED_LIGHT_INTENSITY, ViewUtils, CesiumViewMonitor, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
|
|
43973
45627
|
//# sourceMappingURL=bruce-cesium.es5.js.map
|