bruce-cesium 7.4.5 → 7.4.7

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 +344 -35
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +342 -33
  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 +65 -3
  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,9 +5809,14 @@
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;
5817
+ // Null outside a 3D view.
5818
+ this.horizon = null;
5819
+ this.horizonScratch = new Cesium.Cartesian3();
5795
5820
  this.TrackPointer = (event) => {
5796
5821
  var _a, _b;
5797
5822
  if (event.pointerType && event.pointerType !== "mouse" && event.pointerType !== "pen")
@@ -5979,9 +6004,45 @@
5979
6004
  }
5980
6005
  };
5981
6006
  }
6007
+ /*
6008
+ * Whether the globe hides a position; labels skip the depth test, so this is their only occluder.
6009
+ */
6010
+ IsBeyondHorizon(position) {
6011
+ if (!this.horizon) {
6012
+ return false;
6013
+ }
6014
+ const { ellipsoid, camera, limbSquared } = this.horizon;
6015
+ // Scaled space makes the ellipsoid a unit sphere.
6016
+ const scaled = ellipsoid.transformPositionToScaledSpace(position, this.horizonScratch);
6017
+ const magnitudeSquared = Cesium.Cartesian3.magnitudeSquared(scaled);
6018
+ // Test sub-sea-level points from the surface.
6019
+ if (magnitudeSquared > 0 && magnitudeSquared < 1) {
6020
+ Cesium.Cartesian3.normalize(scaled, scaled);
6021
+ }
6022
+ const toPoint = Cesium.Cartesian3.subtract(scaled, camera, scaled);
6023
+ const along = -Cesium.Cartesian3.dot(toPoint, camera);
6024
+ return along > limbSquared && along * along / Cesium.Cartesian3.magnitudeSquared(toPoint) > limbSquared;
6025
+ }
6026
+ UpdateHorizon() {
6027
+ var _a, _b, _c;
6028
+ const scene = this.viewer.scene;
6029
+ const position = ((_a = scene === null || scene === void 0 ? void 0 : scene.camera) === null || _a === void 0 ? void 0 : _a.positionWC) || ((_b = scene === null || scene === void 0 ? void 0 : scene.camera) === null || _b === void 0 ? void 0 : _b.position);
6030
+ this.horizon = null;
6031
+ if ((scene === null || scene === void 0 ? void 0 : scene.mode) !== Cesium.SceneMode.SCENE3D || !Number.isFinite(position === null || position === void 0 ? void 0 : position.x)) {
6032
+ return;
6033
+ }
6034
+ const ellipsoid = ((_c = scene.globe) === null || _c === void 0 ? void 0 : _c.ellipsoid) || Cesium.Ellipsoid.WGS84;
6035
+ const camera = ellipsoid.transformPositionToScaledSpace(position, new Cesium.Cartesian3());
6036
+ const limbSquared = Cesium.Cartesian3.magnitudeSquared(camera) - 1;
6037
+ // No horizon from underground.
6038
+ if (limbSquared > 0) {
6039
+ this.horizon = { ellipsoid, camera, limbSquared };
6040
+ }
6041
+ }
5982
6042
  Update() {
5983
6043
  var _a, _b;
5984
6044
  this.coordinates = this.MeasureCoordinates();
6045
+ this.UpdateHorizon();
5985
6046
  const rect = (_b = (_a = this.viewer.canvas).getBoundingClientRect) === null || _b === void 0 ? void 0 : _b.call(_a);
5986
6047
  const pointer = this.pointerClient;
5987
6048
  this.pointer = pointer && (rect === null || rect === void 0 ? void 0 : rect.width) && (rect === null || rect === void 0 ? void 0 : rect.height) && pointer.x >= rect.left && pointer.x < rect.right && pointer.y >= rect.top && pointer.y < rect.bottom ?
@@ -5989,15 +6050,35 @@
5989
6050
  x: (pointer.x - rect.left) * this.viewer.canvas.clientWidth / rect.width,
5990
6051
  y: (pointer.y - rect.top) * this.viewer.canvas.clientHeight / rect.height
5991
6052
  } : undefined;
6053
+ // This runs inside Cesium's frame, where one throwing label would stop rendering for the whole scene.
5992
6054
  const projected = new Map();
5993
6055
  for (const participant of this.participants) {
5994
- projected.set(participant, participant.project());
6056
+ let frame = null;
6057
+ try {
6058
+ frame = participant.project();
6059
+ }
6060
+ catch (e) {
6061
+ this.logFailure(participant, e);
6062
+ }
6063
+ projected.set(participant, frame);
5995
6064
  }
5996
6065
  const placements = new Map(Layout(Array.from(projected.values()).filter(Boolean), this.placementMemory).map(item => [item.frame.id, item]));
5997
6066
  for (const [participant, frame] of projected) {
5998
- participant.apply(frame ? placements.get(frame.id) : undefined);
6067
+ try {
6068
+ participant.apply(frame ? placements.get(frame.id) : undefined);
6069
+ }
6070
+ catch (e) {
6071
+ this.logFailure(participant, e);
6072
+ }
5999
6073
  }
6000
6074
  }
6075
+ logFailure(participant, error) {
6076
+ if (this.failed.has(participant)) {
6077
+ return;
6078
+ }
6079
+ this.failed.add(participant);
6080
+ console.error("EntityLabel: a label failed to update and is skipped this frame.", error);
6081
+ }
6001
6082
  }
6002
6083
  let nextLabelId = 0;
6003
6084
  class Label {
@@ -6736,7 +6817,8 @@
6736
6817
  // If we're labelling from a temp label (mouse hover) don't drive opacity by cam distance.
6737
6818
  const hoverOnly = this.Transient && !this.Persistent;
6738
6819
  if (!Number.isFinite(distance) || depth <= 0 || distance < ((_c = this.options.near) !== null && _c !== void 0 ? _c : 0) ||
6739
- distance > ((_d = this.options.far) !== null && _d !== void 0 ? _d : Infinity) || (!this.prePositioned && !hoverOnly && distance >= MAX_DISTANCE)) {
6820
+ distance > ((_d = this.options.far) !== null && _d !== void 0 ? _d : Infinity) || (!this.prePositioned && !hoverOnly && distance >= MAX_DISTANCE) ||
6821
+ layer.IsBeyondHorizon(this._pos3d)) {
6740
6822
  return null;
6741
6823
  }
6742
6824
  const transforms = Cesium.SceneTransforms;
@@ -9267,6 +9349,7 @@
9267
9349
  })(exports.VisualRegisterCuller || (exports.VisualRegisterCuller = {}));
9268
9350
 
9269
9351
  function isTilesetFeatureAlive(visual) {
9352
+ var _a, _b;
9270
9353
  const cTileset = visual === null || visual === void 0 ? void 0 : visual.tileset;
9271
9354
  if (!cTileset) {
9272
9355
  return false;
@@ -9277,8 +9360,32 @@
9277
9360
  else if (visual.primitive && visual.primitive.isDestroyed()) {
9278
9361
  return false;
9279
9362
  }
9363
+ // A tile unloads its content while the tileset lives on, and every read through the feature then throws.
9364
+ 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))) {
9365
+ return false;
9366
+ }
9280
9367
  return true;
9281
9368
  }
9369
+ /*
9370
+ * Returns if a visual is currently shown, reading a tileset feature only while its tile is loaded.
9371
+ */
9372
+ function isVisualShown(visual) {
9373
+ if (!visual) {
9374
+ return true;
9375
+ }
9376
+ if (visual instanceof Cesium.Cesium3DTileFeature) {
9377
+ if (!isTilesetFeatureAlive(visual)) {
9378
+ return false;
9379
+ }
9380
+ try {
9381
+ return visual.show !== false;
9382
+ }
9383
+ catch (e) {
9384
+ return false;
9385
+ }
9386
+ }
9387
+ return visual.show !== false;
9388
+ }
9282
9389
  /**
9283
9390
  * Returns if a given visual is alive and in the scene.
9284
9391
  * @param viewer
@@ -9286,6 +9393,7 @@
9286
9393
  * @returns
9287
9394
  */
9288
9395
  function isAlive$1(viewer, visual) {
9396
+ var _a, _b;
9289
9397
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.scene) || viewer.isDestroyed()) {
9290
9398
  return false;
9291
9399
  }
@@ -9306,6 +9414,10 @@
9306
9414
  else if (visual.primitive && visual.primitive.isDestroyed()) {
9307
9415
  return false;
9308
9416
  }
9417
+ // Its tile unloaded, so every read through the feature throws.
9418
+ 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))) {
9419
+ return false;
9420
+ }
9309
9421
  return true;
9310
9422
  }
9311
9423
  return false;
@@ -9764,6 +9876,13 @@
9764
9876
  // Adding is asynchronous, so ordering has to wait until it is actually in the collection.
9765
9877
  Promise.resolve(this.viewer.dataSources.add(source))
9766
9878
  .then(() => {
9879
+ // Released or disposed before the add landed, when the remove found nothing to take out.
9880
+ if (this.menuItemSources.get(menuItemId) !== record) {
9881
+ if (!this.viewer.isDestroyed() && this.viewer.dataSources.contains(source)) {
9882
+ this.viewer.dataSources.remove(source, true);
9883
+ }
9884
+ return;
9885
+ }
9767
9886
  record.added = true;
9768
9887
  this.applySourceOrder();
9769
9888
  })
@@ -9800,7 +9919,9 @@
9800
9919
  if (this.disposed || !this.sourceOrderDirty || !((_a = this.viewer) === null || _a === void 0 ? void 0 : _a.dataSources)) {
9801
9920
  return;
9802
9921
  }
9803
- const records = Array.from(this.menuItemSources.values()).filter(x => x.added);
9922
+ // raiseToTop throws for a source the collection does not hold.
9923
+ const dataSources = this.viewer.dataSources;
9924
+ const records = Array.from(this.menuItemSources.values()).filter(x => x.added && dataSources.contains(x.source));
9804
9925
  if (records.length < 2) {
9805
9926
  // Nothing to order against yet, so leave the flag set for when there is.
9806
9927
  return;
@@ -9810,7 +9931,7 @@
9810
9931
  records.sort((a, b) => a.maxZIndex - b.maxZIndex);
9811
9932
  for (const record of records) {
9812
9933
  try {
9813
- this.viewer.dataSources.raiseToTop(record.source);
9934
+ dataSources.raiseToTop(record.source);
9814
9935
  }
9815
9936
  catch (e) {
9816
9937
  console.error(e);
@@ -12150,7 +12271,7 @@
12150
12271
  return (hover === null || hover === void 0 ? void 0 : hover.entityId) === entityId && (hover.menuItemId == null || hover.menuItemId === menuItemId) ? hover.position : undefined;
12151
12272
  }
12152
12273
  SyncLabelHover() {
12153
- var _a, _b, _c, _d;
12274
+ var _a, _b, _c;
12154
12275
  const hover = this.labelHover;
12155
12276
  if (!hover || this.disposed) {
12156
12277
  return;
@@ -12175,7 +12296,7 @@
12175
12296
  }
12176
12297
  const existing = rego && exports.EntityLabel.GetLabel({ rego });
12177
12298
  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 ||
12299
+ if ((!rego && !group) || state.hidden || ((rego === null || rego === void 0 ? void 0 : rego.visual) && !isVisualShown(rego.visual)) ||
12179
12300
  (this.GetIsIsolatedAny() && !state.isolated && this.GetIsolationOpacity() == null) ||
12180
12301
  (existing && existing !== this.hoverLabel && existing.HasText) ||
12181
12302
  (group && group.options.presentation !== "icon")) {
@@ -12187,7 +12308,7 @@
12187
12308
  return;
12188
12309
  }
12189
12310
  // 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)) {
12311
+ if (!rego && group && [group.item.longitude, group.item.latitude, (_b = group.item.altitude) !== null && _b !== void 0 ? _b : 0].every(Number.isFinite)) {
12191
12312
  rego = {
12192
12313
  entityId: hover.entityId,
12193
12314
  menuItemId: hover.menuItemId,
@@ -12209,7 +12330,7 @@
12209
12330
  rego,
12210
12331
  viewer: this.viewer,
12211
12332
  visualRegister: this,
12212
- api: (_d = this.ApiGetters) === null || _d === void 0 ? void 0 : _d.GetBruceApi(),
12333
+ api: (_c = this.ApiGetters) === null || _c === void 0 ? void 0 : _c.GetBruceApi(),
12213
12334
  persistent: false,
12214
12335
  transient: true,
12215
12336
  hoverRingState: this.hoverRingState,
@@ -12219,10 +12340,10 @@
12219
12340
  selectOnClick: false
12220
12341
  },
12221
12342
  visible: () => {
12222
- var _a, _b;
12343
+ var _a;
12223
12344
  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))) &&
12345
+ return (!this.disposed && !current.hidden && isVisualShown(rego.visual) &&
12346
+ Boolean(this.GetRego(hover) || ((_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.Has(hover.entityId, hover.menuItemId))) &&
12226
12347
  (!this.GetIsIsolatedAny() || current.isolated || this.GetIsolationOpacity() != null));
12227
12348
  }
12228
12349
  };
@@ -14837,12 +14958,20 @@
14837
14958
  strokeWidth: lineWidthPx,
14838
14959
  clampToGround: lineWidthPx <= 0 && polygonsClamped
14839
14960
  });
14840
- this.viewer.dataSources.add(source);
14841
- this.sources.push(source);
14842
- if (this.disposed) {
14843
- this.doDispose();
14961
+ // Dispose already cleared what was added before this, and this one has not been added.
14962
+ if (this.disposed || this.viewer.isDestroyed()) {
14844
14963
  return;
14845
14964
  }
14965
+ const viewer = this.viewer;
14966
+ this.sources.push(source);
14967
+ // The add lands asynchronously, so a dispose in between removes nothing and would orphan the source.
14968
+ Promise.resolve(viewer.dataSources.add(source)).then(() => {
14969
+ if (this.disposed && !viewer.isDestroyed() && viewer.dataSources.contains(source)) {
14970
+ viewer.dataSources.remove(source);
14971
+ }
14972
+ }).catch((e) => {
14973
+ console.error(e);
14974
+ });
14846
14975
  const groups = [];
14847
14976
  /**
14848
14977
  * Applies individual styling to a rendered entity.
@@ -15878,12 +16007,20 @@
15878
16007
  strokeWidth: lineWidthPx,
15879
16008
  clampToGround: lineWidthPx <= 0 && polygonsClamped
15880
16009
  });
15881
- this.viewer.dataSources.add(source);
15882
- this.sources.push(source);
15883
- if (this.disposed) {
15884
- this.doDispose();
16010
+ // Dispose already cleared what was added before this, and this one has not been added.
16011
+ if (this.disposed || this.viewer.isDestroyed()) {
15885
16012
  return;
15886
16013
  }
16014
+ const viewer = this.viewer;
16015
+ this.sources.push(source);
16016
+ // The add lands asynchronously, so a dispose in between removes nothing and would orphan the source.
16017
+ Promise.resolve(viewer.dataSources.add(source)).then(() => {
16018
+ if (this.disposed && !viewer.isDestroyed() && viewer.dataSources.contains(source)) {
16019
+ viewer.dataSources.remove(source);
16020
+ }
16021
+ }).catch((e) => {
16022
+ console.error(e);
16023
+ });
15887
16024
  const groups = [];
15888
16025
  /**
15889
16026
  * Applies individual styling to a rendered entity.
@@ -19563,7 +19700,7 @@
19563
19700
  });
19564
19701
  this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
19565
19702
  try {
19566
- this.queueTile(tile, false);
19703
+ this.unloadTile(tile);
19567
19704
  }
19568
19705
  catch (e) {
19569
19706
  console.error(e);
@@ -19747,7 +19884,7 @@
19747
19884
  });
19748
19885
  this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
19749
19886
  try {
19750
- this.queueTile(tile, false);
19887
+ this.unloadTile(tile);
19751
19888
  }
19752
19889
  catch (e) {
19753
19890
  console.error(e);
@@ -19812,6 +19949,35 @@
19812
19949
  }
19813
19950
  this.pingFeatureQueue();
19814
19951
  }
19952
+ /*
19953
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
19954
+ */
19955
+ unloadTile(tile) {
19956
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
19957
+ if (!content) {
19958
+ return;
19959
+ }
19960
+ const features = [];
19961
+ for (let i = 0; i < content.featuresLength; i++) {
19962
+ const feature = content.getFeature(i);
19963
+ // Drops a load still queued for it, the batch skips anything marked.
19964
+ this.featureQueueStates.delete(feature);
19965
+ feature._bruceUnloaded = true;
19966
+ features.push(feature);
19967
+ }
19968
+ if (!features.length) {
19969
+ return;
19970
+ }
19971
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
19972
+ visuals: features,
19973
+ requestRender: false,
19974
+ menuItemId: this.item.id,
19975
+ doRemove: false
19976
+ });
19977
+ for (const entityId of removedEntityIds) {
19978
+ delete this.loadedCesiumEntities[entityId];
19979
+ }
19980
+ }
19815
19981
  /**
19816
19982
  * Pings the feature queue to process any queued features.
19817
19983
  * Does nothing if we're already processing.
@@ -19828,7 +19994,7 @@
19828
19994
  return;
19829
19995
  }
19830
19996
  this.processFeatureQueueBatch();
19831
- if (!this.featureQueue) {
19997
+ if (!this.featureQueue.length) {
19832
19998
  clearInterval(this.featureQueueInterval);
19833
19999
  this.featureQueueInterval = null;
19834
20000
  }
@@ -19860,6 +20026,10 @@
19860
20026
  const regosToStyle = new Map();
19861
20027
  const featuresToRemove = [];
19862
20028
  for (const feature of batch) {
20029
+ // Its tile unloaded while it waited, unloadTile already removed whatever it registered.
20030
+ if (feature._bruceUnloaded) {
20031
+ continue;
20032
+ }
19863
20033
  const load = this.featureQueueStates.get(feature);
19864
20034
  this.featureQueueStates.delete(feature);
19865
20035
  if (!load) {
@@ -23027,6 +23197,7 @@
23027
23197
  this.processingQueue = 0;
23028
23198
  this._osmIdToBruceId = {};
23029
23199
  this.tileLoadRemoval = null;
23200
+ this.tileUnloadRemoval = null;
23030
23201
  const { viewer, register: visualsManager, getters: apiGetter, monitor, item } = params;
23031
23202
  this.viewer = viewer;
23032
23203
  this.getters = apiGetter;
@@ -23035,7 +23206,7 @@
23035
23206
  this.monitor = monitor;
23036
23207
  }
23037
23208
  Dispose() {
23038
- var _a;
23209
+ var _a, _b;
23039
23210
  if (this.disposed) {
23040
23211
  return;
23041
23212
  }
@@ -23043,6 +23214,8 @@
23043
23214
  clearInterval(this.queueCheckInterval);
23044
23215
  (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
23045
23216
  this.tileLoadRemoval = null;
23217
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
23218
+ this.tileUnloadRemoval = null;
23046
23219
  if (this.cTileset) {
23047
23220
  const viewer = this.viewer;
23048
23221
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -23138,6 +23311,14 @@
23138
23311
  console.error(e);
23139
23312
  }
23140
23313
  });
23314
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
23315
+ try {
23316
+ this.unloadTile(tile);
23317
+ }
23318
+ catch (e) {
23319
+ console.error(e);
23320
+ }
23321
+ });
23141
23322
  this.queueCheckInterval = setInterval(() => {
23142
23323
  if (this.featureQueue.length > 0) {
23143
23324
  this.processQueue();
@@ -23174,6 +23355,34 @@
23174
23355
  this.processQueue();
23175
23356
  }
23176
23357
  }
23358
+ /*
23359
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
23360
+ */
23361
+ unloadTile(tile) {
23362
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
23363
+ if (!content) {
23364
+ return;
23365
+ }
23366
+ const features = [];
23367
+ for (let i = 0; i < content.featuresLength; i++) {
23368
+ const feature = content.getFeature(i);
23369
+ feature._bruceUnloaded = true;
23370
+ features.push(feature);
23371
+ }
23372
+ if (!features.length) {
23373
+ return;
23374
+ }
23375
+ this.featureQueue = this.featureQueue.filter(x => !x._bruceUnloaded);
23376
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
23377
+ visuals: features,
23378
+ requestRender: false,
23379
+ menuItemId: this.item.id,
23380
+ doRemove: false
23381
+ });
23382
+ for (const entityId of removedEntityIds) {
23383
+ delete this._loadedCesiumEntities[entityId];
23384
+ }
23385
+ }
23177
23386
  async processQueue() {
23178
23387
  if (this.processingQueue >= MAX_BATCHES) {
23179
23388
  return;
@@ -23306,6 +23515,10 @@
23306
23515
  bruceId = BModels.ObjectUtils.UId();
23307
23516
  }
23308
23517
  const feature = featureMap[i];
23518
+ // Its tile unloaded while the ID lookup was in flight.
23519
+ if (feature._bruceUnloaded) {
23520
+ continue;
23521
+ }
23309
23522
  if (this.item.ghostLoading == true) {
23310
23523
  feature.color = feature._orgColor;
23311
23524
  }
@@ -23397,7 +23610,7 @@
23397
23610
  return `osm:${sourceId}:${entityTypeId}`;
23398
23611
  }
23399
23612
  PrepareHandoff() {
23400
- var _a;
23613
+ var _a, _b;
23401
23614
  if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
23402
23615
  return null;
23403
23616
  }
@@ -23405,6 +23618,8 @@
23405
23618
  this.queueCheckInterval = null;
23406
23619
  (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
23407
23620
  this.tileLoadRemoval = null;
23621
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
23622
+ this.tileUnloadRemoval = null;
23408
23623
  const payload = {
23409
23624
  cTileset: this.cTileset,
23410
23625
  loadedCesiumEntities: this._loadedCesiumEntities,
@@ -23452,6 +23667,14 @@
23452
23667
  console.error(e);
23453
23668
  }
23454
23669
  });
23670
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
23671
+ try {
23672
+ this.unloadTile(tile);
23673
+ }
23674
+ catch (e) {
23675
+ console.error(e);
23676
+ }
23677
+ });
23455
23678
  this.queueCheckInterval = setInterval(() => {
23456
23679
  if (this.featureQueue.length > 0) {
23457
23680
  this.processQueue();
@@ -23641,7 +23864,7 @@
23641
23864
  });
23642
23865
  this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
23643
23866
  try {
23644
- this.queueTile(tile, false);
23867
+ this.unloadTile(tile);
23645
23868
  }
23646
23869
  catch (e) {
23647
23870
  console.error(e);
@@ -23687,6 +23910,35 @@
23687
23910
  }
23688
23911
  this.pingFeatureQueue();
23689
23912
  }
23913
+ /*
23914
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
23915
+ */
23916
+ unloadTile(tile) {
23917
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
23918
+ if (!content) {
23919
+ return;
23920
+ }
23921
+ const features = [];
23922
+ for (let i = 0; i < content.featuresLength; i++) {
23923
+ const feature = content.getFeature(i);
23924
+ // Drops a load still queued for it, the batch skips anything marked.
23925
+ this.featureQueueStates.delete(feature);
23926
+ feature._bruceUnloaded = true;
23927
+ features.push(feature);
23928
+ }
23929
+ if (!features.length) {
23930
+ return;
23931
+ }
23932
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
23933
+ visuals: features,
23934
+ requestRender: false,
23935
+ menuItemId: this.item.id,
23936
+ doRemove: false
23937
+ });
23938
+ for (const entityId of removedEntityIds) {
23939
+ delete this.loadedCesiumEntities[entityId];
23940
+ }
23941
+ }
23690
23942
  /**
23691
23943
  * Pings the feature queue to process any queued features.
23692
23944
  * Does nothing if we're already processing.
@@ -23703,7 +23955,7 @@
23703
23955
  return;
23704
23956
  }
23705
23957
  this.processFeatureQueueBatch();
23706
- if (!this.featureQueue) {
23958
+ if (!this.featureQueue.length) {
23707
23959
  clearInterval(this.featureQueueInterval);
23708
23960
  this.featureQueueInterval = null;
23709
23961
  }
@@ -23721,6 +23973,10 @@
23721
23973
  const regosToStyle = new Map();
23722
23974
  const featuresToRemove = [];
23723
23975
  for (const feature of batch) {
23976
+ // Its tile unloaded while it waited, unloadTile already removed whatever it registered.
23977
+ if (feature._bruceUnloaded) {
23978
+ continue;
23979
+ }
23724
23980
  const load = this.featureQueueStates.get(feature);
23725
23981
  this.featureQueueStates.delete(feature);
23726
23982
  if (!load) {
@@ -23926,7 +24182,7 @@
23926
24182
  });
23927
24183
  this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
23928
24184
  try {
23929
- this.queueTile(tile, false);
24185
+ this.unloadTile(tile);
23930
24186
  }
23931
24187
  catch (e) {
23932
24188
  console.error(e);
@@ -24880,7 +25136,7 @@
24880
25136
  });
24881
25137
  this.cTileset.tileUnload.addEventListener((tile) => {
24882
25138
  try {
24883
- this.queueTile(tile, false);
25139
+ this.unloadTile(tile);
24884
25140
  }
24885
25141
  catch (e) {
24886
25142
  console.error(e);
@@ -24908,6 +25164,35 @@
24908
25164
  }
24909
25165
  this.pingFeatureQueue();
24910
25166
  }
25167
+ /*
25168
+ * Removes a tile's regos and marks its features dead, while the tile's content can still be read.
25169
+ */
25170
+ unloadTile(tile) {
25171
+ const content = tile === null || tile === void 0 ? void 0 : tile.content;
25172
+ if (!content) {
25173
+ return;
25174
+ }
25175
+ const features = [];
25176
+ for (let i = 0; i < content.featuresLength; i++) {
25177
+ const feature = content.getFeature(i);
25178
+ // Drops a load still queued for it, the batch skips anything marked.
25179
+ this.featureQueueStates.delete(feature);
25180
+ feature._bruceUnloaded = true;
25181
+ features.push(feature);
25182
+ }
25183
+ if (!features.length) {
25184
+ return;
25185
+ }
25186
+ const { removedEntityIds } = this.visualsManager.RemoveRegosByVisuals({
25187
+ visuals: features,
25188
+ requestRender: false,
25189
+ menuItemId: this.item.id,
25190
+ doRemove: false
25191
+ });
25192
+ for (const entityId of removedEntityIds) {
25193
+ delete this.loadedCesiumEntities[entityId];
25194
+ }
25195
+ }
24911
25196
  /**
24912
25197
  * Pings the feature queue to process any queued features.
24913
25198
  * Does nothing if we're already processing.
@@ -24924,7 +25209,7 @@
24924
25209
  return;
24925
25210
  }
24926
25211
  this.processFeatureQueueBatch();
24927
- if (!this.featureQueue) {
25212
+ if (!this.featureQueue.length) {
24928
25213
  clearInterval(this.featureQueueInterval);
24929
25214
  this.featureQueueInterval = null;
24930
25215
  }
@@ -24942,6 +25227,10 @@
24942
25227
  const regosToStyle = new Map();
24943
25228
  const featuresToRemove = [];
24944
25229
  for (const feature of batch) {
25230
+ // Its tile unloaded while it waited, unloadTile already removed whatever it registered.
25231
+ if (feature._bruceUnloaded) {
25232
+ continue;
25233
+ }
24945
25234
  const load = this.featureQueueStates.get(feature);
24946
25235
  this.featureQueueStates.delete(feature);
24947
25236
  if (!load) {
@@ -25558,6 +25847,8 @@
25558
25847
  constructor(params) {
25559
25848
  this.disposed = false;
25560
25849
  this.cTileset = null;
25850
+ // Set while a tileset is being created, so a second Init in the meantime cannot add another.
25851
+ this.initializing = false;
25561
25852
  const { viewer, item, getters } = params;
25562
25853
  this.viewer = viewer;
25563
25854
  this.item = item;
@@ -25578,6 +25869,10 @@
25578
25869
  this.viewer.scene.primitives.remove(this.cTileset);
25579
25870
  this.viewer.scene.requestRender();
25580
25871
  }
25872
+ // Disposed before it was added, so nothing else will ever destroy it.
25873
+ else if (!this.cTileset.isDestroyed()) {
25874
+ this.cTileset.destroy();
25875
+ }
25581
25876
  this.cTileset = null;
25582
25877
  }
25583
25878
  }
@@ -25596,6 +25891,11 @@
25596
25891
  this.viewer.scene.requestRender();
25597
25892
  return;
25598
25893
  }
25894
+ // The creation in flight applies the latest item's colour mask when it lands.
25895
+ if (this.initializing) {
25896
+ return;
25897
+ }
25898
+ this.initializing = true;
25599
25899
  try {
25600
25900
  const api = this.getters.GetBruceApi();
25601
25901
  await api.Loading;
@@ -25681,6 +25981,14 @@
25681
25981
  console.error("Failed to load Google Photorealistic 3D tileset:", e);
25682
25982
  return;
25683
25983
  }
25984
+ finally {
25985
+ this.initializing = false;
25986
+ }
25987
+ // Creation can come back empty, and the viewer can go while it loads.
25988
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed() || !this.viewer || this.viewer.isDestroyed()) {
25989
+ this.doDispose();
25990
+ return;
25991
+ }
25684
25992
  this.viewer.scene.primitives.add(this.cTileset);
25685
25993
  this.applyColorMask();
25686
25994
  this.viewer.scene.requestRender();
@@ -43124,7 +43432,8 @@
43124
43432
  const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(heading), Cesium.Math.toRadians(pitch), Cesium.Math.toRadians(roll));
43125
43433
  getOrient = new Cesium.CallbackProperty(() => {
43126
43434
  const currentPos = position ? GetCValue(params.viewer, position) : pos3d;
43127
- return Cesium.Transforms.headingPitchRollQuaternion(currentPos, hpr);
43435
+ // A live position can read as null mid-update, and throwing here would stop Cesium's frame.
43436
+ return Cesium.Transforms.headingPitchRollQuaternion(currentPos || pos3d, hpr);
43128
43437
  }, false);
43129
43438
  }
43130
43439
  cEntity = new Cesium.Entity({
@@ -50434,7 +50743,7 @@ void main() {
50434
50743
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
50435
50744
  })(exports.StyleUtils || (exports.StyleUtils = {}));
50436
50745
 
50437
- const VERSION = "7.4.5";
50746
+ const VERSION = "7.4.7";
50438
50747
  /**
50439
50748
  * Updates the environment instance used by bruce-cesium to one specified.
50440
50749
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.