bruce-cesium 2.1.8 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,40 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
2
38
  Object.defineProperty(exports, "__esModule", { value: true });
3
39
  exports.PointClustering = void 0;
4
40
  var bruce_models_1 = require("bruce-models");
@@ -154,19 +190,208 @@ var Quad = /** @class */ (function () {
154
190
  this.southeast.Remove(point);
155
191
  }
156
192
  };
193
+ Quad.prototype.GetDistanceToQuad = function (pos3d) {
194
+ var minLat = this.boundary.y - this.boundary.h;
195
+ var maxLat = this.boundary.y + this.boundary.h;
196
+ var minLon = this.boundary.x - this.boundary.w;
197
+ var maxLon = this.boundary.x + this.boundary.w;
198
+ var points = [
199
+ // Corners.
200
+ new Cesium.Cartesian3(minLon, minLat, 0),
201
+ new Cesium.Cartesian3(minLon, maxLat, 0),
202
+ new Cesium.Cartesian3(maxLon, minLat, 0),
203
+ new Cesium.Cartesian3(maxLon, maxLat, 0),
204
+ // Center.
205
+ new Cesium.Cartesian3(this.boundary.x, this.boundary.y, 0)
206
+ ];
207
+ var shortest = Number.MAX_VALUE;
208
+ for (var _i = 0, points_1 = points; _i < points_1.length; _i++) {
209
+ var point = points_1[_i];
210
+ var distance = Cesium.Cartesian3.distance(pos3d, point);
211
+ if (distance < shortest) {
212
+ shortest = distance;
213
+ }
214
+ }
215
+ return shortest;
216
+ };
157
217
  return Quad;
158
218
  }());
219
+ var _clusterImageCache = new bruce_models_1.LRUCache(500);
220
+ var _clusterImageLoadedCache = new bruce_models_1.LRUCache(500);
221
+ function _loadClusterImage(params) {
222
+ return __awaiter(this, void 0, void 0, function () {
223
+ var size, txtColor, bgColor, text, iconUrl, key, cacheData, prom;
224
+ return __generator(this, function (_a) {
225
+ switch (_a.label) {
226
+ case 0:
227
+ size = params.size, txtColor = params.txtColor, bgColor = params.bgColor, text = params.text, iconUrl = params.iconUrl;
228
+ key = "".concat(size, "-").concat(txtColor, "-").concat(bgColor, "-").concat(text, "-").concat(iconUrl);
229
+ cacheData = _clusterImageCache.Get(key);
230
+ if (!cacheData) return [3 /*break*/, 2];
231
+ return [4 /*yield*/, cacheData];
232
+ case 1: return [2 /*return*/, _a.sent()];
233
+ case 2:
234
+ prom = new Promise(function (res, rej) {
235
+ var canvas = document.createElement("canvas");
236
+ canvas.width = size;
237
+ canvas.height = size;
238
+ var ctx = canvas.getContext("2d");
239
+ var WHITESPACE_PADDING_PERCENT = 0.05;
240
+ var radius = (size / 2) - (size * WHITESPACE_PADDING_PERCENT);
241
+ var drawWithoutImage = function (img) {
242
+ ctx.beginPath();
243
+ ctx.arc(size / 2, size / 2, radius, 0, 2 * Math.PI, false);
244
+ var fill = null;
245
+ var txt = null;
246
+ if (img) {
247
+ var brightness = calculateImageBrightness(img);
248
+ fill = brightness < 128 ? "white" : "#114d78";
249
+ txt = brightness < 128 ? "black" : "white";
250
+ }
251
+ else {
252
+ fill = bgColor ? bgColor : "#114d78";
253
+ txt = txtColor ? txtColor : "white";
254
+ }
255
+ ctx.fillStyle = fill;
256
+ ctx.fill();
257
+ var maxTextWidth = size * 0.7;
258
+ var maxTextHeight = size * 0.7;
259
+ var minTextSize = Math.floor(size / 12);
260
+ var textSize = findOptimalFontSize(text, maxTextWidth, maxTextHeight, minTextSize);
261
+ ctx.font = "bold ".concat(textSize, "px Arial");
262
+ ctx.fillStyle = txt;
263
+ ctx.textAlign = "center";
264
+ ctx.textBaseline = "middle";
265
+ ctx.fillText(text, size / 2, size / 2);
266
+ };
267
+ var drawWithImage = function (img) {
268
+ var aspectRatio = img.width / img.height;
269
+ var imageSize = Math.min(radius, img.width, img.height);
270
+ if (imageSize / aspectRatio > radius) {
271
+ imageSize = radius * aspectRatio;
272
+ }
273
+ var imageX = (size - imageSize) / 2;
274
+ var imageY = (size - imageSize) / 2 - imageSize / 3;
275
+ ctx.beginPath();
276
+ ctx.arc(size / 2, size / 2, radius, 0, 2 * Math.PI, false);
277
+ var brightness = calculateImageBrightness(img);
278
+ var bgColor = brightness < 128 ? "white" : "#114d78";
279
+ var txtColor = brightness < 128 ? "black" : "white";
280
+ ctx.fillStyle = bgColor;
281
+ ctx.fill();
282
+ ctx.shadowColor = "rgba(0, 0, 0, 0.3)";
283
+ ctx.shadowOffsetX = 3;
284
+ ctx.shadowOffsetY = 3;
285
+ ctx.shadowBlur = 5;
286
+ ctx.drawImage(img, imageX, imageY, imageSize, imageSize);
287
+ var padding = imageSize / 7;
288
+ var maxTextWidth = imageSize;
289
+ var maxTextHeight = imageSize - padding;
290
+ var minTextSize = Math.floor(imageSize / 5);
291
+ var textSize = findOptimalFontSize(text, maxTextWidth, maxTextHeight, minTextSize);
292
+ ctx.font = "bold ".concat(textSize, "px Arial");
293
+ ctx.fillStyle = txtColor;
294
+ ctx.textAlign = "center";
295
+ ctx.textBaseline = "top";
296
+ ctx.shadowColor = "rgba(0, 0, 0, 0)";
297
+ ctx.shadowOffsetX = 0;
298
+ ctx.shadowOffsetY = 0;
299
+ ctx.shadowBlur = 0;
300
+ ctx.fillText(text, size / 2, imageY + imageSize + padding);
301
+ };
302
+ var findOptimalFontSize = function (text, maxWidth, maxHeight, minSize) {
303
+ var fontSize = maxHeight;
304
+ var tempCanvas = document.createElement("canvas");
305
+ var tempCtx = tempCanvas.getContext("2d");
306
+ while (fontSize > minSize) {
307
+ tempCtx.font = "bold ".concat(fontSize, "px Arial");
308
+ var measuredWidth = tempCtx.measureText(text).width;
309
+ var measuredHeight = fontSize * 1.2;
310
+ if (measuredWidth <= maxWidth && measuredHeight <= maxHeight) {
311
+ break;
312
+ }
313
+ fontSize--;
314
+ }
315
+ return fontSize;
316
+ };
317
+ var calculateImageBrightness = function (img) {
318
+ var brightness = 0;
319
+ var tempCanvas = document.createElement("canvas");
320
+ tempCanvas.width = img.width;
321
+ tempCanvas.height = img.height;
322
+ var tempCtx = tempCanvas.getContext("2d");
323
+ tempCtx.drawImage(img, 0, 0);
324
+ var imageData = tempCtx.getImageData(0, 0, img.width, img.height).data;
325
+ for (var i = 0; i < imageData.length; i += 4) {
326
+ var r = imageData[i];
327
+ var g = imageData[i + 1];
328
+ var b = imageData[i + 2];
329
+ brightness += (r + g + b) / 3;
330
+ }
331
+ return Math.round(brightness / (img.width * img.height));
332
+ };
333
+ if (iconUrl) {
334
+ var img_1 = new Image();
335
+ img_1.crossOrigin = "anonymous";
336
+ img_1.src = iconUrl;
337
+ img_1.onload = function () {
338
+ if (size > 50) {
339
+ drawWithImage(img_1);
340
+ }
341
+ else {
342
+ drawWithoutImage(img_1);
343
+ }
344
+ res(canvas);
345
+ };
346
+ img_1.onerror = function () {
347
+ drawWithoutImage();
348
+ res(canvas);
349
+ };
350
+ }
351
+ else {
352
+ drawWithoutImage();
353
+ res(canvas);
354
+ }
355
+ });
356
+ _clusterImageCache.Set(key, prom);
357
+ prom.then(function (canvas) {
358
+ _clusterImageLoadedCache.Set(key, canvas);
359
+ _clusterImageCache.Set(key, null);
360
+ });
361
+ return [4 /*yield*/, prom];
362
+ case 3: return [2 /*return*/, _a.sent()];
363
+ }
364
+ });
365
+ });
366
+ }
367
+ function getClusterImage(params) {
368
+ var size = params.size, txtColor = params.txtColor, bgColor = params.bgColor, text = params.text, iconUrl = params.iconUrl;
369
+ var key = "".concat(size, "-").concat(txtColor, "-").concat(bgColor, "-").concat(text, "-").concat(iconUrl);
370
+ var cacheData = _clusterImageLoadedCache.Get(key);
371
+ // If available then return.
372
+ if (cacheData) {
373
+ return cacheData;
374
+ }
375
+ // If not available then queue for it to cook.
376
+ else {
377
+ _loadClusterImage(params);
378
+ }
379
+ return null;
380
+ }
381
+ var FORCE_UPDATE_BATCH_SIZE = 1000;
382
+ var FORCE_UPDATE_BATCH_DELAY = 100;
159
383
  var PointClustering = /** @class */ (function () {
160
384
  function PointClustering(register, menuItemId) {
161
385
  var _this = this;
162
386
  this.disposed = false;
163
387
  this.registeredEntityIds = new Set();
388
+ // Queue to force update entities.
389
+ this.updateEntityQueue = [];
164
390
  this.register = register;
165
391
  this.viewer = register.Viewer;
166
392
  this.menuItemId = menuItemId;
167
- this.updateClusterSpacing(0);
168
393
  var boundary = new Rectangle(0, 0, 360, 180);
169
- this.quadTree = new Quad(boundary, 4);
394
+ this.quadTree = new Quad(boundary, 30);
170
395
  this.prevClusteredEntities = new Set();
171
396
  this.currClusteredEntities = new Set();
172
397
  this.clusterEntities = new Map();
@@ -175,6 +400,39 @@ var PointClustering = /** @class */ (function () {
175
400
  }, 1000);
176
401
  this.listenCamera();
177
402
  }
403
+ PointClustering.prototype.queueForceUpdate = function (entityIds) {
404
+ for (var i = 0; i < entityIds.length; i++) {
405
+ if (this.updateEntityQueue.includes(entityIds[i])) {
406
+ continue;
407
+ }
408
+ this.updateEntityQueue.push(entityIds[i]);
409
+ }
410
+ this.runForceUpdateQueue();
411
+ };
412
+ PointClustering.prototype.runForceUpdateQueue = function () {
413
+ var _this = this;
414
+ if (!this.updateEntityQueue.length) {
415
+ return;
416
+ }
417
+ if (this.queueInterval) {
418
+ return;
419
+ }
420
+ this.queueInterval = setInterval(function () {
421
+ if (_this.disposed) {
422
+ clearInterval(_this.queueInterval);
423
+ _this.queueInterval = null;
424
+ return;
425
+ }
426
+ var ids = _this.updateEntityQueue.splice(0, FORCE_UPDATE_BATCH_SIZE);
427
+ _this.register.ForceUpdate({
428
+ entityIds: ids
429
+ });
430
+ if (!_this.updateEntityQueue.length) {
431
+ clearInterval(_this.queueInterval);
432
+ _this.queueInterval = null;
433
+ }
434
+ }, FORCE_UPDATE_BATCH_DELAY);
435
+ };
178
436
  /**
179
437
  * Starts listening to camera changes.
180
438
  * This will trigger the clustering update whenever camera is moved.
@@ -229,6 +487,7 @@ var PointClustering = /** @class */ (function () {
229
487
  this.clusterEntities.clear();
230
488
  this.unlistenCamera();
231
489
  this.disposed = true;
490
+ this.updateEntityQueue = [];
232
491
  // Restore entities.
233
492
  var toUpdateIds = [];
234
493
  for (var _c = 0, _d = Array.from(this.registeredEntityIds); _c < _d.length; _c++) {
@@ -256,8 +515,8 @@ var PointClustering = /** @class */ (function () {
256
515
  PointClustering.prototype.calculateCentroid = function (points) {
257
516
  var lonSum = 0;
258
517
  var latSum = 0;
259
- for (var _i = 0, points_1 = points; _i < points_1.length; _i++) {
260
- var point = points_1[_i];
518
+ for (var _i = 0, points_2 = points; _i < points_2.length; _i++) {
519
+ var point = points_2[_i];
261
520
  lonSum += point.lon;
262
521
  latSum += point.lat;
263
522
  }
@@ -277,9 +536,10 @@ var PointClustering = /** @class */ (function () {
277
536
  // 1: Update precision.
278
537
  // This defines how far apart these clusters can be.
279
538
  var cameraHeight = this.viewer.camera.positionCartographic.height;
280
- this.updateClusterSpacing(cameraHeight);
539
+ this.getClusterSpacing(cameraHeight);
281
540
  // 2: Get clusters.
282
541
  var _a = this.getClusters(), clusters = _a.clusters, noLongerClustered = _a.noLongerClustered;
542
+ var entitiesToUpdate = [];
283
543
  // 3: Remove all cesium cluster entities that are no longer clustered.
284
544
  for (var _i = 0, _b = Array.from(noLongerClustered); _i < _b.length; _i++) {
285
545
  var id = _b[_i];
@@ -296,193 +556,180 @@ var PointClustering = /** @class */ (function () {
296
556
  });
297
557
  if (rego && rego.suppressShow) {
298
558
  rego.suppressShow = false;
299
- this.register.ForceUpdate({
300
- entityIds: [id],
301
- });
559
+ entitiesToUpdate.push(id);
302
560
  }
303
561
  }
304
562
  }
305
563
  var getScale = function (count) {
306
- var baseSize = 20;
307
- var scalingFactor = 2;
308
- var scale = baseSize + scalingFactor * (count - 1);
309
- scale = Math.min(scale, 120);
310
- return scale;
564
+ // const MIN_SCALE = 15;
565
+ // const MAX_SCALE = 80;
566
+ // const SCALE_PER_POINT = 1.01;
567
+ // let scale = MIN_SCALE + (count * SCALE_PER_POINT);
568
+ // scale = Math.min(scale, MAX_SCALE);
569
+ // return scale;
570
+ if (count >= 10000) {
571
+ return 140;
572
+ }
573
+ if (count >= 1000) {
574
+ return 120;
575
+ }
576
+ if (count >= 100) {
577
+ return 90;
578
+ }
579
+ if (count >= 10) {
580
+ return 80;
581
+ }
582
+ return 70;
311
583
  };
312
- var getLabel = function (count) {
313
- var color = _this.pointColorTxt ? _this.pointColorTxt : "white";
314
- var canvas = document.createElement("canvas");
315
- var ctx = canvas.getContext("2d");
316
- var size = getScale(count);
317
- canvas.width = size;
318
- canvas.height = size;
319
- ctx.font = "bold 20px Arial";
320
- ctx.fillStyle = color;
321
- ctx.textAlign = "center";
322
- ctx.textBaseline = "middle";
323
- ctx.fillText(String(count), size / 2, size / 2);
324
- return canvas;
584
+ /**
585
+ * Generate circle with label in center.
586
+ * @param count
587
+ * @returns
588
+ */
589
+ var getCanvas = function (count) {
590
+ var params = {
591
+ size: getScale(count),
592
+ txtColor: _this.pointColorTxt,
593
+ bgColor: _this.pointColorBg,
594
+ text: String(count),
595
+ iconUrl: _this.iconUrl
596
+ };
597
+ return getClusterImage(params);
325
598
  };
326
- // 5: iterate over clusters and add/update them as Cesium entities.
327
- for (var _e = 0, clusters_1 = clusters; _e < clusters_1.length; _e++) {
328
- var cluster = clusters_1[_e];
599
+ var _loop_1 = function (cluster) {
329
600
  var clusterId = cluster.center.id;
330
- var clusterEntity = this.clusterEntities.get(clusterId);
331
- var centroid = this.calculateCentroid(cluster.points);
601
+ var clusterEntity = this_1.clusterEntities.get(clusterId);
602
+ var centroid = this_1.calculateCentroid(cluster.points);
332
603
  var count = cluster.points.length;
333
604
  if (clusterEntity) {
334
605
  clusterEntity.position = Cesium.Cartesian3.fromDegrees(centroid.lon, centroid.lat, 150);
335
- clusterEntity.point.pixelSize = getScale(cluster.points.length);
336
- clusterEntity.billboard.image = getLabel(cluster.points.length);
337
- clusterEntity.billboard.width = getScale(cluster.points.length);
606
+ clusterEntity.billboard.image = new Cesium.CallbackProperty(function () {
607
+ return getCanvas(count);
608
+ }, false),
609
+ clusterEntity.billboard.width = getScale(cluster.points.length);
338
610
  clusterEntity.billboard.height = getScale(cluster.points.length);
339
611
  }
340
612
  else {
341
- clusterEntity = this.viewer.entities.add({
613
+ clusterEntity = this_1.viewer.entities.add({
342
614
  position: Cesium.Cartesian3.fromDegrees(centroid.lon, centroid.lat, 150),
343
- point: {
344
- heightReference: Cesium.HeightReference.NONE,
345
- pixelSize: getScale(count),
346
- color: this.pointColorBg ? this.pointColorBg : Cesium.Color.fromCssColorString("#4287f5")
347
- },
348
615
  billboard: {
349
616
  heightReference: Cesium.HeightReference.NONE,
350
- image: getLabel(count),
617
+ image: new Cesium.CallbackProperty(function () {
618
+ return getCanvas(count);
619
+ }, false),
351
620
  width: getScale(count),
352
621
  height: getScale(count),
353
622
  verticalOrigin: Cesium.VerticalOrigin.CENTER,
354
623
  horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
355
- disableDepthTestDistance: Number.POSITIVE_INFINITY
624
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
625
+ scaleByDistance: new Cesium.NearFarScalar(1.5e2, 0.5, 3.0e7, 0.1),
356
626
  }
357
627
  });
358
- this.clusterEntities.set(clusterId, clusterEntity);
628
+ this_1.clusterEntities.set(clusterId, clusterEntity);
359
629
  }
360
630
  for (var i = 0; i < cluster.points.length; i++) {
361
631
  var entityId = cluster.points[i].id;
362
- var rego = this.register.GetRego({
632
+ var rego = this_1.register.GetRego({
363
633
  entityId: entityId,
364
- menuItemId: this.menuItemId
634
+ menuItemId: this_1.menuItemId
365
635
  });
366
636
  if (rego && !rego.suppressShow) {
367
637
  rego.suppressShow = true;
368
- this.register.ForceUpdate({
369
- entityIds: [entityId],
370
- });
638
+ entitiesToUpdate.push(entityId);
371
639
  }
372
640
  }
641
+ };
642
+ var this_1 = this;
643
+ // 5: iterate over clusters and add/update them as Cesium entities.
644
+ for (var _e = 0, clusters_1 = clusters; _e < clusters_1.length; _e++) {
645
+ var cluster = clusters_1[_e];
646
+ _loop_1(cluster);
373
647
  }
374
- var _loop_1 = function (clusterId, clusterEntity) {
648
+ this.queueForceUpdate(entitiesToUpdate);
649
+ var _loop_2 = function (clusterId, clusterEntity) {
375
650
  if (!clusters.find(function (x) { return x.center.id == clusterId; })) {
376
- this_1.viewer.entities.remove(clusterEntity);
377
- this_1.clusterEntities.delete(clusterId);
651
+ this_2.viewer.entities.remove(clusterEntity);
652
+ this_2.clusterEntities.delete(clusterId);
378
653
  }
379
654
  };
380
- var this_1 = this;
655
+ var this_2 = this;
381
656
  // 6: Iterate over existing cluster entities and remove those that are no longer clustered.
382
657
  for (var _f = 0, _g = Array.from(this.clusterEntities); _f < _g.length; _f++) {
383
658
  var _h = _g[_f], clusterId = _h[0], clusterEntity = _h[1];
384
- _loop_1(clusterId, clusterEntity);
659
+ _loop_2(clusterId, clusterEntity);
385
660
  }
386
661
  };
387
- /**
388
- * Updates how apart clusters can be based on camera distance.
389
- * @param cameraHeight
390
- */
391
- PointClustering.prototype.updateClusterSpacing = function (cameraHeight) {
392
- // Camera height thresholds in meters.
393
- var cameraHeightThresholds = [2000, 5000, 8000, 13000, 25000, 40000];
662
+ PointClustering.prototype.getClusterSpacing = function (distanceFromCluster) {
663
+ // Distance thresholds in meters from the cluster.
664
+ var distanceThresholds = [3000, 4000, 5300, 6000, 7000];
394
665
  // Distance increments in degrees.
395
- var distanceIncrements = [0.005, 0.01, 0.02, 0.04, 0.1, 0.5];
396
- // Find the appropriate spacing based on the camera height.
397
- var spacing = 0;
398
- for (var i = 0; i < cameraHeightThresholds.length; i++) {
399
- if (cameraHeight && cameraHeight < cameraHeightThresholds[i]) {
400
- spacing += distanceIncrements[i];
401
- break;
666
+ var distanceIncrements = [0.001, 0.002, 0.01, 0.03, 0.09];
667
+ var index = 0;
668
+ if (distanceFromCluster) {
669
+ for (var i = 0; i < distanceThresholds.length; i++) {
670
+ if (distanceFromCluster > distanceThresholds[i]) {
671
+ index = i;
672
+ }
402
673
  }
403
- spacing += distanceIncrements[i];
404
674
  }
405
- this.distanceBetweenClusters = spacing;
675
+ return distanceIncrements[index];
406
676
  };
407
- /**
408
- * Gathers clusters.
409
- * @returns
410
- */
411
677
  PointClustering.prototype.getClusters = function () {
412
678
  var _this = this;
413
679
  this.currClusteredEntities.clear();
414
680
  var clusters = [];
415
681
  var processedPoints = new Set();
416
682
  var cameraPosition = this.viewer.camera.position;
417
- for (var _i = 0, _a = this.quadTree.points; _i < _a.length; _i++) {
418
- var point = _a[_i];
419
- // Skip points already processed in previous clusters.
420
- if (processedPoints.has(point.id)) {
421
- continue;
422
- }
423
- // Skip points closer than 2000 meters to the camera.
424
- var cartesian3 = Cesium.Cartesian3.fromDegrees(point.lon, point.lat);
425
- if (Cesium.Cartesian3.distance(cartesian3, cameraPosition) <= 2000) {
426
- continue;
427
- }
428
- var found = [];
429
- var nearbyPoints = this.quadTree.Query(new Circle(point.lon, point.lat, this.distanceBetweenClusters), found);
430
- if (nearbyPoints.length > 1) {
431
- var cluster = { center: point, points: [] };
432
- for (var _b = 0, nearbyPoints_1 = nearbyPoints; _b < nearbyPoints_1.length; _b++) {
433
- var nearby = nearbyPoints_1[_b];
434
- if (!cluster.points.includes(nearby)) {
435
- cluster.points.push(nearby);
436
- processedPoints.add(nearby.id);
437
- this.currClusteredEntities.add(nearby.id);
683
+ var MIN_CAMERA_DISTANCE = 5000;
684
+ var processQuad = function (quad) {
685
+ // Distance to quad.
686
+ // TODO: Needs improvement.
687
+ var distanceToQuad = quad.GetDistanceToQuad(cameraPosition);
688
+ // Skip quads that are too close.
689
+ if (distanceToQuad >= MIN_CAMERA_DISTANCE) {
690
+ for (var _i = 0, _a = quad.points; _i < _a.length; _i++) {
691
+ var point = _a[_i];
692
+ // Skip points already processed in previous clusters.
693
+ if (processedPoints.has(point.id)) {
694
+ continue;
695
+ }
696
+ // Skip points closer than MIN_CAMERA_DISTANCE meters to the camera.
697
+ var cartesian3 = Cesium.Cartesian3.fromDegrees(point.lon, point.lat);
698
+ var distanceFromCluster = Cesium.Cartesian3.distance(cartesian3, cameraPosition);
699
+ if (distanceFromCluster <= MIN_CAMERA_DISTANCE) {
700
+ continue;
701
+ }
702
+ var found = [];
703
+ var nearbyPoints = quad.Query(new Circle(point.lon, point.lat, _this.getClusterSpacing(distanceFromCluster)), found);
704
+ if (nearbyPoints.length > 1) {
705
+ var cluster = { center: point, points: [] };
706
+ for (var _b = 0, nearbyPoints_1 = nearbyPoints; _b < nearbyPoints_1.length; _b++) {
707
+ var nearby = nearbyPoints_1[_b];
708
+ if (!cluster.points.includes(nearby)) {
709
+ cluster.points.push(nearby);
710
+ processedPoints.add(nearby.id);
711
+ _this.currClusteredEntities.add(nearby.id);
712
+ }
713
+ }
714
+ clusters.push(cluster);
438
715
  }
439
716
  }
440
- clusters.push(cluster);
441
717
  }
442
- }
718
+ if (quad.divided) {
719
+ processQuad(quad.northwest);
720
+ processQuad(quad.northeast);
721
+ processQuad(quad.southwest);
722
+ processQuad(quad.southeast);
723
+ }
724
+ };
725
+ processQuad(this.quadTree);
443
726
  // Filter out clusters with only one point.
444
727
  var validClusters = clusters.filter(function (cluster) { return cluster.points.length > 1; });
445
- // Merge adjacent clusters.
446
- var mergedClusters = this.mergeClusters(validClusters);
447
728
  // Get the entity IDs that are no longer clustered
448
729
  var noLongerClustered = new Set(Array.from(this.prevClusteredEntities).filter(function (id) { return !_this.currClusteredEntities.has(id); }));
449
730
  // Update the previous clustered entities ref.
450
731
  this.prevClusteredEntities = new Set(this.currClusteredEntities);
451
- return { clusters: mergedClusters, noLongerClustered: noLongerClustered };
452
- };
453
- /**
454
- * Merges clusters that are nearby based on the distanceBetweenClusters value.
455
- * @param clusters
456
- * @returns
457
- */
458
- PointClustering.prototype.mergeClusters = function (clusters) {
459
- var _a;
460
- var mergedClusters = [].concat(clusters);
461
- // Keep looping while merges keep happening.
462
- var mergeOccurred = true;
463
- while (mergeOccurred) {
464
- mergeOccurred = false;
465
- for (var i = 0; i < mergedClusters.length - 1; i++) {
466
- for (var j = i + 1; j < mergedClusters.length; j++) {
467
- var cluster1 = mergedClusters[i];
468
- var cluster2 = mergedClusters[j];
469
- var centerDistance = this.calculateDistance(cluster1.center.lon, cluster1.center.lat, cluster2.center.lon, cluster2.center.lat);
470
- var distanceThreshold = this.distanceBetweenClusters;
471
- if (centerDistance <= distanceThreshold) {
472
- // Merge clusters.
473
- (_a = cluster1.points).push.apply(_a, cluster2.points);
474
- mergedClusters.splice(j, 1);
475
- this.removeClusterEntity(cluster2.center.id);
476
- mergeOccurred = true;
477
- break;
478
- }
479
- }
480
- if (mergeOccurred) {
481
- break;
482
- }
483
- }
484
- }
485
- return mergedClusters;
732
+ return { clusters: validClusters, noLongerClustered: noLongerClustered };
486
733
  };
487
734
  /**
488
735
  * Removes Cesium cluster entity.
@@ -510,24 +757,7 @@ var PointClustering = /** @class */ (function () {
510
757
  PointClustering.prototype.addPoint = function (id, cartesian3) {
511
758
  var point = this.convertCartesianToCartographic(cartesian3);
512
759
  point.id = id;
513
- this.quadTree.Insert(point);
514
- };
515
- /**
516
- * Calculates rough distance across earth between two points.
517
- * @param lon1
518
- * @param lat1
519
- * @param lon2
520
- * @param lat2
521
- * @returns
522
- */
523
- PointClustering.prototype.calculateDistance = function (lon1, lat1, lon2, lat2) {
524
- var lonDelta = Math.abs(lon1 - lon2);
525
- var latDelta = Math.abs(lat1 - lat2);
526
- // Approximate radius of the Earth in kilometers
527
- var earthRadius = 6371;
528
- var distance = 2 * Math.asin(Math.sqrt(Math.sin(latDelta / 2) * Math.sin(latDelta / 2) +
529
- Math.cos(lat1) * Math.cos(lat2) * Math.sin(lonDelta / 2) * Math.sin(lonDelta / 2))) * earthRadius;
530
- return distance;
760
+ return this.quadTree.Insert(point);
531
761
  };
532
762
  /**
533
763
  * Adds entity to clustering logic.
@@ -551,14 +781,14 @@ var PointClustering = /** @class */ (function () {
551
781
  return false;
552
782
  }
553
783
  if (!this.pointColorBg && entity.point) {
554
- this.pointColorBg = GetValue(this.viewer, entity.point.color);
555
- if (this.pointColorBg) {
784
+ var pointColorBg = GetValue(this.viewer, entity.point.color);
785
+ if (pointColorBg) {
556
786
  var cColor = null;
557
- if (this.pointColorBg instanceof Object) {
558
- cColor = new Cesium.Color(this.pointColorBg.red, this.pointColorBg.green, this.pointColorBg.blue, this.pointColorBg.alpha);
787
+ if (pointColorBg instanceof Object) {
788
+ cColor = new Cesium.Color(pointColorBg.red, pointColorBg.green, pointColorBg.blue, pointColorBg.alpha);
559
789
  }
560
- else if (typeof this.pointColorBg === "string") {
561
- cColor = Cesium.Color.fromCssColorString(this.pointColorBg);
790
+ else if (typeof pointColorBg === "string") {
791
+ cColor = Cesium.Color.fromCssColorString(pointColorBg);
562
792
  }
563
793
  // Determine if text color should instead be black based on background.
564
794
  // cColor contains r,g,b,a values where r,g,b are in the range [0,1].
@@ -570,11 +800,21 @@ var PointClustering = /** @class */ (function () {
570
800
  else {
571
801
  this.pointColorTxt = "white";
572
802
  }
803
+ this.pointColorBg = cColor.toCssColorString();
573
804
  }
574
805
  }
575
806
  }
807
+ if (!this.iconUrl && entity.billboard) {
808
+ var iconUrl = GetValue(this.viewer, entity.billboard.image);
809
+ if (typeof iconUrl == "string") {
810
+ this.iconUrl = iconUrl;
811
+ }
812
+ }
813
+ var added = this.addPoint(id, pos3d);
814
+ if (!added) {
815
+ return false;
816
+ }
576
817
  this.registeredEntityIds.add(id);
577
- this.addPoint(id, pos3d);
578
818
  this.updateQueue.Call();
579
819
  return true;
580
820
  };