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.
- package/dist/bruce-cesium.es5.js +332 -158
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +330 -156
- 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 +329 -155
- 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;
|
|
@@ -1365,8 +1384,10 @@ var EntityLabel;
|
|
|
1365
1384
|
const relative = Cesium.Cartesian3.subtract(this._pos3d, cameraPosition, new Cesium.Cartesian3());
|
|
1366
1385
|
const depth = Cesium.Cartesian3.dot(relative, camera.directionWC || camera.direction);
|
|
1367
1386
|
const distance = Cesium.Cartesian3.magnitude(relative);
|
|
1387
|
+
// If we're labelling from a temp label (mouse hover) don't drive opacity by cam distance.
|
|
1388
|
+
const hoverOnly = this.Transient && !this.Persistent;
|
|
1368
1389
|
if (!Number.isFinite(distance) || depth <= 0 || distance < ((_c = this.options.near) !== null && _c !== void 0 ? _c : 0) ||
|
|
1369
|
-
distance > ((_d = this.options.far) !== null && _d !== void 0 ? _d : Infinity) || (!this.prePositioned && distance >= MAX_DISTANCE)) {
|
|
1390
|
+
distance > ((_d = this.options.far) !== null && _d !== void 0 ? _d : Infinity) || (!this.prePositioned && !hoverOnly && distance >= MAX_DISTANCE)) {
|
|
1370
1391
|
return null;
|
|
1371
1392
|
}
|
|
1372
1393
|
const transforms = Cesium.SceneTransforms;
|
|
@@ -1407,9 +1428,13 @@ var EntityLabel;
|
|
|
1407
1428
|
this.cursorMotion = cursorMotion;
|
|
1408
1429
|
const cursorAmount = cursorMotion.amount;
|
|
1409
1430
|
leader = normalLeader || cursorAmount > 0;
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1431
|
+
// An icon keeps its callout centred on the glyph rather than following the cursor across it.
|
|
1432
|
+
const icon = measureIcon();
|
|
1433
|
+
const iconRing = icon ? Math.max(ICON_RING_MIN, icon.radius + ICON_RING_PADDING) : 0;
|
|
1434
|
+
const pivot = icon ? undefined : transientPivot;
|
|
1435
|
+
let x = ((_l = pivot === null || pivot === void 0 ? void 0 : pivot.x) !== null && _l !== void 0 ? _l : anchor.x) + (icon || pixelOffset).x;
|
|
1436
|
+
let y = ((_m = pivot === null || pivot === void 0 ? void 0 : pivot.y) !== null && _m !== void 0 ? _m : anchor.y) + (icon || pixelOffset).y;
|
|
1437
|
+
if (cursorAmount > 0 && !icon) {
|
|
1413
1438
|
// A leader-less label starts centred on its anchor; preserve that box as it lifts away.
|
|
1414
1439
|
if (!normalLeader) {
|
|
1415
1440
|
x -= ele.offsetWidth / layer.coordinates.scaleX / 2;
|
|
@@ -1448,9 +1473,11 @@ var EntityLabel;
|
|
|
1448
1473
|
leaderLength += (96 - leaderLength) * cursorAmount;
|
|
1449
1474
|
}
|
|
1450
1475
|
;
|
|
1476
|
+
// Measured from the ring, so the visible stem is the same length whatever the glyph's size.
|
|
1477
|
+
leaderLength += iconRing;
|
|
1451
1478
|
y = anchorY - leaderLength;
|
|
1452
1479
|
}
|
|
1453
|
-
else if (hover && !reused) {
|
|
1480
|
+
else if (hover && !reused && !icon) {
|
|
1454
1481
|
x = hover.x + pixelOffset.x;
|
|
1455
1482
|
y = hover.y + pixelOffset.y - 24;
|
|
1456
1483
|
}
|
|
@@ -1493,7 +1520,7 @@ var EntityLabel;
|
|
|
1493
1520
|
return null;
|
|
1494
1521
|
}
|
|
1495
1522
|
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 });
|
|
1496
|
-
const opacity = this.prePositioned || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
|
|
1523
|
+
const opacity = this.prePositioned || hoverOnly || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
|
|
1497
1524
|
return {
|
|
1498
1525
|
canFlip: leader && presentation !== "icon", hasLeader: leader,
|
|
1499
1526
|
textWrapped: ((sourceNaturalText === null || sourceNaturalText === void 0 ? void 0 : sourceNaturalText.offsetWidth) || 0) > TEXT_MAX_WIDTH,
|
|
@@ -1502,6 +1529,7 @@ var EntityLabel;
|
|
|
1502
1529
|
viewportWidth: this.viewer.canvas.clientWidth,
|
|
1503
1530
|
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
1531
|
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,
|
|
1532
|
+
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius,
|
|
1505
1533
|
// Priority shows the hovered member individually without dragging its cluster along.
|
|
1506
1534
|
opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
1507
1535
|
};
|
|
@@ -1509,7 +1537,7 @@ var EntityLabel;
|
|
|
1509
1537
|
let displacement = { x: 0, y: 0 };
|
|
1510
1538
|
let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
1511
1539
|
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;
|
|
1540
|
+
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
1541
|
const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
|
|
1514
1542
|
clickEntityIds = show ? placement.entityIds : [];
|
|
1515
1543
|
ele.style.visibility = show ? "visible" : "hidden";
|
|
@@ -1546,6 +1574,29 @@ var EntityLabel;
|
|
|
1546
1574
|
this.viewer.scene.requestRender();
|
|
1547
1575
|
}
|
|
1548
1576
|
fitIconBackground === null || fitIconBackground === void 0 ? void 0 : fitIconBackground();
|
|
1577
|
+
const stack = JSON.stringify(placement.icons || []);
|
|
1578
|
+
if (iconStack && stack !== displayedStack) {
|
|
1579
|
+
displayedStack = stack;
|
|
1580
|
+
const looks = ((_b = placement.icons) === null || _b === void 0 ? void 0 : _b.length) ? [iconLook(this.options), ...placement.icons] : [];
|
|
1581
|
+
// One small size for all, so no icon in the arrangement reads as more important than another.
|
|
1582
|
+
const size = (this.options.iconSize || 24) * ((_c = this.options.iconScale) !== null && _c !== void 0 ? _c : 1) * ICON_STACK_SCALE;
|
|
1583
|
+
const built = looks.map(look => this.buildIcon({ ...look, iconSize: size }, counter));
|
|
1584
|
+
// Two sit side by side; three make a triangle with the representative on top.
|
|
1585
|
+
iconStack.style.gridTemplateColumns = built.length === 3 ? "auto auto" : `repeat(${built.length}, auto)`;
|
|
1586
|
+
if (built.length === 3) {
|
|
1587
|
+
built[0].icon.style.gridColumn = "1 / span 2";
|
|
1588
|
+
}
|
|
1589
|
+
iconStack.replaceChildren(...built.map(({ icon }) => icon));
|
|
1590
|
+
iconStack.style.display = built.length ? "inline-grid" : "none";
|
|
1591
|
+
// The glyph stays measurable, since cluster membership is taken from its footprint.
|
|
1592
|
+
iconElement.style.visibility = built.length ? "hidden" : "";
|
|
1593
|
+
iconElement.style.position = built.length ? "absolute" : "relative";
|
|
1594
|
+
stackedFits = built.map(({ fit }) => fit).filter(Boolean);
|
|
1595
|
+
// The stack widens the label, which the next frame has to measure.
|
|
1596
|
+
this.viewer.scene.requestRender();
|
|
1597
|
+
}
|
|
1598
|
+
for (const fit of stackedFits)
|
|
1599
|
+
fit();
|
|
1549
1600
|
if (iconContainer) {
|
|
1550
1601
|
const badgeWidth = countElement.offsetWidth;
|
|
1551
1602
|
const iconWidth = iconElement.offsetWidth;
|
|
@@ -1561,37 +1612,44 @@ var EntityLabel;
|
|
|
1561
1612
|
// Summary and badge changes must fit on their first visible frame, including after resize.
|
|
1562
1613
|
const ring = this.options.anchorRing;
|
|
1563
1614
|
const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
|
|
1564
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
1565
|
-
const
|
|
1566
|
-
const
|
|
1615
|
+
(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));
|
|
1616
|
+
const iconRing = frame.iconRadius ? Math.max(ICON_RING_MIN, frame.iconRadius + ICON_RING_PADDING) : 0;
|
|
1617
|
+
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);
|
|
1618
|
+
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
1619
|
const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
|
|
1620
|
+
// Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
|
|
1621
|
+
const targetOuter = iconRing || targetRadius || 2.5;
|
|
1622
|
+
const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
|
|
1568
1623
|
const now = Date.now();
|
|
1569
|
-
const duration = (
|
|
1624
|
+
const duration = (_l = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _l !== void 0 ? _l : 200;
|
|
1570
1625
|
const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
|
|
1571
1626
|
let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
|
|
1572
1627
|
if (!ringAnimation) {
|
|
1573
|
-
|
|
1628
|
+
const outer = iconRing || 2.5;
|
|
1629
|
+
const inner = iconRing;
|
|
1630
|
+
ringAnimation = { target: outer, targetInner: inner, start: now, outer, inner, fromOuter: outer, fromInner: inner };
|
|
1574
1631
|
}
|
|
1575
1632
|
;
|
|
1576
1633
|
const animateRing = () => {
|
|
1577
1634
|
const progress = duration > 0 ? Math.min(1, Math.max(0, (now - ringAnimation.start) / duration)) : 1;
|
|
1578
1635
|
const eased = progress * progress * (3 - 2 * progress);
|
|
1579
|
-
ringAnimation.outer = ringAnimation.fromOuter + (
|
|
1580
|
-
ringAnimation.inner = ringAnimation.fromInner + (
|
|
1636
|
+
ringAnimation.outer = ringAnimation.fromOuter + (ringAnimation.target - ringAnimation.fromOuter) * eased;
|
|
1637
|
+
ringAnimation.inner = ringAnimation.fromInner + (ringAnimation.targetInner - ringAnimation.fromInner) * eased;
|
|
1581
1638
|
return progress;
|
|
1582
1639
|
};
|
|
1583
1640
|
animateRing();
|
|
1584
|
-
if (ringAnimation.target !==
|
|
1641
|
+
if (ringAnimation.target !== targetOuter || ringAnimation.targetInner !== targetInner) {
|
|
1585
1642
|
ringAnimation = {
|
|
1586
1643
|
...ringAnimation,
|
|
1587
|
-
target:
|
|
1644
|
+
target: targetOuter,
|
|
1645
|
+
targetInner,
|
|
1588
1646
|
start: now,
|
|
1589
1647
|
fromOuter: ringAnimation.outer,
|
|
1590
1648
|
fromInner: ringAnimation.inner
|
|
1591
1649
|
};
|
|
1592
1650
|
}
|
|
1593
1651
|
// Animate the hole and stem together; no CSS transition may lag behind a moving world anchor.
|
|
1594
|
-
if (animateRing() < 1 && (ringAnimation.fromOuter !==
|
|
1652
|
+
if (animateRing() < 1 && (ringAnimation.fromOuter !== targetOuter || ringAnimation.fromInner !== targetInner)) {
|
|
1595
1653
|
this.viewer.scene.requestRender();
|
|
1596
1654
|
}
|
|
1597
1655
|
this.ringAnimation = ringAnimation;
|
|
@@ -1606,19 +1664,19 @@ var EntityLabel;
|
|
|
1606
1664
|
if (leader && angled) {
|
|
1607
1665
|
// The declutter owns the side; the fit only says which one had the room to begin with.
|
|
1608
1666
|
const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
|
|
1609
|
-
const side = (
|
|
1667
|
+
const side = (_o = (_m = placement.textSide) !== null && _m !== void 0 ? _m : frame.leaderSide) !== null && _o !== void 0 ? _o : "right";
|
|
1610
1668
|
const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
|
|
1611
1669
|
position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
|
|
1612
1670
|
}
|
|
1613
1671
|
else if (leader && placement.textSide === "left") {
|
|
1614
1672
|
position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
|
|
1615
1673
|
}
|
|
1616
|
-
const gap = (
|
|
1674
|
+
const gap = (_q = (_p = this.options.pointerAvoidance) === null || _p === void 0 ? void 0 : _p.gap) !== null && _q !== void 0 ? _q : 12;
|
|
1617
1675
|
const containsPointer = (x, y, margin) => {
|
|
1618
1676
|
return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
|
|
1619
1677
|
};
|
|
1620
1678
|
const passive = !selectOnClick || !this.visualRegister;
|
|
1621
|
-
if (passive && ((
|
|
1679
|
+
if (passive && ((_r = this.options.pointerAvoidance) === null || _r === void 0 ? void 0 : _r.enabled) !== false && pointer &&
|
|
1622
1680
|
(containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
|
|
1623
1681
|
if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
|
|
1624
1682
|
// Choose against the original box and retain the offset until the pointer leaves both boxes.
|
|
@@ -1636,7 +1694,7 @@ var EntityLabel;
|
|
|
1636
1694
|
else {
|
|
1637
1695
|
displacement = { x: 0, y: 0 };
|
|
1638
1696
|
}
|
|
1639
|
-
const nudgeDuration = (
|
|
1697
|
+
const nudgeDuration = (_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.transitionDuration) !== null && _t !== void 0 ? _t : 200;
|
|
1640
1698
|
const animateNudge = () => {
|
|
1641
1699
|
const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
|
|
1642
1700
|
const eased = progress * progress * (3 - 2 * progress);
|
|
@@ -1652,7 +1710,7 @@ var EntityLabel;
|
|
|
1652
1710
|
fromY: nudge.y,
|
|
1653
1711
|
targetX: displacement.x,
|
|
1654
1712
|
targetY: displacement.y,
|
|
1655
|
-
start: now + ((
|
|
1713
|
+
start: now + ((_v = (_u = this.options.pointerAvoidance) === null || _u === void 0 ? void 0 : _u.delay) !== null && _v !== void 0 ? _v : 180)
|
|
1656
1714
|
};
|
|
1657
1715
|
}
|
|
1658
1716
|
if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
|
|
@@ -1662,9 +1720,8 @@ var EntityLabel;
|
|
|
1662
1720
|
position.y += nudge.y;
|
|
1663
1721
|
ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
|
|
1664
1722
|
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}`);
|
|
1723
|
+
for (const clickTarget of Array.from(content.querySelectorAll('[role="button"]'))) {
|
|
1724
|
+
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
1725
|
}
|
|
1669
1726
|
if (leader) {
|
|
1670
1727
|
const nodeRadius = ringAnimation.outer;
|
|
@@ -1704,13 +1761,13 @@ var EntityLabel;
|
|
|
1704
1761
|
// Keep the stem inside the 126-degree arc, away from antialiased endpoints.
|
|
1705
1762
|
const arcFraction = 0.35;
|
|
1706
1763
|
const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
|
|
1707
|
-
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((
|
|
1764
|
+
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_z = ringAnimation.arcOffset) !== null && _z !== void 0 ? _z : 0);
|
|
1708
1765
|
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 - ((
|
|
1766
|
+
const elapsed = Math.min(32, Math.max(0, now - ((_0 = ringAnimation.arcTime) !== null && _0 !== void 0 ? _0 : now)));
|
|
1710
1767
|
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
1711
1768
|
const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
|
|
1712
|
-
ringAnimation.arcOffset = approach((
|
|
1713
|
-
ringAnimation.arcOpen = approach((
|
|
1769
|
+
ringAnimation.arcOffset = approach((_1 = ringAnimation.arcOffset) !== null && _1 !== void 0 ? _1 : 0, targetOffset);
|
|
1770
|
+
ringAnimation.arcOpen = approach((_2 = ringAnimation.arcOpen) !== null && _2 !== void 0 ? _2 : 0, targetOpen);
|
|
1714
1771
|
ringAnimation.arcTime = now;
|
|
1715
1772
|
if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
|
|
1716
1773
|
this.viewer.scene.requestRender();
|
|
@@ -1763,6 +1820,123 @@ var EntityLabel;
|
|
|
1763
1820
|
}, 2500);
|
|
1764
1821
|
});
|
|
1765
1822
|
}
|
|
1823
|
+
/*
|
|
1824
|
+
* Builds one icon glyph from label options, with its background chip when asked for.
|
|
1825
|
+
*
|
|
1826
|
+
* The returned fit squares the chip off once the glyph has a measurable size.
|
|
1827
|
+
*/
|
|
1828
|
+
buildIcon(options, counter) {
|
|
1829
|
+
var _a, _b, _c;
|
|
1830
|
+
let fit;
|
|
1831
|
+
const icon = document.createElement("span");
|
|
1832
|
+
icon.className = "entity-label-icon";
|
|
1833
|
+
icon.style.cssText = `font-size:${(options.iconSize || 24) * ((_a = options.iconScale) !== null && _a !== void 0 ? _a : 1)}px;line-height:1;`;
|
|
1834
|
+
icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
|
|
1835
|
+
const iconColor = typeof options.iconColor === "string" ? options.iconColor : undefined;
|
|
1836
|
+
const iconBackground = options.iconBackground && options.iconScale !== 0;
|
|
1837
|
+
if (iconColor) {
|
|
1838
|
+
icon.style.color = iconColor;
|
|
1839
|
+
}
|
|
1840
|
+
if (options.iconUrl) {
|
|
1841
|
+
const image = document.createElement("img");
|
|
1842
|
+
image.draggable = false;
|
|
1843
|
+
image.alt = options.icon || "";
|
|
1844
|
+
const scale = (_b = options.iconScale) !== null && _b !== void 0 ? _b : 1;
|
|
1845
|
+
const size = options.iconSize;
|
|
1846
|
+
const svgNs = "http://www.w3.org/2000/svg";
|
|
1847
|
+
const tint = iconColor ? document.createElementNS(svgNs, "svg") : null;
|
|
1848
|
+
const painted = tint ? document.createElementNS(svgNs, "image") : null;
|
|
1849
|
+
const inked = tint ? document.createElementNS(svgNs, "rect") : null;
|
|
1850
|
+
if (tint) {
|
|
1851
|
+
const maskId = `entity-label-tint-${++tintCounter}`;
|
|
1852
|
+
const mask = document.createElementNS(svgNs, "mask");
|
|
1853
|
+
mask.setAttribute("id", maskId);
|
|
1854
|
+
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
|
1855
|
+
painted.setAttribute("href", options.iconUrl);
|
|
1856
|
+
painted.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
1857
|
+
mask.appendChild(painted);
|
|
1858
|
+
inked.setAttribute("fill", iconColor);
|
|
1859
|
+
inked.setAttribute("mask", `url(#${maskId})`);
|
|
1860
|
+
tint.append(mask, inked);
|
|
1861
|
+
tint.style.display = "block";
|
|
1862
|
+
icon.style.display = "inline-flex";
|
|
1863
|
+
icon.appendChild(tint);
|
|
1864
|
+
}
|
|
1865
|
+
const resize = () => {
|
|
1866
|
+
if (this.disposed || this.counter !== counter) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
const intrinsic = options.iconScale != null && size == null;
|
|
1870
|
+
const width = (intrinsic ? image.naturalWidth || 24 : size || 24) * scale;
|
|
1871
|
+
const height = (intrinsic ? image.naturalHeight || 24 : size || 24) * scale;
|
|
1872
|
+
if (tint) {
|
|
1873
|
+
tint.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
1874
|
+
for (const node of [tint, painted, inked]) {
|
|
1875
|
+
node.setAttribute("width", String(width));
|
|
1876
|
+
node.setAttribute("height", String(height));
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
else {
|
|
1880
|
+
image.style.width = `${width}px`;
|
|
1881
|
+
image.style.height = `${height}px`;
|
|
1882
|
+
}
|
|
1883
|
+
this.viewer.scene.requestRender();
|
|
1884
|
+
};
|
|
1885
|
+
image.onload = resize;
|
|
1886
|
+
resize();
|
|
1887
|
+
image.src = options.iconUrl;
|
|
1888
|
+
if (!tint) {
|
|
1889
|
+
icon.appendChild(image);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
else if (typeof options.iconClass === "string" && options.iconClass) {
|
|
1893
|
+
icon.className = `entity-label-icon ${options.iconClass}`;
|
|
1894
|
+
}
|
|
1895
|
+
else {
|
|
1896
|
+
icon.textContent = options.icon || "●";
|
|
1897
|
+
}
|
|
1898
|
+
if (iconBackground) {
|
|
1899
|
+
const color = Cesium.Color.fromCssColorString(iconColor || "white") || Cesium.Color.WHITE;
|
|
1900
|
+
const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
|
|
1901
|
+
const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Cesium.Color()) :
|
|
1902
|
+
color.brighten(ICON_BACKGROUND_BLEND, new Cesium.Color()));
|
|
1903
|
+
const size = (options.iconSize || 24) * ((_c = options.iconScale) !== null && _c !== void 0 ? _c : 1);
|
|
1904
|
+
icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
|
|
1905
|
+
icon.style.borderRadius = "50%";
|
|
1906
|
+
icon.style.padding = `${size / 3}px`;
|
|
1907
|
+
icon.style.display = "inline-flex";
|
|
1908
|
+
icon.style.alignItems = "center";
|
|
1909
|
+
icon.style.justifyContent = "center";
|
|
1910
|
+
icon.style.minWidth = `${size}px`;
|
|
1911
|
+
icon.style.minHeight = `${size}px`;
|
|
1912
|
+
icon.style.boxSizing = "content-box";
|
|
1913
|
+
let fitted = false;
|
|
1914
|
+
fit = () => {
|
|
1915
|
+
if (fitted) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const padding = size / 3;
|
|
1919
|
+
const width = icon.offsetWidth - padding * 2;
|
|
1920
|
+
const height = icon.offsetHeight - padding * 2;
|
|
1921
|
+
if (width <= 0 || height <= 0) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
fitted = true;
|
|
1925
|
+
const longest = Math.max(width, height);
|
|
1926
|
+
const difference = (longest - Math.min(width, height)) / longest;
|
|
1927
|
+
if (difference > ICON_BACKGROUND_UNIFORM_TOLERANCE) {
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
icon.style.minWidth = `${longest}px`;
|
|
1931
|
+
icon.style.minHeight = `${longest}px`;
|
|
1932
|
+
this.viewer.scene.requestRender();
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
if (options.iconScale === 0) {
|
|
1936
|
+
icon.style.display = "none";
|
|
1937
|
+
}
|
|
1938
|
+
return { icon, fit };
|
|
1939
|
+
}
|
|
1766
1940
|
/**
|
|
1767
1941
|
* Removes label from the viewer and unsubscribes from events.
|
|
1768
1942
|
*/
|