bruce-cesium 7.7.6 → 7.7.8

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.
Files changed (49) hide show
  1. package/dist/bruce-cesium.es5.js +2313 -69
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2292 -66
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +6 -1
  6. package/dist/lib/bruce-cesium.js.map +1 -1
  7. package/dist/lib/rendering/entity-render-engine-model3d.js +2 -2
  8. package/dist/lib/rendering/entity-render-engine-model3d.js.map +1 -1
  9. package/dist/lib/rendering/entity-render-engine-polygon.js +26 -4
  10. package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
  11. package/dist/lib/rendering/entity-render-engine-polyline.js +2 -1
  12. package/dist/lib/rendering/entity-render-engine-polyline.js.map +1 -1
  13. package/dist/lib/rendering/render-managers/common/entity-simulation.js +1 -1
  14. package/dist/lib/rendering/render-managers/common/entity-simulation.js.map +1 -1
  15. package/dist/lib/rendering/tileset-render-engine.js +47 -14
  16. package/dist/lib/rendering/tileset-render-engine.js.map +1 -1
  17. package/dist/lib/rendering/view-render-engine.js +30 -24
  18. package/dist/lib/rendering/view-render-engine.js.map +1 -1
  19. package/dist/lib/rendering/visuals-register.js +93 -8
  20. package/dist/lib/rendering/visuals-register.js.map +1 -1
  21. package/dist/lib/rendering/xgrids-render-engine.js +20 -3
  22. package/dist/lib/rendering/xgrids-render-engine.js.map +1 -1
  23. package/dist/lib/utils/cesium-map-grid.js +807 -0
  24. package/dist/lib/utils/cesium-map-grid.js.map +1 -0
  25. package/dist/lib/utils/device-profile.js +302 -0
  26. package/dist/lib/utils/device-profile.js.map +1 -0
  27. package/dist/lib/utils/gltf-generator.js +307 -0
  28. package/dist/lib/utils/gltf-generator.js.map +1 -0
  29. package/dist/lib/utils/gltf-gizmo.js +136 -0
  30. package/dist/lib/utils/gltf-gizmo.js.map +1 -0
  31. package/dist/lib/utils/view-utils.js +9 -0
  32. package/dist/lib/utils/view-utils.js.map +1 -1
  33. package/dist/lib/viewer/camera-controls.js +544 -0
  34. package/dist/lib/viewer/camera-controls.js.map +1 -0
  35. package/dist/lib/viewer/viewer-utils.js +9 -1
  36. package/dist/lib/viewer/viewer-utils.js.map +1 -1
  37. package/dist/types/bruce-cesium.d.ts +6 -1
  38. package/dist/types/rendering/tileset-render-engine.d.ts +10 -2
  39. package/dist/types/rendering/view-render-engine.d.ts +4 -0
  40. package/dist/types/rendering/visuals-register.d.ts +4 -0
  41. package/dist/types/rendering/xgrids-render-engine.d.ts +2 -0
  42. package/dist/types/utils/cesium-map-grid.d.ts +23 -0
  43. package/dist/types/utils/device-profile.d.ts +84 -0
  44. package/dist/types/utils/gltf-generator.d.ts +104 -0
  45. package/dist/types/utils/gltf-gizmo.d.ts +18 -0
  46. package/dist/types/utils/view-utils.d.ts +4 -0
  47. package/dist/types/viewer/camera-controls.d.ts +19 -0
  48. package/dist/types/viewer/viewer-utils.d.ts +1 -0
  49. package/package.json +3 -2
@@ -0,0 +1,807 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CesiumMapGrid = void 0;
4
+ const bruce_models_1 = require("bruce-models");
5
+ const Cesium = require("cesium");
6
+ const viewer_utils_1 = require("../viewer/viewer-utils");
7
+ const view_utils_1 = require("./view-utils");
8
+ const gltf_gizmo_1 = require("./gltf-gizmo");
9
+ // viewer[CLASS_INSTANCE_KEY] will point at an instance of MapGridHandler.
10
+ const CLASS_INSTANCE_KEY = "nextspace-navigator-grid-handler";
11
+ // viewer[UPDATE_MARKER_KEY] will point at a number that is incremented every time the grid is updated.
12
+ // This is used to determine if the grid is still valid or if it needs to be redrawn.
13
+ const UPDATE_MARKER_KEY = "nextspace-marker-grid";
14
+ // viewer[LAST_SETTINGS_KEY] will point at the last settings used for the grid.
15
+ const LAST_SETTINGS_KEY = "nextspace-navigator-last-settings-grid";
16
+ /*
17
+ * The point a distance in metres along a heading in degrees from another, over a spherical earth.
18
+ */
19
+ function offsetPoint(point, distance, heading) {
20
+ // Radius of earth.
21
+ const radius = 6371e3;
22
+ const δ = distance / radius;
23
+ const θ = Cesium.Math.toRadians(heading);
24
+ const φ1 = Cesium.Math.toRadians(point.latitude);
25
+ const λ1 = Cesium.Math.toRadians(point.longitude);
26
+ const sinφ2 = Math.sin(φ1) * Math.cos(δ) + Math.cos(φ1) * Math.sin(δ) * Math.cos(θ);
27
+ const φ2 = Math.asin(sinφ2);
28
+ const y = Math.sin(θ) * Math.sin(δ) * Math.cos(φ1);
29
+ const x = Math.cos(δ) - Math.sin(φ1) * sinφ2;
30
+ const λ2 = λ1 + Math.atan2(y, x);
31
+ return {
32
+ altitude: point.altitude,
33
+ latitude: Cesium.Math.toDegrees(φ2),
34
+ longitude: Cesium.Math.toDegrees(λ2)
35
+ };
36
+ }
37
+ /**
38
+ * Handler for drawing a grid of lines on the map with a specified spacing.
39
+ * Initially this grid is for model-space viewing which means we start at 0,0 lat/lon and have it expand from there.
40
+ * However, it should be a general-use tool for drawing a grid anywhere.
41
+ *
42
+ * It reacts to camera changes and draws the grid within view with padding.
43
+ * This helps avoid performance issues around drawing too many lines.
44
+ */
45
+ class MapGridHandler {
46
+ constructor(viewer, spacingInMeters = 100, modelSpace = false) {
47
+ this.gridCenterGizmoPieces = [];
48
+ this.maxCacheSize = 1000;
49
+ // === Thresholds for grid stability.
50
+ // As a fraction of current grid extent.
51
+ this.CAMERA_MOVE_THRESHOLD = 0.5;
52
+ // 40% change in camera height triggers redraw.
53
+ this.HEIGHT_CHANGE_THRESHOLD = 0.4;
54
+ // 40% change in grid extent triggers redraw.
55
+ this.GRID_EXTENT_CHANGE_THRESHOLD = 0.4;
56
+ // 5km - distance that triggers origin reset.
57
+ this.EXTREME_DISTANCE_THRESHOLD = 5000;
58
+ // Grid limits to prevent drawing too many lines.
59
+ this.MAX_LINES = 500;
60
+ // 50% padding around visible area.
61
+ this.VIEW_PADDING_FACTOR = 0.5;
62
+ // Minimum pixels between grid lines to show them.
63
+ this.MIN_GRID_PIXEL_SIZE = 20;
64
+ // Base ratio - will be adjusted dynamically based on spacing size and camera height.
65
+ this.BASE_MAX_VISIBILITY_RATIO = 30;
66
+ // Height to spacing ratio.
67
+ this.HEIGHT_TO_SPACING_RATIO = 100;
68
+ // === Label settings
69
+ // Label every nth intersection.
70
+ this.LABEL_INTERVAL = 2;
71
+ // Maximum number of labels to show.
72
+ this.MAX_LABELS = 100;
73
+ // Spacing levels to use for adaptive spacing.
74
+ this.SPACING_LEVELS = [
75
+ 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 300, 400, 500, 1000, 2000, 2500, 5000, 10000, 20000, 25000, 50000, 100000
76
+ ];
77
+ // Minimum zoom distance to prevent precision issues.
78
+ this.MINIMUM_ZOOM_DISTANCE = 40;
79
+ // Base spacing to use for adaptive spacing.
80
+ this.BASE_SPACING = 1;
81
+ // Maximum number of attempts to find a suitable spacing.
82
+ this.MAX_ADAPTIVE_ATTEMPTS = 5;
83
+ this.viewer = viewer;
84
+ this.spacingInMeters = spacingInMeters;
85
+ this.isUpdating = false;
86
+ this.modelSpace = modelSpace;
87
+ this.lastCameraPosition = null;
88
+ this.fixedGridOrigin = null;
89
+ this.lastGridExtent = 0;
90
+ this.offsetCache = new Map();
91
+ this.gridPrimitives = new Cesium.PrimitiveCollection();
92
+ this.viewer.scene.primitives.add(this.gridPrimitives);
93
+ this.currentMarker = viewer[UPDATE_MARKER_KEY] || 1;
94
+ const isMac = typeof navigator !== "undefined" && (navigator.platform.toUpperCase().indexOf("MAC") !== -1 ||
95
+ navigator.userAgent.toUpperCase().indexOf("MAC") !== -1);
96
+ if (modelSpace && isMac) {
97
+ console.log("[MAC-FIX] Applying camera distance limits for Mac");
98
+ viewer.scene.screenSpaceCameraController.minimumZoomDistance = this.MINIMUM_ZOOM_DISTANCE;
99
+ }
100
+ const manager = viewer_utils_1.ViewerUtils.GetManager({
101
+ viewer: this.viewer,
102
+ createIfMissing: false
103
+ });
104
+ if (manager) {
105
+ this.cameraChangeRemoval = manager.Monitor.Updated().Subscribe(() => {
106
+ this.onCameraChanged();
107
+ });
108
+ }
109
+ // Initial grid draw.
110
+ this.onCameraChanged();
111
+ }
112
+ Update(spacingInMeters, modelSpace) {
113
+ this.currentMarker = this.viewer[UPDATE_MARKER_KEY] || 1;
114
+ let needsUpdate = false;
115
+ if (this.spacingInMeters !== spacingInMeters) {
116
+ this.spacingInMeters = spacingInMeters;
117
+ needsUpdate = true;
118
+ }
119
+ if (modelSpace !== undefined && this.modelSpace !== modelSpace) {
120
+ this.modelSpace = modelSpace;
121
+ needsUpdate = true;
122
+ }
123
+ if (needsUpdate) {
124
+ this.clearGrid();
125
+ // When spacing or model space changes, clear the cached origin to force recalculation
126
+ // This ensures grid alignment at the new spacing.
127
+ this.fixedGridOrigin = null;
128
+ this.offsetCache.clear();
129
+ this.onCameraChanged();
130
+ }
131
+ }
132
+ Dispose() {
133
+ this.clearGrid();
134
+ this.clearGizmoPieces();
135
+ if (this.cameraChangeRemoval) {
136
+ this.cameraChangeRemoval();
137
+ this.cameraChangeRemoval = null;
138
+ }
139
+ if (this.gridPrimitives) {
140
+ this.viewer.scene.primitives.remove(this.gridPrimitives);
141
+ this.gridPrimitives = null;
142
+ }
143
+ if (this.viewer) {
144
+ this.viewer = null;
145
+ }
146
+ this.offsetCache.clear();
147
+ this.fixedGridOrigin = null;
148
+ }
149
+ clearGrid() {
150
+ if (this.gridPrimitives) {
151
+ this.gridPrimitives.removeAll();
152
+ this.gridLines = null;
153
+ this.gridLabels = null;
154
+ }
155
+ }
156
+ calculateAdaptiveSpacing(cameraHeight, baseSpacing) {
157
+ // Define spacing levels that look good (powers of 2, 5, and 10)
158
+ const spacingLevels = this.SPACING_LEVELS;
159
+ // Ensure base spacing is at least 1m
160
+ const adjustedBaseSpacing = Math.max(this.BASE_SPACING, baseSpacing);
161
+ // For very close cameras, use much smaller spacing regardless of base
162
+ if (cameraHeight < 10) {
163
+ // Find the smallest reasonable spacing for this height
164
+ // Rule: spacing should be roughly 1/20th to 1/50th of camera height, but at least 1m
165
+ const targetSpacing = Math.max(this.BASE_SPACING, cameraHeight / 30);
166
+ // Find the closest spacing level to our target
167
+ let bestSpacing = this.BASE_SPACING; // Start with minimum
168
+ let bestDifference = Math.abs(1 - targetSpacing);
169
+ for (const spacing of spacingLevels) {
170
+ if (spacing >= this.BASE_SPACING) { // Only consider valid spacings
171
+ const difference = Math.abs(spacing - targetSpacing);
172
+ if (difference < bestDifference) {
173
+ bestSpacing = spacing;
174
+ bestDifference = difference;
175
+ }
176
+ }
177
+ }
178
+ return bestSpacing;
179
+ }
180
+ // For medium distances (10m - 200m), scale more gradually
181
+ if (cameraHeight < 200) {
182
+ const targetSpacing = cameraHeight / 10; // More conservative scaling// Find closest spacing level, but prefer going up from base spacing
183
+ let bestSpacing = adjustedBaseSpacing;
184
+ let bestDifference = Math.abs(adjustedBaseSpacing - targetSpacing);
185
+ for (const spacing of spacingLevels) {
186
+ if (spacing >= Math.min(adjustedBaseSpacing, targetSpacing)) {
187
+ const difference = Math.abs(spacing - targetSpacing);
188
+ if (difference < bestDifference) {
189
+ bestSpacing = spacing;
190
+ bestDifference = difference;
191
+ }
192
+ }
193
+ }
194
+ return bestSpacing;
195
+ }
196
+ // For far distances (100m+), scale up aggressively
197
+ const targetSpacing = cameraHeight / 20;
198
+ // Find the spacing level that's closest to target but not smaller than base
199
+ let bestSpacing = adjustedBaseSpacing;
200
+ let bestDifference = Math.abs(adjustedBaseSpacing - targetSpacing);
201
+ for (const spacing of spacingLevels) {
202
+ if (spacing >= adjustedBaseSpacing) {
203
+ const difference = Math.abs(spacing - targetSpacing);
204
+ if (difference < bestDifference) {
205
+ bestSpacing = spacing;
206
+ bestDifference = difference;
207
+ }
208
+ }
209
+ }
210
+ return bestSpacing;
211
+ }
212
+ /*
213
+ * The point model-space treats as 0,0,0: the UCS in UCS placement, otherwise 0,0 lat/lon at sea level.
214
+ */
215
+ modelSpaceOrigin() {
216
+ const origin = view_utils_1.ViewUtils.IsModelSpaceUcs(this.viewer) ? view_utils_1.ViewUtils.GetModelSpaceOrigin(this.viewer) : null;
217
+ return origin !== null && origin !== void 0 ? origin : { latitude: 0, longitude: 0, altitude: 0 };
218
+ }
219
+ /*
220
+ * The origin as a scene position, where the axis gizmo sits.
221
+ */
222
+ modelSpaceOriginPos3d() {
223
+ const origin = this.modelSpaceOrigin();
224
+ return Cesium.Cartesian3.fromDegrees(origin.longitude, origin.latitude, origin.altitude);
225
+ }
226
+ drawGizmoPieces() {
227
+ if (this.gridCenterGizmoPieces.length) {
228
+ return;
229
+ }
230
+ // X.
231
+ {
232
+ const pos3d = this.modelSpaceOriginPos3d();
233
+ const scale = new Cesium.CallbackProperty(() => Cesium.Cartesian3.distance(this.viewer.camera.position, pos3d) * 0.0002, false);
234
+ const orientation = Cesium.Transforms.headingPitchRollQuaternion(pos3d, new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(90), 0, 0));
235
+ const entity = this.viewer.entities.add(new Cesium.Entity({
236
+ model: {
237
+ uri: gltf_gizmo_1.GltfGizmo.GetUri(gltf_gizmo_1.GltfGizmo.EPiece.X),
238
+ heightReference: Cesium.HeightReference.NONE,
239
+ shadows: Cesium.ShadowMode.DISABLED,
240
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.MAX_VALUE),
241
+ scale
242
+ },
243
+ position: pos3d,
244
+ orientation: new Cesium.ConstantProperty(orientation)
245
+ }));
246
+ this.gridCenterGizmoPieces.push(entity);
247
+ }
248
+ // Y.
249
+ {
250
+ const pos3d = this.modelSpaceOriginPos3d();
251
+ const scale = new Cesium.CallbackProperty(() => Cesium.Cartesian3.distance(this.viewer.camera.position, pos3d) * 0.0002, false);
252
+ const orientation = Cesium.Transforms.headingPitchRollQuaternion(pos3d, new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(90), 0, 0));
253
+ const entity = this.viewer.entities.add(new Cesium.Entity({
254
+ model: {
255
+ uri: gltf_gizmo_1.GltfGizmo.GetUri(gltf_gizmo_1.GltfGizmo.EPiece.Y),
256
+ heightReference: Cesium.HeightReference.NONE,
257
+ shadows: Cesium.ShadowMode.DISABLED,
258
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.MAX_VALUE),
259
+ scale
260
+ },
261
+ position: pos3d,
262
+ orientation: new Cesium.ConstantProperty(orientation)
263
+ }));
264
+ this.gridCenterGizmoPieces.push(entity);
265
+ }
266
+ // Z.
267
+ {
268
+ const pos3d = this.modelSpaceOriginPos3d();
269
+ const scale = new Cesium.CallbackProperty(() => Cesium.Cartesian3.distance(this.viewer.camera.position, pos3d) * 0.0002, false);
270
+ const orientation = Cesium.Transforms.headingPitchRollQuaternion(pos3d, new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(90), 0, 0));
271
+ const entity = this.viewer.entities.add(new Cesium.Entity({
272
+ model: {
273
+ uri: gltf_gizmo_1.GltfGizmo.GetUri(gltf_gizmo_1.GltfGizmo.EPiece.Z),
274
+ heightReference: Cesium.HeightReference.NONE,
275
+ shadows: Cesium.ShadowMode.DISABLED,
276
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.MAX_VALUE),
277
+ scale
278
+ },
279
+ position: pos3d,
280
+ orientation: new Cesium.ConstantProperty(orientation)
281
+ }));
282
+ this.gridCenterGizmoPieces.push(entity);
283
+ }
284
+ }
285
+ clearGizmoPieces() {
286
+ for (const piece of this.gridCenterGizmoPieces) {
287
+ if (piece && this.viewer.entities.contains(piece)) {
288
+ this.viewer.entities.remove(piece);
289
+ }
290
+ }
291
+ this.gridCenterGizmoPieces = [];
292
+ }
293
+ onCameraChanged() {
294
+ if (this.isUpdating) {
295
+ return;
296
+ }
297
+ else if (this.currentMarker !== this.viewer[UPDATE_MARKER_KEY]) {
298
+ return;
299
+ }
300
+ this.isUpdating = true;
301
+ // Small delay to avoid excessive updates during camera movement.
302
+ setTimeout(() => {
303
+ if (this.currentMarker !== this.viewer[UPDATE_MARKER_KEY]) {
304
+ this.isUpdating = false;
305
+ return;
306
+ }
307
+ try {
308
+ // The grid goes first, since it notices when the origin has moved and the gizmo has to follow.
309
+ this.updateGrid();
310
+ if (this.modelSpace) {
311
+ this.drawGizmoPieces();
312
+ }
313
+ else {
314
+ this.clearGizmoPieces();
315
+ }
316
+ }
317
+ catch (e) {
318
+ console.error(e);
319
+ }
320
+ finally {
321
+ this.isUpdating = false;
322
+ }
323
+ }, 100);
324
+ }
325
+ cachedOffsetPoint(point, distance, heading) {
326
+ const key = `${point.latitude.toFixed(8)},${point.longitude.toFixed(8)},${distance.toFixed(1)},${heading}`;
327
+ if (this.offsetCache.has(key)) {
328
+ return this.offsetCache.get(key);
329
+ }
330
+ const result = offsetPoint(point, distance, heading);
331
+ this.offsetCache.set(key, result);
332
+ if (this.offsetCache.size > this.maxCacheSize) {
333
+ const keys = Array.from(this.offsetCache.keys());
334
+ for (let i = 0; i < 100; i++) {
335
+ this.offsetCache.delete(keys[i]);
336
+ }
337
+ }
338
+ return result;
339
+ }
340
+ /**
341
+ * Calculates a dynamic visibility ratio based on spacing size.
342
+ * For small grid spacings, we can show the grid from farther away.
343
+ * For large grid spacings, we need to hide it sooner to prevent performance issues.
344
+ */
345
+ getMaxVisibilityRatio() {
346
+ if (this.spacingInMeters <= 10) {
347
+ return this.BASE_MAX_VISIBILITY_RATIO;
348
+ }
349
+ if (this.spacingInMeters <= 100) {
350
+ const factor = 1 - ((this.spacingInMeters - 10) / 90);
351
+ return this.BASE_MAX_VISIBILITY_RATIO * (0.5 + (factor * 0.5));
352
+ }
353
+ const largeGridFactor = Math.min(1, 100 / this.spacingInMeters);
354
+ return this.BASE_MAX_VISIBILITY_RATIO * 0.5 * largeGridFactor;
355
+ }
356
+ updateGrid() {
357
+ const cameraPosition = this.viewer.camera.position;
358
+ const cameraCartographic = Cesium.Cartographic.fromCartesian(cameraPosition);
359
+ const cameraHeight = cameraCartographic.height;
360
+ let adaptiveSpacing = this.calculateAdaptiveSpacing(cameraHeight, this.spacingInMeters);
361
+ // Calculate visible area.
362
+ const visibleArea = this.calculateVisibleArea();
363
+ if (!visibleArea) {
364
+ return;
365
+ }
366
+ let densityCheckPassed = false;
367
+ let attempts = 0;
368
+ while (!densityCheckPassed && attempts < this.MAX_ADAPTIVE_ATTEMPTS) {
369
+ if (this.isGridDensityAcceptable(visibleArea, cameraHeight, adaptiveSpacing)) {
370
+ densityCheckPassed = true;
371
+ }
372
+ else {
373
+ adaptiveSpacing *= 2;
374
+ attempts++;
375
+ }
376
+ }
377
+ // The origin follows the UCS of what is shown, so the grid moves with it when that changes.
378
+ if (this.modelSpace && this.fixedGridOrigin && !bruce_models_1.Carto.IsEqual(this.fixedGridOrigin, this.modelSpaceOrigin())) {
379
+ this.fixedGridOrigin = null;
380
+ this.lastCameraPosition = null;
381
+ this.clearGizmoPieces();
382
+ }
383
+ // Calculate grid origin if we don't have one yet.
384
+ if (!this.fixedGridOrigin) {
385
+ if (this.modelSpace) {
386
+ this.fixedGridOrigin = this.modelSpaceOrigin();
387
+ }
388
+ else {
389
+ const centerCartographic = Cesium.Cartographic.fromCartesian(visibleArea.centerCartesian);
390
+ const centerPoint = {
391
+ latitude: Cesium.Math.toDegrees(centerCartographic.latitude),
392
+ longitude: Cesium.Math.toDegrees(centerCartographic.longitude),
393
+ altitude: 0
394
+ };
395
+ // Simple attempt at snapping to the grid, should do something smarter.
396
+ this.fixedGridOrigin = {
397
+ latitude: Math.round(centerPoint.latitude * 100000) / 100000,
398
+ longitude: Math.round(centerPoint.longitude * 100000) / 100000,
399
+ altitude: 0
400
+ };
401
+ }
402
+ }
403
+ // Calculate appropriate grid extent based on camera height.
404
+ // For larger grid spacings, we use a tighter extent to prevent performance issues.
405
+ const scaledExtentFactor = Math.max(0.5, Math.min(3, 3 - Math.log10(Math.max(1, adaptiveSpacing)) * 0.5));
406
+ const visibilityRadius = Math.min(cameraHeight * scaledExtentFactor, Infinity);
407
+ // Check if we should redraw the grid.
408
+ const shouldRedraw = this.shouldRedrawGrid(cameraPosition, cameraHeight, visibilityRadius, adaptiveSpacing);
409
+ if (!shouldRedraw) {
410
+ return;
411
+ }
412
+ // Remember current settings for next comparison.
413
+ this.lastCameraHeight = cameraHeight;
414
+ this.lastCameraPosition = cameraPosition.clone();
415
+ this.lastGridExtent = visibilityRadius;
416
+ // Clear existing grid and create a new one.
417
+ this.clearGrid();
418
+ this.gridLines = new Cesium.PolylineCollection();
419
+ this.gridLabels = new Cesium.LabelCollection({
420
+ scene: this.viewer.scene
421
+ });
422
+ this.gridPrimitives.add(this.gridLines);
423
+ this.gridPrimitives.add(this.gridLabels);
424
+ // Generate grid lines and labels using our fixed origin.
425
+ this.generateGridLines(this.fixedGridOrigin, visibilityRadius, adaptiveSpacing);
426
+ }
427
+ shouldRedrawGrid(cameraPosition, cameraHeight, gridExtent, currentSpacing) {
428
+ // check if the spacing has changed
429
+ if (this.lastUsedSpacing && this.lastUsedSpacing !== currentSpacing) {
430
+ return true;
431
+ }
432
+ // Always draw if we don't have a grid yet.
433
+ if (!this.gridLines || !this.lastCameraPosition || !this.fixedGridOrigin) {
434
+ return true;
435
+ }
436
+ // Check for significant height change.
437
+ const heightRatio = cameraHeight / this.lastCameraHeight;
438
+ if (heightRatio > (1 + this.HEIGHT_CHANGE_THRESHOLD) ||
439
+ heightRatio < (1 - this.HEIGHT_CHANGE_THRESHOLD)) {
440
+ return true;
441
+ }
442
+ // Check for significant position change relative to current grid extent.
443
+ const distance = Cesium.Cartesian3.distance(cameraPosition, this.lastCameraPosition);
444
+ const moveThreshold = gridExtent * this.CAMERA_MOVE_THRESHOLD;
445
+ if (distance > moveThreshold) {
446
+ return true;
447
+ }
448
+ // Check for significant grid extent change.
449
+ const extentRatio = gridExtent / this.lastGridExtent;
450
+ if (extentRatio > (1 + this.GRID_EXTENT_CHANGE_THRESHOLD) ||
451
+ extentRatio < (1 - this.GRID_EXTENT_CHANGE_THRESHOLD)) {
452
+ return true;
453
+ }
454
+ // No significant changes, keep current grid.
455
+ return false;
456
+ }
457
+ isGridDensityAcceptable(visibleArea, cameraHeight, spacingToCheck = this.spacingInMeters) {
458
+ const lonRange = visibleArea.eastLon - visibleArea.westLon;
459
+ const latRange = visibleArea.northLat - visibleArea.southLat;
460
+ // Convert to approximate meters at center
461
+ const centerCartographic = Cesium.Cartographic.fromCartesian(visibleArea.centerCartesian);
462
+ const metersPerDegreeLon = 111319.9 * Math.cos(centerCartographic.latitude);
463
+ const metersPerDegreeLat = 111319.9;
464
+ const widthInMeters = lonRange * metersPerDegreeLon;
465
+ const heightInMeters = latRange * metersPerDegreeLat;
466
+ const canvas = this.viewer.scene.canvas;
467
+ const spacingInDegrees = spacingToCheck / metersPerDegreeLon;
468
+ const canvasWidth = canvas.clientWidth;
469
+ const pixelsPerDegree = canvasWidth / lonRange;
470
+ const pixelsBetweenGridLines = pixelsPerDegree * spacingInDegrees;
471
+ const minPixelSize = 1;
472
+ return pixelsBetweenGridLines >= minPixelSize;
473
+ }
474
+ calculateVisibleArea() {
475
+ const scene = this.viewer.scene;
476
+ const camera = scene.camera;
477
+ const globe = scene.globe;
478
+ // Get the corners of the view frustum
479
+ const canvas = scene.canvas;
480
+ const corners = [
481
+ new Cesium.Cartesian2(0, 0),
482
+ new Cesium.Cartesian2(canvas.clientWidth, 0),
483
+ new Cesium.Cartesian2(0, canvas.clientHeight),
484
+ new Cesium.Cartesian2(canvas.clientWidth, canvas.clientHeight),
485
+ new Cesium.Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2) // Center
486
+ ];
487
+ // Project corners to the globe
488
+ const cartographicCorners = [];
489
+ for (const corner of corners) {
490
+ const ray = camera.getPickRay(corner);
491
+ if (!ray) {
492
+ continue;
493
+ }
494
+ const position = globe.pick(ray, scene);
495
+ if (!position) {
496
+ continue;
497
+ }
498
+ cartographicCorners.push(Cesium.Cartographic.fromCartesian(position));
499
+ }
500
+ if (cartographicCorners.length === 0) {
501
+ return null;
502
+ }
503
+ // Find the bounding rectangle
504
+ let westLon = Number.POSITIVE_INFINITY;
505
+ let eastLon = Number.NEGATIVE_INFINITY;
506
+ let southLat = Number.POSITIVE_INFINITY;
507
+ let northLat = Number.NEGATIVE_INFINITY;
508
+ for (const corner of cartographicCorners) {
509
+ westLon = Math.min(westLon, corner.longitude);
510
+ eastLon = Math.max(eastLon, corner.longitude);
511
+ southLat = Math.min(southLat, corner.latitude);
512
+ northLat = Math.max(northLat, corner.latitude);
513
+ }
514
+ // Add some padding around the visible area
515
+ const lonPadding = (eastLon - westLon) * this.VIEW_PADDING_FACTOR;
516
+ const latPadding = (northLat - southLat) * this.VIEW_PADDING_FACTOR;
517
+ westLon -= lonPadding;
518
+ eastLon += lonPadding;
519
+ southLat -= latPadding;
520
+ northLat += latPadding;
521
+ // Get center position for reference
522
+ const centerCartographic = new Cesium.Cartographic((westLon + eastLon) / 2, (southLat + northLat) / 2);
523
+ const centerCartesian = Cesium.Cartesian3.fromRadians(centerCartographic.longitude, centerCartographic.latitude, 0);
524
+ return {
525
+ westLon: Cesium.Math.toDegrees(westLon),
526
+ eastLon: Cesium.Math.toDegrees(eastLon),
527
+ southLat: Cesium.Math.toDegrees(southLat),
528
+ northLat: Cesium.Math.toDegrees(northLat),
529
+ centerCartesian: centerCartesian
530
+ };
531
+ }
532
+ generateGridLines(originPoint, visibilityRadius, actualSpacing = this.spacingInMeters) {
533
+ // Number of grid cells in each direction (add 1 for edge lines).
534
+ const gridCellCount = Math.ceil(visibilityRadius / actualSpacing) + 1;
535
+ const maxCells = Math.min(gridCellCount, Math.floor(this.MAX_LINES / 2));
536
+ // Calculate interval to skip lines for density control.
537
+ const spacingFactor = actualSpacing / 100;
538
+ const heightFactor = this.lastCameraHeight / 500;
539
+ let lineSkipInterval = Math.floor(spacingFactor * heightFactor);
540
+ lineSkipInterval = Math.min(Math.max(lineSkipInterval, 1), 10); // Clamp between 1 and 10
541
+ const intersections = [];
542
+ // Generate north-south grid lines (longitude lines)
543
+ for (let i = -maxCells; i <= maxCells; i++) {
544
+ if (i % lineSkipInterval !== 0) {
545
+ continue;
546
+ }
547
+ const positions = [];
548
+ // Create a vertical line at this longitude offset.
549
+ // East heading for longitude.
550
+ const startPoint = this.cachedOffsetPoint(originPoint, i * actualSpacing, 90);
551
+ // Draw the line from south to north
552
+ for (let j = -maxCells; j <= maxCells; j++) {
553
+ if (j % lineSkipInterval !== 0) {
554
+ continue;
555
+ }
556
+ // North heading for latitude.
557
+ const linePoint = this.cachedOffsetPoint(startPoint, j * actualSpacing, 0);
558
+ const position = Cesium.Cartesian3.fromDegrees(linePoint.longitude, linePoint.latitude, linePoint.altitude);
559
+ positions.push(position);
560
+ intersections.push({
561
+ position: position,
562
+ i: i,
563
+ j: j
564
+ });
565
+ }
566
+ if (positions.length >= 2) {
567
+ // Check if this is the primary Y axis (i=0) in model space
568
+ const isPrimaryYAxis = this.modelSpace && i === 0;
569
+ this.addGridLine(positions, isPrimaryYAxis ? "y" : null);
570
+ }
571
+ }
572
+ // Generate east-west grid lines (latitude lines).
573
+ for (let j = -maxCells; j <= maxCells; j++) {
574
+ if (j % lineSkipInterval !== 0) {
575
+ continue;
576
+ }
577
+ const positions = [];
578
+ // Create a horizontal line at this latitude offset
579
+ // North heading for latitude.
580
+ const startPoint = this.cachedOffsetPoint(originPoint, j * actualSpacing, 0);
581
+ // Draw the line from west to east.
582
+ for (let i = -maxCells; i <= maxCells; i++) {
583
+ if (i % lineSkipInterval !== 0) {
584
+ continue;
585
+ }
586
+ // East heading for longitude.
587
+ const linePoint = this.cachedOffsetPoint(startPoint, i * actualSpacing, 90);
588
+ const position = Cesium.Cartesian3.fromDegrees(linePoint.longitude, linePoint.latitude, linePoint.altitude);
589
+ positions.push(position);
590
+ }
591
+ if (positions.length >= 2) {
592
+ // Check if this is the primary X axis (j=0) in model space
593
+ const isPrimaryXAxis = this.modelSpace && j === 0;
594
+ this.addGridLine(positions, isPrimaryXAxis ? "x" : null);
595
+ }
596
+ }
597
+ this.addGridLabels(intersections, actualSpacing);
598
+ }
599
+ addGridLabels(intersections, actualSpacing) {
600
+ if (!this.gridLabels) {
601
+ return;
602
+ }
603
+ // Determine label interval based on number of intersections.
604
+ // Use higher interval for more intersections to prevent crowding.
605
+ const totalIntersections = intersections.length;
606
+ let labelInterval = this.LABEL_INTERVAL;
607
+ if (totalIntersections > 1000) {
608
+ labelInterval = 5;
609
+ }
610
+ else if (totalIntersections > 500) {
611
+ labelInterval = 3;
612
+ }
613
+ let labelCount = 0;
614
+ // Filter intersections to only those at the labelInterval.
615
+ const labelPoints = intersections.filter(point => {
616
+ // Always label the origin (0,0).
617
+ if (point.i === 0 && point.j === 0) {
618
+ return true;
619
+ }
620
+ return (point.i % labelInterval === 0 && point.j % labelInterval === 0);
621
+ });
622
+ // Sort points by distance from origin.
623
+ labelPoints.sort((a, b) => {
624
+ const distA = Math.abs(a.i) + Math.abs(a.j);
625
+ const distB = Math.abs(b.i) + Math.abs(b.j);
626
+ return distA - distB;
627
+ });
628
+ // Add labels, prioritizing those closest to the origin.
629
+ for (const point of labelPoints) {
630
+ if (labelCount >= this.MAX_LABELS) {
631
+ // Limit reached.
632
+ break;
633
+ }
634
+ let labelText;
635
+ // Model space coordinates (meters from origin).
636
+ if (this.modelSpace) {
637
+ const xMeters = point.i * actualSpacing;
638
+ const yMeters = point.j * actualSpacing;
639
+ if (point.i === 0 && point.j === 0) {
640
+ labelText = "0,0,0";
641
+ }
642
+ else {
643
+ labelText = `${xMeters},${yMeters}`;
644
+ }
645
+ }
646
+ // Geographic coordinates (degrees).
647
+ else {
648
+ const cartographic = Cesium.Cartographic.fromCartesian(point.position);
649
+ const latitude = Cesium.Math.toDegrees(cartographic.latitude);
650
+ const longitude = Cesium.Math.toDegrees(cartographic.longitude);
651
+ const latFormatted = latitude.toFixed(5);
652
+ const lonFormatted = longitude.toFixed(5);
653
+ labelText = `${latFormatted}, ${lonFormatted}`;
654
+ }
655
+ this.gridLabels.add({
656
+ position: point.position,
657
+ text: labelText,
658
+ font: this.modelSpace && point.i === 0 && point.j === 0 ? "20px sans-serif" : "14px sans-serif",
659
+ fillColor: this.modelSpace && point.i === 0 && point.j === 0 ? Cesium.Color.WHITE : Cesium.Color.WHITE.withAlpha(0.5),
660
+ style: Cesium.LabelStyle.FILL,
661
+ horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
662
+ verticalOrigin: Cesium.VerticalOrigin.TOP,
663
+ pixelOffset: new Cesium.Cartesian2(0, 10),
664
+ // Fade out with distance.
665
+ translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 15000, 0.4)
666
+ });
667
+ labelCount++;
668
+ }
669
+ }
670
+ addGridLine(positions, primaryAxis = null) {
671
+ if (!this.gridLines || positions.length < 2) {
672
+ return;
673
+ }
674
+ let lineColor;
675
+ let lineWidth = 1.0;
676
+ let lineAlpha = 0.2;
677
+ // Red for X axis.
678
+ if (primaryAxis === "x") {
679
+ lineColor = Cesium.Color.RED;
680
+ lineAlpha = 0.5;
681
+ lineWidth = 1.5;
682
+ }
683
+ // Green for Y axis.
684
+ else if (primaryAxis === "y") {
685
+ lineColor = Cesium.Color.GREEN;
686
+ lineAlpha = 0.5;
687
+ lineWidth = 1.5;
688
+ }
689
+ // Default gray for regular grid lines.
690
+ else {
691
+ lineColor = Cesium.Color.LIGHTGRAY;
692
+ }
693
+ this.gridLines.add({
694
+ positions: positions,
695
+ width: lineWidth,
696
+ material: Cesium.Material.fromType("Color", {
697
+ color: lineColor.withAlpha(lineAlpha)
698
+ })
699
+ });
700
+ }
701
+ }
702
+ // Grid square size used when settings name none, in metres.
703
+ const DEFAULT_SPACING = 50;
704
+ var CesiumMapGrid;
705
+ (function (CesiumMapGrid) {
706
+ function GetCurrentSpacing(viewer) {
707
+ if (!viewer || viewer.isDestroyed()) {
708
+ return 0;
709
+ }
710
+ const handler = viewer[CLASS_INSTANCE_KEY];
711
+ return handler ? handler.getCurrentSpacing() : 0;
712
+ }
713
+ CesiumMapGrid.GetCurrentSpacing = GetCurrentSpacing;
714
+ function Remove(viewer) {
715
+ if (!viewer || viewer.isDestroyed()) {
716
+ return;
717
+ }
718
+ else if (!viewer[LAST_SETTINGS_KEY]) {
719
+ return;
720
+ }
721
+ if (!viewer[UPDATE_MARKER_KEY]) {
722
+ viewer[UPDATE_MARKER_KEY] = 1;
723
+ }
724
+ else {
725
+ viewer[UPDATE_MARKER_KEY]++;
726
+ }
727
+ // Dispose of the handler.
728
+ if (viewer[CLASS_INSTANCE_KEY]) {
729
+ viewer[CLASS_INSTANCE_KEY].Dispose();
730
+ viewer[CLASS_INSTANCE_KEY] = null;
731
+ }
732
+ viewer[LAST_SETTINGS_KEY] = {
733
+ ...viewer[LAST_SETTINGS_KEY],
734
+ enabled: false
735
+ };
736
+ }
737
+ CesiumMapGrid.Remove = Remove;
738
+ function Draw(viewer, spacingInMeters, modelSpace = true) {
739
+ if (!viewer || viewer.isDestroyed()) {
740
+ return;
741
+ }
742
+ let settings = viewer[LAST_SETTINGS_KEY];
743
+ if (settings) {
744
+ // See if the settings are the same as the last time.
745
+ if (settings.spacing === spacingInMeters &&
746
+ settings.modelSpace === modelSpace &&
747
+ settings.enabled) {
748
+ return;
749
+ }
750
+ }
751
+ if (!viewer[UPDATE_MARKER_KEY]) {
752
+ viewer[UPDATE_MARKER_KEY] = 1;
753
+ }
754
+ else {
755
+ viewer[UPDATE_MARKER_KEY]++;
756
+ }
757
+ settings = {
758
+ ...settings,
759
+ enabled: true,
760
+ spacing: spacingInMeters,
761
+ modelSpace: modelSpace
762
+ };
763
+ viewer[LAST_SETTINGS_KEY] = settings;
764
+ // Create or update the handler.
765
+ if (viewer[CLASS_INSTANCE_KEY]) {
766
+ viewer[CLASS_INSTANCE_KEY].Update(spacingInMeters, modelSpace);
767
+ }
768
+ else {
769
+ viewer[CLASS_INSTANCE_KEY] = new MapGridHandler(viewer, spacingInMeters, modelSpace);
770
+ }
771
+ }
772
+ CesiumMapGrid.Draw = Draw;
773
+ /**
774
+ * Draws a bookmark's grid, or removes the grid when it has none; only model-space draws one.
775
+ */
776
+ function Apply(viewer, grid, modelSpace) {
777
+ if ((grid === null || grid === void 0 ? void 0 : grid.enabled) && modelSpace) {
778
+ Draw(viewer, Number(grid.spacing) || DEFAULT_SPACING, modelSpace);
779
+ }
780
+ else {
781
+ Remove(viewer);
782
+ }
783
+ }
784
+ CesiumMapGrid.Apply = Apply;
785
+ function GetIsEnabled(viewer) {
786
+ if (!viewer || viewer.isDestroyed()) {
787
+ return false;
788
+ }
789
+ const settings = viewer[LAST_SETTINGS_KEY];
790
+ if (settings) {
791
+ return Boolean(settings.enabled);
792
+ }
793
+ return false;
794
+ }
795
+ CesiumMapGrid.GetIsEnabled = GetIsEnabled;
796
+ /**
797
+ * Returns the last applied grid settings.
798
+ */
799
+ function GetSettings(viewer) {
800
+ if (!viewer || viewer.isDestroyed()) {
801
+ return null;
802
+ }
803
+ return viewer[LAST_SETTINGS_KEY];
804
+ }
805
+ CesiumMapGrid.GetSettings = GetSettings;
806
+ })(CesiumMapGrid = exports.CesiumMapGrid || (exports.CesiumMapGrid = {}));
807
+ //# sourceMappingURL=cesium-map-grid.js.map