bruce-cesium 7.3.9 → 7.4.1
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 +2655 -603
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +2654 -600
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +8 -2
- package/dist/lib/bruce-cesium.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label-group.js +538 -0
- package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -0
- package/dist/lib/rendering/render-managers/common/entity-label.js +1213 -155
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/point-clustering-core.js +85 -0
- package/dist/lib/rendering/render-managers/common/point-clustering-core.js.map +1 -0
- package/dist/lib/rendering/render-managers/common/point-clustering.js +566 -419
- package/dist/lib/rendering/render-managers/common/point-clustering.js.map +1 -1
- package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +1 -1
- package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +2 -2
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
- package/dist/lib/rendering/visuals-register.js +273 -24
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +4 -1
- package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +109 -0
- package/dist/types/rendering/render-managers/common/entity-label.d.ts +138 -1
- package/dist/types/rendering/render-managers/common/point-clustering-core.d.ts +17 -0
- package/dist/types/rendering/render-managers/common/point-clustering.d.ts +37 -72
- package/dist/types/rendering/visuals-register.d.ts +43 -2
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ const js_utils_2 = require("../../../internal/js-utils");
|
|
|
8
8
|
const drawing_utils_1 = require("../../../utils/drawing-utils");
|
|
9
9
|
const measure_utils_1 = require("../../../utils/measure-utils");
|
|
10
10
|
const bruce_models_1 = require("bruce-models");
|
|
11
|
+
const point_clustering_core_1 = require("./point-clustering-core");
|
|
11
12
|
/**
|
|
12
13
|
* Returns cesium property's value.
|
|
13
14
|
* This will check if it's one that changes over time, or just a fixed value.
|
|
@@ -30,10 +31,565 @@ function getValue(viewer, obj) {
|
|
|
30
31
|
*/
|
|
31
32
|
var EntityLabel;
|
|
32
33
|
(function (EntityLabel) {
|
|
34
|
+
const ICON_BACKGROUND_BLEND = 0.8;
|
|
35
|
+
const ICON_BACKGROUND_ALPHA = 0.45;
|
|
36
|
+
const TEXT_MAX_WIDTH = 200;
|
|
33
37
|
// Key where the label instance is stored within the cesium entity.
|
|
34
38
|
// Eg: rego[ATTACH_KEY] = new EntityLabel(...);
|
|
35
39
|
EntityLabel.ATTACH_KEY = "nextspace-navigator-entity-label";
|
|
40
|
+
/*
|
|
41
|
+
* Indexes screen rectangles so sparse scenes do not compare every label with every other label.
|
|
42
|
+
*/
|
|
43
|
+
class ScreenIndex {
|
|
44
|
+
constructor() {
|
|
45
|
+
this.cells = new Map();
|
|
46
|
+
this.large = [];
|
|
47
|
+
this.all = [];
|
|
48
|
+
}
|
|
49
|
+
keys(frame, padding = 0) {
|
|
50
|
+
const x0 = Math.floor((frame.x - padding) / 64);
|
|
51
|
+
const y0 = Math.floor((frame.y - padding) / 64);
|
|
52
|
+
const x1 = Math.floor((frame.x + frame.width + padding) / 64);
|
|
53
|
+
const y1 = Math.floor((frame.y + frame.height + padding) / 64);
|
|
54
|
+
if ((x1 - x0 + 1) * (y1 - y0 + 1) > 4096) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const keys = [];
|
|
58
|
+
for (let x = x0; x <= x1; x++) {
|
|
59
|
+
for (let y = y0; y <= y1; y++) {
|
|
60
|
+
keys.push(`${x},${y}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return keys;
|
|
64
|
+
}
|
|
65
|
+
Add(frame) {
|
|
66
|
+
this.all.push(frame);
|
|
67
|
+
const keys = this.keys(frame);
|
|
68
|
+
if (!keys) {
|
|
69
|
+
this.large.push(frame);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const key of keys) {
|
|
73
|
+
if (!this.cells.has(key)) {
|
|
74
|
+
this.cells.set(key, []);
|
|
75
|
+
}
|
|
76
|
+
this.cells.get(key).push(frame);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
Query(frame, padding = 0) {
|
|
80
|
+
const keys = this.keys(frame, padding);
|
|
81
|
+
const found = new Set(keys ? this.large : this.all);
|
|
82
|
+
for (const key of keys || []) {
|
|
83
|
+
for (const item of this.cells.get(key) || []) {
|
|
84
|
+
found.add(item);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return (Array.from(found).filter(other => frame.x <= other.x + other.width + padding && frame.x + frame.width + padding >= other.x &&
|
|
88
|
+
frame.y <= other.y + other.height + padding && frame.y + frame.height + padding >= other.y));
|
|
89
|
+
}
|
|
90
|
+
HasOverlap(frame) {
|
|
91
|
+
var _a;
|
|
92
|
+
const intersects = (other) => (frame.x <= other.x + other.width && frame.x + frame.width >= other.x &&
|
|
93
|
+
frame.y <= other.y + other.height && frame.y + frame.height >= other.y);
|
|
94
|
+
const keys = this.keys(frame);
|
|
95
|
+
if ((keys ? this.large : this.all).some(intersects)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
for (const key of keys || []) {
|
|
99
|
+
if ((_a = this.cells.get(key)) === null || _a === void 0 ? void 0 : _a.some(intersects)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/*
|
|
107
|
+
* Includes the stem in collision checks without treating two crossing stems as text occlusion.
|
|
108
|
+
*/
|
|
109
|
+
function leaderBounds(frame) {
|
|
110
|
+
if (!frame.hasLeader || !Number.isFinite(frame.anchorX) || !Number.isFinite(frame.anchorY)) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const endX = Math.max(frame.x, Math.min(frame.anchorX, frame.x + frame.width));
|
|
114
|
+
const endY = Math.max(frame.y, Math.min(frame.anchorY, frame.y + frame.height));
|
|
115
|
+
if (Math.hypot(frame.anchorX - endX, frame.anchorY - endY) < .01) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
...frame,
|
|
120
|
+
x: Math.min(frame.anchorX, endX) - 1,
|
|
121
|
+
y: Math.min(frame.anchorY, endY) + .01,
|
|
122
|
+
width: Math.abs(frame.anchorX - endX) + 2,
|
|
123
|
+
height: Math.max(0, Math.abs(frame.anchorY - endY) - .02)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/*
|
|
127
|
+
* Remembers sides, never rendered positions: its own output cannot trigger another layout pass.
|
|
128
|
+
*/
|
|
129
|
+
class PlacementMemory {
|
|
130
|
+
constructor() {
|
|
131
|
+
this.previous = new Map();
|
|
132
|
+
this.nextOrder = 0;
|
|
133
|
+
}
|
|
134
|
+
Resolve(frames) {
|
|
135
|
+
var _a, _b;
|
|
136
|
+
const live = new Set(frames.map(frame => frame.id));
|
|
137
|
+
const moved = (frame) => {
|
|
138
|
+
var _a;
|
|
139
|
+
const old = (_a = this.previous.get(frame.id)) === null || _a === void 0 ? void 0 : _a.frame;
|
|
140
|
+
return (!old ||
|
|
141
|
+
old.transient !== frame.transient || old.canFlip !== frame.canFlip ||
|
|
142
|
+
Math.hypot(frame.x - old.x, frame.y - old.y) >= 24 ||
|
|
143
|
+
Math.abs(frame.width - old.width) >= 8 || Math.abs(frame.height - old.height) >= 8);
|
|
144
|
+
};
|
|
145
|
+
const persistent = frames.filter(frame => !frame.transient);
|
|
146
|
+
const changes = (persistent.filter(moved).length +
|
|
147
|
+
Array.from(this.previous.values()).filter(old => !old.frame.transient && !live.has(old.frame.id)).length);
|
|
148
|
+
// A hover or one arrival cannot rearrange incumbents; a substantial batch/camera change can.
|
|
149
|
+
const restructure = this.previous.size > 0 && changes >= Math.max(2, Math.ceil(persistent.length * 0.3));
|
|
150
|
+
const fixed = (frame) => !restructure && !frame.transient && this.previous.has(frame.id) && !moved(frame);
|
|
151
|
+
const ordered = ([...frames].sort((a, b) => {
|
|
152
|
+
var _a, _b, _c, _d;
|
|
153
|
+
return Number(fixed(b)) - Number(fixed(a)) ||
|
|
154
|
+
Number(Boolean(a.transient)) - Number(Boolean(b.transient)) ||
|
|
155
|
+
((_b = (_a = this.previous.get(a.id)) === null || _a === void 0 ? void 0 : _a.order) !== null && _b !== void 0 ? _b : Infinity) - ((_d = (_c = this.previous.get(b.id)) === null || _c === void 0 ? void 0 : _c.order) !== null && _d !== void 0 ? _d : Infinity) ||
|
|
156
|
+
a.depth - b.depth || a.id.localeCompare(b.id);
|
|
157
|
+
}));
|
|
158
|
+
const text = new ScreenIndex(), leaders = new ScreenIndex();
|
|
159
|
+
const placed = new Map();
|
|
160
|
+
const next = new Map();
|
|
161
|
+
const candidate = (frame, side) => {
|
|
162
|
+
var _a;
|
|
163
|
+
return (side === "left" && frame.canFlip ?
|
|
164
|
+
{ ...frame, x: Math.max(0, Math.min(frame.anchorX - frame.width, ((_a = frame.viewportWidth) !== null && _a !== void 0 ? _a : Infinity) - frame.width)) } :
|
|
165
|
+
frame);
|
|
166
|
+
};
|
|
167
|
+
const score = (frame, padding = 0) => {
|
|
168
|
+
const box = {
|
|
169
|
+
...frame,
|
|
170
|
+
x: frame.x - padding + .01,
|
|
171
|
+
y: frame.y - padding + .01,
|
|
172
|
+
width: Math.max(0, frame.width + padding * 2 - .02),
|
|
173
|
+
height: Math.max(0, frame.height + padding * 2 - .02)
|
|
174
|
+
};
|
|
175
|
+
const stem = leaderBounds(frame);
|
|
176
|
+
return (Number(text.HasOverlap(box)) * 4 + Number(leaders.HasOverlap(box)) +
|
|
177
|
+
Number(Boolean(stem && text.HasOverlap(stem))));
|
|
178
|
+
};
|
|
179
|
+
for (const frame of ordered) {
|
|
180
|
+
const old = this.previous.get(frame.id);
|
|
181
|
+
let side = frame.canFlip ? (_a = old === null || old === void 0 ? void 0 : old.side) !== null && _a !== void 0 ? _a : "right" : "right";
|
|
182
|
+
let chosen = candidate(frame, side);
|
|
183
|
+
if (!fixed(frame) && frame.canFlip) {
|
|
184
|
+
const otherSide = side === "left" ? "right" : "left";
|
|
185
|
+
const other = candidate(frame, otherSide);
|
|
186
|
+
// An established side only changes when the alternative has a little breathing room.
|
|
187
|
+
if (score(other, old ? 4 : 0) < score(chosen)) {
|
|
188
|
+
side = otherSide;
|
|
189
|
+
chosen = other;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
placed.set(frame.id, { frame: chosen, side });
|
|
193
|
+
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++ });
|
|
194
|
+
text.Add(chosen);
|
|
195
|
+
const stem = leaderBounds(chosen);
|
|
196
|
+
if (stem) {
|
|
197
|
+
leaders.Add(stem);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
this.previous = next;
|
|
201
|
+
return placed;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
EntityLabel.PlacementMemory = PlacementMemory;
|
|
205
|
+
/*
|
|
206
|
+
* Prefer declared semantics; only infers from recognisable glyphs and filename tokens, never image pixels.
|
|
207
|
+
*/
|
|
208
|
+
EntityLabel.IconPriority = point_clustering_core_1.GetClusterIconPriority;
|
|
209
|
+
/*
|
|
210
|
+
* Resolves clustering and stable text placement before depth fading; highlighted labels receive priority.
|
|
211
|
+
*/
|
|
212
|
+
function Layout(input, memory = new PlacementMemory()) {
|
|
213
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
214
|
+
const frames = input.filter(frame => [frame.x, frame.y, frame.width, frame.height, frame.depth].every(Number.isFinite))
|
|
215
|
+
.sort((a, b) => (b.priority || 0) - (a.priority || 0) || a.depth - b.depth || a.id.localeCompare(b.id));
|
|
216
|
+
const result = new Map();
|
|
217
|
+
const entityKeys = new Set();
|
|
218
|
+
const groups = new Map();
|
|
219
|
+
for (const frame of frames) {
|
|
220
|
+
const duplicate = frame.entityKey && entityKeys.has(frame.entityKey);
|
|
221
|
+
result.set(frame.id, { frame, visible: !duplicate, opacity: frame.opacity, zIndex: 0, members: [], entityIds: frame.entityId ? [frame.entityId] : [] });
|
|
222
|
+
if (duplicate) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (frame.entityKey)
|
|
226
|
+
entityKeys.add(frame.entityKey);
|
|
227
|
+
if (!((_b = (_a = frame.options) === null || _a === void 0 ? void 0 : _a.cluster) === null || _b === void 0 ? void 0 : _b.enabled) || frame.priority > 0) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const key = JSON.stringify([frame.group || "", frame.options.presentation || "text"]);
|
|
231
|
+
if (!groups.has(key)) {
|
|
232
|
+
groups.set(key, []);
|
|
233
|
+
}
|
|
234
|
+
groups.get(key).push(frame);
|
|
235
|
+
}
|
|
236
|
+
for (const group of groups.values()) {
|
|
237
|
+
// Summary and badge dimensions are output, never the next frame's membership input.
|
|
238
|
+
const candidates = group.map(frame => frame.extendedGeometry && frame.canFlip &&
|
|
239
|
+
frame.options.cluster.pixelRange == null ? { ...frame, x: frame.anchorX, y: frame.anchorY, width: 1, height: 1 } :
|
|
240
|
+
frame.clusterBounds ? { ...frame, ...frame.clusterBounds } : frame);
|
|
241
|
+
const index = new ScreenIndex();
|
|
242
|
+
for (const frame of candidates) {
|
|
243
|
+
index.Add(frame);
|
|
244
|
+
}
|
|
245
|
+
const clusters = (0, point_clustering_core_1.ClusterItems)({
|
|
246
|
+
items: candidates,
|
|
247
|
+
id: frame => frame.id,
|
|
248
|
+
minSize: group[0].options.cluster.minSize,
|
|
249
|
+
neighbours: frame => {
|
|
250
|
+
var _a;
|
|
251
|
+
return (index.Query(frame, (_a = frame.options.cluster.pixelRange) !== null && _a !== void 0 ? _a : (frame.options.presentation === "icon" ? 24 : 8)));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
for (const cluster of clusters) {
|
|
255
|
+
let chosen = cluster.representative;
|
|
256
|
+
if ((chosen.options.presentation || "text") !== "text" && chosen.options.cluster.representative !== "nearest") {
|
|
257
|
+
let priority = EntityLabel.IconPriority(chosen.options);
|
|
258
|
+
for (const member of cluster.members) {
|
|
259
|
+
const candidate = EntityLabel.IconPriority(member.options);
|
|
260
|
+
if (candidate > priority) {
|
|
261
|
+
chosen = member;
|
|
262
|
+
priority = candidate;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const representative = result.get(chosen.id);
|
|
267
|
+
representative.members = cluster.members.map(frame => frame.id);
|
|
268
|
+
representative.entityIds = Array.from(new Set(cluster.members.map(frame => frame.entityId).filter(Boolean)));
|
|
269
|
+
if (chosen.options.presentation !== "icon") {
|
|
270
|
+
const values = new Set();
|
|
271
|
+
const stableMembers = [...cluster.members].sort((a, b) => (a.entityKey || a.entityId || a.id).localeCompare(b.entityKey || b.entityId || b.id));
|
|
272
|
+
for (const member of stableMembers) {
|
|
273
|
+
const value = ((_e = (_c = member.text) !== null && _c !== void 0 ? _c : (_d = member.options) === null || _d === void 0 ? void 0 : _d.text) !== null && _e !== void 0 ? _e : "").replace(/\s+/g, " ").trim();
|
|
274
|
+
if (value) {
|
|
275
|
+
values.add(value);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (values.size > 1) {
|
|
279
|
+
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()); });
|
|
280
|
+
if (description) {
|
|
281
|
+
representative.text = ((_f = description.text) !== null && _f !== void 0 ? _f : description.options.text).trim();
|
|
282
|
+
representative.summaryMore = true;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const rows = Array.from(values).slice(0, 2);
|
|
286
|
+
if (values.size > 2)
|
|
287
|
+
rows.push(`+${values.size - 2} more…`);
|
|
288
|
+
representative.text = rows.join("\n");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (const frame of cluster.members) {
|
|
293
|
+
if (frame !== chosen)
|
|
294
|
+
result.get(frame.id).visible = false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const placed = memory.Resolve(frames.filter(frame => result.get(frame.id).visible));
|
|
299
|
+
const foreground = new ScreenIndex(), foregroundLeaders = new ScreenIndex();
|
|
300
|
+
let zIndex = frames.length + 1;
|
|
301
|
+
for (const original of frames) {
|
|
302
|
+
const placement = result.get(original.id);
|
|
303
|
+
if (!placement.visible) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const chosen = placed.get(original.id);
|
|
307
|
+
const frame = placement.frame = chosen.frame;
|
|
308
|
+
placement.textSide = chosen.side;
|
|
309
|
+
const stem = leaderBounds(frame);
|
|
310
|
+
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) };
|
|
311
|
+
const overlap = foreground.HasOverlap(box) || foregroundLeaders.HasOverlap(box) || Boolean(stem && foreground.HasOverlap(stem));
|
|
312
|
+
if (overlap) {
|
|
313
|
+
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));
|
|
314
|
+
}
|
|
315
|
+
;
|
|
316
|
+
placement.zIndex = zIndex--;
|
|
317
|
+
foreground.Add(frame);
|
|
318
|
+
if (stem) {
|
|
319
|
+
foregroundLeaders.Add(stem);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return Array.from(result.values());
|
|
323
|
+
}
|
|
324
|
+
EntityLabel.Layout = Layout;
|
|
325
|
+
const layers = new WeakMap();
|
|
326
|
+
const containers = new WeakMap();
|
|
327
|
+
/*
|
|
328
|
+
* Moves the shared overlay without recreating labels or resolving their sources.
|
|
329
|
+
*/
|
|
330
|
+
function SetContainer(viewer, container) {
|
|
331
|
+
const layer = layers.get(viewer);
|
|
332
|
+
if (container && (container === (layer === null || layer === void 0 ? void 0 : layer.element) || (layer === null || layer === void 0 ? void 0 : layer.element.contains(container)))) {
|
|
333
|
+
throw new Error("Label container cannot be inside the label overlay");
|
|
334
|
+
}
|
|
335
|
+
if (container) {
|
|
336
|
+
containers.set(viewer, container);
|
|
337
|
+
}
|
|
338
|
+
else
|
|
339
|
+
containers.delete(viewer);
|
|
340
|
+
layer === null || layer === void 0 ? void 0 : layer.Move();
|
|
341
|
+
}
|
|
342
|
+
EntityLabel.SetContainer = SetContainer;
|
|
343
|
+
/*
|
|
344
|
+
* Owns one scene callback per viewer and separates projection/measurement from DOM writes.
|
|
345
|
+
*/
|
|
346
|
+
class Layer {
|
|
347
|
+
constructor(viewer) {
|
|
348
|
+
this.viewer = viewer;
|
|
349
|
+
this.element = document.createElement("div");
|
|
350
|
+
this.coordinates = { x: 0, y: 0, scaleX: 1, scaleY: 1 };
|
|
351
|
+
this.participants = new Set();
|
|
352
|
+
this.placementMemory = new PlacementMemory();
|
|
353
|
+
this.forwardingWheel = false;
|
|
354
|
+
this.pendingDrag = null;
|
|
355
|
+
this.TrackPointer = (event) => {
|
|
356
|
+
var _a, _b;
|
|
357
|
+
if (event.pointerType && event.pointerType !== "mouse" && event.pointerType !== "pen")
|
|
358
|
+
return;
|
|
359
|
+
const rect = (_b = (_a = this.viewer.canvas).getBoundingClientRect) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
360
|
+
if (!rect || event.clientX < rect.left || event.clientX >= rect.right || event.clientY < rect.top || event.clientY >= rect.bottom) {
|
|
361
|
+
if (this.pointerClient)
|
|
362
|
+
this.ClearPointer();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.pointerClient = { x: event.clientX, y: event.clientY };
|
|
366
|
+
this.viewer.scene.requestRender();
|
|
367
|
+
};
|
|
368
|
+
this.PointerOut = (event) => { if (!event.relatedTarget)
|
|
369
|
+
this.ClearPointer(); };
|
|
370
|
+
this.ClearPointer = () => {
|
|
371
|
+
this.pointerClient = undefined;
|
|
372
|
+
this.pointer = undefined;
|
|
373
|
+
if (!this.viewer.isDestroyed()) {
|
|
374
|
+
this.viewer.scene.requestRender();
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
this.PreventNativeDrag = (event) => {
|
|
378
|
+
event.preventDefault();
|
|
379
|
+
};
|
|
380
|
+
this.BeginDrag = (event) => {
|
|
381
|
+
var _a, _b, _c;
|
|
382
|
+
this.EndPendingDrag();
|
|
383
|
+
const detection = Cesium.FeatureDetection;
|
|
384
|
+
const target = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest) === null || _b === void 0 ? void 0 : _b.call(_a, '.entity-label [role="button"]');
|
|
385
|
+
const canvas = this.viewer.canvas;
|
|
386
|
+
if (event.defaultPrevented || event.button !== 0 || event.pointerType !== "mouse" ||
|
|
387
|
+
((_c = detection.supportsPointerEvents) === null || _c === void 0 ? void 0 : _c.call(detection)) === false || !canvas.setPointerCapture || this.viewer.isDestroyed() ||
|
|
388
|
+
!target || !this.element.contains(target) ||
|
|
389
|
+
this.element.ownerDocument.defaultView.getComputedStyle(target).visibility !== "visible") {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const bounds = canvas.getBoundingClientRect();
|
|
393
|
+
if (event.clientX < bounds.left || event.clientX >= bounds.right || event.clientY < bounds.top || event.clientY >= bounds.bottom) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
this.pendingDrag = event;
|
|
397
|
+
const doc = this.element.ownerDocument;
|
|
398
|
+
doc.addEventListener("pointermove", this.ContinueDrag, true);
|
|
399
|
+
doc.addEventListener("pointerup", this.FinishPendingDrag, true);
|
|
400
|
+
doc.addEventListener("pointercancel", this.FinishPendingDrag, true);
|
|
401
|
+
doc.defaultView.addEventListener("blur", this.EndPendingDrag);
|
|
402
|
+
};
|
|
403
|
+
this.FinishPendingDrag = (event) => {
|
|
404
|
+
var _a;
|
|
405
|
+
if (event.pointerId === ((_a = this.pendingDrag) === null || _a === void 0 ? void 0 : _a.pointerId)) {
|
|
406
|
+
this.EndPendingDrag();
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
this.EndPendingDrag = () => {
|
|
410
|
+
this.pendingDrag = null;
|
|
411
|
+
const doc = this.element.ownerDocument;
|
|
412
|
+
doc.removeEventListener("pointermove", this.ContinueDrag, true);
|
|
413
|
+
doc.removeEventListener("pointerup", this.FinishPendingDrag, true);
|
|
414
|
+
doc.removeEventListener("pointercancel", this.FinishPendingDrag, true);
|
|
415
|
+
doc.defaultView.removeEventListener("blur", this.EndPendingDrag);
|
|
416
|
+
};
|
|
417
|
+
this.ContinueDrag = (event) => {
|
|
418
|
+
const start = this.pendingDrag;
|
|
419
|
+
if (!start || event.pointerId !== start.pointerId) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (!(event.buttons & 1) || this.viewer.isDestroyed() || !this.element.contains(start.target)) {
|
|
423
|
+
this.EndPendingDrag();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
// Keep ordinary clicks local; only a deliberate drag starts Cesium's camera gesture.
|
|
427
|
+
if (Math.hypot(event.clientX - start.clientX, event.clientY - start.clientY) < 6) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
this.EndPendingDrag();
|
|
431
|
+
const canvas = this.viewer.canvas;
|
|
432
|
+
try {
|
|
433
|
+
canvas.setPointerCapture(start.pointerId);
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// A cancelled pointer or unsupported capture still cannot start native image dragging.
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const win = this.element.ownerDocument.defaultView;
|
|
440
|
+
const copy = (source) => ({
|
|
441
|
+
bubbles: true, cancelable: true, pointerId: source.pointerId, pointerType: source.pointerType,
|
|
442
|
+
isPrimary: source.isPrimary, button: source.button, buttons: source.buttons,
|
|
443
|
+
clientX: source.clientX, clientY: source.clientY, screenX: source.screenX, screenY: source.screenY,
|
|
444
|
+
ctrlKey: source.ctrlKey, shiftKey: source.shiftKey, altKey: source.altKey, metaKey: source.metaKey
|
|
445
|
+
});
|
|
446
|
+
// Capture routes subsequent real move/up events to Cesium, including releases outside the label.
|
|
447
|
+
canvas.dispatchEvent(new win.PointerEvent("pointerdown", copy(start)));
|
|
448
|
+
canvas.dispatchEvent(new win.PointerEvent("pointermove", copy(event)));
|
|
449
|
+
event.preventDefault();
|
|
450
|
+
event.stopPropagation();
|
|
451
|
+
this.viewer.scene.requestRender();
|
|
452
|
+
};
|
|
453
|
+
this.ForwardWheel = (event) => {
|
|
454
|
+
var _a, _b, _c;
|
|
455
|
+
if (this.forwardingWheel || event.defaultPrevented || this.viewer.isDestroyed()) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const target = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest) === null || _b === void 0 ? void 0 : _b.call(_a, '.entity-label [role="button"]');
|
|
459
|
+
const canvas = this.viewer.canvas;
|
|
460
|
+
const win = this.element.ownerDocument.defaultView;
|
|
461
|
+
if (!target || !this.element.contains(target) || win.getComputedStyle(target).visibility !== "visible") {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const bounds = canvas.getBoundingClientRect();
|
|
465
|
+
if (event.clientX < bounds.left || event.clientX >= bounds.right || event.clientY < bounds.top || event.clientY >= bounds.bottom) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const position = {
|
|
469
|
+
bubbles: true, cancelable: true, clientX: event.clientX, clientY: event.clientY,
|
|
470
|
+
screenX: event.screenX, screenY: event.screenY, buttons: event.buttons,
|
|
471
|
+
ctrlKey: event.ctrlKey, shiftKey: event.shiftKey, altKey: event.altKey, metaKey: event.metaKey,
|
|
472
|
+
pointerId: 1, pointerType: "mouse", isPrimary: true
|
|
473
|
+
};
|
|
474
|
+
this.forwardingWheel = true;
|
|
475
|
+
try {
|
|
476
|
+
// Cesium's wheel zoom uses its last mouse position, not the wheel event's coordinates.
|
|
477
|
+
const detection = Cesium.FeatureDetection;
|
|
478
|
+
// Older Cesium versions opt Firefox out even when the browser exposes PointerEvent.
|
|
479
|
+
canvas.dispatchEvent(((_c = detection.supportsPointerEvents) === null || _c === void 0 ? void 0 : _c.call(detection)) !== false && typeof win.PointerEvent === "function" ?
|
|
480
|
+
new win.PointerEvent("pointermove", position) : new win.MouseEvent("mousemove", position));
|
|
481
|
+
canvas.dispatchEvent(new win.WheelEvent("wheel", {
|
|
482
|
+
...position, deltaX: event.deltaX, deltaY: event.deltaY, deltaZ: event.deltaZ, deltaMode: event.deltaMode
|
|
483
|
+
}));
|
|
484
|
+
this.viewer.scene.requestRender();
|
|
485
|
+
event.preventDefault();
|
|
486
|
+
event.stopPropagation();
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
this.forwardingWheel = false;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
this.element.className = "entity-label-layer";
|
|
493
|
+
// Depth ordering belongs inside the map overlay, never in the application's UI stack.
|
|
494
|
+
this.element.style.cssText = "position:absolute;inset:0;z-index:0;isolation:isolate;pointer-events:none;";
|
|
495
|
+
this.Move();
|
|
496
|
+
this.element.addEventListener("wheel", this.ForwardWheel, { passive: false });
|
|
497
|
+
this.element.addEventListener("pointerdown", this.BeginDrag, true);
|
|
498
|
+
this.element.addEventListener("dragstart", this.PreventNativeDrag);
|
|
499
|
+
this.element.ownerDocument.addEventListener("pointermove", this.TrackPointer, true);
|
|
500
|
+
this.element.ownerDocument.addEventListener("pointerout", this.PointerOut, true);
|
|
501
|
+
this.element.ownerDocument.defaultView.addEventListener("blur", this.ClearPointer);
|
|
502
|
+
this.remove = viewer.scene.postUpdate.addEventListener(() => this.Update());
|
|
503
|
+
}
|
|
504
|
+
Move() {
|
|
505
|
+
(containers.get(this.viewer) || this.viewer.container).appendChild(this.element);
|
|
506
|
+
this.Update();
|
|
507
|
+
this.viewer.scene.requestRender();
|
|
508
|
+
}
|
|
509
|
+
MeasureCoordinates() {
|
|
510
|
+
var _a;
|
|
511
|
+
const canvas = this.viewer.canvas;
|
|
512
|
+
const canvasRect = (_a = canvas.getBoundingClientRect) === null || _a === void 0 ? void 0 : _a.call(canvas);
|
|
513
|
+
const rect = this.element.getBoundingClientRect();
|
|
514
|
+
const scaleX = rect.width / this.element.offsetWidth || 1;
|
|
515
|
+
const scaleY = rect.height / this.element.offsetHeight || 1;
|
|
516
|
+
return {
|
|
517
|
+
x: (((canvasRect === null || canvasRect === void 0 ? void 0 : canvasRect.left) || 0) - rect.left) / scaleX,
|
|
518
|
+
y: (((canvasRect === null || canvasRect === void 0 ? void 0 : canvasRect.top) || 0) - rect.top) / scaleY,
|
|
519
|
+
scaleX: ((canvasRect === null || canvasRect === void 0 ? void 0 : canvasRect.width) / canvas.clientWidth || 1) / scaleX,
|
|
520
|
+
scaleY: ((canvasRect === null || canvasRect === void 0 ? void 0 : canvasRect.height) / canvas.clientHeight || 1) / scaleY
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
Add(participant) {
|
|
524
|
+
this.participants.add(participant);
|
|
525
|
+
this.viewer.scene.requestRender();
|
|
526
|
+
return () => {
|
|
527
|
+
this.participants.delete(participant);
|
|
528
|
+
if (!this.participants.size) {
|
|
529
|
+
this.remove();
|
|
530
|
+
this.element.removeEventListener("wheel", this.ForwardWheel);
|
|
531
|
+
this.element.removeEventListener("pointerdown", this.BeginDrag, true);
|
|
532
|
+
this.element.removeEventListener("dragstart", this.PreventNativeDrag);
|
|
533
|
+
this.element.ownerDocument.removeEventListener("pointermove", this.TrackPointer, true);
|
|
534
|
+
this.element.ownerDocument.removeEventListener("pointerout", this.PointerOut, true);
|
|
535
|
+
this.element.ownerDocument.defaultView.removeEventListener("blur", this.ClearPointer);
|
|
536
|
+
this.EndPendingDrag();
|
|
537
|
+
this.element.remove();
|
|
538
|
+
layers.delete(this.viewer);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
Update() {
|
|
543
|
+
var _a, _b;
|
|
544
|
+
this.coordinates = this.MeasureCoordinates();
|
|
545
|
+
const rect = (_b = (_a = this.viewer.canvas).getBoundingClientRect) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
546
|
+
const pointer = this.pointerClient;
|
|
547
|
+
this.pointer = pointer && (rect === null || rect === void 0 ? void 0 : rect.width) && (rect === null || rect === void 0 ? void 0 : rect.height) && pointer.x >= rect.left && pointer.x < rect.right && pointer.y >= rect.top && pointer.y < rect.bottom ?
|
|
548
|
+
{
|
|
549
|
+
x: (pointer.x - rect.left) * this.viewer.canvas.clientWidth / rect.width,
|
|
550
|
+
y: (pointer.y - rect.top) * this.viewer.canvas.clientHeight / rect.height
|
|
551
|
+
} : undefined;
|
|
552
|
+
const projected = new Map();
|
|
553
|
+
for (const participant of this.participants) {
|
|
554
|
+
projected.set(participant, participant.project());
|
|
555
|
+
}
|
|
556
|
+
const placements = new Map(Layout(Array.from(projected.values()).filter(Boolean), this.placementMemory).map(item => [item.frame.id, item]));
|
|
557
|
+
for (const [participant, frame] of projected) {
|
|
558
|
+
participant.apply(frame ? placements.get(frame.id) : undefined);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
let nextLabelId = 0;
|
|
36
563
|
class Label {
|
|
564
|
+
get HasText() { return this.options.presentation !== "icon"; }
|
|
565
|
+
SetHoverVisible(visible) {
|
|
566
|
+
var _a, _b, _c;
|
|
567
|
+
if (!this.Transient || this.Persistent) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (!visible) {
|
|
571
|
+
this.retainedHoverPosition = (_a = this.visualRegister) === null || _a === void 0 ? void 0 : _a.GetLabelHoverPosition((_b = this.rego) === null || _b === void 0 ? void 0 : _b.entityId, (_c = this.rego) === null || _c === void 0 ? void 0 : _c.menuItemId);
|
|
572
|
+
// Retiring labels must not change the ring now owned by the next hover target.
|
|
573
|
+
if (this.ringAnimation) {
|
|
574
|
+
this.ringAnimation = { ...this.ringAnimation };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (this.hoverFade.to === Number(visible)) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
this.hoverFade = { from: this.HoverOpacity(), to: Number(visible), start: Date.now() };
|
|
581
|
+
this.viewer.scene.requestRender();
|
|
582
|
+
}
|
|
583
|
+
HoverOpacity() {
|
|
584
|
+
if (!this.Transient || this.Persistent) {
|
|
585
|
+
return 1;
|
|
586
|
+
}
|
|
587
|
+
const progress = Math.min(1, Math.max(0, (Date.now() - this.hoverFade.start) / 120));
|
|
588
|
+
if (progress < 1) {
|
|
589
|
+
this.viewer.scene.requestRender();
|
|
590
|
+
}
|
|
591
|
+
return this.hoverFade.from + (this.hoverFade.to - this.hoverFade.from) * progress * progress * (3 - 2 * progress);
|
|
592
|
+
}
|
|
37
593
|
get Rego() {
|
|
38
594
|
return this.rego;
|
|
39
595
|
}
|
|
@@ -46,10 +602,18 @@ var EntityLabel;
|
|
|
46
602
|
set pos3d(val) {
|
|
47
603
|
this._pos3d = val;
|
|
48
604
|
this.prePositioned = !isNaN(val === null || val === void 0 ? void 0 : val.x);
|
|
49
|
-
this.
|
|
605
|
+
if (this.label && this.prePositioned)
|
|
606
|
+
this.viewer.scene.requestRender();
|
|
607
|
+
else
|
|
608
|
+
this.createLabel(val);
|
|
50
609
|
}
|
|
51
610
|
constructor(params) {
|
|
52
|
-
var _a;
|
|
611
|
+
var _a, _b, _c, _d;
|
|
612
|
+
this.id = `label-${++nextLabelId}`;
|
|
613
|
+
this.Persistent = false;
|
|
614
|
+
this.Transient = false;
|
|
615
|
+
this.hoverFade = { from: 0, to: 1, start: Date.now() };
|
|
616
|
+
this.cursorMotion = { amount: 0, from: 0, target: 0, start: 0, position: undefined };
|
|
53
617
|
// Counter to help differentiate between createLabel calls.
|
|
54
618
|
this.counter = 0;
|
|
55
619
|
// The label element itself.
|
|
@@ -69,15 +633,24 @@ var EntityLabel;
|
|
|
69
633
|
this.loadTimeout = null;
|
|
70
634
|
this.loadedName = false;
|
|
71
635
|
this.line = null;
|
|
636
|
+
this.anchor = null;
|
|
72
637
|
// Null = not attempted to load.
|
|
73
638
|
this.additionalTexts = null;
|
|
74
639
|
this.rego = params.rego;
|
|
75
640
|
this.viewer = params.viewer;
|
|
76
641
|
this.api = params.api;
|
|
77
642
|
this.visualRegister = params.visualRegister;
|
|
78
|
-
this.
|
|
643
|
+
this.options = params.options || {};
|
|
644
|
+
this.groupId = params.groupId;
|
|
645
|
+
this.Persistent = (_a = params.persistent) !== null && _a !== void 0 ? _a : Boolean(params.groupId);
|
|
646
|
+
this.Transient = (_b = params.transient) !== null && _b !== void 0 ? _b : false;
|
|
647
|
+
this.hoverRingState = params.hoverRingState;
|
|
648
|
+
this.visible = params.visible;
|
|
649
|
+
if (((_c = params.options) === null || _c === void 0 ? void 0 : _c.text) != null)
|
|
650
|
+
this.additionalTexts = [];
|
|
651
|
+
this.prePositioned = !isNaN((_d = params.pos3d) === null || _d === void 0 ? void 0 : _d.x);
|
|
79
652
|
this.createLabel(params.pos3d);
|
|
80
|
-
if (!params.api && !bruce_models_1.ENVIRONMENT.IS_SELF_MANAGED) {
|
|
653
|
+
if (!params.api && !bruce_models_1.ENVIRONMENT.IS_SELF_MANAGED && this.options.text == null) {
|
|
81
654
|
this.api = bruce_models_1.ENVIRONMENT.Api().GetBruceApi();
|
|
82
655
|
}
|
|
83
656
|
}
|
|
@@ -127,12 +700,24 @@ var EntityLabel;
|
|
|
127
700
|
clearTimeout(this.loadTimeout);
|
|
128
701
|
this.loadTimeout = null;
|
|
129
702
|
}
|
|
703
|
+
/*
|
|
704
|
+
* Rebuilds only when configuration changes; state refreshes must not recreate DOM every frame.
|
|
705
|
+
*/
|
|
706
|
+
Configure(options, groupId) {
|
|
707
|
+
if (this.groupId === groupId && JSON.stringify(this.options) === JSON.stringify(options || {})) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
this.options = options || {};
|
|
711
|
+
this.groupId = groupId;
|
|
712
|
+
this.additionalTexts = this.options.text != null ? [] : null;
|
|
713
|
+
this.createLabel();
|
|
714
|
+
}
|
|
130
715
|
/**
|
|
131
716
|
* Creates cesium entity for the label if one does not exist.
|
|
132
717
|
* @param starterPos3d optional position to create label at. Ignored for billboard labels.
|
|
133
718
|
*/
|
|
134
719
|
async createLabel(starterPos3d) {
|
|
135
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
720
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
136
721
|
this.removeLabel();
|
|
137
722
|
let pos3d = starterPos3d ? starterPos3d.clone() : null;
|
|
138
723
|
const counter = ++this.counter;
|
|
@@ -341,7 +926,7 @@ var EntityLabel;
|
|
|
341
926
|
}
|
|
342
927
|
clearTimeout(this.loadTimeout);
|
|
343
928
|
this.loadTimeout = setTimeout(async () => {
|
|
344
|
-
var _a, _b, _c, _d;
|
|
929
|
+
var _a, _b, _c, _d, _e;
|
|
345
930
|
if (this.disposed || this.counter != counter) {
|
|
346
931
|
return;
|
|
347
932
|
}
|
|
@@ -349,10 +934,10 @@ var EntityLabel;
|
|
|
349
934
|
if (this.additionalTexts == null) {
|
|
350
935
|
const additionalTexts = [];
|
|
351
936
|
// Rego might not have a known or default Entity Type. Eg: OSM building.
|
|
352
|
-
if (this.rego.entityTypeId) {
|
|
937
|
+
if (this.rego.entityTypeId || this.options.attributes) {
|
|
353
938
|
let entityType = null;
|
|
354
939
|
try {
|
|
355
|
-
const data = await bruce_models_1.EntityType.Get({
|
|
940
|
+
const data = this.options.attributes ? null : await bruce_models_1.EntityType.Get({
|
|
356
941
|
api: this.api,
|
|
357
942
|
entityTypeId: this.rego.entityTypeId
|
|
358
943
|
});
|
|
@@ -367,14 +952,14 @@ var EntityLabel;
|
|
|
367
952
|
}
|
|
368
953
|
// Type could be deleted, however we'll just catch that to show a label instead of nothing.
|
|
369
954
|
// This is not unusual for Tilesets where the Type that is baked might be outdated.
|
|
370
|
-
if (entityType) {
|
|
371
|
-
const attributes = (
|
|
955
|
+
if (entityType || this.options.attributes) {
|
|
956
|
+
const attributes = (_a = this.options.attributes) !== null && _a !== void 0 ? _a : (_c = (_b = entityType.VisualSettings) === null || _b === void 0 ? void 0 : _b.label) === null || _c === void 0 ? void 0 : _c.attributes;
|
|
372
957
|
if (attributes) {
|
|
373
958
|
try {
|
|
374
959
|
const { entity } = await bruce_models_1.Entity.Get({
|
|
375
960
|
api: this.api,
|
|
376
961
|
entityId: this.rego.entityId,
|
|
377
|
-
scenario: ((
|
|
962
|
+
scenario: ((_d = this.rego.outline) === null || _d === void 0 ? void 0 : _d.length) ? (_e = this.rego.outline.find(x => !!x.Scenario)) === null || _e === void 0 ? void 0 : _e.Scenario : null,
|
|
378
963
|
schemaId: this.rego.schema,
|
|
379
964
|
migrated: true
|
|
380
965
|
});
|
|
@@ -383,7 +968,7 @@ var EntityLabel;
|
|
|
383
968
|
entity: entity,
|
|
384
969
|
path: attributes[i]
|
|
385
970
|
});
|
|
386
|
-
if (value) {
|
|
971
|
+
if (value != null) {
|
|
387
972
|
additionalTexts.push(value);
|
|
388
973
|
}
|
|
389
974
|
}
|
|
@@ -412,12 +997,166 @@ var EntityLabel;
|
|
|
412
997
|
if (this.disposed || this.counter != counter) {
|
|
413
998
|
return;
|
|
414
999
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
1000
|
+
const presentation = this.options.presentation || "text";
|
|
1001
|
+
let clickEntityIds = [];
|
|
1002
|
+
const selectOnClick = (_j = this.options.selectOnClick) !== null && _j !== void 0 ? _j : Boolean(this.groupId);
|
|
1003
|
+
const select = (event) => {
|
|
1004
|
+
event.preventDefault();
|
|
1005
|
+
event.stopPropagation();
|
|
1006
|
+
if (!selectOnClick || this.disposed || this.counter !== counter || !clickEntityIds.length || !this.visualRegister) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
this.visualRegister.ClearSelected({ requestRender: false });
|
|
1010
|
+
this.visualRegister.SetSelected({ entityIds: clickEntityIds, selected: true });
|
|
1011
|
+
};
|
|
1012
|
+
const makeClickable = (target) => {
|
|
1013
|
+
if (!selectOnClick || !this.visualRegister) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
target.style.pointerEvents = "auto";
|
|
1017
|
+
target.style.cursor = "pointer";
|
|
1018
|
+
target.style.userSelect = "none";
|
|
1019
|
+
target.setAttribute("role", "button");
|
|
1020
|
+
target.tabIndex = 0;
|
|
1021
|
+
target.addEventListener("click", select);
|
|
1022
|
+
target.addEventListener("keydown", event => {
|
|
1023
|
+
if (event.key === "Enter" || event.key === " ")
|
|
1024
|
+
select(event);
|
|
1025
|
+
});
|
|
1026
|
+
// Prevent viewer picking and camera gestures from interpreting label interactions.
|
|
1027
|
+
for (const type of ["pointerdown", "pointerup", "mousedown", "mouseup", "dblclick"]) {
|
|
1028
|
+
target.addEventListener(type, event => event.stopPropagation());
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
const normalLeader = (_k = this.options.leader) !== null && _k !== void 0 ? _k : presentation === "text";
|
|
1032
|
+
let leader = normalLeader;
|
|
1033
|
+
const content = document.createElement("span");
|
|
1034
|
+
const originalText = (_l = this.options.text) !== null && _l !== void 0 ? _l : [text, ...additionalTexts].join("\n");
|
|
1035
|
+
let textElement;
|
|
1036
|
+
let sourceText;
|
|
1037
|
+
let sourceNaturalText;
|
|
1038
|
+
let iconElement;
|
|
1039
|
+
let iconContainer;
|
|
1040
|
+
content.className = "entity-label-content";
|
|
1041
|
+
content.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
1042
|
+
if (presentation === "icon-text-above") {
|
|
1043
|
+
content.style.flexDirection = "column-reverse";
|
|
1044
|
+
}
|
|
1045
|
+
if (presentation !== "text") {
|
|
1046
|
+
const icon = document.createElement("span");
|
|
1047
|
+
iconElement = icon;
|
|
1048
|
+
icon.className = "entity-label-icon";
|
|
1049
|
+
icon.style.cssText = `font-size:${(this.options.iconSize || 24) * ((_m = this.options.iconScale) !== null && _m !== void 0 ? _m : 1)}px;line-height:1;`;
|
|
1050
|
+
icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
|
|
1051
|
+
const iconColor = typeof this.options.iconColor === "string" ? this.options.iconColor : undefined;
|
|
1052
|
+
const iconBackground = this.options.iconBackground && this.options.iconScale !== 0;
|
|
1053
|
+
if (iconColor) {
|
|
1054
|
+
icon.style.color = iconColor;
|
|
1055
|
+
}
|
|
1056
|
+
if (this.options.iconUrl) {
|
|
1057
|
+
const image = document.createElement("img");
|
|
1058
|
+
image.draggable = false;
|
|
1059
|
+
image.alt = this.options.icon || "";
|
|
1060
|
+
const scale = (_o = this.options.iconScale) !== null && _o !== void 0 ? _o : 1;
|
|
1061
|
+
const size = this.options.iconSize;
|
|
1062
|
+
const mask = iconColor && iconBackground ? document.createElement("span") : icon;
|
|
1063
|
+
if (mask !== icon) {
|
|
1064
|
+
icon.appendChild(mask);
|
|
1065
|
+
}
|
|
1066
|
+
const target = iconColor ? mask : image;
|
|
1067
|
+
if (iconColor) {
|
|
1068
|
+
mask.style.display = "inline-block";
|
|
1069
|
+
mask.style.backgroundColor = iconColor;
|
|
1070
|
+
for (const prefix of ["", "-webkit-"]) {
|
|
1071
|
+
mask.style.setProperty(`${prefix}mask-image`, `url(${JSON.stringify(this.options.iconUrl)})`);
|
|
1072
|
+
mask.style.setProperty(`${prefix}mask-size`, "contain");
|
|
1073
|
+
mask.style.setProperty(`${prefix}mask-repeat`, "no-repeat");
|
|
1074
|
+
mask.style.setProperty(`${prefix}mask-position`, "center");
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const resize = () => {
|
|
1078
|
+
if (this.disposed || this.counter !== counter) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const intrinsic = this.options.iconScale != null && size == null;
|
|
1082
|
+
target.style.width = `${(intrinsic ? image.naturalWidth || 24 : size || 24) * scale}px`;
|
|
1083
|
+
target.style.height = `${(intrinsic ? image.naturalHeight || 24 : size || 24) * scale}px`;
|
|
1084
|
+
this.viewer.scene.requestRender();
|
|
1085
|
+
};
|
|
1086
|
+
image.onload = resize;
|
|
1087
|
+
resize();
|
|
1088
|
+
image.src = this.options.iconUrl;
|
|
1089
|
+
if (!iconColor) {
|
|
1090
|
+
icon.appendChild(image);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
else if (typeof this.options.iconClass === "string" && this.options.iconClass) {
|
|
1094
|
+
icon.className = `entity-label-icon ${this.options.iconClass}`;
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
icon.textContent = this.options.icon || "●";
|
|
1098
|
+
}
|
|
1099
|
+
if (iconBackground) {
|
|
1100
|
+
const color = Cesium.Color.fromCssColorString(iconColor || "white") || Cesium.Color.WHITE;
|
|
1101
|
+
const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
|
|
1102
|
+
const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Cesium.Color()) :
|
|
1103
|
+
color.brighten(ICON_BACKGROUND_BLEND, new Cesium.Color()));
|
|
1104
|
+
const size = (this.options.iconSize || 24) * ((_p = this.options.iconScale) !== null && _p !== void 0 ? _p : 1);
|
|
1105
|
+
icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
|
|
1106
|
+
icon.style.borderRadius = "50%";
|
|
1107
|
+
icon.style.padding = `${size / 3}px`;
|
|
1108
|
+
icon.style.display = "inline-flex";
|
|
1109
|
+
icon.style.alignItems = "center";
|
|
1110
|
+
icon.style.justifyContent = "center";
|
|
1111
|
+
icon.style.minWidth = `${size}px`;
|
|
1112
|
+
icon.style.minHeight = `${size}px`;
|
|
1113
|
+
icon.style.boxSizing = "content-box";
|
|
1114
|
+
}
|
|
1115
|
+
if (this.options.iconScale === 0) {
|
|
1116
|
+
icon.style.display = "none";
|
|
1117
|
+
}
|
|
1118
|
+
makeClickable(icon);
|
|
1119
|
+
// Keep badges outside the glyph so font classes and image masks cannot affect them.
|
|
1120
|
+
iconContainer = document.createElement("span");
|
|
1121
|
+
iconContainer.style.cssText = "position:relative;display:inline-flex;flex:none;";
|
|
1122
|
+
iconContainer.appendChild(icon);
|
|
1123
|
+
content.appendChild(iconContainer);
|
|
1124
|
+
}
|
|
1125
|
+
if (presentation !== "icon") {
|
|
1126
|
+
textElement = document.createElement("span");
|
|
1127
|
+
textElement.className = "entity-label-text";
|
|
1128
|
+
const textStyle = `white-space:pre-wrap;overflow-wrap:anywhere;max-width:${TEXT_MAX_WIDTH}px;width:max-content;flex-shrink:0;`;
|
|
1129
|
+
textElement.style.cssText = textStyle;
|
|
1130
|
+
textElement.textContent = originalText;
|
|
1131
|
+
if (presentation === "text")
|
|
1132
|
+
makeClickable(textElement);
|
|
1133
|
+
content.appendChild(textElement);
|
|
1134
|
+
if ((_q = this.options.cluster) === null || _q === void 0 ? void 0 : _q.enabled) {
|
|
1135
|
+
sourceText = document.createElement("span");
|
|
1136
|
+
sourceText.className = "entity-label-source-text";
|
|
1137
|
+
sourceText.setAttribute("aria-hidden", "true");
|
|
1138
|
+
sourceText.style.cssText = textStyle + "position:absolute;visibility:hidden;pointer-events:none;";
|
|
1139
|
+
sourceText.textContent = originalText;
|
|
1140
|
+
content.appendChild(sourceText);
|
|
1141
|
+
// Measure the original unwrapped text, never a rendered cluster summary.
|
|
1142
|
+
sourceNaturalText = document.createElement("span");
|
|
1143
|
+
sourceNaturalText.setAttribute("aria-hidden", "true");
|
|
1144
|
+
sourceNaturalText.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;white-space:pre;width:max-content;";
|
|
1145
|
+
sourceNaturalText.textContent = originalText;
|
|
1146
|
+
content.appendChild(sourceNaturalText);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const countElement = document.createElement("span");
|
|
1150
|
+
countElement.className = "entity-label-count";
|
|
1151
|
+
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;";
|
|
1152
|
+
if (iconContainer) {
|
|
1153
|
+
countElement.style.cssText += "position:absolute;left:100%;top:0;transform:translate(-50%,-50%);";
|
|
1154
|
+
iconContainer.appendChild(countElement);
|
|
1155
|
+
}
|
|
1156
|
+
if (!layers.has(this.viewer)) {
|
|
1157
|
+
layers.set(this.viewer, new Layer(this.viewer));
|
|
1158
|
+
}
|
|
1159
|
+
const layer = layers.get(this.viewer);
|
|
421
1160
|
let ele = null;
|
|
422
1161
|
let lineEle = null;
|
|
423
1162
|
if (this.label) {
|
|
@@ -425,11 +1164,22 @@ var EntityLabel;
|
|
|
425
1164
|
}
|
|
426
1165
|
else {
|
|
427
1166
|
ele = document.createElement("div");
|
|
428
|
-
ele.
|
|
1167
|
+
ele.appendChild(content);
|
|
1168
|
+
if (!iconContainer) {
|
|
1169
|
+
ele.appendChild(countElement);
|
|
1170
|
+
}
|
|
1171
|
+
ele.className = "entity-label";
|
|
1172
|
+
ele.dataset.entityId = ((_r = this.rego) === null || _r === void 0 ? void 0 : _r.entityId) || "";
|
|
1173
|
+
ele.dataset.presentation = presentation;
|
|
1174
|
+
if (this.groupId) {
|
|
1175
|
+
ele.dataset.groupId = this.groupId;
|
|
1176
|
+
}
|
|
429
1177
|
ele.setAttribute("style", `
|
|
430
1178
|
position: absolute;
|
|
431
1179
|
z-index: 0;
|
|
432
1180
|
display: none;
|
|
1181
|
+
align-items: center;
|
|
1182
|
+
gap: 6px;
|
|
433
1183
|
pointer-events: none;
|
|
434
1184
|
padding: 6px 8px;
|
|
435
1185
|
border-radius: 6px;
|
|
@@ -438,9 +1188,10 @@ var EntityLabel;
|
|
|
438
1188
|
border-radius: 9px;
|
|
439
1189
|
color: #ffffff;
|
|
440
1190
|
text-shadow: 0 0 5px rgba(0, 0, 0, 0.8);
|
|
441
|
-
|
|
1191
|
+
`);
|
|
442
1192
|
this.label = ele;
|
|
443
|
-
|
|
1193
|
+
ele.style.width = "max-content";
|
|
1194
|
+
layer.element.appendChild(ele);
|
|
444
1195
|
}
|
|
445
1196
|
if (this.line) {
|
|
446
1197
|
lineEle = this.line;
|
|
@@ -457,7 +1208,10 @@ var EntityLabel;
|
|
|
457
1208
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
|
|
458
1209
|
`);
|
|
459
1210
|
this.line = lineEle;
|
|
460
|
-
|
|
1211
|
+
lineEle.className = "entity-label-leader";
|
|
1212
|
+
if (this.options.leaderColor)
|
|
1213
|
+
lineEle.style.background = this.options.leaderColor;
|
|
1214
|
+
// A separate anchor keeps the stem out of the hollow centre when proximity opens the ring.
|
|
461
1215
|
const lineEleNode = document.createElement("div");
|
|
462
1216
|
lineEleNode.setAttribute("style", `
|
|
463
1217
|
position: absolute;
|
|
@@ -470,35 +1224,20 @@ var EntityLabel;
|
|
|
470
1224
|
top: -2px;
|
|
471
1225
|
left: -2px;
|
|
472
1226
|
`);
|
|
473
|
-
|
|
474
|
-
|
|
1227
|
+
lineEleNode.className = "entity-label-anchor";
|
|
1228
|
+
// SVG preserves fractional strokes in scaled containers where CSS borders round to pixels.
|
|
1229
|
+
const ringSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1230
|
+
ringSvg.style.cssText = "position:absolute;inset:0;width:100%;height:100%;overflow:visible;";
|
|
1231
|
+
ringSvg.setAttribute("aria-hidden", "true");
|
|
1232
|
+
ringSvg.setAttribute("preserveAspectRatio", "none");
|
|
1233
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
1234
|
+
circle.setAttribute("fill", "none");
|
|
1235
|
+
ringSvg.appendChild(circle);
|
|
1236
|
+
lineEleNode.appendChild(ringSvg);
|
|
1237
|
+
this.anchor = lineEleNode;
|
|
1238
|
+
layer.element.appendChild(lineEleNode);
|
|
1239
|
+
layer.element.appendChild(lineEle);
|
|
475
1240
|
}
|
|
476
|
-
let _lastDistance = null;
|
|
477
|
-
let _lastCameraPos = null;
|
|
478
|
-
const getDistance = () => {
|
|
479
|
-
var _a;
|
|
480
|
-
if (this.disposed) {
|
|
481
|
-
return null;
|
|
482
|
-
}
|
|
483
|
-
const cameraPos = (_a = this.viewer.camera) === null || _a === void 0 ? void 0 : _a.position;
|
|
484
|
-
if (isNaN(cameraPos === null || cameraPos === void 0 ? void 0 : cameraPos.x)) {
|
|
485
|
-
return null;
|
|
486
|
-
}
|
|
487
|
-
// If camera hasn't moved much then we can use the last distance.
|
|
488
|
-
const TOLERANCE = 0.1;
|
|
489
|
-
if (_lastCameraPos) {
|
|
490
|
-
const totalMovement = Math.abs(_lastCameraPos.x - cameraPos.x) +
|
|
491
|
-
Math.abs(_lastCameraPos.y - cameraPos.y) +
|
|
492
|
-
Math.abs(_lastCameraPos.z - cameraPos.z);
|
|
493
|
-
if (totalMovement < TOLERANCE) {
|
|
494
|
-
return _lastDistance;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
const distance = Cesium.Cartesian3.distance(cameraPos, this._pos3d);
|
|
498
|
-
_lastDistance = distance;
|
|
499
|
-
_lastCameraPos = cameraPos.clone();
|
|
500
|
-
return distance;
|
|
501
|
-
};
|
|
502
1241
|
let MAX_DISTANCE = Math.max(15000, sizeInM * 35);
|
|
503
1242
|
if (visual && visual instanceof Cesium.Entity) {
|
|
504
1243
|
// Consistent sized things.
|
|
@@ -506,123 +1245,404 @@ var EntityLabel;
|
|
|
506
1245
|
MAX_DISTANCE = Infinity;
|
|
507
1246
|
}
|
|
508
1247
|
}
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
1248
|
+
const fitLeader = (anchorX, anchorY, desired, width, height, radius = 0) => {
|
|
1249
|
+
const above = Math.max(0, anchorY - height);
|
|
1250
|
+
const below = Math.max(0, this.viewer.canvas.clientHeight - anchorY - height);
|
|
1251
|
+
// Keep a short visible stem before switching sides; never aim it at the cursor or a box corner.
|
|
1252
|
+
const upwards = above >= Math.max(radius ? radius + 4 : 0, Math.min(8, desired)) || above >= below;
|
|
1253
|
+
const length = Math.min(desired, upwards ? above : below);
|
|
1254
|
+
const endY = anchorY + (upwards ? -length : length);
|
|
1255
|
+
return {
|
|
1256
|
+
x: Math.max(0, Math.min(anchorX, this.viewer.canvas.clientWidth - width)),
|
|
1257
|
+
y: upwards ? endY - height : endY, endY
|
|
1258
|
+
};
|
|
1259
|
+
};
|
|
1260
|
+
let cursorMotion = this.cursorMotion;
|
|
1261
|
+
const project = () => {
|
|
1262
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
1263
|
+
if (this.disposed || !this.viewer || this.viewer.isDestroyed() || ((_a = this.visible) === null || _a === void 0 ? void 0 : _a.call(this)) === false ||
|
|
1264
|
+
(presentation === "icon" && this.options.iconScale === 0)) {
|
|
1265
|
+
return null;
|
|
513
1266
|
}
|
|
514
|
-
if (!this.
|
|
515
|
-
this.
|
|
516
|
-
return;
|
|
1267
|
+
if (this.Transient && !this.Persistent && this.hoverFade.to === 0 &&
|
|
1268
|
+
(this.hoverFade.from === 0 || Date.now() - this.hoverFade.start >= 120)) {
|
|
1269
|
+
return null;
|
|
517
1270
|
}
|
|
518
|
-
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
1271
|
+
// Moving point/model positions must be sampled at the current clock time.
|
|
1272
|
+
if (!this.prePositioned && visual instanceof Cesium.Entity && visual.position) {
|
|
1273
|
+
const position = getValue(this.viewer, visual.position);
|
|
1274
|
+
if (position)
|
|
1275
|
+
this._pos3d = drawing_utils_1.DrawingUtils.EnsurePosHeight({
|
|
1276
|
+
desiredHeightRef: Cesium.HeightReference.NONE, heightRef, pos3d: position, viewer: this.viewer
|
|
1277
|
+
});
|
|
523
1278
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
1279
|
+
if (!Number.isFinite((_b = this._pos3d) === null || _b === void 0 ? void 0 : _b.x)) {
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1282
|
+
const camera = this.viewer.camera;
|
|
1283
|
+
const cameraPosition = camera.positionWC || camera.position;
|
|
1284
|
+
const relative = Cesium.Cartesian3.subtract(this._pos3d, cameraPosition, new Cesium.Cartesian3());
|
|
1285
|
+
const depth = Cesium.Cartesian3.dot(relative, camera.directionWC || camera.direction);
|
|
1286
|
+
const distance = Cesium.Cartesian3.magnitude(relative);
|
|
1287
|
+
if (!Number.isFinite(distance) || depth <= 0 || distance < ((_c = this.options.near) !== null && _c !== void 0 ? _c : 0) ||
|
|
1288
|
+
distance > ((_d = this.options.far) !== null && _d !== void 0 ? _d : Infinity) || (!this.prePositioned && distance >= MAX_DISTANCE)) {
|
|
1289
|
+
return null;
|
|
1290
|
+
}
|
|
1291
|
+
const transforms = Cesium.SceneTransforms;
|
|
1292
|
+
const projectPosition = transforms.wgs84ToWindowCoordinates || transforms.worldToWindowCoordinates;
|
|
1293
|
+
const anchor = projectPosition(this.viewer.scene, this._pos3d);
|
|
1294
|
+
if (!Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.x) || !Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.y)) {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
if (leader && (anchor.x < 0 || anchor.y < 0 || anchor.x >= this.viewer.canvas.clientWidth || anchor.y >= this.viewer.canvas.clientHeight))
|
|
1298
|
+
return null;
|
|
1299
|
+
const transientPivot = (this.Transient && !this.Persistent ?
|
|
1300
|
+
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (_e = this.visualRegister) === null || _e === void 0 ? void 0 : _e.GetLabelHoverPosition((_f = this.rego) === null || _f === void 0 ? void 0 : _f.entityId, (_g = this.rego) === null || _g === void 0 ? void 0 : _g.menuItemId)) : undefined);
|
|
1301
|
+
const hover = (presentation !== "icon" ?
|
|
1302
|
+
(this.Transient && !this.Persistent && this.hoverFade.to === 0 ? this.retainedHoverPosition :
|
|
1303
|
+
(_h = this.visualRegister) === null || _h === void 0 ? void 0 : _h.GetLabelHoverPosition((_j = this.rego) === null || _j === void 0 ? void 0 : _j.entityId, (_k = this.rego) === null || _k === void 0 ? void 0 : _k.menuItemId)) : undefined);
|
|
1304
|
+
const reused = !this.Transient || this.Persistent;
|
|
1305
|
+
const now = Date.now();
|
|
1306
|
+
const animateCursor = () => {
|
|
1307
|
+
const progress = Math.min(1, Math.max(0, (now - cursorMotion.start) / 200));
|
|
1308
|
+
const eased = progress * progress * (3 - 2 * progress);
|
|
1309
|
+
cursorMotion.amount = cursorMotion.from + (cursorMotion.target - cursorMotion.from) * eased;
|
|
1310
|
+
return progress;
|
|
1311
|
+
};
|
|
1312
|
+
animateCursor();
|
|
1313
|
+
const cursorTarget = reused && hover ? 1 : 0;
|
|
1314
|
+
if (cursorMotion.target !== cursorTarget) {
|
|
1315
|
+
cursorMotion = { ...cursorMotion, from: cursorMotion.amount, target: cursorTarget, start: now };
|
|
1316
|
+
}
|
|
1317
|
+
;
|
|
1318
|
+
if (hover) {
|
|
1319
|
+
cursorMotion.position = { x: hover.x, y: hover.y };
|
|
533
1320
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1321
|
+
;
|
|
1322
|
+
if (animateCursor() < 1 && cursorMotion.from !== cursorMotion.target) {
|
|
1323
|
+
this.viewer.scene.requestRender();
|
|
1324
|
+
}
|
|
1325
|
+
;
|
|
1326
|
+
this.cursorMotion = cursorMotion;
|
|
1327
|
+
const cursorAmount = cursorMotion.amount;
|
|
1328
|
+
leader = normalLeader || cursorAmount > 0;
|
|
1329
|
+
let x = ((_l = transientPivot === null || transientPivot === void 0 ? void 0 : transientPivot.x) !== null && _l !== void 0 ? _l : anchor.x) + pixelOffset.x;
|
|
1330
|
+
let y = ((_m = transientPivot === null || transientPivot === void 0 ? void 0 : transientPivot.y) !== null && _m !== void 0 ? _m : anchor.y) + pixelOffset.y;
|
|
1331
|
+
if (cursorAmount > 0) {
|
|
1332
|
+
// A leader-less label starts centred on its anchor; preserve that box as it lifts away.
|
|
1333
|
+
if (!normalLeader) {
|
|
1334
|
+
x -= ele.offsetWidth / layer.coordinates.scaleX / 2;
|
|
1335
|
+
y += ele.offsetHeight / layer.coordinates.scaleY / 2;
|
|
1336
|
+
}
|
|
1337
|
+
x += (cursorMotion.position.x + pixelOffset.x - x) * cursorAmount;
|
|
1338
|
+
y += (cursorMotion.position.y + pixelOffset.y - y) * cursorAmount;
|
|
1339
|
+
}
|
|
1340
|
+
const anchorX = x;
|
|
1341
|
+
const anchorY = y;
|
|
1342
|
+
let leaderLength;
|
|
1343
|
+
if (leader) {
|
|
1344
|
+
if (!normalLeader) {
|
|
1345
|
+
leaderLength = 0;
|
|
1346
|
+
}
|
|
1347
|
+
else if (this.options.leaderLength != null) {
|
|
1348
|
+
leaderLength = Math.max(0, this.options.leaderLength);
|
|
1349
|
+
}
|
|
1350
|
+
else if (transientPivot) {
|
|
1351
|
+
// Cursor callouts must not jump when the next hover target has a different extent.
|
|
1352
|
+
leaderLength = 96;
|
|
553
1353
|
}
|
|
554
1354
|
else {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
//
|
|
561
|
-
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
1355
|
+
const point = Cesium.Cartographic.fromCartesian(this._pos3d);
|
|
1356
|
+
const height = (this.prePositioned ? distance / 24 : sizeInM > 0 ?
|
|
1357
|
+
Math.min(distance / 5, sizeInM * 1.2) : Math.min(distance, camera.positionCartographic.height) / 7);
|
|
1358
|
+
point.height += Math.max(0.001, height);
|
|
1359
|
+
const end = projectPosition(this.viewer.scene, Cesium.Cartographic.toCartesian(point));
|
|
1360
|
+
// A tall world-space leader can cross the camera plane while its anchor remains visible.
|
|
1361
|
+
leaderLength = Number.isFinite(end === null || end === void 0 ? void 0 : end.x) && Number.isFinite(end === null || end === void 0 ? void 0 : end.y) ? Math.hypot(end.x - anchor.x, end.y - anchor.y) : 24;
|
|
1362
|
+
// Scale after projection so large areas and routes get shorter leaders at every zoom.
|
|
1363
|
+
if (visual instanceof Cesium.Entity && (visual.polygon || visual.polyline))
|
|
1364
|
+
leaderLength /= 3;
|
|
566
1365
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if (additionalTexts === null || additionalTexts === void 0 ? void 0 : additionalTexts.length) {
|
|
570
|
-
// Each additional text is about 16px tall.
|
|
571
|
-
// So we get the total px height and divide by 11 to get the meters.
|
|
572
|
-
addHeight += ((additionalTexts.length * 16) / 11);
|
|
1366
|
+
if (cursorAmount > 0) {
|
|
1367
|
+
leaderLength += (96 - leaderLength) * cursorAmount;
|
|
573
1368
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1369
|
+
;
|
|
1370
|
+
y = anchorY - leaderLength;
|
|
1371
|
+
}
|
|
1372
|
+
else if (hover && !reused) {
|
|
1373
|
+
x = hover.x + pixelOffset.x;
|
|
1374
|
+
y = hover.y + pixelOffset.y - 24;
|
|
1375
|
+
}
|
|
1376
|
+
// Hidden participants remain measurable; visibility never removes their layout boxes.
|
|
1377
|
+
const coordinates = layer.coordinates;
|
|
1378
|
+
const width = ele.offsetWidth / coordinates.scaleX;
|
|
1379
|
+
const height = ele.offsetHeight / coordinates.scaleY;
|
|
1380
|
+
let clusterBounds;
|
|
1381
|
+
if ((_o = this.options.cluster) === null || _o === void 0 ? void 0 : _o.enabled) {
|
|
1382
|
+
const textWidth = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetWidth) || 0;
|
|
1383
|
+
const textHeight = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetHeight) || 0;
|
|
1384
|
+
const iconWidth = (iconElement === null || iconElement === void 0 ? void 0 : iconElement.offsetWidth) || 0;
|
|
1385
|
+
const iconHeight = (iconElement === null || iconElement === void 0 ? void 0 : iconElement.offsetHeight) || 0;
|
|
1386
|
+
const gap = sourceText && iconWidth ? 6 : 0;
|
|
1387
|
+
const above = presentation === "icon-text-above";
|
|
1388
|
+
// Match the fixed content gap and label padding, excluding the count badge.
|
|
1389
|
+
const baseWidth = ((above ? Math.max(textWidth, iconWidth) : textWidth + iconWidth + gap) + 16) / coordinates.scaleX;
|
|
1390
|
+
const baseHeight = ((above ? textHeight + iconHeight + gap : Math.max(textHeight, iconHeight)) + 12) / coordinates.scaleY;
|
|
1391
|
+
clusterBounds = {
|
|
1392
|
+
x: leader ? x : x - baseWidth / 2,
|
|
1393
|
+
y: leader ? y - baseHeight : y - baseHeight / 2, width: baseWidth, height: baseHeight
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (!leader) {
|
|
1397
|
+
x -= width / 2;
|
|
1398
|
+
y -= height / 2;
|
|
1399
|
+
}
|
|
1400
|
+
// Membership uses the unclamped source footprint, never the summary's changing dimensions.
|
|
1401
|
+
if (leader)
|
|
1402
|
+
({ x, y } = fitLeader(anchorX, anchorY, leaderLength, width, height));
|
|
1403
|
+
const bounds = clusterBounds || { x, y, width, height };
|
|
1404
|
+
if (!leader && (bounds.x + bounds.width < 0 || bounds.y + bounds.height < 0 || bounds.x > this.viewer.canvas.clientWidth || bounds.y > this.viewer.canvas.clientHeight)) {
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
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 });
|
|
1408
|
+
const opacity = this.prePositioned || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
|
|
1409
|
+
return {
|
|
1410
|
+
canFlip: leader && presentation !== "icon", hasLeader: leader,
|
|
1411
|
+
textWrapped: ((sourceNaturalText === null || sourceNaturalText === void 0 ? void 0 : sourceNaturalText.offsetWidth) || 0) > TEXT_MAX_WIDTH,
|
|
1412
|
+
transient: this.Transient || Boolean(hover) || cursorAmount > 0,
|
|
1413
|
+
extendedGeometry: visual instanceof Cesium.Entity && Boolean(visual.polygon || visual.polyline || visual.corridor),
|
|
1414
|
+
viewportWidth: this.viewer.canvas.clientWidth,
|
|
1415
|
+
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,
|
|
1416
|
+
entityId: (_t = this.rego) === null || _t === void 0 ? void 0 : _t.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, distance,
|
|
1417
|
+
// Priority shows the hovered member individually without dragging its cluster along.
|
|
1418
|
+
opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
1419
|
+
};
|
|
1420
|
+
};
|
|
1421
|
+
let displacement = { x: 0, y: 0 };
|
|
1422
|
+
let displayedSummaryMore = false;
|
|
1423
|
+
let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
1424
|
+
const apply = (placement) => {
|
|
1425
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
1426
|
+
const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
|
|
1427
|
+
clickEntityIds = show ? placement.entityIds : [];
|
|
1428
|
+
ele.style.visibility = show ? "visible" : "hidden";
|
|
1429
|
+
lineEle.style.display = show && leader ? "block" : "none";
|
|
1430
|
+
this.anchor.style.display = show && leader ? "block" : "none";
|
|
1431
|
+
if (!show) {
|
|
1432
|
+
displacement = { x: 0, y: 0 };
|
|
1433
|
+
nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
1434
|
+
if (!this.Transient) {
|
|
1435
|
+
this.ringAnimation = undefined;
|
|
577
1436
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const displayText = (_a = placement.text) !== null && _a !== void 0 ? _a : originalText;
|
|
1440
|
+
const summaryMore = Boolean(placement.summaryMore);
|
|
1441
|
+
if (textElement && (textElement.textContent !== displayText + (summaryMore ? "\n…" : "") || displayedSummaryMore !== summaryMore)) {
|
|
1442
|
+
textElement.textContent = displayText;
|
|
1443
|
+
if (summaryMore) {
|
|
1444
|
+
const more = document.createElement("span");
|
|
1445
|
+
more.className = "entity-label-more";
|
|
1446
|
+
more.style.opacity = "0.65";
|
|
1447
|
+
more.textContent = "\n…";
|
|
1448
|
+
textElement.appendChild(more);
|
|
589
1449
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
1450
|
+
displayedSummaryMore = summaryMore;
|
|
1451
|
+
// Measure the summary's footprint next frame without replacing each member's source text.
|
|
1452
|
+
this.viewer.scene.requestRender();
|
|
1453
|
+
}
|
|
1454
|
+
const frame = placement.frame;
|
|
1455
|
+
const coordinates = layer.coordinates;
|
|
1456
|
+
const opacity = placement.opacity * this.HoverOpacity();
|
|
1457
|
+
ele.style.opacity = String(opacity);
|
|
1458
|
+
ele.style.zIndex = String(placement.zIndex);
|
|
1459
|
+
ele.dataset.clusterCount = String(placement.members.length || 1);
|
|
1460
|
+
countElement.style.display = placement.members.length ? "block" : "none";
|
|
1461
|
+
countElement.style.order = placement.textSide === "left" ? "-1" : "0";
|
|
1462
|
+
const count = placement.members.length ? String(placement.members.length) : "";
|
|
1463
|
+
if (countElement.textContent !== count) {
|
|
1464
|
+
countElement.textContent = count;
|
|
1465
|
+
// The badge participates in layout, so its new width needs another measurement.
|
|
1466
|
+
this.viewer.scene.requestRender();
|
|
1467
|
+
}
|
|
1468
|
+
if (iconContainer) {
|
|
1469
|
+
const badgeWidth = countElement.offsetWidth;
|
|
1470
|
+
const iconWidth = iconElement.offsetWidth;
|
|
1471
|
+
const narrow = iconWidth < iconElement.offsetHeight * 0.6;
|
|
1472
|
+
const clearance = badgeWidth ? (narrow ? badgeWidth / 2 + 2 : Math.max(0, badgeWidth / 2 - iconWidth / 4)) : 0;
|
|
1473
|
+
countElement.style.left = `calc(100% + ${clearance}px)`;
|
|
1474
|
+
// Reserve the badge overhang so adjacent text and viewport edges remain clear.
|
|
1475
|
+
iconContainer.style.marginTop = `${countElement.offsetHeight / 2}px`;
|
|
1476
|
+
iconContainer.style.marginRight = `${badgeWidth / 2 + clearance}px`;
|
|
1477
|
+
}
|
|
1478
|
+
// Summary and badge changes must fit on their first visible frame, including after resize.
|
|
1479
|
+
const ring = this.options.anchorRing;
|
|
1480
|
+
const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
|
|
1481
|
+
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (_b = this.visualRegister) === null || _b === void 0 ? void 0 : _b.GetLabelHoverPosition((_c = this.rego) === null || _c === void 0 ? void 0 : _c.entityId, (_d = this.rego) === null || _d === void 0 ? void 0 : _d.menuItemId)) : undefined));
|
|
1482
|
+
const hovered = pointer && Math.hypot(pointer.x - frame.anchorX, pointer.y - frame.anchorY) <= ((_e = ring === null || ring === void 0 ? void 0 : ring.hoverDistance) !== null && _e !== void 0 ? _e : 24);
|
|
1483
|
+
const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((_f = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _f !== void 0 ? _f : 100) ? ((_g = ring === null || ring === void 0 ? void 0 : ring.nearRadius) !== null && _g !== void 0 ? _g : 10) : 0, hovered ? ((_h = ring === null || ring === void 0 ? void 0 : ring.hoverRadius) !== null && _h !== void 0 ? _h : (this.Transient && !this.Persistent ? 15.4 : 22)) : 0));
|
|
1484
|
+
const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
|
|
1485
|
+
const now = Date.now();
|
|
1486
|
+
const duration = (_j = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _j !== void 0 ? _j : 200;
|
|
1487
|
+
const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
|
|
1488
|
+
let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
|
|
1489
|
+
if (!ringAnimation) {
|
|
1490
|
+
ringAnimation = { target: 0, start: now, outer: 2.5, inner: 0, fromOuter: 2.5, fromInner: 0 };
|
|
1491
|
+
}
|
|
1492
|
+
;
|
|
1493
|
+
const animateRing = () => {
|
|
1494
|
+
const progress = duration > 0 ? Math.min(1, Math.max(0, (now - ringAnimation.start) / duration)) : 1;
|
|
1495
|
+
const eased = progress * progress * (3 - 2 * progress);
|
|
1496
|
+
ringAnimation.outer = ringAnimation.fromOuter + ((ringAnimation.target || 2.5) - ringAnimation.fromOuter) * eased;
|
|
1497
|
+
ringAnimation.inner = ringAnimation.fromInner + (Math.max(0, ringAnimation.target - 2) - ringAnimation.fromInner) * eased;
|
|
1498
|
+
return progress;
|
|
1499
|
+
};
|
|
1500
|
+
animateRing();
|
|
1501
|
+
if (ringAnimation.target !== targetRadius) {
|
|
1502
|
+
ringAnimation = {
|
|
1503
|
+
...ringAnimation,
|
|
1504
|
+
target: targetRadius,
|
|
1505
|
+
start: now,
|
|
1506
|
+
fromOuter: ringAnimation.outer,
|
|
1507
|
+
fromInner: ringAnimation.inner
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
// Animate the hole and stem together; no CSS transition may lag behind a moving world anchor.
|
|
1511
|
+
if (animateRing() < 1 && (ringAnimation.fromOuter !== (targetRadius || 2.5) || ringAnimation.fromInner !== Math.max(0, targetRadius - 2))) {
|
|
1512
|
+
this.viewer.scene.requestRender();
|
|
1513
|
+
}
|
|
1514
|
+
this.ringAnimation = ringAnimation;
|
|
1515
|
+
if (sharedRing) {
|
|
1516
|
+
sharedRing.animation = ringAnimation;
|
|
1517
|
+
}
|
|
1518
|
+
const radius = ringAnimation.inner > 0 ? ringAnimation.outer : 0;
|
|
1519
|
+
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);
|
|
1520
|
+
const position = { x: (stem || frame).x, y: (stem || frame).y };
|
|
1521
|
+
const width = ele.offsetWidth / coordinates.scaleX;
|
|
1522
|
+
const height = ele.offsetHeight / coordinates.scaleY;
|
|
1523
|
+
if (leader && placement.textSide === "left") {
|
|
1524
|
+
position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
|
|
1525
|
+
}
|
|
1526
|
+
const gap = (_l = (_k = this.options.pointerAvoidance) === null || _k === void 0 ? void 0 : _k.gap) !== null && _l !== void 0 ? _l : 12;
|
|
1527
|
+
const containsPointer = (x, y, margin) => {
|
|
1528
|
+
return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
|
|
1529
|
+
};
|
|
1530
|
+
const passive = !selectOnClick || !this.visualRegister;
|
|
1531
|
+
if (passive && ((_m = this.options.pointerAvoidance) === null || _m === void 0 ? void 0 : _m.enabled) !== false && pointer &&
|
|
1532
|
+
(containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
|
|
1533
|
+
if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
|
|
1534
|
+
// Choose against the original box and retain the offset until the pointer leaves both boxes.
|
|
1535
|
+
const candidates = [
|
|
1536
|
+
{ x: 0, y: pointer.y - gap - height - position.y },
|
|
1537
|
+
{ x: 0, y: pointer.y + gap - position.y },
|
|
1538
|
+
{ x: pointer.x - gap - width - position.x, y: 0 },
|
|
1539
|
+
{ x: pointer.x + gap - position.x, y: 0 }
|
|
1540
|
+
].filter(offset => position.x + offset.x >= 0 && position.y + offset.y >= 0 &&
|
|
1541
|
+
position.x + offset.x + width <= this.viewer.canvas.clientWidth && position.y + offset.y + height <= this.viewer.canvas.clientHeight)
|
|
1542
|
+
.sort((a, b) => Math.hypot(a.x, a.y) - Math.hypot(b.x, b.y));
|
|
1543
|
+
displacement = candidates[0] || { x: 0, y: 0 };
|
|
603
1544
|
}
|
|
604
|
-
// Position the label above the line end point.
|
|
605
|
-
this.label.style.left = `${lineEndPos2d.x}px`;
|
|
606
|
-
this.label.style.top = `${lineEndPos2d.y}px`;
|
|
607
|
-
// Update line position and size.
|
|
608
|
-
const dx = lineEndPos2d.x - lineStartPos2d.x;
|
|
609
|
-
const dy = lineEndPos2d.y - lineStartPos2d.y;
|
|
610
|
-
const length = Math.sqrt(dx * dx + dy * dy);
|
|
611
|
-
const angle = Math.atan2(dy, dx) - Math.PI / 2;
|
|
612
|
-
this.line.style.left = `${lineStartPos2d.x}px`;
|
|
613
|
-
this.line.style.top = `${lineStartPos2d.y}px`;
|
|
614
|
-
this.line.style.height = `${length}px`;
|
|
615
|
-
this.line.style.transform = `rotate(${angle}rad)`;
|
|
616
|
-
this.line.style.transformOrigin = 'top center';
|
|
617
1545
|
}
|
|
618
1546
|
else {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1547
|
+
displacement = { x: 0, y: 0 };
|
|
1548
|
+
}
|
|
1549
|
+
const nudgeDuration = (_p = (_o = this.options.pointerAvoidance) === null || _o === void 0 ? void 0 : _o.transitionDuration) !== null && _p !== void 0 ? _p : 200;
|
|
1550
|
+
const animateNudge = () => {
|
|
1551
|
+
const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
|
|
1552
|
+
const eased = progress * progress * (3 - 2 * progress);
|
|
1553
|
+
nudge.x = nudge.fromX + (nudge.targetX - nudge.fromX) * eased;
|
|
1554
|
+
nudge.y = nudge.fromY + (nudge.targetY - nudge.fromY) * eased;
|
|
1555
|
+
return progress;
|
|
1556
|
+
};
|
|
1557
|
+
animateNudge();
|
|
1558
|
+
if (nudge.targetX !== displacement.x || nudge.targetY !== displacement.y) {
|
|
1559
|
+
nudge = {
|
|
1560
|
+
...nudge,
|
|
1561
|
+
fromX: nudge.x,
|
|
1562
|
+
fromY: nudge.y,
|
|
1563
|
+
targetX: displacement.x,
|
|
1564
|
+
targetY: displacement.y,
|
|
1565
|
+
start: now + ((_r = (_q = this.options.pointerAvoidance) === null || _q === void 0 ? void 0 : _q.delay) !== null && _r !== void 0 ? _r : 180)
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
|
|
1569
|
+
this.viewer.scene.requestRender();
|
|
1570
|
+
}
|
|
1571
|
+
position.x += nudge.x;
|
|
1572
|
+
position.y += nudge.y;
|
|
1573
|
+
ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
|
|
1574
|
+
ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
|
|
1575
|
+
const clickTarget = content.querySelector('[role="button"]');
|
|
1576
|
+
if (clickTarget) {
|
|
1577
|
+
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}`);
|
|
1578
|
+
}
|
|
1579
|
+
if (leader) {
|
|
1580
|
+
const nodeRadius = ringAnimation.outer;
|
|
1581
|
+
const anchor = this.anchor;
|
|
1582
|
+
anchor.style.left = `${coordinates.x + (frame.anchorX - nodeRadius) * coordinates.scaleX}px`;
|
|
1583
|
+
anchor.style.top = `${coordinates.y + (frame.anchorY - nodeRadius) * coordinates.scaleY}px`;
|
|
1584
|
+
anchor.style.width = `${nodeRadius * 2 * coordinates.scaleX}px`;
|
|
1585
|
+
anchor.style.height = `${nodeRadius * 2 * coordinates.scaleY}px`;
|
|
1586
|
+
anchor.style.boxSizing = "border-box";
|
|
1587
|
+
anchor.style.background = "transparent";
|
|
1588
|
+
const svg = anchor.firstElementChild;
|
|
1589
|
+
svg.setAttribute("viewBox", `0 0 ${nodeRadius * 2} ${nodeRadius * 2}`);
|
|
1590
|
+
const circle = svg.firstElementChild;
|
|
1591
|
+
circle.setAttribute("cx", String(nodeRadius));
|
|
1592
|
+
circle.setAttribute("cy", String(nodeRadius));
|
|
1593
|
+
circle.setAttribute("r", String((nodeRadius + ringAnimation.inner) / 2));
|
|
1594
|
+
circle.setAttribute("stroke", this.options.leaderColor || "rgba(255, 255, 255, 0.8)");
|
|
1595
|
+
circle.setAttribute("stroke-width", String(nodeRadius - ringAnimation.inner));
|
|
1596
|
+
anchor.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
|
|
1597
|
+
anchor.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
|
|
1598
|
+
anchor.style.zIndex = String(placement.zIndex);
|
|
1599
|
+
// The stem starts at the rim, never across the transparent centre, even after passive displacement.
|
|
1600
|
+
const endX = Math.max(position.x, Math.min(frame.anchorX, position.x + width));
|
|
1601
|
+
const endY = frame.anchorY < position.y ? position.y : frame.anchorY > position.y + height ? position.y + height : frame.anchorY;
|
|
1602
|
+
const dx = endX - frame.anchorX, dy = endY - frame.anchorY;
|
|
1603
|
+
const length = Math.hypot(dx, dy);
|
|
1604
|
+
const leaderAngle = Math.atan2(dy, dx);
|
|
1605
|
+
const pointerDx = pointer ? pointer.x - frame.anchorX : 0;
|
|
1606
|
+
const pointerDy = pointer ? pointer.y - frame.anchorY : 0;
|
|
1607
|
+
const desiredAngle = Math.hypot(pointerDx, pointerDy) > 0.5 ? Math.atan2(pointerDy, pointerDx) + Math.PI - leaderAngle : 0;
|
|
1608
|
+
const wrappedAngle = Math.atan2(Math.sin(desiredAngle), Math.cos(desiredAngle));
|
|
1609
|
+
// Keep the stem inside the 126-degree arc, away from antialiased endpoints.
|
|
1610
|
+
const arcFraction = 0.35;
|
|
1611
|
+
const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
|
|
1612
|
+
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_v = ringAnimation.arcOffset) !== null && _v !== void 0 ? _v : 0);
|
|
1613
|
+
const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
|
|
1614
|
+
const elapsed = Math.min(32, Math.max(0, now - ((_w = ringAnimation.arcTime) !== null && _w !== void 0 ? _w : now)));
|
|
1615
|
+
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
1616
|
+
const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
|
|
1617
|
+
ringAnimation.arcOffset = approach((_x = ringAnimation.arcOffset) !== null && _x !== void 0 ? _x : 0, targetOffset);
|
|
1618
|
+
ringAnimation.arcOpen = approach((_y = ringAnimation.arcOpen) !== null && _y !== void 0 ? _y : 0, targetOpen);
|
|
1619
|
+
ringAnimation.arcTime = now;
|
|
1620
|
+
if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
|
|
1621
|
+
this.viewer.scene.requestRender();
|
|
1622
|
+
}
|
|
1623
|
+
const circumference = Math.PI * (nodeRadius + ringAnimation.inner);
|
|
1624
|
+
const filledFraction = 1 - ringAnimation.arcOpen * (1 - arcFraction);
|
|
1625
|
+
circle.setAttribute("stroke-dasharray", `${circumference * filledFraction} ${circumference * (1 - filledFraction)}`);
|
|
1626
|
+
circle.setAttribute("transform", `rotate(${(leaderAngle + ringAnimation.arcOffset - Math.PI * filledFraction) * 180 / Math.PI} ${nodeRadius} ${nodeRadius})`);
|
|
1627
|
+
const cut = Math.min(radius, length);
|
|
1628
|
+
const startX = frame.anchorX + (length ? dx / length * cut : 0);
|
|
1629
|
+
const startY = frame.anchorY + (length ? dy / length * cut : 0);
|
|
1630
|
+
const screenDx = (endX - startX) * coordinates.scaleX;
|
|
1631
|
+
const screenDy = (endY - startY) * coordinates.scaleY;
|
|
1632
|
+
lineEle.style.width = `${2 * coordinates.scaleX}px`;
|
|
1633
|
+
lineEle.style.left = `${coordinates.x + (startX - 1) * coordinates.scaleX}px`;
|
|
1634
|
+
lineEle.style.top = `${coordinates.y + startY * coordinates.scaleY}px`;
|
|
1635
|
+
lineEle.style.height = `${Math.hypot(screenDx, screenDy)}px`;
|
|
1636
|
+
lineEle.style.transform = `rotate(${Math.atan2(screenDy, screenDx) - Math.PI / 2}rad)`;
|
|
1637
|
+
lineEle.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
|
|
1638
|
+
lineEle.style.transformOrigin = "top center";
|
|
1639
|
+
lineEle.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
|
|
1640
|
+
lineEle.style.zIndex = String(placement.zIndex);
|
|
622
1641
|
}
|
|
623
1642
|
};
|
|
624
|
-
|
|
625
|
-
|
|
1643
|
+
ele.style.display = "flex";
|
|
1644
|
+
ele.style.visibility = "hidden";
|
|
1645
|
+
this.renderRemoval = layer.Add({ project, apply });
|
|
626
1646
|
// If terrain changes then we'll re-call createLabel as the position may have changed.
|
|
627
1647
|
let terrTimeout = null;
|
|
628
1648
|
this.terrChangedRemoval = this.viewer.scene.terrainProviderChanged.addEventListener(() => {
|
|
@@ -642,6 +1662,9 @@ var EntityLabel;
|
|
|
642
1662
|
* Removes label from the viewer and unsubscribes from events.
|
|
643
1663
|
*/
|
|
644
1664
|
removeLabel() {
|
|
1665
|
+
var _a;
|
|
1666
|
+
(_a = this.anchor) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1667
|
+
this.anchor = null;
|
|
645
1668
|
if (this.label &&
|
|
646
1669
|
this.label.parentElement) {
|
|
647
1670
|
this.label.parentElement.removeChild(this.label);
|
|
@@ -667,16 +1690,44 @@ var EntityLabel;
|
|
|
667
1690
|
* If one exists then the action is ignored.
|
|
668
1691
|
*/
|
|
669
1692
|
function Attach(params) {
|
|
670
|
-
|
|
671
|
-
|
|
1693
|
+
var _a, _b, _c;
|
|
1694
|
+
const { rego, viewer, pos3d, visualRegister, api, options, groupId } = params;
|
|
1695
|
+
if (!rego || !viewer) {
|
|
672
1696
|
return;
|
|
673
1697
|
}
|
|
1698
|
+
if (params.persistent == null && visualRegister) {
|
|
1699
|
+
const group = visualRegister.GetLabelGroupFor(rego.entityId, rego.menuItemId);
|
|
1700
|
+
if (group || visualRegister.GetIsLabelled({ entityId: rego.entityId, menuItemId: rego.menuItemId })) {
|
|
1701
|
+
return Attach({
|
|
1702
|
+
...params,
|
|
1703
|
+
pos3d: undefined,
|
|
1704
|
+
options: (_a = group === null || group === void 0 ? void 0 : group.options) !== null && _a !== void 0 ? _a : options,
|
|
1705
|
+
groupId: (_b = group === null || group === void 0 ? void 0 : group.groupId) !== null && _b !== void 0 ? _b : groupId,
|
|
1706
|
+
persistent: true
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (rego[EntityLabel.ATTACH_KEY]) {
|
|
1711
|
+
// Legacy hover callers omit ownership/options and must not reconfigure a register-owned label.
|
|
1712
|
+
if (rego[EntityLabel.ATTACH_KEY].Persistent && params.persistent !== true && !groupId) {
|
|
1713
|
+
return rego[EntityLabel.ATTACH_KEY];
|
|
1714
|
+
}
|
|
1715
|
+
rego[EntityLabel.ATTACH_KEY].Persistent = (_c = params.persistent) !== null && _c !== void 0 ? _c : Boolean(groupId);
|
|
1716
|
+
rego[EntityLabel.ATTACH_KEY].Configure(options, groupId);
|
|
1717
|
+
return rego[EntityLabel.ATTACH_KEY];
|
|
1718
|
+
}
|
|
674
1719
|
rego[EntityLabel.ATTACH_KEY] = new Label({
|
|
675
1720
|
rego,
|
|
676
1721
|
viewer,
|
|
677
1722
|
pos3d,
|
|
678
1723
|
visualRegister,
|
|
679
|
-
api
|
|
1724
|
+
api,
|
|
1725
|
+
options,
|
|
1726
|
+
groupId,
|
|
1727
|
+
persistent: params.persistent,
|
|
1728
|
+
transient: params.transient,
|
|
1729
|
+
hoverRingState: params.hoverRingState,
|
|
1730
|
+
visible: params.visible
|
|
680
1731
|
});
|
|
681
1732
|
return rego[EntityLabel.ATTACH_KEY];
|
|
682
1733
|
}
|
|
@@ -691,12 +1742,19 @@ var EntityLabel;
|
|
|
691
1742
|
if (!rego && label) {
|
|
692
1743
|
rego = label.Rego;
|
|
693
1744
|
}
|
|
1745
|
+
if (label && label !== (rego === null || rego === void 0 ? void 0 : rego[EntityLabel.ATTACH_KEY])) {
|
|
1746
|
+
if (!label.Persistent || params.force)
|
|
1747
|
+
label.Dispose();
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
694
1750
|
if (!rego || !rego[EntityLabel.ATTACH_KEY]) {
|
|
695
1751
|
if (label) {
|
|
696
1752
|
(_a = label.Dispose) === null || _a === void 0 ? void 0 : _a.call(label);
|
|
697
1753
|
}
|
|
698
1754
|
return;
|
|
699
1755
|
}
|
|
1756
|
+
if (rego[EntityLabel.ATTACH_KEY].Persistent && !params.force)
|
|
1757
|
+
return;
|
|
700
1758
|
(_c = (_b = rego[EntityLabel.ATTACH_KEY]).Dispose) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
701
1759
|
rego[EntityLabel.ATTACH_KEY] = null;
|
|
702
1760
|
}
|