bruce-cesium 7.2.4 → 7.2.5

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 (31) hide show
  1. package/dist/bruce-cesium.es5.js +326 -34
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +324 -32
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/internal/cesium-utils.js +32 -1
  7. package/dist/lib/internal/cesium-utils.js.map +1 -1
  8. package/dist/lib/rendering/entity-gatherer.js +111 -0
  9. package/dist/lib/rendering/entity-gatherer.js.map +1 -1
  10. package/dist/lib/rendering/menu-item-manager.js +1 -0
  11. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  12. package/dist/lib/rendering/render-managers/common/historic-utils.js +91 -24
  13. package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -1
  14. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +1 -0
  15. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  16. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +1 -0
  17. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  18. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +61 -3
  19. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  20. package/dist/lib/rendering/texture-frame-series-animator.js +20 -3
  21. package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
  22. package/dist/lib/rendering/tileset-styler.js +10 -1
  23. package/dist/lib/rendering/tileset-styler.js.map +1 -1
  24. package/dist/types/bruce-cesium.d.ts +1 -1
  25. package/dist/types/internal/cesium-utils.d.ts +9 -0
  26. package/dist/types/rendering/entity-gatherer.d.ts +23 -0
  27. package/dist/types/rendering/render-managers/common/historic-utils.d.ts +30 -5
  28. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +5 -0
  29. package/dist/types/rendering/texture-frame-series-animator.d.ts +1 -0
  30. package/dist/types/rendering/tileset-styler.d.ts +3 -0
  31. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import * as Cesium from 'cesium';
2
- import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, DistanceDisplayCondition, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, HeightReference, ColorBlendMode, ShadowMode, ClassificationType, Model, ShaderSource, Ellipsoid, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Cartesian2, HorizontalOrigin, VerticalOrigin, ConstantPositionProperty, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, ArcType, CornerType, Rectangle, SceneTransforms, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, HeadingPitchRange, Ion, IonResource, Cesium3DTileStyle, Cesium3DTileColorBlendMode, Cesium3DTileset, OrthographicFrustum, EasingFunction, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Intersect, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, CustomDataSource, GeometryInstance, PolygonPipeline, CesiumInspector, defined, ClockRange, DynamicEnvironmentMapManager, EllipsoidGeodesic, sampleTerrainMostDetailed, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
- import { Cartes, Entity as Entity$1, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Api, Bounds, Color as Color$1, Geometry as Geometry$1, Carto, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, DataLab, DelayQueue, BruceApi, AccountConcept, RecordChangeFeed, EntityRelation, Camera, ProgramKey, ProjectViewLegacyTile, EntitySource, Account, AccountLimits, Session, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, EntityRelationType, EntityHistoricData } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, ShaderSource, Ellipsoid, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Cartesian2, DistanceDisplayCondition, HeightReference, HorizontalOrigin, VerticalOrigin, ClassificationType, ConstantPositionProperty, ArcType, CornerType, ShadowMode, ColorBlendMode, Model, SceneTransforms, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileset, Cesium3DTileColorBlendMode, OrthographicFrustum, EasingFunction, Intersect, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, GeometryInstance, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
+ import { Cartes, Entity as Entity$1, ProjectViewTile, ENVIRONMENT, Api, Calculator, ClientFile, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry as Geometry$1, EntityLod, Color as Color$1, Bounds, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, DelayQueue, DataLab, BruceApi, EntityRelation, AccountConcept, RecordChangeFeed, EntitySource, Camera, ProgramKey, ProjectViewLegacyTile, Account, AccountLimits, Session, UrlUtils, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
4
4
 
5
5
  /**
6
6
  * Ensures a number is returned from a given value.
@@ -2889,6 +2889,36 @@ function SetCPropIfChanged(params) {
2889
2889
  function ColorToCColor(color) {
2890
2890
  return new Color(color.red ? color.red / 255 : 0, color.green ? color.green / 255 : 0, color.blue ? color.blue / 255 : 0, color.alpha);
2891
2891
  }
2892
+ const degreesCache = new WeakMap();
2893
+ /**
2894
+ * Returns a world position's latitude and longitude in degrees, or null when it has none.
2895
+ * @param pos
2896
+ */
2897
+ function CartesianToDegrees(pos) {
2898
+ if (!pos) {
2899
+ return null;
2900
+ }
2901
+ const cached = degreesCache.get(pos);
2902
+ if (cached) {
2903
+ return cached;
2904
+ }
2905
+ let carto;
2906
+ try {
2907
+ carto = Cartographic.fromCartesian(pos);
2908
+ }
2909
+ catch {
2910
+ return null;
2911
+ }
2912
+ if (!carto) {
2913
+ return null;
2914
+ }
2915
+ const degrees = {
2916
+ lat: Math$1.toDegrees(carto.latitude),
2917
+ lon: Math$1.toDegrees(carto.longitude)
2918
+ };
2919
+ degreesCache.set(pos, degrees);
2920
+ return degrees;
2921
+ }
2892
2922
  /**
2893
2923
  * Removes duplicate points in a row and returns a new array.
2894
2924
  * Passed array is not mutated.
@@ -10758,41 +10788,107 @@ var HistoricFetchOrder;
10758
10788
  return Math.round(minMs + ((maxMs - minMs) * progress));
10759
10789
  }
10760
10790
  HistoricFetchOrder.IntervalFor = IntervalFor;
10791
+ // Upper bounds in metres for the distance bands, nearest first.
10792
+ HistoricFetchOrder.DEFAULT_BANDS = [500, 2000, 10000, 50000];
10761
10793
  /**
10762
- * Returns the Entities grouped into bands by distance from the camera, nearest band first.
10763
- *
10764
- * Bands rather than a true sort, so a small camera move does not reshuffle the whole queue and
10765
- * restart it. What the user is looking at resolves first either way.
10794
+ * Resolves what to order by from the view monitor, falling back to the camera itself.
10766
10795
  */
10767
- function ByCameraDistance(params) {
10796
+ function FocusFrom(params) {
10768
10797
  var _a;
10769
- const { viewer, entities } = params;
10770
- const bands = params.bands || [500, 2000, 10000, 50000];
10771
- if (!(entities === null || entities === void 0 ? void 0 : entities.length)) {
10798
+ const { viewer, monitor } = params;
10799
+ let origin = null;
10800
+ let bounds = null;
10801
+ try {
10802
+ // Null whenever the view has no ground intersection, eg. a camera aimed at the sky.
10803
+ const target = monitor === null || monitor === void 0 ? void 0 : monitor.GetTarget();
10804
+ if (target && isFinite(target.latitude) && isFinite(target.longitude)) {
10805
+ origin = Cartesian3.fromDegrees(target.longitude, target.latitude);
10806
+ }
10807
+ bounds = (monitor === null || monitor === void 0 ? void 0 : monitor.GetBounds()) || null;
10808
+ }
10809
+ catch {
10810
+ origin = null;
10811
+ bounds = null;
10812
+ }
10813
+ if (!origin) {
10814
+ origin = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.camera) === null || _a === void 0 ? void 0 : _a.position;
10815
+ }
10816
+ if (!origin) {
10817
+ return null;
10818
+ }
10819
+ return bounds ? { origin, bounds } : { origin };
10820
+ }
10821
+ HistoricFetchOrder.FocusFrom = FocusFrom;
10822
+ /**
10823
+ * Returns the Entities grouped nearest first, on screen ahead of off screen.
10824
+ */
10825
+ function ByCameraDistance(params) {
10826
+ const { viewer, entities, monitor } = params;
10827
+ return ByCameraDistanceOf({
10828
+ focus: FocusFrom({ viewer, monitor }),
10829
+ items: entities,
10830
+ posOf: (entity) => SafePos(entity, viewer),
10831
+ bands: params.bands
10832
+ });
10833
+ }
10834
+ HistoricFetchOrder.ByCameraDistance = ByCameraDistance;
10835
+ /**
10836
+ * Bands anything that can name its own world position, on the same bounds as the Entity form.
10837
+ */
10838
+ function ByCameraDistanceOf(params) {
10839
+ const { focus, items, posOf } = params;
10840
+ const bands = params.bands || HistoricFetchOrder.DEFAULT_BANDS;
10841
+ if (!(items === null || items === void 0 ? void 0 : items.length)) {
10772
10842
  return [];
10773
10843
  }
10774
- const cameraPos = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.camera) === null || _a === void 0 ? void 0 : _a.position;
10775
- if (!cameraPos) {
10776
- return [entities];
10844
+ if (!(focus === null || focus === void 0 ? void 0 : focus.origin)) {
10845
+ return [items];
10846
+ }
10847
+ // One band past the named bounds, for everything beyond the last of them. Then the same
10848
+ // again for what is off screen, so nothing visible waits behind something that is not.
10849
+ const perTier = bands.length + 1;
10850
+ const offScreenTier = focus.bounds ? perTier : 0;
10851
+ const placeless = offScreenTier + perTier;
10852
+ const grouped = [];
10853
+ for (let i = 0; i <= placeless; i++) {
10854
+ grouped.push([]);
10777
10855
  }
10778
- // One band past the named bounds, for everything beyond the last of them.
10779
- const banded = bands.map(() => []).concat([[]]);
10780
- for (const entity of entities) {
10781
- const pos = SafePos(entity, viewer);
10856
+ for (const item of items) {
10857
+ // An item with no position falls to the last group rather than blocking the near ones.
10858
+ const pos = posOf(item);
10782
10859
  if (!pos) {
10783
- banded[banded.length - 1].push(entity);
10860
+ grouped[placeless].push(item);
10784
10861
  continue;
10785
10862
  }
10786
- const distance = Cartesian3.distance(cameraPos, pos);
10787
- let index = bands.findIndex(bound => distance <= bound);
10788
- if (index < 0) {
10789
- index = banded.length - 1;
10863
+ const distance = Cartesian3.distance(focus.origin, pos);
10864
+ let band = bands.findIndex(bound => distance <= bound);
10865
+ if (band < 0) {
10866
+ band = bands.length;
10790
10867
  }
10791
- banded[index].push(entity);
10868
+ grouped[(focus.bounds && !InBounds(pos, focus.bounds)) ? offScreenTier + band : band].push(item);
10792
10869
  }
10793
- return banded.filter(band => band.length > 0);
10870
+ return grouped.filter(group => group.length > 0);
10794
10871
  }
10795
- HistoricFetchOrder.ByCameraDistance = ByCameraDistance;
10872
+ HistoricFetchOrder.ByCameraDistanceOf = ByCameraDistanceOf;
10873
+ /*
10874
+ * Returns whether a world position sits inside a ground rectangle given in degrees.
10875
+ */
10876
+ function InBounds(pos, bounds) {
10877
+ const degrees = CartesianToDegrees(pos);
10878
+ if (!degrees) {
10879
+ return false;
10880
+ }
10881
+ const { lat, lon } = degrees;
10882
+ if (lat < bounds.south || lat > bounds.north) {
10883
+ return false;
10884
+ }
10885
+ // A view straddling the antimeridian reports west east of east, so the span is the outside.
10886
+ if (bounds.west > bounds.east) {
10887
+ return lon >= bounds.west || lon <= bounds.east;
10888
+ }
10889
+ return lon >= bounds.west && lon <= bounds.east;
10890
+ }
10891
+ HistoricFetchOrder.InBounds = InBounds;
10796
10892
  /*
10797
10893
  * Returns an Entity's world position, or null when it has none to place it by.
10798
10894
  */
@@ -11110,7 +11206,7 @@ class HistoricGatherer {
11110
11206
  }
11111
11207
  if (plan.detail) {
11112
11208
  const bands = entities.length > HistoricFetchOrder.SMALL_SET
11113
- ? HistoricFetchOrder.ByCameraDistance({ viewer, entities })
11209
+ ? HistoricFetchOrder.ByCameraDistance({ viewer, entities, monitor: this.params.monitor })
11114
11210
  : [entities];
11115
11211
  for (const range of this.cache.MissingRanges(plan.detail.start, plan.detail.stop)) {
11116
11212
  if (this.disposed) {
@@ -11279,6 +11375,7 @@ var EntitiesRenderManager;
11279
11375
  this.onSeriesDiscovered = onSeriesDiscovered;
11280
11376
  this.historicGatherer = new HistoricGatherer({
11281
11377
  viewer: this.viewer,
11378
+ monitor: this.monitor,
11282
11379
  getApi: () => this.apiGetter.getApi(),
11283
11380
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
11284
11381
  // A second either side, to take in records sitting exactly on the boundary.
@@ -13395,6 +13492,7 @@ var EntitiesIdsRenderManager;
13395
13492
  this.visualsManager = visualsManager;
13396
13493
  this.historicGatherer = new HistoricGatherer({
13397
13494
  viewer: this.viewer,
13495
+ monitor: this.monitor,
13398
13496
  getApi: () => this.apiGetter.getApi(),
13399
13497
  getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
13400
13498
  // Padding either side of a requested range, for clock desync between us and the server.
@@ -14033,6 +14131,16 @@ class EntityGatherer {
14033
14131
  this.lastFoundDateTimes = new Map();
14034
14132
  // ID of Entity IDs that need to be requested.
14035
14133
  this.eIdQueue = [];
14134
+ // Resolves where an Entity sits without reading its record, when the caller can say.
14135
+ // Unset leaves the queue in insertion order, which is what sources that bake no coordinates get.
14136
+ this.positionOf = null;
14137
+ // How many IDs at the front of the queue were explicitly asked for first.
14138
+ // Camera ordering leaves those alone, since an explicit re-render outranks proximity.
14139
+ this.priorityCount = 0;
14140
+ // View focus the queue was last banded from.
14141
+ this.lastOrderFocus = null;
14142
+ // Reports what the camera is aimed at, rather than only where it sits.
14143
+ this.monitor = null;
14036
14144
  // All IDs that have ever been queued.
14037
14145
  // When nothing is in queue, we'll re-request the existing IDs based on timeline changes.
14038
14146
  this.allEIds = new Set();
@@ -14132,6 +14240,8 @@ class EntityGatherer {
14132
14240
  return;
14133
14241
  }
14134
14242
  this.eIdQueue = [];
14243
+ this.priorityCount = 0;
14244
+ this.lastOrderFocus = null;
14135
14245
  this.allEIds.clear();
14136
14246
  this.lastFoundDateTimes.clear();
14137
14247
  this.lastDateTime = null;
@@ -14141,6 +14251,89 @@ class EntityGatherer {
14141
14251
  return;
14142
14252
  }
14143
14253
  this.eIdQueue = Array.from(this.allEIds);
14254
+ this.priorityCount = 0;
14255
+ this.lastOrderFocus = null;
14256
+ }
14257
+ /**
14258
+ * Supplies a lookup from Entity ID to world position, letting the queue resolve what the camera
14259
+ * is nearest first. Sources that cannot answer should not call this, and are served in order.
14260
+ */
14261
+ SetPositionSource(positionOf) {
14262
+ this.positionOf = positionOf;
14263
+ this.lastOrderFocus = null;
14264
+ }
14265
+ /**
14266
+ * Supplies the view monitor, so the order follows the ground the camera is aimed at and prefers
14267
+ * what is on screen. Without one the ordering falls back to distance from the camera itself.
14268
+ */
14269
+ SetViewMonitor(monitor) {
14270
+ this.monitor = monitor;
14271
+ this.lastOrderFocus = null;
14272
+ }
14273
+ /**
14274
+ * Re-bands the pending queue so the nearest Entities are requested first.
14275
+ */
14276
+ orderQueueByCamera() {
14277
+ if (!this.positionOf || this.eIdQueue.length < EntityGatherer.MIN_ORDER_QUEUE) {
14278
+ return;
14279
+ }
14280
+ const focus = HistoricFetchOrder.FocusFrom({ viewer: this.viewer, monitor: this.monitor });
14281
+ if (!focus) {
14282
+ return;
14283
+ }
14284
+ // Re-banding on every batch would cost a full pass per request for no change in the answer.
14285
+ // Measured on the focus, so panning across the ground counts even when the eye barely moves.
14286
+ if (this.lastOrderFocus && !EntityGatherer.boundsChanged(this.lastOrderFocus, focus) &&
14287
+ Cartesian3.distance(this.lastOrderFocus.origin, focus.origin) < EntityGatherer.REORDER_MOVE_METRES) {
14288
+ return;
14289
+ }
14290
+ this.lastOrderFocus = focus;
14291
+ const priority = this.priorityCount > 0 ? this.eIdQueue.slice(0, this.priorityCount) : [];
14292
+ const rest = this.priorityCount > 0 ? this.eIdQueue.slice(this.priorityCount) : this.eIdQueue;
14293
+ if (!rest.length) {
14294
+ return;
14295
+ }
14296
+ this.eIdQueue = priority.concat(this.bandIds(rest, focus));
14297
+ }
14298
+ /*
14299
+ * Returns whether the visible rectangle differs enough to change what counts as on screen.
14300
+ */
14301
+ static boundsChanged(a, b) {
14302
+ if (!a.bounds || !b.bounds) {
14303
+ return Boolean(a.bounds) !== Boolean(b.bounds);
14304
+ }
14305
+ return (a.bounds.north !== b.bounds.north ||
14306
+ a.bounds.south !== b.bounds.south ||
14307
+ a.bounds.east !== b.bounds.east ||
14308
+ a.bounds.west !== b.bounds.west);
14309
+ }
14310
+ /*
14311
+ * Returns the IDs reordered nearest band first, or unchanged when there is nothing to order by.
14312
+ */
14313
+ bandIds(entityIds, focus) {
14314
+ const from = focus || HistoricFetchOrder.FocusFrom({ viewer: this.viewer, monitor: this.monitor });
14315
+ if (!this.positionOf || !from || !entityIds.length) {
14316
+ return entityIds;
14317
+ }
14318
+ const banded = HistoricFetchOrder.ByCameraDistanceOf({
14319
+ focus: from,
14320
+ items: entityIds,
14321
+ posOf: (entityId) => {
14322
+ try {
14323
+ return this.positionOf(entityId);
14324
+ }
14325
+ catch {
14326
+ return null;
14327
+ }
14328
+ }
14329
+ });
14330
+ const ordered = [];
14331
+ for (const band of banded) {
14332
+ for (const entityId of band) {
14333
+ ordered.push(entityId);
14334
+ }
14335
+ }
14336
+ return ordered;
14144
14337
  }
14145
14338
  Queue(entityIds, topOfQueue = false) {
14146
14339
  if (this.disposed || !(entityIds === null || entityIds === void 0 ? void 0 : entityIds.length)) {
@@ -14154,8 +14347,12 @@ class EntityGatherer {
14154
14347
  // If we want it to be at the top of the queue, remove it from the queue first.
14155
14348
  if (index !== -1) {
14156
14349
  this.eIdQueue.splice(index, 1);
14350
+ if (index < this.priorityCount) {
14351
+ this.priorityCount -= 1;
14352
+ }
14157
14353
  }
14158
14354
  this.eIdQueue.unshift(eId);
14355
+ this.priorityCount += 1;
14159
14356
  changes += 1;
14160
14357
  }
14161
14358
  else if (index === -1) {
@@ -14306,7 +14503,9 @@ class EntityGatherer {
14306
14503
  if (this.historic && this.lastDateTime !== rTime) {
14307
14504
  break;
14308
14505
  }
14506
+ this.orderQueueByCamera();
14309
14507
  const batch = this.eIdQueue.splice(0, QUEUE_BATCH_SIZE);
14508
+ this.priorityCount = Math.max(0, this.priorityCount - batch.length);
14310
14509
  const { entities } = await Entity$1.GetListByIds({
14311
14510
  entityIds: batch,
14312
14511
  historicPoint: this.historic ? rTime : null,
@@ -14339,6 +14538,9 @@ class EntityGatherer {
14339
14538
  allEIds = allEIds.filter((eId) => requestedIds.indexOf(eId) === -1);
14340
14539
  }
14341
14540
  if (allEIds.length) {
14541
+ // A historic tick re-reads the whole set, so the near ones are worth resolving
14542
+ // first here even more than in the queue above.
14543
+ allEIds = this.bandIds(allEIds);
14342
14544
  while (allEIds.length && !this.disposed && !this.eIdQueue.length) {
14343
14545
  if (this.historic && this.lastDateTime !== rTime) {
14344
14546
  break;
@@ -14372,6 +14574,11 @@ class EntityGatherer {
14372
14574
  })();
14373
14575
  }
14374
14576
  }
14577
+ // Below this the whole queue clears in one or two requests, so ordering it buys nothing.
14578
+ EntityGatherer.MIN_ORDER_QUEUE = 500;
14579
+ // How far the point being looked at has to move before the queue is worth re-banding, in metres.
14580
+ // Under the nearest band's width, so a move that could change what is nearest still triggers one.
14581
+ EntityGatherer.REORDER_MOVE_METRES = 250;
14375
14582
 
14376
14583
  // ND-1641. BOOKMARKS - (DEMO) View does not match bookmark.
14377
14584
  // We have some evil hard-coded style mappings that need to be fixed.
@@ -14495,7 +14702,8 @@ class TilesetStyler {
14495
14702
  }
14496
14703
  Init(params) {
14497
14704
  var _a, _b;
14498
- let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic, applyStyles } = params;
14705
+ let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic, applyStyles, monitor } = params;
14706
+ this.monitor = monitor;
14499
14707
  this.viewer = viewer;
14500
14708
  this.api = api;
14501
14709
  this.cTileset = cTileset;
@@ -14551,6 +14759,14 @@ class TilesetStyler {
14551
14759
  }
14552
14760
  }
14553
14761
  });
14762
+ // Only Entities Sets bake coordinates into their features, so for any other source this
14763
+ // resolves nothing and the gather stays in insertion order.
14764
+ this.entityGatherer.SetViewMonitor(this.monitor);
14765
+ this.entityGatherer.SetPositionSource((entityId) => {
14766
+ var _a;
14767
+ const visual = (_a = this.getEntityRego(entityId)) === null || _a === void 0 ? void 0 : _a.visual;
14768
+ return (visual === null || visual === void 0 ? void 0 : visual._bakedPos) || null;
14769
+ });
14554
14770
  this.loaded = true;
14555
14771
  this.loadStyles();
14556
14772
  const feed = (_b = this.api) === null || _b === void 0 ? void 0 : _b.RecordChangeFeed;
@@ -19774,7 +19990,8 @@ var TilesetEntitiesRenderManager;
19774
19990
  // Saves having to do a case-insensitive lookup every time.
19775
19991
  this.featurePropCache = new Map();
19776
19992
  this.featurePropsChecked = 0;
19777
- const { viewer, register: visualsManager, getters: apiGetter, item, initQueue } = params;
19993
+ const { viewer, register: visualsManager, getters: apiGetter, item, initQueue, monitor } = params;
19994
+ this.monitor = monitor;
19778
19995
  this.viewer = viewer;
19779
19996
  this.getters = apiGetter;
19780
19997
  this.item = item;
@@ -19883,7 +20100,8 @@ var TilesetEntitiesRenderManager;
19883
20100
  menuItemId: this.item.id,
19884
20101
  register: this.visualsManager,
19885
20102
  historic: Manager.IsHistoric(this.item),
19886
- applyStyles: Boolean(this.item.ApplyStyles)
20103
+ applyStyles: Boolean(this.item.ApplyStyles),
20104
+ monitor: this.monitor
19887
20105
  });
19888
20106
  }
19889
20107
  this.onCTilesetLoad();
@@ -20043,6 +20261,16 @@ var TilesetEntitiesRenderManager;
20043
20261
  else if (lowered === "internalid") {
20044
20262
  this.featurePropCache.set("internalId", prop);
20045
20263
  }
20264
+ else if (lowered === "latitude") {
20265
+ this.featurePropCache.set("latitude", prop);
20266
+ }
20267
+ else if (lowered === "longitude") {
20268
+ this.featurePropCache.set("longitude", prop);
20269
+ }
20270
+ // b3dm names the baked height TerrainHeight, the glTF metadata table calls it Altitude.
20271
+ else if (lowered === "terrainheight" || lowered === "altitude") {
20272
+ this.featurePropCache.set("height", prop);
20273
+ }
20046
20274
  }
20047
20275
  }
20048
20276
  Dispose() {
@@ -20197,7 +20425,8 @@ var TilesetEntitiesRenderManager;
20197
20425
  menuItemId: this.item.id,
20198
20426
  register: this.visualsManager,
20199
20427
  historic: Manager.IsHistoric(this.item),
20200
- applyStyles: Boolean(this.item.ApplyStyles)
20428
+ applyStyles: Boolean(this.item.ApplyStyles),
20429
+ monitor: this.monitor
20201
20430
  });
20202
20431
  this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
20203
20432
  }
@@ -20240,6 +20469,45 @@ var TilesetEntitiesRenderManager;
20240
20469
  }
20241
20470
  }, 10);
20242
20471
  }
20472
+ /*
20473
+ * Reads a numeric feature property, returning null for anything that is not one.
20474
+ */
20475
+ static readNumber(feature, prop) {
20476
+ const raw = feature.getProperty(prop);
20477
+ // Coercing straight to a number would turn null, undefined and "" into a valid zero,
20478
+ // which for a coordinate is a real place off the coast of Africa.
20479
+ if (raw == null || raw === "" || typeof raw === "boolean") {
20480
+ return null;
20481
+ }
20482
+ const value = +raw;
20483
+ return isFinite(value) ? value : null;
20484
+ }
20485
+ /*
20486
+ * Returns the world position an Entities Set bakes into each feature, or null when it has none.
20487
+ */
20488
+ readBakedPos(feature) {
20489
+ const latProp = this.featurePropCache.get("latitude");
20490
+ const lonProp = this.featurePropCache.get("longitude");
20491
+ if (!latProp || !lonProp) {
20492
+ return null;
20493
+ }
20494
+ try {
20495
+ const lat = Manager.readNumber(feature, latProp);
20496
+ const lon = Manager.readNumber(feature, lonProp);
20497
+ // Degrees, per the batch table the generator writes. The range check doubles as a
20498
+ // guard against a source that bakes radians, which would otherwise read as valid.
20499
+ if (lat == null || lon == null || Math.abs(lat) > 90 || Math.abs(lon) > 180) {
20500
+ return null;
20501
+ }
20502
+ // A height that will not read is worth losing, the position it belongs to is not.
20503
+ const heightProp = this.featurePropCache.get("height");
20504
+ const height = heightProp ? Manager.readNumber(feature, heightProp) : 0;
20505
+ return Cartesian3.fromDegrees(lon, lat, height !== null && height !== void 0 ? height : 0);
20506
+ }
20507
+ catch {
20508
+ return null;
20509
+ }
20510
+ }
20243
20511
  mapTilesetFeature(feature) {
20244
20512
  var _a, _b, _c;
20245
20513
  this.evaluateFeatureProps(feature);
@@ -20320,6 +20588,12 @@ var TilesetEntitiesRenderManager;
20320
20588
  rego.internalId = parsed;
20321
20589
  }
20322
20590
  }
20591
+ // Stamped on the feature the properties came from, so a sibling keeps its own position
20592
+ // and nothing has to be cleaned up when the tile unloads.
20593
+ const bakedPos = this.readBakedPos(feature);
20594
+ if (bakedPos) {
20595
+ feature._bakedPos = bakedPos;
20596
+ }
20323
20597
  this.loadedCesiumEntities[rego.entityId] = rego;
20324
20598
  this.visualsManager.AddRego({
20325
20599
  rego,
@@ -24205,6 +24479,7 @@ var MenuItemManager;
24205
24479
  viewer: this.viewer,
24206
24480
  register: this.visualsRegister,
24207
24481
  getters: this.getters,
24482
+ monitor: this.sharedMonitor,
24208
24483
  item: params.item,
24209
24484
  initQueue: this.tilesetInitQueue
24210
24485
  });
@@ -40999,9 +41274,11 @@ var TextureFrameSeriesAnimator;
40999
41274
  return;
41000
41275
  }
41001
41276
  let next = -1;
41277
+ let isTarget = false;
41002
41278
  if (this.pendingIndex !== -1) {
41003
41279
  next = this.pendingIndex;
41004
41280
  this.pendingIndex = -1;
41281
+ isTarget = true;
41005
41282
  }
41006
41283
  else {
41007
41284
  next = this.nearestUncachedFrame();
@@ -41010,7 +41287,7 @@ var TextureFrameSeriesAnimator;
41010
41287
  return;
41011
41288
  }
41012
41289
  this.inFlightIndex = next;
41013
- this.fetchAndTint(next)
41290
+ this.fetchAndTint(next, isTarget)
41014
41291
  .catch(() => {
41015
41292
  // Eating it.
41016
41293
  })
@@ -41021,7 +41298,7 @@ var TextureFrameSeriesAnimator;
41021
41298
  }
41022
41299
  });
41023
41300
  }
41024
- async fetchAndTint(idx) {
41301
+ async fetchAndTint(idx, isTarget = true) {
41025
41302
  const tiles = this.tiles;
41026
41303
  // Every tile's frame for one timestep sits contiguously, because the generator writes
41027
41304
  // frame major. So a tiled frame is still ONE range request, not one per tile.
@@ -41053,10 +41330,25 @@ var TextureFrameSeriesAnimator;
41053
41330
  this.frameCache[idx] = decoded.pixels;
41054
41331
  this.frameDims[idx] = { width: decoded.width, height: decoded.height };
41055
41332
  this.valueCache[idx] = decoded.valuePixels || null;
41056
- if (idx === this.currentFrameIndex) {
41333
+ if (this.shouldPresent(idx, isTarget)) {
41057
41334
  this.beginCrossfadeTo(idx);
41058
41335
  }
41059
41336
  }
41337
+ /*
41338
+ * Whether a frame that has just decoded is worth putting on screen.
41339
+ */
41340
+ shouldPresent(idx, isTarget) {
41341
+ if (idx === this.currentFrameIndex) {
41342
+ return true;
41343
+ }
41344
+ // A prefetch is for a frame nobody asked to see, so it stays in the cache.
41345
+ if (!isTarget) {
41346
+ return false;
41347
+ }
41348
+ // Dragging the timeline retargets faster than a frame can be fetched, so the frame that
41349
+ // lands is rarely the one now under the playhead.
41350
+ return !this.frameCache[this.currentFrameIndex];
41351
+ }
41060
41352
  /*
41061
41353
  * Draws every tile of one frame into a single raster covering the drape extent.
41062
41354
  */
@@ -45619,7 +45911,7 @@ var StyleUtils;
45619
45911
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
45620
45912
  })(StyleUtils || (StyleUtils = {}));
45621
45913
 
45622
- const VERSION = "7.2.4";
45914
+ const VERSION = "7.2.5";
45623
45915
  /**
45624
45916
  * Updates the environment instance used by bruce-cesium to one specified.
45625
45917
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.