bruce-cesium 7.4.1 → 7.4.2

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, HeightReference, DistanceDisplayCondition, 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, ColorBlendMode, ShadowMode, ClassificationType, Model, HorizontalOrigin, VerticalOrigin, ConstantPositionProperty, ArcType, CornerType, ImageMaterialProperty, SceneTransforms, PolygonHierarchy, PolylineGraphics, Rectangle, FeatureDetection, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, HeadingPitchRange, Cesium3DTileStyle, Ion, IonResource, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Intersect, OrthographicFrustum, EasingFunction, Cesium3DTileColorBlendMode, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, CustomDataSource, GeometryInstance, 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, Carto, Geometry as Geometry$1, ClientFileValueMap, Color as Color$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, Bounds, BruceEvent, EntityCoords, DataLab, DelayQueue, AccountConcept, BruceApi, EntityRelation, RecordChangeFeed, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, ProjectViewBookmarkGroup, Session, Account, AccountLimits, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, HeightReference, ColorBlendMode, HeadingPitchRoll, Math as Math$1, Transforms, ShadowMode, Cartesian3, ClassificationType, Model, ShaderSource, Ellipsoid, 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, DistanceDisplayCondition, ArcType, CornerType, ConstantPositionProperty, HorizontalOrigin, VerticalOrigin, ImageMaterialProperty, SceneTransforms, PolygonHierarchy, PolylineGraphics, Rectangle, FeatureDetection, KmlDataSource, NearFarScalar, GeoJsonDataSource, Matrix3, SceneMode, Ion, IonResource, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Cesium3DTileset, Intersect, OrthographicFrustum, EasingFunction, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, EllipsoidTerrainProvider, sampleTerrainMostDetailed, defined, PolygonPipeline, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, GeometryInstance, ScreenSpaceEventHandler, ScreenSpaceEventType, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, CustomDataSource, 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, ClientFileValueMap, Color as Color$1, BruceEvent, EntityCoords, MenuItem, Tileset, ProjectView, ProjectViewBookmark, ZoomControl, Bounds, DataLab, DelayQueue, AccountConcept, BruceApi, EntityRelation, RecordChangeFeed, EntitySource, Camera, ProgramKey, ProjectViewLegacyTile, 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.
@@ -5399,7 +5399,14 @@ var EntityLabel;
5399
5399
  (function (EntityLabel) {
5400
5400
  const ICON_BACKGROUND_BLEND = 0.8;
5401
5401
  const ICON_BACKGROUND_ALPHA = 0.45;
5402
+ // Under this much difference between the glyph's width and height the chip is squared off,
5403
+ // so a row of labels reads as one size rather than a set of differently shaped pills.
5404
+ const ICON_BACKGROUND_UNIFORM_TOLERANCE = 0.3;
5402
5405
  const TEXT_MAX_WIDTH = 200;
5406
+ // How far an angled leader runs sideways before the label starts.
5407
+ const ANGLED_LEADER_RUN = 28;
5408
+ // One per tinted icon, since an SVG mask is addressed by id across the whole document.
5409
+ let tintCounter = 0;
5403
5410
  // Key where the label instance is stored within the cesium entity.
5404
5411
  // Eg: rego[ATTACH_KEY] = new EntityLabel(...);
5405
5412
  EntityLabel.ATTACH_KEY = "nextspace-navigator-entity-label";
@@ -5498,7 +5505,7 @@ var EntityLabel;
5498
5505
  this.nextOrder = 0;
5499
5506
  }
5500
5507
  Resolve(frames) {
5501
- var _a, _b;
5508
+ var _a, _b, _c;
5502
5509
  const live = new Set(frames.map(frame => frame.id));
5503
5510
  const moved = (frame) => {
5504
5511
  var _a;
@@ -5525,10 +5532,17 @@ var EntityLabel;
5525
5532
  const placed = new Map();
5526
5533
  const next = new Map();
5527
5534
  const candidate = (frame, side) => {
5528
- var _a;
5529
- return (side === "left" && frame.canFlip ?
5530
- { ...frame, x: Math.max(0, Math.min(frame.anchorX - frame.width, ((_a = frame.viewportWidth) !== null && _a !== void 0 ? _a : Infinity) - frame.width)) } :
5531
- frame);
5535
+ var _a, _b;
5536
+ if (!frame.canFlip) {
5537
+ return frame;
5538
+ }
5539
+ if (frame.leaderRun != null) {
5540
+ const x = side === "left" ? frame.anchorX - frame.leaderRun - frame.width : frame.anchorX + frame.leaderRun;
5541
+ return { ...frame, x: Math.max(0, Math.min(x, ((_a = frame.viewportWidth) !== null && _a !== void 0 ? _a : Infinity) - frame.width)) };
5542
+ }
5543
+ return (side === "left" ?
5544
+ { ...frame, x: Math.max(0, Math.min(frame.anchorX - frame.width, ((_b = frame.viewportWidth) !== null && _b !== void 0 ? _b : Infinity) - frame.width)) }
5545
+ : frame);
5532
5546
  };
5533
5547
  const score = (frame, padding = 0) => {
5534
5548
  const box = {
@@ -5544,7 +5558,7 @@ var EntityLabel;
5544
5558
  };
5545
5559
  for (const frame of ordered) {
5546
5560
  const old = this.previous.get(frame.id);
5547
- let side = frame.canFlip ? (_a = old === null || old === void 0 ? void 0 : old.side) !== null && _a !== void 0 ? _a : "right" : "right";
5561
+ let side = frame.canFlip ? (_b = (_a = old === null || old === void 0 ? void 0 : old.side) !== null && _a !== void 0 ? _a : frame.leaderSide) !== null && _b !== void 0 ? _b : "right" : "right";
5548
5562
  let chosen = candidate(frame, side);
5549
5563
  if (!fixed(frame) && frame.canFlip) {
5550
5564
  const otherSide = side === "left" ? "right" : "left";
@@ -5556,7 +5570,7 @@ var EntityLabel;
5556
5570
  }
5557
5571
  }
5558
5572
  placed.set(frame.id, { frame: chosen, side });
5559
- next.set(frame.id, { frame: fixed(frame) ? old.frame : { ...frame }, side, order: (_b = old === null || old === void 0 ? void 0 : old.order) !== null && _b !== void 0 ? _b : this.nextOrder++ });
5573
+ next.set(frame.id, { frame: fixed(frame) ? old.frame : { ...frame }, side, order: (_c = old === null || old === void 0 ? void 0 : old.order) !== null && _c !== void 0 ? _c : this.nextOrder++ });
5560
5574
  text.Add(chosen);
5561
5575
  const stem = leaderBounds(chosen);
5562
5576
  if (stem) {
@@ -5576,7 +5590,7 @@ var EntityLabel;
5576
5590
  * Resolves clustering and stable text placement before depth fading; highlighted labels receive priority.
5577
5591
  */
5578
5592
  function Layout(input, memory = new PlacementMemory()) {
5579
- var _a, _b, _c, _d, _e, _f, _g, _h;
5593
+ var _a, _b, _c, _d, _e, _f, _g;
5580
5594
  const frames = input.filter(frame => [frame.x, frame.y, frame.width, frame.height, frame.depth].every(Number.isFinite))
5581
5595
  .sort((a, b) => (b.priority || 0) - (a.priority || 0) || a.depth - b.depth || a.id.localeCompare(b.id));
5582
5596
  const result = new Map();
@@ -5642,17 +5656,13 @@ var EntityLabel;
5642
5656
  }
5643
5657
  }
5644
5658
  if (values.size > 1) {
5645
- const description = stableMembers.find(member => { var _a, _b; return member.textWrapped && ((_b = (member.text || ((_a = member.options) === null || _a === void 0 ? void 0 : _a.text))) === null || _b === void 0 ? void 0 : _b.trim()); });
5646
- if (description) {
5647
- representative.text = ((_f = description.text) !== null && _f !== void 0 ? _f : description.options.text).trim();
5648
- representative.summaryMore = true;
5649
- }
5650
- else {
5651
- const rows = Array.from(values).slice(0, 2);
5652
- if (values.size > 2)
5653
- rows.push(`+${values.size - 2} more…`);
5654
- representative.text = rows.join("\n");
5655
- }
5659
+ // A value long enough to wrap fills the box on its own, so only one is listed.
5660
+ const wrapped = stableMembers.some(member => { var _a, _b; return member.textWrapped && ((_b = (member.text || ((_a = member.options) === null || _a === void 0 ? void 0 : _a.text))) === null || _b === void 0 ? void 0 : _b.trim()); });
5661
+ const limit = wrapped ? 1 : 2;
5662
+ const rows = Array.from(values).slice(0, limit);
5663
+ if (values.size > limit)
5664
+ rows.push(`+${values.size - limit} more…`);
5665
+ representative.text = rows.join("\n");
5656
5666
  }
5657
5667
  }
5658
5668
  for (const frame of cluster.members) {
@@ -5676,7 +5686,7 @@ var EntityLabel;
5676
5686
  const box = { ...frame, x: frame.x + .01, y: frame.y + .01, width: Math.max(0, frame.width - .02), height: Math.max(0, frame.height - .02) };
5677
5687
  const overlap = foreground.HasOverlap(box) || foregroundLeaders.HasOverlap(box) || Boolean(stem && foreground.HasOverlap(stem));
5678
5688
  if (overlap) {
5679
- placement.opacity *= Math.max(0, Math.min(1, (_h = (_g = frame.options) === null || _g === void 0 ? void 0 : _g.overlapOpacity) !== null && _h !== void 0 ? _h : 0.25));
5689
+ placement.opacity *= Math.max(0, Math.min(1, (_g = (_f = frame.options) === null || _f === void 0 ? void 0 : _f.overlapOpacity) !== null && _g !== void 0 ? _g : 0.25));
5680
5690
  }
5681
5691
  placement.zIndex = zIndex--;
5682
5692
  foreground.Add(frame);
@@ -5998,6 +6008,8 @@ var EntityLabel;
5998
6008
  this.loadTimeout = null;
5999
6009
  this.loadedName = false;
6000
6010
  this.line = null;
6011
+ // The sideways half of an angled leader; the rise is drawn by line.
6012
+ this.leaderRun = null;
6001
6013
  this.anchor = null;
6002
6014
  // Null = not attempted to load.
6003
6015
  this.additionalTexts = null;
@@ -6394,6 +6406,7 @@ var EntityLabel;
6394
6406
  }
6395
6407
  };
6396
6408
  const normalLeader = (_k = this.options.leader) !== null && _k !== void 0 ? _k : presentation === "text";
6409
+ const angled = this.options.leaderStyle === "angled";
6397
6410
  let leader = normalLeader;
6398
6411
  const content = document.createElement("span");
6399
6412
  const originalText = (_l = this.options.text) !== null && _l !== void 0 ? _l : [text, ...additionalTexts].join("\n");
@@ -6402,6 +6415,7 @@ var EntityLabel;
6402
6415
  let sourceNaturalText;
6403
6416
  let iconElement;
6404
6417
  let iconContainer;
6418
+ let fitIconBackground;
6405
6419
  content.className = "entity-label-content";
6406
6420
  content.style.cssText = "display:flex;align-items:center;gap:6px;";
6407
6421
  if (presentation === "icon-text-above") {
@@ -6424,34 +6438,49 @@ var EntityLabel;
6424
6438
  image.alt = this.options.icon || "";
6425
6439
  const scale = (_o = this.options.iconScale) !== null && _o !== void 0 ? _o : 1;
6426
6440
  const size = this.options.iconSize;
6427
- const mask = iconColor && iconBackground ? document.createElement("span") : icon;
6428
- if (mask !== icon) {
6429
- icon.appendChild(mask);
6430
- }
6431
- const target = iconColor ? mask : image;
6432
- if (iconColor) {
6433
- mask.style.display = "inline-block";
6434
- mask.style.backgroundColor = iconColor;
6435
- for (const prefix of ["", "-webkit-"]) {
6436
- mask.style.setProperty(`${prefix}mask-image`, `url(${JSON.stringify(this.options.iconUrl)})`);
6437
- mask.style.setProperty(`${prefix}mask-size`, "contain");
6438
- mask.style.setProperty(`${prefix}mask-repeat`, "no-repeat");
6439
- mask.style.setProperty(`${prefix}mask-position`, "center");
6440
- }
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);
6441
6459
  }
6442
6460
  const resize = () => {
6443
6461
  if (this.disposed || this.counter !== counter) {
6444
6462
  return;
6445
6463
  }
6446
6464
  const intrinsic = this.options.iconScale != null && size == null;
6447
- target.style.width = `${(intrinsic ? image.naturalWidth || 24 : size || 24) * scale}px`;
6448
- target.style.height = `${(intrinsic ? image.naturalHeight || 24 : size || 24) * scale}px`;
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
+ }
6449
6478
  this.viewer.scene.requestRender();
6450
6479
  };
6451
6480
  image.onload = resize;
6452
6481
  resize();
6453
6482
  image.src = this.options.iconUrl;
6454
- if (!iconColor) {
6483
+ if (!tint) {
6455
6484
  icon.appendChild(image);
6456
6485
  }
6457
6486
  }
@@ -6476,6 +6505,27 @@ var EntityLabel;
6476
6505
  icon.style.minWidth = `${size}px`;
6477
6506
  icon.style.minHeight = `${size}px`;
6478
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
+ };
6479
6529
  }
6480
6530
  if (this.options.iconScale === 0) {
6481
6531
  icon.style.display = "none";
@@ -6514,8 +6564,10 @@ var EntityLabel;
6514
6564
  const countElement = document.createElement("span");
6515
6565
  countElement.className = "entity-label-count";
6516
6566
  countElement.style.cssText = "flex:none;align-self:center;white-space:nowrap;background:#fff;color:#000;text-shadow:none;border-radius:8px;padding:0 3px;display:none;";
6567
+ // Text above the icon would have the badge land in it, so the free corner is the lower one.
6568
+ const badgeBelow = presentation === "icon-text-above";
6517
6569
  if (iconContainer) {
6518
- countElement.style.cssText += "position:absolute;left:100%;top:0;transform:translate(-50%,-50%);";
6570
+ countElement.style.cssText += `position:absolute;left:100%;top:${badgeBelow ? "100%" : "0"};transform:translate(-50%,-50%);`;
6519
6571
  iconContainer.appendChild(countElement);
6520
6572
  }
6521
6573
  if (!layers.has(this.viewer)) {
@@ -6524,6 +6576,7 @@ var EntityLabel;
6524
6576
  const layer = layers.get(this.viewer);
6525
6577
  let ele = null;
6526
6578
  let lineEle = null;
6579
+ let runEle = null;
6527
6580
  if (this.label) {
6528
6581
  ele = this.label;
6529
6582
  }
@@ -6560,6 +6613,7 @@ var EntityLabel;
6560
6613
  }
6561
6614
  if (this.line) {
6562
6615
  lineEle = this.line;
6616
+ runEle = this.leaderRun;
6563
6617
  }
6564
6618
  else {
6565
6619
  lineEle = document.createElement("div");
@@ -6603,6 +6657,14 @@ var EntityLabel;
6603
6657
  layer.element.appendChild(lineEleNode);
6604
6658
  layer.element.appendChild(lineEle);
6605
6659
  }
6660
+ // Built here rather than beside the stem, since the style can change on a label that is reused.
6661
+ if (angled && !runEle) {
6662
+ runEle = lineEle.cloneNode(false);
6663
+ runEle.className = "entity-label-leader-run";
6664
+ runEle.style.display = "none";
6665
+ this.leaderRun = runEle;
6666
+ layer.element.appendChild(runEle);
6667
+ }
6606
6668
  let MAX_DISTANCE = Math.max(15000, sizeInM * 35);
6607
6669
  if (visual && visual instanceof Entity) {
6608
6670
  // Consistent sized things.
@@ -6622,6 +6684,25 @@ var EntityLabel;
6622
6684
  y: upwards ? endY - height : endY, endY
6623
6685
  };
6624
6686
  };
6687
+ /*
6688
+ * An angled leader parks the label beside the anchor instead of over it,
6689
+ * so the stem rises to the label's middle and then runs out to whichever side has the room.
6690
+ */
6691
+ const fitAngledLeader = (anchorX, anchorY, desired, width, height, radius = 0) => {
6692
+ const canvas = this.viewer.canvas;
6693
+ const above = Math.max(0, anchorY - height / 2);
6694
+ const below = Math.max(0, canvas.clientHeight - anchorY - height / 2);
6695
+ const upwards = above >= Math.max(radius ? radius + 4 : 0, Math.min(8, desired)) || above >= below;
6696
+ const length = Math.min(desired, upwards ? above : below);
6697
+ const endY = anchorY + (upwards ? -length : length);
6698
+ const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
6699
+ const rightwards = anchorX + run + width <= canvas.clientWidth || anchorX - run - width < 0;
6700
+ return {
6701
+ x: Math.max(0, Math.min(rightwards ? anchorX + run : anchorX - run - width, canvas.clientWidth - width)),
6702
+ y: Math.max(0, Math.min(endY - height / 2, canvas.clientHeight - height)), endY,
6703
+ run, side: (rightwards ? "right" : "left")
6704
+ };
6705
+ };
6625
6706
  let cursorMotion = this.cursorMotion;
6626
6707
  const project = () => {
6627
6708
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
@@ -6759,7 +6840,14 @@ var EntityLabel;
6759
6840
  y -= height / 2;
6760
6841
  }
6761
6842
  // Membership uses the unclamped source footprint, never the summary's changing dimensions.
6762
- if (leader)
6843
+ let leaderRun;
6844
+ let leaderSide;
6845
+ if (leader && angled) {
6846
+ const fit = fitAngledLeader(anchorX, anchorY, leaderLength, width, height);
6847
+ ({ x, y } = fit);
6848
+ ({ run: leaderRun, side: leaderSide } = fit);
6849
+ }
6850
+ else if (leader)
6763
6851
  ({ x, y } = fitLeader(anchorX, anchorY, leaderLength, width, height));
6764
6852
  const bounds = clusterBounds || { x, y, width, height };
6765
6853
  if (!leader && (bounds.x + bounds.width < 0 || bounds.y + bounds.height < 0 || bounds.x > this.viewer.canvas.clientWidth || bounds.y > this.viewer.canvas.clientHeight)) {
@@ -6774,20 +6862,21 @@ var EntityLabel;
6774
6862
  extendedGeometry: visual instanceof Entity && Boolean(visual.polygon || visual.polyline || visual.corridor),
6775
6863
  viewportWidth: this.viewer.canvas.clientWidth,
6776
6864
  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,
6777
- entityId: (_t = this.rego) === null || _t === void 0 ? void 0 : _t.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, distance,
6865
+ 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,
6778
6866
  // Priority shows the hovered member individually without dragging its cluster along.
6779
6867
  opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
6780
6868
  };
6781
6869
  };
6782
6870
  let displacement = { x: 0, y: 0 };
6783
- let displayedSummaryMore = false;
6784
6871
  let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
6785
6872
  const apply = (placement) => {
6786
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
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;
6787
6874
  const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
6788
6875
  clickEntityIds = show ? placement.entityIds : [];
6789
6876
  ele.style.visibility = show ? "visible" : "hidden";
6790
6877
  lineEle.style.display = show && leader ? "block" : "none";
6878
+ if (runEle)
6879
+ runEle.style.display = show && leader ? "block" : "none";
6791
6880
  this.anchor.style.display = show && leader ? "block" : "none";
6792
6881
  if (!show) {
6793
6882
  displacement = { x: 0, y: 0 };
@@ -6798,17 +6887,8 @@ var EntityLabel;
6798
6887
  return;
6799
6888
  }
6800
6889
  const displayText = (_a = placement.text) !== null && _a !== void 0 ? _a : originalText;
6801
- const summaryMore = Boolean(placement.summaryMore);
6802
- if (textElement && (textElement.textContent !== displayText + (summaryMore ? "\n…" : "") || displayedSummaryMore !== summaryMore)) {
6890
+ if (textElement && textElement.textContent !== displayText) {
6803
6891
  textElement.textContent = displayText;
6804
- if (summaryMore) {
6805
- const more = document.createElement("span");
6806
- more.className = "entity-label-more";
6807
- more.style.opacity = "0.65";
6808
- more.textContent = "\n…";
6809
- textElement.appendChild(more);
6810
- }
6811
- displayedSummaryMore = summaryMore;
6812
6892
  // Measure the summary's footprint next frame without replacing each member's source text.
6813
6893
  this.viewer.scene.requestRender();
6814
6894
  }
@@ -6826,6 +6906,7 @@ var EntityLabel;
6826
6906
  // The badge participates in layout, so its new width needs another measurement.
6827
6907
  this.viewer.scene.requestRender();
6828
6908
  }
6909
+ fitIconBackground === null || fitIconBackground === void 0 ? void 0 : fitIconBackground();
6829
6910
  if (iconContainer) {
6830
6911
  const badgeWidth = countElement.offsetWidth;
6831
6912
  const iconWidth = iconElement.offsetWidth;
@@ -6833,7 +6914,9 @@ var EntityLabel;
6833
6914
  const clearance = badgeWidth ? (narrow ? badgeWidth / 2 + 2 : Math.max(0, badgeWidth / 2 - iconWidth / 4)) : 0;
6834
6915
  countElement.style.left = `calc(100% + ${clearance}px)`;
6835
6916
  // Reserve the badge overhang so adjacent text and viewport edges remain clear.
6836
- iconContainer.style.marginTop = `${countElement.offsetHeight / 2}px`;
6917
+ const overhang = countElement.offsetHeight / 2;
6918
+ iconContainer.style.marginTop = badgeBelow ? "0px" : `${overhang}px`;
6919
+ iconContainer.style.marginBottom = badgeBelow ? `${overhang}px` : "0px";
6837
6920
  iconContainer.style.marginRight = `${badgeWidth / 2 + clearance}px`;
6838
6921
  }
6839
6922
  // Summary and badge changes must fit on their first visible frame, including after resize.
@@ -6876,19 +6959,26 @@ var EntityLabel;
6876
6959
  sharedRing.animation = ringAnimation;
6877
6960
  }
6878
6961
  const radius = ringAnimation.inner > 0 ? ringAnimation.outer : 0;
6879
- const stem = (leader ? fitLeader(frame.anchorX, frame.anchorY, Math.max(frame.leaderLength, radius ? radius + 4 : 0), ele.offsetWidth / coordinates.scaleX, ele.offsetHeight / coordinates.scaleY, radius) : undefined);
6962
+ const stem = (leader ? (angled ? fitAngledLeader : fitLeader)(frame.anchorX, frame.anchorY, Math.max(frame.leaderLength, radius ? radius + 4 : 0), ele.offsetWidth / coordinates.scaleX, ele.offsetHeight / coordinates.scaleY, radius) : undefined);
6880
6963
  const position = { x: (stem || frame).x, y: (stem || frame).y };
6881
6964
  const width = ele.offsetWidth / coordinates.scaleX;
6882
6965
  const height = ele.offsetHeight / coordinates.scaleY;
6883
- if (leader && placement.textSide === "left") {
6966
+ if (leader && angled) {
6967
+ // The declutter owns the side; the fit only says which one had the room to begin with.
6968
+ 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";
6970
+ const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
6971
+ position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
6972
+ }
6973
+ else if (leader && placement.textSide === "left") {
6884
6974
  position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
6885
6975
  }
6886
- const gap = (_l = (_k = this.options.pointerAvoidance) === null || _k === void 0 ? void 0 : _k.gap) !== null && _l !== void 0 ? _l : 12;
6976
+ const gap = (_o = (_m = this.options.pointerAvoidance) === null || _m === void 0 ? void 0 : _m.gap) !== null && _o !== void 0 ? _o : 12;
6887
6977
  const containsPointer = (x, y, margin) => {
6888
6978
  return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
6889
6979
  };
6890
6980
  const passive = !selectOnClick || !this.visualRegister;
6891
- if (passive && ((_m = this.options.pointerAvoidance) === null || _m === void 0 ? void 0 : _m.enabled) !== false && pointer &&
6981
+ if (passive && ((_p = this.options.pointerAvoidance) === null || _p === void 0 ? void 0 : _p.enabled) !== false && pointer &&
6892
6982
  (containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
6893
6983
  if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
6894
6984
  // Choose against the original box and retain the offset until the pointer leaves both boxes.
@@ -6906,7 +6996,7 @@ var EntityLabel;
6906
6996
  else {
6907
6997
  displacement = { x: 0, y: 0 };
6908
6998
  }
6909
- const nudgeDuration = (_p = (_o = this.options.pointerAvoidance) === null || _o === void 0 ? void 0 : _o.transitionDuration) !== null && _p !== void 0 ? _p : 200;
6999
+ const nudgeDuration = (_r = (_q = this.options.pointerAvoidance) === null || _q === void 0 ? void 0 : _q.transitionDuration) !== null && _r !== void 0 ? _r : 200;
6910
7000
  const animateNudge = () => {
6911
7001
  const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
6912
7002
  const eased = progress * progress * (3 - 2 * progress);
@@ -6922,7 +7012,7 @@ var EntityLabel;
6922
7012
  fromY: nudge.y,
6923
7013
  targetX: displacement.x,
6924
7014
  targetY: displacement.y,
6925
- start: now + ((_r = (_q = this.options.pointerAvoidance) === null || _q === void 0 ? void 0 : _q.delay) !== null && _r !== void 0 ? _r : 180)
7015
+ start: now + ((_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.delay) !== null && _t !== void 0 ? _t : 180)
6926
7016
  };
6927
7017
  }
6928
7018
  if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
@@ -6934,7 +7024,7 @@ var EntityLabel;
6934
7024
  ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
6935
7025
  const clickTarget = content.querySelector('[role="button"]');
6936
7026
  if (clickTarget) {
6937
- clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_t = (_s = this.options.text) !== null && _s !== void 0 ? _s : text) !== null && _t !== void 0 ? _t : (_u = this.rego) === null || _u === void 0 ? void 0 : _u.entityId}`);
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}`);
6938
7028
  }
6939
7029
  if (leader) {
6940
7030
  const nodeRadius = ringAnimation.outer;
@@ -6957,8 +7047,13 @@ var EntityLabel;
6957
7047
  anchor.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
6958
7048
  anchor.style.zIndex = String(placement.zIndex);
6959
7049
  // The stem starts at the rim, never across the transparent centre, even after passive displacement.
6960
- const endX = Math.max(position.x, Math.min(frame.anchorX, position.x + width));
6961
- const endY = frame.anchorY < position.y ? position.y : frame.anchorY > position.y + height ? position.y + height : frame.anchorY;
7050
+ const straightX = Math.max(position.x, Math.min(frame.anchorX, position.x + width));
7051
+ const straightY = frame.anchorY < position.y ? position.y : frame.anchorY > position.y + height ? position.y + height : frame.anchorY;
7052
+ const beside = angled && (frame.anchorX < position.x || frame.anchorX > position.x + width);
7053
+ const elbowY = position.y + height / 2;
7054
+ const endX = beside ? frame.anchorX : straightX;
7055
+ const endY = beside ? elbowY : straightY;
7056
+ const runToX = frame.anchorX < position.x ? position.x : position.x + width;
6962
7057
  const dx = endX - frame.anchorX, dy = endY - frame.anchorY;
6963
7058
  const length = Math.hypot(dx, dy);
6964
7059
  const leaderAngle = Math.atan2(dy, dx);
@@ -6969,13 +7064,13 @@ var EntityLabel;
6969
7064
  // Keep the stem inside the 126-degree arc, away from antialiased endpoints.
6970
7065
  const arcFraction = 0.35;
6971
7066
  const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
6972
- const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_v = ringAnimation.arcOffset) !== null && _v !== void 0 ? _v : 0);
7067
+ const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_x = ringAnimation.arcOffset) !== null && _x !== void 0 ? _x : 0);
6973
7068
  const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
6974
- const elapsed = Math.min(32, Math.max(0, now - ((_w = ringAnimation.arcTime) !== null && _w !== void 0 ? _w : now)));
7069
+ const elapsed = Math.min(32, Math.max(0, now - ((_y = ringAnimation.arcTime) !== null && _y !== void 0 ? _y : now)));
6975
7070
  const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
6976
7071
  const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
6977
- ringAnimation.arcOffset = approach((_x = ringAnimation.arcOffset) !== null && _x !== void 0 ? _x : 0, targetOffset);
6978
- ringAnimation.arcOpen = approach((_y = ringAnimation.arcOpen) !== null && _y !== void 0 ? _y : 0, targetOpen);
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);
6979
7074
  ringAnimation.arcTime = now;
6980
7075
  if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
6981
7076
  this.viewer.scene.requestRender();
@@ -6987,17 +7082,27 @@ var EntityLabel;
6987
7082
  const cut = Math.min(radius, length);
6988
7083
  const startX = frame.anchorX + (length ? dx / length * cut : 0);
6989
7084
  const startY = frame.anchorY + (length ? dy / length * cut : 0);
6990
- const screenDx = (endX - startX) * coordinates.scaleX;
6991
- const screenDy = (endY - startY) * coordinates.scaleY;
6992
- lineEle.style.width = `${2 * coordinates.scaleX}px`;
6993
- lineEle.style.left = `${coordinates.x + (startX - 1) * coordinates.scaleX}px`;
6994
- lineEle.style.top = `${coordinates.y + startY * coordinates.scaleY}px`;
6995
- lineEle.style.height = `${Math.hypot(screenDx, screenDy)}px`;
6996
- lineEle.style.transform = `rotate(${Math.atan2(screenDy, screenDx) - Math.PI / 2}rad)`;
6997
- lineEle.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
6998
- lineEle.style.transformOrigin = "top center";
6999
- lineEle.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
7000
- lineEle.style.zIndex = String(placement.zIndex);
7085
+ const draw = (element, fromX, fromY, toX, toY) => {
7086
+ const screenDx = (toX - fromX) * coordinates.scaleX;
7087
+ const screenDy = (toY - fromY) * coordinates.scaleY;
7088
+ element.style.width = `${2 * coordinates.scaleX}px`;
7089
+ element.style.left = `${coordinates.x + (fromX - 1) * coordinates.scaleX}px`;
7090
+ element.style.top = `${coordinates.y + fromY * coordinates.scaleY}px`;
7091
+ element.style.height = `${Math.hypot(screenDx, screenDy)}px`;
7092
+ element.style.transform = `rotate(${Math.atan2(screenDy, screenDx) - Math.PI / 2}rad)`;
7093
+ element.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
7094
+ element.style.transformOrigin = "top center";
7095
+ element.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
7096
+ element.style.zIndex = String(placement.zIndex);
7097
+ };
7098
+ draw(lineEle, startX, startY, endX, endY);
7099
+ if (runEle) {
7100
+ // Overlap the corner by the stem's own width so the elbow has no notch in it.
7101
+ const overlap = frame.anchorX < runToX ? -1 : 1;
7102
+ runEle.style.display = beside ? "block" : "none";
7103
+ if (beside)
7104
+ draw(runEle, endX + overlap, endY, runToX, endY);
7105
+ }
7001
7106
  }
7002
7107
  };
7003
7108
  ele.style.display = "flex";
@@ -7022,7 +7127,7 @@ var EntityLabel;
7022
7127
  * Removes label from the viewer and unsubscribes from events.
7023
7128
  */
7024
7129
  removeLabel() {
7025
- var _a;
7130
+ var _a, _b;
7026
7131
  (_a = this.anchor) === null || _a === void 0 ? void 0 : _a.remove();
7027
7132
  this.anchor = null;
7028
7133
  if (this.label &&
@@ -7034,6 +7139,8 @@ var EntityLabel;
7034
7139
  this.line.parentElement.removeChild(this.line);
7035
7140
  this.line = null;
7036
7141
  }
7142
+ (_b = this.leaderRun) === null || _b === void 0 ? void 0 : _b.remove();
7143
+ this.leaderRun = null;
7037
7144
  if (this.renderRemoval) {
7038
7145
  this.renderRemoval();
7039
7146
  this.renderRemoval = null;
@@ -50264,7 +50371,7 @@ var StyleUtils;
50264
50371
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
50265
50372
  })(StyleUtils || (StyleUtils = {}));
50266
50373
 
50267
- const VERSION = "7.4.1";
50374
+ const VERSION = "7.4.2";
50268
50375
  /**
50269
50376
  * Updates the environment instance used by bruce-cesium to one specified.
50270
50377
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.