From 6638236f80c80d1f9e8d207a6ccdda037f69cdb5 Mon Sep 17 00:00:00 2001 From: Wagram Airiian Date: Tue, 25 Jun 2024 13:31:28 +0200 Subject: [PATCH 1/4] * Add clustering for feature search pins * Change priority of processed tiles for feature search --- erdblick_app/app/cesium.ts | 5 +- erdblick_app/app/feature.search.service.ts | 35 ++++-- erdblick_app/app/featurefilter.worker.ts | 8 +- erdblick_app/app/map.service.ts | 2 +- erdblick_app/app/view.component.ts | 120 ++++++++++++++++++++- 5 files changed, 151 insertions(+), 19 deletions(-) diff --git a/erdblick_app/app/cesium.ts b/erdblick_app/app/cesium.ts index fe130db..7dec33c 100644 --- a/erdblick_app/app/cesium.ts +++ b/erdblick_app/app/cesium.ts @@ -50,8 +50,9 @@ export type PrimitiveCollection = Cesium.PrimitiveCollection; export const PrimitiveCollection = Cesium.PrimitiveCollection; export type BillboardCollection = Cesium.BillboardCollection; export const BillboardCollection = Cesium.BillboardCollection; -export type PointPrimitiveCollection = Cesium.PointPrimitiveCollection; -export const PointPrimitiveCollection = Cesium.PointPrimitiveCollection; +export type PinBuilder = Cesium.PinBuilder; +export const PinBuilder = Cesium.PinBuilder; +export const SceneTransforms = Cesium.SceneTransforms; export type Entity = Cesium.Entity; export const Entity = Cesium.Entity; export type Camera = Cesium.Camera; diff --git a/erdblick_app/app/feature.search.service.ts b/erdblick_app/app/feature.search.service.ts index 6ae2589..a565043 100644 --- a/erdblick_app/app/feature.search.service.ts +++ b/erdblick_app/app/feature.search.service.ts @@ -13,6 +13,7 @@ export class FeatureSearchService { currentQuery: string = "" workers: Array = [] visualization: BillboardCollection = new BillboardCollection(); + visualizationPositions: Array = []; visualizationChanged: Subject = new Subject(); resultsPerTile: Map = new Map(); workQueue: Array = []; @@ -65,9 +66,21 @@ export class FeatureSearchService { this.startTime = Date.now(); // Fill up work queue and start processing. + // TODO: What if we move / change the viewport during the search? + let priorityTiles = new Array(); + let nonPriorityTiles = new Array(); for (const [_, tile] of this.mapService.loadedTileLayers) { - this.workQueue.push(tile); + if ([...this.mapService.visualizedTileLayers.values()].some(tileVisializations => { + return tileVisializations.some(tileVisialization => { + return tileVisialization.tile.tileId == tile.tileId && tileVisialization.tile.mapName == tile.mapName; + }); + })) { + priorityTiles.push(tile); + } else { + nonPriorityTiles.push(tile); + } } + this.workQueue = [...nonPriorityTiles, ...priorityTiles]; this.totalTiles = this.workQueue.length; this.isFeatureSearchActive.next(true); @@ -92,6 +105,7 @@ export class FeatureSearchService { this.stop(); this.currentQuery = ""; this.visualization.removeAll(); + this.visualizationPositions = []; this.resultsPerTile.clear(); this.workQueue = []; this.totalTiles = 0; @@ -118,15 +132,16 @@ export class FeatureSearchService { tileResult.billboardPrimitiveIndices = []; for (const [_, __, position] of tileResult.matches) { - tileResult.billboardPrimitiveIndices.push(this.visualization.length); - this.visualization.add({ - position: position, - image: this.markerGraphics(), - width: 32, - height: 32, - pixelOffset: new Cartesian2(0, -10), - eyeOffset: new Cartesian3(0, 0, -100) - }); + this.visualizationPositions.push(new Cartesian3(position.x, position.y, position.z)); + tileResult.billboardPrimitiveIndices.push(this.visualizationPositions.length); + // this.visualization.add({ + // position: position, + // image: this.markerGraphics(), + // width: 32, + // height: 32, + // pixelOffset: new Cartesian2(0, -10), + // eyeOffset: new Cartesian3(0, 0, -100) + // }); } } diff --git a/erdblick_app/app/featurefilter.worker.ts b/erdblick_app/app/featurefilter.worker.ts index b4ef2a9..be1f20d 100644 --- a/erdblick_app/app/featurefilter.worker.ts +++ b/erdblick_app/app/featurefilter.worker.ts @@ -10,10 +10,10 @@ export interface SearchWorkerTask { } export interface SearchResultForTile { - query: string; - numFeatures: number; - matches: Array<[string, string, [number, number, number]]>; // Array of (MapTileKey, FeatureId, (x, y, z)) - billboardPrimitiveIndices?: Array; // Used by search service for visualization. + query: string; + numFeatures: number; + matches: Array<[string, string, {x: number, y: number, z: number}]>; // Array of (MapTileKey, FeatureId, (x, y, z)) + billboardPrimitiveIndices?: Array; // Used by search service for visualization. } addEventListener('message', async ({data}) => { diff --git a/erdblick_app/app/map.service.ts b/erdblick_app/app/map.service.ts index 0de11cd..0e35d3d 100644 --- a/erdblick_app/app/map.service.ts +++ b/erdblick_app/app/map.service.ts @@ -64,7 +64,7 @@ export class MapService { public maps: BehaviorSubject> = new BehaviorSubject>(new Map()); public loadedTileLayers: Map; - private visualizedTileLayers: Map; + public visualizedTileLayers: Map; private currentFetch: any; private currentViewport: ViewportProperties; private currentVisibleTileIds: Set; diff --git a/erdblick_app/app/view.component.ts b/erdblick_app/app/view.component.ts index 047cb31..d63d42a 100644 --- a/erdblick_app/app/view.component.ts +++ b/erdblick_app/app/view.component.ts @@ -15,7 +15,10 @@ import { ScreenSpaceEventType, UrlTemplateImageryProvider, Viewer, - HeightReference + HeightReference, + PinBuilder, + BillboardCollection, + SceneTransforms } from "./cesium"; import {ParametersService} from "./parameters.service"; import {AfterViewInit, Component} from "@angular/core"; @@ -51,6 +54,7 @@ export class ErdblickViewComponent implements AfterViewInit { private tileVisForPrimitive: Map; private openStreetMapLayer: ImageryLayer | null = null; private marker: Entity | null = null; + private pinBuilder: PinBuilder | null = null; /** * Construct a Cesium View with a Model. @@ -127,6 +131,8 @@ export class ErdblickViewComponent implements AfterViewInit { } ); + this.pinBuilder = new PinBuilder(); + this.openStreetMapLayer = this.viewer.imageryLayers.addImageryProvider(this.getOpenStreetMapLayerProvider()); this.openStreetMapLayer.alpha = 0.3; @@ -203,8 +209,11 @@ export class ErdblickViewComponent implements AfterViewInit { this.viewer.scene.primitives.add(this.featureSearchService.visualization); this.featureSearchService.visualizationChanged.subscribe(_ => { + this.addBillboards(); this.viewer.scene.requestRender(); }); + // Set up camera event handlers for clustering + this.setupCameraHandlers(); this.jumpService.markedPosition.subscribe(position => { if (position.length >= 2) { @@ -384,7 +393,6 @@ export class ErdblickViewComponent implements AfterViewInit { } } - addMarker(cartesian: Cartesian3) { if (this.marker) { this.viewer.entities.remove(this.marker); @@ -402,4 +410,112 @@ export class ErdblickViewComponent implements AfterViewInit { } }); } + + addBillboards() { + this.featureSearchService.visualization.removeAll(); + + this.featureSearchService.visualizationPositions.forEach((position) => { + this.featureSearchService.visualization.add({ + position: position, + image: this.featureSearchService.markerGraphics(), + width: 32, + height: 32, + pixelOffset: new Cartesian2(0, -10), + eyeOffset: new Cartesian3(0, 0, -100), + color: Color.fromCssColorString(this.featureSearchService.pointColor) + }); + }); + } + + performClustering(pixelRange: number, minimumClusterSize: number) { + // Calculate screen positions + const screenPositions = this.featureSearchService.visualizationPositions.map(pos => + SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, pos) + ); + + // Clustering logic + const clusters: { [key: string]: Cartesian3[] } = {}; + + screenPositions.forEach((screenPosition: Cartesian2, index: number) => { + let addedToCluster = false; + + for (const clusterKey in clusters) { + const cluster = clusters[clusterKey]; + const clusterScreenPosition = SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, cluster[0]); + + if (Cartesian2.distance(screenPosition, clusterScreenPosition) < pixelRange) { + cluster.push(this.featureSearchService.visualizationPositions[index]); + addedToCluster = true; + break; + } + } + + if (!addedToCluster) { + clusters[index] = [this.featureSearchService.visualizationPositions[index]]; + } + }); + + // Clear the current billboards and add clustered billboards + this.featureSearchService.visualization.removeAll(); + + Object.values(clusters).forEach(cluster => { + if (cluster.length >= minimumClusterSize) { + const averagePosition = this.calculateAveragePosition(cluster); + const clusterImage = this.pinBuilder?.fromText( + cluster.length.toString(), + Color.fromCssColorString(this.featureSearchService.pointColor), + 48 + ).toDataURL(); + this.featureSearchService.visualization.add({ + position: averagePosition, + image: clusterImage, + width: 48, + height: 48, + eyeOffset: new Cartesian3(0, 0, -100) + }); + } else { + cluster.forEach(position => { + this.featureSearchService.visualization.add({ + position: position, + image: this.featureSearchService.markerGraphics(), + width: 32, + height: 32, + pixelOffset: new Cartesian2(0, -10), + eyeOffset: new Cartesian3(0, 0, -100), + color: Color.fromCssColorString(this.featureSearchService.pointColor) + }); + }); + } + }); + } + + calculateAveragePosition(cluster: Cartesian3[]): Cartesian3 { + const sum = cluster.reduce((acc, pos) => { + acc.x += pos.x; + acc.y += pos.y; + acc.z += pos.z; + return acc; + }, new Cartesian3(0, 0, 0)); + + return new Cartesian3( + sum.x / cluster.length, + sum.y / cluster.length, + sum.z / cluster.length + ); + } + + setupCameraHandlers() { + const pixelRange = 40; + const minimumClusterSize = 5; + + this.viewer.scene.camera.moveEnd.addEventListener(() => { + this.performClustering(pixelRange, minimumClusterSize); + }); + + this.viewer.scene.camera.changed.addEventListener(() => { + this.performClustering(pixelRange, minimumClusterSize); + }); + + this.performClustering(pixelRange, minimumClusterSize); + } } From 44255b5ac4023e01a6acb729ff249a394ca3c9d9 Mon Sep 17 00:00:00 2001 From: Wagram Airiian Date: Tue, 25 Jun 2024 13:34:29 +0200 Subject: [PATCH 2/4] Fix CVE-2024-37890 --- package-lock.json | 106 +++++++++++----------------------------------- 1 file changed, 24 insertions(+), 82 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3bc7b43..8aa2f03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6690,9 +6690,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -6704,7 +6704,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "engines": { "node": ">=10.2.0" @@ -6719,27 +6719,6 @@ "node": ">=10.0.0" } }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/enhanced-resolve": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", @@ -12322,33 +12301,13 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "dependencies": { - "ws": "~8.11.0" - } - }, - "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "debug": "~4.3.4", + "ws": "~8.17.1" } }, "node_modules/socket.io-parser": { @@ -14326,9 +14285,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -19160,9 +19119,9 @@ } }, "engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -19174,16 +19133,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, - "dependencies": { - "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "requires": {} - } + "ws": "~8.17.1" } }, "engine.io-parser": { @@ -23302,21 +23252,13 @@ } }, "socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "requires": { - "ws": "~8.11.0" - }, - "dependencies": { - "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "requires": {} - } + "debug": "~4.3.4", + "ws": "~8.17.1" } }, "socket.io-parser": { @@ -24615,9 +24557,9 @@ "dev": true }, "ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "requires": {} }, From c88f531fb8e77704422e8380be4d1739ea69f48b Mon Sep 17 00:00:00 2001 From: Wagram Airiian Date: Wed, 26 Jun 2024 08:46:17 +0200 Subject: [PATCH 3/4] Improve priority processing --- erdblick_app/app/feature.search.service.ts | 15 +-------------- erdblick_app/app/map.service.ts | 9 +++++++++ libs/core/src/bindings.cpp | 5 +++++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/erdblick_app/app/feature.search.service.ts b/erdblick_app/app/feature.search.service.ts index a565043..3e2744a 100644 --- a/erdblick_app/app/feature.search.service.ts +++ b/erdblick_app/app/feature.search.service.ts @@ -67,20 +67,7 @@ export class FeatureSearchService { // Fill up work queue and start processing. // TODO: What if we move / change the viewport during the search? - let priorityTiles = new Array(); - let nonPriorityTiles = new Array(); - for (const [_, tile] of this.mapService.loadedTileLayers) { - if ([...this.mapService.visualizedTileLayers.values()].some(tileVisializations => { - return tileVisializations.some(tileVisialization => { - return tileVisialization.tile.tileId == tile.tileId && tileVisialization.tile.mapName == tile.mapName; - }); - })) { - priorityTiles.push(tile); - } else { - nonPriorityTiles.push(tile); - } - } - this.workQueue = [...nonPriorityTiles, ...priorityTiles]; + this.workQueue = this.mapService.getPrioritisedTiles(); this.totalTiles = this.workQueue.length; this.isFeatureSearchActive.next(true); diff --git a/erdblick_app/app/map.service.ts b/erdblick_app/app/map.service.ts index 0e35d3d..7b97e08 100644 --- a/erdblick_app/app/map.service.ts +++ b/erdblick_app/app/map.service.ts @@ -599,6 +599,15 @@ export class MapService { this.update(); } + getPrioritisedTiles() { + let tiles = new Array<[number, FeatureTile]>(); + for (const [_, tile] of this.loadedTileLayers) { + tiles.push([coreLib.getTilePriorityById(this.currentViewport, tile.tileId), tile]); + } + tiles.sort((a, b) => a[0] - b[0]); + return tiles.map(val => val[1]); + } + getFeatureTile(tileKey: string): FeatureTile|null { return this.loadedTileLayers.get(tileKey) || null; } diff --git a/libs/core/src/bindings.cpp b/libs/core/src/bindings.cpp index fd052de..3f43d87 100644 --- a/libs/core/src/bindings.cpp +++ b/libs/core/src/bindings.cpp @@ -85,6 +85,10 @@ em::val getTileIds(Viewport const& vp, int level, int limit) return resultArray; } +double getTilePriorityById(Viewport const& vp, uint64_t tileId) { + return Wgs84AABB::radialDistancePrioFn({vp.camPosLon, vp.camPosLat}, vp.orientation)(tileId); +} + /** Get the center position for a mapget tile id in WGS84. */ mapget::Point getTilePosition(uint64_t tileIdValue) { mapget::TileId tid(tileIdValue); @@ -321,6 +325,7 @@ EMSCRIPTEN_BINDINGS(erdblick) ////////// Viewport TileID calculation em::function("getTileIds", &getTileIds); + em::function("getTilePriorityById", &getTilePriorityById); em::function("getTilePosition", &getTilePosition); em::function("getTileIdFromPosition", &getTileIdFromPosition); From f97d3a214ca430d9634fe9b9eafd4b6ea7e5eb60 Mon Sep 17 00:00:00 2001 From: Wagram Airiian Date: Wed, 26 Jun 2024 09:11:15 +0200 Subject: [PATCH 4/4] Change order --- erdblick_app/app/map.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erdblick_app/app/map.service.ts b/erdblick_app/app/map.service.ts index 7b97e08..5dc74a9 100644 --- a/erdblick_app/app/map.service.ts +++ b/erdblick_app/app/map.service.ts @@ -604,7 +604,7 @@ export class MapService { for (const [_, tile] of this.loadedTileLayers) { tiles.push([coreLib.getTilePriorityById(this.currentViewport, tile.tileId), tile]); } - tiles.sort((a, b) => a[0] - b[0]); + tiles.sort((a, b) => b[0] - a[0]); return tiles.map(val => val[1]); }