bruce-cesium 7.4.3 → 7.4.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.
@@ -1,6 +1,6 @@
1
1
  import * as Cesium from 'cesium';
2
- import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, ConstantPositionProperty, HeightReference, DistanceDisplayCondition, ColorBlendMode, HeadingPitchRoll, Math as Math$1, Transforms, ShadowMode, Cartesian3, ClassificationType, Model, JulianDate, Quaternion, Matrix4, ShaderSource, Ellipsoid, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, ArcType, CornerType, HorizontalOrigin, VerticalOrigin, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneTransforms, FeatureDetection, NearFarScalar, Matrix3, KmlDataSource, SceneMode, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, OrthographicFrustum, EasingFunction, Intersect, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, GeometryInstance, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
- import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1, EntityLod, EntityTag, EntityType, ObjectUtils, Style, Carto, Geometry as Geometry$1, Api, Bounds, ClientFileValueMap, Color as Color$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, DataLab, BruceEvent, EntityCoords, AccountConcept, DelayQueue, BruceApi, EntityRelation, RecordChangeFeed, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, UrlUtils, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, JulianDate, Quaternion, HeadingPitchRoll, HeightReference, ConstantPositionProperty, DistanceDisplayCondition, ColorBlendMode, ShadowMode, ClassificationType, Model, HorizontalOrigin, VerticalOrigin, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, ArcType, CornerType, Rectangle, SceneTransforms, FeatureDetection, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Ion, IonResource, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Cesium3DTileset, OrthographicFrustum, EasingFunction, Intersect, CustomDataSource, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, GeometryInstance, PolygonPipeline, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
+ import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1, EntityLod, EntityTag, EntityType, ObjectUtils, Style, Api, Bounds, ClientFileValueMap, Color as Color$1, Geometry as Geometry$1, Carto, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, DataLab, DelayQueue, AccountConcept, RecordChangeFeed, BruceApi, EntityRelation, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, EntityRelationType, EntityHistoricData } from 'bruce-models';
4
4
 
5
5
  /**
6
6
  * Ensures a number is returned from a given value.
@@ -5403,8 +5403,16 @@ var EntityLabel;
5403
5403
  // so a row of labels reads as one size rather than a set of differently shaped pills.
5404
5404
  const ICON_BACKGROUND_UNIFORM_TOLERANCE = 0.3;
5405
5405
  const TEXT_MAX_WIDTH = 200;
5406
+ // Distinct icons a cluster shows, the representative's included.
5407
+ const ICON_STACK_LIMIT = 3;
5408
+ // Size of every icon in a cluster's arrangement, relative to the representative's own.
5409
+ const ICON_STACK_SCALE = 0.6;
5406
5410
  // How far an angled leader runs sideways before the label starts.
5407
5411
  const ANGLED_LEADER_RUN = 28;
5412
+ // Clearance between a point or billboard glyph and the ring drawn around it.
5413
+ const ICON_RING_PADDING = 4;
5414
+ // Small points still get a ring as large as the default near ring.
5415
+ const ICON_RING_MIN = 10;
5408
5416
  // One per tinted icon, since an SVG mask is addressed by id across the whole document.
5409
5417
  let tintCounter = 0;
5410
5418
  // Key where the label instance is stored within the cesium entity.
@@ -5586,6 +5594,28 @@ var EntityLabel;
5586
5594
  * Prefer declared semantics; only infers from recognisable glyphs and filename tokens, never image pixels.
5587
5595
  */
5588
5596
  EntityLabel.IconPriority = GetClusterIconPriority;
5597
+ function iconLook(options) {
5598
+ return {
5599
+ icon: options.icon || undefined,
5600
+ iconUrl: options.iconUrl || undefined,
5601
+ iconClass: typeof options.iconClass === "string" && options.iconClass ? options.iconClass : undefined,
5602
+ iconColor: typeof options.iconColor === "string" && options.iconColor ? options.iconColor : undefined,
5603
+ iconBackground: options.iconBackground || undefined
5604
+ };
5605
+ }
5606
+ /*
5607
+ * Everything that makes two labels look alike, so labels from different groups only merge when they would read the same.
5608
+ */
5609
+ function clusterLook(options) {
5610
+ var _a;
5611
+ const presentation = options.presentation || "text";
5612
+ return JSON.stringify([
5613
+ presentation,
5614
+ (_a = options.leader) !== null && _a !== void 0 ? _a : null,
5615
+ options.leaderStyle || "default", options.leaderColor || "",
5616
+ presentation === "text" ? null : iconLook(options)
5617
+ ]);
5618
+ }
5589
5619
  /*
5590
5620
  * Resolves clustering and stable text placement before depth fading; highlighted labels receive priority.
5591
5621
  */
@@ -5607,7 +5637,8 @@ var EntityLabel;
5607
5637
  if (!((_b = (_a = frame.options) === null || _a === void 0 ? void 0 : _a.cluster) === null || _b === void 0 ? void 0 : _b.enabled) || frame.priority > 0) {
5608
5638
  continue;
5609
5639
  }
5610
- const key = JSON.stringify([frame.group || "", frame.options.presentation || "text"]);
5640
+ // Groups meet inside a presentation; which of them may merge is decided pairwise below.
5641
+ const key = frame.options.presentation || "text";
5611
5642
  if (!groups.has(key)) {
5612
5643
  groups.set(key, []);
5613
5644
  }
@@ -5619,32 +5650,54 @@ var EntityLabel;
5619
5650
  frame.options.cluster.pixelRange == null ? { ...frame, x: frame.anchorX, y: frame.anchorY, width: 1, height: 1 } :
5620
5651
  frame.clusterBounds ? { ...frame, ...frame.clusterBounds } : frame);
5621
5652
  const index = new ScreenIndex();
5622
- for (const frame of candidates) {
5653
+ const rank = new Map();
5654
+ const looks = new Map();
5655
+ candidates.forEach((frame, i) => {
5623
5656
  index.Add(frame);
5624
- }
5657
+ rank.set(frame.id, i);
5658
+ looks.set(frame.id, clusterLook(frame.options));
5659
+ });
5625
5660
  const clusters = ClusterItems({
5626
5661
  items: candidates,
5627
5662
  id: frame => frame.id,
5628
5663
  minSize: group[0].options.cluster.minSize,
5629
5664
  neighbours: frame => {
5630
5665
  var _a;
5631
- return (index.Query(frame, (_a = frame.options.cluster.pixelRange) !== null && _a !== void 0 ? _a : (frame.options.presentation === "icon" ? 24 : 8)));
5666
+ const nearby = index.Query(frame, (_a = frame.options.cluster.pixelRange) !== null && _a !== void 0 ? _a : (frame.options.presentation === "icon" ? 24 : 8)).sort((a, b) => rank.get(a.id) - rank.get(b.id));
5667
+ return compatibleMembers(frame, nearby, other => other.group || "", other => looks.get(other.id));
5632
5668
  }
5633
5669
  });
5634
5670
  for (const cluster of clusters) {
5635
5671
  let chosen = cluster.representative;
5636
- if ((chosen.options.presentation || "text") !== "text" && chosen.options.cluster.representative !== "nearest") {
5637
- let priority = EntityLabel.IconPriority(chosen.options);
5638
- for (const member of cluster.members) {
5639
- const candidate = EntityLabel.IconPriority(member.options);
5640
- if (candidate > priority) {
5641
- chosen = member;
5642
- priority = candidate;
5643
- }
5644
- }
5672
+ const iconic = (chosen.options.presentation || "text") !== "text";
5673
+ // Members in the order they would win representation; the seed is the nearest.
5674
+ let ordered = [chosen, ...cluster.members.filter(member => member !== chosen)];
5675
+ if (iconic && chosen.options.cluster.representative !== "nearest") {
5676
+ ordered = ordered.map((member, i) => ({ member, i, priority: EntityLabel.IconPriority(member.options) }))
5677
+ .sort((a, b) => b.priority - a.priority || a.i - b.i).map(entry => entry.member);
5678
+ chosen = ordered[0];
5645
5679
  }
5646
5680
  const representative = result.get(chosen.id);
5647
5681
  representative.members = cluster.members.map(frame => frame.id);
5682
+ if (iconic && chosen.options.iconScale !== 0) {
5683
+ const seen = new Set();
5684
+ const icons = [];
5685
+ for (const member of ordered) {
5686
+ const look = iconLook(member.options);
5687
+ const key = JSON.stringify(look);
5688
+ if (seen.has(key)) {
5689
+ continue;
5690
+ }
5691
+ seen.add(key);
5692
+ if (member !== ordered[0])
5693
+ icons.push(look);
5694
+ if (seen.size >= ICON_STACK_LIMIT) {
5695
+ break;
5696
+ }
5697
+ }
5698
+ if (icons.length)
5699
+ representative.icons = icons;
5700
+ }
5648
5701
  representative.entityIds = Array.from(new Set(cluster.members.map(frame => frame.entityId).filter(Boolean)));
5649
5702
  if (chosen.options.presentation !== "icon") {
5650
5703
  const values = new Set();
@@ -5697,6 +5750,34 @@ var EntityLabel;
5697
5750
  return Array.from(result.values());
5698
5751
  }
5699
5752
  EntityLabel.Layout = Layout;
5753
+ /*
5754
+ * The seed plus every neighbour, in order, that shares a group or a look with each member accepted so far.
5755
+ *
5756
+ * Checking against the whole accepted set stops a group with mixed icons bridging two groups that look different.
5757
+ */
5758
+ function compatibleMembers(seed, nearby, group, look) {
5759
+ const accepted = [seed];
5760
+ const byGroup = new Map([[group(seed), 1]]);
5761
+ const byLook = new Map([[look(seed), 1]]);
5762
+ const byBoth = new Map([[JSON.stringify([group(seed), look(seed)]), 1]]);
5763
+ for (const item of nearby) {
5764
+ if (item === seed) {
5765
+ continue;
5766
+ }
5767
+ const itemGroup = group(item), itemLook = look(item), both = JSON.stringify([itemGroup, itemLook]);
5768
+ // Accepted members in another group with another look are the ones this item may not sit beside.
5769
+ const conflicting = accepted.length - (byGroup.get(itemGroup) || 0) - (byLook.get(itemLook) || 0) + (byBoth.get(both) || 0);
5770
+ if (conflicting > 0) {
5771
+ continue;
5772
+ }
5773
+ accepted.push(item);
5774
+ byGroup.set(itemGroup, (byGroup.get(itemGroup) || 0) + 1);
5775
+ byLook.set(itemLook, (byLook.get(itemLook) || 0) + 1);
5776
+ byBoth.set(both, (byBoth.get(both) || 0) + 1);
5777
+ }
5778
+ return accepted;
5779
+ }
5780
+ EntityLabel.compatibleMembers = compatibleMembers;
5700
5781
  const layers = new WeakMap();
5701
5782
  const containers = new WeakMap();
5702
5783
  /*
@@ -6094,7 +6175,7 @@ var EntityLabel;
6094
6175
  * @param starterPos3d optional position to create label at. Ignored for billboard labels.
6095
6176
  */
6096
6177
  async createLabel(starterPos3d) {
6097
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
6178
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
6098
6179
  this.removeLabel();
6099
6180
  let pos3d = starterPos3d ? starterPos3d.clone() : null;
6100
6181
  const counter = ++this.counter;
@@ -6416,125 +6497,28 @@ var EntityLabel;
6416
6497
  let iconElement;
6417
6498
  let iconContainer;
6418
6499
  let fitIconBackground;
6500
+ let iconStack;
6501
+ let stackedFits = [];
6502
+ let displayedStack = "[]";
6419
6503
  content.className = "entity-label-content";
6420
6504
  content.style.cssText = "display:flex;align-items:center;gap:6px;";
6421
6505
  if (presentation === "icon-text-above") {
6422
6506
  content.style.flexDirection = "column-reverse";
6423
6507
  }
6424
6508
  if (presentation !== "text") {
6425
- const icon = document.createElement("span");
6426
- iconElement = icon;
6427
- icon.className = "entity-label-icon";
6428
- icon.style.cssText = `font-size:${(this.options.iconSize || 24) * ((_m = this.options.iconScale) !== null && _m !== void 0 ? _m : 1)}px;line-height:1;`;
6429
- icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
6430
- const iconColor = typeof this.options.iconColor === "string" ? this.options.iconColor : undefined;
6431
- const iconBackground = this.options.iconBackground && this.options.iconScale !== 0;
6432
- if (iconColor) {
6433
- icon.style.color = iconColor;
6434
- }
6435
- if (this.options.iconUrl) {
6436
- const image = document.createElement("img");
6437
- image.draggable = false;
6438
- image.alt = this.options.icon || "";
6439
- const scale = (_o = this.options.iconScale) !== null && _o !== void 0 ? _o : 1;
6440
- const size = this.options.iconSize;
6441
- const svgNs = "http://www.w3.org/2000/svg";
6442
- const tint = iconColor ? document.createElementNS(svgNs, "svg") : null;
6443
- const painted = tint ? document.createElementNS(svgNs, "image") : null;
6444
- const inked = tint ? document.createElementNS(svgNs, "rect") : null;
6445
- if (tint) {
6446
- const maskId = `entity-label-tint-${++tintCounter}`;
6447
- const mask = document.createElementNS(svgNs, "mask");
6448
- mask.setAttribute("id", maskId);
6449
- mask.setAttribute("maskUnits", "userSpaceOnUse");
6450
- painted.setAttribute("href", this.options.iconUrl);
6451
- painted.setAttribute("preserveAspectRatio", "xMidYMid meet");
6452
- mask.appendChild(painted);
6453
- inked.setAttribute("fill", iconColor);
6454
- inked.setAttribute("mask", `url(#${maskId})`);
6455
- tint.append(mask, inked);
6456
- tint.style.display = "block";
6457
- icon.style.display = "inline-flex";
6458
- icon.appendChild(tint);
6459
- }
6460
- const resize = () => {
6461
- if (this.disposed || this.counter !== counter) {
6462
- return;
6463
- }
6464
- const intrinsic = this.options.iconScale != null && size == null;
6465
- const width = (intrinsic ? image.naturalWidth || 24 : size || 24) * scale;
6466
- const height = (intrinsic ? image.naturalHeight || 24 : size || 24) * scale;
6467
- if (tint) {
6468
- tint.setAttribute("viewBox", `0 0 ${width} ${height}`);
6469
- for (const node of [tint, painted, inked]) {
6470
- node.setAttribute("width", String(width));
6471
- node.setAttribute("height", String(height));
6472
- }
6473
- }
6474
- else {
6475
- image.style.width = `${width}px`;
6476
- image.style.height = `${height}px`;
6477
- }
6478
- this.viewer.scene.requestRender();
6479
- };
6480
- image.onload = resize;
6481
- resize();
6482
- image.src = this.options.iconUrl;
6483
- if (!tint) {
6484
- icon.appendChild(image);
6485
- }
6486
- }
6487
- else if (typeof this.options.iconClass === "string" && this.options.iconClass) {
6488
- icon.className = `entity-label-icon ${this.options.iconClass}`;
6489
- }
6490
- else {
6491
- icon.textContent = this.options.icon || "●";
6492
- }
6493
- if (iconBackground) {
6494
- const color = Color.fromCssColorString(iconColor || "white") || Color.WHITE;
6495
- const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
6496
- const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Color()) :
6497
- color.brighten(ICON_BACKGROUND_BLEND, new Color()));
6498
- const size = (this.options.iconSize || 24) * ((_p = this.options.iconScale) !== null && _p !== void 0 ? _p : 1);
6499
- icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
6500
- icon.style.borderRadius = "50%";
6501
- icon.style.padding = `${size / 3}px`;
6502
- icon.style.display = "inline-flex";
6503
- icon.style.alignItems = "center";
6504
- icon.style.justifyContent = "center";
6505
- icon.style.minWidth = `${size}px`;
6506
- icon.style.minHeight = `${size}px`;
6507
- icon.style.boxSizing = "content-box";
6508
- let fitted = false;
6509
- fitIconBackground = () => {
6510
- if (fitted) {
6511
- return;
6512
- }
6513
- const padding = size / 3;
6514
- const width = icon.offsetWidth - padding * 2;
6515
- const height = icon.offsetHeight - padding * 2;
6516
- if (width <= 0 || height <= 0) {
6517
- return;
6518
- }
6519
- fitted = true;
6520
- const longest = Math.max(width, height);
6521
- const difference = (longest - Math.min(width, height)) / longest;
6522
- if (difference > ICON_BACKGROUND_UNIFORM_TOLERANCE) {
6523
- return;
6524
- }
6525
- icon.style.minWidth = `${longest}px`;
6526
- icon.style.minHeight = `${longest}px`;
6527
- this.viewer.scene.requestRender();
6528
- };
6529
- }
6530
- if (this.options.iconScale === 0) {
6531
- icon.style.display = "none";
6532
- }
6509
+ const built = this.buildIcon(this.options, counter);
6510
+ const icon = iconElement = built.icon;
6511
+ fitIconBackground = built.fit;
6533
6512
  makeClickable(icon);
6534
6513
  // Keep badges outside the glyph so font classes and image masks cannot affect them.
6535
6514
  iconContainer = document.createElement("span");
6536
- iconContainer.style.cssText = "position:relative;display:inline-flex;flex:none;";
6537
- iconContainer.appendChild(icon);
6515
+ iconContainer.style.cssText = "position:relative;display:inline-flex;flex:none;align-items:center;";
6516
+ icon.style.position = "relative";
6517
+ iconStack = document.createElement("span");
6518
+ iconStack.className = "entity-label-icon-stack";
6519
+ iconStack.style.cssText = "display:none;gap:2px;align-items:center;justify-items:center;flex:none;";
6520
+ makeClickable(iconStack);
6521
+ iconContainer.append(iconStack, icon);
6538
6522
  content.appendChild(iconContainer);
6539
6523
  }
6540
6524
  if (presentation !== "icon") {
@@ -6546,7 +6530,7 @@ var EntityLabel;
6546
6530
  if (presentation === "text")
6547
6531
  makeClickable(textElement);
6548
6532
  content.appendChild(textElement);
6549
- if ((_q = this.options.cluster) === null || _q === void 0 ? void 0 : _q.enabled) {
6533
+ if ((_m = this.options.cluster) === null || _m === void 0 ? void 0 : _m.enabled) {
6550
6534
  sourceText = document.createElement("span");
6551
6535
  sourceText.className = "entity-label-source-text";
6552
6536
  sourceText.setAttribute("aria-hidden", "true");
@@ -6587,7 +6571,7 @@ var EntityLabel;
6587
6571
  ele.appendChild(countElement);
6588
6572
  }
6589
6573
  ele.className = "entity-label";
6590
- ele.dataset.entityId = ((_r = this.rego) === null || _r === void 0 ? void 0 : _r.entityId) || "";
6574
+ ele.dataset.entityId = ((_o = this.rego) === null || _o === void 0 ? void 0 : _o.entityId) || "";
6591
6575
  ele.dataset.presentation = presentation;
6592
6576
  if (this.groupId) {
6593
6577
  ele.dataset.groupId = this.groupId;
@@ -6703,6 +6687,41 @@ var EntityLabel;
6703
6687
  run, side: (rightwards ? "right" : "left")
6704
6688
  };
6705
6689
  };
6690
+ /*
6691
+ * The on-screen centre and half-size of a point or billboard glyph, relative to its projected position.
6692
+ * Read every frame, since styling and selection can rescale a billboard in place.
6693
+ */
6694
+ const measureIcon = () => {
6695
+ var _a;
6696
+ if (!normalLeader || !(visual instanceof Entity)) {
6697
+ return null;
6698
+ }
6699
+ if (visual.point) {
6700
+ const size = EnsureNumber(getValue(this.viewer, visual.point.pixelSize), 1) +
6701
+ EnsureNumber(getValue(this.viewer, visual.point.outlineWidth), 0) * 2;
6702
+ return size > 0 ? { x: 0, y: 0, radius: size / 2 } : null;
6703
+ }
6704
+ if (!visual.billboard) {
6705
+ return null;
6706
+ }
6707
+ const billboard = visual.billboard;
6708
+ const scale = EnsureNumber(getValue(this.viewer, billboard.scale), 1);
6709
+ // Icon billboards only record their image height; generated circles set both sides.
6710
+ const height = EnsureNumber((_a = getValue(this.viewer, billboard.height)) !== null && _a !== void 0 ? _a : billboard._billboardSize, 0) * scale;
6711
+ const width = EnsureNumber(getValue(this.viewer, billboard.width), height / scale) * scale;
6712
+ if (!(height > 0) || !(width > 0)) {
6713
+ return null;
6714
+ }
6715
+ const vertical = getValue(this.viewer, billboard.verticalOrigin);
6716
+ const horizontal = getValue(this.viewer, billboard.horizontalOrigin);
6717
+ const offset = getValue(this.viewer, billboard.pixelOffset);
6718
+ return {
6719
+ x: ((offset === null || offset === void 0 ? void 0 : offset.x) || 0) + (horizontal === HorizontalOrigin.LEFT ? width / 2 : horizontal === HorizontalOrigin.RIGHT ? -width / 2 : 0),
6720
+ y: ((offset === null || offset === void 0 ? void 0 : offset.y) || 0) + (vertical === VerticalOrigin.BOTTOM || vertical === VerticalOrigin.BASELINE ? -height / 2 :
6721
+ vertical === VerticalOrigin.TOP ? height / 2 : 0),
6722
+ radius: Math.max(width, height) / 2
6723
+ };
6724
+ };
6706
6725
  let cursorMotion = this.cursorMotion;
6707
6726
  const project = () => {
6708
6727
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
@@ -6730,8 +6749,10 @@ var EntityLabel;
6730
6749
  const relative = Cartesian3.subtract(this._pos3d, cameraPosition, new Cartesian3());
6731
6750
  const depth = Cartesian3.dot(relative, camera.directionWC || camera.direction);
6732
6751
  const distance = Cartesian3.magnitude(relative);
6752
+ // If we're labelling from a temp label (mouse hover) don't drive opacity by cam distance.
6753
+ const hoverOnly = this.Transient && !this.Persistent;
6733
6754
  if (!Number.isFinite(distance) || depth <= 0 || distance < ((_c = this.options.near) !== null && _c !== void 0 ? _c : 0) ||
6734
- distance > ((_d = this.options.far) !== null && _d !== void 0 ? _d : Infinity) || (!this.prePositioned && distance >= MAX_DISTANCE)) {
6755
+ distance > ((_d = this.options.far) !== null && _d !== void 0 ? _d : Infinity) || (!this.prePositioned && !hoverOnly && distance >= MAX_DISTANCE)) {
6735
6756
  return null;
6736
6757
  }
6737
6758
  const transforms = SceneTransforms;
@@ -6769,9 +6790,13 @@ var EntityLabel;
6769
6790
  this.cursorMotion = cursorMotion;
6770
6791
  const cursorAmount = cursorMotion.amount;
6771
6792
  leader = normalLeader || cursorAmount > 0;
6772
- let x = ((_l = transientPivot === null || transientPivot === void 0 ? void 0 : transientPivot.x) !== null && _l !== void 0 ? _l : anchor.x) + pixelOffset.x;
6773
- let y = ((_m = transientPivot === null || transientPivot === void 0 ? void 0 : transientPivot.y) !== null && _m !== void 0 ? _m : anchor.y) + pixelOffset.y;
6774
- if (cursorAmount > 0) {
6793
+ // An icon keeps its callout centred on the glyph rather than following the cursor across it.
6794
+ const icon = measureIcon();
6795
+ const iconRing = icon ? Math.max(ICON_RING_MIN, icon.radius + ICON_RING_PADDING) : 0;
6796
+ const pivot = icon ? undefined : transientPivot;
6797
+ let x = ((_l = pivot === null || pivot === void 0 ? void 0 : pivot.x) !== null && _l !== void 0 ? _l : anchor.x) + (icon || pixelOffset).x;
6798
+ let y = ((_m = pivot === null || pivot === void 0 ? void 0 : pivot.y) !== null && _m !== void 0 ? _m : anchor.y) + (icon || pixelOffset).y;
6799
+ if (cursorAmount > 0 && !icon) {
6775
6800
  // A leader-less label starts centred on its anchor; preserve that box as it lifts away.
6776
6801
  if (!normalLeader) {
6777
6802
  x -= ele.offsetWidth / layer.coordinates.scaleX / 2;
@@ -6809,9 +6834,11 @@ var EntityLabel;
6809
6834
  if (cursorAmount > 0) {
6810
6835
  leaderLength += (96 - leaderLength) * cursorAmount;
6811
6836
  }
6837
+ // Measured from the ring, so the visible stem is the same length whatever the glyph's size.
6838
+ leaderLength += iconRing;
6812
6839
  y = anchorY - leaderLength;
6813
6840
  }
6814
- else if (hover && !reused) {
6841
+ else if (hover && !reused && !icon) {
6815
6842
  x = hover.x + pixelOffset.x;
6816
6843
  y = hover.y + pixelOffset.y - 24;
6817
6844
  }
@@ -6854,7 +6881,7 @@ var EntityLabel;
6854
6881
  return null;
6855
6882
  }
6856
6883
  const state = (_p = this.visualRegister) === null || _p === void 0 ? void 0 : _p.GetState({ entityId: (_q = this.rego) === null || _q === void 0 ? void 0 : _q.entityId, menuItemId: (_r = this.rego) === null || _r === void 0 ? void 0 : _r.menuItemId });
6857
- const opacity = this.prePositioned || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
6884
+ const opacity = this.prePositioned || hoverOnly || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
6858
6885
  return {
6859
6886
  canFlip: leader && presentation !== "icon", hasLeader: leader,
6860
6887
  textWrapped: ((sourceNaturalText === null || sourceNaturalText === void 0 ? void 0 : sourceNaturalText.offsetWidth) || 0) > TEXT_MAX_WIDTH,
@@ -6863,6 +6890,7 @@ var EntityLabel;
6863
6890
  viewportWidth: this.viewer.canvas.clientWidth,
6864
6891
  id: this.id, entityKey: ((_s = this.rego) === null || _s === void 0 ? void 0 : _s.entityId) ? JSON.stringify([this.rego.entityId, this.rego.menuItemId || "", this.Transient && !this.Persistent ? "hover" : ""]) : undefined,
6865
6892
  entityId: (_t = this.rego) === null || _t === void 0 ? void 0 : _t.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, leaderRun, leaderSide, distance,
6893
+ iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius,
6866
6894
  // Priority shows the hovered member individually without dragging its cluster along.
6867
6895
  opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
6868
6896
  };
@@ -6870,7 +6898,7 @@ var EntityLabel;
6870
6898
  let displacement = { x: 0, y: 0 };
6871
6899
  let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
6872
6900
  const apply = (placement) => {
6873
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
6901
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
6874
6902
  const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
6875
6903
  clickEntityIds = show ? placement.entityIds : [];
6876
6904
  ele.style.visibility = show ? "visible" : "hidden";
@@ -6907,6 +6935,29 @@ var EntityLabel;
6907
6935
  this.viewer.scene.requestRender();
6908
6936
  }
6909
6937
  fitIconBackground === null || fitIconBackground === void 0 ? void 0 : fitIconBackground();
6938
+ const stack = JSON.stringify(placement.icons || []);
6939
+ if (iconStack && stack !== displayedStack) {
6940
+ displayedStack = stack;
6941
+ const looks = ((_b = placement.icons) === null || _b === void 0 ? void 0 : _b.length) ? [iconLook(this.options), ...placement.icons] : [];
6942
+ // One small size for all, so no icon in the arrangement reads as more important than another.
6943
+ const size = (this.options.iconSize || 24) * ((_c = this.options.iconScale) !== null && _c !== void 0 ? _c : 1) * ICON_STACK_SCALE;
6944
+ const built = looks.map(look => this.buildIcon({ ...look, iconSize: size }, counter));
6945
+ // Two sit side by side; three make a triangle with the representative on top.
6946
+ iconStack.style.gridTemplateColumns = built.length === 3 ? "auto auto" : `repeat(${built.length}, auto)`;
6947
+ if (built.length === 3) {
6948
+ built[0].icon.style.gridColumn = "1 / span 2";
6949
+ }
6950
+ iconStack.replaceChildren(...built.map(({ icon }) => icon));
6951
+ iconStack.style.display = built.length ? "inline-grid" : "none";
6952
+ // The glyph stays measurable, since cluster membership is taken from its footprint.
6953
+ iconElement.style.visibility = built.length ? "hidden" : "";
6954
+ iconElement.style.position = built.length ? "absolute" : "relative";
6955
+ stackedFits = built.map(({ fit }) => fit).filter(Boolean);
6956
+ // The stack widens the label, which the next frame has to measure.
6957
+ this.viewer.scene.requestRender();
6958
+ }
6959
+ for (const fit of stackedFits)
6960
+ fit();
6910
6961
  if (iconContainer) {
6911
6962
  const badgeWidth = countElement.offsetWidth;
6912
6963
  const iconWidth = iconElement.offsetWidth;
@@ -6922,36 +6973,43 @@ var EntityLabel;
6922
6973
  // Summary and badge changes must fit on their first visible frame, including after resize.
6923
6974
  const ring = this.options.anchorRing;
6924
6975
  const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
6925
- (this.hoverFade.to === 0 ? this.retainedHoverPosition : (_b = this.visualRegister) === null || _b === void 0 ? void 0 : _b.GetLabelHoverPosition((_c = this.rego) === null || _c === void 0 ? void 0 : _c.entityId, (_d = this.rego) === null || _d === void 0 ? void 0 : _d.menuItemId)) : undefined));
6926
- const hovered = pointer && Math.hypot(pointer.x - frame.anchorX, pointer.y - frame.anchorY) <= ((_e = ring === null || ring === void 0 ? void 0 : ring.hoverDistance) !== null && _e !== void 0 ? _e : 24);
6927
- const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((_f = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _f !== void 0 ? _f : 100) ? ((_g = ring === null || ring === void 0 ? void 0 : ring.nearRadius) !== null && _g !== void 0 ? _g : 10) : 0, hovered ? ((_h = ring === null || ring === void 0 ? void 0 : ring.hoverRadius) !== null && _h !== void 0 ? _h : (this.Transient && !this.Persistent ? 15.4 : 22)) : 0));
6976
+ (this.hoverFade.to === 0 ? this.retainedHoverPosition : (_d = this.visualRegister) === null || _d === void 0 ? void 0 : _d.GetLabelHoverPosition((_e = this.rego) === null || _e === void 0 ? void 0 : _e.entityId, (_f = this.rego) === null || _f === void 0 ? void 0 : _f.menuItemId)) : undefined));
6977
+ const iconRing = frame.iconRadius ? Math.max(ICON_RING_MIN, frame.iconRadius + ICON_RING_PADDING) : 0;
6978
+ const hovered = pointer && Math.hypot(pointer.x - frame.anchorX, pointer.y - frame.anchorY) <= Math.max((_g = ring === null || ring === void 0 ? void 0 : ring.hoverDistance) !== null && _g !== void 0 ? _g : 24, iconRing);
6979
+ const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((_h = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _h !== void 0 ? _h : 100) ? ((_j = ring === null || ring === void 0 ? void 0 : ring.nearRadius) !== null && _j !== void 0 ? _j : 10) : 0, hovered ? ((_k = ring === null || ring === void 0 ? void 0 : ring.hoverRadius) !== null && _k !== void 0 ? _k : (this.Transient && !this.Persistent ? 15.4 : 22)) : 0));
6928
6980
  const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
6981
+ // Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
6982
+ const targetOuter = iconRing || targetRadius || 2.5;
6983
+ const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
6929
6984
  const now = Date.now();
6930
- const duration = (_j = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _j !== void 0 ? _j : 200;
6985
+ const duration = (_l = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _l !== void 0 ? _l : 200;
6931
6986
  const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
6932
6987
  let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
6933
6988
  if (!ringAnimation) {
6934
- ringAnimation = { target: 0, start: now, outer: 2.5, inner: 0, fromOuter: 2.5, fromInner: 0 };
6989
+ const outer = iconRing || 2.5;
6990
+ const inner = iconRing;
6991
+ ringAnimation = { target: outer, targetInner: inner, start: now, outer, inner, fromOuter: outer, fromInner: inner };
6935
6992
  }
6936
6993
  const animateRing = () => {
6937
6994
  const progress = duration > 0 ? Math.min(1, Math.max(0, (now - ringAnimation.start) / duration)) : 1;
6938
6995
  const eased = progress * progress * (3 - 2 * progress);
6939
- ringAnimation.outer = ringAnimation.fromOuter + ((ringAnimation.target || 2.5) - ringAnimation.fromOuter) * eased;
6940
- ringAnimation.inner = ringAnimation.fromInner + (Math.max(0, ringAnimation.target - 2) - ringAnimation.fromInner) * eased;
6996
+ ringAnimation.outer = ringAnimation.fromOuter + (ringAnimation.target - ringAnimation.fromOuter) * eased;
6997
+ ringAnimation.inner = ringAnimation.fromInner + (ringAnimation.targetInner - ringAnimation.fromInner) * eased;
6941
6998
  return progress;
6942
6999
  };
6943
7000
  animateRing();
6944
- if (ringAnimation.target !== targetRadius) {
7001
+ if (ringAnimation.target !== targetOuter || ringAnimation.targetInner !== targetInner) {
6945
7002
  ringAnimation = {
6946
7003
  ...ringAnimation,
6947
- target: targetRadius,
7004
+ target: targetOuter,
7005
+ targetInner,
6948
7006
  start: now,
6949
7007
  fromOuter: ringAnimation.outer,
6950
7008
  fromInner: ringAnimation.inner
6951
7009
  };
6952
7010
  }
6953
7011
  // Animate the hole and stem together; no CSS transition may lag behind a moving world anchor.
6954
- if (animateRing() < 1 && (ringAnimation.fromOuter !== (targetRadius || 2.5) || ringAnimation.fromInner !== Math.max(0, targetRadius - 2))) {
7012
+ if (animateRing() < 1 && (ringAnimation.fromOuter !== targetOuter || ringAnimation.fromInner !== targetInner)) {
6955
7013
  this.viewer.scene.requestRender();
6956
7014
  }
6957
7015
  this.ringAnimation = ringAnimation;
@@ -6966,19 +7024,19 @@ var EntityLabel;
6966
7024
  if (leader && angled) {
6967
7025
  // The declutter owns the side; the fit only says which one had the room to begin with.
6968
7026
  const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
6969
- const side = (_l = (_k = placement.textSide) !== null && _k !== void 0 ? _k : frame.leaderSide) !== null && _l !== void 0 ? _l : "right";
7027
+ const side = (_o = (_m = placement.textSide) !== null && _m !== void 0 ? _m : frame.leaderSide) !== null && _o !== void 0 ? _o : "right";
6970
7028
  const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
6971
7029
  position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
6972
7030
  }
6973
7031
  else if (leader && placement.textSide === "left") {
6974
7032
  position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
6975
7033
  }
6976
- const gap = (_o = (_m = this.options.pointerAvoidance) === null || _m === void 0 ? void 0 : _m.gap) !== null && _o !== void 0 ? _o : 12;
7034
+ const gap = (_q = (_p = this.options.pointerAvoidance) === null || _p === void 0 ? void 0 : _p.gap) !== null && _q !== void 0 ? _q : 12;
6977
7035
  const containsPointer = (x, y, margin) => {
6978
7036
  return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
6979
7037
  };
6980
7038
  const passive = !selectOnClick || !this.visualRegister;
6981
- if (passive && ((_p = this.options.pointerAvoidance) === null || _p === void 0 ? void 0 : _p.enabled) !== false && pointer &&
7039
+ if (passive && ((_r = this.options.pointerAvoidance) === null || _r === void 0 ? void 0 : _r.enabled) !== false && pointer &&
6982
7040
  (containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
6983
7041
  if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
6984
7042
  // Choose against the original box and retain the offset until the pointer leaves both boxes.
@@ -6996,7 +7054,7 @@ var EntityLabel;
6996
7054
  else {
6997
7055
  displacement = { x: 0, y: 0 };
6998
7056
  }
6999
- const nudgeDuration = (_r = (_q = this.options.pointerAvoidance) === null || _q === void 0 ? void 0 : _q.transitionDuration) !== null && _r !== void 0 ? _r : 200;
7057
+ const nudgeDuration = (_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.transitionDuration) !== null && _t !== void 0 ? _t : 200;
7000
7058
  const animateNudge = () => {
7001
7059
  const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
7002
7060
  const eased = progress * progress * (3 - 2 * progress);
@@ -7012,7 +7070,7 @@ var EntityLabel;
7012
7070
  fromY: nudge.y,
7013
7071
  targetX: displacement.x,
7014
7072
  targetY: displacement.y,
7015
- start: now + ((_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.delay) !== null && _t !== void 0 ? _t : 180)
7073
+ start: now + ((_v = (_u = this.options.pointerAvoidance) === null || _u === void 0 ? void 0 : _u.delay) !== null && _v !== void 0 ? _v : 180)
7016
7074
  };
7017
7075
  }
7018
7076
  if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
@@ -7022,9 +7080,8 @@ var EntityLabel;
7022
7080
  position.y += nudge.y;
7023
7081
  ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
7024
7082
  ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
7025
- const clickTarget = content.querySelector('[role="button"]');
7026
- if (clickTarget) {
7027
- clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_v = (_u = this.options.text) !== null && _u !== void 0 ? _u : text) !== null && _v !== void 0 ? _v : (_w = this.rego) === null || _w === void 0 ? void 0 : _w.entityId}`);
7083
+ for (const clickTarget of Array.from(content.querySelectorAll('[role="button"]'))) {
7084
+ clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_x = (_w = this.options.text) !== null && _w !== void 0 ? _w : text) !== null && _x !== void 0 ? _x : (_y = this.rego) === null || _y === void 0 ? void 0 : _y.entityId}`);
7028
7085
  }
7029
7086
  if (leader) {
7030
7087
  const nodeRadius = ringAnimation.outer;
@@ -7064,13 +7121,13 @@ var EntityLabel;
7064
7121
  // Keep the stem inside the 126-degree arc, away from antialiased endpoints.
7065
7122
  const arcFraction = 0.35;
7066
7123
  const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
7067
- const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_x = ringAnimation.arcOffset) !== null && _x !== void 0 ? _x : 0);
7124
+ const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_z = ringAnimation.arcOffset) !== null && _z !== void 0 ? _z : 0);
7068
7125
  const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
7069
- const elapsed = Math.min(32, Math.max(0, now - ((_y = ringAnimation.arcTime) !== null && _y !== void 0 ? _y : now)));
7126
+ const elapsed = Math.min(32, Math.max(0, now - ((_0 = ringAnimation.arcTime) !== null && _0 !== void 0 ? _0 : now)));
7070
7127
  const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
7071
7128
  const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
7072
- ringAnimation.arcOffset = approach((_z = ringAnimation.arcOffset) !== null && _z !== void 0 ? _z : 0, targetOffset);
7073
- ringAnimation.arcOpen = approach((_0 = ringAnimation.arcOpen) !== null && _0 !== void 0 ? _0 : 0, targetOpen);
7129
+ ringAnimation.arcOffset = approach((_1 = ringAnimation.arcOffset) !== null && _1 !== void 0 ? _1 : 0, targetOffset);
7130
+ ringAnimation.arcOpen = approach((_2 = ringAnimation.arcOpen) !== null && _2 !== void 0 ? _2 : 0, targetOpen);
7074
7131
  ringAnimation.arcTime = now;
7075
7132
  if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
7076
7133
  this.viewer.scene.requestRender();
@@ -7123,6 +7180,123 @@ var EntityLabel;
7123
7180
  }, 2500);
7124
7181
  });
7125
7182
  }
7183
+ /*
7184
+ * Builds one icon glyph from label options, with its background chip when asked for.
7185
+ *
7186
+ * The returned fit squares the chip off once the glyph has a measurable size.
7187
+ */
7188
+ buildIcon(options, counter) {
7189
+ var _a, _b, _c;
7190
+ let fit;
7191
+ const icon = document.createElement("span");
7192
+ icon.className = "entity-label-icon";
7193
+ icon.style.cssText = `font-size:${(options.iconSize || 24) * ((_a = options.iconScale) !== null && _a !== void 0 ? _a : 1)}px;line-height:1;`;
7194
+ icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
7195
+ const iconColor = typeof options.iconColor === "string" ? options.iconColor : undefined;
7196
+ const iconBackground = options.iconBackground && options.iconScale !== 0;
7197
+ if (iconColor) {
7198
+ icon.style.color = iconColor;
7199
+ }
7200
+ if (options.iconUrl) {
7201
+ const image = document.createElement("img");
7202
+ image.draggable = false;
7203
+ image.alt = options.icon || "";
7204
+ const scale = (_b = options.iconScale) !== null && _b !== void 0 ? _b : 1;
7205
+ const size = options.iconSize;
7206
+ const svgNs = "http://www.w3.org/2000/svg";
7207
+ const tint = iconColor ? document.createElementNS(svgNs, "svg") : null;
7208
+ const painted = tint ? document.createElementNS(svgNs, "image") : null;
7209
+ const inked = tint ? document.createElementNS(svgNs, "rect") : null;
7210
+ if (tint) {
7211
+ const maskId = `entity-label-tint-${++tintCounter}`;
7212
+ const mask = document.createElementNS(svgNs, "mask");
7213
+ mask.setAttribute("id", maskId);
7214
+ mask.setAttribute("maskUnits", "userSpaceOnUse");
7215
+ painted.setAttribute("href", options.iconUrl);
7216
+ painted.setAttribute("preserveAspectRatio", "xMidYMid meet");
7217
+ mask.appendChild(painted);
7218
+ inked.setAttribute("fill", iconColor);
7219
+ inked.setAttribute("mask", `url(#${maskId})`);
7220
+ tint.append(mask, inked);
7221
+ tint.style.display = "block";
7222
+ icon.style.display = "inline-flex";
7223
+ icon.appendChild(tint);
7224
+ }
7225
+ const resize = () => {
7226
+ if (this.disposed || this.counter !== counter) {
7227
+ return;
7228
+ }
7229
+ const intrinsic = options.iconScale != null && size == null;
7230
+ const width = (intrinsic ? image.naturalWidth || 24 : size || 24) * scale;
7231
+ const height = (intrinsic ? image.naturalHeight || 24 : size || 24) * scale;
7232
+ if (tint) {
7233
+ tint.setAttribute("viewBox", `0 0 ${width} ${height}`);
7234
+ for (const node of [tint, painted, inked]) {
7235
+ node.setAttribute("width", String(width));
7236
+ node.setAttribute("height", String(height));
7237
+ }
7238
+ }
7239
+ else {
7240
+ image.style.width = `${width}px`;
7241
+ image.style.height = `${height}px`;
7242
+ }
7243
+ this.viewer.scene.requestRender();
7244
+ };
7245
+ image.onload = resize;
7246
+ resize();
7247
+ image.src = options.iconUrl;
7248
+ if (!tint) {
7249
+ icon.appendChild(image);
7250
+ }
7251
+ }
7252
+ else if (typeof options.iconClass === "string" && options.iconClass) {
7253
+ icon.className = `entity-label-icon ${options.iconClass}`;
7254
+ }
7255
+ else {
7256
+ icon.textContent = options.icon || "●";
7257
+ }
7258
+ if (iconBackground) {
7259
+ const color = Color.fromCssColorString(iconColor || "white") || Color.WHITE;
7260
+ const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
7261
+ const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Color()) :
7262
+ color.brighten(ICON_BACKGROUND_BLEND, new Color()));
7263
+ const size = (options.iconSize || 24) * ((_c = options.iconScale) !== null && _c !== void 0 ? _c : 1);
7264
+ icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
7265
+ icon.style.borderRadius = "50%";
7266
+ icon.style.padding = `${size / 3}px`;
7267
+ icon.style.display = "inline-flex";
7268
+ icon.style.alignItems = "center";
7269
+ icon.style.justifyContent = "center";
7270
+ icon.style.minWidth = `${size}px`;
7271
+ icon.style.minHeight = `${size}px`;
7272
+ icon.style.boxSizing = "content-box";
7273
+ let fitted = false;
7274
+ fit = () => {
7275
+ if (fitted) {
7276
+ return;
7277
+ }
7278
+ const padding = size / 3;
7279
+ const width = icon.offsetWidth - padding * 2;
7280
+ const height = icon.offsetHeight - padding * 2;
7281
+ if (width <= 0 || height <= 0) {
7282
+ return;
7283
+ }
7284
+ fitted = true;
7285
+ const longest = Math.max(width, height);
7286
+ const difference = (longest - Math.min(width, height)) / longest;
7287
+ if (difference > ICON_BACKGROUND_UNIFORM_TOLERANCE) {
7288
+ return;
7289
+ }
7290
+ icon.style.minWidth = `${longest}px`;
7291
+ icon.style.minHeight = `${longest}px`;
7292
+ this.viewer.scene.requestRender();
7293
+ };
7294
+ }
7295
+ if (options.iconScale === 0) {
7296
+ icon.style.display = "none";
7297
+ }
7298
+ return { icon, fit };
7299
+ }
7126
7300
  /**
7127
7301
  * Removes label from the viewer and unsubscribes from events.
7128
7302
  */
@@ -50400,7 +50574,7 @@ var StyleUtils;
50400
50574
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
50401
50575
  })(StyleUtils || (StyleUtils = {}));
50402
50576
 
50403
- const VERSION = "7.4.3";
50577
+ const VERSION = "7.4.5";
50404
50578
  /**
50405
50579
  * Updates the environment instance used by bruce-cesium to one specified.
50406
50580
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.