bruce-cesium 6.9.7 → 6.9.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 +501 -39
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +499 -37
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/internal/image-utils.js +35 -0
- package/dist/lib/internal/image-utils.js.map +1 -0
- package/dist/lib/internal/series-segment.js +3 -0
- package/dist/lib/internal/series-segment.js.map +1 -0
- package/dist/lib/rendering/entity-render-engine-polygon.js +146 -35
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine.js +1 -0
- package/dist/lib/rendering/entity-render-engine.js.map +1 -1
- package/dist/lib/rendering/menu-item-manager.js +46 -1
- package/dist/lib/rendering/menu-item-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +3 -1
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +281 -0
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -0
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/internal/image-utils.d.ts +10 -0
- package/dist/types/internal/series-segment.d.ts +10 -0
- package/dist/types/rendering/entity-render-engine-polygon.d.ts +4 -0
- package/dist/types/rendering/entity-render-engine.d.ts +2 -0
- package/dist/types/rendering/menu-item-manager.d.ts +19 -0
- package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +3 -0
- package/dist/types/rendering/texture-frame-series-animator.d.ts +93 -0
- package/package.json +2 -2
package/dist/bruce-cesium.umd.js
CHANGED
|
@@ -9661,13 +9661,14 @@
|
|
|
9661
9661
|
// Type IDs that we'll allow to be rendered when receiving Entities.
|
|
9662
9662
|
// This might be exactly the Menu Item Type ID because of local data sources.
|
|
9663
9663
|
this.allowedTypeIDs = [];
|
|
9664
|
-
const { viewer, apiGetter, monitor, item, register: visualsManager, sharedGetters } = params;
|
|
9664
|
+
const { viewer, apiGetter, monitor, item, register: visualsManager, sharedGetters, onSeriesDiscovered } = params;
|
|
9665
9665
|
this.viewer = viewer;
|
|
9666
9666
|
this.sharedGetters = sharedGetters;
|
|
9667
9667
|
this.monitor = monitor;
|
|
9668
9668
|
this.apiGetter = apiGetter;
|
|
9669
9669
|
this.item = item;
|
|
9670
9670
|
this.visualsManager = visualsManager;
|
|
9671
|
+
this.onSeriesDiscovered = onSeriesDiscovered;
|
|
9671
9672
|
this.useGeojson = item.renderAsGeojson == true;
|
|
9672
9673
|
if (item.enableClustering) {
|
|
9673
9674
|
this.clustering = new PointClustering(this.visualsManager, this.item.id);
|
|
@@ -10636,6 +10637,7 @@
|
|
|
10636
10637
|
apiGetter: this.apiGetter,
|
|
10637
10638
|
entities: entities,
|
|
10638
10639
|
menuItemId: this.item.id,
|
|
10640
|
+
onSeriesDiscovered: this.onSeriesDiscovered,
|
|
10639
10641
|
visualRegister: this.visualsManager,
|
|
10640
10642
|
zoomControl: this.zoomControl,
|
|
10641
10643
|
entitiesHistoric: entitiesHistoric,
|
|
@@ -22197,6 +22199,46 @@
|
|
|
22197
22199
|
}
|
|
22198
22200
|
return this.onUpdate;
|
|
22199
22201
|
}
|
|
22202
|
+
get OnSeriesUpdated() {
|
|
22203
|
+
if (!this.onSeriesUpdated) {
|
|
22204
|
+
this.onSeriesUpdated = new BModels.BruceEvent();
|
|
22205
|
+
}
|
|
22206
|
+
return this.onSeriesUpdated;
|
|
22207
|
+
}
|
|
22208
|
+
/**
|
|
22209
|
+
* Currently known series segments for a menu item (empty array if none discovered yet).
|
|
22210
|
+
*/
|
|
22211
|
+
GetSeriesSegments(itemId) {
|
|
22212
|
+
return this.seriesSegments.get(itemId) || [];
|
|
22213
|
+
}
|
|
22214
|
+
/**
|
|
22215
|
+
* @param itemId
|
|
22216
|
+
* @param segment
|
|
22217
|
+
*/
|
|
22218
|
+
RecordSeriesSegment(itemId, segment) {
|
|
22219
|
+
const existing = this.seriesSegments.get(itemId) || [];
|
|
22220
|
+
const idx = existing.findIndex((s) => s.key === segment.key);
|
|
22221
|
+
if (idx !== -1) {
|
|
22222
|
+
const current = existing[idx];
|
|
22223
|
+
if (current.min.getTime() === segment.min.getTime() && current.max.getTime() === segment.max.getTime() && current.label === segment.label) {
|
|
22224
|
+
return; // No-op.
|
|
22225
|
+
}
|
|
22226
|
+
existing[idx] = segment;
|
|
22227
|
+
}
|
|
22228
|
+
else {
|
|
22229
|
+
existing.push(segment);
|
|
22230
|
+
}
|
|
22231
|
+
this.seriesSegments.set(itemId, existing);
|
|
22232
|
+
this.OnSeriesUpdated.Trigger({ itemId, segments: existing });
|
|
22233
|
+
}
|
|
22234
|
+
/**
|
|
22235
|
+
* Clears any series segments recorded for a menu item, eg when it's disabled/removed.
|
|
22236
|
+
*/
|
|
22237
|
+
ClearSeriesSegments(itemId) {
|
|
22238
|
+
if (this.seriesSegments.delete(itemId)) {
|
|
22239
|
+
this.OnSeriesUpdated.Trigger({ itemId, segments: [] });
|
|
22240
|
+
}
|
|
22241
|
+
}
|
|
22200
22242
|
get Monitor() {
|
|
22201
22243
|
return this.sharedMonitor;
|
|
22202
22244
|
}
|
|
@@ -22204,6 +22246,9 @@
|
|
|
22204
22246
|
this.items = [];
|
|
22205
22247
|
this.pendingHandoffPool = [];
|
|
22206
22248
|
this.onUpdate = null;
|
|
22249
|
+
// Segments discovered so far per menu item ID.
|
|
22250
|
+
this.seriesSegments = new Map();
|
|
22251
|
+
this.onSeriesUpdated = null;
|
|
22207
22252
|
let { viewer, visualsRegister, getters } = params;
|
|
22208
22253
|
this.viewer = viewer;
|
|
22209
22254
|
if (!getters) {
|
|
@@ -22429,7 +22474,8 @@
|
|
|
22429
22474
|
apiGetter: params.apiGetter,
|
|
22430
22475
|
monitor: this.sharedMonitor,
|
|
22431
22476
|
item: params.item,
|
|
22432
|
-
sharedGetters: this.sharedGetters
|
|
22477
|
+
sharedGetters: this.sharedGetters,
|
|
22478
|
+
onSeriesDiscovered: (segment) => this.RecordSeriesSegment(rItem.id, segment)
|
|
22433
22479
|
});
|
|
22434
22480
|
break;
|
|
22435
22481
|
case BModels.MenuItem.EType.EntitiesLoaded:
|
|
@@ -22655,6 +22701,7 @@
|
|
|
22655
22701
|
}
|
|
22656
22702
|
}
|
|
22657
22703
|
this.items = this.items.filter(x => x.id !== id);
|
|
22704
|
+
this.ClearSeriesSegments(item.id);
|
|
22658
22705
|
(_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
|
|
22659
22706
|
}
|
|
22660
22707
|
}
|
|
@@ -36618,6 +36665,312 @@
|
|
|
36618
36665
|
return heightRef;
|
|
36619
36666
|
}
|
|
36620
36667
|
|
|
36668
|
+
/**
|
|
36669
|
+
* Remaps a (assumed grayscale, server-rendered black-to-white) image's pixels into a gradient between minColor/maxColor,
|
|
36670
|
+
* preserving source alpha blended with the mask's own alpha.
|
|
36671
|
+
* @param imageData
|
|
36672
|
+
* @param minColor
|
|
36673
|
+
* @param maxColor
|
|
36674
|
+
*/
|
|
36675
|
+
function ApplyGrayscaleColorMask(imageData, minColor, maxColor) {
|
|
36676
|
+
const data = imageData.data;
|
|
36677
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
36678
|
+
// The texture is rendered black-to-white server-side, so any channel carries the value.
|
|
36679
|
+
const t = data[i] / 255;
|
|
36680
|
+
data[i] = Math.round(minColor.red + (maxColor.red - minColor.red) * t);
|
|
36681
|
+
data[i + 1] = Math.round(minColor.green + (maxColor.green - minColor.green) * t);
|
|
36682
|
+
data[i + 2] = Math.round(minColor.blue + (maxColor.blue - minColor.blue) * t);
|
|
36683
|
+
// Preserve source transparency (eg unrasterized pixels) blended with the mask's own alpha.
|
|
36684
|
+
const srcAlpha = data[i + 3] / 255;
|
|
36685
|
+
const maskAlpha = minColor.alpha + (maxColor.alpha - minColor.alpha) * t;
|
|
36686
|
+
data[i + 3] = Math.round(srcAlpha * maskAlpha * 255);
|
|
36687
|
+
}
|
|
36688
|
+
}
|
|
36689
|
+
function loadImage(src) {
|
|
36690
|
+
return new Promise((res, rej) => {
|
|
36691
|
+
const image = new Image();
|
|
36692
|
+
image.onload = () => res(image);
|
|
36693
|
+
image.onerror = (e) => rej(e);
|
|
36694
|
+
image.src = src;
|
|
36695
|
+
});
|
|
36696
|
+
}
|
|
36697
|
+
|
|
36698
|
+
var TextureFrameSeriesAnimator;
|
|
36699
|
+
(function (TextureFrameSeriesAnimator) {
|
|
36700
|
+
/**
|
|
36701
|
+
* Detects whether a ClientFile's `Data.generation` metadata describes a frame archive rather than a single static image,
|
|
36702
|
+
* so a caller can decide whether to construct an Animator or fall back to the existing static-texture path.
|
|
36703
|
+
* @param data ClientFile.Data (the `IFile.Data` field), as returned by ClientFile.Get().
|
|
36704
|
+
*/
|
|
36705
|
+
function IsFrameArchiveMetadata(data) {
|
|
36706
|
+
return Boolean(data && data.generation && Array.isArray(data.generation.Frames) && data.generation.Frames.length > 0);
|
|
36707
|
+
}
|
|
36708
|
+
TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
|
|
36709
|
+
const DEFAULT_CROSSFADE_MS = 900;
|
|
36710
|
+
const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
|
|
36711
|
+
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
36712
|
+
class Animator {
|
|
36713
|
+
constructor(options) {
|
|
36714
|
+
var _a;
|
|
36715
|
+
this.removeOnTick = null;
|
|
36716
|
+
this.removeCrossfadeTick = null;
|
|
36717
|
+
this.disposed = false;
|
|
36718
|
+
this.currentFrameIndex = -1;
|
|
36719
|
+
// Single-in-flight fetch queue: only one Range GET outstanding at a time, and the NEXT one
|
|
36720
|
+
// is chosen right before sending, this is what makes reprioritizing mid-scrub take effect
|
|
36721
|
+
// immediately instead of draining a backlog of stale requests first.
|
|
36722
|
+
this.inFlightIndex = -1;
|
|
36723
|
+
this.pendingIndex = -1;
|
|
36724
|
+
this.displayedPixels = null;
|
|
36725
|
+
this.fadeFromPixels = null;
|
|
36726
|
+
this.fadeToIndex = -1;
|
|
36727
|
+
this.fadeToDims = null;
|
|
36728
|
+
this.fadeStartMs = null;
|
|
36729
|
+
// Two-canvas pool: Cesium compares image references each frame, so alternating between
|
|
36730
|
+
// pool[0]/pool[1] gives a new object reference whenever we actually present a new frame,
|
|
36731
|
+
// forcing Cesium to re-upload the texture only when something changed.
|
|
36732
|
+
this.pool = [document.createElement("canvas"), document.createElement("canvas")];
|
|
36733
|
+
this.poolIdx = 0;
|
|
36734
|
+
if (!options.entity.polygon) {
|
|
36735
|
+
throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
|
|
36736
|
+
}
|
|
36737
|
+
if (!options.frames || options.frames.length === 0) {
|
|
36738
|
+
throw new Error("TextureFrameSeriesAnimator requires at least one frame.");
|
|
36739
|
+
}
|
|
36740
|
+
this.viewer = options.viewer;
|
|
36741
|
+
this.entity = options.entity;
|
|
36742
|
+
this.archiveUrl = options.archiveUrl;
|
|
36743
|
+
this.frames = options.frames;
|
|
36744
|
+
this.crossfadeMs = (_a = options.crossfadeMs) !== null && _a !== void 0 ? _a : DEFAULT_CROSSFADE_MS;
|
|
36745
|
+
const mask = options.textureColorMask;
|
|
36746
|
+
this.lowColor = (mask && BModels.Color.ColorFromStr(mask.minColor)) || DEFAULT_LOW_COLOR;
|
|
36747
|
+
this.highColor = (mask && BModels.Color.ColorFromStr(mask.maxColor)) || DEFAULT_HIGH_COLOR;
|
|
36748
|
+
this.frameDates = this.frames.map((f) => Cesium.JulianDate.fromIso8601(f.Timestamp));
|
|
36749
|
+
this.frameCache = new Array(this.frames.length).fill(null);
|
|
36750
|
+
this.frameDims = new Array(this.frames.length).fill(null);
|
|
36751
|
+
this.originalMaterial = options.entity.polygon.material;
|
|
36752
|
+
this.imageProperty = new Cesium.CallbackProperty(() => this.pool[this.poolIdx], false);
|
|
36753
|
+
options.entity.polygon.material = new Cesium.ImageMaterialProperty({ image: this.imageProperty, transparent: true });
|
|
36754
|
+
this.removeCrossfadeTick = this.viewer.scene.postUpdate.addEventListener(() => this.tickCrossfade());
|
|
36755
|
+
this.removeOnTick = this.viewer.clock.onTick.addEventListener((clock) => this.onClockTick(clock.currentTime));
|
|
36756
|
+
this.onClockTick(this.viewer.clock.currentTime);
|
|
36757
|
+
}
|
|
36758
|
+
/**
|
|
36759
|
+
* Explicit teardown: stops driving the entity's material, abandons any in-flight/pending fetch (results are simply ignored when they land),
|
|
36760
|
+
* and restores whatever material the entity had before construction. Never called automatically.
|
|
36761
|
+
*/
|
|
36762
|
+
Dispose() {
|
|
36763
|
+
if (this.disposed) {
|
|
36764
|
+
return;
|
|
36765
|
+
}
|
|
36766
|
+
this.disposed = true;
|
|
36767
|
+
if (this.removeOnTick) {
|
|
36768
|
+
this.removeOnTick();
|
|
36769
|
+
this.removeOnTick = null;
|
|
36770
|
+
}
|
|
36771
|
+
if (this.removeCrossfadeTick) {
|
|
36772
|
+
this.removeCrossfadeTick();
|
|
36773
|
+
this.removeCrossfadeTick = null;
|
|
36774
|
+
}
|
|
36775
|
+
if (this.entity && this.entity.polygon) {
|
|
36776
|
+
this.entity.polygon.material = this.originalMaterial;
|
|
36777
|
+
}
|
|
36778
|
+
}
|
|
36779
|
+
IsDisposed() {
|
|
36780
|
+
return this.disposed;
|
|
36781
|
+
}
|
|
36782
|
+
/**
|
|
36783
|
+
* The archive URL this instance was constructed with,
|
|
36784
|
+
* lets a caller re-rendering the same entity tell whether an existing instance is already correct, without reaching into private state.
|
|
36785
|
+
*/
|
|
36786
|
+
GetArchiveUrl() {
|
|
36787
|
+
return this.archiveUrl;
|
|
36788
|
+
}
|
|
36789
|
+
onClockTick(currentTime) {
|
|
36790
|
+
if (this.disposed) {
|
|
36791
|
+
return;
|
|
36792
|
+
}
|
|
36793
|
+
this.swapToFrame(this.findFrameIndex(currentTime));
|
|
36794
|
+
}
|
|
36795
|
+
// Binary-search the frame whose timestamp is <= currentTime.
|
|
36796
|
+
findFrameIndex(currentTime) {
|
|
36797
|
+
const dates = this.frameDates;
|
|
36798
|
+
let lo = 0;
|
|
36799
|
+
let hi = dates.length - 1;
|
|
36800
|
+
if (Cesium.JulianDate.lessThanOrEquals(currentTime, dates[0])) {
|
|
36801
|
+
return 0;
|
|
36802
|
+
}
|
|
36803
|
+
if (Cesium.JulianDate.greaterThanOrEquals(currentTime, dates[hi])) {
|
|
36804
|
+
return hi;
|
|
36805
|
+
}
|
|
36806
|
+
while (lo < hi) {
|
|
36807
|
+
const mid = (lo + hi + 1) >> 1;
|
|
36808
|
+
if (Cesium.JulianDate.lessThanOrEquals(dates[mid], currentTime)) {
|
|
36809
|
+
lo = mid;
|
|
36810
|
+
}
|
|
36811
|
+
else {
|
|
36812
|
+
hi = mid - 1;
|
|
36813
|
+
}
|
|
36814
|
+
}
|
|
36815
|
+
return lo;
|
|
36816
|
+
}
|
|
36817
|
+
// Called with the discrete target frame (never a fractional/interpolated index).
|
|
36818
|
+
// Scrubbing rapidly just retargets what's fetched next.
|
|
36819
|
+
swapToFrame(idx) {
|
|
36820
|
+
if (idx === this.currentFrameIndex) {
|
|
36821
|
+
return;
|
|
36822
|
+
}
|
|
36823
|
+
this.currentFrameIndex = idx;
|
|
36824
|
+
if (this.frameCache[idx]) {
|
|
36825
|
+
this.beginCrossfadeTo(idx);
|
|
36826
|
+
return;
|
|
36827
|
+
}
|
|
36828
|
+
this.ensureFrame(idx);
|
|
36829
|
+
}
|
|
36830
|
+
ensureFrame(idx) {
|
|
36831
|
+
if (this.frameCache[idx]) {
|
|
36832
|
+
return;
|
|
36833
|
+
}
|
|
36834
|
+
if (this.inFlightIndex === idx) {
|
|
36835
|
+
return;
|
|
36836
|
+
}
|
|
36837
|
+
this.pendingIndex = idx;
|
|
36838
|
+
this.pumpFetchQueue();
|
|
36839
|
+
}
|
|
36840
|
+
// Nearest still-uncached frame to wherever the viewer CURRENTLY is.
|
|
36841
|
+
nearestUncachedFrame() {
|
|
36842
|
+
const n = this.frameCache.length;
|
|
36843
|
+
if (this.currentFrameIndex < 0) {
|
|
36844
|
+
return this.frameCache[0] ? -1 : 0;
|
|
36845
|
+
}
|
|
36846
|
+
for (let d = 0; d < n; d++) {
|
|
36847
|
+
const forward = this.currentFrameIndex + d;
|
|
36848
|
+
if (forward < n && !this.frameCache[forward]) {
|
|
36849
|
+
return forward;
|
|
36850
|
+
}
|
|
36851
|
+
const backward = this.currentFrameIndex - d;
|
|
36852
|
+
if (backward >= 0 && !this.frameCache[backward]) {
|
|
36853
|
+
return backward;
|
|
36854
|
+
}
|
|
36855
|
+
}
|
|
36856
|
+
return -1;
|
|
36857
|
+
}
|
|
36858
|
+
pumpFetchQueue() {
|
|
36859
|
+
if (this.disposed || this.inFlightIndex !== -1) {
|
|
36860
|
+
return;
|
|
36861
|
+
}
|
|
36862
|
+
let next = -1;
|
|
36863
|
+
if (this.pendingIndex !== -1) {
|
|
36864
|
+
next = this.pendingIndex;
|
|
36865
|
+
this.pendingIndex = -1;
|
|
36866
|
+
}
|
|
36867
|
+
else {
|
|
36868
|
+
next = this.nearestUncachedFrame();
|
|
36869
|
+
}
|
|
36870
|
+
if (next === -1) {
|
|
36871
|
+
return;
|
|
36872
|
+
}
|
|
36873
|
+
this.inFlightIndex = next;
|
|
36874
|
+
this.fetchAndTint(next)
|
|
36875
|
+
.catch(() => {
|
|
36876
|
+
// Eating it.
|
|
36877
|
+
})
|
|
36878
|
+
.finally(() => {
|
|
36879
|
+
this.inFlightIndex = -1;
|
|
36880
|
+
if (!this.disposed) {
|
|
36881
|
+
this.pumpFetchQueue();
|
|
36882
|
+
}
|
|
36883
|
+
});
|
|
36884
|
+
}
|
|
36885
|
+
async fetchAndTint(idx) {
|
|
36886
|
+
const entry = this.frames[idx];
|
|
36887
|
+
const start = entry.ByteOffset;
|
|
36888
|
+
const end = entry.ByteOffset + entry.ByteLength - 1;
|
|
36889
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${start}-${end}` } });
|
|
36890
|
+
const buffer = await response.arrayBuffer();
|
|
36891
|
+
if (this.disposed) {
|
|
36892
|
+
return;
|
|
36893
|
+
}
|
|
36894
|
+
const decoded = await this.decodeAndTint(buffer);
|
|
36895
|
+
if (this.disposed) {
|
|
36896
|
+
return;
|
|
36897
|
+
}
|
|
36898
|
+
this.frameCache[idx] = decoded.pixels;
|
|
36899
|
+
this.frameDims[idx] = { width: decoded.width, height: decoded.height };
|
|
36900
|
+
if (idx === this.currentFrameIndex) {
|
|
36901
|
+
this.beginCrossfadeTo(idx);
|
|
36902
|
+
}
|
|
36903
|
+
}
|
|
36904
|
+
/**
|
|
36905
|
+
* Decodes one frame's raw PNG bytes (as returned by a Range GET) and applies the grayscale color mask.
|
|
36906
|
+
*/
|
|
36907
|
+
async decodeAndTint(buffer) {
|
|
36908
|
+
const blob = new Blob([buffer], { type: "image/png" });
|
|
36909
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
36910
|
+
let image;
|
|
36911
|
+
try {
|
|
36912
|
+
image = await loadImage(objectUrl);
|
|
36913
|
+
}
|
|
36914
|
+
finally {
|
|
36915
|
+
URL.revokeObjectURL(objectUrl);
|
|
36916
|
+
}
|
|
36917
|
+
const canvas = document.createElement("canvas");
|
|
36918
|
+
canvas.width = image.width;
|
|
36919
|
+
canvas.height = image.height;
|
|
36920
|
+
const ctx = canvas.getContext("2d");
|
|
36921
|
+
ctx.drawImage(image, 0, 0);
|
|
36922
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
36923
|
+
ApplyGrayscaleColorMask(imageData, this.lowColor, this.highColor);
|
|
36924
|
+
return { pixels: imageData.data, width: canvas.width, height: canvas.height };
|
|
36925
|
+
}
|
|
36926
|
+
beginCrossfadeTo(idx) {
|
|
36927
|
+
const to = this.frameCache[idx];
|
|
36928
|
+
this.fadeFromPixels = this.displayedPixels === null ? new Uint8ClampedArray(to.length) : this.displayedPixels;
|
|
36929
|
+
this.fadeToIndex = idx;
|
|
36930
|
+
this.fadeToDims = this.frameDims[idx];
|
|
36931
|
+
this.fadeStartMs = Now();
|
|
36932
|
+
}
|
|
36933
|
+
tickCrossfade() {
|
|
36934
|
+
if (this.disposed || this.fadeStartMs === null) {
|
|
36935
|
+
return;
|
|
36936
|
+
}
|
|
36937
|
+
const t = Math.min(1, (Now() - this.fadeStartMs) / this.crossfadeMs);
|
|
36938
|
+
const eased = easeInOutQuad(t);
|
|
36939
|
+
const from = this.fadeFromPixels;
|
|
36940
|
+
const to = this.frameCache[this.fadeToIndex];
|
|
36941
|
+
const n = Math.min(from.length, to.length);
|
|
36942
|
+
const blended = new Uint8ClampedArray(to.length);
|
|
36943
|
+
for (let i = 0; i < n; i++) {
|
|
36944
|
+
blended[i] = from[i] + (to[i] - from[i]) * eased;
|
|
36945
|
+
}
|
|
36946
|
+
this.presentPixels(blended, this.fadeToDims);
|
|
36947
|
+
if (t >= 1) {
|
|
36948
|
+
this.fadeStartMs = null;
|
|
36949
|
+
this.fadeFromPixels = null;
|
|
36950
|
+
}
|
|
36951
|
+
}
|
|
36952
|
+
presentPixels(pixels, dims) {
|
|
36953
|
+
this.poolIdx = 1 - this.poolIdx;
|
|
36954
|
+
const target = this.pool[this.poolIdx];
|
|
36955
|
+
target.width = dims.width;
|
|
36956
|
+
target.height = dims.height;
|
|
36957
|
+
const ctx = target.getContext("2d");
|
|
36958
|
+
const imgData = ctx.createImageData(dims.width, dims.height);
|
|
36959
|
+
imgData.data.set(pixels);
|
|
36960
|
+
ctx.putImageData(imgData, 0, 0);
|
|
36961
|
+
this.displayedPixels = pixels;
|
|
36962
|
+
}
|
|
36963
|
+
}
|
|
36964
|
+
TextureFrameSeriesAnimator.Animator = Animator;
|
|
36965
|
+
function easeInOutQuad(t) {
|
|
36966
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
36967
|
+
}
|
|
36968
|
+
function Now() {
|
|
36969
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
36970
|
+
}
|
|
36971
|
+
})(TextureFrameSeriesAnimator || (TextureFrameSeriesAnimator = {}));
|
|
36972
|
+
|
|
36973
|
+
const TEXTURE_FRAME_SERIES_ANIMATOR_KEY = "TextureFrameSeriesAnimator.Animator";
|
|
36621
36974
|
(function (EntityRenderEnginePolygon) {
|
|
36622
36975
|
async function Render(params) {
|
|
36623
36976
|
var _a, _b;
|
|
@@ -36636,6 +36989,7 @@
|
|
|
36636
36989
|
const cFillColor = bFillColor ? ColorToCColor(bFillColor) : Cesium.Color.fromCssColorString("rgba(139, 195, 74, 0.8)");
|
|
36637
36990
|
let textureDataUri = null;
|
|
36638
36991
|
let textureTraceEffective = null;
|
|
36992
|
+
let frameArchive = null;
|
|
36639
36993
|
if (fillType === BModels.Style.EPolygonFillType.Texture) {
|
|
36640
36994
|
const resolved = await resolveTexturedFill({
|
|
36641
36995
|
api: params.api,
|
|
@@ -36645,11 +36999,16 @@
|
|
|
36645
36999
|
});
|
|
36646
37000
|
textureDataUri = resolved.dataUri;
|
|
36647
37001
|
textureTraceEffective = resolved.effective;
|
|
37002
|
+
frameArchive = resolved.frameArchive || null;
|
|
36648
37003
|
}
|
|
36649
|
-
const hasFill = fillType === BModels.Style.EPolygonFillType.Texture
|
|
37004
|
+
const hasFill = fillType === BModels.Style.EPolygonFillType.Texture
|
|
37005
|
+
? Boolean(textureDataUri || frameArchive || cFillColor.alpha > 0)
|
|
37006
|
+
: cFillColor.alpha > 0;
|
|
36650
37007
|
const fillMaterial = textureDataUri
|
|
36651
37008
|
? new Cesium.ImageMaterialProperty({ image: textureDataUri, transparent: true })
|
|
36652
|
-
:
|
|
37009
|
+
: frameArchive
|
|
37010
|
+
? Cesium.Color.WHITE.withAlpha(0)
|
|
37011
|
+
: cFillColor;
|
|
36653
37012
|
const lineColorTrace = style.lineColor ? BModels.Calculator.TraceGetColor(style.lineColor, entity, params.tags) : { value: null, effective: null };
|
|
36654
37013
|
const bLineColor = lineColorTrace.value;
|
|
36655
37014
|
const cLineColor = bLineColor ? ColorToCColor(bLineColor) : Cesium.Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
|
|
@@ -36819,6 +37178,13 @@
|
|
|
36819
37178
|
}
|
|
36820
37179
|
cEntity.show = true;
|
|
36821
37180
|
}
|
|
37181
|
+
syncTextureFrameArchive(cEntity, frameArchive, params.viewer, style.textureColorMask);
|
|
37182
|
+
if (frameArchive && params.onSeriesDiscovered) {
|
|
37183
|
+
const segment = frameArchiveToSeriesSegment(frameArchive, params.entity);
|
|
37184
|
+
if (segment) {
|
|
37185
|
+
params.onSeriesDiscovered(segment);
|
|
37186
|
+
}
|
|
37187
|
+
}
|
|
36822
37188
|
if (hasOutline) {
|
|
36823
37189
|
let borderHeight = undefined;
|
|
36824
37190
|
if (heightRef != Cesium.HeightReference.CLAMP_TO_GROUND) {
|
|
@@ -37027,7 +37393,9 @@
|
|
|
37027
37393
|
// Temporarily disabling re-using graphics for polygons as they have some bugs, and
|
|
37028
37394
|
// they don't support any animation yet.
|
|
37029
37395
|
// rendered: null
|
|
37030
|
-
rendered: (_c = params.rendered) === null || _c === void 0 ? void 0 : _c.get(entity.Bruce.ID)
|
|
37396
|
+
rendered: (_c = params.rendered) === null || _c === void 0 ? void 0 : _c.get(entity.Bruce.ID),
|
|
37397
|
+
menuItemId: params.menuItemId,
|
|
37398
|
+
onSeriesDiscovered: params.onSeriesDiscovered
|
|
37031
37399
|
});
|
|
37032
37400
|
if (cEntity) {
|
|
37033
37401
|
const name = await getName$3(api, entity);
|
|
@@ -37143,11 +37511,86 @@
|
|
|
37143
37511
|
// same polygon (or multiple polygons sharing a texture) don't refetch/reprocess the same image.
|
|
37144
37512
|
const _textureCache = new BModels.LRUCache(50);
|
|
37145
37513
|
/**
|
|
37146
|
-
*
|
|
37514
|
+
* Reconciles cEntity's TextureFrameSeriesAnimator.Animator (if any) against this render's resolved texture.
|
|
37515
|
+
* Called once cEntity is fully created/updated and definitely has a `polygon` graphics.
|
|
37516
|
+
* @param cEntity
|
|
37517
|
+
* @param frameArchive
|
|
37518
|
+
* @param viewer
|
|
37519
|
+
* @param textureColorMask
|
|
37520
|
+
*/
|
|
37521
|
+
function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask) {
|
|
37522
|
+
const existing = cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
|
|
37523
|
+
if (frameArchive && existing && !existing.IsDisposed() && existing.GetArchiveUrl() === frameArchive.url) {
|
|
37524
|
+
return;
|
|
37525
|
+
}
|
|
37526
|
+
if (existing && !existing.IsDisposed()) {
|
|
37527
|
+
existing.Dispose();
|
|
37528
|
+
cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = null;
|
|
37529
|
+
}
|
|
37530
|
+
if (frameArchive) {
|
|
37531
|
+
const animator = new TextureFrameSeriesAnimator.Animator({
|
|
37532
|
+
viewer,
|
|
37533
|
+
entity: cEntity,
|
|
37534
|
+
archiveUrl: frameArchive.url,
|
|
37535
|
+
frames: frameArchive.metadata.Frames,
|
|
37536
|
+
textureColorMask
|
|
37537
|
+
});
|
|
37538
|
+
cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = animator;
|
|
37539
|
+
}
|
|
37540
|
+
}
|
|
37541
|
+
/**
|
|
37542
|
+
* Derives a min/max/label time-range segment from a resolved frame archive's Frames metadata,
|
|
37543
|
+
* for reporting up to whoever owns the menu item this entity belongs to (see IParams.onSeriesDiscovered).
|
|
37544
|
+
* @param frameArchive
|
|
37545
|
+
* @param entity
|
|
37546
|
+
*/
|
|
37547
|
+
function frameArchiveToSeriesSegment(frameArchive, entity) {
|
|
37548
|
+
var _a;
|
|
37549
|
+
const timestamps = frameArchive.metadata.Frames
|
|
37550
|
+
.map((f) => new Date(f.Timestamp))
|
|
37551
|
+
.filter((d) => !isNaN(d.getTime()));
|
|
37552
|
+
if (!timestamps.length) {
|
|
37553
|
+
return null;
|
|
37554
|
+
}
|
|
37555
|
+
return {
|
|
37556
|
+
key: frameArchive.url,
|
|
37557
|
+
min: new Date(Math.min(...timestamps.map((d) => d.getTime()))),
|
|
37558
|
+
max: new Date(Math.max(...timestamps.map((d) => d.getTime()))),
|
|
37559
|
+
label: ((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Name) || undefined
|
|
37560
|
+
};
|
|
37561
|
+
}
|
|
37562
|
+
/**
|
|
37563
|
+
* Returns if a value looks like a URL rather than a bare client file ID.
|
|
37147
37564
|
* @param value
|
|
37148
37565
|
*/
|
|
37149
|
-
function
|
|
37150
|
-
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value) || value.startsWith("//")
|
|
37566
|
+
function looksLikeUrl(value) {
|
|
37567
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value) || value.startsWith("//");
|
|
37568
|
+
}
|
|
37569
|
+
/**
|
|
37570
|
+
* Extracts a client file ID from a resolved polygon "texture" value, which is either a bare
|
|
37571
|
+
* file ID or a URL. Follows the "/file/<id>" route path regardless of domain,
|
|
37572
|
+
* eg: "https://host/file/AGPWMAQR6F5ZVDJXZVRODGLCJE.png" or "https://host/RV:1/file/AGPWMAQR6F5ZVDJXZVRODGLCJE?cc=1".
|
|
37573
|
+
* @param raw
|
|
37574
|
+
*/
|
|
37575
|
+
function extractClientFileIdFromTextureValue(raw) {
|
|
37576
|
+
if (!raw || typeof raw !== "string") {
|
|
37577
|
+
return null;
|
|
37578
|
+
}
|
|
37579
|
+
if (!looksLikeUrl(raw)) {
|
|
37580
|
+
return raw;
|
|
37581
|
+
}
|
|
37582
|
+
try {
|
|
37583
|
+
const withoutQuery = raw.split("?")[0];
|
|
37584
|
+
const match = withoutQuery.match(/\/file\/([^\/]+)/);
|
|
37585
|
+
if (!match) {
|
|
37586
|
+
return null;
|
|
37587
|
+
}
|
|
37588
|
+
return match[1].split(".")[0];
|
|
37589
|
+
}
|
|
37590
|
+
catch (e) {
|
|
37591
|
+
console.warn("Failed to parse client file ID from texture URL:", e, raw);
|
|
37592
|
+
return null;
|
|
37593
|
+
}
|
|
37151
37594
|
}
|
|
37152
37595
|
/**
|
|
37153
37596
|
* Resolves a polygon style's texture field and if a color ask is needed it tints the resulting image.
|
|
@@ -37155,7 +37598,11 @@
|
|
|
37155
37598
|
*/
|
|
37156
37599
|
async function resolveTexturedFill(params) {
|
|
37157
37600
|
const { api, style, entity, tags } = params;
|
|
37158
|
-
|
|
37601
|
+
const textureValue = style.texture;
|
|
37602
|
+
if (textureValue && !Array.isArray(textureValue)) {
|
|
37603
|
+
return resolveTextureCondition({ api, condition: textureValue });
|
|
37604
|
+
}
|
|
37605
|
+
let textureRows = Array.isArray(textureValue) ? textureValue : [];
|
|
37159
37606
|
textureRows.forEach((row) => {
|
|
37160
37607
|
if (row.type == BModels.Calculator.EValueType.Color) {
|
|
37161
37608
|
row.type = BModels.Calculator.EValueType.Input;
|
|
@@ -37167,13 +37614,21 @@
|
|
|
37167
37614
|
return { dataUri: null, effective: textureTrace.effective };
|
|
37168
37615
|
}
|
|
37169
37616
|
let url = raw;
|
|
37170
|
-
if (!
|
|
37171
|
-
|
|
37172
|
-
|
|
37173
|
-
api
|
|
37174
|
-
fileId:
|
|
37175
|
-
|
|
37176
|
-
|
|
37617
|
+
if (!raw.startsWith("data:")) {
|
|
37618
|
+
const clientFileId = extractClientFileIdFromTextureValue(raw);
|
|
37619
|
+
if (clientFileId) {
|
|
37620
|
+
await api.Loading;
|
|
37621
|
+
const { clientFile } = await BModels.ClientFile.Get({ api, fileId: clientFileId });
|
|
37622
|
+
url = clientFile.URL;
|
|
37623
|
+
const clientFileData = clientFile.Data;
|
|
37624
|
+
if (TextureFrameSeriesAnimator.IsFrameArchiveMetadata(clientFileData)) {
|
|
37625
|
+
return {
|
|
37626
|
+
dataUri: null,
|
|
37627
|
+
effective: textureTrace.effective,
|
|
37628
|
+
frameArchive: { url, metadata: clientFileData.generation }
|
|
37629
|
+
};
|
|
37630
|
+
}
|
|
37631
|
+
}
|
|
37177
37632
|
}
|
|
37178
37633
|
const mask = style.textureColorMask;
|
|
37179
37634
|
const cacheKey = "texture-" + url + "-" + (mask ? mask.type + "-" + mask.minColor + "-" + mask.maxColor : "none");
|
|
@@ -37184,6 +37639,31 @@
|
|
|
37184
37639
|
}
|
|
37185
37640
|
return { dataUri: await prom, effective: textureTrace.effective };
|
|
37186
37641
|
}
|
|
37642
|
+
async function resolveTextureCondition(params) {
|
|
37643
|
+
const { api, condition } = params;
|
|
37644
|
+
const entityTypeSourceId = condition === null || condition === void 0 ? void 0 : condition["EntityType.Source.ID"];
|
|
37645
|
+
const attribute = condition === null || condition === void 0 ? void 0 : condition.Attribute;
|
|
37646
|
+
if (!api || !entityTypeSourceId || !attribute) {
|
|
37647
|
+
return { dataUri: null, effective: null };
|
|
37648
|
+
}
|
|
37649
|
+
await api.Loading;
|
|
37650
|
+
const clientFile = await BModels.ClientFile.FindGeneratedTexture({ api, entityTypeSourceId, attribute });
|
|
37651
|
+
if (!clientFile) {
|
|
37652
|
+
return { dataUri: null, effective: null };
|
|
37653
|
+
}
|
|
37654
|
+
const url = clientFile.URL;
|
|
37655
|
+
const clientFileData = clientFile.Data;
|
|
37656
|
+
if (TextureFrameSeriesAnimator.IsFrameArchiveMetadata(clientFileData)) {
|
|
37657
|
+
return { dataUri: null, effective: null, frameArchive: { url, metadata: clientFileData.generation } };
|
|
37658
|
+
}
|
|
37659
|
+
const cacheKey = "texture-" + url + "-none";
|
|
37660
|
+
let prom = _textureCache.Get(cacheKey);
|
|
37661
|
+
if (!prom) {
|
|
37662
|
+
prom = buildTintedTextureDataUri(url).catch(() => null);
|
|
37663
|
+
_textureCache.Set(cacheKey, prom);
|
|
37664
|
+
}
|
|
37665
|
+
return { dataUri: await prom, effective: null };
|
|
37666
|
+
}
|
|
37187
37667
|
/**
|
|
37188
37668
|
* Fetches an image and, if a color mask is provided, remaps its (assumed grayscale) pixel values
|
|
37189
37669
|
* into a gradient between the mask's minColor/maxColor, preserving source alpha.
|
|
@@ -37210,30 +37690,11 @@
|
|
|
37210
37690
|
const minColor = BModels.Color.ColorFromStr(mask.minColor) || { red: 0, green: 0, blue: 0, alpha: 1 };
|
|
37211
37691
|
const maxColor = BModels.Color.ColorFromStr(mask.maxColor) || { red: 255, green: 255, blue: 255, alpha: 1 };
|
|
37212
37692
|
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
37213
|
-
|
|
37214
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
37215
|
-
// The texture is rendered black-to-white server-side, so any channel carries the value.
|
|
37216
|
-
const t = data[i] / 255;
|
|
37217
|
-
data[i] = Math.round(minColor.red + (maxColor.red - minColor.red) * t);
|
|
37218
|
-
data[i + 1] = Math.round(minColor.green + (maxColor.green - minColor.green) * t);
|
|
37219
|
-
data[i + 2] = Math.round(minColor.blue + (maxColor.blue - minColor.blue) * t);
|
|
37220
|
-
// Preserve source transparency (eg unrasterized pixels) blended with the mask's own alpha.
|
|
37221
|
-
const srcAlpha = data[i + 3] / 255;
|
|
37222
|
-
const maskAlpha = minColor.alpha + (maxColor.alpha - minColor.alpha) * t;
|
|
37223
|
-
data[i + 3] = Math.round(srcAlpha * maskAlpha * 255);
|
|
37224
|
-
}
|
|
37693
|
+
ApplyGrayscaleColorMask(imageData, minColor, maxColor);
|
|
37225
37694
|
context.putImageData(imageData, 0, 0);
|
|
37226
37695
|
}
|
|
37227
37696
|
return canvas.toDataURL("image/png");
|
|
37228
37697
|
}
|
|
37229
|
-
function loadImage(src) {
|
|
37230
|
-
return new Promise((res, rej) => {
|
|
37231
|
-
const image = new Image();
|
|
37232
|
-
image.onload = () => res(image);
|
|
37233
|
-
image.onerror = (e) => rej(e);
|
|
37234
|
-
image.src = src;
|
|
37235
|
-
});
|
|
37236
|
-
}
|
|
37237
37698
|
/**
|
|
37238
37699
|
* Compares two color materials.
|
|
37239
37700
|
* Returns if they are equal.
|
|
@@ -37566,6 +38027,7 @@
|
|
|
37566
38027
|
entities: [],
|
|
37567
38028
|
zoomItems: {},
|
|
37568
38029
|
menuItemId: params.menuItemId,
|
|
38030
|
+
onSeriesDiscovered: params.onSeriesDiscovered,
|
|
37569
38031
|
visualRegister: params.visualRegister
|
|
37570
38032
|
};
|
|
37571
38033
|
const updated = new Map();
|
|
@@ -38528,7 +38990,7 @@
|
|
|
38528
38990
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
38529
38991
|
})(exports.StyleUtils || (exports.StyleUtils = {}));
|
|
38530
38992
|
|
|
38531
|
-
const VERSION = "6.9.
|
|
38993
|
+
const VERSION = "6.9.9";
|
|
38532
38994
|
/**
|
|
38533
38995
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
38534
38996
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|