bruce-cesium 7.5.8 → 7.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bruce-cesium.es5.js +938 -274
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +936 -272
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/rendering/displaced-surface-primitive.js +187 -36
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +20 -9
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js +2 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +220 -20
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +478 -194
- package/dist/lib/rendering/texture-value-labels.js.map +1 -1
- package/dist/lib/rendering/visuals-register.js +27 -9
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/rendering/displaced-surface-primitive.d.ts +32 -0
- package/dist/types/rendering/entity-render-engine-polygon.d.ts +1 -0
- package/dist/types/rendering/texture-frame-series-animator.d.ts +42 -0
- package/dist/types/rendering/texture-value-labels.d.ts +59 -11
- package/dist/types/rendering/visuals-register.d.ts +6 -0
- package/package.json +1 -1
package/dist/bruce-cesium.es5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Cesium from 'cesium';
|
|
2
|
-
import { Cartographic,
|
|
3
|
-
import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1,
|
|
2
|
+
import { Cartographic, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, PixelDatatype, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Entity, ConstantPositionProperty, HeightReference, Color, DistanceDisplayCondition, ColorMaterialProperty, JulianDate, Quaternion, HeadingPitchRoll, CallbackProperty, HorizontalOrigin, VerticalOrigin, ConstantProperty, ClassificationType, Primitive, Cesium3DTileFeature, ColorBlendMode, ShadowMode, Model, ArcType, CornerType, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneMode, IntersectionTests, Ray, SceneTransforms, FeatureDetection, Matrix3, NearFarScalar, KmlDataSource, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Ion, Cesium3DTileColorBlendMode, IonResource, Cesium3DTileset, Intersect, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, OrthographicFrustum, EasingFunction, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, GeometryInstance, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
|
|
3
|
+
import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Api, Calculator, ClientFile, Entity as Entity$1, EntityTag, EntityType, ObjectUtils, Style, EntityLod, UrlUtils, Carto, Geometry as Geometry$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, ClientFileValueMap, Color as Color$1, Bounds, BruceEvent, EntityCoords, DataLab, DelayQueue, AccountConcept, BruceApi, RecordChangeFeed, EntityRelation, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Ensures a number is returned from a given value.
|
|
@@ -7311,7 +7311,8 @@ var EntityLabel;
|
|
|
7311
7311
|
this.groupId = groupId;
|
|
7312
7312
|
this.additionalTexts = this.options.text != null ? [] : null;
|
|
7313
7313
|
this.photo = null;
|
|
7314
|
-
|
|
7314
|
+
// A label given its position keeps it, or the rebuild goes looking for the entity's own.
|
|
7315
|
+
this.createLabel(this.prePositioned ? this._pos3d : undefined);
|
|
7315
7316
|
}
|
|
7316
7317
|
/**
|
|
7317
7318
|
* Creates cesium entity for the label if one does not exist.
|
|
@@ -13811,7 +13812,9 @@ var VisualsRegister;
|
|
|
13811
13812
|
entityId: params.entityId,
|
|
13812
13813
|
menuItemId: params.menuItemId,
|
|
13813
13814
|
position: params.position ? { ...params.position } : undefined,
|
|
13814
|
-
createIfMissing: params.createIfMissing !== false
|
|
13815
|
+
createIfMissing: params.createIfMissing !== false,
|
|
13816
|
+
text: params.text,
|
|
13817
|
+
pos3d: params.pos3d ? Cartesian3.clone(params.pos3d) : undefined
|
|
13815
13818
|
};
|
|
13816
13819
|
const doc = this.viewer.container.ownerDocument;
|
|
13817
13820
|
// Only a label following the cursor should go when the cursor leaves the canvas.
|
|
@@ -13827,6 +13830,13 @@ var VisualsRegister;
|
|
|
13827
13830
|
this.SyncLabelHover();
|
|
13828
13831
|
this.viewer.scene.requestRender();
|
|
13829
13832
|
}
|
|
13833
|
+
/*
|
|
13834
|
+
* Which Entity holds the label hover, if any, so a caller can leave another's hover alone.
|
|
13835
|
+
*/
|
|
13836
|
+
GetLabelHoverTarget() {
|
|
13837
|
+
const hover = this.labelHover;
|
|
13838
|
+
return hover ? { entityId: hover.entityId, menuItemId: hover.menuItemId } : undefined;
|
|
13839
|
+
}
|
|
13830
13840
|
/*
|
|
13831
13841
|
* Whether the label hover is on this Entity, with or without a cursor position.
|
|
13832
13842
|
*/
|
|
@@ -13839,7 +13849,7 @@ var VisualsRegister;
|
|
|
13839
13849
|
return (hover === null || hover === void 0 ? void 0 : hover.entityId) === entityId && (hover.menuItemId == null || hover.menuItemId === menuItemId) ? hover.position : undefined;
|
|
13840
13850
|
}
|
|
13841
13851
|
SyncLabelHover() {
|
|
13842
|
-
var _a, _b, _c;
|
|
13852
|
+
var _a, _b, _c, _d;
|
|
13843
13853
|
const hover = this.labelHover;
|
|
13844
13854
|
if (!hover || this.disposed) {
|
|
13845
13855
|
return;
|
|
@@ -13871,12 +13881,24 @@ var VisualsRegister;
|
|
|
13871
13881
|
this.ReleaseHoverLabel(true);
|
|
13872
13882
|
return;
|
|
13873
13883
|
}
|
|
13884
|
+
const hoverOptions = {
|
|
13885
|
+
presentation: "text",
|
|
13886
|
+
text: (_b = hover.text) !== null && _b !== void 0 ? _b : group === null || group === void 0 ? void 0 : group.options.text,
|
|
13887
|
+
selectOnClick: false
|
|
13888
|
+
};
|
|
13874
13889
|
if (this.hoverLabel && !this.hoverLabel.Disposed) {
|
|
13890
|
+
// Rebuilds only when the text changed, as it does when the pointer crosses a cell.
|
|
13891
|
+
if (hover.pos3d && !Cartesian3.equals(hover.pos3d, this.hoverLabel.pos3d)) {
|
|
13892
|
+
this.hoverLabel.pos3d = hover.pos3d;
|
|
13893
|
+
}
|
|
13894
|
+
if (hover.text != null) {
|
|
13895
|
+
this.hoverLabel.Configure(hoverOptions);
|
|
13896
|
+
}
|
|
13875
13897
|
this.hoverLabel.SetHoverVisible(true);
|
|
13876
13898
|
return;
|
|
13877
13899
|
}
|
|
13878
13900
|
// Query-only icon groups have coordinates but no registered scene visual to attach to.
|
|
13879
|
-
if (!rego && group && [group.item.longitude, group.item.latitude, (
|
|
13901
|
+
if (!rego && group && [group.item.longitude, group.item.latitude, (_c = group.item.altitude) !== null && _c !== void 0 ? _c : 0].every(Number.isFinite)) {
|
|
13880
13902
|
rego = {
|
|
13881
13903
|
entityId: hover.entityId,
|
|
13882
13904
|
menuItemId: hover.menuItemId,
|
|
@@ -13898,15 +13920,12 @@ var VisualsRegister;
|
|
|
13898
13920
|
rego,
|
|
13899
13921
|
viewer: this.viewer,
|
|
13900
13922
|
visualRegister: this,
|
|
13901
|
-
api: (
|
|
13923
|
+
api: (_d = this.ApiGetters) === null || _d === void 0 ? void 0 : _d.GetBruceApi(),
|
|
13902
13924
|
persistent: false,
|
|
13903
13925
|
transient: true,
|
|
13904
13926
|
hoverRingState: this.hoverRingState,
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
text: group === null || group === void 0 ? void 0 : group.options.text,
|
|
13908
|
-
selectOnClick: false
|
|
13909
|
-
},
|
|
13927
|
+
pos3d: hover.pos3d,
|
|
13928
|
+
options: hoverOptions,
|
|
13910
13929
|
visible: () => {
|
|
13911
13930
|
var _a;
|
|
13912
13931
|
const current = this.GetState(hover);
|
|
@@ -46823,6 +46842,10 @@ var TextureFrameSeriesAnimator;
|
|
|
46823
46842
|
TextureFrameSeriesAnimator.AppearanceSignature = AppearanceSignature;
|
|
46824
46843
|
const MAX_COMPOSITE_TEXELS = 2048;
|
|
46825
46844
|
const VALUE_DILATE_PASSES = 2;
|
|
46845
|
+
// Decoded frame bytes kept at once. A 2048 square composite is 16 MB, so a long series cannot all stay.
|
|
46846
|
+
const MAX_CACHED_FRAME_BYTES = 256 * 1024 * 1024;
|
|
46847
|
+
// Fetched bytes kept for decoding again, a small fraction of what they decode to.
|
|
46848
|
+
const MAX_RAW_FRAME_BYTES = 96 * 1024 * 1024;
|
|
46826
46849
|
/*
|
|
46827
46850
|
* Rewrites a packed 16 bit value as the 8 bit grey the colour ramps index on.
|
|
46828
46851
|
*
|
|
@@ -46843,19 +46866,33 @@ var TextureFrameSeriesAnimator;
|
|
|
46843
46866
|
/*
|
|
46844
46867
|
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
46845
46868
|
*/
|
|
46846
|
-
function
|
|
46869
|
+
function DilateValues(value, width, height, passes, packed) {
|
|
46847
46870
|
const texels = width * height;
|
|
46848
|
-
|
|
46871
|
+
// 255 for a texel that had data to begin with, otherwise the 1 based pass that filled it, so a pass
|
|
46872
|
+
// reads only texels filled before it and can work in place.
|
|
46873
|
+
const stamp = new Uint8Array(texels);
|
|
46849
46874
|
for (let p = 0; p < texels; p++) {
|
|
46850
|
-
|
|
46875
|
+
stamp[p] = value[p * 4 + 3] !== 0 ? 255 : 0;
|
|
46851
46876
|
}
|
|
46877
|
+
// Whether a texel or a horizontal neighbour had data, so empty space is skipped in three reads.
|
|
46878
|
+
const across = new Uint8Array(texels);
|
|
46852
46879
|
for (let pass = 0; pass < passes; pass++) {
|
|
46853
|
-
const
|
|
46854
|
-
const
|
|
46880
|
+
const current = pass + 1;
|
|
46881
|
+
const had = (p) => stamp[p] !== 0 && stamp[p] !== current;
|
|
46882
|
+
for (let y = 0; y < height; y++) {
|
|
46883
|
+
const row = y * width;
|
|
46884
|
+
for (let x = 0; x < width; x++) {
|
|
46885
|
+
const p = row + x;
|
|
46886
|
+
across[p] = (had(p) || (x > 0 && had(p - 1)) || (x + 1 < width && had(p + 1))) ? 1 : 0;
|
|
46887
|
+
}
|
|
46888
|
+
}
|
|
46855
46889
|
for (let y = 0; y < height; y++) {
|
|
46856
46890
|
for (let x = 0; x < width; x++) {
|
|
46857
46891
|
const p = y * width + x;
|
|
46858
|
-
if (
|
|
46892
|
+
if (stamp[p] !== 0) {
|
|
46893
|
+
continue;
|
|
46894
|
+
}
|
|
46895
|
+
if (!across[p] && !(y > 0 && across[p - width]) && !(y + 1 < height && across[p + width])) {
|
|
46859
46896
|
continue;
|
|
46860
46897
|
}
|
|
46861
46898
|
let sum = 0;
|
|
@@ -46864,13 +46901,13 @@ var TextureFrameSeriesAnimator;
|
|
|
46864
46901
|
for (let dx = -1; dx <= 1; dx++) {
|
|
46865
46902
|
const nx = x + dx;
|
|
46866
46903
|
const ny = y + dy;
|
|
46867
|
-
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !
|
|
46904
|
+
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !had(ny * width + nx)) {
|
|
46868
46905
|
continue;
|
|
46869
46906
|
}
|
|
46870
46907
|
const at = (ny * width + nx) * 4;
|
|
46871
46908
|
// Averaged as a value rather than per byte: averaging a packed low byte
|
|
46872
46909
|
// on its own wraps at every 256th step and speckles the bled edge.
|
|
46873
|
-
sum += packed ?
|
|
46910
|
+
sum += packed ? value[at] * 256 + value[at + 1] : value[at];
|
|
46874
46911
|
hits++;
|
|
46875
46912
|
}
|
|
46876
46913
|
}
|
|
@@ -46886,18 +46923,25 @@ var TextureFrameSeriesAnimator;
|
|
|
46886
46923
|
value[p * 4 + 1] = v;
|
|
46887
46924
|
value[p * 4 + 2] = v;
|
|
46888
46925
|
}
|
|
46889
|
-
|
|
46926
|
+
stamp[p] = current;
|
|
46890
46927
|
}
|
|
46891
46928
|
}
|
|
46892
46929
|
}
|
|
46893
46930
|
}
|
|
46894
46931
|
}
|
|
46932
|
+
TextureFrameSeriesAnimator.DilateValues = DilateValues;
|
|
46933
|
+
/*
|
|
46934
|
+
* Bytes one cached frame holds, counting a buffer shared between colour and value once.
|
|
46935
|
+
*/
|
|
46936
|
+
function frameBytes(pixels, value) {
|
|
46937
|
+
return (pixels ? pixels.byteLength : 0) + (value && value !== pixels ? value.byteLength : 0);
|
|
46938
|
+
}
|
|
46895
46939
|
const DEFAULT_CROSSFADE_MS = 900;
|
|
46896
46940
|
const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
|
|
46897
46941
|
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
46898
46942
|
class Animator {
|
|
46899
46943
|
constructor(options) {
|
|
46900
|
-
var _a, _b, _c;
|
|
46944
|
+
var _a, _b, _c, _d;
|
|
46901
46945
|
this.removeOnTick = null;
|
|
46902
46946
|
this.removeCrossfadeTick = null;
|
|
46903
46947
|
this.disposed = false;
|
|
@@ -46928,6 +46972,15 @@ var TextureFrameSeriesAnimator;
|
|
|
46928
46972
|
this.extremesLoad = null;
|
|
46929
46973
|
this.floorPixels = null;
|
|
46930
46974
|
this.ceilingPixels = null;
|
|
46975
|
+
// How far through the fade from the previous value frame to the presented one, 1 when settled.
|
|
46976
|
+
this.fadeMix = 1;
|
|
46977
|
+
// Where the current fade started from, above zero when it took over from one still running.
|
|
46978
|
+
this.fadeMixStart = 0;
|
|
46979
|
+
// Version the value canvas was last written at, since in value only mode it is written on demand.
|
|
46980
|
+
this.valueCanvasVersion = -1;
|
|
46981
|
+
this.rawBytes = 0;
|
|
46982
|
+
this.cachedBytes = 0;
|
|
46983
|
+
this.lastFrameBytes = 0;
|
|
46931
46984
|
if (!options.entity.polygon) {
|
|
46932
46985
|
throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
|
|
46933
46986
|
}
|
|
@@ -46975,6 +47028,9 @@ var TextureFrameSeriesAnimator;
|
|
|
46975
47028
|
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : this.tiles.some((tile) => Boolean(tile.BaselineMask));
|
|
46976
47029
|
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
46977
47030
|
this.driveMaterial = options.driveMaterial !== false;
|
|
47031
|
+
this.valueOnly = this.produceValueCanvas && !this.driveMaterial;
|
|
47032
|
+
this.rawCache = new Array(this.frames.length).fill(null);
|
|
47033
|
+
this.cacheBudget = (_d = options.cacheBytes) !== null && _d !== void 0 ? _d : MAX_CACHED_FRAME_BYTES;
|
|
46978
47034
|
this.originalMaterial = options.entity.polygon.material;
|
|
46979
47035
|
this.imageProperty = new CallbackProperty(() => this.pool[this.poolIdx], false);
|
|
46980
47036
|
if (this.driveMaterial) {
|
|
@@ -47040,7 +47096,40 @@ var TextureFrameSeriesAnimator;
|
|
|
47040
47096
|
* or null unless the instance was constructed with produceValueCanvas.
|
|
47041
47097
|
*/
|
|
47042
47098
|
GetValueCanvas() {
|
|
47043
|
-
|
|
47099
|
+
if (!this.valueDims) {
|
|
47100
|
+
return null;
|
|
47101
|
+
}
|
|
47102
|
+
if (this.valueOnly && this.valueCanvasVersion !== this.presentVersion && this.displayedValuePixels) {
|
|
47103
|
+
this.writeInto(this.valueCanvas, this.displayedValuePixels, this.valueDims);
|
|
47104
|
+
this.valueCanvasVersion = this.presentVersion;
|
|
47105
|
+
}
|
|
47106
|
+
return this.valueCanvas;
|
|
47107
|
+
}
|
|
47108
|
+
/**
|
|
47109
|
+
* The presented frame's value pixels, or null before one. Shares the cached array, so never write to it.
|
|
47110
|
+
*/
|
|
47111
|
+
GetValueRaster() {
|
|
47112
|
+
if (!this.valueDims || !this.displayedValuePixels) {
|
|
47113
|
+
return null;
|
|
47114
|
+
}
|
|
47115
|
+
return { data: this.displayedValuePixels, width: this.valueDims.width, height: this.valueDims.height };
|
|
47116
|
+
}
|
|
47117
|
+
/**
|
|
47118
|
+
* The two value frames being faded between and the mix, or null unless the material is not driven.
|
|
47119
|
+
*/
|
|
47120
|
+
GetValueFade() {
|
|
47121
|
+
if (!this.valueOnly || !this.valueDims || !this.displayedValuePixels) {
|
|
47122
|
+
return null;
|
|
47123
|
+
}
|
|
47124
|
+
const previous = this.fadeFromValuePixels;
|
|
47125
|
+
const from = previous && previous.length === this.displayedValuePixels.length ? previous : null;
|
|
47126
|
+
return {
|
|
47127
|
+
from,
|
|
47128
|
+
to: this.displayedValuePixels,
|
|
47129
|
+
width: this.valueDims.width,
|
|
47130
|
+
height: this.valueDims.height,
|
|
47131
|
+
mix: from ? this.fadeMix : 1
|
|
47132
|
+
};
|
|
47044
47133
|
}
|
|
47045
47134
|
/**
|
|
47046
47135
|
* Increments every time a frame is presented, so a consumer holding the value canvas can tell
|
|
@@ -47107,7 +47196,11 @@ var TextureFrameSeriesAnimator;
|
|
|
47107
47196
|
if (this.currentFrameIndex < 0) {
|
|
47108
47197
|
return this.frameCache[0] ? -1 : 0;
|
|
47109
47198
|
}
|
|
47110
|
-
|
|
47199
|
+
// Full once another frame will not fit, so a prefetch only displaces a frame further from the
|
|
47200
|
+
// playhead than itself and never trades the two ends of the window back and forth.
|
|
47201
|
+
const full = this.cachedBytes + this.lastFrameBytes > this.cacheBudget;
|
|
47202
|
+
const reach = full ? this.farthestCachedDistance() : n;
|
|
47203
|
+
for (let d = 0; d < Math.min(n, reach); d++) {
|
|
47111
47204
|
const forward = this.currentFrameIndex + d;
|
|
47112
47205
|
if (forward < n && !this.frameCache[forward]) {
|
|
47113
47206
|
return forward;
|
|
@@ -47119,6 +47212,68 @@ var TextureFrameSeriesAnimator;
|
|
|
47119
47212
|
}
|
|
47120
47213
|
return -1;
|
|
47121
47214
|
}
|
|
47215
|
+
farthestCachedDistance() {
|
|
47216
|
+
let farthest = 0;
|
|
47217
|
+
for (let i = 0; i < this.frameCache.length; i++) {
|
|
47218
|
+
if (this.frameCache[i]) {
|
|
47219
|
+
farthest = Math.max(farthest, Math.abs(i - this.currentFrameIndex));
|
|
47220
|
+
}
|
|
47221
|
+
}
|
|
47222
|
+
return farthest;
|
|
47223
|
+
}
|
|
47224
|
+
/*
|
|
47225
|
+
* Drops decoded frames furthest from the playhead until the cache fits its budget again.
|
|
47226
|
+
*/
|
|
47227
|
+
evictDecoded(keep) {
|
|
47228
|
+
// A tinted fade reads its target from the cache each tick, where a value fade holds the arrays.
|
|
47229
|
+
const fading = !this.valueOnly && this.fadeStartMs !== null ? this.fadeToIndex : -1;
|
|
47230
|
+
while (this.cachedBytes > this.cacheBudget) {
|
|
47231
|
+
let victim = -1;
|
|
47232
|
+
let distance = -1;
|
|
47233
|
+
for (let i = 0; i < this.frameCache.length; i++) {
|
|
47234
|
+
if (!this.frameCache[i] || i === keep || i === this.currentFrameIndex || i === fading) {
|
|
47235
|
+
continue;
|
|
47236
|
+
}
|
|
47237
|
+
const d = Math.abs(i - this.currentFrameIndex);
|
|
47238
|
+
if (d > distance) {
|
|
47239
|
+
distance = d;
|
|
47240
|
+
victim = i;
|
|
47241
|
+
}
|
|
47242
|
+
}
|
|
47243
|
+
if (victim < 0) {
|
|
47244
|
+
return;
|
|
47245
|
+
}
|
|
47246
|
+
this.cachedBytes -= frameBytes(this.frameCache[victim], this.valueCache[victim]);
|
|
47247
|
+
this.frameCache[victim] = null;
|
|
47248
|
+
this.valueCache[victim] = null;
|
|
47249
|
+
}
|
|
47250
|
+
}
|
|
47251
|
+
/*
|
|
47252
|
+
* Keeps a frame's fetched bytes unless they are large, as a tile major archive's are.
|
|
47253
|
+
*/
|
|
47254
|
+
keepRaw(idx, buffer, base) {
|
|
47255
|
+
if (buffer.byteLength > MAX_RAW_FRAME_BYTES / 16) {
|
|
47256
|
+
return;
|
|
47257
|
+
}
|
|
47258
|
+
this.rawCache[idx] = { buffer, base };
|
|
47259
|
+
this.rawBytes += buffer.byteLength;
|
|
47260
|
+
while (this.rawBytes > MAX_RAW_FRAME_BYTES) {
|
|
47261
|
+
let victim = -1;
|
|
47262
|
+
let distance = -1;
|
|
47263
|
+
for (let i = 0; i < this.rawCache.length; i++) {
|
|
47264
|
+
const d = Math.abs(i - this.currentFrameIndex);
|
|
47265
|
+
if (this.rawCache[i] && i !== idx && d > distance) {
|
|
47266
|
+
distance = d;
|
|
47267
|
+
victim = i;
|
|
47268
|
+
}
|
|
47269
|
+
}
|
|
47270
|
+
if (victim < 0) {
|
|
47271
|
+
return;
|
|
47272
|
+
}
|
|
47273
|
+
this.rawBytes -= this.rawCache[victim].buffer.byteLength;
|
|
47274
|
+
this.rawCache[victim] = null;
|
|
47275
|
+
}
|
|
47276
|
+
}
|
|
47122
47277
|
pumpFetchQueue() {
|
|
47123
47278
|
if (this.disposed || this.inFlightIndex !== -1) {
|
|
47124
47279
|
return;
|
|
@@ -47162,10 +47317,18 @@ var TextureFrameSeriesAnimator;
|
|
|
47162
47317
|
lo = Math.min(lo, at.ByteOffset);
|
|
47163
47318
|
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
47164
47319
|
}
|
|
47165
|
-
|
|
47166
|
-
const
|
|
47167
|
-
if (
|
|
47168
|
-
|
|
47320
|
+
let buffer;
|
|
47321
|
+
const raw = this.rawCache[idx];
|
|
47322
|
+
if (raw && raw.base === lo) {
|
|
47323
|
+
buffer = raw.buffer;
|
|
47324
|
+
}
|
|
47325
|
+
else {
|
|
47326
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
47327
|
+
buffer = await response.arrayBuffer();
|
|
47328
|
+
if (this.disposed) {
|
|
47329
|
+
return;
|
|
47330
|
+
}
|
|
47331
|
+
this.keepRaw(idx, buffer, lo);
|
|
47169
47332
|
}
|
|
47170
47333
|
// Awaited before decoding rather than applied later, so no frame is ever cached unmasked and
|
|
47171
47334
|
// then corrected once the mask lands.
|
|
@@ -47177,9 +47340,15 @@ var TextureFrameSeriesAnimator;
|
|
|
47177
47340
|
if (this.disposed) {
|
|
47178
47341
|
return;
|
|
47179
47342
|
}
|
|
47343
|
+
if (this.frameCache[idx]) {
|
|
47344
|
+
this.cachedBytes -= frameBytes(this.frameCache[idx], this.valueCache[idx]);
|
|
47345
|
+
}
|
|
47180
47346
|
this.frameCache[idx] = decoded.pixels;
|
|
47181
47347
|
this.frameDims[idx] = { width: decoded.width, height: decoded.height };
|
|
47182
47348
|
this.valueCache[idx] = decoded.valuePixels || null;
|
|
47349
|
+
this.lastFrameBytes = frameBytes(decoded.pixels, decoded.valuePixels || null);
|
|
47350
|
+
this.cachedBytes += this.lastFrameBytes;
|
|
47351
|
+
this.evictDecoded(idx);
|
|
47183
47352
|
if (this.shouldPresent(idx, isTarget)) {
|
|
47184
47353
|
this.beginCrossfadeTo(idx);
|
|
47185
47354
|
}
|
|
@@ -47249,6 +47418,13 @@ var TextureFrameSeriesAnimator;
|
|
|
47249
47418
|
*/
|
|
47250
47419
|
tint(imageData) {
|
|
47251
47420
|
const canvas = { width: imageData.width, height: imageData.height };
|
|
47421
|
+
if (this.valueOnly) {
|
|
47422
|
+
// One array serves as both, since nothing will ever display the colour.
|
|
47423
|
+
const values = imageData.data;
|
|
47424
|
+
this.applyBaselineMask(values, undefined, canvas.width, canvas.height);
|
|
47425
|
+
DilateValues(values, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
47426
|
+
return { pixels: values, width: canvas.width, height: canvas.height, valuePixels: values };
|
|
47427
|
+
}
|
|
47252
47428
|
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
47253
47429
|
// recovered from the tinted pixels afterwards.
|
|
47254
47430
|
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
@@ -47269,7 +47445,7 @@ var TextureFrameSeriesAnimator;
|
|
|
47269
47445
|
}
|
|
47270
47446
|
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
47271
47447
|
if (valuePixels) {
|
|
47272
|
-
|
|
47448
|
+
DilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
47273
47449
|
}
|
|
47274
47450
|
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
47275
47451
|
}
|
|
@@ -47527,6 +47703,10 @@ var TextureFrameSeriesAnimator;
|
|
|
47527
47703
|
}
|
|
47528
47704
|
}
|
|
47529
47705
|
beginCrossfadeTo(idx) {
|
|
47706
|
+
if (this.valueOnly) {
|
|
47707
|
+
this.beginValueFadeTo(idx);
|
|
47708
|
+
return;
|
|
47709
|
+
}
|
|
47530
47710
|
const to = this.frameCache[idx];
|
|
47531
47711
|
this.fadeFromValuePixels = this.displayedValuePixels === null && this.valueCache[idx]
|
|
47532
47712
|
? new Uint8ClampedArray(this.valueCache[idx].length)
|
|
@@ -47536,12 +47716,53 @@ var TextureFrameSeriesAnimator;
|
|
|
47536
47716
|
this.fadeToDims = this.frameDims[idx];
|
|
47537
47717
|
this.fadeStartMs = Now();
|
|
47538
47718
|
}
|
|
47719
|
+
/*
|
|
47720
|
+
* Presents a value frame at once and leaves the fade to the consumer, which blends on the GPU.
|
|
47721
|
+
*/
|
|
47722
|
+
beginValueFadeTo(idx) {
|
|
47723
|
+
const to = this.valueCache[idx];
|
|
47724
|
+
if (!to || to === this.displayedValuePixels) {
|
|
47725
|
+
return;
|
|
47726
|
+
}
|
|
47727
|
+
// A new target mid fade keeps whichever frame is mostly on screen and carries on from the current
|
|
47728
|
+
// mix, so frames landing in quick succession do not jump the sheet back.
|
|
47729
|
+
const midFade = this.fadeStartMs !== null && Boolean(this.fadeFromValuePixels);
|
|
47730
|
+
if (midFade && this.fadeMix < 0.5) {
|
|
47731
|
+
this.fadeMixStart = this.fadeMix;
|
|
47732
|
+
}
|
|
47733
|
+
else {
|
|
47734
|
+
this.fadeFromValuePixels = this.displayedValuePixels;
|
|
47735
|
+
this.fadeMixStart = 0;
|
|
47736
|
+
}
|
|
47737
|
+
this.displayedValuePixels = to;
|
|
47738
|
+
this.valueDims = this.frameDims[idx];
|
|
47739
|
+
this.fadeToIndex = idx;
|
|
47740
|
+
this.fadeMix = this.fadeFromValuePixels ? this.fadeMixStart : 1;
|
|
47741
|
+
this.fadeStartMs = this.fadeFromValuePixels ? Now() : null;
|
|
47742
|
+
this.presentVersion++;
|
|
47743
|
+
this.requestRender();
|
|
47744
|
+
}
|
|
47745
|
+
requestRender() {
|
|
47746
|
+
if (this.viewer && this.viewer.scene && this.viewer.scene.requestRenderMode) {
|
|
47747
|
+
this.viewer.scene.requestRender();
|
|
47748
|
+
}
|
|
47749
|
+
}
|
|
47539
47750
|
tickCrossfade() {
|
|
47540
47751
|
if (this.disposed || this.fadeStartMs === null) {
|
|
47541
47752
|
return;
|
|
47542
47753
|
}
|
|
47543
47754
|
const t = Math.min(1, (Now() - this.fadeStartMs) / this.crossfadeMs);
|
|
47544
47755
|
const eased = easeInOutQuad(t);
|
|
47756
|
+
if (this.valueOnly) {
|
|
47757
|
+
this.fadeMix = this.fadeMixStart + (1 - this.fadeMixStart) * eased;
|
|
47758
|
+
if (t >= 1) {
|
|
47759
|
+
this.fadeStartMs = null;
|
|
47760
|
+
this.fadeFromValuePixels = null;
|
|
47761
|
+
this.fadeMix = 1;
|
|
47762
|
+
}
|
|
47763
|
+
this.requestRender();
|
|
47764
|
+
return;
|
|
47765
|
+
}
|
|
47545
47766
|
const from = this.fadeFromPixels;
|
|
47546
47767
|
const to = this.frameCache[this.fadeToIndex];
|
|
47547
47768
|
const n = Math.min(from.length, to.length);
|
|
@@ -47581,9 +47802,7 @@ var TextureFrameSeriesAnimator;
|
|
|
47581
47802
|
this.valueDims = dims;
|
|
47582
47803
|
}
|
|
47583
47804
|
this.presentVersion++;
|
|
47584
|
-
|
|
47585
|
-
this.viewer.scene.requestRender();
|
|
47586
|
-
}
|
|
47805
|
+
this.requestRender();
|
|
47587
47806
|
}
|
|
47588
47807
|
}
|
|
47589
47808
|
TextureFrameSeriesAnimator.Animator = Animator;
|
|
@@ -47606,13 +47825,15 @@ var TextureValueLabels;
|
|
|
47606
47825
|
const MIN_VISIBLE_PIXELS = 60;
|
|
47607
47826
|
const MAX_STRIDE = 512;
|
|
47608
47827
|
const STRIDE_HYSTERESIS = 1.4;
|
|
47609
|
-
|
|
47610
|
-
const
|
|
47611
|
-
const
|
|
47612
|
-
|
|
47613
|
-
|
|
47614
|
-
|
|
47615
|
-
const
|
|
47828
|
+
// Scene labels carry a leader and are wider than a value, so they want room and not many of them.
|
|
47829
|
+
const DEFAULT_MIN_SPACING_PIXELS = 140;
|
|
47830
|
+
const DEFAULT_MAX_LABELS = 60;
|
|
47831
|
+
// How far past the edge of the view an anchor may sit before its label is dropped.
|
|
47832
|
+
const LABEL_EDGE_MARGIN_PIXELS = 120;
|
|
47833
|
+
// Distance discount for a label already up when the cap forces a choice.
|
|
47834
|
+
const LABEL_KEEP_BIAS = 0.6;
|
|
47835
|
+
// How long a label outlives its cell leaving the wanted set, so one back a frame later does not blink.
|
|
47836
|
+
const LABEL_LINGER_MS = 300;
|
|
47616
47837
|
// 8 rather than 5: below this a cell reads as texture rather than as a boundary, and it also
|
|
47617
47838
|
// bounds the worst case, since a full screen of 8 px cells fits inside MAX_GRID_CELLS.
|
|
47618
47839
|
const DEFAULT_MIN_GRID_CELL_PIXELS = 8;
|
|
@@ -47626,7 +47847,8 @@ var TextureValueLabels;
|
|
|
47626
47847
|
const TILE_OUTLINE_ALPHA = 0.45;
|
|
47627
47848
|
// Cells per grid square at the coarsest. Beyond this a tile is outlined instead.
|
|
47628
47849
|
const MAX_GRID_STRIDE = 256;
|
|
47629
|
-
|
|
47850
|
+
// Times the pointer's ground hit is moved onto the sheet's own height before reading a cell.
|
|
47851
|
+
const HOVER_REFINE_STEPS = 3;
|
|
47630
47852
|
/**
|
|
47631
47853
|
* A clip outline from a projected ring, or null when the ring cannot be drawn.
|
|
47632
47854
|
* @param projected one entry per ring vertex, null where the vertex has no window coordinate
|
|
@@ -47720,11 +47942,25 @@ var TextureValueLabels;
|
|
|
47720
47942
|
this.sourceMap = null;
|
|
47721
47943
|
this.source = null;
|
|
47722
47944
|
this.painted = null;
|
|
47945
|
+
this.paintedRamp = null;
|
|
47723
47946
|
this.floor = null;
|
|
47724
47947
|
this.ceiling = null;
|
|
47725
47948
|
this.disposed = false;
|
|
47726
|
-
this.drawn = 0;
|
|
47727
47949
|
this.stride = 0;
|
|
47950
|
+
this.cellLabels = new Map();
|
|
47951
|
+
this.lingerTimer = null;
|
|
47952
|
+
// What the last draw was made from, so a frame that changes none of it draws nothing.
|
|
47953
|
+
this.drawnView = null;
|
|
47954
|
+
this.drawnInputs = [];
|
|
47955
|
+
this.scratchWorld = new Cartesian3();
|
|
47956
|
+
this.scratchWindow = new Cartesian2();
|
|
47957
|
+
this.scratchClip = new Cartesian4();
|
|
47958
|
+
// The camera's view and projection as one matrix, rebuilt per draw, or null outside 3D.
|
|
47959
|
+
this.viewProjection = null;
|
|
47960
|
+
this.viewProjectionScratch = new Matrix4();
|
|
47961
|
+
this.hovering = false;
|
|
47962
|
+
this.hoverFrame = 0;
|
|
47963
|
+
this.hoverAt = null;
|
|
47728
47964
|
this.viewer = options.viewer;
|
|
47729
47965
|
this.extent = options.extent;
|
|
47730
47966
|
this.settings = options.settings || {};
|
|
@@ -47734,6 +47970,9 @@ var TextureValueLabels;
|
|
|
47734
47970
|
this.sourceMap = TextureFrameSeriesAnimator.SourceMap(options.metadata);
|
|
47735
47971
|
this.tiles = options.tiles;
|
|
47736
47972
|
this.clipRing = options.clipRing;
|
|
47973
|
+
this.heightAt = options.heightAt;
|
|
47974
|
+
this.heightKey = options.heightKey;
|
|
47975
|
+
this.hover = options.hover;
|
|
47737
47976
|
this.canvas = document.createElement("canvas");
|
|
47738
47977
|
this.canvas.style.position = "absolute";
|
|
47739
47978
|
this.canvas.style.inset = "0";
|
|
@@ -47743,12 +47982,31 @@ var TextureValueLabels;
|
|
|
47743
47982
|
parent.appendChild(this.canvas);
|
|
47744
47983
|
}
|
|
47745
47984
|
this.ctx = this.canvas.getContext("2d");
|
|
47985
|
+
this.onPointerMove = (event) => {
|
|
47986
|
+
const bounds = this.viewer.canvas.getBoundingClientRect();
|
|
47987
|
+
this.hoverAt = { x: event.clientX - bounds.left, y: event.clientY - bounds.top };
|
|
47988
|
+
// At most once a frame, since a pointer reports far faster than anything is drawn.
|
|
47989
|
+
if (!this.hoverFrame) {
|
|
47990
|
+
this.hoverFrame = requestAnimationFrame(() => {
|
|
47991
|
+
this.hoverFrame = 0;
|
|
47992
|
+
this.updateHover();
|
|
47993
|
+
});
|
|
47994
|
+
}
|
|
47995
|
+
};
|
|
47996
|
+
this.onPointerLeave = () => {
|
|
47997
|
+
this.hoverAt = null;
|
|
47998
|
+
this.releaseHover();
|
|
47999
|
+
};
|
|
48000
|
+
if (this.hover && this.hover.register) {
|
|
48001
|
+
this.viewer.canvas.addEventListener("pointermove", this.onPointerMove, { passive: true });
|
|
48002
|
+
this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
|
|
48003
|
+
}
|
|
47746
48004
|
}
|
|
47747
48005
|
SetExtent(extent) {
|
|
47748
48006
|
this.extent = extent;
|
|
47749
48007
|
}
|
|
47750
48008
|
/**
|
|
47751
|
-
* The current frame's decoded values
|
|
48009
|
+
* The current frame's decoded values.
|
|
47752
48010
|
* @param source value pixels, one texel per cell
|
|
47753
48011
|
*/
|
|
47754
48012
|
SetSource(source) {
|
|
@@ -47756,11 +48014,17 @@ var TextureValueLabels;
|
|
|
47756
48014
|
}
|
|
47757
48015
|
/**
|
|
47758
48016
|
* The tinted pixels of the same frame, so the grid can skip cells the ramp painted nothing in.
|
|
47759
|
-
* @param painted RGBA of the presented frame, or null to fall back to coverage alone
|
|
48017
|
+
* @param painted RGBA of the presented frame, or null to fall back to the ramp or coverage alone
|
|
47760
48018
|
*/
|
|
47761
48019
|
SetPainted(painted) {
|
|
47762
48020
|
this.painted = painted;
|
|
47763
48021
|
}
|
|
48022
|
+
/**
|
|
48023
|
+
* The surface's 256 step RGBA ramp, for telling whether a cell was painted without a tinted frame.
|
|
48024
|
+
*/
|
|
48025
|
+
SetPaintedRamp(ramp) {
|
|
48026
|
+
this.paintedRamp = ramp;
|
|
48027
|
+
}
|
|
47764
48028
|
/**
|
|
47765
48029
|
* The archive's per-cell extremes, when it publishes them. Without these a label can still
|
|
47766
48030
|
* print the current reading, just not the range behind it.
|
|
@@ -47770,21 +48034,28 @@ var TextureValueLabels;
|
|
|
47770
48034
|
this.ceiling = ceiling;
|
|
47771
48035
|
}
|
|
47772
48036
|
GetDrawnCount() {
|
|
47773
|
-
return this.
|
|
48037
|
+
return this.cellLabels.size;
|
|
47774
48038
|
}
|
|
47775
48039
|
Clear() {
|
|
47776
48040
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
47777
|
-
this.
|
|
48041
|
+
this.drawnView = null;
|
|
47778
48042
|
}
|
|
47779
48043
|
Dispose() {
|
|
47780
48044
|
this.disposed = true;
|
|
48045
|
+
this.clearCellLabels();
|
|
48046
|
+
this.releaseHover();
|
|
48047
|
+
if (this.hoverFrame) {
|
|
48048
|
+
cancelAnimationFrame(this.hoverFrame);
|
|
48049
|
+
this.hoverFrame = 0;
|
|
48050
|
+
}
|
|
48051
|
+
this.viewer.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
48052
|
+
this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
|
|
47781
48053
|
if (this.canvas.parentElement) {
|
|
47782
48054
|
this.canvas.parentElement.removeChild(this.canvas);
|
|
47783
48055
|
}
|
|
47784
48056
|
}
|
|
47785
48057
|
/**
|
|
47786
|
-
* Redraws
|
|
47787
|
-
* count it considers is bounded by the view rather than by the texture.
|
|
48058
|
+
* Redraws the grid and syncs the value labels, when anything they are drawn from has changed.
|
|
47788
48059
|
*/
|
|
47789
48060
|
Render() {
|
|
47790
48061
|
var _a, _b, _c;
|
|
@@ -47792,67 +48063,296 @@ var TextureValueLabels;
|
|
|
47792
48063
|
return;
|
|
47793
48064
|
}
|
|
47794
48065
|
const view = this.viewer.canvas;
|
|
48066
|
+
const inputs = [this.source, this.painted, this.paintedRamp, this.floor, this.ceiling,
|
|
48067
|
+
view.clientWidth, view.clientHeight, this.heightKey ? this.heightKey() : null];
|
|
48068
|
+
const camera = this.viewer.camera.viewMatrix;
|
|
48069
|
+
if (this.drawnView &&
|
|
48070
|
+
Matrix4.equals(this.drawnView, camera) &&
|
|
48071
|
+
inputs.every((input, i) => input === this.drawnInputs[i])) {
|
|
48072
|
+
return;
|
|
48073
|
+
}
|
|
47795
48074
|
if (this.canvas.width !== view.clientWidth || this.canvas.height !== view.clientHeight) {
|
|
47796
48075
|
this.canvas.width = view.clientWidth;
|
|
47797
48076
|
this.canvas.height = view.clientHeight;
|
|
47798
48077
|
}
|
|
47799
48078
|
this.Clear();
|
|
48079
|
+
this.drawnView = Matrix4.clone(camera, this.drawnView || undefined);
|
|
48080
|
+
this.drawnInputs = inputs;
|
|
48081
|
+
const projection = this.viewer.camera.frustum.projectionMatrix;
|
|
48082
|
+
this.viewProjection = this.viewer.scene.mode === SceneMode.SCENE3D
|
|
48083
|
+
? Matrix4.multiply(projection, camera, this.viewProjectionScratch)
|
|
48084
|
+
: null;
|
|
47800
48085
|
const cols = this.source.width;
|
|
47801
48086
|
const rows = this.source.height;
|
|
47802
48087
|
const window = this.visibleTexels(cols, rows);
|
|
47803
48088
|
if (!window) {
|
|
48089
|
+
this.syncCellLabels([]);
|
|
47804
48090
|
return;
|
|
47805
48091
|
}
|
|
47806
|
-
|
|
47807
|
-
|
|
47808
|
-
const
|
|
47809
|
-
const
|
|
48092
|
+
// Measured where the view is looking, since in an oblique view up close the middle of the visible
|
|
48093
|
+
// texels sits out toward the horizon, where cells are a fraction of the size.
|
|
48094
|
+
const focus = this.groundAtScreenCentre();
|
|
48095
|
+
const at = focus ? this.texelAt(focus.lon, focus.lat) : null;
|
|
48096
|
+
const focused = (Boolean(at) &&
|
|
48097
|
+
at.col >= window.col0 &&
|
|
48098
|
+
at.col <= window.col1 &&
|
|
48099
|
+
at.row >= window.row0 &&
|
|
48100
|
+
at.row <= window.row1);
|
|
48101
|
+
const midCol = focused ? at.col : (window.col0 + window.col1) / 2;
|
|
48102
|
+
const midRow = focused ? at.row : (window.row0 + window.row1) / 2;
|
|
48103
|
+
const a = this.projectCell(midCol, midRow, cols, rows);
|
|
48104
|
+
const b = this.projectCell(midCol + 1, midRow + 1, cols, rows);
|
|
47810
48105
|
if (!a || !b) {
|
|
48106
|
+
// Left as they are, since one frame where the focus will not project is not the data leaving.
|
|
47811
48107
|
return;
|
|
47812
48108
|
}
|
|
47813
48109
|
const cellPixels = Math.min(Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
47814
48110
|
// Ahead of the label threshold on purpose: a grid still reads at a cell size where a
|
|
47815
48111
|
// number no longer fits.
|
|
47816
48112
|
this.drawGrid(window, cols, rows, cellPixels);
|
|
47817
|
-
|
|
48113
|
+
// By area rather than the shorter side, which an oblique view foreshortens to a fraction of the other.
|
|
48114
|
+
const across = this.projectCell(midCol + 1, midRow, cols, rows);
|
|
48115
|
+
const down = this.projectCell(midCol, midRow + 1, cols, rows);
|
|
48116
|
+
const cellSize = across && down
|
|
48117
|
+
? Math.sqrt(Math.abs((across.x - a.x) * (down.y - a.y) - (across.y - a.y) * (down.x - a.x)))
|
|
48118
|
+
: cellPixels;
|
|
48119
|
+
const visibleWidth = cellSize * (window.col1 - window.col0 + 1);
|
|
48120
|
+
const visibleHeight = cellSize * (window.row1 - window.row0 + 1);
|
|
48121
|
+
if (cellSize < ((_a = this.settings.minCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_CELL_PIXELS) ||
|
|
48122
|
+
Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
48123
|
+
this.syncCellLabels([]);
|
|
47818
48124
|
return;
|
|
47819
48125
|
}
|
|
47820
|
-
const visibleWidth = cellPixels * (window.col1 - window.col0 + 1);
|
|
47821
|
-
const visibleHeight = cellPixels * (window.row1 - window.row0 + 1);
|
|
47822
|
-
if (Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
47823
|
-
return;
|
|
47824
|
-
}
|
|
47825
|
-
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
47826
|
-
this.ctx.font = LABEL_FONT;
|
|
47827
|
-
this.ctx.textAlign = "center";
|
|
47828
48126
|
const maxLabels = (_b = this.settings.maxLabels) !== null && _b !== void 0 ? _b : DEFAULT_MAX_LABELS;
|
|
47829
48127
|
this.stride = ChooseLabelStride({
|
|
47830
|
-
cellPixels,
|
|
48128
|
+
cellPixels: cellSize,
|
|
47831
48129
|
visibleCols: window.col1 - window.col0 + 1,
|
|
47832
48130
|
visibleRows: window.row1 - window.row0 + 1,
|
|
47833
48131
|
maxLabels,
|
|
47834
|
-
|
|
47835
|
-
// one is, so changing what a label says cannot quietly start overlapping them.
|
|
47836
|
-
minSpacingPixels: Math.max((_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS, this.widestLabel(parts)),
|
|
48132
|
+
minSpacingPixels: (_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS,
|
|
47837
48133
|
held: this.stride
|
|
47838
48134
|
});
|
|
47839
|
-
const
|
|
48135
|
+
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
48136
|
+
this.syncCellLabels(this.labelCells(window, cols, rows, this.stride, maxLabels, parts));
|
|
48137
|
+
}
|
|
48138
|
+
/*
|
|
48139
|
+
* One labelled cell per lattice block, keyed by the block's origin so it survives frame and stride changes.
|
|
48140
|
+
*/
|
|
48141
|
+
labelCells(window, cols, rows, stride, max, parts) {
|
|
48142
|
+
const width = this.canvas.width;
|
|
48143
|
+
const height = this.canvas.height;
|
|
47840
48144
|
const firstRow = Math.floor(window.row0 / stride) * stride;
|
|
47841
48145
|
const firstCol = Math.floor(window.col0 / stride) * stride;
|
|
47842
|
-
|
|
47843
|
-
|
|
48146
|
+
const candidates = [];
|
|
48147
|
+
for (let row = firstRow; row <= window.row1; row += stride) {
|
|
48148
|
+
for (let col = firstCol; col <= window.col1; col += stride) {
|
|
47844
48149
|
const found = this.readNear(col, row, cols, rows, stride);
|
|
47845
48150
|
if (!found) {
|
|
47846
48151
|
continue;
|
|
47847
48152
|
}
|
|
47848
|
-
const point = this.
|
|
47849
|
-
|
|
47850
|
-
|
|
48153
|
+
const point = this.projectCell(found.col, found.row, cols, rows);
|
|
48154
|
+
// A margin past the edge, so a label does not drop the moment its anchor does.
|
|
48155
|
+
const margin = LABEL_EDGE_MARGIN_PIXELS;
|
|
48156
|
+
if (!point ||
|
|
48157
|
+
point.x < -margin ||
|
|
48158
|
+
point.y < -margin ||
|
|
48159
|
+
point.x > width + margin ||
|
|
48160
|
+
point.y > height + margin) {
|
|
47851
48161
|
continue;
|
|
47852
48162
|
}
|
|
47853
|
-
this.
|
|
48163
|
+
const text = this.labelLines(found.reading, parts).join("\n");
|
|
48164
|
+
if (!text) {
|
|
48165
|
+
continue;
|
|
48166
|
+
}
|
|
48167
|
+
const key = col + "," + row;
|
|
48168
|
+
const distance = Math.hypot(point.x - width / 2, point.y - height / 2);
|
|
48169
|
+
candidates.push({
|
|
48170
|
+
key,
|
|
48171
|
+
lon: this.lonAt(found.col, cols),
|
|
48172
|
+
lat: this.latAt(found.row, rows),
|
|
48173
|
+
text,
|
|
48174
|
+
// Labels already up outrank new ones, so the capped set does not shuffle as the view pans.
|
|
48175
|
+
rank: this.cellLabels.has(key) ? distance * LABEL_KEEP_BIAS : distance
|
|
48176
|
+
});
|
|
48177
|
+
}
|
|
48178
|
+
}
|
|
48179
|
+
if (candidates.length > max) {
|
|
48180
|
+
candidates.sort((x, y) => x.rank - y.rank);
|
|
48181
|
+
candidates.length = max;
|
|
48182
|
+
}
|
|
48183
|
+
return candidates;
|
|
48184
|
+
}
|
|
48185
|
+
/*
|
|
48186
|
+
* Keeps one scene label per wanted cell, reusing those that are still wanted.
|
|
48187
|
+
*/
|
|
48188
|
+
syncCellLabels(wanted) {
|
|
48189
|
+
var _a;
|
|
48190
|
+
const keep = new Set(wanted.map((w) => w.key));
|
|
48191
|
+
const now = Date.now();
|
|
48192
|
+
let lingering = false;
|
|
48193
|
+
for (const [key, entry] of this.cellLabels) {
|
|
48194
|
+
if (keep.has(key)) {
|
|
48195
|
+
entry.missingSince = undefined;
|
|
48196
|
+
continue;
|
|
48197
|
+
}
|
|
48198
|
+
entry.missingSince = (_a = entry.missingSince) !== null && _a !== void 0 ? _a : now;
|
|
48199
|
+
if (now - entry.missingSince >= LABEL_LINGER_MS) {
|
|
48200
|
+
entry.label.Dispose();
|
|
48201
|
+
this.cellLabels.delete(key);
|
|
48202
|
+
}
|
|
48203
|
+
else {
|
|
48204
|
+
lingering = true;
|
|
48205
|
+
}
|
|
48206
|
+
}
|
|
48207
|
+
// A still camera draws nothing more, so a lingering label is given its own later look.
|
|
48208
|
+
if (lingering && !this.lingerTimer) {
|
|
48209
|
+
this.lingerTimer = setTimeout(() => {
|
|
48210
|
+
this.lingerTimer = null;
|
|
48211
|
+
if (!this.disposed) {
|
|
48212
|
+
this.drawnView = null;
|
|
48213
|
+
this.viewer.scene.requestRender();
|
|
48214
|
+
}
|
|
48215
|
+
}, LABEL_LINGER_MS);
|
|
48216
|
+
}
|
|
48217
|
+
for (const want of wanted) {
|
|
48218
|
+
const pos3d = Cartesian3.fromDegrees(want.lon, want.lat, this.surfaceHeight(want.lon, want.lat));
|
|
48219
|
+
const options = {
|
|
48220
|
+
presentation: "text",
|
|
48221
|
+
text: want.text,
|
|
48222
|
+
leader: true,
|
|
48223
|
+
selectOnClick: false
|
|
48224
|
+
};
|
|
48225
|
+
const existing = this.cellLabels.get(want.key);
|
|
48226
|
+
if (existing) {
|
|
48227
|
+
if (!Cartesian3.equalsEpsilon(existing.label.pos3d, pos3d, 0, 0.01)) {
|
|
48228
|
+
existing.label.pos3d = pos3d;
|
|
48229
|
+
}
|
|
48230
|
+
if (existing.text !== want.text) {
|
|
48231
|
+
existing.label.Configure(options);
|
|
48232
|
+
existing.text = want.text;
|
|
48233
|
+
}
|
|
48234
|
+
continue;
|
|
48235
|
+
}
|
|
48236
|
+
const id = (this.hover ? this.hover.entityId : "texture-value") + "#cell:" + want.key;
|
|
48237
|
+
const label = new EntityLabel.Label({
|
|
48238
|
+
rego: {
|
|
48239
|
+
entityId: id,
|
|
48240
|
+
menuItemId: this.hover ? this.hover.menuItemId : undefined,
|
|
48241
|
+
name: want.text,
|
|
48242
|
+
priority: 0,
|
|
48243
|
+
// No graphic, since a point or billboard visual pulls a label back to itself on rebuild.
|
|
48244
|
+
visual: new Entity({ position: pos3d })
|
|
48245
|
+
},
|
|
48246
|
+
viewer: this.viewer,
|
|
48247
|
+
pos3d,
|
|
48248
|
+
options,
|
|
48249
|
+
persistent: true,
|
|
48250
|
+
visible: () => !this.disposed
|
|
48251
|
+
});
|
|
48252
|
+
this.cellLabels.set(want.key, { label, text: want.text });
|
|
48253
|
+
}
|
|
48254
|
+
}
|
|
48255
|
+
clearCellLabels() {
|
|
48256
|
+
if (this.lingerTimer) {
|
|
48257
|
+
clearTimeout(this.lingerTimer);
|
|
48258
|
+
this.lingerTimer = null;
|
|
48259
|
+
}
|
|
48260
|
+
for (const entry of this.cellLabels.values()) {
|
|
48261
|
+
entry.label.Dispose();
|
|
48262
|
+
}
|
|
48263
|
+
this.cellLabels.clear();
|
|
48264
|
+
}
|
|
48265
|
+
/*
|
|
48266
|
+
* Shows the cell under the pointer through the register's hover label, unless something else holds it.
|
|
48267
|
+
*/
|
|
48268
|
+
updateHover() {
|
|
48269
|
+
const hover = this.hover;
|
|
48270
|
+
if (this.disposed || !hover || !hover.register || !this.source || !this.hoverAt) {
|
|
48271
|
+
return;
|
|
48272
|
+
}
|
|
48273
|
+
const register = hover.register;
|
|
48274
|
+
const target = register.GetLabelHoverTarget();
|
|
48275
|
+
const ours = Boolean(target) && register.IsLabelHovered(hover.entityId, hover.menuItemId);
|
|
48276
|
+
if (target && !ours) {
|
|
48277
|
+
this.hovering = false;
|
|
48278
|
+
return;
|
|
48279
|
+
}
|
|
48280
|
+
const under = this.readingUnder(this.hoverAt.x, this.hoverAt.y);
|
|
48281
|
+
if (!under) {
|
|
48282
|
+
this.releaseHover();
|
|
48283
|
+
return;
|
|
48284
|
+
}
|
|
48285
|
+
register.SetLabelHover({
|
|
48286
|
+
entityId: hover.entityId,
|
|
48287
|
+
menuItemId: hover.menuItemId,
|
|
48288
|
+
position: { x: this.hoverAt.x, y: this.hoverAt.y },
|
|
48289
|
+
text: under.text,
|
|
48290
|
+
pos3d: under.pos3d
|
|
48291
|
+
});
|
|
48292
|
+
this.hovering = true;
|
|
48293
|
+
}
|
|
48294
|
+
releaseHover() {
|
|
48295
|
+
const hover = this.hover;
|
|
48296
|
+
if (!this.hovering || !hover || !hover.register) {
|
|
48297
|
+
return;
|
|
48298
|
+
}
|
|
48299
|
+
this.hovering = false;
|
|
48300
|
+
if (hover.register.IsLabelHovered(hover.entityId, hover.menuItemId)) {
|
|
48301
|
+
hover.register.ClearLabelHover();
|
|
48302
|
+
}
|
|
48303
|
+
}
|
|
48304
|
+
/*
|
|
48305
|
+
* The text for the painted cell under a window position and its centre on the sheet, or null.
|
|
48306
|
+
*/
|
|
48307
|
+
readingUnder(x, y) {
|
|
48308
|
+
const at = this.cellUnder(x, y);
|
|
48309
|
+
if (!at) {
|
|
48310
|
+
return null;
|
|
48311
|
+
}
|
|
48312
|
+
const cols = this.source.width;
|
|
48313
|
+
const found = this.readAt(at.col, at.row, cols);
|
|
48314
|
+
if (!found || !this.paintedAt(at.col, at.row, cols)) {
|
|
48315
|
+
return null;
|
|
48316
|
+
}
|
|
48317
|
+
const text = this.labelLines(found, this.settings.parts || ["value", "floor", "ceiling"]).join("\n");
|
|
48318
|
+
if (!text) {
|
|
48319
|
+
return null;
|
|
48320
|
+
}
|
|
48321
|
+
const lon = this.lonAt(at.col, cols);
|
|
48322
|
+
const lat = this.latAt(at.row, this.source.height);
|
|
48323
|
+
return { text, pos3d: Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat)) };
|
|
48324
|
+
}
|
|
48325
|
+
/*
|
|
48326
|
+
* The cell under a window position, found on the sheet rather than on the ground beneath it.
|
|
48327
|
+
*/
|
|
48328
|
+
cellUnder(x, y) {
|
|
48329
|
+
const scene = this.viewer.scene;
|
|
48330
|
+
const camera = this.viewer.camera;
|
|
48331
|
+
const screen = new Cartesian2(x, y);
|
|
48332
|
+
const ray = camera.getPickRay(screen);
|
|
48333
|
+
if (!ray) {
|
|
48334
|
+
return null;
|
|
48335
|
+
}
|
|
48336
|
+
let hit = (scene.globe && scene.globe.pick(ray, scene)) || camera.pickEllipsoid(screen);
|
|
48337
|
+
if (!hit) {
|
|
48338
|
+
return null;
|
|
48339
|
+
}
|
|
48340
|
+
const radii = Ellipsoid.WGS84.radii;
|
|
48341
|
+
for (let i = 0; i < HOVER_REFINE_STEPS && this.heightAt; i++) {
|
|
48342
|
+
const carto = Cartographic.fromCartesian(hit);
|
|
48343
|
+
const height = this.heightAt(Math$1.toDegrees(carto.longitude), Math$1.toDegrees(carto.latitude));
|
|
48344
|
+
if (height == null) {
|
|
48345
|
+
break;
|
|
47854
48346
|
}
|
|
48347
|
+
const inflated = new Ellipsoid(radii.x + height, radii.y + height, radii.z + height);
|
|
48348
|
+
const interval = IntersectionTests.rayEllipsoid(ray, inflated);
|
|
48349
|
+
if (!interval) {
|
|
48350
|
+
break;
|
|
48351
|
+
}
|
|
48352
|
+
hit = Ray.getPoint(ray, interval.start);
|
|
47855
48353
|
}
|
|
48354
|
+
const carto = Cartographic.fromCartesian(hit);
|
|
48355
|
+
return this.texelAt(Math$1.toDegrees(carto.longitude), Math$1.toDegrees(carto.latitude));
|
|
47856
48356
|
}
|
|
47857
48357
|
/*
|
|
47858
48358
|
* Outlines each covered cell, from a projected lattice of its corners.
|
|
@@ -47880,44 +48380,25 @@ var TextureValueLabels;
|
|
|
47880
48380
|
if (wide * high > MAX_GRID_CELLS) {
|
|
47881
48381
|
return;
|
|
47882
48382
|
}
|
|
47883
|
-
const lattice = new
|
|
47884
|
-
|
|
47885
|
-
|
|
47886
|
-
|
|
47887
|
-
|
|
47888
|
-
|
|
47889
|
-
|
|
47890
|
-
}
|
|
47891
|
-
}
|
|
47892
|
-
const path = new Path2D();
|
|
47893
|
-
let drew = false;
|
|
48383
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => {
|
|
48384
|
+
// Corners sit half a texel out from the centres lonAt/latAt return.
|
|
48385
|
+
const lon = this.lonAt(window.col0 + c - 0.5, cols);
|
|
48386
|
+
const lat = this.latAt(window.row0 + r - 0.5, rows);
|
|
48387
|
+
return this.projectInto(lon, lat);
|
|
48388
|
+
});
|
|
48389
|
+
const covered = new Uint8Array(wide * high);
|
|
47894
48390
|
for (let r = 0; r < high; r++) {
|
|
47895
48391
|
for (let c = 0; c < wide; c++) {
|
|
47896
|
-
|
|
47897
|
-
continue;
|
|
47898
|
-
}
|
|
47899
|
-
const tl = lattice[r * (wide + 1) + c];
|
|
47900
|
-
const tr = lattice[r * (wide + 1) + c + 1];
|
|
47901
|
-
const bl = lattice[(r + 1) * (wide + 1) + c];
|
|
47902
|
-
const br = lattice[(r + 1) * (wide + 1) + c + 1];
|
|
47903
|
-
if (!tl || !tr || !bl || !br) {
|
|
47904
|
-
continue;
|
|
47905
|
-
}
|
|
47906
|
-
path.moveTo(tl.x, tl.y);
|
|
47907
|
-
path.lineTo(tr.x, tr.y);
|
|
47908
|
-
path.lineTo(br.x, br.y);
|
|
47909
|
-
path.lineTo(bl.x, bl.y);
|
|
47910
|
-
path.closePath();
|
|
47911
|
-
drew = true;
|
|
48392
|
+
covered[r * wide + c] = this.isCovered(window.col0 + c, window.row0 + r, cols) ? 1 : 0;
|
|
47912
48393
|
}
|
|
47913
48394
|
}
|
|
47914
|
-
|
|
48395
|
+
const path = new Path2D();
|
|
48396
|
+
if (!lattice.Trace(path, covered, wide, high)) {
|
|
47915
48397
|
return;
|
|
47916
48398
|
}
|
|
47917
48399
|
this.ctx.strokeStyle = color;
|
|
47918
48400
|
this.ctx.lineWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
47919
48401
|
this.ctx.stroke(path);
|
|
47920
|
-
+" alpha" + this.ctx.globalAlpha;
|
|
47921
48402
|
}
|
|
47922
48403
|
/*
|
|
47923
48404
|
* Restricts drawing to the polygon, so nothing is ruled over ground it does not cover.
|
|
@@ -47939,33 +48420,11 @@ var TextureValueLabels;
|
|
|
47939
48420
|
path.closePath();
|
|
47940
48421
|
this.ctx.clip(path);
|
|
47941
48422
|
}
|
|
47942
|
-
/*
|
|
47943
|
-
* The cell of whichever tile covers a point, as screen corners.
|
|
47944
|
-
*/
|
|
47945
|
-
tileCellOutline(lon, lat) {
|
|
47946
|
-
for (const tile of this.tiles) {
|
|
47947
|
-
if (lon < tile.West || lon > tile.East || lat < tile.South || lat > tile.North) {
|
|
47948
|
-
continue;
|
|
47949
|
-
}
|
|
47950
|
-
const c = (Math.min(tile.ResolutionX - 1, Math.max(0, Math.floor((lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX))));
|
|
47951
|
-
const r = (Math.min(tile.ResolutionY - 1, Math.max(0, Math.floor((tile.North - lat) / (tile.North - tile.South) * tile.ResolutionY))));
|
|
47952
|
-
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
47953
|
-
const w1 = tile.West + ((c + 1) / tile.ResolutionX) * (tile.East - tile.West);
|
|
47954
|
-
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
47955
|
-
const n1 = tile.North - ((r + 1) / tile.ResolutionY) * (tile.North - tile.South);
|
|
47956
|
-
const corners = [
|
|
47957
|
-
this.project(w0, n0), this.project(w1, n0),
|
|
47958
|
-
this.project(w1, n1), this.project(w0, n1)
|
|
47959
|
-
];
|
|
47960
|
-
return corners.every((q) => q) ? corners : null;
|
|
47961
|
-
}
|
|
47962
|
-
return null;
|
|
47963
|
-
}
|
|
47964
48423
|
/*
|
|
47965
48424
|
* Outlines each tile's own cells, so cell size still shows where the mesh is refined.
|
|
47966
48425
|
*/
|
|
47967
48426
|
drawTileGrid(color) {
|
|
47968
|
-
var _a, _b
|
|
48427
|
+
var _a, _b;
|
|
47969
48428
|
const view = this.viewer.camera.computeViewRectangle(Ellipsoid.WGS84);
|
|
47970
48429
|
if (!view) {
|
|
47971
48430
|
return;
|
|
@@ -47994,7 +48453,7 @@ var TextureValueLabels;
|
|
|
47994
48453
|
coarse.lineTo(tr.x, tr.y);
|
|
47995
48454
|
coarse.lineTo(br.x, br.y);
|
|
47996
48455
|
coarse.lineTo(bl.x, bl.y);
|
|
47997
|
-
coarse.
|
|
48456
|
+
coarse.lineTo(tl.x, tl.y);
|
|
47998
48457
|
drewCoarse = true;
|
|
47999
48458
|
};
|
|
48000
48459
|
// Planned for every visible tile BEFORE any is drawn.
|
|
@@ -48055,31 +48514,29 @@ var TextureValueLabels;
|
|
|
48055
48514
|
// moves, and so each step up is a subset of the one below it.
|
|
48056
48515
|
const firstR = Math.floor(r0 / step) * step;
|
|
48057
48516
|
const firstC = Math.floor(c0 / step) * step;
|
|
48058
|
-
|
|
48059
|
-
|
|
48060
|
-
|
|
48061
|
-
|
|
48062
|
-
|
|
48063
|
-
|
|
48064
|
-
|
|
48065
|
-
|
|
48066
|
-
|
|
48067
|
-
|
|
48068
|
-
|
|
48069
|
-
|
|
48070
|
-
|
|
48071
|
-
|
|
48072
|
-
|
|
48073
|
-
|
|
48074
|
-
|
|
48075
|
-
|
|
48076
|
-
|
|
48077
|
-
|
|
48078
|
-
|
|
48079
|
-
|
|
48080
|
-
|
|
48081
|
-
drew = true;
|
|
48082
|
-
}
|
|
48517
|
+
const wide = Math.ceil((c1 - firstC) / step);
|
|
48518
|
+
const high = Math.ceil((r1 - firstR) / step);
|
|
48519
|
+
const lonOf = (c) => tile.West
|
|
48520
|
+
+ (Math.min(firstC + c * step, tile.ResolutionX) / tile.ResolutionX) * (tile.East - tile.West);
|
|
48521
|
+
const latOf = (r) => tile.North
|
|
48522
|
+
- (Math.min(firstR + r * step, tile.ResolutionY) / tile.ResolutionY) * (tile.North - tile.South);
|
|
48523
|
+
const covered = new Uint8Array(wide * high);
|
|
48524
|
+
let any = false;
|
|
48525
|
+
for (let r = 0; r < high; r++) {
|
|
48526
|
+
for (let c = 0; c < wide; c++) {
|
|
48527
|
+
if (this.coveredAt((lonOf(c) + lonOf(c + 1)) / 2, (latOf(r) + latOf(r + 1)) / 2)) {
|
|
48528
|
+
covered[r * wide + c] = 1;
|
|
48529
|
+
any = true;
|
|
48530
|
+
}
|
|
48531
|
+
}
|
|
48532
|
+
}
|
|
48533
|
+
if (!any) {
|
|
48534
|
+
continue;
|
|
48535
|
+
}
|
|
48536
|
+
// Projected on demand, since a sparse raster leaves most corners belonging to no cell.
|
|
48537
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => this.projectInto(lonOf(c), latOf(r)));
|
|
48538
|
+
if (lattice.Trace(path, covered, wide, high)) {
|
|
48539
|
+
drew = true;
|
|
48083
48540
|
}
|
|
48084
48541
|
}
|
|
48085
48542
|
const gridWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
@@ -48097,7 +48554,7 @@ var TextureValueLabels;
|
|
|
48097
48554
|
return;
|
|
48098
48555
|
}
|
|
48099
48556
|
this.ctx.strokeStyle = color;
|
|
48100
|
-
this.ctx.lineWidth =
|
|
48557
|
+
this.ctx.lineWidth = gridWidth;
|
|
48101
48558
|
this.ctx.stroke(path);
|
|
48102
48559
|
}
|
|
48103
48560
|
/*
|
|
@@ -48117,7 +48574,7 @@ var TextureValueLabels;
|
|
|
48117
48574
|
};
|
|
48118
48575
|
}
|
|
48119
48576
|
/*
|
|
48120
|
-
* Whether the composited raster holds data at a lon/lat.
|
|
48577
|
+
* Whether the composited raster holds painted data at a lon/lat.
|
|
48121
48578
|
*/
|
|
48122
48579
|
coveredAt(lon, lat) {
|
|
48123
48580
|
if (!this.source) {
|
|
@@ -48130,60 +48587,20 @@ var TextureValueLabels;
|
|
|
48130
48587
|
if (col < 0 || row < 0 || col >= cols || row >= rows) {
|
|
48131
48588
|
return false;
|
|
48132
48589
|
}
|
|
48133
|
-
const at = (row * cols + col) * 4 + 3;
|
|
48134
48590
|
// Painted, not merely covered: a dry cell holds a reading the ramp deliberately hides, and
|
|
48135
48591
|
// ruling a grid over it draws detail where the picture shows none.
|
|
48136
|
-
if (this.painted
|
|
48137
|
-
return this.
|
|
48592
|
+
if (this.painted || this.paintedRamp) {
|
|
48593
|
+
return this.paintedAt(col, row, cols);
|
|
48138
48594
|
}
|
|
48139
|
-
return this.source.data[
|
|
48595
|
+
return this.source.data[(row * cols + col) * 4 + 3] >= 128;
|
|
48140
48596
|
}
|
|
48141
48597
|
isCovered(col, row, cols) {
|
|
48142
48598
|
return Boolean(this.source && this.source.data[(row * cols + col) * 4 + 3] >= 128);
|
|
48143
48599
|
}
|
|
48144
48600
|
/*
|
|
48145
|
-
*
|
|
48146
|
-
*/
|
|
48147
|
-
widestLabel(parts) {
|
|
48148
|
-
const sample = [];
|
|
48149
|
-
if (parts.indexOf("value") >= 0) {
|
|
48150
|
-
sample.push(valueLine("-000.00", LabelUnits(this.metadata)));
|
|
48151
|
-
}
|
|
48152
|
-
const range = [];
|
|
48153
|
-
const showsRange = !TextureFrameSeriesAnimator.IsTransformed(this.metadata);
|
|
48154
|
-
if (showsRange && parts.indexOf("floor") >= 0) {
|
|
48155
|
-
range.push("min: -000.00");
|
|
48156
|
-
}
|
|
48157
|
-
if (showsRange && parts.indexOf("ceiling") >= 0) {
|
|
48158
|
-
range.push("max: -000.00");
|
|
48159
|
-
}
|
|
48160
|
-
if (range.length) {
|
|
48161
|
-
sample.push(range.join(" - "));
|
|
48162
|
-
}
|
|
48163
|
-
if (!sample.length) {
|
|
48164
|
-
return 0;
|
|
48165
|
-
}
|
|
48166
|
-
const widest = Math.max(...sample.map((line) => this.ctx.measureText(line).width));
|
|
48167
|
-
return widest + LABEL_GAP_PIXELS;
|
|
48168
|
-
}
|
|
48169
|
-
/*
|
|
48170
|
-
* The four screen corners of one cell, or null if any of them will not project.
|
|
48601
|
+
* What a label says about one reading, a line at a time.
|
|
48171
48602
|
*/
|
|
48172
|
-
|
|
48173
|
-
// On a pyramid the composited texel is not a cell. Outlining it would draw a second,
|
|
48174
|
-
// uniform lattice over the tile grid, misaligned with it everywhere.
|
|
48175
|
-
if (this.tiles && this.tiles.length) {
|
|
48176
|
-
return this.tileCellOutline(this.lonAt(col, cols), this.latAt(row, rows));
|
|
48177
|
-
}
|
|
48178
|
-
const corners = [
|
|
48179
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
48180
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
48181
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row + 0.5, rows)),
|
|
48182
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row + 0.5, rows))
|
|
48183
|
-
];
|
|
48184
|
-
return corners.every((c) => c) ? corners : null;
|
|
48185
|
-
}
|
|
48186
|
-
drawLabel(point, reading, parts, outline) {
|
|
48603
|
+
labelLines(reading, parts) {
|
|
48187
48604
|
var _a;
|
|
48188
48605
|
const dp = (_a = this.settings.decimals) !== null && _a !== void 0 ? _a : (Math.abs(reading.value) < 10 ? 2 : 1);
|
|
48189
48606
|
const lines = [];
|
|
@@ -48202,32 +48619,7 @@ var TextureValueLabels;
|
|
|
48202
48619
|
if (range.length) {
|
|
48203
48620
|
lines.push(range.join(" - "));
|
|
48204
48621
|
}
|
|
48205
|
-
|
|
48206
|
-
return;
|
|
48207
|
-
}
|
|
48208
|
-
if (outline) {
|
|
48209
|
-
this.ctx.strokeStyle = LABEL_ANCHOR_COLOR;
|
|
48210
|
-
this.ctx.lineWidth = 1.5;
|
|
48211
|
-
this.ctx.beginPath();
|
|
48212
|
-
this.ctx.moveTo(outline[0].x, outline[0].y);
|
|
48213
|
-
for (let i = 1; i < outline.length; i++) {
|
|
48214
|
-
this.ctx.lineTo(outline[i].x, outline[i].y);
|
|
48215
|
-
}
|
|
48216
|
-
this.ctx.closePath();
|
|
48217
|
-
this.ctx.stroke();
|
|
48218
|
-
}
|
|
48219
|
-
const width = Math.max(...lines.map((l) => this.ctx.measureText(l).width)) + 10;
|
|
48220
|
-
const height = lines.length * LINE_HEIGHT + 6;
|
|
48221
|
-
const top = point.y - height / 2;
|
|
48222
|
-
this.ctx.fillStyle = "rgba(16,16,14,0.72)";
|
|
48223
|
-
this.ctx.beginPath();
|
|
48224
|
-
this.ctx.roundRect(point.x - width / 2, top, width, height, LABEL_RADIUS);
|
|
48225
|
-
this.ctx.fill();
|
|
48226
|
-
lines.forEach((line, i) => {
|
|
48227
|
-
this.ctx.fillStyle = i === 0 ? "#ffffff" : "#a8a89f";
|
|
48228
|
-
this.ctx.fillText(line, point.x, top + LINE_HEIGHT * (i + 1) - 1);
|
|
48229
|
-
});
|
|
48230
|
-
this.drawn++;
|
|
48622
|
+
return lines;
|
|
48231
48623
|
}
|
|
48232
48624
|
/*
|
|
48233
48625
|
* The first cell with a reading at or near a lattice point, searched within its own block.
|
|
@@ -48254,11 +48646,24 @@ var TextureValueLabels;
|
|
|
48254
48646
|
return null;
|
|
48255
48647
|
}
|
|
48256
48648
|
paintedAt(col, row, cols) {
|
|
48257
|
-
|
|
48258
|
-
|
|
48649
|
+
const at = (row * cols + col) * 4;
|
|
48650
|
+
if (this.painted && this.painted.length === this.source.data.length) {
|
|
48651
|
+
return this.painted[at + 3] > 0;
|
|
48259
48652
|
}
|
|
48260
|
-
|
|
48261
|
-
|
|
48653
|
+
if (this.paintedRamp) {
|
|
48654
|
+
const data = this.source.data;
|
|
48655
|
+
if (data[at + 3] === 0) {
|
|
48656
|
+
return false;
|
|
48657
|
+
}
|
|
48658
|
+
// The 8 bit grey the ramp is indexed on, which is what a tinted frame would have used.
|
|
48659
|
+
const grey = TextureFrameSeriesAnimator.IsPackedValue(this.metadata)
|
|
48660
|
+
? Math.round((data[at] * 256 + data[at + 1]) / 65535 * 255)
|
|
48661
|
+
: data[at];
|
|
48662
|
+
const steps = this.paintedRamp.length / 4;
|
|
48663
|
+
const index = Math.min(steps - 1, Math.round(grey / 255 * (steps - 1)));
|
|
48664
|
+
return this.paintedRamp[index * 4 + 3] > 0;
|
|
48665
|
+
}
|
|
48666
|
+
return true;
|
|
48262
48667
|
}
|
|
48263
48668
|
// Decodes one cell, skipping anything the texture marks as having no data.
|
|
48264
48669
|
readAt(col, row, cols) {
|
|
@@ -48274,6 +48679,16 @@ var TextureValueLabels;
|
|
|
48274
48679
|
sourceMap: this.sourceMap
|
|
48275
48680
|
});
|
|
48276
48681
|
}
|
|
48682
|
+
/*
|
|
48683
|
+
* The texel holding a lon/lat, or null outside the raster.
|
|
48684
|
+
*/
|
|
48685
|
+
texelAt(lon, lat) {
|
|
48686
|
+
const cols = this.source.width;
|
|
48687
|
+
const rows = this.source.height;
|
|
48688
|
+
const col = Math.floor((lon - this.extent.West) / (this.extent.East - this.extent.West) * cols);
|
|
48689
|
+
const row = Math.floor((this.extent.North - lat) / (this.extent.North - this.extent.South) * rows);
|
|
48690
|
+
return col < 0 || row < 0 || col >= cols || row >= rows ? null : { col, row };
|
|
48691
|
+
}
|
|
48277
48692
|
lonAt(col, cols) {
|
|
48278
48693
|
return this.extent.West + ((col + 0.5) / cols) * (this.extent.East - this.extent.West);
|
|
48279
48694
|
}
|
|
@@ -48302,15 +48717,102 @@ var TextureValueLabels;
|
|
|
48302
48717
|
row1: Math.min(rows - 1, Math.ceil((this.extent.North - south) / spanLat * rows) + 1)
|
|
48303
48718
|
};
|
|
48304
48719
|
}
|
|
48720
|
+
// Metres the sheet sits at over a point, or the ellipsoid when nothing says otherwise.
|
|
48721
|
+
surfaceHeight(lon, lat) {
|
|
48722
|
+
const height = this.heightAt ? this.heightAt(lon, lat) : null;
|
|
48723
|
+
return height == null || !isFinite(height) ? 0 : height;
|
|
48724
|
+
}
|
|
48725
|
+
projectCell(col, row, cols, rows) {
|
|
48726
|
+
return this.project(this.lonAt(col, cols), this.latAt(row, rows));
|
|
48727
|
+
}
|
|
48728
|
+
// Projected at the sheet's own height, since up close the ellipsoid is metres from where it is drawn.
|
|
48305
48729
|
project(lon, lat) {
|
|
48306
|
-
|
|
48730
|
+
return this.projectInto(lon, lat) ? { x: this.scratchWindow.x, y: this.scratchWindow.y } : null;
|
|
48731
|
+
}
|
|
48732
|
+
/*
|
|
48733
|
+
* Projects into the shared scratch window position, returning whether it landed anywhere.
|
|
48734
|
+
*/
|
|
48735
|
+
projectInto(lon, lat) {
|
|
48736
|
+
const world = Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat), Ellipsoid.WGS84, this.scratchWorld);
|
|
48737
|
+
// One matrix multiply in 3D, where the general path rederives the viewport and frustum per point.
|
|
48738
|
+
if (this.viewProjection) {
|
|
48739
|
+
const clip = Matrix4.multiplyByVector(this.viewProjection, Cartesian4.fromElements(world.x, world.y, world.z, 1, this.scratchClip), this.scratchClip);
|
|
48740
|
+
if (!(clip.w > 0)) {
|
|
48741
|
+
return null;
|
|
48742
|
+
}
|
|
48743
|
+
const view = this.viewer.canvas;
|
|
48744
|
+
this.scratchWindow.x = (clip.x / clip.w * 0.5 + 0.5) * view.clientWidth;
|
|
48745
|
+
this.scratchWindow.y = (0.5 - clip.y / clip.w * 0.5) * view.clientHeight;
|
|
48746
|
+
return this.scratchWindow;
|
|
48747
|
+
}
|
|
48307
48748
|
const transforms = SceneTransforms;
|
|
48308
48749
|
// Renamed in newer Cesium, and this library runs on whatever the host app supplies.
|
|
48309
48750
|
const fn = transforms.worldToWindowCoordinates || transforms.wgs84ToWindowCoordinates;
|
|
48310
|
-
return fn ? fn.call(transforms, this.viewer.scene, world) : null;
|
|
48751
|
+
return fn ? fn.call(transforms, this.viewer.scene, world, this.scratchWindow) || null : null;
|
|
48311
48752
|
}
|
|
48312
48753
|
}
|
|
48313
48754
|
TextureValueLabels.Labels = Labels;
|
|
48755
|
+
/*
|
|
48756
|
+
* A grid of window positions projected at most once each, and the cell edges traced through them.
|
|
48757
|
+
*/
|
|
48758
|
+
class Lattice {
|
|
48759
|
+
constructor(wide, high, projectAt) {
|
|
48760
|
+
this.wide = wide;
|
|
48761
|
+
this.projectAt = projectAt;
|
|
48762
|
+
this.xs = new Float64Array(wide * high);
|
|
48763
|
+
this.ys = new Float64Array(wide * high);
|
|
48764
|
+
this.state = new Uint8Array(wide * high);
|
|
48765
|
+
}
|
|
48766
|
+
at(c, r) {
|
|
48767
|
+
const i = r * this.wide + c;
|
|
48768
|
+
if (this.state[i] === 0) {
|
|
48769
|
+
const p = this.projectAt(c, r);
|
|
48770
|
+
if (p) {
|
|
48771
|
+
this.xs[i] = p.x;
|
|
48772
|
+
this.ys[i] = p.y;
|
|
48773
|
+
this.state[i] = 1;
|
|
48774
|
+
}
|
|
48775
|
+
else {
|
|
48776
|
+
this.state[i] = 2;
|
|
48777
|
+
}
|
|
48778
|
+
}
|
|
48779
|
+
return this.state[i] === 1 ? i : -1;
|
|
48780
|
+
}
|
|
48781
|
+
edge(path, a, b) {
|
|
48782
|
+
if (a < 0 || b < 0) {
|
|
48783
|
+
return false;
|
|
48784
|
+
}
|
|
48785
|
+
path.moveTo(this.xs[a], this.ys[a]);
|
|
48786
|
+
path.lineTo(this.xs[b], this.ys[b]);
|
|
48787
|
+
return true;
|
|
48788
|
+
}
|
|
48789
|
+
/*
|
|
48790
|
+
* Adds each covered cell's edges to a path, a shared edge once, and says whether any were added.
|
|
48791
|
+
*/
|
|
48792
|
+
Trace(path, covered, cellsWide, cellsHigh) {
|
|
48793
|
+
let drew = false;
|
|
48794
|
+
for (let r = 0; r < cellsHigh; r++) {
|
|
48795
|
+
for (let c = 0; c < cellsWide; c++) {
|
|
48796
|
+
if (!covered[r * cellsWide + c]) {
|
|
48797
|
+
continue;
|
|
48798
|
+
}
|
|
48799
|
+
const tl = this.at(c, r);
|
|
48800
|
+
const tr = this.at(c + 1, r);
|
|
48801
|
+
const bl = this.at(c, r + 1);
|
|
48802
|
+
if (!(r > 0 && covered[(r - 1) * cellsWide + c])) {
|
|
48803
|
+
drew = this.edge(path, tl, tr) || drew;
|
|
48804
|
+
}
|
|
48805
|
+
if (!(c > 0 && covered[r * cellsWide + c - 1])) {
|
|
48806
|
+
drew = this.edge(path, tl, bl) || drew;
|
|
48807
|
+
}
|
|
48808
|
+
const br = this.at(c + 1, r + 1);
|
|
48809
|
+
drew = this.edge(path, tr, br) || drew;
|
|
48810
|
+
drew = this.edge(path, bl, br) || drew;
|
|
48811
|
+
}
|
|
48812
|
+
}
|
|
48813
|
+
return drew;
|
|
48814
|
+
}
|
|
48815
|
+
}
|
|
48314
48816
|
/*
|
|
48315
48817
|
* The value line. Units go on it alone: repeating them on the range underneath doubles the widest
|
|
48316
48818
|
* label to say nothing, and the two lines are plainly the same quantity.
|
|
@@ -48411,6 +48913,9 @@ in vec2 st;
|
|
|
48411
48913
|
in float skirt;
|
|
48412
48914
|
|
|
48413
48915
|
uniform sampler2D u_valueTexture;
|
|
48916
|
+
// The frame being faded away from, and how far the fade has got. The same texture at 1 when settled.
|
|
48917
|
+
uniform sampler2D u_valueTextureFrom;
|
|
48918
|
+
uniform float u_valueFade;
|
|
48414
48919
|
uniform sampler2D u_groundTexture;
|
|
48415
48920
|
uniform float u_valuePacked;
|
|
48416
48921
|
uniform float u_valueMin;
|
|
@@ -48531,18 +49036,22 @@ float decodeValue(vec4 texel) {
|
|
|
48531
49036
|
}
|
|
48532
49037
|
|
|
48533
49038
|
void main() {
|
|
48534
|
-
vec4
|
|
49039
|
+
vec4 texelTo = texture(u_valueTexture, st);
|
|
49040
|
+
vec4 texelFrom = texture(u_valueTextureFrom, st);
|
|
48535
49041
|
v_st = st;
|
|
49042
|
+
// Decoded before mixing, since blending a packed pair byte by byte reads as some other value
|
|
49043
|
+
// halfway through the fade.
|
|
49044
|
+
float raw = mix(decodeValue(texelFrom), decodeValue(texelTo), u_valueFade);
|
|
49045
|
+
float alpha = mix(texelFrom.a, texelTo.a, u_valueFade);
|
|
48536
49046
|
|
|
48537
49047
|
// Inside a region the scene knows is water, a texel with no value still has to paint, or the
|
|
48538
49048
|
// hole the region exists to close is still a hole.
|
|
48539
49049
|
// The value it paints is the shallowest the ramp is not transparent at.
|
|
48540
49050
|
float inMask = u_maskWeight * texture(u_maskTexture, st).a;
|
|
48541
|
-
float hasData = step(u_coverageCutoffVertex,
|
|
48542
|
-
float raw = decodeValue(texel);
|
|
49051
|
+
float hasData = step(u_coverageCutoffVertex, alpha);
|
|
48543
49052
|
float filled = mix(u_maskFillValue, max(raw, u_maskFillValue), hasData);
|
|
48544
49053
|
v_value = mix(raw, filled, inMask * u_maskFill);
|
|
48545
|
-
v_coverage = max(
|
|
49054
|
+
v_coverage = max(alpha, inMask * u_maskFill);
|
|
48546
49055
|
|
|
48547
49056
|
// Exaggeration stretches the span above the archive's minimum rather than the height itself. On an
|
|
48548
49057
|
// elevation the height is measured from the ellipsoid, tens of metres from any of the data, so
|
|
@@ -48585,6 +49094,8 @@ void main() {
|
|
|
48585
49094
|
`;
|
|
48586
49095
|
const FRAGMENT_SHADER_GLSL300 = `
|
|
48587
49096
|
uniform sampler2D u_valueTexture;
|
|
49097
|
+
uniform sampler2D u_valueTextureFrom;
|
|
49098
|
+
uniform float u_valueFade;
|
|
48588
49099
|
uniform sampler2D u_rampTexture;
|
|
48589
49100
|
uniform float u_coverageCutoff;
|
|
48590
49101
|
uniform vec2 u_texelSize;
|
|
@@ -48621,12 +49132,15 @@ in vec2 v_waveMetres;
|
|
|
48621
49132
|
* Scaled per channel for the same reason the vertex shader's decode is: reassembling the pair reaches
|
|
48622
49133
|
* 65535, which mediump does not have to represent, and this is the shader that may not get highp.
|
|
48623
49134
|
*/
|
|
48624
|
-
float
|
|
48625
|
-
vec4 texel = texture(u_valueTexture, uv);
|
|
49135
|
+
float decodeValue(vec4 texel) {
|
|
48626
49136
|
float packed16 = texel.r * (255.0 * 256.0 / 65535.0) + texel.g * (255.0 / 65535.0);
|
|
48627
49137
|
return mix(texel.r, packed16, u_valuePacked);
|
|
48628
49138
|
}
|
|
48629
49139
|
|
|
49140
|
+
float valueAt(vec2 uv) {
|
|
49141
|
+
return mix(decodeValue(texture(u_valueTextureFrom, uv)), decodeValue(texture(u_valueTexture, uv)), u_valueFade);
|
|
49142
|
+
}
|
|
49143
|
+
|
|
48630
49144
|
//#water
|
|
48631
49145
|
/*
|
|
48632
49146
|
* Slope of the ripple field at a point, in metres of rise per metre travelled.
|
|
@@ -49073,6 +49587,13 @@ void main() {
|
|
|
49073
49587
|
this.rampTexture = null;
|
|
49074
49588
|
this.texture = null;
|
|
49075
49589
|
this.textureDirty = true;
|
|
49590
|
+
// Fed from a fading source: the frame being left, and which pixels each texture holds.
|
|
49591
|
+
this.fade = null;
|
|
49592
|
+
this.textureFrom = null;
|
|
49593
|
+
this.uploadedTo = null;
|
|
49594
|
+
this.uploadedFrom = null;
|
|
49595
|
+
// Size of whichever value raster is bound, canvas or pixels.
|
|
49596
|
+
this.sourceDims = null;
|
|
49076
49597
|
this.shaderProgram = null;
|
|
49077
49598
|
this.shaderProgramWater = false;
|
|
49078
49599
|
this.renderState = null;
|
|
@@ -49243,6 +49764,65 @@ void main() {
|
|
|
49243
49764
|
this.maskGroundTexture = null;
|
|
49244
49765
|
}
|
|
49245
49766
|
}
|
|
49767
|
+
/**
|
|
49768
|
+
* Metres above the ellipsoid the sheet sits at over a point, or null unless a fading source is bound.
|
|
49769
|
+
*/
|
|
49770
|
+
HeightAt(lon, lat) {
|
|
49771
|
+
const pixels = this.fade ? this.fade.to : null;
|
|
49772
|
+
const dims = this.sourceDims;
|
|
49773
|
+
if (!pixels || !dims) {
|
|
49774
|
+
return null;
|
|
49775
|
+
}
|
|
49776
|
+
const fx = (lon - this.extent.West) / (this.extent.East - this.extent.West);
|
|
49777
|
+
const fy = (this.extent.North - lat) / (this.extent.North - this.extent.South);
|
|
49778
|
+
if (!(fx >= 0 && fx <= 1 && fy >= 0 && fy <= 1)) {
|
|
49779
|
+
return null;
|
|
49780
|
+
}
|
|
49781
|
+
const col = Math.min(dims.width - 1, Math.floor(fx * dims.width));
|
|
49782
|
+
const row = Math.min(dims.height - 1, Math.floor(fy * dims.height));
|
|
49783
|
+
const at = (row * dims.width + col) * 4;
|
|
49784
|
+
const coverage = pixels[at + 3] / 255;
|
|
49785
|
+
const normalised = this.packedValue
|
|
49786
|
+
? (pixels[at] * 256 + pixels[at + 1]) / 65535
|
|
49787
|
+
: pixels[at] / 255;
|
|
49788
|
+
const metres = this.valueMin + normalised * (this.valueMax - this.valueMin) * this.exaggeration;
|
|
49789
|
+
const following = this.followGround && Boolean(this.groundPixels);
|
|
49790
|
+
const ground = following ? this.groundAt(fx, fy) : 0;
|
|
49791
|
+
return this.baseHeight + ground + metres * (following ? coverage : 1);
|
|
49792
|
+
}
|
|
49793
|
+
/**
|
|
49794
|
+
* Changes whenever HeightAt would answer differently for the same frame.
|
|
49795
|
+
*/
|
|
49796
|
+
GetPlacementKey() {
|
|
49797
|
+
return [this.groundPixels ? 1 : 0, this.baseHeight, this.exaggeration, this.followGround ? 1 : 0].join("|");
|
|
49798
|
+
}
|
|
49799
|
+
/**
|
|
49800
|
+
* The ramp the sheet is painted with, as 256 RGBA steps across the value range.
|
|
49801
|
+
*/
|
|
49802
|
+
GetRampPixels() {
|
|
49803
|
+
return this.rampPixels;
|
|
49804
|
+
}
|
|
49805
|
+
/*
|
|
49806
|
+
* The packed ground raster read the way sampleGround reads it, bilinear between texel centres.
|
|
49807
|
+
*/
|
|
49808
|
+
groundAt(u, v) {
|
|
49809
|
+
const size = GROUND_SAMPLES_PER_SIDE;
|
|
49810
|
+
const pixels = this.groundPixels;
|
|
49811
|
+
const read = (x, y) => {
|
|
49812
|
+
const i = (Math.min(size - 1, Math.max(0, y)) * size + Math.min(size - 1, Math.max(0, x))) * 4;
|
|
49813
|
+
const packed = (pixels[i] << 16) + (pixels[i + 1] << 8) + pixels[i + 2];
|
|
49814
|
+
return GROUND_MIN_METRES + (packed / 16777215) * (GROUND_MAX_METRES - GROUND_MIN_METRES);
|
|
49815
|
+
};
|
|
49816
|
+
const x = u * size - 0.5;
|
|
49817
|
+
const y = v * size - 0.5;
|
|
49818
|
+
const x0 = Math.floor(x);
|
|
49819
|
+
const y0 = Math.floor(y);
|
|
49820
|
+
const fx = x - x0;
|
|
49821
|
+
const fy = y - y0;
|
|
49822
|
+
const top = read(x0, y0) + (read(x0 + 1, y0) - read(x0, y0)) * fx;
|
|
49823
|
+
const bottom = read(x0, y0 + 1) + (read(x0 + 1, y0 + 1) - read(x0, y0 + 1)) * fx;
|
|
49824
|
+
return top + (bottom - top) * fy;
|
|
49825
|
+
}
|
|
49246
49826
|
/**
|
|
49247
49827
|
* Feeds the surface from an animated frame series.
|
|
49248
49828
|
* Height and colour advance with the clock without the caller having to push each frame in.
|
|
@@ -49261,23 +49841,39 @@ void main() {
|
|
|
49261
49841
|
return;
|
|
49262
49842
|
}
|
|
49263
49843
|
const context = frameState.context;
|
|
49264
|
-
|
|
49265
|
-
|
|
49266
|
-
|
|
49267
|
-
|
|
49268
|
-
|
|
49269
|
-
}
|
|
49270
|
-
|
|
49271
|
-
|
|
49272
|
-
|
|
49273
|
-
|
|
49274
|
-
|
|
49844
|
+
const fade = this.valueSource && this.valueSource.GetValueFade
|
|
49845
|
+
? this.valueSource.GetValueFade()
|
|
49846
|
+
: null;
|
|
49847
|
+
if (fade) {
|
|
49848
|
+
this.fade = fade;
|
|
49849
|
+
this.sourceDims = { width: fade.width, height: fade.height };
|
|
49850
|
+
}
|
|
49851
|
+
else {
|
|
49852
|
+
this.fade = null;
|
|
49853
|
+
if (this.valueSource) {
|
|
49854
|
+
const canvas = this.valueSource.GetValueCanvas();
|
|
49855
|
+
if (!canvas) {
|
|
49856
|
+
// No frame decoded yet, so there is nothing to displace against.
|
|
49857
|
+
return;
|
|
49858
|
+
}
|
|
49859
|
+
const version = this.valueSource.GetPresentVersion();
|
|
49860
|
+
if (canvas !== this.source || version !== this.lastPresentVersion) {
|
|
49861
|
+
this.source = canvas;
|
|
49862
|
+
this.lastPresentVersion = version;
|
|
49863
|
+
this.textureDirty = true;
|
|
49864
|
+
}
|
|
49275
49865
|
}
|
|
49866
|
+
this.sourceDims = this.source ? { width: this.source.width, height: this.source.height } : null;
|
|
49276
49867
|
}
|
|
49277
|
-
if (!this.
|
|
49868
|
+
if (!this.sourceDims || !this.sourceDims.width || !this.sourceDims.height) {
|
|
49278
49869
|
return;
|
|
49279
49870
|
}
|
|
49280
|
-
this.
|
|
49871
|
+
if (this.fade) {
|
|
49872
|
+
this.syncFadeTextures(context);
|
|
49873
|
+
}
|
|
49874
|
+
else {
|
|
49875
|
+
this.syncTexture(context);
|
|
49876
|
+
}
|
|
49281
49877
|
this.syncRampTexture(context);
|
|
49282
49878
|
this.requestGround();
|
|
49283
49879
|
this.syncGroundTexture(context);
|
|
@@ -49325,6 +49921,10 @@ void main() {
|
|
|
49325
49921
|
this.texture.destroy();
|
|
49326
49922
|
this.texture = null;
|
|
49327
49923
|
}
|
|
49924
|
+
if (this.textureFrom) {
|
|
49925
|
+
this.textureFrom.destroy();
|
|
49926
|
+
this.textureFrom = null;
|
|
49927
|
+
}
|
|
49328
49928
|
if (this.rampTexture) {
|
|
49329
49929
|
this.rampTexture.destroy();
|
|
49330
49930
|
this.rampTexture = null;
|
|
@@ -49592,7 +50192,7 @@ void main() {
|
|
|
49592
50192
|
const southWest = Cartesian3.fromDegrees(this.extent.West, this.extent.South, 0);
|
|
49593
50193
|
const southEast = Cartesian3.fromDegrees(this.extent.East, this.extent.South, 0);
|
|
49594
50194
|
const width = Cartesian3.distance(southWest, southEast);
|
|
49595
|
-
return Math.max(1, width / Math.max(1, this.
|
|
50195
|
+
return Math.max(1, width / Math.max(1, this.sourceDims ? this.sourceDims.width : 1));
|
|
49596
50196
|
}
|
|
49597
50197
|
syncRampTexture(context) {
|
|
49598
50198
|
if (this.rampTexture) {
|
|
@@ -49613,6 +50213,67 @@ void main() {
|
|
|
49613
50213
|
})
|
|
49614
50214
|
});
|
|
49615
50215
|
}
|
|
50216
|
+
valueSampler() {
|
|
50217
|
+
// Nearest for a packed value, because filtering would blend the two bytes of the
|
|
50218
|
+
// packing rather than the values they encode, the same trap sampleGround avoids.
|
|
50219
|
+
const minification = this.packedValue
|
|
50220
|
+
? TextureMinificationFilter.NEAREST
|
|
50221
|
+
: TextureMinificationFilter.LINEAR;
|
|
50222
|
+
const magnification = this.packedValue
|
|
50223
|
+
? TextureMagnificationFilter.NEAREST
|
|
50224
|
+
: TextureMagnificationFilter.LINEAR;
|
|
50225
|
+
return new Sampler({
|
|
50226
|
+
wrapS: TextureWrap.CLAMP_TO_EDGE,
|
|
50227
|
+
wrapT: TextureWrap.CLAMP_TO_EDGE,
|
|
50228
|
+
minificationFilter: minification,
|
|
50229
|
+
magnificationFilter: magnification
|
|
50230
|
+
});
|
|
50231
|
+
}
|
|
50232
|
+
/*
|
|
50233
|
+
* Uploads value pixels into a texture, reusing it when the size still fits.
|
|
50234
|
+
*/
|
|
50235
|
+
uploadPixels(context, texture, pixels, width, height) {
|
|
50236
|
+
const view = new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength);
|
|
50237
|
+
const source = { width, height, arrayBufferView: view };
|
|
50238
|
+
if (texture && texture.width === width && texture.height === height) {
|
|
50239
|
+
texture.copyFrom({ source });
|
|
50240
|
+
return texture;
|
|
50241
|
+
}
|
|
50242
|
+
if (texture) {
|
|
50243
|
+
texture.destroy();
|
|
50244
|
+
}
|
|
50245
|
+
return new Texture({
|
|
50246
|
+
context,
|
|
50247
|
+
width,
|
|
50248
|
+
height,
|
|
50249
|
+
pixelFormat: PixelFormat.RGBA,
|
|
50250
|
+
pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
|
|
50251
|
+
source,
|
|
50252
|
+
// Row 0 is the archive's north edge, and has to stay at the top, as the canvas path does.
|
|
50253
|
+
flipY: false,
|
|
50254
|
+
sampler: this.valueSampler()
|
|
50255
|
+
});
|
|
50256
|
+
}
|
|
50257
|
+
/*
|
|
50258
|
+
* Keeps a texture each side of the fade, swapping them on a new frame so only that one is uploaded.
|
|
50259
|
+
*/
|
|
50260
|
+
syncFadeTextures(context) {
|
|
50261
|
+
const fade = this.fade;
|
|
50262
|
+
if (fade.to !== this.uploadedTo) {
|
|
50263
|
+
if (fade.from && fade.from === this.uploadedTo && this.texture) {
|
|
50264
|
+
const previous = this.textureFrom;
|
|
50265
|
+
this.textureFrom = this.texture;
|
|
50266
|
+
this.uploadedFrom = this.uploadedTo;
|
|
50267
|
+
this.texture = previous;
|
|
50268
|
+
}
|
|
50269
|
+
this.texture = this.uploadPixels(context, this.texture, fade.to, fade.width, fade.height);
|
|
50270
|
+
this.uploadedTo = fade.to;
|
|
50271
|
+
}
|
|
50272
|
+
if (fade.from && fade.from !== this.uploadedFrom) {
|
|
50273
|
+
this.textureFrom = this.uploadPixels(context, this.textureFrom, fade.from, fade.width, fade.height);
|
|
50274
|
+
this.uploadedFrom = fade.from;
|
|
50275
|
+
}
|
|
50276
|
+
}
|
|
49616
50277
|
syncTexture(context) {
|
|
49617
50278
|
const sizeChanged = this.texture
|
|
49618
50279
|
&& (this.texture.width !== this.source.width || this.texture.height !== this.source.height);
|
|
@@ -49620,27 +50281,15 @@ void main() {
|
|
|
49620
50281
|
if (this.texture) {
|
|
49621
50282
|
this.texture.destroy();
|
|
49622
50283
|
}
|
|
49623
|
-
// Nearest for a packed value, because filtering would blend the two bytes of the
|
|
49624
|
-
// packing rather than the values they encode, the same trap sampleGround avoids.
|
|
49625
|
-
const minification = this.packedValue
|
|
49626
|
-
? TextureMinificationFilter.NEAREST
|
|
49627
|
-
: TextureMinificationFilter.LINEAR;
|
|
49628
|
-
const magnification = this.packedValue
|
|
49629
|
-
? TextureMagnificationFilter.NEAREST
|
|
49630
|
-
: TextureMagnificationFilter.LINEAR;
|
|
49631
50284
|
this.texture = new Texture({
|
|
49632
50285
|
context,
|
|
49633
50286
|
source: this.source,
|
|
49634
50287
|
// Cesium flips canvas rows on upload by default, which would put the archive's
|
|
49635
50288
|
// row 0 (its north edge) at the south edge and mirror the whole surface.
|
|
49636
50289
|
flipY: false,
|
|
49637
|
-
sampler:
|
|
49638
|
-
wrapS: TextureWrap.CLAMP_TO_EDGE,
|
|
49639
|
-
wrapT: TextureWrap.CLAMP_TO_EDGE,
|
|
49640
|
-
minificationFilter: minification,
|
|
49641
|
-
magnificationFilter: magnification
|
|
49642
|
-
})
|
|
50290
|
+
sampler: this.valueSampler()
|
|
49643
50291
|
});
|
|
50292
|
+
this.uploadedTo = null;
|
|
49644
50293
|
this.textureDirty = false;
|
|
49645
50294
|
return;
|
|
49646
50295
|
}
|
|
@@ -49703,7 +50352,7 @@ void main() {
|
|
|
49703
50352
|
* carry, so the ladder is capped one step above the texel count rather than run to its top.
|
|
49704
50353
|
*/
|
|
49705
50354
|
tierCeiling() {
|
|
49706
|
-
const source = this.
|
|
50355
|
+
const source = this.sourceDims;
|
|
49707
50356
|
if (!source || !source.width || !source.height) {
|
|
49708
50357
|
return TIERS[TIERS.length - 1];
|
|
49709
50358
|
}
|
|
@@ -49781,6 +50430,10 @@ void main() {
|
|
|
49781
50430
|
owner: this,
|
|
49782
50431
|
uniformMap: {
|
|
49783
50432
|
u_valueTexture: () => self.texture,
|
|
50433
|
+
u_valueTextureFrom: () => (self.fade && self.fade.from && self.textureFrom
|
|
50434
|
+
? self.textureFrom
|
|
50435
|
+
: self.texture),
|
|
50436
|
+
u_valueFade: () => (self.fade && self.fade.from ? self.fade.mix : 1),
|
|
49784
50437
|
u_groundTexture: () => self.groundTexture,
|
|
49785
50438
|
u_valuePacked: () => (self.packedValue ? 1 : 0),
|
|
49786
50439
|
u_groundWeight: () => (self.followGround && self.groundPixels ? 1 : 0),
|
|
@@ -49818,7 +50471,7 @@ void main() {
|
|
|
49818
50471
|
: 0,
|
|
49819
50472
|
u_coverageCutoff: () => COVERAGE_CUTOFF,
|
|
49820
50473
|
u_rampTexture: () => self.rampTexture,
|
|
49821
|
-
u_texelSize: () => new Cartesian2(1 / self.
|
|
50474
|
+
u_texelSize: () => new Cartesian2(1 / self.sourceDims.width, 1 / self.sourceDims.height),
|
|
49822
50475
|
u_waterOn: () => (self.water ? 1 : 0),
|
|
49823
50476
|
u_waterWavelength: () => (self.water && self.water.wavelength)
|
|
49824
50477
|
|| DEFAULT_WATER_WAVELENGTH_METRES,
|
|
@@ -50190,6 +50843,10 @@ var EntityRenderEnginePolygon;
|
|
|
50190
50843
|
syncTextureValueLabels({
|
|
50191
50844
|
cEntity,
|
|
50192
50845
|
viewer: params.viewer,
|
|
50846
|
+
surface: drawsDisplacedSurface ? cEntity[DISPLACED_SURFACE_KEY] : null,
|
|
50847
|
+
hover: params.visualRegister
|
|
50848
|
+
? { register: params.visualRegister, entityId: String(entity.Bruce.ID), menuItemId: params.menuItemId }
|
|
50849
|
+
: undefined,
|
|
50193
50850
|
settings: overlaySettings(style, entity, params.tags),
|
|
50194
50851
|
extent: labelArchive
|
|
50195
50852
|
? (drawsDisplacedSurface ? archiveExtent(labelArchive.metadata, posses) : drapeExtent)
|
|
@@ -50496,6 +51153,7 @@ var EntityRenderEnginePolygon;
|
|
|
50496
51153
|
// rendered: null
|
|
50497
51154
|
rendered: (_c = params.rendered) === null || _c === void 0 ? void 0 : _c.get(entity.Bruce.ID),
|
|
50498
51155
|
menuItemId: params.menuItemId,
|
|
51156
|
+
visualRegister: params.visualRegister,
|
|
50499
51157
|
onSeriesDiscovered: params.onSeriesDiscovered
|
|
50500
51158
|
});
|
|
50501
51159
|
}
|
|
@@ -50933,8 +51591,8 @@ function wantsLabels(style) {
|
|
|
50933
51591
|
*/
|
|
50934
51592
|
const LABEL_GRID_COLOR = "rgba(255,255,255,0.55)";
|
|
50935
51593
|
const LABEL_GRID_WIDTH_PIXELS = 1;
|
|
50936
|
-
const LABEL_MIN_SPACING_PIXELS =
|
|
50937
|
-
const LABEL_MAX_LABELS =
|
|
51594
|
+
const LABEL_MIN_SPACING_PIXELS = 140;
|
|
51595
|
+
const LABEL_MAX_LABELS = 60;
|
|
50938
51596
|
function overlaySettings(style, entity, tags) {
|
|
50939
51597
|
if (!wantsLabels(style)) {
|
|
50940
51598
|
return undefined;
|
|
@@ -50954,6 +51612,7 @@ function overlaySettings(style, entity, tags) {
|
|
|
50954
51612
|
*/
|
|
50955
51613
|
function syncTextureValueLabels(params) {
|
|
50956
51614
|
const { cEntity, viewer, settings, extent, animator, metadata } = params;
|
|
51615
|
+
const surface = params.surface && !params.surface.isDestroyed() ? params.surface : null;
|
|
50957
51616
|
const existing = cEntity[TEXTURE_VALUE_LABELS_KEY];
|
|
50958
51617
|
// Offline has no extent to rebuild the overlay from, so an existing one is left running.
|
|
50959
51618
|
if (params.offline && existing) {
|
|
@@ -50972,8 +51631,13 @@ function syncTextureValueLabels(params) {
|
|
|
50972
51631
|
West: tile.West, East: tile.East, South: tile.South, North: tile.North,
|
|
50973
51632
|
ResolutionX: tile.ResolutionX, ResolutionY: tile.ResolutionY
|
|
50974
51633
|
})),
|
|
50975
|
-
clipRing: params.clipRing
|
|
51634
|
+
clipRing: params.clipRing,
|
|
51635
|
+
heightAt: surface ? (lon, lat) => surface.HeightAt(lon, lat) : undefined,
|
|
51636
|
+
heightKey: surface ? () => surface.GetPlacementKey() : undefined,
|
|
51637
|
+
hover: params.hover
|
|
50976
51638
|
});
|
|
51639
|
+
// A surface keeps no tinted frame, so whether a cell was painted is read off its ramp.
|
|
51640
|
+
labels.SetPaintedRamp(surface ? surface.GetRampPixels() : null);
|
|
50977
51641
|
let lastVersion = -1;
|
|
50978
51642
|
let appliedFloor = null;
|
|
50979
51643
|
let appliedCeiling = null;
|
|
@@ -50985,15 +51649,15 @@ function syncTextureValueLabels(params) {
|
|
|
50985
51649
|
if (animator.IsDisposed()) {
|
|
50986
51650
|
return;
|
|
50987
51651
|
}
|
|
50988
|
-
const canvas = animator.GetValueCanvas();
|
|
50989
|
-
if (!canvas) {
|
|
50990
|
-
return;
|
|
50991
|
-
}
|
|
50992
51652
|
const version = animator.GetPresentVersion();
|
|
50993
51653
|
if (version !== lastVersion) {
|
|
51654
|
+
// Read from the frame cache, since reading the value canvas back copies a whole frame.
|
|
51655
|
+
const raster = animator.GetValueRaster();
|
|
51656
|
+
if (!raster) {
|
|
51657
|
+
return;
|
|
51658
|
+
}
|
|
50994
51659
|
lastVersion = version;
|
|
50995
|
-
|
|
50996
|
-
labels.SetSource(ctx.getImageData(0, 0, canvas.width, canvas.height));
|
|
51660
|
+
labels.SetSource(raster);
|
|
50997
51661
|
labels.SetPainted(animator.GetDisplayedPixels());
|
|
50998
51662
|
}
|
|
50999
51663
|
// Tracked by reference rather than latched on the first one to arrive: a tiled archive
|
|
@@ -52732,7 +53396,7 @@ var StyleUtils;
|
|
|
52732
53396
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
52733
53397
|
})(StyleUtils || (StyleUtils = {}));
|
|
52734
53398
|
|
|
52735
|
-
const VERSION = "7.5.
|
|
53399
|
+
const VERSION = "7.5.9";
|
|
52736
53400
|
/**
|
|
52737
53401
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
52738
53402
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|