@pirireis/webglobeplugins 0.17.1 → 1.0.3
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/Math/haversine.js +22 -0
- package/Math/methods.js +15 -2
- package/Math/tessellation/methods.js +4 -1
- package/Math/tessellation/nearest-value-padding.js +112 -0
- package/Math/tessellation/spherical-triangle-area.js +99 -0
- package/Math/tessellation/tile-merger.js +346 -215
- package/Math/tessellation/triangle-tessellation.js +381 -9
- package/Math/vec3.js +4 -0
- package/Math/xyz-tile.js +18 -0
- package/altitude-locator/plugin.js +1 -2
- package/investigation-tools/draw/tiles/adapters.js +2 -2
- package/investigation-tools/draw/tiles/tiles.js +2 -2
- package/package.json +1 -1
- package/programs/helpers/fadeaway.js +6 -1
- package/programs/point-on-globe/square-pixel-point.js +1 -0
- package/programs/polygon-on-globe/texture-dem-triangles.js +94 -116
- package/programs/totems/camera-totem-attactment-interface.js +1 -0
- package/programs/totems/camerauniformblock.js +33 -22
- package/programs/totems/dem-textures-manager.js +265 -0
- package/programs/vectorfields/logics/drawrectangleparticles.js +51 -18
- package/programs/vectorfields/logics/{ubo-new.js → particle-ubo.js} +5 -14
- package/programs/vectorfields/logics/pixelbased.js +42 -36
- package/programs/vectorfields/pingpongbuffermanager.js +34 -8
- package/semiplugins/shape-on-terrain/terrain-polygon/adapters.js +55 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/cache.js +102 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/index-polygon-map.js +45 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/manager.js +4 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/master-worker.js +177 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/polygon-to-triangles.js +100 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/random.js +121 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/types.js +1 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/worker-contact.js +63 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/data/worker.js +125 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/terrain-polygon.js +219 -0
- package/semiplugins/shape-on-terrain/terrain-polygon/types.js +8 -0
- package/semiplugins/shell/bbox-renderer/logic.js +18 -58
- package/semiplugins/shell/bbox-renderer/object.js +19 -9
- package/tracks/point-heat-map/point-to-heat-map-flow.js +1 -1
- package/tracks/point-tracks/plugin.js +13 -6
- package/tracks/timetracks/program-line-strip.js +1 -1
- package/util/account/single-attribute-buffer-management/buffer-manager.js +5 -3
- package/util/account/single-attribute-buffer-management/buffer-orchestrator.js +2 -2
- package/util/gl-util/uniform-block/manager.js +20 -10
- package/util/helper-methods.js +8 -0
- package/util/picking/fence.js +4 -2
- package/util/picking/picker-displayer.js +51 -9
- package/util/programs/draw-texture-on-canvas.js +18 -15
- package/util/shaderfunctions/geometrytransformations.js +67 -1
- package/vectorfield/waveparticles/plugin.js +241 -116
- package/vectorfield/wind/adapters/image-to-fields.js +61 -0
- package/vectorfield/wind/adapters/types.js +1 -0
- package/vectorfield/wind/imagetovectorfieldandmagnitude.js +6 -9
- package/vectorfield/wind/plugin-persistant copy.js +364 -0
- package/vectorfield/wind/plugin-persistant.js +375 -0
- package/vectorfield/wind/plugin.js +1 -1
- package/Math/tessellation/earcut/adapters.js +0 -37
- package/Math/tessellation/hybrid-triangle-tessellation-meta.js +0 -123
- package/Math/tessellation/shred-input.js +0 -18
- package/Math/tessellation/tiler.js +0 -50
- package/Math/tessellation/triangle-tessellation-meta.js +0 -523
- package/programs/polygon-on-globe/texture-dem-triangle-test-plugin-triangle.js +0 -328
- package/programs/vectorfields/logics/drawrectangleparticles1.js +0 -112
- package/semiplugins/shape-on-terrain/terrain-cover/texture-dem-cover.js +0 -1
- package/util/gl-util/uniform-block/types.js +0 -1
- package/util/webglobe/index.js +0 -2
- /package/Math/tessellation/{zoom-catch.js → constants.js} +0 -0
- /package/util/{webglobe/gldefaultstates.js → globe-default-gl-states.js} +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function geoJSONToTerrainPolygon(geojson, keyAdapter, colorAdapter) {
|
|
2
|
+
if (geojson.type !== 'FeatureCollection') {
|
|
3
|
+
throw new Error('Invalid GeoJSON: Expected FeatureCollection');
|
|
4
|
+
}
|
|
5
|
+
const features = geojson.features;
|
|
6
|
+
const polygons = [];
|
|
7
|
+
for (const feature of features) {
|
|
8
|
+
const geometry = feature.geometry;
|
|
9
|
+
const key = keyAdapter(feature);
|
|
10
|
+
const color = colorAdapter(feature);
|
|
11
|
+
const geometries = [];
|
|
12
|
+
if (geometry.type === 'Polygon') {
|
|
13
|
+
const coordinates = geometry.coordinates;
|
|
14
|
+
const vertices = [];
|
|
15
|
+
const holes = [];
|
|
16
|
+
let holeIndex = 0;
|
|
17
|
+
for (let i = 0; i < coordinates.length; i++) {
|
|
18
|
+
if (i > 0) {
|
|
19
|
+
holeIndex += coordinates[i - 1].length;
|
|
20
|
+
holes.push(holeIndex);
|
|
21
|
+
}
|
|
22
|
+
for (const coord of coordinates[i]) {
|
|
23
|
+
vertices.push(coord[0], coord[1]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
geometries.push({ vertices, holes });
|
|
27
|
+
}
|
|
28
|
+
else if (geometry.type === 'MultiPolygon') {
|
|
29
|
+
const multiCoordinates = geometry.coordinates;
|
|
30
|
+
for (const coordinates of multiCoordinates) {
|
|
31
|
+
const vertices = [];
|
|
32
|
+
const holes = [];
|
|
33
|
+
let holeIndex = 0;
|
|
34
|
+
for (let i = 0; i < coordinates.length; i++) {
|
|
35
|
+
if (i > 0) {
|
|
36
|
+
holeIndex += coordinates[i - 1].length;
|
|
37
|
+
holes.push(holeIndex);
|
|
38
|
+
}
|
|
39
|
+
for (const coord of coordinates[i]) {
|
|
40
|
+
vertices.push(coord[0], coord[1]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
geometries.push({ vertices, holes });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
continue; // Skip non-polygon geometries
|
|
48
|
+
}
|
|
49
|
+
if (geometries.length > 0) {
|
|
50
|
+
polygons.push({ key, color, geometry: geometries });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return polygons;
|
|
54
|
+
}
|
|
55
|
+
export { geoJSONToTerrainPolygon };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createTriangleTessellationMeta } from "../../../../Math/tessellation/triangle-tessellation";
|
|
2
|
+
import { triangulation } from "./polygon-to-triangles";
|
|
3
|
+
import RBush from "rbush";
|
|
4
|
+
import { RADIAN } from "../../../../Math/methods";
|
|
5
|
+
// TODO: use type instead of any
|
|
6
|
+
function _entry(polygon, kmThreshold) {
|
|
7
|
+
__polygonInputWGS84ToRadian(polygon);
|
|
8
|
+
const trianglesData = triangulation(polygon, kmThreshold);
|
|
9
|
+
const cacheTriangles = [];
|
|
10
|
+
for (let i = 0; i < trianglesData.indices.length; i += 3) {
|
|
11
|
+
const idx0 = trianglesData.indices[i] * 2;
|
|
12
|
+
const idx1 = trianglesData.indices[i + 1] * 2;
|
|
13
|
+
const idx2 = trianglesData.indices[i + 2] * 2;
|
|
14
|
+
const v0 = [trianglesData.vertices[idx0], trianglesData.vertices[idx0 + 1]];
|
|
15
|
+
const v1 = [trianglesData.vertices[idx1], trianglesData.vertices[idx1 + 1]];
|
|
16
|
+
const v2 = [trianglesData.vertices[idx2], trianglesData.vertices[idx2 + 1]];
|
|
17
|
+
const tessellationMeta = createTriangleTessellationMeta(v0, v1, v2);
|
|
18
|
+
cacheTriangles.push({
|
|
19
|
+
polygon: polygon,
|
|
20
|
+
offset: i / 3,
|
|
21
|
+
tessellationMeta: tessellationMeta,
|
|
22
|
+
minX: tessellationMeta.bbox.min[0],
|
|
23
|
+
minY: tessellationMeta.bbox.min[1],
|
|
24
|
+
maxX: tessellationMeta.bbox.max[0],
|
|
25
|
+
maxY: tessellationMeta.bbox.max[1],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return cacheTriangles;
|
|
29
|
+
}
|
|
30
|
+
function __polygonInputWGS84ToRadian(polygon) {
|
|
31
|
+
for (const geom of polygon.geometry) {
|
|
32
|
+
const vertices = geom.vertices;
|
|
33
|
+
for (let i = 0; i < vertices.length / 2; i++) {
|
|
34
|
+
vertices[i * 2] = vertices[i * 2] * RADIAN;
|
|
35
|
+
vertices[i * 2 + 1] = vertices[i * 2 + 1] * RADIAN;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export class Cache {
|
|
40
|
+
cache = new Map();
|
|
41
|
+
kmThreshold;
|
|
42
|
+
rbush = new RBush();
|
|
43
|
+
bboxes = [];
|
|
44
|
+
constructor(kmThreshold) {
|
|
45
|
+
this.kmThreshold = kmThreshold;
|
|
46
|
+
}
|
|
47
|
+
clear() {
|
|
48
|
+
this.cache.clear();
|
|
49
|
+
this.rbush.clear();
|
|
50
|
+
}
|
|
51
|
+
insert(key, polygon) {
|
|
52
|
+
if (this.cache.has(key)) {
|
|
53
|
+
this.remove(key);
|
|
54
|
+
}
|
|
55
|
+
const triangles = _entry(polygon, this.kmThreshold);
|
|
56
|
+
this.rbush.load(triangles);
|
|
57
|
+
this.cache.set(key, { polygon: polygon, triangles });
|
|
58
|
+
}
|
|
59
|
+
remove(key) {
|
|
60
|
+
const entry = this.cache.get(key);
|
|
61
|
+
if (entry) {
|
|
62
|
+
for (const triangle of entry.triangles) {
|
|
63
|
+
this.rbush.remove(triangle);
|
|
64
|
+
}
|
|
65
|
+
this.cache.delete(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
setBBOXes(BBOXES) {
|
|
69
|
+
this.bboxes = BBOXES;
|
|
70
|
+
}
|
|
71
|
+
search() {
|
|
72
|
+
const results = new Set();
|
|
73
|
+
for (const bbox of this.bboxes) {
|
|
74
|
+
const { minX, minY, maxX, maxY } = bbox;
|
|
75
|
+
if (minX <= maxX && minY <= maxY) {
|
|
76
|
+
// Normal case
|
|
77
|
+
const partialResults = this.rbush.search({ minX, minY, maxX, maxY });
|
|
78
|
+
for (const res of partialResults) {
|
|
79
|
+
results.add(res);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Handle antimeridian crossing
|
|
84
|
+
// MinX > MaxX case is the signal.
|
|
85
|
+
const partialResults1 = this.rbush.search({ minX, minY, maxX: Math.PI, maxY });
|
|
86
|
+
const partialResults2 = this.rbush.search({ minX: -Math.PI, minY, maxX, maxY });
|
|
87
|
+
for (const res of partialResults1) {
|
|
88
|
+
results.add(res);
|
|
89
|
+
}
|
|
90
|
+
for (const res of partialResults2) {
|
|
91
|
+
results.add(res);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
defragment() {
|
|
98
|
+
const allItems = this.rbush.all();
|
|
99
|
+
this.rbush.clear();
|
|
100
|
+
this.rbush.load(allItems);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class IndexPolygonMap {
|
|
2
|
+
_indexPolygonMap = new Map();
|
|
3
|
+
_polygonKeyMap = new Map();
|
|
4
|
+
_thumbstones = [];
|
|
5
|
+
_polygonIndex = 1; // 0 index is null
|
|
6
|
+
constructor() { }
|
|
7
|
+
newIndex() {
|
|
8
|
+
if (this._thumbstones.length > 0) {
|
|
9
|
+
return this._thumbstones.pop();
|
|
10
|
+
}
|
|
11
|
+
return this._polygonIndex++;
|
|
12
|
+
}
|
|
13
|
+
insertBulk(polygons) {
|
|
14
|
+
for (const poly of polygons) {
|
|
15
|
+
if (this._polygonKeyMap.has(poly.key)) {
|
|
16
|
+
this.deleteBulk([poly.key]);
|
|
17
|
+
}
|
|
18
|
+
const index = this.newIndex();
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
poly.index = index; // Assign the new index to the polygon
|
|
21
|
+
this._indexPolygonMap.set(index, poly);
|
|
22
|
+
this._polygonKeyMap.set(poly.key, index);
|
|
23
|
+
}
|
|
24
|
+
return polygons;
|
|
25
|
+
}
|
|
26
|
+
deleteBulk(keys) {
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
const index = this._polygonKeyMap.get(key);
|
|
29
|
+
if (index === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
this._polygonKeyMap.delete(key);
|
|
32
|
+
this._indexPolygonMap.delete(index);
|
|
33
|
+
this._thumbstones.push(index);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
get(index) {
|
|
37
|
+
return this._indexPolygonMap.get(index) || null;
|
|
38
|
+
}
|
|
39
|
+
clear() {
|
|
40
|
+
this._indexPolygonMap.clear();
|
|
41
|
+
this._polygonKeyMap.clear();
|
|
42
|
+
this._thumbstones = [];
|
|
43
|
+
this._polygonIndex = 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
const _workers = [];
|
|
3
|
+
const _workerMap = new Map();
|
|
4
|
+
let _maxWorkers = 4; // Default, updated in init
|
|
5
|
+
let _nextWorkerIndex = 0;
|
|
6
|
+
// Configuration state to pass to new workers or re-broadcast
|
|
7
|
+
let _pickableState;
|
|
8
|
+
let _variativeColorsOn;
|
|
9
|
+
// Initialize workers
|
|
10
|
+
function initWorkers() {
|
|
11
|
+
_maxWorkers = Math.max(1, (navigator.hardwareConcurrency || 4) - 1);
|
|
12
|
+
for (let i = 0; i < _maxWorkers; i++) {
|
|
13
|
+
addWorker();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function addWorker() {
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: 'module' });
|
|
19
|
+
const wrapper = {
|
|
20
|
+
worker,
|
|
21
|
+
inProgress: false,
|
|
22
|
+
insertDeleteQueue: [],
|
|
23
|
+
lastBBOXData: null,
|
|
24
|
+
itemCount: 0,
|
|
25
|
+
lastOutput: null
|
|
26
|
+
};
|
|
27
|
+
worker.onmessage = (event) => {
|
|
28
|
+
wrapper.inProgress = false;
|
|
29
|
+
wrapper.lastOutput = event.data;
|
|
30
|
+
mergeAndSendResults();
|
|
31
|
+
sendToSubWorker(wrapper);
|
|
32
|
+
};
|
|
33
|
+
// Initialize sub-worker with current global state
|
|
34
|
+
worker.postMessage({
|
|
35
|
+
pickableState: _pickableState,
|
|
36
|
+
variativeColorsOn: _variativeColorsOn,
|
|
37
|
+
insertDeleteQueue: [],
|
|
38
|
+
bboxes: null,
|
|
39
|
+
});
|
|
40
|
+
_workers.push(wrapper);
|
|
41
|
+
}
|
|
42
|
+
function mergeAndSendResults() {
|
|
43
|
+
let totalVec3s = 0;
|
|
44
|
+
let totalIndices = 0;
|
|
45
|
+
let totalLongLats = 0;
|
|
46
|
+
let totalPickIndices = 0;
|
|
47
|
+
let totalVariativeColors = 0;
|
|
48
|
+
const validOutputs = [];
|
|
49
|
+
for (const w of _workers) {
|
|
50
|
+
if (w.lastOutput) {
|
|
51
|
+
validOutputs.push(w.lastOutput);
|
|
52
|
+
totalVec3s += w.lastOutput.vec3s.length;
|
|
53
|
+
totalIndices += w.lastOutput.indices.length;
|
|
54
|
+
totalLongLats += w.lastOutput.longLats.length;
|
|
55
|
+
if (w.lastOutput.pickIndices)
|
|
56
|
+
totalPickIndices += w.lastOutput.pickIndices.length;
|
|
57
|
+
if (w.lastOutput.variativeColors)
|
|
58
|
+
totalVariativeColors += w.lastOutput.variativeColors.length;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (validOutputs.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
const merged = {
|
|
64
|
+
vec3s: new Float32Array(totalVec3s),
|
|
65
|
+
indices: new Uint32Array(totalIndices),
|
|
66
|
+
longLats: new Float32Array(totalLongLats),
|
|
67
|
+
pickIndices: totalPickIndices > 0 ? new Float32Array(totalPickIndices) : null,
|
|
68
|
+
variativeColors: totalVariativeColors > 0 ? new Float32Array(totalVariativeColors) : null
|
|
69
|
+
};
|
|
70
|
+
let offsetVec3s = 0;
|
|
71
|
+
let offsetIndices = 0;
|
|
72
|
+
let offsetLongLats = 0;
|
|
73
|
+
let offsetPickIndices = 0;
|
|
74
|
+
let offsetVariativeColors = 0;
|
|
75
|
+
let vertexOffset = 0;
|
|
76
|
+
for (const out of validOutputs) {
|
|
77
|
+
merged.vec3s.set(out.vec3s, offsetVec3s);
|
|
78
|
+
offsetVec3s += out.vec3s.length;
|
|
79
|
+
for (let i = 0; i < out.indices.length; i++) {
|
|
80
|
+
merged.indices[offsetIndices + i] = out.indices[i] + vertexOffset;
|
|
81
|
+
}
|
|
82
|
+
offsetIndices += out.indices.length;
|
|
83
|
+
vertexOffset += out.vec3s.length / 3;
|
|
84
|
+
merged.longLats.set(out.longLats, offsetLongLats);
|
|
85
|
+
offsetLongLats += out.longLats.length;
|
|
86
|
+
if (merged.pickIndices && out.pickIndices) {
|
|
87
|
+
merged.pickIndices.set(out.pickIndices, offsetPickIndices);
|
|
88
|
+
offsetPickIndices += out.pickIndices.length;
|
|
89
|
+
}
|
|
90
|
+
if (merged.variativeColors && out.variativeColors) {
|
|
91
|
+
merged.variativeColors.set(out.variativeColors, offsetVariativeColors);
|
|
92
|
+
offsetVariativeColors += out.variativeColors.length;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Send merged result to Main Thread
|
|
96
|
+
self.postMessage(merged, [
|
|
97
|
+
merged.vec3s.buffer,
|
|
98
|
+
merged.indices.buffer,
|
|
99
|
+
merged.longLats.buffer,
|
|
100
|
+
...(merged.pickIndices ? [merged.pickIndices.buffer] : []),
|
|
101
|
+
...(merged.variativeColors ? [merged.variativeColors.buffer] : [])
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
function sendToSubWorker(wrapper) {
|
|
105
|
+
if (wrapper.inProgress)
|
|
106
|
+
return;
|
|
107
|
+
if (wrapper.insertDeleteQueue.length === 0 && wrapper.lastBBOXData === null)
|
|
108
|
+
return;
|
|
109
|
+
wrapper.inProgress = true;
|
|
110
|
+
wrapper.worker.postMessage({
|
|
111
|
+
pickableState: undefined, // State is set on init, only send updates if needed
|
|
112
|
+
variativeColorsOn: undefined,
|
|
113
|
+
bboxes: wrapper.lastBBOXData,
|
|
114
|
+
insertDeleteQueue: wrapper.insertDeleteQueue,
|
|
115
|
+
});
|
|
116
|
+
wrapper.lastBBOXData = null;
|
|
117
|
+
wrapper.insertDeleteQueue = [];
|
|
118
|
+
}
|
|
119
|
+
function triggerAllWorkers() {
|
|
120
|
+
for (const w of _workers) {
|
|
121
|
+
sendToSubWorker(w);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Handle messages from Main Thread
|
|
125
|
+
self.onmessage = (event) => {
|
|
126
|
+
const { bboxes, insertDeleteQueue, pickableState, variativeColorsOn } = event.data;
|
|
127
|
+
// Initialize if not already done
|
|
128
|
+
if (_workers.length === 0) {
|
|
129
|
+
_pickableState = pickableState;
|
|
130
|
+
_variativeColorsOn = variativeColorsOn;
|
|
131
|
+
initWorkers();
|
|
132
|
+
}
|
|
133
|
+
// Handle BBOX updates
|
|
134
|
+
if (bboxes) {
|
|
135
|
+
for (const w of _workers) {
|
|
136
|
+
w.lastBBOXData = bboxes;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Handle Insert/Delete
|
|
140
|
+
if (insertDeleteQueue && insertDeleteQueue.length > 0) {
|
|
141
|
+
if (insertDeleteQueue[0] === "__CLEAR_ALL_ITEMS__") {
|
|
142
|
+
_workerMap.clear();
|
|
143
|
+
for (const w of _workers) {
|
|
144
|
+
w.insertDeleteQueue = ["__CLEAR_ALL_ITEMS__"];
|
|
145
|
+
w.itemCount = 0;
|
|
146
|
+
w.lastOutput = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
for (const item of insertDeleteQueue) {
|
|
151
|
+
if (typeof item === 'string') {
|
|
152
|
+
// Delete
|
|
153
|
+
const workerIdx = _workerMap.get(item);
|
|
154
|
+
if (workerIdx !== undefined) {
|
|
155
|
+
_workers[workerIdx].insertDeleteQueue.push(item);
|
|
156
|
+
_workers[workerIdx].itemCount--;
|
|
157
|
+
_workerMap.delete(item);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Insert
|
|
162
|
+
let workerIdx = _workerMap.get(item.key);
|
|
163
|
+
if (workerIdx === undefined) {
|
|
164
|
+
workerIdx = _nextWorkerIndex;
|
|
165
|
+
_workerMap.set(item.key, workerIdx);
|
|
166
|
+
_workers[workerIdx].itemCount++;
|
|
167
|
+
_nextWorkerIndex = (_nextWorkerIndex + 1) % _workers.length;
|
|
168
|
+
}
|
|
169
|
+
_workers[workerIdx].insertDeleteQueue.push(item.key);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Trigger all workers to process the new data
|
|
175
|
+
triggerAllWorkers();
|
|
176
|
+
};
|
|
177
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # Purpose
|
|
3
|
+
* Divide long edges of polygons into smaller segments
|
|
4
|
+
*
|
|
5
|
+
* This algorithm should be called after earcutting / triangulation.
|
|
6
|
+
* Then earcut should run second time to further divide long edges.
|
|
7
|
+
*
|
|
8
|
+
* populate points on a arc between two given points on a sphere
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
import { haversine } from "../../../../Math/haversine";
|
|
12
|
+
import earcut from "earcut";
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
function __pointsInBetweenGreateCircle(lat1, lon1, lat2, lon2, unitSphereDistance) {
|
|
17
|
+
const points = [];
|
|
18
|
+
const d1 = haversine(lat1, lon1, lat2, lon2);
|
|
19
|
+
if (d1 <= unitSphereDistance) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const numSegments = Math.ceil(d1 / unitSphereDistance);
|
|
23
|
+
for (let i = 1; i < numSegments; i++) {
|
|
24
|
+
const f = i / numSegments;
|
|
25
|
+
const A = Math.sin((1 - f) * d1) / Math.sin(d1);
|
|
26
|
+
const B = Math.sin(f * d1) / Math.sin(d1);
|
|
27
|
+
const x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2) * Math.cos(lon2);
|
|
28
|
+
const y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2) * Math.sin(lon2);
|
|
29
|
+
const z = A * Math.sin(lat1) + B * Math.sin(lat2);
|
|
30
|
+
const latN = Math.atan2(z, Math.sqrt(x * x + y * y));
|
|
31
|
+
const lonN = Math.atan2(y, x);
|
|
32
|
+
points.push(latN, lonN);
|
|
33
|
+
}
|
|
34
|
+
return points;
|
|
35
|
+
}
|
|
36
|
+
function _dividePolygonEdgeSize(polygon, kmThreshold) {
|
|
37
|
+
const unitSphereDistance = kmThreshold / 6371; // Earth radius in km
|
|
38
|
+
const newGeometries = polygon.geometry.map(geom => {
|
|
39
|
+
const { vertices, holes } = geom;
|
|
40
|
+
const newVertices = [];
|
|
41
|
+
const newHoles = [];
|
|
42
|
+
const processRing = (start, end) => {
|
|
43
|
+
for (let i = start; i < end; i++) {
|
|
44
|
+
const long1 = vertices[2 * i];
|
|
45
|
+
const lat1 = vertices[2 * i + 1];
|
|
46
|
+
const next_i = (i + 1 < end) ? i + 1 : start;
|
|
47
|
+
const long2 = vertices[2 * next_i];
|
|
48
|
+
const lat2 = vertices[2 * next_i + 1];
|
|
49
|
+
newVertices.push(long1, lat1);
|
|
50
|
+
const extraPoints = __pointsInBetweenGreateCircle(lat1, long1, lat2, long2, unitSphereDistance);
|
|
51
|
+
if (extraPoints) {
|
|
52
|
+
newVertices.push(...extraPoints);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const shellEnd = holes.length > 0 ? holes[0] : vertices.length / 2;
|
|
57
|
+
processRing(0, shellEnd);
|
|
58
|
+
for (let h = 0; h < holes.length; h++) {
|
|
59
|
+
newHoles.push(newVertices.length / 2);
|
|
60
|
+
const holeStart = holes[h];
|
|
61
|
+
const holeEnd = h + 1 < holes.length ? holes[h + 1] : vertices.length / 2;
|
|
62
|
+
processRing(holeStart, holeEnd);
|
|
63
|
+
}
|
|
64
|
+
return { vertices: newVertices, holes: newHoles };
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
...polygon,
|
|
68
|
+
geometry: newGeometries,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// TODO:
|
|
72
|
+
// divide TrianglesMiddleData edges longer than threshold
|
|
73
|
+
// function _divideTriangleEdges(
|
|
74
|
+
// key: string,
|
|
75
|
+
// vertices: number[],
|
|
76
|
+
// indices: number[],
|
|
77
|
+
// kmThreshold: number // maybe
|
|
78
|
+
// ): TrianglesMiddleData {
|
|
79
|
+
// return { key, vertices, indices };
|
|
80
|
+
// }
|
|
81
|
+
// TODO: NEED TEST
|
|
82
|
+
function triangulation(polygon, kmThreshold) {
|
|
83
|
+
const finalVertices = [];
|
|
84
|
+
const finalIndices = [];
|
|
85
|
+
let vertexIndexOffset = 0;
|
|
86
|
+
for (const geom of polygon.geometry) {
|
|
87
|
+
const indices = earcut(geom.vertices, geom.holes, 2);
|
|
88
|
+
finalVertices.push(...geom.vertices);
|
|
89
|
+
for (const index of indices) {
|
|
90
|
+
finalIndices.push(index + vertexIndexOffset);
|
|
91
|
+
}
|
|
92
|
+
vertexIndexOffset += geom.vertices.length / 2;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
key: polygon.key,
|
|
96
|
+
vertices: finalVertices,
|
|
97
|
+
indices: finalIndices
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export { triangulation };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates random polygon data for testing PolygonPluginInput
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generates a random polygon with optional holes
|
|
6
|
+
* @param {Object} options - Configuration options
|
|
7
|
+
* @param {number} options.minVertices - Minimum number of vertices for the shell (default: 3)
|
|
8
|
+
* @param {number} options.maxVertices - Maximum number of vertices for the shell (default: 8)
|
|
9
|
+
* @param {number} options.holeCount - Number of holes to generate (default: 0)
|
|
10
|
+
* @param {number} options.minLat - Minimum latitude (default: -85)
|
|
11
|
+
* @param {number} options.maxLat - Maximum latitude (default: 85)
|
|
12
|
+
* @param {number} options.minLng - Minimum longitude (default: -180)
|
|
13
|
+
* @param {number} options.maxLng - Maximum longitude (default: 180)
|
|
14
|
+
* @param {number} options.dimensions - Number of dimensions per vertex (default: 2)
|
|
15
|
+
* @returns {Object} PolygonPluginInput object
|
|
16
|
+
*/
|
|
17
|
+
function generateRandomPolygon(options = {}) {
|
|
18
|
+
const { minVertices = 3, maxVertices = 8, holeCount = 0, minLat = -85, maxLat = 85, minLng = -180, maxLng = 180, dimensions = 2, radius = 4, } = options;
|
|
19
|
+
const key = `polygon_${Date.now()}_${Math.floor(Math.random() * 1000)}_${Math.random()}`;
|
|
20
|
+
const vertices = [];
|
|
21
|
+
const holes = [];
|
|
22
|
+
// Generate shell vertices
|
|
23
|
+
const shellVertexCount = Math.floor(Math.random() * (maxVertices - minVertices + 1)) + minVertices;
|
|
24
|
+
const centerLng = Math.random() * (maxLng - minLng) + minLng;
|
|
25
|
+
const centerLat = Math.random() * (maxLat - minLat) + minLat;
|
|
26
|
+
// Generate shell as a rough circle with random variations
|
|
27
|
+
for (let i = 0; i < shellVertexCount; i++) {
|
|
28
|
+
const angle = (i / shellVertexCount) * 2 * Math.PI;
|
|
29
|
+
const radiusVariation = radius * (0.7 + Math.random() * 0.6); // Add variation to radius
|
|
30
|
+
const lng = centerLng + Math.cos(angle) * radiusVariation;
|
|
31
|
+
const lat = centerLat + Math.sin(angle) * radiusVariation;
|
|
32
|
+
vertices.push(lng, lat);
|
|
33
|
+
if (dimensions > 2) {
|
|
34
|
+
for (let d = 2; d < dimensions; d++) {
|
|
35
|
+
vertices.push(Math.random() * 1000); // Random additional dimensions
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Generate holes
|
|
40
|
+
for (let h = 0; h < holeCount; h++) {
|
|
41
|
+
const holeStartIndex = vertices.length / dimensions;
|
|
42
|
+
holes.push(holeStartIndex);
|
|
43
|
+
const holeVertexCount = Math.floor(Math.random() * 4) + 3; // 3-6 vertices for holes
|
|
44
|
+
const holeRadius = radius * (0.2 + Math.random() * 0.3); // Smaller radius for holes
|
|
45
|
+
const holeCenterLng = centerLng + (Math.random() - 0.5) * radius * 0.8;
|
|
46
|
+
const holeCenterLat = centerLat + (Math.random() - 0.5) * radius * 0.8;
|
|
47
|
+
for (let i = 0; i < holeVertexCount; i++) {
|
|
48
|
+
const angle = (i / holeVertexCount) * 2 * Math.PI;
|
|
49
|
+
const lng = holeCenterLng + Math.cos(angle) * holeRadius;
|
|
50
|
+
const lat = holeCenterLat + Math.sin(angle) * holeRadius;
|
|
51
|
+
vertices.push(lng, lat);
|
|
52
|
+
if (dimensions > 2) {
|
|
53
|
+
for (let d = 2; d < dimensions; d++) {
|
|
54
|
+
vertices.push(Math.random() * 1000);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
key,
|
|
61
|
+
geometry: [{ vertices, holes }],
|
|
62
|
+
color: [Math.random(), Math.random(), Math.random(), 1.0],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generates multiple random polygons
|
|
67
|
+
* @param {number} count - Number of polygons to generate
|
|
68
|
+
* @param {Object} options - Options to pass to generateRandomPolygon
|
|
69
|
+
* @returns {Array} Array of PolygonPluginInput objects
|
|
70
|
+
*/
|
|
71
|
+
function generateRandomPolygons(count, options = {}) {
|
|
72
|
+
const polygons = [];
|
|
73
|
+
for (let i = 0; i < count; i++) {
|
|
74
|
+
polygons.push(generateRandomPolygon(options));
|
|
75
|
+
}
|
|
76
|
+
return polygons;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generates a simple rectangular polygon for basic testing
|
|
80
|
+
* @param {number} lng - Center longitude
|
|
81
|
+
* @param {number} lat - Center latitude
|
|
82
|
+
* @param {number} width - Width in degrees
|
|
83
|
+
* @param {number} height - Height in degrees
|
|
84
|
+
* @returns {Object} PolygonPluginInput object
|
|
85
|
+
*/
|
|
86
|
+
function generateRectanglePolygon(lng = 0, lat = 0, width = 2, height = 1) {
|
|
87
|
+
const halfWidth = width / 2;
|
|
88
|
+
const halfHeight = height / 2;
|
|
89
|
+
return {
|
|
90
|
+
key: `rectangle_${Date.now()}`,
|
|
91
|
+
geometry: [{
|
|
92
|
+
vertices: [
|
|
93
|
+
lng - halfWidth, lat - halfHeight, // Bottom-left
|
|
94
|
+
lng + halfWidth, lat - halfHeight, // Bottom-right
|
|
95
|
+
lng + halfWidth, lat + halfHeight, // Top-right
|
|
96
|
+
lng - halfWidth, lat + halfHeight // Top-left
|
|
97
|
+
],
|
|
98
|
+
holes: []
|
|
99
|
+
}]
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function generateRectangleFromBBOX(bbox, key = null) {
|
|
103
|
+
const { ll, ur } = bbox;
|
|
104
|
+
return {
|
|
105
|
+
key: key || `rectangle_${Date.now()}`,
|
|
106
|
+
geometry: [{
|
|
107
|
+
vertices: [
|
|
108
|
+
ll.x, ll.y, // Bottom-left
|
|
109
|
+
ur.x, ll.y, // Bottom-right
|
|
110
|
+
ur.x, ur.y, // Top-right
|
|
111
|
+
ll.x, ur.y // Top-left
|
|
112
|
+
],
|
|
113
|
+
holes: []
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// // Example usage:
|
|
118
|
+
// console.log('Polygon with holes:', generateRandomPolygon({ holeCount: 2 }));
|
|
119
|
+
// console.log('Multiple polygons:', generateRandomPolygons(3));
|
|
120
|
+
// console.log('Rectangle polygon:', generateRectanglePolygon(-74, 40.7, 0.1, 0.05));
|
|
121
|
+
export { generateRandomPolygon, generateRandomPolygons, generateRectanglePolygon, generateRectangleFromBBOX };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DemTextureManagerCache } from "../../../../programs/totems/dem-textures-manager";
|
|
2
|
+
import { RADIAN } from "../../../../Math/methods";
|
|
3
|
+
export class WorkerContact {
|
|
4
|
+
_masterWorker;
|
|
5
|
+
_options;
|
|
6
|
+
onResult;
|
|
7
|
+
demTextureManager = null;
|
|
8
|
+
constructor(globe, options, onResult) {
|
|
9
|
+
this._options = options;
|
|
10
|
+
this.onResult = onResult;
|
|
11
|
+
// Initialize the Master Worker
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
this._masterWorker = new Worker(new URL("./master-worker.js", import.meta.url), { type: 'module' });
|
|
14
|
+
this._masterWorker.onmessage = (event) => {
|
|
15
|
+
this.onResult(event.data);
|
|
16
|
+
};
|
|
17
|
+
// Initialize Master Worker state
|
|
18
|
+
this._masterWorker.postMessage({
|
|
19
|
+
pickableState: this._options.pickable,
|
|
20
|
+
variativeColorsOn: this._options.variativeColorsOn,
|
|
21
|
+
insertDeleteQueue: [],
|
|
22
|
+
bboxes: null,
|
|
23
|
+
});
|
|
24
|
+
this.demTextureManager = DemTextureManagerCache.get(globe);
|
|
25
|
+
this.demTextureManager.register(this);
|
|
26
|
+
}
|
|
27
|
+
setMergedTiles(mergedTilesInfo) {
|
|
28
|
+
const bboxesData = new Array(mergedTilesInfo.length);
|
|
29
|
+
for (let i = 0; i < mergedTilesInfo.length; i++) {
|
|
30
|
+
const { bbox, zoom } = mergedTilesInfo[i];
|
|
31
|
+
bboxesData[i] = {
|
|
32
|
+
zoom: zoom,
|
|
33
|
+
minX: bbox.ll.x * RADIAN,
|
|
34
|
+
minY: bbox.ll.y * RADIAN,
|
|
35
|
+
maxX: bbox.ur.x * RADIAN,
|
|
36
|
+
maxY: bbox.ur.y * RADIAN,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Send BBOX updates to Master Worker
|
|
40
|
+
this._masterWorker.postMessage({
|
|
41
|
+
bboxes: bboxesData
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
free() {
|
|
45
|
+
this.demTextureManager.unregister(this);
|
|
46
|
+
this._masterWorker.terminate();
|
|
47
|
+
}
|
|
48
|
+
insertBulk(polygons) {
|
|
49
|
+
this._masterWorker.postMessage({
|
|
50
|
+
insertDeleteQueue: polygons
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
deleteBulk(keys) {
|
|
54
|
+
this._masterWorker.postMessage({
|
|
55
|
+
insertDeleteQueue: keys
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
clear() {
|
|
59
|
+
this._masterWorker.postMessage({
|
|
60
|
+
insertDeleteQueue: ["__CLEAR_ALL_ITEMS__"]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|