@arenarium/maps 1.0.34 → 1.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +83 -27400
- package/dist/style.css +1 -1
- package/package.json +19 -24
- package/dist/index.umd.cjs +0 -729
- package/node_modules/@workspace/shared/package.json +0 -6
- package/node_modules/@workspace/shared/src/constants.ts +0 -11
- package/node_modules/@workspace/shared/src/marker/blocks/blocks.ts +0 -129
- package/node_modules/@workspace/shared/src/marker/blocks/bounds.ts +0 -58
- package/node_modules/@workspace/shared/src/marker/blocks/thresholds.ts +0 -546
- package/node_modules/@workspace/shared/src/marker/position.ts +0 -44
- package/node_modules/@workspace/shared/src/marker/projection.ts +0 -16
- package/node_modules/@workspace/shared/src/marker/rectangle.ts +0 -49
- package/node_modules/@workspace/shared/src/types.ts +0 -29
- package/node_modules/@workspace/shared/tsconfig.json +0 -12
|
@@ -1,546 +0,0 @@
|
|
|
1
|
-
import { getBounds, getBoundsZoomWhenTouching, areBoundsOverlaping, type Bounds } from './bounds.js';
|
|
2
|
-
|
|
3
|
-
import { MAP_MAX_ZOOM, MAP_MIN_ZOOM, MARKER_DEFAULT_ANGLE } from '../../constants.js';
|
|
4
|
-
|
|
5
|
-
interface Point {
|
|
6
|
-
x: number;
|
|
7
|
-
y: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface Marker {
|
|
11
|
-
id: string;
|
|
12
|
-
x: number;
|
|
13
|
-
y: number;
|
|
14
|
-
width: number;
|
|
15
|
-
height: number;
|
|
16
|
-
rank: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
namespace Particles {
|
|
20
|
-
export namespace Angles {
|
|
21
|
-
export const COUNT = 12;
|
|
22
|
-
export const DEFAULT = MARKER_DEFAULT_ANGLE;
|
|
23
|
-
export const DEGREES = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330];
|
|
24
|
-
export const RADIANS = DEGREES.map((d) => (d * Math.PI) / 180);
|
|
25
|
-
|
|
26
|
-
function getQuadrantIndex(forceX: number, forceY: number) {
|
|
27
|
-
const ratio = Math.abs(forceY / forceX);
|
|
28
|
-
|
|
29
|
-
// Rounding to 30 degrees, so binary search is possible
|
|
30
|
-
// tan(45) = 1
|
|
31
|
-
if (ratio < 1) {
|
|
32
|
-
// tan(15) = 0.26795
|
|
33
|
-
if (ratio < 0.26795) {
|
|
34
|
-
return 0; // 0 deg;
|
|
35
|
-
} else {
|
|
36
|
-
return 1; // 30 deg;
|
|
37
|
-
}
|
|
38
|
-
} else {
|
|
39
|
-
// tan(75) = 3.73205
|
|
40
|
-
if (ratio < 3.73205) {
|
|
41
|
-
return 2; // 60 deg;
|
|
42
|
-
} else {
|
|
43
|
-
return 3; // 90 deg;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function getAngleIndex(forceX: number, forceY: number) {
|
|
49
|
-
const index = getQuadrantIndex(forceX, forceY);
|
|
50
|
-
|
|
51
|
-
if (forceX > 0) {
|
|
52
|
-
if (forceY > 0) {
|
|
53
|
-
return 0 + index;
|
|
54
|
-
} else {
|
|
55
|
-
return (12 - index) % 12;
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
if (forceY > 0) {
|
|
59
|
-
return 6 - index;
|
|
60
|
-
} else {
|
|
61
|
-
return 6 + index;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface Particle {
|
|
68
|
-
/**
|
|
69
|
-
* The center of the particle.
|
|
70
|
-
* */
|
|
71
|
-
center: Point;
|
|
72
|
-
/**
|
|
73
|
-
* Points are possible positions of the particle.
|
|
74
|
-
*/
|
|
75
|
-
points: Array<Point>;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get the points of the marker.
|
|
80
|
-
* The points are equally spaced around the center of the marker.
|
|
81
|
-
*/
|
|
82
|
-
export function getPoints(marker: Marker, scale: number): Array<Point> {
|
|
83
|
-
const proprtion = 2;
|
|
84
|
-
const radius = Math.min(marker.width, marker.height) / proprtion / scale;
|
|
85
|
-
return Angles.RADIANS.map((r) => ({ x: marker.x + radius * Math.cos(r), y: marker.y + radius * Math.sin(r) }));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Simulate the positions of particles that are influencing each other.
|
|
90
|
-
* The particle coordinates can only be from a given set of points.
|
|
91
|
-
* The simulation is run until the particles are stable.
|
|
92
|
-
* The particles are sorted by rank.
|
|
93
|
-
*
|
|
94
|
-
* In case of marker simulation the points represent the posible centers of the marker
|
|
95
|
-
* from which the marker angle can be calculated.
|
|
96
|
-
*/
|
|
97
|
-
export function updatePointIndexes(particles: Particle[]) {
|
|
98
|
-
const indexes = new Array<number>(particles.length);
|
|
99
|
-
|
|
100
|
-
// Initialze simulation
|
|
101
|
-
// Particle indexes based on the center of the particles
|
|
102
|
-
|
|
103
|
-
const center = getCenter(particles);
|
|
104
|
-
|
|
105
|
-
for (let i = 0; i < particles.length; i++) {
|
|
106
|
-
const particleI = particles[i];
|
|
107
|
-
const centerI = particleI.center;
|
|
108
|
-
|
|
109
|
-
const dx = centerI.x - center.x;
|
|
110
|
-
const dy = centerI.y - center.y;
|
|
111
|
-
|
|
112
|
-
indexes[i] = Angles.getAngleIndex(dx, dy);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Run simulation
|
|
116
|
-
// Wait until all particles are stable or the simulation is stuck
|
|
117
|
-
|
|
118
|
-
let stable = false;
|
|
119
|
-
let set = new Set<string>();
|
|
120
|
-
|
|
121
|
-
while (!stable) {
|
|
122
|
-
stable = true;
|
|
123
|
-
|
|
124
|
-
for (let i = 0; i < particles.length; i++) {
|
|
125
|
-
const particle = particles[i];
|
|
126
|
-
const index = indexes[i];
|
|
127
|
-
|
|
128
|
-
const prevPoint = particle.points[getIndex(index, -1)];
|
|
129
|
-
const currPoint = particle.points[index];
|
|
130
|
-
const nextPoint = particle.points[getIndex(index, +1)];
|
|
131
|
-
|
|
132
|
-
let prevPointForce: number = 0;
|
|
133
|
-
let currPointForce: number = 0;
|
|
134
|
-
let nextPointForce: number = 0;
|
|
135
|
-
|
|
136
|
-
for (let j = 0; j < particles.length; j++) {
|
|
137
|
-
if (i === j) continue;
|
|
138
|
-
|
|
139
|
-
const particleF = particles[j];
|
|
140
|
-
const indexF = indexes[i];
|
|
141
|
-
const pointF = particleF.points[indexF];
|
|
142
|
-
|
|
143
|
-
const prevDx = prevPoint.x - pointF.x;
|
|
144
|
-
const prevDy = prevPoint.y - pointF.y;
|
|
145
|
-
prevPointForce += 1 / (prevDx * prevDx + prevDy * prevDy);
|
|
146
|
-
|
|
147
|
-
const currDx = currPoint.x - pointF.x;
|
|
148
|
-
const currDy = currPoint.y - pointF.y;
|
|
149
|
-
currPointForce += 1 / (currDx * currDx + currDy * currDy);
|
|
150
|
-
|
|
151
|
-
const nextDx = nextPoint.x - pointF.x;
|
|
152
|
-
const nextDy = nextPoint.y - pointF.y;
|
|
153
|
-
nextPointForce += 1 / (nextDx * nextDx + nextDy * nextDy);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
let direction = 0;
|
|
157
|
-
// If minimal force is on the left, direction is left
|
|
158
|
-
if (prevPointForce < currPointForce && prevPointForce < nextPointForce) direction = -1;
|
|
159
|
-
// If minimal force is on the right, direction is right
|
|
160
|
-
if (nextPointForce < currPointForce && nextPointForce < prevPointForce) direction = +1;
|
|
161
|
-
|
|
162
|
-
// Move particle point index in the direction of the minimal force
|
|
163
|
-
indexes[i] = getIndex(index, direction);
|
|
164
|
-
|
|
165
|
-
// If at least one particle moved, the simulation is not stable
|
|
166
|
-
if (direction !== 0) stable = false;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const setId = indexes.join('-');
|
|
170
|
-
if (set.has(setId)) break;
|
|
171
|
-
set.add(setId);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return indexes;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function getCenter(particles: Particle[]): Point {
|
|
178
|
-
if (particles?.length === 0) {
|
|
179
|
-
return { x: 0, y: 0 };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
let centerX = 0;
|
|
183
|
-
let centerY = 0;
|
|
184
|
-
|
|
185
|
-
for (const particle of particles) {
|
|
186
|
-
centerX += particle.center.x;
|
|
187
|
-
centerY += particle.center.y;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
centerX /= particles.length;
|
|
191
|
-
centerY /= particles.length;
|
|
192
|
-
|
|
193
|
-
return { x: centerX, y: centerY };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function getIndex(index: number, direction: number): number {
|
|
197
|
-
if (direction == 0) return index;
|
|
198
|
-
return (((index + direction) % Angles.COUNT) + Angles.COUNT) % Angles.COUNT;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
namespace Nodes {
|
|
203
|
-
export interface Node {
|
|
204
|
-
/** The marker that this node represents. */
|
|
205
|
-
marker: Marker;
|
|
206
|
-
/** State of the marker expanded or not. */
|
|
207
|
-
expanded: boolean;
|
|
208
|
-
/** The angle of the marker node. */
|
|
209
|
-
angle: number;
|
|
210
|
-
/** A marker node has a particle whose position is used to calculate the angle */
|
|
211
|
-
particle: Particles.Particle;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export interface Connection {
|
|
215
|
-
/** The zoom when bounds of influence are touching,
|
|
216
|
-
* used to filter out connections that are not influencing the marker nodes. */
|
|
217
|
-
zwt: number;
|
|
218
|
-
/** The marker nodes that are influencing each other. */
|
|
219
|
-
node1: Node;
|
|
220
|
-
node2: Node;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export interface Layer {
|
|
224
|
-
/** The zoom level of the layer */
|
|
225
|
-
zoom: number;
|
|
226
|
-
/** The connections of the layer */
|
|
227
|
-
connections: Array<Connection>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export function createNodes(markers: Array<Marker>): Array<Node> {
|
|
231
|
-
let nodes = new Array<Node>(markers.length);
|
|
232
|
-
|
|
233
|
-
// Create marker nodes
|
|
234
|
-
for (let i = 0; i < markers.length; i++) {
|
|
235
|
-
const marker = markers[i];
|
|
236
|
-
nodes[i] = {
|
|
237
|
-
marker: marker,
|
|
238
|
-
expanded: true,
|
|
239
|
-
angle: Particles.Angles.DEFAULT,
|
|
240
|
-
particle: {
|
|
241
|
-
center: { x: marker.x, y: marker.y },
|
|
242
|
-
points: Particles.getPoints(marker, 1)
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return nodes;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export function createConnections(nodes: Node[]): Array<Connection> {
|
|
251
|
-
const connections = new Array<Connection>();
|
|
252
|
-
|
|
253
|
-
// Create marker connection bounds of influence,
|
|
254
|
-
// bounds are the maximum rectangle where the marker can be positioned
|
|
255
|
-
const bounds = new Array<Bounds>(nodes.length);
|
|
256
|
-
|
|
257
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
258
|
-
const node = nodes[i];
|
|
259
|
-
const marker = node.marker;
|
|
260
|
-
|
|
261
|
-
bounds[i] = {
|
|
262
|
-
x: marker.x,
|
|
263
|
-
y: marker.y,
|
|
264
|
-
left: marker.width,
|
|
265
|
-
right: marker.width,
|
|
266
|
-
top: marker.height,
|
|
267
|
-
bottom: marker.height
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Calculate marker connections zoom when touching,
|
|
272
|
-
// the zoom when touching is the maximum zoom level at
|
|
273
|
-
// which the markers influencing each others position (angle, expanded, etc.)
|
|
274
|
-
for (let i1 = 0; i1 < nodes.length; i1++) {
|
|
275
|
-
const node1 = nodes[i1];
|
|
276
|
-
const bounds1 = bounds[i1];
|
|
277
|
-
|
|
278
|
-
for (let i2 = i1 + 1; i2 < nodes.length; i2++) {
|
|
279
|
-
const node2 = nodes[i2];
|
|
280
|
-
const bounds2 = bounds[i2];
|
|
281
|
-
|
|
282
|
-
const zwt = getBoundsZoomWhenTouching(bounds1, bounds2);
|
|
283
|
-
connections.push({ zwt, node1, node2 });
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return connections;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export function createLayers(connections: Connection[]): Array<Layer> {
|
|
291
|
-
const layers = new Array<Layer>();
|
|
292
|
-
|
|
293
|
-
// Create layers
|
|
294
|
-
for (let zoom = Zoom.MIN; zoom <= Zoom.MAX; zoom = Zoom.addSteps(zoom, 1)) {
|
|
295
|
-
layers.push({
|
|
296
|
-
zoom: zoom,
|
|
297
|
-
connections: []
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Add connections to layers
|
|
302
|
-
for (let i = 0; i < connections.length; i++) {
|
|
303
|
-
const connection = connections[i];
|
|
304
|
-
const zwt = connection.zwt;
|
|
305
|
-
|
|
306
|
-
for (let j = 0; j < layers.length; j++) {
|
|
307
|
-
const layer = layers[j];
|
|
308
|
-
if (zwt <= layer.zoom) break;
|
|
309
|
-
|
|
310
|
-
layer.connections.push(connection);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return layers;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function getExpandedConnections(layers: Layer[], zoom: number): Array<Connection> {
|
|
318
|
-
const layer = layers.find((l) => l.zoom == zoom);
|
|
319
|
-
if (!layer) throw new Error('Layer not found');
|
|
320
|
-
return layer.connections.filter((c) => c.node1.expanded && c.node2.expanded);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export function getNodeGraphs(connections: Array<Connection>): Array<Set<Node>> {
|
|
324
|
-
const graphs = new Array<Set<Node>>();
|
|
325
|
-
|
|
326
|
-
for (let i = 0; i < connections.length; i++) {
|
|
327
|
-
const connection = connections[i];
|
|
328
|
-
const node1 = connection.node1;
|
|
329
|
-
const node2 = connection.node2;
|
|
330
|
-
|
|
331
|
-
// Try to find the graphs of the nodes
|
|
332
|
-
let graph1: Set<Node> | undefined = undefined;
|
|
333
|
-
let graph2: Set<Node> | undefined = undefined;
|
|
334
|
-
|
|
335
|
-
for (let i = 0; i < graphs.length; i++) {
|
|
336
|
-
const graph = graphs[i];
|
|
337
|
-
if (graph.has(node1)) graph1 = graph;
|
|
338
|
-
if (graph.has(node2)) graph2 = graph;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Both nodes are in separate graphs
|
|
342
|
-
if (graph1 != undefined && graph2 != undefined) {
|
|
343
|
-
if (graph1 != graph2) {
|
|
344
|
-
graph2.forEach((n) => graph1.add(n));
|
|
345
|
-
graphs.splice(graphs.indexOf(graph2), 1);
|
|
346
|
-
}
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// One node is in a graph, the other is not
|
|
351
|
-
if (graph1 != undefined && graph2 == undefined) {
|
|
352
|
-
if (node2.expanded) graph1.add(node2);
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (graph1 == undefined && graph2 != undefined) {
|
|
357
|
-
if (node1.expanded) graph2.add(node1);
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Neither node is in a graph
|
|
362
|
-
const graph = new Set<Node>();
|
|
363
|
-
if (node1.expanded) graph.add(node1);
|
|
364
|
-
if (node2.expanded) graph.add(node2);
|
|
365
|
-
graphs.push(graph);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return graphs;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export function getAngles(nodes: Array<Node>): Array<number> {
|
|
372
|
-
if (nodes.length == 1) return [Particles.Angles.DEFAULT];
|
|
373
|
-
|
|
374
|
-
const indexes = Particles.updatePointIndexes(nodes.map((m) => m.particle));
|
|
375
|
-
const angles = indexes.map((i) => Particles.Angles.DEGREES[i]);
|
|
376
|
-
|
|
377
|
-
return angles;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export function getOverlapingNodeIndex(nodes: Array<Node>, scale: number) {
|
|
381
|
-
const overlaps = new Array<Array<Node>>();
|
|
382
|
-
const bounds = new Array<Bounds>(nodes.length);
|
|
383
|
-
|
|
384
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
385
|
-
const node1 = nodes[i];
|
|
386
|
-
const bounds1 = getBounds(node1.marker, node1.angle, scale);
|
|
387
|
-
|
|
388
|
-
bounds[i] = bounds1;
|
|
389
|
-
|
|
390
|
-
for (let j = 0; j < i; j++) {
|
|
391
|
-
const bounds2 = bounds[j];
|
|
392
|
-
|
|
393
|
-
if (areBoundsOverlaping(bounds2, bounds1)) {
|
|
394
|
-
const node2 = nodes[j];
|
|
395
|
-
|
|
396
|
-
if (overlaps[i] == undefined) overlaps[i] = [node2];
|
|
397
|
-
else overlaps[i].push(node2);
|
|
398
|
-
|
|
399
|
-
if (overlaps[j] == undefined) overlaps[j] = [node1];
|
|
400
|
-
else overlaps[j].push(node1);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
let worstNodeIndex = -1;
|
|
406
|
-
let worstScore = 0;
|
|
407
|
-
|
|
408
|
-
for (let j = 0; j < overlaps.length; j++) {
|
|
409
|
-
const node = nodes[j];
|
|
410
|
-
const nodesOverlaping = overlaps[j];
|
|
411
|
-
if (nodesOverlaping == undefined) continue;
|
|
412
|
-
|
|
413
|
-
const score = nodesOverlaping.reduce((s, n) => s + (n.marker.rank - node.marker.rank), 0);
|
|
414
|
-
|
|
415
|
-
if (worstNodeIndex == undefined || score > worstScore) {
|
|
416
|
-
worstNodeIndex = j;
|
|
417
|
-
worstScore = score;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return worstNodeIndex;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
export function updateNodeParticles(nodes: Array<Node>, scale: number) {
|
|
425
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
426
|
-
const node = nodes[i];
|
|
427
|
-
node.particle.points = Particles.getPoints(node.marker, scale);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
export function updateNodeAngles(nodes: Array<Node>) {
|
|
432
|
-
if (nodes.length == 1) {
|
|
433
|
-
nodes[0].angle = Particles.Angles.DEFAULT;
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const indexes = Particles.updatePointIndexes(nodes.map((m) => m.particle));
|
|
438
|
-
|
|
439
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
440
|
-
const node = nodes[i];
|
|
441
|
-
node.angle = Particles.Angles.DEGREES[indexes[i]];
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
namespace Zoom {
|
|
447
|
-
export const MIN = MAP_MIN_ZOOM;
|
|
448
|
-
export const MAX = MAP_MAX_ZOOM;
|
|
449
|
-
|
|
450
|
-
export const SCALE = 10;
|
|
451
|
-
export const STEP = 1 / SCALE;
|
|
452
|
-
|
|
453
|
-
export function addSteps(zoom: number, count: number) {
|
|
454
|
-
return Math.round((zoom + count * STEP) * SCALE) / SCALE;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
namespace Threshold {
|
|
459
|
-
export interface Event {
|
|
460
|
-
/** The zoom threshold */
|
|
461
|
-
zoom: number;
|
|
462
|
-
/** The expanded markers */
|
|
463
|
-
ids: Array<string>;
|
|
464
|
-
/** The angles of all the markers after the zoom expand threshold */
|
|
465
|
-
angles: Array<{ id: string; value: number }>;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
export function createEvent(nodes: Nodes.Node[], zoom: number): Event {
|
|
469
|
-
return {
|
|
470
|
-
zoom: zoom,
|
|
471
|
-
ids: nodes.map((n) => n.marker.id),
|
|
472
|
-
angles: nodes.map((n) => ({ id: n.marker.id, value: n.angle }))
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Create marker nodes, all expanded initially
|
|
479
|
-
* Calculate zooms when touching bounds of influence for markers
|
|
480
|
-
* From max zoom to min zoom in steps, get graphs of expanded markers influencing each other
|
|
481
|
-
* Foreach graph:
|
|
482
|
-
* get best fit angles of marker particles
|
|
483
|
-
* get all overlaping bounds
|
|
484
|
-
* based on number of overlaps and rank, collapse a node and recalculate graphs
|
|
485
|
-
* Add threshold event
|
|
486
|
-
* If only one node expanded left, break
|
|
487
|
-
*/
|
|
488
|
-
function getThresholds(markers: Array<Marker>): Array<Threshold.Event> {
|
|
489
|
-
let thresholds = new Array<Threshold.Event>();
|
|
490
|
-
|
|
491
|
-
// Initialze nodes
|
|
492
|
-
const nodes = Nodes.createNodes(markers);
|
|
493
|
-
const connections = Nodes.createConnections(nodes);
|
|
494
|
-
const layers = Nodes.createLayers(connections);
|
|
495
|
-
|
|
496
|
-
// Initialize zoom
|
|
497
|
-
const maxZoom = layers.at(-1)?.zoom ?? MAP_MAX_ZOOM;
|
|
498
|
-
const minZoom = MAP_MIN_ZOOM;
|
|
499
|
-
|
|
500
|
-
// Initially add the last threshold event
|
|
501
|
-
thresholds.push(Threshold.createEvent(nodes, Zoom.addSteps(maxZoom, 1)));
|
|
502
|
-
|
|
503
|
-
// Go from last to first zoom
|
|
504
|
-
for (let zoom = maxZoom; zoom >= minZoom; zoom = Zoom.addSteps(zoom, -1)) {
|
|
505
|
-
// Calculate scale
|
|
506
|
-
const scale = Math.pow(2, zoom);
|
|
507
|
-
|
|
508
|
-
// Get connections of expaneded nodes from a layer
|
|
509
|
-
const expandedConnections = Nodes.getExpandedConnections(layers, zoom);
|
|
510
|
-
// Get the graphs of expanded markers influencing each other
|
|
511
|
-
const expandedNodeGraphs = Nodes.getNodeGraphs(expandedConnections);
|
|
512
|
-
|
|
513
|
-
for (let i = 0; i < expandedNodeGraphs.length; i++) {
|
|
514
|
-
// Get the array of expanded nodes from graph
|
|
515
|
-
let nodeArray = Array.from(expandedNodeGraphs[i]).toSorted((p1, p2) => p1.marker.rank - p2.marker.rank);
|
|
516
|
-
|
|
517
|
-
// Update nodes particles for the given zoom level
|
|
518
|
-
Nodes.updateNodeParticles(nodeArray, scale);
|
|
519
|
-
|
|
520
|
-
while (nodeArray.length > 1) {
|
|
521
|
-
// Update nodes angles
|
|
522
|
-
Nodes.updateNodeAngles(nodeArray);
|
|
523
|
-
|
|
524
|
-
// Get the index of the overlaping node
|
|
525
|
-
// If there is an no overlaping node break
|
|
526
|
-
let overlapingNodeIndex = Nodes.getOverlapingNodeIndex(nodeArray, scale);
|
|
527
|
-
if (overlapingNodeIndex == -1) break;
|
|
528
|
-
|
|
529
|
-
// Else, collapse it
|
|
530
|
-
// and remove it from the array and try again
|
|
531
|
-
nodeArray[overlapingNodeIndex].expanded = false;
|
|
532
|
-
nodeArray.splice(overlapingNodeIndex, 1);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Get the expanded nodes
|
|
537
|
-
const expandedNodes = nodes.filter((n) => n.expanded);
|
|
538
|
-
// Create threshold event
|
|
539
|
-
thresholds.push(Threshold.createEvent(expandedNodes, zoom));
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Return the thresholds in reverse order (from min zoom to max zoom)
|
|
543
|
-
return thresholds.reverse();
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
export { getThresholds };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { getRectangleOffsets } from './rectangle.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get the marker position parameters given the marker width, height and angle.
|
|
5
|
-
* The output are the marker x and y offset, the pin angle and skew.
|
|
6
|
-
* The marker pin points from the marker body to the anchor of the marker
|
|
7
|
-
* */
|
|
8
|
-
export function getPositionParams(markerWidth: number, markerHeight: number, markerAngle: number) {
|
|
9
|
-
// Calculate marker offsets, its values are such that the rectnagle edge always touches the center anchor
|
|
10
|
-
const markerOffsets = getRectangleOffsets(markerWidth, markerHeight, markerAngle);
|
|
11
|
-
const markerOffsetX = markerOffsets.offsetX;
|
|
12
|
-
const markerOffsetY = markerOffsets.offsetY;
|
|
13
|
-
|
|
14
|
-
// Calculate pin angle, it point to the center of the inverse width/height rectangle of the marker
|
|
15
|
-
const pinRectWidth = markerHeight;
|
|
16
|
-
const pinRectHeight = markerWidth;
|
|
17
|
-
|
|
18
|
-
const pinRectOffsets = getRectangleOffsets(pinRectWidth, pinRectHeight, markerAngle);
|
|
19
|
-
const pinCenterX = pinRectWidth / 2 + pinRectOffsets.offsetX;
|
|
20
|
-
const pinCenterY = pinRectHeight / 2 + pinRectOffsets.offsetY;
|
|
21
|
-
const pinAngleRad = Math.atan2(pinCenterY, pinCenterX);
|
|
22
|
-
const pinAngleDeg = (pinAngleRad / Math.PI) * 180 - 45;
|
|
23
|
-
|
|
24
|
-
// Calculate pin skew, its is lower (ak. wider) the closer the pin is to the center of the marker
|
|
25
|
-
const pinMinSkew = 0;
|
|
26
|
-
const pinMaxSkew = 30;
|
|
27
|
-
|
|
28
|
-
const markerCenterX = markerOffsetX + markerWidth / 2;
|
|
29
|
-
const markerCenterY = markerOffsetY + markerHeight / 2;
|
|
30
|
-
|
|
31
|
-
const pinCenterDistance = Math.sqrt(markerCenterX * markerCenterX + markerCenterY * markerCenterY);
|
|
32
|
-
const pinCenterMinDistance = Math.min(markerWidth, markerHeight) / 2;
|
|
33
|
-
const pinCenterMaxDistance = Math.sqrt(markerWidth * markerWidth + markerHeight * markerHeight) / 2;
|
|
34
|
-
|
|
35
|
-
const pinSkewRatio = (pinCenterDistance - pinCenterMinDistance) / (pinCenterMaxDistance - pinCenterMinDistance);
|
|
36
|
-
const pinSkewDeg = pinMinSkew + pinSkewRatio * (pinMaxSkew - pinMinSkew);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
markerOffsetX,
|
|
40
|
-
markerOffsetY,
|
|
41
|
-
pinAngleDeg,
|
|
42
|
-
pinSkewDeg
|
|
43
|
-
};
|
|
44
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { MAP_BASE_SIZE } from '../constants.js';
|
|
2
|
-
|
|
3
|
-
// The mapping between latitude, longitude and pixels is defined by the web
|
|
4
|
-
// mercator projection.
|
|
5
|
-
export function getPoint(lat: number, lng: number) {
|
|
6
|
-
let siny = Math.sin((lat * Math.PI) / 180);
|
|
7
|
-
|
|
8
|
-
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
|
|
9
|
-
// about a third of a tile past the edge of the world tile.
|
|
10
|
-
siny = Math.min(Math.max(siny, -0.9999), 0.9999);
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
x: MAP_BASE_SIZE * (0.5 + lng / 360),
|
|
14
|
-
y: MAP_BASE_SIZE * (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI))
|
|
15
|
-
};
|
|
16
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Get the rectangles position offsets, its values are such that the rectnagle edge always touches the anchor (0,0)
|
|
3
|
-
* The recangles initial position is such that is top left corner is at the anchor (0,0)
|
|
4
|
-
* The angle is the direction from the achor to the center of the rectangle, to which the rectangle is pointed
|
|
5
|
-
* */
|
|
6
|
-
export function getRectangleOffsets(width: number, height: number, angleDeg: number) {
|
|
7
|
-
const angleRad = angleDeg * (Math.PI / 180);
|
|
8
|
-
|
|
9
|
-
const widthHalf = width / 2;
|
|
10
|
-
const heightHalf = height / 2;
|
|
11
|
-
const diagonalHalf = Math.sqrt(widthHalf * widthHalf + heightHalf * heightHalf);
|
|
12
|
-
const aspectDeg = Math.atan(heightHalf / widthHalf) * (180 / Math.PI);
|
|
13
|
-
|
|
14
|
-
const brDeg = aspectDeg;
|
|
15
|
-
const blDeg = 180 - aspectDeg;
|
|
16
|
-
const trDeg = 180 + aspectDeg;
|
|
17
|
-
const tlDeg = 360 - aspectDeg;
|
|
18
|
-
|
|
19
|
-
switch (true) {
|
|
20
|
-
// Quadrant bottom
|
|
21
|
-
case brDeg <= angleDeg && angleDeg <= blDeg: {
|
|
22
|
-
return {
|
|
23
|
-
offsetX: diagonalHalf * Math.cos(angleRad) - widthHalf,
|
|
24
|
-
offsetY: 0
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
// Quadrant left
|
|
28
|
-
case blDeg <= angleDeg && angleDeg <= trDeg: {
|
|
29
|
-
return {
|
|
30
|
-
offsetX: -width,
|
|
31
|
-
offsetY: diagonalHalf * Math.sin(angleRad) - heightHalf
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
// Quadrant top
|
|
35
|
-
case trDeg <= angleDeg && angleDeg <= tlDeg: {
|
|
36
|
-
return {
|
|
37
|
-
offsetX: diagonalHalf * Math.cos(angleRad) - widthHalf,
|
|
38
|
-
offsetY: -height
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
// Quadrant right
|
|
42
|
-
default: {
|
|
43
|
-
return {
|
|
44
|
-
offsetX: 0,
|
|
45
|
-
offsetY: diagonalHalf * Math.sin(angleRad) - heightHalf
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export namespace Types {
|
|
2
|
-
export interface Popup {
|
|
3
|
-
id: string;
|
|
4
|
-
index: number;
|
|
5
|
-
lat: number;
|
|
6
|
-
lng: number;
|
|
7
|
-
width: number;
|
|
8
|
-
height: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface Marker {
|
|
12
|
-
id: string;
|
|
13
|
-
lat: number;
|
|
14
|
-
lng: number;
|
|
15
|
-
width: number;
|
|
16
|
-
height: number;
|
|
17
|
-
zet: number;
|
|
18
|
-
angs: [number, number][];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface Block {
|
|
22
|
-
id: string;
|
|
23
|
-
sw: { lat: number; lng: number };
|
|
24
|
-
ne: { lat: number; lng: number };
|
|
25
|
-
zs: number;
|
|
26
|
-
ze: number;
|
|
27
|
-
markers: Marker[];
|
|
28
|
-
}
|
|
29
|
-
}
|