bruce-cesium 7.4.5 → 7.4.6

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.
Files changed (36) hide show
  1. package/dist/bruce-cesium.es5.js +303 -34
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +301 -32
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/rendering/cesium-animated-property.js +16 -1
  7. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  8. package/dist/lib/rendering/entity-render-engine-model3d.js +2 -1
  9. package/dist/lib/rendering/entity-render-engine-model3d.js.map +1 -1
  10. package/dist/lib/rendering/render-managers/common/entity-label.js +24 -2
  11. package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
  12. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js +12 -4
  13. package/dist/lib/rendering/render-managers/entities/entities-loaded-render-manager.js.map +1 -1
  14. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +12 -4
  15. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  16. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +35 -2
  17. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  18. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +36 -3
  19. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  20. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +36 -3
  21. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  22. package/dist/lib/rendering/render-managers/tilesets/tileset-google-photos-render-manager.js +19 -0
  23. package/dist/lib/rendering/render-managers/tilesets/tileset-google-photos-render-manager.js.map +1 -1
  24. package/dist/lib/rendering/render-managers/tilesets/tileset-osm-render-manager.js +55 -2
  25. package/dist/lib/rendering/render-managers/tilesets/tileset-osm-render-manager.js.map +1 -1
  26. package/dist/lib/rendering/visuals-register.js +48 -9
  27. package/dist/lib/rendering/visuals-register.js.map +1 -1
  28. package/dist/lib/utils/cesium-entity-styler.js +5 -0
  29. package/dist/lib/utils/cesium-entity-styler.js.map +1 -1
  30. package/dist/types/bruce-cesium.d.ts +1 -1
  31. package/dist/types/rendering/render-managers/tilesets/tileset-arb-render-manager.d.ts +1 -0
  32. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +1 -0
  33. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +1 -0
  34. package/dist/types/rendering/render-managers/tilesets/tileset-google-photos-render-manager.d.ts +1 -0
  35. package/dist/types/rendering/render-managers/tilesets/tileset-osm-render-manager.d.ts +2 -0
  36. package/package.json +1 -1
@@ -1516,6 +1516,7 @@
1516
1516
  * @returns
1517
1517
  */
1518
1518
  function isFeatureAlive(viewer, feature) {
1519
+ var _a, _b;
1519
1520
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.scene) || viewer.isDestroyed()) {
1520
1521
  return false;
1521
1522
  }
@@ -1526,6 +1527,10 @@
1526
1527
  if (cTileset.isDestroyed() || !viewer.scene.primitives.contains(cTileset)) {
1527
1528
  return false;
1528
1529
  }
1530
+ // A tile unloads its content while the tileset lives on, and the feature's colour is then unreadable.
1531
+ if (feature._bruceUnloaded || ((_b = (_a = feature.content) === null || _a === void 0 ? void 0 : _a.isDestroyed) === null || _b === void 0 ? void 0 : _b.call(_a))) {
1532
+ return false;
1533
+ }
1529
1534
  return true;
1530
1535
  }
1531
1536
  let _lastMark = 0;
@@ -1715,7 +1720,17 @@
1715
1720
  removal = null;
1716
1721
  return;
1717
1722
  }
1718
- if (!assertColorMark(mark, feature.color)) {
1723
+ // A throw here is inside Cesium's frame and stops rendering, so an unreadable feature ends the animation.
1724
+ let current;
1725
+ try {
1726
+ current = feature.color;
1727
+ }
1728
+ catch (e) {
1729
+ removal === null || removal === void 0 ? void 0 : removal();
1730
+ removal = null;
1731
+ return;
1732
+ }
1733
+ if (!assertColorMark(mark, current)) {
1719
1734
  removal === null || removal === void 0 ? void 0 : removal();
1720
1735
  removal = null;
1721
1736
  onDone === null || onDone === void 0 ? void 0 : onDone();
@@ -2934,6 +2949,7 @@
2934
2949
  * @returns
2935
2950
  */
2936
2951
  function isAlive(viewer, visual) {
2952
+ var _a, _b;
2937
2953
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.scene) || viewer.isDestroyed()) {
2938
2954
  return false;
2939
2955
  }
@@ -2951,6 +2967,10 @@
2951
2967
  if (cTileset.isDestroyed() || !viewer.scene.primitives.contains(cTileset)) {
2952
2968
  return false;
2953
2969
  }
2970
+ // Its tile unloaded, so reading or writing the feature's colour throws.
2971
+ if (visual._bruceUnloaded || ((_b = (_a = visual.content) === null || _a === void 0 ? void 0 : _a.isDestroyed) === null || _b === void 0 ? void 0 : _b.call(_a))) {
2972
+ return false;
2973
+ }
2954
2974
  return true;
2955
2975
  }
2956
2976
  return false;
@@ -5789,6 +5809,8 @@
5789
5809
  this.element = document.createElement("div");
5790
5810
  this.coordinates = { x: 0, y: 0, scaleX: 1, scaleY: 1 };
5791
5811
  this.participants = new Set();
5812
+ // Participants whose failure has been logged, so a label that throws every frame logs once.
5813
+ this.failed = new WeakSet();
5792
5814
  this.placementMemory = new PlacementMemory();
5793
5815
  this.forwardingWheel = false;
5794
5816
  this.pendingDrag = null;
@@ -5989,15 +6011,35 @@
5989
6011
  x: (pointer.x - rect.left) * this.viewer.canvas.clientWidth / rect.width,
5990
6012
  y: (pointer.y - rect.top) * this.viewer.canvas.clientHeight / rect.height
5991
6013
  } : undefined;
6014
+ // This runs inside Cesium's frame, where one throwing label would stop rendering for the whole scene.
5992
6015
  const projected = new Map();
5993
6016
  for (const participant of this.participants) {
5994
- projected.set(participant, participant.project());
6017
+ let frame = null;
6018
+ try {
6019
+ frame = participant.project();
6020
+ }
6021
+ catch (e) {
6022
+ this.logFailure(participant, e);
6023
+ }
6024
+ projected.set(participant, frame);
5995
6025
  }
5996
6026
  const placements = new Map(Layout(Array.from(projected.values()).filter(Boolean), this.placementMemory).map(item => [item.frame.id, item]));
5997
6027
  for (const [participant, frame] of projected) {
5998
- participant.apply(frame ? placements.get(frame.id) : undefined);
6028
+ try {
6029
+ participant.apply(frame ? placements.get(frame.id) : undefined);
6030
+ }
6031
+ catch (e) {
6032
+ this.logFailure(participant, e);
6033
+ }
5999
6034
  }
6000
6035
  }
6036
+ logFailure(participant, error) {
6037
+ if (this.failed.has(participant)) {
6038
+ return;
6039
+ }
6040
+ this.failed.add(participant);
6041
+ console.error("EntityLabel: a label failed to update and is skipped this frame.", error);
6042
+ }
6001
6043
  }
6002
6044
  let nextLabelId = 0;
6003
6045
  class Label {
@@ -9267,6 +9309,7 @@
9267
9309
  })(exports.VisualRegisterCuller || (exports.VisualRegisterCuller = {}));
9268
9310
 
9269
9311
  function isTilesetFeatureAlive(visual) {
9312
+ var _a, _b;
9270
9313
  const cTileset = visual === null || visual === void 0 ? void 0 : visual.tileset;
9271
9314
  if (!cTileset) {
9272
9315
  return false;
@@ -9277,8 +9320,32 @@
9277
9320
  else if (visual.primitive && visual.primitive.isDestroyed()) {
9278
9321
  return false;
9279
9322
  }
9323
+ // A tile unloads its content while the tileset lives on, and every read through the feature then throws.
9324
+ else if (visual._bruceUnloaded || ((_b = (_a = visual.content) === null || _a === void 0 ? void 0 : _a.isDestroyed) === null || _b === void 0 ? void 0 : _b.call(_a))) {
9325
+ return false;
9326
+ }
9280
9327
  return true;
9281
9328
  }
9329
+ /*
9330
+ * Returns if a visual is currently shown, reading a tileset feature only while its tile is loaded.
9331
+ */
9332
+ function isVisualShown(visual) {
9333
+ if (!visual) {
9334
+ return true;
9335
+ }
9336
+ if (visual instanceof Cesium.Cesium3DTileFeature) {
9337
+ if (!isTilesetFeatureAlive(visual)) {
9338
+ return false;
9339
+ }
9340
+ try {
9341
+ return visual.show !== false;
9342
+ }
9343
+ catch (e) {
9344
+ return false;
9345
+ }
9346
+ }
9347
+ return visual.show !== false;
9348
+ }
9282
9349
  /**
9283
9350
  * Returns if a given visual is alive and in the scene.
9284
9351
  * @param viewer
@@ -9286,6 +9353,7 @@
9286
9353
  * @returns
9287
9354
  */
9288
9355
  function isAlive$1(viewer, visual) {
9356
+ var _a, _b;
9289
9357
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.scene) || viewer.isDestroyed()) {
9290
9358
  return false;
9291
9359
  }
@@ -9306,6 +9374,10 @@
9306
9374
  else if (visual.primitive && visual.primitive.isDestroyed()) {
9307
9375
  return false;
9308
9376
  }
9377
+ // Its tile unloaded, so every read through the feature throws.
9378
+ else if (visual._bruceUnloaded || ((_b = (_a = visual.content) === null || _a === void 0 ? void 0 : _a.isDestroyed) === null || _b === void 0 ? void 0 : _b.call(_a))) {
9379
+ return false;
9380
+ }
9309
9381
  return true;
9310
9382
  }
9311
9383
  return false;
@@ -9764,6 +9836,13 @@
9764
9836
  // Adding is asynchronous, so ordering has to wait until it is actually in the collection.
9765
9837
  Promise.resolve(this.viewer.dataSources.add(source))
9766
9838
  .then(() => {
9839
+ // Released or disposed before the add landed, when the remove found nothing to take out.
9840
+ if (this.menuItemSources.get(menuItemId) !== record) {
9841
+ if (!this.viewer.isDestroyed() && this.viewer.dataSources.contains(source)) {
9842
+ this.viewer.dataSources.remove(source, true);
9843
+ }
9844
+ return;
9845
+ }
9767
9846
  record.added = true;
9768
9847
  this.applySourceOrder();
9769
9848
  })
@@ -9800,7 +9879,9 @@
9800
9879
  if (this.disposed || !this.sourceOrderDirty || !((_a = this.viewer) === null || _a === void 0 ? void 0 : _a.dataSources)) {
9801
9880
  return;
9802
9881
  }
9803
- const records = Array.from(this.menuItemSources.values()).filter(x => x.added);
9882
+ // raiseToTop throws for a source the collection does not hold.
9883
+ const dataSources = this.viewer.dataSources;
9884
+ const records = Array.from(this.menuItemSources.values()).filter(x => x.added && dataSources.contains(x.source));
9804
9885
  if (records.length < 2) {
9805
9886
  // Nothing to order against yet, so leave the flag set for when there is.
9806
9887
  return;
@@ -9810,7 +9891,7 @@
9810
9891
  records.sort((a, b) => a.maxZIndex - b.maxZIndex);
9811
9892
  for (const record of records) {
9812
9893
  try {
9813
- this.viewer.dataSources.raiseToTop(record.source);
9894
+ dataSources.raiseToTop(record.source);
9814
9895
  }
9815
9896
  catch (e) {
9816
9897
  console.error(e);
@@ -12150,7 +12231,7 @@
12150
12231
  return (hover === null || hover === void 0 ? void 0 : hover.entityId) === entityId && (hover.menuItemId == null || hover.menuItemId === menuItemId) ? hover.position : undefined;
12151
12232
  }
12152
12233
  SyncLabelHover() {
12153
- var _a, _b, _c, _d;
12234
+ var _a, _b, _c;
12154
12235
  const hover = this.labelHover;
12155
12236
  if (!hover || this.disposed) {
12156
12237
  return;
@@ -12175,7 +12256,7 @@
12175
12256
  }
12176
12257
  const existing = rego && exports.EntityLabel.GetLabel({ rego });
12177
12258
  const state = this.GetState(hover);
12178
- if ((!rego && !group) || state.hidden || ((_b = rego === null || rego === void 0 ? void 0 : rego.visual) === null || _b === void 0 ? void 0 : _b.show) === false ||
12259
+ if ((!rego && !group) || state.hidden || ((rego === null || rego === void 0 ? void 0 : rego.visual) && !isVisualShown(rego.visual)) ||
12179
12260
  (this.GetIsIsolatedAny() && !state.isolated && this.GetIsolationOpacity() == null) ||
12180
12261
  (existing && existing !== this.hoverLabel && existing.HasText) ||
12181
12262
  (group && group.options.presentation !== "icon")) {
@@ -12187,7 +12268,7 @@
12187
12268
  return;
12188
12269
  }
12189
12270
  // Query-only icon groups have coordinates but no registered scene visual to attach to.
12190
- if (!rego && group && [group.item.longitude, group.item.latitude, (_c = group.item.altitude) !== null && _c !== void 0 ? _c : 0].every(Number.isFinite)) {
12271
+ if (!rego && group && [group.item.longitude, group.item.latitude, (_b = group.item.altitude) !== null && _b !== void 0 ? _b : 0].every(Number.isFinite)) {
12191
12272
  rego = {
12192
12273
  entityId: hover.entityId,
12193
12274
  menuItemId: hover.menuItemId,
@@ -12209,7 +12290,7 @@
12209
12290
  rego,
12210
12291
  viewer: this.viewer,
12211
12292
  visualRegister: this,
12212
- api: (_d = this.ApiGetters) === null || _d === void 0 ? void 0 : _d.GetBruceApi(),
12293
+ api: (_c = this.ApiGetters) === null || _c === void 0 ? void 0 : _c.GetBruceApi(),
12213
12294
  persistent: false,
12214
12295
  transient: true,
12215
12296
  hoverRingState: this.hoverRingState,
@@ -12219,10 +12300,10 @@
12219
12300
  selectOnClick: false
12220
12301
  },
12221
12302
  visible: () => {
12222
- var _a, _b;
12303
+ var _a;
12223
12304
  const current = this.GetState(hover);
12224
- return (!this.disposed && !current.hidden && ((_a = rego.visual) === null || _a === void 0 ? void 0 : _a.show) !== false &&
12225
- Boolean(this.GetRego(hover) || ((_b = this.labelGroups) === null || _b === void 0 ? void 0 : _b.Has(hover.entityId, hover.menuItemId))) &&
12305
+ return (!this.disposed && !current.hidden && isVisualShown(rego.visual) &&
12306
+ Boolean(this.GetRego(hover) || ((_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.Has(hover.entityId, hover.menuItemId))) &&
12226
12307
  (!this.GetIsIsolatedAny() || current.isolated || this.GetIsolationOpacity() != null));
12227
12308
  }
12228
12309
  };
@@ -14837,12 +14918,20 @@
14837
14918
  strokeWidth: lineWidthPx,
14838
14919
  clampToGround: lineWidthPx <= 0 && polygonsClamped
14839
14920
  });
14840
- this.viewer.dataSources.add(source);
14841
- this.sources.push(source);
14842
- if (this.disposed) {
14843
- this.doDispose();
14921
+ // Dispose already cleared what was added before this, and this one has not been added.
14922
+ if (this.disposed || this.viewer.isDestroyed()) {
14844
14923
  return;
14845
14924
  }
14925
+ const viewer = this.viewer;
14926
+ this.sources.push(source);
14927
+ // The add lands asynchronously, so a dispose in between removes nothing and would orphan the source.
14928
+ Promise.resolve(viewer.dataSources.add(source)).then(() => {
14929
+ if (this.disposed && !viewer.isDestroyed() && viewer.dataSources.contains(source)) {
14930
+ viewer.dataSources.remove(source);
14931
+ }
14932
+ }).catch((e) => {
14933
+ console.error(e);
14934
+ });
14846
14935
  const groups = [];
14847
14936
  /**
14848
14937
  * Applies individual styling to a rendered entity.
@@ -15878,12 +15967,20 @@
15878
15967
  strokeWidth: lineWidthPx,
15879
15968
  clampToGround: lineWidthPx <= 0 && polygonsClamped
15880
15969
  });
15881
- this.viewer.dataSources.add(source);
15882
- this.sources.push(source);
15883
- if (this.disposed) {
15884
- this.doDispose();
15970
+ // Dispose already cleared what was added before this, and this one has not been added.
15971
+ if (this.disposed || this.viewer.isDestroyed()) {
15885
15972
  return;
15886
15973
  }
15974
+ const viewer = this.viewer;
15975
+ this.sources.push(source);
15976
+ // The add lands asynchronously, so a dispose in between removes nothing and would orphan the source.
15977
+ Promise.resolve(viewer.dataSources.add(source)).then(() => {
15978
+ if (this.disposed && !viewer.isDestroyed() && viewer.dataSources.contains(source)) {
15979
+ viewer.dataSources.remove(source);
15980
+ }
15981
+ }).catch((e) => {
15982
+ console.error(e);
15983
+ });
15887
15984
  const groups = [];
15888
15985
  /**
15889
15986
  * Applies individual styling to a rendered entity.
@@ -19563,7 +19660,7 @@
19563
19660
  });
19564
19661
  this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
19565
19662
  try {
19566
- this.queueTile(tile, false);
19663
+ this.unloadTile(tile);
19567
19664
  }
19568
19665
  catch (e) {
19569
19666
  console.error(e);
@@ -19747,7 +19844,7 @@
19747
19844
  });
19748
19845
  this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
19749
19846
  try {
19750
- this.queueTile(tile, false);
19847
+ this.unloadTile(tile);
19751
19848
  }
19752
19849
  catch (e) {
19753
19850
  console.error(e);
@@ -19812,6 +19909,35 @@
19812
19909
  }
19813
19910
  this.pingFeatureQueue();
19814
19911
  }
19912
+ /*
19913
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
19914
+ */
19915
+ unloadTile(tile) {
19916
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
19917
+ if (!content) {
19918
+ return;
19919
+ }
19920
+ const features = [];
19921
+ for (let i = 0; i < content.featuresLength; i++) {
19922
+ const feature = content.getFeature(i);
19923
+ // Drops a load still queued for it, the batch skips anything marked.
19924
+ this.featureQueueStates.delete(feature);
19925
+ feature._bruceUnloaded = true;
19926
+ features.push(feature);
19927
+ }
19928
+ if (!features.length) {
19929
+ return;
19930
+ }
19931
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
19932
+ visuals: features,
19933
+ requestRender: false,
19934
+ menuItemId: this.item.id,
19935
+ doRemove: false
19936
+ });
19937
+ for (const entityId of removedEntityIds) {
19938
+ delete this.loadedCesiumEntities[entityId];
19939
+ }
19940
+ }
19815
19941
  /**
19816
19942
  * Pings the feature queue to process any queued features.
19817
19943
  * Does nothing if we're already processing.
@@ -19828,7 +19954,7 @@
19828
19954
  return;
19829
19955
  }
19830
19956
  this.processFeatureQueueBatch();
19831
- if (!this.featureQueue) {
19957
+ if (!this.featureQueue.length) {
19832
19958
  clearInterval(this.featureQueueInterval);
19833
19959
  this.featureQueueInterval = null;
19834
19960
  }
@@ -19860,6 +19986,10 @@
19860
19986
  const regosToStyle = new Map();
19861
19987
  const featuresToRemove = [];
19862
19988
  for (const feature of batch) {
19989
+ // Its tile unloaded while it waited, unloadTile already removed whatever it registered.
19990
+ if (feature._bruceUnloaded) {
19991
+ continue;
19992
+ }
19863
19993
  const load = this.featureQueueStates.get(feature);
19864
19994
  this.featureQueueStates.delete(feature);
19865
19995
  if (!load) {
@@ -23027,6 +23157,7 @@
23027
23157
  this.processingQueue = 0;
23028
23158
  this._osmIdToBruceId = {};
23029
23159
  this.tileLoadRemoval = null;
23160
+ this.tileUnloadRemoval = null;
23030
23161
  const { viewer, register: visualsManager, getters: apiGetter, monitor, item } = params;
23031
23162
  this.viewer = viewer;
23032
23163
  this.getters = apiGetter;
@@ -23035,7 +23166,7 @@
23035
23166
  this.monitor = monitor;
23036
23167
  }
23037
23168
  Dispose() {
23038
- var _a;
23169
+ var _a, _b;
23039
23170
  if (this.disposed) {
23040
23171
  return;
23041
23172
  }
@@ -23043,6 +23174,8 @@
23043
23174
  clearInterval(this.queueCheckInterval);
23044
23175
  (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
23045
23176
  this.tileLoadRemoval = null;
23177
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
23178
+ this.tileUnloadRemoval = null;
23046
23179
  if (this.cTileset) {
23047
23180
  const viewer = this.viewer;
23048
23181
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -23138,6 +23271,14 @@
23138
23271
  console.error(e);
23139
23272
  }
23140
23273
  });
23274
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
23275
+ try {
23276
+ this.unloadTile(tile);
23277
+ }
23278
+ catch (e) {
23279
+ console.error(e);
23280
+ }
23281
+ });
23141
23282
  this.queueCheckInterval = setInterval(() => {
23142
23283
  if (this.featureQueue.length > 0) {
23143
23284
  this.processQueue();
@@ -23174,6 +23315,34 @@
23174
23315
  this.processQueue();
23175
23316
  }
23176
23317
  }
23318
+ /*
23319
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
23320
+ */
23321
+ unloadTile(tile) {
23322
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
23323
+ if (!content) {
23324
+ return;
23325
+ }
23326
+ const features = [];
23327
+ for (let i = 0; i < content.featuresLength; i++) {
23328
+ const feature = content.getFeature(i);
23329
+ feature._bruceUnloaded = true;
23330
+ features.push(feature);
23331
+ }
23332
+ if (!features.length) {
23333
+ return;
23334
+ }
23335
+ this.featureQueue = this.featureQueue.filter(x => !x._bruceUnloaded);
23336
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
23337
+ visuals: features,
23338
+ requestRender: false,
23339
+ menuItemId: this.item.id,
23340
+ doRemove: false
23341
+ });
23342
+ for (const entityId of removedEntityIds) {
23343
+ delete this._loadedCesiumEntities[entityId];
23344
+ }
23345
+ }
23177
23346
  async processQueue() {
23178
23347
  if (this.processingQueue >= MAX_BATCHES) {
23179
23348
  return;
@@ -23306,6 +23475,10 @@
23306
23475
  bruceId = BModels.ObjectUtils.UId();
23307
23476
  }
23308
23477
  const feature = featureMap[i];
23478
+ // Its tile unloaded while the ID lookup was in flight.
23479
+ if (feature._bruceUnloaded) {
23480
+ continue;
23481
+ }
23309
23482
  if (this.item.ghostLoading == true) {
23310
23483
  feature.color = feature._orgColor;
23311
23484
  }
@@ -23397,7 +23570,7 @@
23397
23570
  return `osm:${sourceId}:${entityTypeId}`;
23398
23571
  }
23399
23572
  PrepareHandoff() {
23400
- var _a;
23573
+ var _a, _b;
23401
23574
  if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
23402
23575
  return null;
23403
23576
  }
@@ -23405,6 +23578,8 @@
23405
23578
  this.queueCheckInterval = null;
23406
23579
  (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
23407
23580
  this.tileLoadRemoval = null;
23581
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
23582
+ this.tileUnloadRemoval = null;
23408
23583
  const payload = {
23409
23584
  cTileset: this.cTileset,
23410
23585
  loadedCesiumEntities: this._loadedCesiumEntities,
@@ -23452,6 +23627,14 @@
23452
23627
  console.error(e);
23453
23628
  }
23454
23629
  });
23630
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
23631
+ try {
23632
+ this.unloadTile(tile);
23633
+ }
23634
+ catch (e) {
23635
+ console.error(e);
23636
+ }
23637
+ });
23455
23638
  this.queueCheckInterval = setInterval(() => {
23456
23639
  if (this.featureQueue.length > 0) {
23457
23640
  this.processQueue();
@@ -23641,7 +23824,7 @@
23641
23824
  });
23642
23825
  this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
23643
23826
  try {
23644
- this.queueTile(tile, false);
23827
+ this.unloadTile(tile);
23645
23828
  }
23646
23829
  catch (e) {
23647
23830
  console.error(e);
@@ -23687,6 +23870,35 @@
23687
23870
  }
23688
23871
  this.pingFeatureQueue();
23689
23872
  }
23873
+ /*
23874
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
23875
+ */
23876
+ unloadTile(tile) {
23877
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
23878
+ if (!content) {
23879
+ return;
23880
+ }
23881
+ const features = [];
23882
+ for (let i = 0; i < content.featuresLength; i++) {
23883
+ const feature = content.getFeature(i);
23884
+ // Drops a load still queued for it, the batch skips anything marked.
23885
+ this.featureQueueStates.delete(feature);
23886
+ feature._bruceUnloaded = true;
23887
+ features.push(feature);
23888
+ }
23889
+ if (!features.length) {
23890
+ return;
23891
+ }
23892
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
23893
+ visuals: features,
23894
+ requestRender: false,
23895
+ menuItemId: this.item.id,
23896
+ doRemove: false
23897
+ });
23898
+ for (const entityId of removedEntityIds) {
23899
+ delete this.loadedCesiumEntities[entityId];
23900
+ }
23901
+ }
23690
23902
  /**
23691
23903
  * Pings the feature queue to process any queued features.
23692
23904
  * Does nothing if we're already processing.
@@ -23703,7 +23915,7 @@
23703
23915
  return;
23704
23916
  }
23705
23917
  this.processFeatureQueueBatch();
23706
- if (!this.featureQueue) {
23918
+ if (!this.featureQueue.length) {
23707
23919
  clearInterval(this.featureQueueInterval);
23708
23920
  this.featureQueueInterval = null;
23709
23921
  }
@@ -23721,6 +23933,10 @@
23721
23933
  const regosToStyle = new Map();
23722
23934
  const featuresToRemove = [];
23723
23935
  for (const feature of batch) {
23936
+ // Its tile unloaded while it waited, unloadTile already removed whatever it registered.
23937
+ if (feature._bruceUnloaded) {
23938
+ continue;
23939
+ }
23724
23940
  const load = this.featureQueueStates.get(feature);
23725
23941
  this.featureQueueStates.delete(feature);
23726
23942
  if (!load) {
@@ -23926,7 +24142,7 @@
23926
24142
  });
23927
24143
  this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
23928
24144
  try {
23929
- this.queueTile(tile, false);
24145
+ this.unloadTile(tile);
23930
24146
  }
23931
24147
  catch (e) {
23932
24148
  console.error(e);
@@ -24880,7 +25096,7 @@
24880
25096
  });
24881
25097
  this.cTileset.tileUnload.addEventListener((tile) => {
24882
25098
  try {
24883
- this.queueTile(tile, false);
25099
+ this.unloadTile(tile);
24884
25100
  }
24885
25101
  catch (e) {
24886
25102
  console.error(e);
@@ -24908,6 +25124,35 @@
24908
25124
  }
24909
25125
  this.pingFeatureQueue();
24910
25126
  }
25127
+ /*
25128
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
25129
+ */
25130
+ unloadTile(tile) {
25131
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
25132
+ if (!content) {
25133
+ return;
25134
+ }
25135
+ const features = [];
25136
+ for (let i = 0; i < content.featuresLength; i++) {
25137
+ const feature = content.getFeature(i);
25138
+ // Drops a load still queued for it, the batch skips anything marked.
25139
+ this.featureQueueStates.delete(feature);
25140
+ feature._bruceUnloaded = true;
25141
+ features.push(feature);
25142
+ }
25143
+ if (!features.length) {
25144
+ return;
25145
+ }
25146
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
25147
+ visuals: features,
25148
+ requestRender: false,
25149
+ menuItemId: this.item.id,
25150
+ doRemove: false
25151
+ });
25152
+ for (const entityId of removedEntityIds) {
25153
+ delete this.loadedCesiumEntities[entityId];
25154
+ }
25155
+ }
24911
25156
  /**
24912
25157
  * Pings the feature queue to process any queued features.
24913
25158
  * Does nothing if we're already processing.
@@ -24924,7 +25169,7 @@
24924
25169
  return;
24925
25170
  }
24926
25171
  this.processFeatureQueueBatch();
24927
- if (!this.featureQueue) {
25172
+ if (!this.featureQueue.length) {
24928
25173
  clearInterval(this.featureQueueInterval);
24929
25174
  this.featureQueueInterval = null;
24930
25175
  }
@@ -24942,6 +25187,10 @@
24942
25187
  const regosToStyle = new Map();
24943
25188
  const featuresToRemove = [];
24944
25189
  for (const feature of batch) {
25190
+ // Its tile unloaded while it waited, unloadTile already removed whatever it registered.
25191
+ if (feature._bruceUnloaded) {
25192
+ continue;
25193
+ }
24945
25194
  const load = this.featureQueueStates.get(feature);
24946
25195
  this.featureQueueStates.delete(feature);
24947
25196
  if (!load) {
@@ -25558,6 +25807,8 @@
25558
25807
  constructor(params) {
25559
25808
  this.disposed = false;
25560
25809
  this.cTileset = null;
25810
+ // Set while a tileset is being created, so a second Init in the meantime cannot add another.
25811
+ this.initializing = false;
25561
25812
  const { viewer, item, getters } = params;
25562
25813
  this.viewer = viewer;
25563
25814
  this.item = item;
@@ -25578,6 +25829,10 @@
25578
25829
  this.viewer.scene.primitives.remove(this.cTileset);
25579
25830
  this.viewer.scene.requestRender();
25580
25831
  }
25832
+ // Disposed before it was added, so nothing else will ever destroy it.
25833
+ else if (!this.cTileset.isDestroyed()) {
25834
+ this.cTileset.destroy();
25835
+ }
25581
25836
  this.cTileset = null;
25582
25837
  }
25583
25838
  }
@@ -25596,6 +25851,11 @@
25596
25851
  this.viewer.scene.requestRender();
25597
25852
  return;
25598
25853
  }
25854
+ // The creation in flight applies the latest item's colour mask when it lands.
25855
+ if (this.initializing) {
25856
+ return;
25857
+ }
25858
+ this.initializing = true;
25599
25859
  try {
25600
25860
  const api = this.getters.GetBruceApi();
25601
25861
  await api.Loading;
@@ -25681,6 +25941,14 @@
25681
25941
  console.error("Failed to load Google Photorealistic 3D tileset:", e);
25682
25942
  return;
25683
25943
  }
25944
+ finally {
25945
+ this.initializing = false;
25946
+ }
25947
+ // Creation can come back empty, and the viewer can go while it loads.
25948
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed() || !this.viewer || this.viewer.isDestroyed()) {
25949
+ this.doDispose();
25950
+ return;
25951
+ }
25684
25952
  this.viewer.scene.primitives.add(this.cTileset);
25685
25953
  this.applyColorMask();
25686
25954
  this.viewer.scene.requestRender();
@@ -43124,7 +43392,8 @@
43124
43392
  const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(heading), Cesium.Math.toRadians(pitch), Cesium.Math.toRadians(roll));
43125
43393
  getOrient = new Cesium.CallbackProperty(() => {
43126
43394
  const currentPos = position ? GetCValue(params.viewer, position) : pos3d;
43127
- return Cesium.Transforms.headingPitchRollQuaternion(currentPos, hpr);
43395
+ // A live position can read as null mid-update, and throwing here would stop Cesium's frame.
43396
+ return Cesium.Transforms.headingPitchRollQuaternion(currentPos || pos3d, hpr);
43128
43397
  }, false);
43129
43398
  }
43130
43399
  cEntity = new Cesium.Entity({
@@ -50434,7 +50703,7 @@ void main() {
50434
50703
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
50435
50704
  })(exports.StyleUtils || (exports.StyleUtils = {}));
50436
50705
 
50437
- const VERSION = "7.4.5";
50706
+ const VERSION = "7.4.6";
50438
50707
  /**
50439
50708
  * Updates the environment instance used by bruce-cesium to one specified.
50440
50709
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.