bruce-cesium 7.4.3 → 7.4.4
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.
- package/dist/bruce-cesium.es5.js +328 -156
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +326 -154
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js +325 -153
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/rendering/render-managers/common/entity-label.d.ts +6 -0
- package/package.json +1 -1
|
@@ -37,8 +37,16 @@ var EntityLabel;
|
|
|
37
37
|
// so a row of labels reads as one size rather than a set of differently shaped pills.
|
|
38
38
|
const ICON_BACKGROUND_UNIFORM_TOLERANCE = 0.3;
|
|
39
39
|
const TEXT_MAX_WIDTH = 200;
|
|
40
|
+
// Distinct icons a cluster shows, the representative's included.
|
|
41
|
+
const ICON_STACK_LIMIT = 3;
|
|
42
|
+
// Size of every icon in a cluster's arrangement, relative to the representative's own.
|
|
43
|
+
const ICON_STACK_SCALE = 0.6;
|
|
40
44
|
// How far an angled leader runs sideways before the label starts.
|
|
41
45
|
const ANGLED_LEADER_RUN = 28;
|
|
46
|
+
// Clearance between a point or billboard glyph and the ring drawn around it.
|
|
47
|
+
const ICON_RING_PADDING = 4;
|
|
48
|
+
// Small points still get a ring as large as the default near ring.
|
|
49
|
+
const ICON_RING_MIN = 10;
|
|
42
50
|
// One per tinted icon, since an SVG mask is addressed by id across the whole document.
|
|
43
51
|
let tintCounter = 0;
|
|
44
52
|
// Key where the label instance is stored within the cesium entity.
|
|
@@ -220,6 +228,28 @@ var EntityLabel;
|
|
|
220
228
|
* Prefer declared semantics; only infers from recognisable glyphs and filename tokens, never image pixels.
|
|
221
229
|
*/
|
|
222
230
|
EntityLabel.IconPriority = point_clustering_core_1.GetClusterIconPriority;
|
|
231
|
+
function iconLook(options) {
|
|
232
|
+
return {
|
|
233
|
+
icon: options.icon || undefined,
|
|
234
|
+
iconUrl: options.iconUrl || undefined,
|
|
235
|
+
iconClass: typeof options.iconClass === "string" && options.iconClass ? options.iconClass : undefined,
|
|
236
|
+
iconColor: typeof options.iconColor === "string" && options.iconColor ? options.iconColor : undefined,
|
|
237
|
+
iconBackground: options.iconBackground || undefined
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/*
|
|
241
|
+
* Everything that makes two labels look alike, so labels from different groups only merge when they would read the same.
|
|
242
|
+
*/
|
|
243
|
+
function clusterLook(options) {
|
|
244
|
+
var _a;
|
|
245
|
+
const presentation = options.presentation || "text";
|
|
246
|
+
return JSON.stringify([
|
|
247
|
+
presentation,
|
|
248
|
+
(_a = options.leader) !== null && _a !== void 0 ? _a : null,
|
|
249
|
+
options.leaderStyle || "default", options.leaderColor || "",
|
|
250
|
+
presentation === "text" ? null : iconLook(options)
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
223
253
|
/*
|
|
224
254
|
* Resolves clustering and stable text placement before depth fading; highlighted labels receive priority.
|
|
225
255
|
*/
|
|
@@ -241,7 +271,8 @@ var EntityLabel;
|
|
|
241
271
|
if (!((_b = (_a = frame.options) === null || _a === void 0 ? void 0 : _a.cluster) === null || _b === void 0 ? void 0 : _b.enabled) || frame.priority > 0) {
|
|
242
272
|
continue;
|
|
243
273
|
}
|
|
244
|
-
|
|
274
|
+
// Groups meet inside a presentation; which of them may merge is decided pairwise below.
|
|
275
|
+
const key = frame.options.presentation || "text";
|
|
245
276
|
if (!groups.has(key)) {
|
|
246
277
|
groups.set(key, []);
|
|
247
278
|
}
|
|
@@ -253,32 +284,54 @@ var EntityLabel;
|
|
|
253
284
|
frame.options.cluster.pixelRange == null ? { ...frame, x: frame.anchorX, y: frame.anchorY, width: 1, height: 1 } :
|
|
254
285
|
frame.clusterBounds ? { ...frame, ...frame.clusterBounds } : frame);
|
|
255
286
|
const index = new ScreenIndex();
|
|
256
|
-
|
|
287
|
+
const rank = new Map();
|
|
288
|
+
const looks = new Map();
|
|
289
|
+
candidates.forEach((frame, i) => {
|
|
257
290
|
index.Add(frame);
|
|
258
|
-
|
|
291
|
+
rank.set(frame.id, i);
|
|
292
|
+
looks.set(frame.id, clusterLook(frame.options));
|
|
293
|
+
});
|
|
259
294
|
const clusters = (0, point_clustering_core_1.ClusterItems)({
|
|
260
295
|
items: candidates,
|
|
261
296
|
id: frame => frame.id,
|
|
262
297
|
minSize: group[0].options.cluster.minSize,
|
|
263
298
|
neighbours: frame => {
|
|
264
299
|
var _a;
|
|
265
|
-
|
|
300
|
+
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));
|
|
301
|
+
return compatibleMembers(frame, nearby, other => other.group || "", other => looks.get(other.id));
|
|
266
302
|
}
|
|
267
303
|
});
|
|
268
304
|
for (const cluster of clusters) {
|
|
269
305
|
let chosen = cluster.representative;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
}
|
|
306
|
+
const iconic = (chosen.options.presentation || "text") !== "text";
|
|
307
|
+
// Members in the order they would win representation; the seed is the nearest.
|
|
308
|
+
let ordered = [chosen, ...cluster.members.filter(member => member !== chosen)];
|
|
309
|
+
if (iconic && chosen.options.cluster.representative !== "nearest") {
|
|
310
|
+
ordered = ordered.map((member, i) => ({ member, i, priority: EntityLabel.IconPriority(member.options) }))
|
|
311
|
+
.sort((a, b) => b.priority - a.priority || a.i - b.i).map(entry => entry.member);
|
|
312
|
+
chosen = ordered[0];
|
|
279
313
|
}
|
|
280
314
|
const representative = result.get(chosen.id);
|
|
281
315
|
representative.members = cluster.members.map(frame => frame.id);
|
|
316
|
+
if (iconic && chosen.options.iconScale !== 0) {
|
|
317
|
+
const seen = new Set();
|
|
318
|
+
const icons = [];
|
|
319
|
+
for (const member of ordered) {
|
|
320
|
+
const look = iconLook(member.options);
|
|
321
|
+
const key = JSON.stringify(look);
|
|
322
|
+
if (seen.has(key)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
seen.add(key);
|
|
326
|
+
if (member !== ordered[0])
|
|
327
|
+
icons.push(look);
|
|
328
|
+
if (seen.size >= ICON_STACK_LIMIT) {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (icons.length)
|
|
333
|
+
representative.icons = icons;
|
|
334
|
+
}
|
|
282
335
|
representative.entityIds = Array.from(new Set(cluster.members.map(frame => frame.entityId).filter(Boolean)));
|
|
283
336
|
if (chosen.options.presentation !== "icon") {
|
|
284
337
|
const values = new Set();
|
|
@@ -332,6 +385,34 @@ var EntityLabel;
|
|
|
332
385
|
return Array.from(result.values());
|
|
333
386
|
}
|
|
334
387
|
EntityLabel.Layout = Layout;
|
|
388
|
+
/*
|
|
389
|
+
* The seed plus every neighbour, in order, that shares a group or a look with each member accepted so far.
|
|
390
|
+
*
|
|
391
|
+
* Checking against the whole accepted set stops a group with mixed icons bridging two groups that look different.
|
|
392
|
+
*/
|
|
393
|
+
function compatibleMembers(seed, nearby, group, look) {
|
|
394
|
+
const accepted = [seed];
|
|
395
|
+
const byGroup = new Map([[group(seed), 1]]);
|
|
396
|
+
const byLook = new Map([[look(seed), 1]]);
|
|
397
|
+
const byBoth = new Map([[JSON.stringify([group(seed), look(seed)]), 1]]);
|
|
398
|
+
for (const item of nearby) {
|
|
399
|
+
if (item === seed) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const itemGroup = group(item), itemLook = look(item), both = JSON.stringify([itemGroup, itemLook]);
|
|
403
|
+
// Accepted members in another group with another look are the ones this item may not sit beside.
|
|
404
|
+
const conflicting = accepted.length - (byGroup.get(itemGroup) || 0) - (byLook.get(itemLook) || 0) + (byBoth.get(both) || 0);
|
|
405
|
+
if (conflicting > 0) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
accepted.push(item);
|
|
409
|
+
byGroup.set(itemGroup, (byGroup.get(itemGroup) || 0) + 1);
|
|
410
|
+
byLook.set(itemLook, (byLook.get(itemLook) || 0) + 1);
|
|
411
|
+
byBoth.set(both, (byBoth.get(both) || 0) + 1);
|
|
412
|
+
}
|
|
413
|
+
return accepted;
|
|
414
|
+
}
|
|
415
|
+
EntityLabel.compatibleMembers = compatibleMembers;
|
|
335
416
|
const layers = new WeakMap();
|
|
336
417
|
const containers = new WeakMap();
|
|
337
418
|
/*
|
|
@@ -729,7 +810,7 @@ var EntityLabel;
|
|
|
729
810
|
* @param starterPos3d optional position to create label at. Ignored for billboard labels.
|
|
730
811
|
*/
|
|
731
812
|
async createLabel(starterPos3d) {
|
|
732
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o
|
|
813
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
733
814
|
this.removeLabel();
|
|
734
815
|
let pos3d = starterPos3d ? starterPos3d.clone() : null;
|
|
735
816
|
const counter = ++this.counter;
|
|
@@ -1051,125 +1132,28 @@ var EntityLabel;
|
|
|
1051
1132
|
let iconElement;
|
|
1052
1133
|
let iconContainer;
|
|
1053
1134
|
let fitIconBackground;
|
|
1135
|
+
let iconStack;
|
|
1136
|
+
let stackedFits = [];
|
|
1137
|
+
let displayedStack = "[]";
|
|
1054
1138
|
content.className = "entity-label-content";
|
|
1055
1139
|
content.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
1056
1140
|
if (presentation === "icon-text-above") {
|
|
1057
1141
|
content.style.flexDirection = "column-reverse";
|
|
1058
1142
|
}
|
|
1059
1143
|
if (presentation !== "text") {
|
|
1060
|
-
const
|
|
1061
|
-
iconElement = icon;
|
|
1062
|
-
|
|
1063
|
-
icon.style.cssText = `font-size:${(this.options.iconSize || 24) * ((_m = this.options.iconScale) !== null && _m !== void 0 ? _m : 1)}px;line-height:1;`;
|
|
1064
|
-
icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
|
|
1065
|
-
const iconColor = typeof this.options.iconColor === "string" ? this.options.iconColor : undefined;
|
|
1066
|
-
const iconBackground = this.options.iconBackground && this.options.iconScale !== 0;
|
|
1067
|
-
if (iconColor) {
|
|
1068
|
-
icon.style.color = iconColor;
|
|
1069
|
-
}
|
|
1070
|
-
if (this.options.iconUrl) {
|
|
1071
|
-
const image = document.createElement("img");
|
|
1072
|
-
image.draggable = false;
|
|
1073
|
-
image.alt = this.options.icon || "";
|
|
1074
|
-
const scale = (_o = this.options.iconScale) !== null && _o !== void 0 ? _o : 1;
|
|
1075
|
-
const size = this.options.iconSize;
|
|
1076
|
-
const svgNs = "http://www.w3.org/2000/svg";
|
|
1077
|
-
const tint = iconColor ? document.createElementNS(svgNs, "svg") : null;
|
|
1078
|
-
const painted = tint ? document.createElementNS(svgNs, "image") : null;
|
|
1079
|
-
const inked = tint ? document.createElementNS(svgNs, "rect") : null;
|
|
1080
|
-
if (tint) {
|
|
1081
|
-
const maskId = `entity-label-tint-${++tintCounter}`;
|
|
1082
|
-
const mask = document.createElementNS(svgNs, "mask");
|
|
1083
|
-
mask.setAttribute("id", maskId);
|
|
1084
|
-
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
|
1085
|
-
painted.setAttribute("href", this.options.iconUrl);
|
|
1086
|
-
painted.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
1087
|
-
mask.appendChild(painted);
|
|
1088
|
-
inked.setAttribute("fill", iconColor);
|
|
1089
|
-
inked.setAttribute("mask", `url(#${maskId})`);
|
|
1090
|
-
tint.append(mask, inked);
|
|
1091
|
-
tint.style.display = "block";
|
|
1092
|
-
icon.style.display = "inline-flex";
|
|
1093
|
-
icon.appendChild(tint);
|
|
1094
|
-
}
|
|
1095
|
-
const resize = () => {
|
|
1096
|
-
if (this.disposed || this.counter !== counter) {
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
const intrinsic = this.options.iconScale != null && size == null;
|
|
1100
|
-
const width = (intrinsic ? image.naturalWidth || 24 : size || 24) * scale;
|
|
1101
|
-
const height = (intrinsic ? image.naturalHeight || 24 : size || 24) * scale;
|
|
1102
|
-
if (tint) {
|
|
1103
|
-
tint.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
1104
|
-
for (const node of [tint, painted, inked]) {
|
|
1105
|
-
node.setAttribute("width", String(width));
|
|
1106
|
-
node.setAttribute("height", String(height));
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
else {
|
|
1110
|
-
image.style.width = `${width}px`;
|
|
1111
|
-
image.style.height = `${height}px`;
|
|
1112
|
-
}
|
|
1113
|
-
this.viewer.scene.requestRender();
|
|
1114
|
-
};
|
|
1115
|
-
image.onload = resize;
|
|
1116
|
-
resize();
|
|
1117
|
-
image.src = this.options.iconUrl;
|
|
1118
|
-
if (!tint) {
|
|
1119
|
-
icon.appendChild(image);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
else if (typeof this.options.iconClass === "string" && this.options.iconClass) {
|
|
1123
|
-
icon.className = `entity-label-icon ${this.options.iconClass}`;
|
|
1124
|
-
}
|
|
1125
|
-
else {
|
|
1126
|
-
icon.textContent = this.options.icon || "●";
|
|
1127
|
-
}
|
|
1128
|
-
if (iconBackground) {
|
|
1129
|
-
const color = Cesium.Color.fromCssColorString(iconColor || "white") || Cesium.Color.WHITE;
|
|
1130
|
-
const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
|
|
1131
|
-
const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Cesium.Color()) :
|
|
1132
|
-
color.brighten(ICON_BACKGROUND_BLEND, new Cesium.Color()));
|
|
1133
|
-
const size = (this.options.iconSize || 24) * ((_p = this.options.iconScale) !== null && _p !== void 0 ? _p : 1);
|
|
1134
|
-
icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
|
|
1135
|
-
icon.style.borderRadius = "50%";
|
|
1136
|
-
icon.style.padding = `${size / 3}px`;
|
|
1137
|
-
icon.style.display = "inline-flex";
|
|
1138
|
-
icon.style.alignItems = "center";
|
|
1139
|
-
icon.style.justifyContent = "center";
|
|
1140
|
-
icon.style.minWidth = `${size}px`;
|
|
1141
|
-
icon.style.minHeight = `${size}px`;
|
|
1142
|
-
icon.style.boxSizing = "content-box";
|
|
1143
|
-
let fitted = false;
|
|
1144
|
-
fitIconBackground = () => {
|
|
1145
|
-
if (fitted) {
|
|
1146
|
-
return;
|
|
1147
|
-
}
|
|
1148
|
-
const padding = size / 3;
|
|
1149
|
-
const width = icon.offsetWidth - padding * 2;
|
|
1150
|
-
const height = icon.offsetHeight - padding * 2;
|
|
1151
|
-
if (width <= 0 || height <= 0) {
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
fitted = true;
|
|
1155
|
-
const longest = Math.max(width, height);
|
|
1156
|
-
const difference = (longest - Math.min(width, height)) / longest;
|
|
1157
|
-
if (difference > ICON_BACKGROUND_UNIFORM_TOLERANCE) {
|
|
1158
|
-
return;
|
|
1159
|
-
}
|
|
1160
|
-
icon.style.minWidth = `${longest}px`;
|
|
1161
|
-
icon.style.minHeight = `${longest}px`;
|
|
1162
|
-
this.viewer.scene.requestRender();
|
|
1163
|
-
};
|
|
1164
|
-
}
|
|
1165
|
-
if (this.options.iconScale === 0) {
|
|
1166
|
-
icon.style.display = "none";
|
|
1167
|
-
}
|
|
1144
|
+
const built = this.buildIcon(this.options, counter);
|
|
1145
|
+
const icon = iconElement = built.icon;
|
|
1146
|
+
fitIconBackground = built.fit;
|
|
1168
1147
|
makeClickable(icon);
|
|
1169
1148
|
// Keep badges outside the glyph so font classes and image masks cannot affect them.
|
|
1170
1149
|
iconContainer = document.createElement("span");
|
|
1171
|
-
iconContainer.style.cssText = "position:relative;display:inline-flex;flex:none;";
|
|
1172
|
-
|
|
1150
|
+
iconContainer.style.cssText = "position:relative;display:inline-flex;flex:none;align-items:center;";
|
|
1151
|
+
icon.style.position = "relative";
|
|
1152
|
+
iconStack = document.createElement("span");
|
|
1153
|
+
iconStack.className = "entity-label-icon-stack";
|
|
1154
|
+
iconStack.style.cssText = "display:none;gap:2px;align-items:center;justify-items:center;flex:none;";
|
|
1155
|
+
makeClickable(iconStack);
|
|
1156
|
+
iconContainer.append(iconStack, icon);
|
|
1173
1157
|
content.appendChild(iconContainer);
|
|
1174
1158
|
}
|
|
1175
1159
|
if (presentation !== "icon") {
|
|
@@ -1181,7 +1165,7 @@ var EntityLabel;
|
|
|
1181
1165
|
if (presentation === "text")
|
|
1182
1166
|
makeClickable(textElement);
|
|
1183
1167
|
content.appendChild(textElement);
|
|
1184
|
-
if ((
|
|
1168
|
+
if ((_m = this.options.cluster) === null || _m === void 0 ? void 0 : _m.enabled) {
|
|
1185
1169
|
sourceText = document.createElement("span");
|
|
1186
1170
|
sourceText.className = "entity-label-source-text";
|
|
1187
1171
|
sourceText.setAttribute("aria-hidden", "true");
|
|
@@ -1222,7 +1206,7 @@ var EntityLabel;
|
|
|
1222
1206
|
ele.appendChild(countElement);
|
|
1223
1207
|
}
|
|
1224
1208
|
ele.className = "entity-label";
|
|
1225
|
-
ele.dataset.entityId = ((
|
|
1209
|
+
ele.dataset.entityId = ((_o = this.rego) === null || _o === void 0 ? void 0 : _o.entityId) || "";
|
|
1226
1210
|
ele.dataset.presentation = presentation;
|
|
1227
1211
|
if (this.groupId) {
|
|
1228
1212
|
ele.dataset.groupId = this.groupId;
|
|
@@ -1338,6 +1322,41 @@ var EntityLabel;
|
|
|
1338
1322
|
run, side: (rightwards ? "right" : "left")
|
|
1339
1323
|
};
|
|
1340
1324
|
};
|
|
1325
|
+
/*
|
|
1326
|
+
* The on-screen centre and half-size of a point or billboard glyph, relative to its projected position.
|
|
1327
|
+
* Read every frame, since styling and selection can rescale a billboard in place.
|
|
1328
|
+
*/
|
|
1329
|
+
const measureIcon = () => {
|
|
1330
|
+
var _a;
|
|
1331
|
+
if (!normalLeader || !(visual instanceof Cesium.Entity)) {
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
if (visual.point) {
|
|
1335
|
+
const size = (0, js_utils_2.EnsureNumber)(getValue(this.viewer, visual.point.pixelSize), 1) +
|
|
1336
|
+
(0, js_utils_2.EnsureNumber)(getValue(this.viewer, visual.point.outlineWidth), 0) * 2;
|
|
1337
|
+
return size > 0 ? { x: 0, y: 0, radius: size / 2 } : null;
|
|
1338
|
+
}
|
|
1339
|
+
if (!visual.billboard) {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
const billboard = visual.billboard;
|
|
1343
|
+
const scale = (0, js_utils_2.EnsureNumber)(getValue(this.viewer, billboard.scale), 1);
|
|
1344
|
+
// Icon billboards only record their image height; generated circles set both sides.
|
|
1345
|
+
const height = (0, js_utils_2.EnsureNumber)((_a = getValue(this.viewer, billboard.height)) !== null && _a !== void 0 ? _a : billboard._billboardSize, 0) * scale;
|
|
1346
|
+
const width = (0, js_utils_2.EnsureNumber)(getValue(this.viewer, billboard.width), height / scale) * scale;
|
|
1347
|
+
if (!(height > 0) || !(width > 0)) {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
const vertical = getValue(this.viewer, billboard.verticalOrigin);
|
|
1351
|
+
const horizontal = getValue(this.viewer, billboard.horizontalOrigin);
|
|
1352
|
+
const offset = getValue(this.viewer, billboard.pixelOffset);
|
|
1353
|
+
return {
|
|
1354
|
+
x: ((offset === null || offset === void 0 ? void 0 : offset.x) || 0) + (horizontal === Cesium.HorizontalOrigin.LEFT ? width / 2 : horizontal === Cesium.HorizontalOrigin.RIGHT ? -width / 2 : 0),
|
|
1355
|
+
y: ((offset === null || offset === void 0 ? void 0 : offset.y) || 0) + (vertical === Cesium.VerticalOrigin.BOTTOM || vertical === Cesium.VerticalOrigin.BASELINE ? -height / 2 :
|
|
1356
|
+
vertical === Cesium.VerticalOrigin.TOP ? height / 2 : 0),
|
|
1357
|
+
radius: Math.max(width, height) / 2
|
|
1358
|
+
};
|
|
1359
|
+
};
|
|
1341
1360
|
let cursorMotion = this.cursorMotion;
|
|
1342
1361
|
const project = () => {
|
|
1343
1362
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
@@ -1407,9 +1426,13 @@ var EntityLabel;
|
|
|
1407
1426
|
this.cursorMotion = cursorMotion;
|
|
1408
1427
|
const cursorAmount = cursorMotion.amount;
|
|
1409
1428
|
leader = normalLeader || cursorAmount > 0;
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1429
|
+
// An icon keeps its callout centred on the glyph rather than following the cursor across it.
|
|
1430
|
+
const icon = measureIcon();
|
|
1431
|
+
const iconRing = icon ? Math.max(ICON_RING_MIN, icon.radius + ICON_RING_PADDING) : 0;
|
|
1432
|
+
const pivot = icon ? undefined : transientPivot;
|
|
1433
|
+
let x = ((_l = pivot === null || pivot === void 0 ? void 0 : pivot.x) !== null && _l !== void 0 ? _l : anchor.x) + (icon || pixelOffset).x;
|
|
1434
|
+
let y = ((_m = pivot === null || pivot === void 0 ? void 0 : pivot.y) !== null && _m !== void 0 ? _m : anchor.y) + (icon || pixelOffset).y;
|
|
1435
|
+
if (cursorAmount > 0 && !icon) {
|
|
1413
1436
|
// A leader-less label starts centred on its anchor; preserve that box as it lifts away.
|
|
1414
1437
|
if (!normalLeader) {
|
|
1415
1438
|
x -= ele.offsetWidth / layer.coordinates.scaleX / 2;
|
|
@@ -1448,9 +1471,11 @@ var EntityLabel;
|
|
|
1448
1471
|
leaderLength += (96 - leaderLength) * cursorAmount;
|
|
1449
1472
|
}
|
|
1450
1473
|
;
|
|
1474
|
+
// Measured from the ring, so the visible stem is the same length whatever the glyph's size.
|
|
1475
|
+
leaderLength += iconRing;
|
|
1451
1476
|
y = anchorY - leaderLength;
|
|
1452
1477
|
}
|
|
1453
|
-
else if (hover && !reused) {
|
|
1478
|
+
else if (hover && !reused && !icon) {
|
|
1454
1479
|
x = hover.x + pixelOffset.x;
|
|
1455
1480
|
y = hover.y + pixelOffset.y - 24;
|
|
1456
1481
|
}
|
|
@@ -1502,6 +1527,7 @@ var EntityLabel;
|
|
|
1502
1527
|
viewportWidth: this.viewer.canvas.clientWidth,
|
|
1503
1528
|
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,
|
|
1504
1529
|
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,
|
|
1530
|
+
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius,
|
|
1505
1531
|
// Priority shows the hovered member individually without dragging its cluster along.
|
|
1506
1532
|
opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
1507
1533
|
};
|
|
@@ -1509,7 +1535,7 @@ var EntityLabel;
|
|
|
1509
1535
|
let displacement = { x: 0, y: 0 };
|
|
1510
1536
|
let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
1511
1537
|
const apply = (placement) => {
|
|
1512
|
-
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;
|
|
1538
|
+
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;
|
|
1513
1539
|
const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
|
|
1514
1540
|
clickEntityIds = show ? placement.entityIds : [];
|
|
1515
1541
|
ele.style.visibility = show ? "visible" : "hidden";
|
|
@@ -1546,6 +1572,29 @@ var EntityLabel;
|
|
|
1546
1572
|
this.viewer.scene.requestRender();
|
|
1547
1573
|
}
|
|
1548
1574
|
fitIconBackground === null || fitIconBackground === void 0 ? void 0 : fitIconBackground();
|
|
1575
|
+
const stack = JSON.stringify(placement.icons || []);
|
|
1576
|
+
if (iconStack && stack !== displayedStack) {
|
|
1577
|
+
displayedStack = stack;
|
|
1578
|
+
const looks = ((_b = placement.icons) === null || _b === void 0 ? void 0 : _b.length) ? [iconLook(this.options), ...placement.icons] : [];
|
|
1579
|
+
// One small size for all, so no icon in the arrangement reads as more important than another.
|
|
1580
|
+
const size = (this.options.iconSize || 24) * ((_c = this.options.iconScale) !== null && _c !== void 0 ? _c : 1) * ICON_STACK_SCALE;
|
|
1581
|
+
const built = looks.map(look => this.buildIcon({ ...look, iconSize: size }, counter));
|
|
1582
|
+
// Two sit side by side; three make a triangle with the representative on top.
|
|
1583
|
+
iconStack.style.gridTemplateColumns = built.length === 3 ? "auto auto" : `repeat(${built.length}, auto)`;
|
|
1584
|
+
if (built.length === 3) {
|
|
1585
|
+
built[0].icon.style.gridColumn = "1 / span 2";
|
|
1586
|
+
}
|
|
1587
|
+
iconStack.replaceChildren(...built.map(({ icon }) => icon));
|
|
1588
|
+
iconStack.style.display = built.length ? "inline-grid" : "none";
|
|
1589
|
+
// The glyph stays measurable, since cluster membership is taken from its footprint.
|
|
1590
|
+
iconElement.style.visibility = built.length ? "hidden" : "";
|
|
1591
|
+
iconElement.style.position = built.length ? "absolute" : "relative";
|
|
1592
|
+
stackedFits = built.map(({ fit }) => fit).filter(Boolean);
|
|
1593
|
+
// The stack widens the label, which the next frame has to measure.
|
|
1594
|
+
this.viewer.scene.requestRender();
|
|
1595
|
+
}
|
|
1596
|
+
for (const fit of stackedFits)
|
|
1597
|
+
fit();
|
|
1549
1598
|
if (iconContainer) {
|
|
1550
1599
|
const badgeWidth = countElement.offsetWidth;
|
|
1551
1600
|
const iconWidth = iconElement.offsetWidth;
|
|
@@ -1561,37 +1610,44 @@ var EntityLabel;
|
|
|
1561
1610
|
// Summary and badge changes must fit on their first visible frame, including after resize.
|
|
1562
1611
|
const ring = this.options.anchorRing;
|
|
1563
1612
|
const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
|
|
1564
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
1565
|
-
const
|
|
1566
|
-
const
|
|
1613
|
+
(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));
|
|
1614
|
+
const iconRing = frame.iconRadius ? Math.max(ICON_RING_MIN, frame.iconRadius + ICON_RING_PADDING) : 0;
|
|
1615
|
+
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);
|
|
1616
|
+
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));
|
|
1567
1617
|
const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
|
|
1618
|
+
// Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
|
|
1619
|
+
const targetOuter = iconRing || targetRadius || 2.5;
|
|
1620
|
+
const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
|
|
1568
1621
|
const now = Date.now();
|
|
1569
|
-
const duration = (
|
|
1622
|
+
const duration = (_l = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _l !== void 0 ? _l : 200;
|
|
1570
1623
|
const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
|
|
1571
1624
|
let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
|
|
1572
1625
|
if (!ringAnimation) {
|
|
1573
|
-
|
|
1626
|
+
const outer = iconRing || 2.5;
|
|
1627
|
+
const inner = iconRing;
|
|
1628
|
+
ringAnimation = { target: outer, targetInner: inner, start: now, outer, inner, fromOuter: outer, fromInner: inner };
|
|
1574
1629
|
}
|
|
1575
1630
|
;
|
|
1576
1631
|
const animateRing = () => {
|
|
1577
1632
|
const progress = duration > 0 ? Math.min(1, Math.max(0, (now - ringAnimation.start) / duration)) : 1;
|
|
1578
1633
|
const eased = progress * progress * (3 - 2 * progress);
|
|
1579
|
-
ringAnimation.outer = ringAnimation.fromOuter + (
|
|
1580
|
-
ringAnimation.inner = ringAnimation.fromInner + (
|
|
1634
|
+
ringAnimation.outer = ringAnimation.fromOuter + (ringAnimation.target - ringAnimation.fromOuter) * eased;
|
|
1635
|
+
ringAnimation.inner = ringAnimation.fromInner + (ringAnimation.targetInner - ringAnimation.fromInner) * eased;
|
|
1581
1636
|
return progress;
|
|
1582
1637
|
};
|
|
1583
1638
|
animateRing();
|
|
1584
|
-
if (ringAnimation.target !==
|
|
1639
|
+
if (ringAnimation.target !== targetOuter || ringAnimation.targetInner !== targetInner) {
|
|
1585
1640
|
ringAnimation = {
|
|
1586
1641
|
...ringAnimation,
|
|
1587
|
-
target:
|
|
1642
|
+
target: targetOuter,
|
|
1643
|
+
targetInner,
|
|
1588
1644
|
start: now,
|
|
1589
1645
|
fromOuter: ringAnimation.outer,
|
|
1590
1646
|
fromInner: ringAnimation.inner
|
|
1591
1647
|
};
|
|
1592
1648
|
}
|
|
1593
1649
|
// Animate the hole and stem together; no CSS transition may lag behind a moving world anchor.
|
|
1594
|
-
if (animateRing() < 1 && (ringAnimation.fromOuter !==
|
|
1650
|
+
if (animateRing() < 1 && (ringAnimation.fromOuter !== targetOuter || ringAnimation.fromInner !== targetInner)) {
|
|
1595
1651
|
this.viewer.scene.requestRender();
|
|
1596
1652
|
}
|
|
1597
1653
|
this.ringAnimation = ringAnimation;
|
|
@@ -1606,19 +1662,19 @@ var EntityLabel;
|
|
|
1606
1662
|
if (leader && angled) {
|
|
1607
1663
|
// The declutter owns the side; the fit only says which one had the room to begin with.
|
|
1608
1664
|
const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
|
|
1609
|
-
const side = (
|
|
1665
|
+
const side = (_o = (_m = placement.textSide) !== null && _m !== void 0 ? _m : frame.leaderSide) !== null && _o !== void 0 ? _o : "right";
|
|
1610
1666
|
const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
|
|
1611
1667
|
position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
|
|
1612
1668
|
}
|
|
1613
1669
|
else if (leader && placement.textSide === "left") {
|
|
1614
1670
|
position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
|
|
1615
1671
|
}
|
|
1616
|
-
const gap = (
|
|
1672
|
+
const gap = (_q = (_p = this.options.pointerAvoidance) === null || _p === void 0 ? void 0 : _p.gap) !== null && _q !== void 0 ? _q : 12;
|
|
1617
1673
|
const containsPointer = (x, y, margin) => {
|
|
1618
1674
|
return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
|
|
1619
1675
|
};
|
|
1620
1676
|
const passive = !selectOnClick || !this.visualRegister;
|
|
1621
|
-
if (passive && ((
|
|
1677
|
+
if (passive && ((_r = this.options.pointerAvoidance) === null || _r === void 0 ? void 0 : _r.enabled) !== false && pointer &&
|
|
1622
1678
|
(containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
|
|
1623
1679
|
if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
|
|
1624
1680
|
// Choose against the original box and retain the offset until the pointer leaves both boxes.
|
|
@@ -1636,7 +1692,7 @@ var EntityLabel;
|
|
|
1636
1692
|
else {
|
|
1637
1693
|
displacement = { x: 0, y: 0 };
|
|
1638
1694
|
}
|
|
1639
|
-
const nudgeDuration = (
|
|
1695
|
+
const nudgeDuration = (_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.transitionDuration) !== null && _t !== void 0 ? _t : 200;
|
|
1640
1696
|
const animateNudge = () => {
|
|
1641
1697
|
const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
|
|
1642
1698
|
const eased = progress * progress * (3 - 2 * progress);
|
|
@@ -1652,7 +1708,7 @@ var EntityLabel;
|
|
|
1652
1708
|
fromY: nudge.y,
|
|
1653
1709
|
targetX: displacement.x,
|
|
1654
1710
|
targetY: displacement.y,
|
|
1655
|
-
start: now + ((
|
|
1711
|
+
start: now + ((_v = (_u = this.options.pointerAvoidance) === null || _u === void 0 ? void 0 : _u.delay) !== null && _v !== void 0 ? _v : 180)
|
|
1656
1712
|
};
|
|
1657
1713
|
}
|
|
1658
1714
|
if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
|
|
@@ -1662,9 +1718,8 @@ var EntityLabel;
|
|
|
1662
1718
|
position.y += nudge.y;
|
|
1663
1719
|
ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
|
|
1664
1720
|
ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
|
|
1665
|
-
const clickTarget
|
|
1666
|
-
|
|
1667
|
-
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}`);
|
|
1721
|
+
for (const clickTarget of Array.from(content.querySelectorAll('[role="button"]'))) {
|
|
1722
|
+
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}`);
|
|
1668
1723
|
}
|
|
1669
1724
|
if (leader) {
|
|
1670
1725
|
const nodeRadius = ringAnimation.outer;
|
|
@@ -1704,13 +1759,13 @@ var EntityLabel;
|
|
|
1704
1759
|
// Keep the stem inside the 126-degree arc, away from antialiased endpoints.
|
|
1705
1760
|
const arcFraction = 0.35;
|
|
1706
1761
|
const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
|
|
1707
|
-
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((
|
|
1762
|
+
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_z = ringAnimation.arcOffset) !== null && _z !== void 0 ? _z : 0);
|
|
1708
1763
|
const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
|
|
1709
|
-
const elapsed = Math.min(32, Math.max(0, now - ((
|
|
1764
|
+
const elapsed = Math.min(32, Math.max(0, now - ((_0 = ringAnimation.arcTime) !== null && _0 !== void 0 ? _0 : now)));
|
|
1710
1765
|
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
1711
1766
|
const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
|
|
1712
|
-
ringAnimation.arcOffset = approach((
|
|
1713
|
-
ringAnimation.arcOpen = approach((
|
|
1767
|
+
ringAnimation.arcOffset = approach((_1 = ringAnimation.arcOffset) !== null && _1 !== void 0 ? _1 : 0, targetOffset);
|
|
1768
|
+
ringAnimation.arcOpen = approach((_2 = ringAnimation.arcOpen) !== null && _2 !== void 0 ? _2 : 0, targetOpen);
|
|
1714
1769
|
ringAnimation.arcTime = now;
|
|
1715
1770
|
if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
|
|
1716
1771
|
this.viewer.scene.requestRender();
|
|
@@ -1763,6 +1818,123 @@ var EntityLabel;
|
|
|
1763
1818
|
}, 2500);
|
|
1764
1819
|
});
|
|
1765
1820
|
}
|
|
1821
|
+
/*
|
|
1822
|
+
* Builds one icon glyph from label options, with its background chip when asked for.
|
|
1823
|
+
*
|
|
1824
|
+
* The returned fit squares the chip off once the glyph has a measurable size.
|
|
1825
|
+
*/
|
|
1826
|
+
buildIcon(options, counter) {
|
|
1827
|
+
var _a, _b, _c;
|
|
1828
|
+
let fit;
|
|
1829
|
+
const icon = document.createElement("span");
|
|
1830
|
+
icon.className = "entity-label-icon";
|
|
1831
|
+
icon.style.cssText = `font-size:${(options.iconSize || 24) * ((_a = options.iconScale) !== null && _a !== void 0 ? _a : 1)}px;line-height:1;`;
|
|
1832
|
+
icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
|
|
1833
|
+
const iconColor = typeof options.iconColor === "string" ? options.iconColor : undefined;
|
|
1834
|
+
const iconBackground = options.iconBackground && options.iconScale !== 0;
|
|
1835
|
+
if (iconColor) {
|
|
1836
|
+
icon.style.color = iconColor;
|
|
1837
|
+
}
|
|
1838
|
+
if (options.iconUrl) {
|
|
1839
|
+
const image = document.createElement("img");
|
|
1840
|
+
image.draggable = false;
|
|
1841
|
+
image.alt = options.icon || "";
|
|
1842
|
+
const scale = (_b = options.iconScale) !== null && _b !== void 0 ? _b : 1;
|
|
1843
|
+
const size = options.iconSize;
|
|
1844
|
+
const svgNs = "http://www.w3.org/2000/svg";
|
|
1845
|
+
const tint = iconColor ? document.createElementNS(svgNs, "svg") : null;
|
|
1846
|
+
const painted = tint ? document.createElementNS(svgNs, "image") : null;
|
|
1847
|
+
const inked = tint ? document.createElementNS(svgNs, "rect") : null;
|
|
1848
|
+
if (tint) {
|
|
1849
|
+
const maskId = `entity-label-tint-${++tintCounter}`;
|
|
1850
|
+
const mask = document.createElementNS(svgNs, "mask");
|
|
1851
|
+
mask.setAttribute("id", maskId);
|
|
1852
|
+
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
|
1853
|
+
painted.setAttribute("href", options.iconUrl);
|
|
1854
|
+
painted.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
1855
|
+
mask.appendChild(painted);
|
|
1856
|
+
inked.setAttribute("fill", iconColor);
|
|
1857
|
+
inked.setAttribute("mask", `url(#${maskId})`);
|
|
1858
|
+
tint.append(mask, inked);
|
|
1859
|
+
tint.style.display = "block";
|
|
1860
|
+
icon.style.display = "inline-flex";
|
|
1861
|
+
icon.appendChild(tint);
|
|
1862
|
+
}
|
|
1863
|
+
const resize = () => {
|
|
1864
|
+
if (this.disposed || this.counter !== counter) {
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
const intrinsic = options.iconScale != null && size == null;
|
|
1868
|
+
const width = (intrinsic ? image.naturalWidth || 24 : size || 24) * scale;
|
|
1869
|
+
const height = (intrinsic ? image.naturalHeight || 24 : size || 24) * scale;
|
|
1870
|
+
if (tint) {
|
|
1871
|
+
tint.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
1872
|
+
for (const node of [tint, painted, inked]) {
|
|
1873
|
+
node.setAttribute("width", String(width));
|
|
1874
|
+
node.setAttribute("height", String(height));
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
else {
|
|
1878
|
+
image.style.width = `${width}px`;
|
|
1879
|
+
image.style.height = `${height}px`;
|
|
1880
|
+
}
|
|
1881
|
+
this.viewer.scene.requestRender();
|
|
1882
|
+
};
|
|
1883
|
+
image.onload = resize;
|
|
1884
|
+
resize();
|
|
1885
|
+
image.src = options.iconUrl;
|
|
1886
|
+
if (!tint) {
|
|
1887
|
+
icon.appendChild(image);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
else if (typeof options.iconClass === "string" && options.iconClass) {
|
|
1891
|
+
icon.className = `entity-label-icon ${options.iconClass}`;
|
|
1892
|
+
}
|
|
1893
|
+
else {
|
|
1894
|
+
icon.textContent = options.icon || "●";
|
|
1895
|
+
}
|
|
1896
|
+
if (iconBackground) {
|
|
1897
|
+
const color = Cesium.Color.fromCssColorString(iconColor || "white") || Cesium.Color.WHITE;
|
|
1898
|
+
const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
|
|
1899
|
+
const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Cesium.Color()) :
|
|
1900
|
+
color.brighten(ICON_BACKGROUND_BLEND, new Cesium.Color()));
|
|
1901
|
+
const size = (options.iconSize || 24) * ((_c = options.iconScale) !== null && _c !== void 0 ? _c : 1);
|
|
1902
|
+
icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
|
|
1903
|
+
icon.style.borderRadius = "50%";
|
|
1904
|
+
icon.style.padding = `${size / 3}px`;
|
|
1905
|
+
icon.style.display = "inline-flex";
|
|
1906
|
+
icon.style.alignItems = "center";
|
|
1907
|
+
icon.style.justifyContent = "center";
|
|
1908
|
+
icon.style.minWidth = `${size}px`;
|
|
1909
|
+
icon.style.minHeight = `${size}px`;
|
|
1910
|
+
icon.style.boxSizing = "content-box";
|
|
1911
|
+
let fitted = false;
|
|
1912
|
+
fit = () => {
|
|
1913
|
+
if (fitted) {
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
const padding = size / 3;
|
|
1917
|
+
const width = icon.offsetWidth - padding * 2;
|
|
1918
|
+
const height = icon.offsetHeight - padding * 2;
|
|
1919
|
+
if (width <= 0 || height <= 0) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
fitted = true;
|
|
1923
|
+
const longest = Math.max(width, height);
|
|
1924
|
+
const difference = (longest - Math.min(width, height)) / longest;
|
|
1925
|
+
if (difference > ICON_BACKGROUND_UNIFORM_TOLERANCE) {
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
icon.style.minWidth = `${longest}px`;
|
|
1929
|
+
icon.style.minHeight = `${longest}px`;
|
|
1930
|
+
this.viewer.scene.requestRender();
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
if (options.iconScale === 0) {
|
|
1934
|
+
icon.style.display = "none";
|
|
1935
|
+
}
|
|
1936
|
+
return { icon, fit };
|
|
1937
|
+
}
|
|
1766
1938
|
/**
|
|
1767
1939
|
* Removes label from the viewer and unsubscribes from events.
|
|
1768
1940
|
*/
|