Sebastiankay commited on
Commit
f00d02d
·
verified ·
1 Parent(s): 7b1b425

Update static/map.html

Browse files
Files changed (1) hide show
  1. static/map.html +1000 -995
static/map.html CHANGED
@@ -128,1050 +128,1055 @@
128
  const normalizedName = pathSegments[pathSegments.length - 1];
129
 
130
  fetch(`/api/map/${normalizedName}`)
131
- .then(response => response.json())
132
- .then(mapData => {
133
- console.log("Map Data:", mapData);
134
- })
135
- .catch(error => console.error("Fehler beim Laden der Map:", error));
136
-
137
- //const transform = [0.1855, 113.1, 0.1855, 167.8]
138
- const transform = mapData.transform
139
- const bounds = mapData.bounds
140
- const svgBounds = mapData.svgBounds ? mapData.svgBounds : bounds
141
- const coordinateRotation = mapData.coordinateRotation ? mapData.coordinateRotation : 0
142
- const svgPath = mapData.svgPath
143
- const imageUrl = mapData.svgPath
144
- const minZoom = mapData.minZoom ? mapData.minZoom : 1
145
- const maxZoom = mapData.maxZoom ? mapData.maxZoom : 6
146
- const showElevation = false;
147
- const showStaticMarkers = false
148
- const wsgroup = "1234"
149
-
150
- let currentZoom = 3;
151
-
152
- //MARK: INIT WEBSOCKET
153
- // Initialize WebSocket connection
154
- const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`);
155
-
156
- // WebSocket event handlers
157
- ws.onmessage = function (event) {
158
- console.log(event.data);
159
- const data = JSON.parse(event.data);
160
-
161
- switch (data.type) {
162
- case "coordinates":
163
- const parsedData = data.data;
164
- localStorage.setItem("last_marker", JSON.stringify(parsedData));
165
- loadLocalData();
166
- break;
167
-
168
- case "location_map":
169
- const map_name = data.data.map.toLowerCase().replaceAll(" ", "-");
170
- localStorage.setItem("last_map_name", map_name);
171
- localStorage.removeItem("last_marker");
172
- removeAllMarkers(); // Clear all markers when the map changes
173
- if (!location.pathname.includes(map_name)) {
174
- location.pathname = `/map/${map_name}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  } else {
176
- location.reload();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  }
178
- break;
179
-
180
- case "new_rade_data":
181
- console.log(data.data);
182
- const radeData = JSON.parse(data.data);
183
- localStorage.setItem("rade_data", JSON.stringify(radeData));
184
- loadLocalData();
185
- break;
186
- }
187
- };
188
-
189
- ws.onopen = function (event) {
190
- ws.send(JSON.stringify({ type: "join", group: wsgroup }));
191
- };
192
-
193
- // Function to remove all markers
194
- function removeAllMarkers() {
195
- for (const playername in playerMarkers) {
196
- if (playerMarkers.hasOwnProperty(playername)) {
197
- map.removeLayer(playerMarkers[playername]);
198
  }
199
- }
200
- Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]);
201
- console.log("Alle Marker wurden entfernt.");
202
- }
203
-
204
- // Function to load and display local marker data
205
- function loadLocalData() {
206
- const map_name = localStorage.getItem("last_map_name");
207
- if (map_name) {
208
- if (!location.pathname.includes(map_name)) {
209
- localStorage.removeItem("last_marker");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  }
211
- } else {
212
- localStorage.removeItem("last_marker");
213
- }
214
-
215
- const markerData = localStorage.getItem("last_marker");
216
- if (markerData) {
217
- const parsedData = JSON.parse(markerData);
218
- // Use the new addMarker function with all required parameters
219
- addMarker(
220
- parsedData.x,
221
- parsedData.y,
222
- parsedData.z,
223
- parsedData.timestamp,
224
- parsedData.preview || false,
225
- parsedData.actualmap || "Unbekannte Map",
226
- parsedData.playername || "Unnamed Player",
227
- parsedData.markercolor || false
228
- );
229
- }
230
- }
231
-
232
- const images = {
233
- 'container_bank-cash-register': 'container_cash-register',
234
- 'container_bank-safe': 'container_safe',
235
- 'container_buried-barrel-cache': 'container_buried-barrel-cache',
236
- 'container_cash-register': 'container_cash-register',
237
- 'container_cash-register-tar2-2': 'container_cash-register',
238
- 'container_dead-civilian': 'container_dead-scav',
239
- 'container_dead-scav': 'container_dead-scav',
240
- 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate',
241
- 'container_pmc-body': 'container_dead-scav',
242
- 'container_civilian-body': 'container_dead-scav',
243
- 'container_drawer': 'container_drawer',
244
- 'container_duffle-bag': 'container_duffle-bag',
245
- 'container_grenade-box': 'container_grenade-box',
246
- 'container_ground-cache': 'container_ground-cache',
247
- 'container_jacket': 'container_jacket',
248
- 'container_lab-technician-body': 'container_dead-scav',
249
- 'container_medbag-smu06': 'container_medbag-smu06',
250
- 'container_medcase': 'container_medcase',
251
- 'container_medical-supply-crate': 'container_crate',
252
- 'container_pc-block': 'container_pc-block',
253
- 'container_plastic-suitcase': 'container_plastic-suitcase',
254
- 'container_ration-supply-crate': 'container_crate',
255
- 'container_safe': 'container_safe',
256
- 'container_scav-body': 'container_dead-scav',
257
- 'container_shturmans-stash': 'container_weapon-box',
258
- 'container_technical-supply-crate': 'container_crate',
259
- 'container_toolbox': 'container_toolbox',
260
- 'container_weapon-box': 'container_weapon-box',
261
- 'container_wooden-ammo-box': 'container_wooden-ammo-box',
262
- 'container_wooden-crate': 'container_wooden-crate',
263
- 'extract_pmc': 'extract_pmc',
264
- 'extract_scav': 'extract_scav',
265
- 'extract_shared': 'extract_shared',
266
- 'extract_transit': 'extract_transit',
267
- 'hazard': 'hazard',
268
- 'hazard_mortar': 'hazard_mortar',
269
- 'hazard_minefield': 'hazard',
270
- 'hazard_sniper': 'hazard',
271
- 'key': 'key',
272
- 'lock': 'lock',
273
- 'loose_loot': 'loose_loot',
274
- 'quest_item': 'quest_item',
275
- 'quest_objective': 'quest_objective',
276
- 'spawn_sniper_scav': 'spawn_sniper_scav',
277
- 'spawn_bloodhound': 'spawn_bloodhound',
278
- 'spawn_boss': 'spawn_boss',
279
- 'spawn_cultist-priest': 'spawn_cultist-priest',
280
- 'spawn_pmc': 'spawn_pmc',
281
- 'spawn_rogue': 'spawn_rogue',
282
- 'spawn_scav': 'spawn_scav',
283
- 'stationarygun': 'stationarygun',
284
- 'switch': 'switch',
285
- };
286
-
287
- const categories = {
288
- 'extract_pmc': 'PMC',
289
- 'extract_shared': 'Shared',
290
- 'extract_scav': 'Scav',
291
- 'extract_transit': 'Transit',
292
- 'spawn_sniper_scav': 'Sniper Scav',
293
- 'spawn_pmc': 'PMC',
294
- 'spawn_scav': 'Scav',
295
- 'spawn_boss': 'Boss',
296
- 'quest_item': 'Item',
297
- 'quest_objective': 'Objective',
298
- 'lock': 'Locks',
299
- 'lever': 'Lever',
300
- 'stationarygun': 'Stationary Gun',
301
- 'switch': 'Switch',
302
- 'place-names': 'Place Names',
303
- };
304
-
305
- function getCRS(transform) {
306
- let scaleX = 1;
307
- let scaleY = 1;
308
- let marginX = 0;
309
- let marginY = 0;
310
- if (transform) {
311
- scaleX = transform[0];
312
- scaleY = transform[2] * -1;
313
- marginX = transform[1];
314
- marginY = transform[3];
315
- }
316
- return L.extend({}, L.CRS.Simple, {
317
- transformation: new L.Transformation(scaleX, marginX, scaleY, marginY),
318
- projection: L.extend({}, L.Projection.LonLat, {
319
- project: latLng => {
320
- return L.Projection.LonLat.project(applyRotation(latLng, coordinateRotation));
321
- },
322
- unproject: point => {
323
- return applyRotation(L.Projection.LonLat.unproject(point), coordinateRotation * -1);
324
- },
325
- }),
326
- });
327
- }
328
-
329
- function applyRotation(latLng, rotation) {
330
- if (!latLng.lng && !latLng.lat) {
331
- return L.latLng(0, 0);
332
- }
333
- if (!rotation) {
334
- return latLng;
335
- }
336
-
337
- const angleInRadians = (rotation * Math.PI) / 180;
338
- const cosAngle = Math.cos(angleInRadians);
339
- const sinAngle = Math.sin(angleInRadians);
340
-
341
- const { lng: x, lat: y } = latLng;
342
- const rotatedX = x * cosAngle - y * sinAngle;
343
- const rotatedY = x * sinAngle + y * cosAngle;
344
- return L.latLng(rotatedY, rotatedX);
345
- }
346
-
347
- function pos(position) {
348
- return [position.z, position.x];
349
- }
350
-
351
- function addElevation(item, popup) {
352
- if (!showElevation) {
353
- return;
354
- }
355
- const elevationContent = L.DomUtil.create('div', undefined, popup);
356
- elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`;
357
- if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) {
358
- const heightContent = L.DomUtil.create('div', undefined, popup);
359
- heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`;
360
- }
361
- }
362
-
363
- function markerIsOnLayer(marker, layer) {
364
- if (!layer) {
365
- return true;
366
- }
367
- var top = marker.options.top || marker.options.position.y;
368
- var bottom = marker.options.bottom || marker.options.position.y;
369
- for (const extent of layer.options.extents) {
370
- if (top >= extent.height[0] && bottom < extent.height[1]) {
371
- let containedType = 'partial';
372
- if (bottom >= extent.height[0] && top <= extent.height[1]) {
373
- containedType = 'full';
374
  }
375
- if (extent.bounds) {
376
- for (const boundsArray of extent.bounds) {
377
- const bounds = getBounds(boundsArray);
378
- if (bounds.contains(pos(marker.options.position))) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  return containedType;
380
  }
381
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  } else {
383
- return containedType;
 
 
 
384
  }
385
  }
386
- }
387
- return false;
388
- }
389
-
390
- function markerIsOnActiveLayer(marker) {
391
- if (!marker.options.position) {
392
- return true;
393
- }
394
-
395
- const map = marker._map;
396
-
397
- // check if marker is completely contained by inactive layer
398
- const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay);
399
- for (const layer of overlays) {
400
- for (const extent of layer.options.extents) {
401
- if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) {
402
- return false;
403
  }
404
  }
405
- }
406
-
407
- // check if marker is on active overlay
408
- const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay);
409
- if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) {
410
- return true;
411
- }
412
-
413
- // check if marker is on base layer
414
- const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !L.options?.overlay);
415
- if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) {
416
- return true;
417
- }
418
-
419
- return false;
420
- }
421
-
422
- function checkMarkerForActiveLayers(event) {
423
- const marker = event.target || event;
424
- const outline = marker.options.outline;
425
- const onLevel = markerIsOnActiveLayer(marker);
426
- if (onLevel) {
427
- marker._icon?.classList.remove('off-level');
428
- if (outline) {
429
- outline._path?.classList.remove('off-level');
 
 
 
 
 
 
 
 
 
 
430
  }
431
- } else {
432
- marker._icon?.classList.add('off-level');
433
- if (outline) {
434
- outline._path?.classList.add('off-level');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
- }
437
- }
438
-
439
- function activateMarkerLayer(event) {
440
- const marker = event.target || event;
441
- if (markerIsOnActiveLayer(marker)) {
442
- return;
443
- }
444
- const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
445
- for (const layer of activeLayers) {
446
- layer.removeFrom(marker._map);
447
- }
448
- const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
449
- for (const layer of heightLayers) {
450
- if (markerIsOnLayer(marker, layer)) {
451
- layer.addTo(marker._map);
452
- break;
453
  }
454
- }
455
- }
456
-
457
- const getALink = (path, contents) => {
458
- const a = L.DomUtil.create('a');
459
- a.setAttribute('href', path);
460
- a.setAttribute('target', '_blank');
461
- a.append(contents);
462
- // a.addEventListener('click', (event) => {
463
- // navigate(path);
464
- // event.preventDefault();
465
- // });
466
- return a;
467
- };
468
-
469
- function getScaledBounds(bounds, scaleFactor) {
470
- // Calculate the center point of the bounds
471
- const centerX = (bounds[0][0] + bounds[1][0]) / 2;
472
- const centerY = (bounds[0][1] + bounds[1][1]) / 2;
473
-
474
- // Calculate the new width and height
475
- const width = bounds[1][0] - bounds[0][0];
476
- const height = bounds[1][1] - bounds[0][1];
477
- const newWidth = width * scaleFactor;
478
- const newHeight = height * scaleFactor;
479
-
480
- // Update the coordinates of the two points defining the bounds
481
- const newBounds = [
482
- [centerY - newHeight / 2, centerX - newWidth / 2],
483
- [centerY + newHeight / 2, centerX + newWidth / 2]
484
- ];
485
-
486
- // console.log("Initial Rectangle:", bounds);
487
- // console.log("Scaled Rectangle:", newBounds);
488
- // console.log("Center:", L.bounds(bounds).getCenter(true));
489
-
490
- return newBounds;
491
- }
492
-
493
- // Erstelle die Karte
494
- //const map = L.map('map').setView([0, 0], 2);
495
-
496
- const map = L.map('map', {
497
- maxBounds: getScaledBounds(svgBounds, 1.5),
498
- //maxBounds: bounds,
499
- center: [0, 0],
500
- zoom: 2,
501
- minZoom: minZoom,
502
- maxZoom: maxZoom,
503
- zoomSnap: 0.1,
504
- scrollWheelZoom: true,
505
- wheelPxPerZoomLevel: 120,
506
- crs: getCRS(transform),
507
- attributionControl: false,
508
- id: "wwoodsMap",
509
- });
510
-
511
- const layerControl = L.control.groupedLayers(null, null, {
512
- position: 'topleft',
513
- collapsed: true,
514
- groupCheckboxes: true,
515
- groupsCollapsable: true,
516
- exclusiveOptionalGroups: ['Levels'],
517
- }).addTo(map);
518
- layerControl.on('overlayToggle', (e) => {
519
- const layerState = e.detail;
520
- if (layerState.checked) {
521
- mapViewRef.current.layer = layerState.key;
522
- } else {
523
- mapViewRef.current.layer = undefined;
524
- }
525
- });
526
- layerControl.on('layerToggle', (e) => {
527
- const layerState = e.detail;
528
- if (!layerState.checked) {
529
- mapSettingsRef.current.hiddenLayers.push(layerState.key);
530
- } else {
531
- mapViewRef.current.layer = layerState.key;
532
- mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== layerState.key);
533
- }
534
- updateSavedMapSettings();
535
- });
536
- layerControl.on('groupToggle', (e) => {
537
- const groupState = e.detail;
538
- for (const groupLayer of layerControl._layers) {
539
- if (groupLayer.group?.key !== groupState.key) {
540
- continue;
541
  }
542
- if (!groupState.checked) {
543
- mapSettingsRef.current.hiddenLayers.push(groupLayer.key);
544
- } else {
545
- mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== groupLayer.key);
 
 
 
 
546
  }
547
- }
548
- if (!groupState.checked) {
549
- mapSettingsRef.current.hiddenGroups.push(groupState.key);
550
- } else {
551
- mapSettingsRef.current.hiddenGroups = mapSettingsRef.current.hiddenGroups.filter(key => key !== groupState.key);
552
- }
553
- updateSavedMapSettings();
554
- });
555
- layerControl.on('groupCollapseToggle', (e) => {
556
- const groupState = e.detail;
557
- if (groupState.collapsed) {
558
- mapSettingsRef.current.collapsedGroups.push(groupState.key);
559
-
560
- } else {
561
- mapSettingsRef.current.collapsedGroups = mapSettingsRef.current.collapsedGroups.filter(key => key !== groupState.key);
562
- }
563
- updateSavedMapSettings();
564
- });
565
-
566
- const getLayerOptions = (layerKey, groupKey, layerName) => {
567
- return {
568
- groupKey,
569
- layerKey,
570
- groupName: groupKey,
571
- layerName: layerName || categories[layerKey] || layerKey,
572
- //groupHidden: Boolean(mapSettingsRef.current.hiddenGroups?.includes(groupKey)),
573
- //layerHidden: Boolean(mapSettingsRef.current.hiddenLayers?.includes(layerKey)),
574
- image: images[layerKey] ? "/static/maps/interactive/${images[layerKey]}.png" : undefined,
575
- //groupCollapsed: Boolean(mapSettingsRef.current.collapsedGroups?.includes(groupKey)),
576
- };
577
- };
578
-
579
- const addLayer = (layer, layerKey, groupKey, layerName) => {
580
- layer.key = layerKey;
581
- const layerOptions = getLayerOptions(layerKey, groupKey, layerName);
582
- if (!layerOptions.layerHidden) {
583
- layer.addTo(map);
584
- }
585
- layerControl.addOverlay(layer, layerOptions.layerName, layerOptions);
586
- };
587
- map.layerControl = layerControl;
588
-
589
- // Hinzufügen des Image-Overlays
590
- const overlay = L.imageOverlay(imageUrl, getBounds(svgBounds));
591
- overlay.addTo(map);
592
-
593
- function checkMarkerBounds(position, markerBounds) {
594
- if (position.x < markerBounds.TL.x) markerBounds.TL.x = position.x;
595
- if (position.z > markerBounds.TL.z) markerBounds.TL.z = position.z;
596
- if (position.x > markerBounds.BR.x) markerBounds.BR.x = position.x;
597
- if (position.z < markerBounds.BR.z) markerBounds.BR.z = position.z;
598
- }
599
-
600
- function getBounds(bounds) {
601
- if (!bounds) {
602
- return undefined;
603
- }
604
- return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]);
605
- //return [[bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]];
606
- }
607
-
608
- function mouseHoverOutline(event) {
609
- const outline = event.target.options.outline;
610
- if (event.originalEvent.type === 'mouseover') {
611
- outline._path.classList.remove('not-shown');
612
- } else if (!outline._path.classList.contains('force-show')) {
613
- outline._path.classList.add('not-shown');
614
- }
615
- }
616
-
617
- function toggleForceOutline(event) {
618
- const outline = event.target.options.outline;
619
- outline._path.classList.toggle('force-show');
620
- if (outline._path.classList.contains('force-show')) {
621
- outline._path.classList.remove('not-shown');
622
- }
623
- activateMarkerLayer(event);
624
- }
625
-
626
- function activateMarkerLayer(event) {
627
- const marker = event.target || event;
628
- if (markerIsOnActiveLayer(marker)) {
629
- return;
630
- }
631
- const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
632
- for (const layer of activeLayers) {
633
- layer.removeFrom(marker._map);
634
- }
635
- const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
636
- for (const layer of heightLayers) {
637
- if (markerIsOnLayer(marker, layer)) {
638
- layer.addTo(marker._map);
639
- break;
640
  }
641
- }
642
- }
643
-
644
- function outlineToPoly(outline) {
645
- if (!outline) return [];
646
- return outline.map(vector => [vector.z, vector.x]);
647
- }
648
-
649
- const layerOptions = {
650
- maxZoom: maxZoom,
651
- maxNativeZoom: maxZoom,
652
- extents: [
653
- {
654
- height: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
655
- bounds: [bounds],
656
  }
657
- ],
658
- type: 'map-layer',
659
- };
660
- let tileLayer = false;
661
- const baseLayers = [];
662
- const tileSize = 256;
663
- let svgLayer = false;
664
- if (svgPath) {
665
- const svgBounds2 = svgBounds ? getBounds(svgBounds) : bounds;
666
- svgLayer = L.imageOverlay(svgPath, svgBounds2, layerOptions);
667
- baseLayers.push(svgLayer);
668
- }
669
-
670
- const positionIsInBounds = (position) => {
671
- return getBounds(bounds).contains(pos(position));
672
- };
673
-
674
- let markerBounds = {
675
- 'TL': { x: Number.MAX_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER },
676
- 'BR': { x: Number.MIN_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER }
677
- }
678
-
679
- if (mapData.labels?.length > 0) {
680
- const labelsGroup = L.layerGroup();
681
- const defaultHeight = ((layerOptions.extents[0].height[1] - layerOptions.extents[0].height[0]) / 2) + layerOptions.extents[0].height[0];
682
- for (const label of mapData.labels) {
683
- const fontSize = label.size ? label.size : 100;
684
- const height = label.position.length < 3 ? defaultHeight : label.position[2];
685
- const rotation = label.rotation ? label.rotation : 0;
686
- L.marker(pos({ x: label.position[0], z: label.position[1] }), {
687
- icon: L.divIcon({ html: `<div class="label" style="font-size: ${fontSize}%; transform: translate3d(-50%, -50%, 0) rotate(${rotation}deg)">${label.text}</div>`, className: 'map-area-label', layers: baseLayers, }),
688
- interactive: false,
689
- zIndexOffset: -100000,
690
- position: {
691
- x: label.position[0],
692
- y: height,
693
- z: label.position[1],
694
- },
695
- top: typeof label.top !== 'undefined' ? label.top : 1000,
696
- bottom: typeof label.bottom !== 'undefined' ? label.bottom : -1000,
697
- }).addTo(labelsGroup);
698
- }
699
- addLayer(labelsGroup, 'place-names', 'Landmarks');
700
- //labelsGroup.addTo(map);
701
- }
702
-
703
- // Add spawns
704
- if (mapData.spawns.length > 0) {
705
- const spawnLayers = {
706
- 'pmc': L.layerGroup(),
707
- 'scav': L.layerGroup(),
708
- 'sniper_scav': L.layerGroup(),
709
- 'boss': L.layerGroup(),
710
- 'cultist-priest': L.layerGroup(),
711
- 'rogue': L.layerGroup(),
712
- 'bloodhound': L.layerGroup(),
713
- }
714
- for (const spawn of mapData.spawns) {
715
- if (!positionIsInBounds(spawn.position)) {
716
- continue;
717
  }
718
- let spawnType = '';
719
- let bosses = [];
720
-
721
- if (spawn.categories.includes('boss')) {
722
- bosses = mapData.bosses.filter(boss => boss.spawnLocations.some(sl => sl.spawnKey === spawn.zoneName));
723
- if (bosses.length === 0) {
724
- if (spawn.categories.includes('bot') && spawn.sides.includes('scav')) {
725
- spawnType = 'scav';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  }
727
  else {
728
  console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
729
  continue;
730
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
  }
732
- else if (bosses.length === 1 && (bosses[0].normalizedName === 'bloodhound' || bosses[0].normalizedName === 'cultist-priest' || bosses[0].normalizedName === 'rogue')) {
733
- spawnType = bosses[0].normalizedName;
734
- }
735
- else {
736
- spawnType = 'boss';
737
- }
738
- } else if (spawn.categories.includes('sniper')) {
739
- spawnType = 'sniper_scav';
740
- } else if (spawn.sides.includes('scav')) {
741
- if (spawn.categories.includes('bot') || spawn.categories.includes('all')) {
742
- spawnType = 'scav';
743
- }
744
- else {
745
- console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
746
- continue;
747
  }
748
  }
749
- else if (spawn.categories.includes('player')) {
750
- if (spawn.sides.includes('pmc') || spawn.sides.includes('all')) {
751
- spawnType = 'pmc'
 
 
 
 
752
  }
753
- else {
754
- console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
755
- continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  }
757
- }
758
- else {
759
- console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
760
- continue;
761
- }
762
-
763
- const spawnIcon = L.icon({
764
- iconUrl: `/static/maps/interactive/spawn_${spawnType}.png`,
765
- iconSize: [24, 24],
766
- popupAnchor: [0, -12],
767
- });
768
-
769
- if (spawnType === 'pmc') {
770
- spawnIcon.iconAnchor = [12, 24];
771
- spawnIcon.popupAnchor = [0, -24];
772
- }
773
-
774
- const popupContent = L.DomUtil.create('div')
775
- if (spawn.categories.includes('boss') && bosses.length > 0) {
776
- bosses = bosses.reduce((unique, current) => {
777
- if (!unique.some(b => b.normalizedName === current.normalizedName)) {
778
- unique.push(current)
779
- if (!categories[`spawn_${current.normalizedName}`]) {
780
- categories[`spawn_${current.normalizedName}`] = current.name
781
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
782
  }
783
- return unique;
784
- }, []);
785
- const bossList = L.DomUtil.create('div', undefined, popupContent)
786
- for (const boss of bosses) {
787
- if (bossList.childNodes.length > 0) {
788
- const comma = L.DomUtil.create('span', undefined, bossList)
789
- comma.textContent = ', '
790
  }
791
- bossList.append(getALink(`https://escapefromtarkov.fandom.com/wiki/Special:Search?scope=internal&query=${boss.name}`, `${boss.name} (${Math.round(boss.spawnChance * 100)}%)`))
792
  }
793
  }
794
- else {
795
- const spawnDiv = L.DomUtil.create('div', undefined, popupContent)
796
- spawnDiv.textContent = categories[`spawn_${spawnType}`]
797
- }
798
- addElevation(spawn, popupContent)
799
-
800
- const marker = L.marker(pos(spawn.position), {
801
- icon: spawnIcon,
802
- position: spawn.position,
803
- });
804
- if (popupContent.childNodes.length > 0) {
805
- marker.bindPopup(L.popup().setContent(popupContent))
806
- }
807
- marker.position = spawn.position
808
- marker.on('add', checkMarkerForActiveLayers)
809
- marker.on('click', activateMarkerLayer)
810
- marker.addTo(spawnLayers[spawnType])
811
-
812
- checkMarkerBounds(spawn.position, markerBounds)
813
- }
814
- for (const key in spawnLayers) {
815
- if (Object.keys(spawnLayers[key]._layers).length > 0) {
816
- addLayer(spawnLayers[key], `spawn_${key}`, 'Spawns')
817
- }
818
- }
819
- }
820
-
821
- //add extracts
822
- if (mapData.extracts.length > 0) {
823
- const extractLayers = {
824
- pmc: L.layerGroup(),
825
- scav: L.layerGroup(),
826
- shared: L.layerGroup(),
827
- }
828
- const zIndexOffsets = {
829
- pmc: 150,
830
- shared: 125,
831
- scav: 100,
832
- };
833
- for (const extract of mapData.extracts) {
834
- const faction = extract.faction ?? 'shared';
835
- if (!positionIsInBounds(extract.position)) {
836
- //continue;
837
- }
838
- const colorMap = {
839
- scav: '#ff7800',
840
- pmc: '#00e599',
841
- shared: '#00e4e5',
842
  }
843
- const rect = L.polygon(outlineToPoly(extract.outline), { color: colorMap[faction], weight: 1, className: 'not-shown' });
844
- const extractIcon = L.divIcon({
845
- className: 'extract-icon',
846
- html: `<img src="/static/maps/interactive/extract_${faction}.png"/><span class="extract-name ${faction}">${extract.name}</span>`,
847
- iconAnchor: [12, 12]
848
- });
849
- const extractMarker = L.marker(pos(extract.position), {
850
- icon: extractIcon,
851
- title: extract.name,
852
- zIndexOffset: zIndexOffsets[faction],
853
- position: extract.position,
854
- top: extract.top,
855
- bottom: extract.bottom,
856
- outline: rect,
857
- id: extract.id,
 
 
 
 
 
 
 
 
 
858
  });
859
- extractMarker.on('mouseover', mouseHoverOutline);
860
- extractMarker.on('mouseout', mouseHoverOutline);
861
- extractMarker.on('click', toggleForceOutline);
862
- if (extract.switches?.length > 0) {
863
- const popup = L.DomUtil.create('div');
864
- const textElement = L.DomUtil.create('div');
865
- textElement.textContent = `${tMaps('Activated by')}:`;
866
- popup.appendChild(textElement);
867
- for (const sw of extract.switches) {
868
- const linkElement = getPoiLinkElement(sw.id, 'switch');
869
- const nameElement = L.DomUtil.create('span');
870
- nameElement.innerHTML = `<strong>${sw.name}</strong>`;
871
- linkElement.append(nameElement);
872
- popup.appendChild(linkElement);
873
  }
874
- addElevation(extract, popup);
875
- extractMarker.bindPopup(L.popup().setContent(popup));
876
- } else if (showElevation) {
877
- const popup = L.DomUtil.create('div');
878
- addElevation(extract, popup);
879
- extractMarker.bindPopup(L.popup().setContent(popup));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
880
  }
881
- extractMarker.on('add', checkMarkerForActiveLayers);
882
- L.layerGroup([rect, extractMarker]).addTo(extractLayers[faction]);
883
-
884
- checkMarkerBounds(extract.position, markerBounds);
885
- }
886
- if (mapData.transits.length > 0) {
887
- extractLayers.transit = L.layerGroup();
888
-
889
- for (const transit of mapData.transits) {
890
- if (!positionIsInBounds(transit.position)) {
891
- //continue;
 
 
 
 
 
 
 
 
 
892
  }
893
- const rect = L.polygon(outlineToPoly(transit.outline), { color: '#e53500', weight: 1, className: 'not-shown' });
894
- const transitIcon = L.divIcon({
895
- className: 'extract-icon',
896
- html: `<img src="/static/maps/interactive/extract_transit.png"/><span class="extract-name transit">${transit.description}</span>`,
897
- iconAnchor: [12, 12]
898
- });
899
- const transitMarker = L.marker(pos(transit.position), {
900
- icon: transitIcon,
901
- title: transit.description,
902
- zIndexOffset: zIndexOffsets.pmc,
903
- position: transit.position,
904
- top: transit.top,
905
- bottom: transit.bottom,
906
- outline: rect,
907
- id: transit.id,
 
 
 
 
 
 
 
 
 
 
 
908
  });
909
- transitMarker.on('mouseover', mouseHoverOutline);
910
- transitMarker.on('mouseout', mouseHoverOutline);
911
- transitMarker.on('click', toggleForceOutline);
912
- if (showElevation) {
913
- const popup = L.DomUtil.create('div');
914
- addElevation(transit, popup);
915
- transitMarker.bindPopup(L.popup().setContent(popup));
916
  }
917
- transitMarker.on('add', checkMarkerForActiveLayers);
918
- L.layerGroup([rect, transitMarker]).addTo(extractLayers.transit);
919
-
920
- checkMarkerBounds(transit.position, markerBounds);
921
- }
922
- }
923
- for (const key in extractLayers) {
924
- if (Object.keys(extractLayers[key]._layers).length > 0) {
925
- addLayer(extractLayers[key], `extract_${key}`, 'Extracts');
926
- }
927
- }
928
- }
929
-
930
- // Add static items
931
- if (showStaticMarkers) {
932
- for (const category in mapData) {
933
- const markerLayer = L.layerGroup();
934
-
935
- const items = mapData[category];
936
- for (const item of items) {
937
- const itemIcon = L.icon({
938
- iconUrl: `/static/maps/interactive/${category}.png`,
939
- iconSize: [24, 24],
940
- popupAnchor: [0, -12],
941
- //className: layerIncludesMarker(heightLayer, item) ? '' : 'off-level',
942
  });
943
- L.marker(pos(item.position), { icon: itemIcon, position: item.position })
944
- .bindPopup(L.popup().setContent(`${item.name}<br>Elevation: ${item.position.y}`))
945
- .addTo(markerLayer);
946
-
947
- checkMarkerBounds(item.position, markerBounds);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
948
  }
949
-
950
- if (items.length > 0) {
951
- var section;
952
- if (category.startsWith('extract')) {
953
- section = 'Extracts';
954
- }
955
- else if (category.startsWith('spawn')) {
956
- section = 'Spawns';
 
957
  }
958
- else {
959
- section = 'Lootable Items';
 
960
  }
961
- markerLayer.addTo(map);
962
- addLayer(markerLayer, category, section);
963
- // layerControl.addOverlay(markerLayer, `<img src='${process.env.PUBLIC_URL}/maps/interactive/${category}.png' class='control-item-image' /> ${categories[category] || category}`, section);
964
- }
965
- }
966
- }
967
-
968
- // Erstelle ein neues Control-Element
969
- const customControl = L.Control.extend({
970
- onAdd: function (map) {
971
- this.container = L.DomUtil.create('div', 'custom-control leaflet-control-layers leaflet-control-layers-expanded')
972
- this.container.innerHTML = '<h2>Add Marker to Pos: </h2>'
973
- return this.container
974
- },
975
-
976
- updateText: function (html) {
977
- if (this.container) {
978
- this.container.innerHTML = html
979
- }
980
- },
981
-
982
- clearText: function () {
983
- if (this.container) {
984
- this.container.innerHTML = ''
985
- }
986
- },
987
-
988
- onRemove: function (map) {
989
- // Nichts zu tun hier
990
- }
991
- });
992
- const myControl = new customControl({ position: 'topright' })
993
- map.addControl(myControl)
994
-
995
- function startCountdown(elementId) {
996
- const element = document.getElementById(elementId);
997
- if (!element) {
998
- console.error(`Element with id ${elementId} not found.`);
999
- return;
1000
- }
1001
-
1002
- //let secondsStart = Math.floor(new Date().getTime() / 1000.0) - parseInt(element.getAttribute('data-rade-end')) - 60
1003
- let secondsStart = parseInt(element.getAttribute('data-seconds')) - 60
1004
- let seconds = secondsStart
1005
- if (isNaN(secondsStart)) {
1006
- console.error('Invalid data-seconds attribute.');
1007
- return;
1008
- }
1009
-
1010
- const intervalId = setInterval(() => {
1011
- if (!document.hidden) {
1012
- //seconds = (secondsStart - parseInt(Math.floor(new Date().getTime() / 1000.0)))
1013
- //localStorage.setItem('rade_data', JSON.stringify({ remaining_time: seconds }));
1014
- seconds--
1015
-
1016
- if (seconds <= 0) {
1017
- clearInterval(intervalId);
1018
- element.textContent = '0:00:00';
1019
- myControl.updateText("")
1020
- document.querySelector("head > title").textContent = `${mapData.name} | Map with friends`
1021
- return;
1022
  }
1023
-
1024
- //seconds--;
1025
- const hours = Math.floor(seconds / 3600);
1026
- const minutes = Math.floor((seconds % 3600) / 60);
1027
- const remainingSeconds = seconds % 60;
1028
-
1029
- const timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
1030
- element.textContent = timeString;
1031
- document.querySelector("head > title").textContent = `${mapData.name} | ${timeString}`
1032
- }
1033
- }, 1000);
1034
- }
1035
-
1036
- function addNewRadeData(new_html) {
1037
- myControl.updateText(new_html)
1038
- startCountdown("rade_time_remain")
1039
- };
1040
-
1041
- //MARK: ADDMARKER
1042
- // Funktion zum Hinzufügen eines Markers
1043
- function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) {
1044
-
1045
- // Validate position
1046
- const position = {
1047
- x: parseFloat(x),
1048
- y: parseFloat(y),
1049
- z: parseFloat(z)
1050
- };
1051
-
1052
- if (!positionIsInBounds(position)) {
1053
- console.error("Position außerhalb der Karte:", position);
1054
- return;
1055
- }
1056
-
1057
- // SVG-Icon als String mit dynamischer Farbe
1058
- let markerColor;
1059
- if (markercolor) {
1060
- markerColor = '#' + markercolor;
1061
- } else {
1062
- markerColor = 'currentColor';
1063
- }
1064
- const svgString = `
1065
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg">
1066
- <path d="M60.8 50.5 38 72.9 15.2 50.5 8.1 3.9 38 13.3l29.8-9.4-7 46.6z" style="stroke-linecap:round;stroke-linejoin:round;fill:#fff;stroke:#fff;stroke-width:4.4px"/><path d="M58.8 49.4 38 69.9 17.1 49.4 10.7 6.9 38 15.5l27.2-8.6-6.4 42.5z" style="fill:${markerColor};stroke:#000;stroke-width:4px;stroke-linecap:round;stroke-linejoin:round"/>
1067
- </svg>
1068
- `;
1069
- const svgString2 = `
1070
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512' class="marker-svg">
1071
- <path fill='${markerColor}' d='M172.3 501.7C27 291 0 269.4 0 192 0 86 86 0 192 0s192 86 192 192c0 77.4-27 99-172.3 309.7-9.5 13.8-29.9 13.8-39.5 0zM192 272c44.2 0 80-35.8 80-80s-35.8-80-80-80-80 35.8-80 80 35.8 80 80 80z'/>
1072
- </svg>
1073
- `;
1074
-
1075
- // DivIcon mit dynamischem SVG erstellen
1076
- const markerIcon = L.divIcon({
1077
- html: svgString,
1078
- className: 'custom-marker',
1079
- iconSize: [32, 32],
1080
- iconAnchor: [16, 32],
1081
- popupAnchor: [0, -32]
1082
- });
1083
-
1084
- const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-")
1085
-
1086
- // Remove old marker for this player, if it exists
1087
- if (playerMarkers[playerMarkerName]) {
1088
- map.removeLayer(playerMarkers[playerMarkerName]);
1089
- }
1090
-
1091
- // Create new marker
1092
- playerMarkers[playerMarkerName] = L.marker(pos(position), {
1093
- icon: markerIcon,
1094
- position: position,
1095
- title: `Koordinaten: ${x}, ${y}, ${z}`,
1096
- riseOnHover: true,
1097
- zIndexOffset: 400
1098
- });
1099
-
1100
- // Popup mit Informationen erstellen
1101
- const popupContent = `
1102
- <div class="marker-popup">
1103
- ${playername ? `<strong>Player: ${playername}</strong>` : ''}
1104
- <strong>Koordinaten:</strong>
1105
- <span>X: ${x} Y: ${y} Z: ${z}</span>
1106
- ${preview ? `<img class="preview-image" src="${preview}"><br>` : ''}
1107
- <small>${timestamp}</small>
1108
- </div>
1109
- `;
1110
- playerMarkers[playerMarkerName].bindPopup(popupContent, {
1111
- maxWidth: 250,
1112
- minWidth: 150,
1113
- autoClose: true,
1114
- closeOnClick: true
1115
- });
1116
-
1117
- // Marker zur Karte hinzufügen
1118
- playerMarkers[playerMarkerName].addTo(map);
1119
-
1120
- // Karte auf Marker zentrieren
1121
- map.setView(pos(position), map.getZoom(), {
1122
- animate: true,
1123
- duration: 0.5
1124
- });
1125
-
1126
- console.log("Neuer Marker gesetzt für " + playername + ": " + position);
1127
- }
1128
-
1129
- document.onload = loadLocalData()
1130
-
1131
-
1132
- // MODAL Trigger
1133
- document.addEventListener('DOMContentLoaded', () => {
1134
- // Functions to open and close a modal
1135
- function openModal($el) {
1136
- $el.classList.add('is-active');
1137
- }
1138
-
1139
- function closeModal($el) {
1140
- $el.classList.remove('is-active');
1141
- }
1142
-
1143
- function closeAllModals() {
1144
- (document.querySelectorAll('.modal') || []).forEach(($modal) => {
1145
- closeModal($modal);
1146
- });
1147
- }
1148
-
1149
- // Add a click event on buttons to open a specific modal
1150
- (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
1151
- const modal = $trigger.dataset.target;
1152
- const $target = document.getElementById(modal);
1153
-
1154
- $trigger.addEventListener('click', () => {
1155
- openModal($target);
1156
- });
1157
- });
1158
-
1159
- // Add a click event on various child elements to close the parent modal
1160
- (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
1161
- const $target = $close.closest('.modal');
1162
-
1163
- $close.addEventListener('click', () => {
1164
- closeModal($target);
1165
  });
1166
- });
1167
-
1168
- // Add a keyboard event to close all modals
1169
- document.addEventListener('keydown', (event) => {
1170
- if (event.key === "Escape") {
1171
- closeAllModals();
1172
- }
1173
- });
1174
- });
1175
  </script>
1176
  </body>
1177
 
 
128
  const normalizedName = pathSegments[pathSegments.length - 1];
129
 
130
  fetch(`/api/map/${normalizedName}`)
131
+ .then(response => response.json())
132
+ .then(mapData => {
133
+ console.log("Map Data:", mapData);
134
+
135
+ // Jetzt alle Variablen initialisieren
136
+ const transform = mapData.transform;
137
+ const bounds = mapData.bounds;
138
+ const svgBounds = mapData.svgBounds ? mapData.svgBounds : bounds;
139
+ const coordinateRotation = mapData.coordinateRotation ? mapData.coordinateRotation : 0;
140
+ const svgPath = mapData.svgPath;
141
+ const imageUrl = mapData.svgPath;
142
+ const minZoom = mapData.minZoom ?? 1;
143
+ const maxZoom = mapData.maxZoom ?? 6;
144
+ const showElevation = false;
145
+ const showStaticMarkers = false;
146
+ const wsgroup = "1234";
147
+ let currentZoom = 3;
148
+
149
+ // Hier kommt der restliche Code, der mapData benötigt
150
+ initMap(mapData);
151
+ })
152
+ .catch(error => console.error("Fehler beim Laden der Map:", error));
153
+
154
+ function initMap(mapData) {
155
+
156
+ //MARK: INIT WEBSOCKET
157
+ // Initialize WebSocket connection
158
+ const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`);
159
+
160
+ // WebSocket event handlers
161
+ ws.onmessage = function (event) {
162
+ console.log(event.data);
163
+ const data = JSON.parse(event.data);
164
+
165
+ switch (data.type) {
166
+ case "coordinates":
167
+ const parsedData = data.data;
168
+ localStorage.setItem("last_marker", JSON.stringify(parsedData));
169
+ loadLocalData();
170
+ break;
171
+
172
+ case "location_map":
173
+ const map_name = data.data.map.toLowerCase().replaceAll(" ", "-");
174
+ localStorage.setItem("last_map_name", map_name);
175
+ localStorage.removeItem("last_marker");
176
+ removeAllMarkers(); // Clear all markers when the map changes
177
+ if (!location.pathname.includes(map_name)) {
178
+ location.pathname = `/map/${map_name}`;
179
+ } else {
180
+ location.reload();
181
+ }
182
+ break;
183
+
184
+ case "new_rade_data":
185
+ console.log(data.data);
186
+ const radeData = JSON.parse(data.data);
187
+ localStorage.setItem("rade_data", JSON.stringify(radeData));
188
+ loadLocalData();
189
+ break;
190
+ }
191
+ };
192
+
193
+ ws.onopen = function (event) {
194
+ ws.send(JSON.stringify({ type: "join", group: wsgroup }));
195
+ };
196
+
197
+ // Function to remove all markers
198
+ function removeAllMarkers() {
199
+ for (const playername in playerMarkers) {
200
+ if (playerMarkers.hasOwnProperty(playername)) {
201
+ map.removeLayer(playerMarkers[playername]);
202
+ }
203
+ }
204
+ Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]);
205
+ console.log("Alle Marker wurden entfernt.");
206
+ }
207
+
208
+ // Function to load and display local marker data
209
+ function loadLocalData() {
210
+ const map_name = localStorage.getItem("last_map_name");
211
+ if (map_name) {
212
+ if (!location.pathname.includes(map_name)) {
213
+ localStorage.removeItem("last_marker");
214
+ }
215
  } else {
216
+ localStorage.removeItem("last_marker");
217
+ }
218
+
219
+ const markerData = localStorage.getItem("last_marker");
220
+ if (markerData) {
221
+ const parsedData = JSON.parse(markerData);
222
+ // Use the new addMarker function with all required parameters
223
+ addMarker(
224
+ parsedData.x,
225
+ parsedData.y,
226
+ parsedData.z,
227
+ parsedData.timestamp,
228
+ parsedData.preview || false,
229
+ parsedData.actualmap || "Unbekannte Map",
230
+ parsedData.playername || "Unnamed Player",
231
+ parsedData.markercolor || false
232
+ );
233
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  }
235
+
236
+ const images = {
237
+ 'container_bank-cash-register': 'container_cash-register',
238
+ 'container_bank-safe': 'container_safe',
239
+ 'container_buried-barrel-cache': 'container_buried-barrel-cache',
240
+ 'container_cash-register': 'container_cash-register',
241
+ 'container_cash-register-tar2-2': 'container_cash-register',
242
+ 'container_dead-civilian': 'container_dead-scav',
243
+ 'container_dead-scav': 'container_dead-scav',
244
+ 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate',
245
+ 'container_pmc-body': 'container_dead-scav',
246
+ 'container_civilian-body': 'container_dead-scav',
247
+ 'container_drawer': 'container_drawer',
248
+ 'container_duffle-bag': 'container_duffle-bag',
249
+ 'container_grenade-box': 'container_grenade-box',
250
+ 'container_ground-cache': 'container_ground-cache',
251
+ 'container_jacket': 'container_jacket',
252
+ 'container_lab-technician-body': 'container_dead-scav',
253
+ 'container_medbag-smu06': 'container_medbag-smu06',
254
+ 'container_medcase': 'container_medcase',
255
+ 'container_medical-supply-crate': 'container_crate',
256
+ 'container_pc-block': 'container_pc-block',
257
+ 'container_plastic-suitcase': 'container_plastic-suitcase',
258
+ 'container_ration-supply-crate': 'container_crate',
259
+ 'container_safe': 'container_safe',
260
+ 'container_scav-body': 'container_dead-scav',
261
+ 'container_shturmans-stash': 'container_weapon-box',
262
+ 'container_technical-supply-crate': 'container_crate',
263
+ 'container_toolbox': 'container_toolbox',
264
+ 'container_weapon-box': 'container_weapon-box',
265
+ 'container_wooden-ammo-box': 'container_wooden-ammo-box',
266
+ 'container_wooden-crate': 'container_wooden-crate',
267
+ 'extract_pmc': 'extract_pmc',
268
+ 'extract_scav': 'extract_scav',
269
+ 'extract_shared': 'extract_shared',
270
+ 'extract_transit': 'extract_transit',
271
+ 'hazard': 'hazard',
272
+ 'hazard_mortar': 'hazard_mortar',
273
+ 'hazard_minefield': 'hazard',
274
+ 'hazard_sniper': 'hazard',
275
+ 'key': 'key',
276
+ 'lock': 'lock',
277
+ 'loose_loot': 'loose_loot',
278
+ 'quest_item': 'quest_item',
279
+ 'quest_objective': 'quest_objective',
280
+ 'spawn_sniper_scav': 'spawn_sniper_scav',
281
+ 'spawn_bloodhound': 'spawn_bloodhound',
282
+ 'spawn_boss': 'spawn_boss',
283
+ 'spawn_cultist-priest': 'spawn_cultist-priest',
284
+ 'spawn_pmc': 'spawn_pmc',
285
+ 'spawn_rogue': 'spawn_rogue',
286
+ 'spawn_scav': 'spawn_scav',
287
+ 'stationarygun': 'stationarygun',
288
+ 'switch': 'switch',
289
+ };
290
+
291
+ const categories = {
292
+ 'extract_pmc': 'PMC',
293
+ 'extract_shared': 'Shared',
294
+ 'extract_scav': 'Scav',
295
+ 'extract_transit': 'Transit',
296
+ 'spawn_sniper_scav': 'Sniper Scav',
297
+ 'spawn_pmc': 'PMC',
298
+ 'spawn_scav': 'Scav',
299
+ 'spawn_boss': 'Boss',
300
+ 'quest_item': 'Item',
301
+ 'quest_objective': 'Objective',
302
+ 'lock': 'Locks',
303
+ 'lever': 'Lever',
304
+ 'stationarygun': 'Stationary Gun',
305
+ 'switch': 'Switch',
306
+ 'place-names': 'Place Names',
307
+ };
308
+
309
+ function getCRS(transform) {
310
+ let scaleX = 1;
311
+ let scaleY = 1;
312
+ let marginX = 0;
313
+ let marginY = 0;
314
+ if (transform) {
315
+ scaleX = transform[0];
316
+ scaleY = transform[2] * -1;
317
+ marginX = transform[1];
318
+ marginY = transform[3];
319
+ }
320
+ return L.extend({}, L.CRS.Simple, {
321
+ transformation: new L.Transformation(scaleX, marginX, scaleY, marginY),
322
+ projection: L.extend({}, L.Projection.LonLat, {
323
+ project: latLng => {
324
+ return L.Projection.LonLat.project(applyRotation(latLng, coordinateRotation));
325
+ },
326
+ unproject: point => {
327
+ return applyRotation(L.Projection.LonLat.unproject(point), coordinateRotation * -1);
328
+ },
329
+ }),
330
+ });
331
  }
332
+
333
+ function applyRotation(latLng, rotation) {
334
+ if (!latLng.lng && !latLng.lat) {
335
+ return L.latLng(0, 0);
336
+ }
337
+ if (!rotation) {
338
+ return latLng;
339
+ }
340
+
341
+ const angleInRadians = (rotation * Math.PI) / 180;
342
+ const cosAngle = Math.cos(angleInRadians);
343
+ const sinAngle = Math.sin(angleInRadians);
344
+
345
+ const { lng: x, lat: y } = latLng;
346
+ const rotatedX = x * cosAngle - y * sinAngle;
347
+ const rotatedY = x * sinAngle + y * cosAngle;
348
+ return L.latLng(rotatedY, rotatedX);
349
+ }
350
+
351
+ function pos(position) {
352
+ return [position.z, position.x];
353
+ }
354
+
355
+ function addElevation(item, popup) {
356
+ if (!showElevation) {
357
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  }
359
+ const elevationContent = L.DomUtil.create('div', undefined, popup);
360
+ elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`;
361
+ if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) {
362
+ const heightContent = L.DomUtil.create('div', undefined, popup);
363
+ heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`;
364
+ }
365
+ }
366
+
367
+ function markerIsOnLayer(marker, layer) {
368
+ if (!layer) {
369
+ return true;
370
+ }
371
+ var top = marker.options.top || marker.options.position.y;
372
+ var bottom = marker.options.bottom || marker.options.position.y;
373
+ for (const extent of layer.options.extents) {
374
+ if (top >= extent.height[0] && bottom < extent.height[1]) {
375
+ let containedType = 'partial';
376
+ if (bottom >= extent.height[0] && top <= extent.height[1]) {
377
+ containedType = 'full';
378
+ }
379
+ if (extent.bounds) {
380
+ for (const boundsArray of extent.bounds) {
381
+ const bounds = getBounds(boundsArray);
382
+ if (bounds.contains(pos(marker.options.position))) {
383
+ return containedType;
384
+ }
385
+ }
386
+ } else {
387
  return containedType;
388
  }
389
  }
390
+ }
391
+ return false;
392
+ }
393
+
394
+ function markerIsOnActiveLayer(marker) {
395
+ if (!marker.options.position) {
396
+ return true;
397
+ }
398
+
399
+ const map = marker._map;
400
+
401
+ // check if marker is completely contained by inactive layer
402
+ const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay);
403
+ for (const layer of overlays) {
404
+ for (const extent of layer.options.extents) {
405
+ if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) {
406
+ return false;
407
+ }
408
+ }
409
+ }
410
+
411
+ // check if marker is on active overlay
412
+ const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay);
413
+ if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) {
414
+ return true;
415
+ }
416
+
417
+ // check if marker is on base layer
418
+ const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !L.options?.overlay);
419
+ if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) {
420
+ return true;
421
+ }
422
+
423
+ return false;
424
+ }
425
+
426
+ function checkMarkerForActiveLayers(event) {
427
+ const marker = event.target || event;
428
+ const outline = marker.options.outline;
429
+ const onLevel = markerIsOnActiveLayer(marker);
430
+ if (onLevel) {
431
+ marker._icon?.classList.remove('off-level');
432
+ if (outline) {
433
+ outline._path?.classList.remove('off-level');
434
+ }
435
  } else {
436
+ marker._icon?.classList.add('off-level');
437
+ if (outline) {
438
+ outline._path?.classList.add('off-level');
439
+ }
440
  }
441
  }
442
+
443
+ function activateMarkerLayer(event) {
444
+ const marker = event.target || event;
445
+ if (markerIsOnActiveLayer(marker)) {
446
+ return;
447
+ }
448
+ const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
449
+ for (const layer of activeLayers) {
450
+ layer.removeFrom(marker._map);
451
+ }
452
+ const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
453
+ for (const layer of heightLayers) {
454
+ if (markerIsOnLayer(marker, layer)) {
455
+ layer.addTo(marker._map);
456
+ break;
457
+ }
 
458
  }
459
  }
460
+
461
+ const getALink = (path, contents) => {
462
+ const a = L.DomUtil.create('a');
463
+ a.setAttribute('href', path);
464
+ a.setAttribute('target', '_blank');
465
+ a.append(contents);
466
+ // a.addEventListener('click', (event) => {
467
+ // navigate(path);
468
+ // event.preventDefault();
469
+ // });
470
+ return a;
471
+ };
472
+
473
+ function getScaledBounds(bounds, scaleFactor) {
474
+ // Calculate the center point of the bounds
475
+ const centerX = (bounds[0][0] + bounds[1][0]) / 2;
476
+ const centerY = (bounds[0][1] + bounds[1][1]) / 2;
477
+
478
+ // Calculate the new width and height
479
+ const width = bounds[1][0] - bounds[0][0];
480
+ const height = bounds[1][1] - bounds[0][1];
481
+ const newWidth = width * scaleFactor;
482
+ const newHeight = height * scaleFactor;
483
+
484
+ // Update the coordinates of the two points defining the bounds
485
+ const newBounds = [
486
+ [centerY - newHeight / 2, centerX - newWidth / 2],
487
+ [centerY + newHeight / 2, centerX + newWidth / 2]
488
+ ];
489
+
490
+ // console.log("Initial Rectangle:", bounds);
491
+ // console.log("Scaled Rectangle:", newBounds);
492
+ // console.log("Center:", L.bounds(bounds).getCenter(true));
493
+
494
+ return newBounds;
495
  }
496
+
497
+ // Erstelle die Karte
498
+ //const map = L.map('map').setView([0, 0], 2);
499
+
500
+ const map = L.map('map', {
501
+ maxBounds: getScaledBounds(svgBounds, 1.5),
502
+ //maxBounds: bounds,
503
+ center: [0, 0],
504
+ zoom: 2,
505
+ minZoom: minZoom,
506
+ maxZoom: maxZoom,
507
+ zoomSnap: 0.1,
508
+ scrollWheelZoom: true,
509
+ wheelPxPerZoomLevel: 120,
510
+ crs: getCRS(transform),
511
+ attributionControl: false,
512
+ id: "wwoodsMap",
513
+ });
514
+
515
+ const layerControl = L.control.groupedLayers(null, null, {
516
+ position: 'topleft',
517
+ collapsed: true,
518
+ groupCheckboxes: true,
519
+ groupsCollapsable: true,
520
+ exclusiveOptionalGroups: ['Levels'],
521
+ }).addTo(map);
522
+ layerControl.on('overlayToggle', (e) => {
523
+ const layerState = e.detail;
524
+ if (layerState.checked) {
525
+ mapViewRef.current.layer = layerState.key;
526
+ } else {
527
+ mapViewRef.current.layer = undefined;
528
+ }
529
+ });
530
+ layerControl.on('layerToggle', (e) => {
531
+ const layerState = e.detail;
532
+ if (!layerState.checked) {
533
+ mapSettingsRef.current.hiddenLayers.push(layerState.key);
534
+ } else {
535
+ mapViewRef.current.layer = layerState.key;
536
+ mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== layerState.key);
537
+ }
538
+ updateSavedMapSettings();
539
+ });
540
+ layerControl.on('groupToggle', (e) => {
541
+ const groupState = e.detail;
542
+ for (const groupLayer of layerControl._layers) {
543
+ if (groupLayer.group?.key !== groupState.key) {
544
+ continue;
545
+ }
546
+ if (!groupState.checked) {
547
+ mapSettingsRef.current.hiddenLayers.push(groupLayer.key);
548
+ } else {
549
+ mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== groupLayer.key);
550
+ }
551
+ }
552
+ if (!groupState.checked) {
553
+ mapSettingsRef.current.hiddenGroups.push(groupState.key);
554
+ } else {
555
+ mapSettingsRef.current.hiddenGroups = mapSettingsRef.current.hiddenGroups.filter(key => key !== groupState.key);
556
+ }
557
+ updateSavedMapSettings();
558
+ });
559
+ layerControl.on('groupCollapseToggle', (e) => {
560
+ const groupState = e.detail;
561
+ if (groupState.collapsed) {
562
+ mapSettingsRef.current.collapsedGroups.push(groupState.key);
563
+
564
+ } else {
565
+ mapSettingsRef.current.collapsedGroups = mapSettingsRef.current.collapsedGroups.filter(key => key !== groupState.key);
566
+ }
567
+ updateSavedMapSettings();
568
+ });
569
+
570
+ const getLayerOptions = (layerKey, groupKey, layerName) => {
571
+ return {
572
+ groupKey,
573
+ layerKey,
574
+ groupName: groupKey,
575
+ layerName: layerName || categories[layerKey] || layerKey,
576
+ //groupHidden: Boolean(mapSettingsRef.current.hiddenGroups?.includes(groupKey)),
577
+ //layerHidden: Boolean(mapSettingsRef.current.hiddenLayers?.includes(layerKey)),
578
+ image: images[layerKey] ? "/static/maps/interactive/${images[layerKey]}.png" : undefined,
579
+ //groupCollapsed: Boolean(mapSettingsRef.current.collapsedGroups?.includes(groupKey)),
580
+ };
581
+ };
582
+
583
+ const addLayer = (layer, layerKey, groupKey, layerName) => {
584
+ layer.key = layerKey;
585
+ const layerOptions = getLayerOptions(layerKey, groupKey, layerName);
586
+ if (!layerOptions.layerHidden) {
587
+ layer.addTo(map);
588
+ }
589
+ layerControl.addOverlay(layer, layerOptions.layerName, layerOptions);
590
+ };
591
+ map.layerControl = layerControl;
592
+
593
+ // Hinzufügen des Image-Overlays
594
+ const overlay = L.imageOverlay(imageUrl, getBounds(svgBounds));
595
+ overlay.addTo(map);
596
+
597
+ function checkMarkerBounds(position, markerBounds) {
598
+ if (position.x < markerBounds.TL.x) markerBounds.TL.x = position.x;
599
+ if (position.z > markerBounds.TL.z) markerBounds.TL.z = position.z;
600
+ if (position.x > markerBounds.BR.x) markerBounds.BR.x = position.x;
601
+ if (position.z < markerBounds.BR.z) markerBounds.BR.z = position.z;
602
  }
603
+
604
+ function getBounds(bounds) {
605
+ if (!bounds) {
606
+ return undefined;
607
+ }
608
+ return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]);
609
+ //return [[bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]];
 
 
 
 
 
 
 
 
 
 
610
  }
611
+
612
+ function mouseHoverOutline(event) {
613
+ const outline = event.target.options.outline;
614
+ if (event.originalEvent.type === 'mouseover') {
615
+ outline._path.classList.remove('not-shown');
616
+ } else if (!outline._path.classList.contains('force-show')) {
617
+ outline._path.classList.add('not-shown');
618
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  }
620
+
621
+ function toggleForceOutline(event) {
622
+ const outline = event.target.options.outline;
623
+ outline._path.classList.toggle('force-show');
624
+ if (outline._path.classList.contains('force-show')) {
625
+ outline._path.classList.remove('not-shown');
626
+ }
627
+ activateMarkerLayer(event);
628
  }
629
+
630
+ function activateMarkerLayer(event) {
631
+ const marker = event.target || event;
632
+ if (markerIsOnActiveLayer(marker)) {
633
+ return;
634
+ }
635
+ const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
636
+ for (const layer of activeLayers) {
637
+ layer.removeFrom(marker._map);
638
+ }
639
+ const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
640
+ for (const layer of heightLayers) {
641
+ if (markerIsOnLayer(marker, layer)) {
642
+ layer.addTo(marker._map);
643
+ break;
644
+ }
645
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  }
647
+
648
+ function outlineToPoly(outline) {
649
+ if (!outline) return [];
650
+ return outline.map(vector => [vector.z, vector.x]);
 
 
 
 
 
 
 
 
 
 
 
651
  }
652
+
653
+ const layerOptions = {
654
+ maxZoom: maxZoom,
655
+ maxNativeZoom: maxZoom,
656
+ extents: [
657
+ {
658
+ height: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
659
+ bounds: [bounds],
660
+ }
661
+ ],
662
+ type: 'map-layer',
663
+ };
664
+ let tileLayer = false;
665
+ const baseLayers = [];
666
+ const tileSize = 256;
667
+ let svgLayer = false;
668
+ if (svgPath) {
669
+ const svgBounds2 = svgBounds ? getBounds(svgBounds) : bounds;
670
+ svgLayer = L.imageOverlay(svgPath, svgBounds2, layerOptions);
671
+ baseLayers.push(svgLayer);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  }
673
+
674
+ const positionIsInBounds = (position) => {
675
+ return getBounds(bounds).contains(pos(position));
676
+ };
677
+
678
+ let markerBounds = {
679
+ 'TL': { x: Number.MAX_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER },
680
+ 'BR': { x: Number.MIN_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER }
681
+ }
682
+
683
+ if (mapData.labels?.length > 0) {
684
+ const labelsGroup = L.layerGroup();
685
+ const defaultHeight = ((layerOptions.extents[0].height[1] - layerOptions.extents[0].height[0]) / 2) + layerOptions.extents[0].height[0];
686
+ for (const label of mapData.labels) {
687
+ const fontSize = label.size ? label.size : 100;
688
+ const height = label.position.length < 3 ? defaultHeight : label.position[2];
689
+ const rotation = label.rotation ? label.rotation : 0;
690
+ L.marker(pos({ x: label.position[0], z: label.position[1] }), {
691
+ icon: L.divIcon({ html: `<div class="label" style="font-size: ${fontSize}%; transform: translate3d(-50%, -50%, 0) rotate(${rotation}deg)">${label.text}</div>`, className: 'map-area-label', layers: baseLayers, }),
692
+ interactive: false,
693
+ zIndexOffset: -100000,
694
+ position: {
695
+ x: label.position[0],
696
+ y: height,
697
+ z: label.position[1],
698
+ },
699
+ top: typeof label.top !== 'undefined' ? label.top : 1000,
700
+ bottom: typeof label.bottom !== 'undefined' ? label.bottom : -1000,
701
+ }).addTo(labelsGroup);
702
+ }
703
+ addLayer(labelsGroup, 'place-names', 'Landmarks');
704
+ //labelsGroup.addTo(map);
705
+ }
706
+
707
+ // Add spawns
708
+ if (mapData.spawns.length > 0) {
709
+ const spawnLayers = {
710
+ 'pmc': L.layerGroup(),
711
+ 'scav': L.layerGroup(),
712
+ 'sniper_scav': L.layerGroup(),
713
+ 'boss': L.layerGroup(),
714
+ 'cultist-priest': L.layerGroup(),
715
+ 'rogue': L.layerGroup(),
716
+ 'bloodhound': L.layerGroup(),
717
+ }
718
+ for (const spawn of mapData.spawns) {
719
+ if (!positionIsInBounds(spawn.position)) {
720
+ continue;
721
+ }
722
+ let spawnType = '';
723
+ let bosses = [];
724
+
725
+ if (spawn.categories.includes('boss')) {
726
+ bosses = mapData.bosses.filter(boss => boss.spawnLocations.some(sl => sl.spawnKey === spawn.zoneName));
727
+ if (bosses.length === 0) {
728
+ if (spawn.categories.includes('bot') && spawn.sides.includes('scav')) {
729
+ spawnType = 'scav';
730
+ }
731
+ else {
732
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
733
+ continue;
734
+ }
735
+ }
736
+ else if (bosses.length === 1 && (bosses[0].normalizedName === 'bloodhound' || bosses[0].normalizedName === 'cultist-priest' || bosses[0].normalizedName === 'rogue')) {
737
+ spawnType = bosses[0].normalizedName;
738
+ }
739
+ else {
740
+ spawnType = 'boss';
741
+ }
742
+ } else if (spawn.categories.includes('sniper')) {
743
+ spawnType = 'sniper_scav';
744
+ } else if (spawn.sides.includes('scav')) {
745
+ if (spawn.categories.includes('bot') || spawn.categories.includes('all')) {
746
+ spawnType = 'scav';
747
+ }
748
+ else {
749
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
750
+ continue;
751
+ }
752
+ }
753
+ else if (spawn.categories.includes('player')) {
754
+ if (spawn.sides.includes('pmc') || spawn.sides.includes('all')) {
755
+ spawnType = 'pmc'
756
+ }
757
+ else {
758
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
759
+ continue;
760
+ }
761
  }
762
  else {
763
  console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
764
  continue;
765
  }
766
+
767
+ const spawnIcon = L.icon({
768
+ iconUrl: `/static/maps/interactive/spawn_${spawnType}.png`,
769
+ iconSize: [24, 24],
770
+ popupAnchor: [0, -12],
771
+ });
772
+
773
+ if (spawnType === 'pmc') {
774
+ spawnIcon.iconAnchor = [12, 24];
775
+ spawnIcon.popupAnchor = [0, -24];
776
+ }
777
+
778
+ const popupContent = L.DomUtil.create('div')
779
+ if (spawn.categories.includes('boss') && bosses.length > 0) {
780
+ bosses = bosses.reduce((unique, current) => {
781
+ if (!unique.some(b => b.normalizedName === current.normalizedName)) {
782
+ unique.push(current)
783
+ if (!categories[`spawn_${current.normalizedName}`]) {
784
+ categories[`spawn_${current.normalizedName}`] = current.name
785
+ }
786
+ }
787
+ return unique;
788
+ }, []);
789
+ const bossList = L.DomUtil.create('div', undefined, popupContent)
790
+ for (const boss of bosses) {
791
+ if (bossList.childNodes.length > 0) {
792
+ const comma = L.DomUtil.create('span', undefined, bossList)
793
+ comma.textContent = ', '
794
+ }
795
+ bossList.append(getALink(`https://escapefromtarkov.fandom.com/wiki/Special:Search?scope=internal&query=${boss.name}`, `${boss.name} (${Math.round(boss.spawnChance * 100)}%)`))
796
+ }
797
+ }
798
+ else {
799
+ const spawnDiv = L.DomUtil.create('div', undefined, popupContent)
800
+ spawnDiv.textContent = categories[`spawn_${spawnType}`]
801
+ }
802
+ addElevation(spawn, popupContent)
803
+
804
+ const marker = L.marker(pos(spawn.position), {
805
+ icon: spawnIcon,
806
+ position: spawn.position,
807
+ });
808
+ if (popupContent.childNodes.length > 0) {
809
+ marker.bindPopup(L.popup().setContent(popupContent))
810
+ }
811
+ marker.position = spawn.position
812
+ marker.on('add', checkMarkerForActiveLayers)
813
+ marker.on('click', activateMarkerLayer)
814
+ marker.addTo(spawnLayers[spawnType])
815
+
816
+ checkMarkerBounds(spawn.position, markerBounds)
817
  }
818
+ for (const key in spawnLayers) {
819
+ if (Object.keys(spawnLayers[key]._layers).length > 0) {
820
+ addLayer(spawnLayers[key], `spawn_${key}`, 'Spawns')
821
+ }
 
 
 
 
 
 
 
 
 
 
 
822
  }
823
  }
824
+
825
+ //add extracts
826
+ if (mapData.extracts.length > 0) {
827
+ const extractLayers = {
828
+ pmc: L.layerGroup(),
829
+ scav: L.layerGroup(),
830
+ shared: L.layerGroup(),
831
  }
832
+ const zIndexOffsets = {
833
+ pmc: 150,
834
+ shared: 125,
835
+ scav: 100,
836
+ };
837
+ for (const extract of mapData.extracts) {
838
+ const faction = extract.faction ?? 'shared';
839
+ if (!positionIsInBounds(extract.position)) {
840
+ //continue;
841
+ }
842
+ const colorMap = {
843
+ scav: '#ff7800',
844
+ pmc: '#00e599',
845
+ shared: '#00e4e5',
846
+ }
847
+ const rect = L.polygon(outlineToPoly(extract.outline), { color: colorMap[faction], weight: 1, className: 'not-shown' });
848
+ const extractIcon = L.divIcon({
849
+ className: 'extract-icon',
850
+ html: `<img src="/static/maps/interactive/extract_${faction}.png"/><span class="extract-name ${faction}">${extract.name}</span>`,
851
+ iconAnchor: [12, 12]
852
+ });
853
+ const extractMarker = L.marker(pos(extract.position), {
854
+ icon: extractIcon,
855
+ title: extract.name,
856
+ zIndexOffset: zIndexOffsets[faction],
857
+ position: extract.position,
858
+ top: extract.top,
859
+ bottom: extract.bottom,
860
+ outline: rect,
861
+ id: extract.id,
862
+ });
863
+ extractMarker.on('mouseover', mouseHoverOutline);
864
+ extractMarker.on('mouseout', mouseHoverOutline);
865
+ extractMarker.on('click', toggleForceOutline);
866
+ if (extract.switches?.length > 0) {
867
+ const popup = L.DomUtil.create('div');
868
+ const textElement = L.DomUtil.create('div');
869
+ textElement.textContent = `${tMaps('Activated by')}:`;
870
+ popup.appendChild(textElement);
871
+ for (const sw of extract.switches) {
872
+ const linkElement = getPoiLinkElement(sw.id, 'switch');
873
+ const nameElement = L.DomUtil.create('span');
874
+ nameElement.innerHTML = `<strong>${sw.name}</strong>`;
875
+ linkElement.append(nameElement);
876
+ popup.appendChild(linkElement);
877
+ }
878
+ addElevation(extract, popup);
879
+ extractMarker.bindPopup(L.popup().setContent(popup));
880
+ } else if (showElevation) {
881
+ const popup = L.DomUtil.create('div');
882
+ addElevation(extract, popup);
883
+ extractMarker.bindPopup(L.popup().setContent(popup));
884
+ }
885
+ extractMarker.on('add', checkMarkerForActiveLayers);
886
+ L.layerGroup([rect, extractMarker]).addTo(extractLayers[faction]);
887
+
888
+ checkMarkerBounds(extract.position, markerBounds);
889
  }
890
+ if (mapData.transits.length > 0) {
891
+ extractLayers.transit = L.layerGroup();
892
+
893
+ for (const transit of mapData.transits) {
894
+ if (!positionIsInBounds(transit.position)) {
895
+ //continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896
  }
897
+ const rect = L.polygon(outlineToPoly(transit.outline), { color: '#e53500', weight: 1, className: 'not-shown' });
898
+ const transitIcon = L.divIcon({
899
+ className: 'extract-icon',
900
+ html: `<img src="/static/maps/interactive/extract_transit.png"/><span class="extract-name transit">${transit.description}</span>`,
901
+ iconAnchor: [12, 12]
902
+ });
903
+ const transitMarker = L.marker(pos(transit.position), {
904
+ icon: transitIcon,
905
+ title: transit.description,
906
+ zIndexOffset: zIndexOffsets.pmc,
907
+ position: transit.position,
908
+ top: transit.top,
909
+ bottom: transit.bottom,
910
+ outline: rect,
911
+ id: transit.id,
912
+ });
913
+ transitMarker.on('mouseover', mouseHoverOutline);
914
+ transitMarker.on('mouseout', mouseHoverOutline);
915
+ transitMarker.on('click', toggleForceOutline);
916
+ if (showElevation) {
917
+ const popup = L.DomUtil.create('div');
918
+ addElevation(transit, popup);
919
+ transitMarker.bindPopup(L.popup().setContent(popup));
920
+ }
921
+ transitMarker.on('add', checkMarkerForActiveLayers);
922
+ L.layerGroup([rect, transitMarker]).addTo(extractLayers.transit);
923
+
924
+ checkMarkerBounds(transit.position, markerBounds);
925
  }
926
+ }
927
+ for (const key in extractLayers) {
928
+ if (Object.keys(extractLayers[key]._layers).length > 0) {
929
+ addLayer(extractLayers[key], `extract_${key}`, 'Extracts');
 
 
 
930
  }
 
931
  }
932
  }
933
+
934
+ // Add static items
935
+ if (showStaticMarkers) {
936
+ for (const category in mapData) {
937
+ const markerLayer = L.layerGroup();
938
+
939
+ const items = mapData[category];
940
+ for (const item of items) {
941
+ const itemIcon = L.icon({
942
+ iconUrl: `/static/maps/interactive/${category}.png`,
943
+ iconSize: [24, 24],
944
+ popupAnchor: [0, -12],
945
+ //className: layerIncludesMarker(heightLayer, item) ? '' : 'off-level',
946
+ });
947
+ L.marker(pos(item.position), { icon: itemIcon, position: item.position })
948
+ .bindPopup(L.popup().setContent(`${item.name}<br>Elevation: ${item.position.y}`))
949
+ .addTo(markerLayer);
950
+
951
+ checkMarkerBounds(item.position, markerBounds);
952
+ }
953
+
954
+ if (items.length > 0) {
955
+ var section;
956
+ if (category.startsWith('extract')) {
957
+ section = 'Extracts';
958
+ }
959
+ else if (category.startsWith('spawn')) {
960
+ section = 'Spawns';
961
+ }
962
+ else {
963
+ section = 'Lootable Items';
964
+ }
965
+ markerLayer.addTo(map);
966
+ addLayer(markerLayer, category, section);
967
+ // layerControl.addOverlay(markerLayer, `<img src='${process.env.PUBLIC_URL}/maps/interactive/${category}.png' class='control-item-image' /> ${categories[category] || category}`, section);
968
+ }
969
+ }
 
 
 
 
 
 
 
 
 
 
 
970
  }
971
+
972
+ // Erstelle ein neues Control-Element
973
+ const customControl = L.Control.extend({
974
+ onAdd: function (map) {
975
+ this.container = L.DomUtil.create('div', 'custom-control leaflet-control-layers leaflet-control-layers-expanded')
976
+ this.container.innerHTML = '<h2>Add Marker to Pos: </h2>'
977
+ return this.container
978
+ },
979
+
980
+ updateText: function (html) {
981
+ if (this.container) {
982
+ this.container.innerHTML = html
983
+ }
984
+ },
985
+
986
+ clearText: function () {
987
+ if (this.container) {
988
+ this.container.innerHTML = ''
989
+ }
990
+ },
991
+
992
+ onRemove: function (map) {
993
+ // Nichts zu tun hier
994
+ }
995
  });
996
+ const myControl = new customControl({ position: 'topright' })
997
+ map.addControl(myControl)
998
+
999
+ function startCountdown(elementId) {
1000
+ const element = document.getElementById(elementId);
1001
+ if (!element) {
1002
+ console.error(`Element with id ${elementId} not found.`);
1003
+ return;
 
 
 
 
 
 
1004
  }
1005
+
1006
+ //let secondsStart = Math.floor(new Date().getTime() / 1000.0) - parseInt(element.getAttribute('data-rade-end')) - 60
1007
+ let secondsStart = parseInt(element.getAttribute('data-seconds')) - 60
1008
+ let seconds = secondsStart
1009
+ if (isNaN(secondsStart)) {
1010
+ console.error('Invalid data-seconds attribute.');
1011
+ return;
1012
+ }
1013
+
1014
+ const intervalId = setInterval(() => {
1015
+ if (!document.hidden) {
1016
+ //seconds = (secondsStart - parseInt(Math.floor(new Date().getTime() / 1000.0)))
1017
+ //localStorage.setItem('rade_data', JSON.stringify({ remaining_time: seconds }));
1018
+ seconds--
1019
+
1020
+ if (seconds <= 0) {
1021
+ clearInterval(intervalId);
1022
+ element.textContent = '0:00:00';
1023
+ myControl.updateText("")
1024
+ document.querySelector("head > title").textContent = `${mapData.name} | Map with friends`
1025
+ return;
1026
+ }
1027
+
1028
+ //seconds--;
1029
+ const hours = Math.floor(seconds / 3600);
1030
+ const minutes = Math.floor((seconds % 3600) / 60);
1031
+ const remainingSeconds = seconds % 60;
1032
+
1033
+ const timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
1034
+ element.textContent = timeString;
1035
+ document.querySelector("head > title").textContent = `${mapData.name} | ${timeString}`
1036
+ }
1037
+ }, 1000);
1038
  }
1039
+
1040
+ function addNewRadeData(new_html) {
1041
+ myControl.updateText(new_html)
1042
+ startCountdown("rade_time_remain")
1043
+ };
1044
+
1045
+ //MARK: ADDMARKER
1046
+ // Funktion zum Hinzufügen eines Markers
1047
+ function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) {
1048
+
1049
+ // Validate position
1050
+ const position = {
1051
+ x: parseFloat(x),
1052
+ y: parseFloat(y),
1053
+ z: parseFloat(z)
1054
+ };
1055
+
1056
+ if (!positionIsInBounds(position)) {
1057
+ console.error("Position außerhalb der Karte:", position);
1058
+ return;
1059
  }
1060
+
1061
+ // SVG-Icon als String mit dynamischer Farbe
1062
+ let markerColor;
1063
+ if (markercolor) {
1064
+ markerColor = '#' + markercolor;
1065
+ } else {
1066
+ markerColor = 'currentColor';
1067
+ }
1068
+ const svgString = `
1069
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg">
1070
+ <path d="M60.8 50.5 38 72.9 15.2 50.5 8.1 3.9 38 13.3l29.8-9.4-7 46.6z" style="stroke-linecap:round;stroke-linejoin:round;fill:#fff;stroke:#fff;stroke-width:4.4px"/><path d="M58.8 49.4 38 69.9 17.1 49.4 10.7 6.9 38 15.5l27.2-8.6-6.4 42.5z" style="fill:${markerColor};stroke:#000;stroke-width:4px;stroke-linecap:round;stroke-linejoin:round"/>
1071
+ </svg>
1072
+ `;
1073
+ const svgString2 = `
1074
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512' class="marker-svg">
1075
+ <path fill='${markerColor}' d='M172.3 501.7C27 291 0 269.4 0 192 0 86 86 0 192 0s192 86 192 192c0 77.4-27 99-172.3 309.7-9.5 13.8-29.9 13.8-39.5 0zM192 272c44.2 0 80-35.8 80-80s-35.8-80-80-80-80 35.8-80 80 35.8 80 80 80z'/>
1076
+ </svg>
1077
+ `;
1078
+
1079
+ // DivIcon mit dynamischem SVG erstellen
1080
+ const markerIcon = L.divIcon({
1081
+ html: svgString,
1082
+ className: 'custom-marker',
1083
+ iconSize: [32, 32],
1084
+ iconAnchor: [16, 32],
1085
+ popupAnchor: [0, -32]
1086
  });
1087
+
1088
+ const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-")
1089
+
1090
+ // Remove old marker for this player, if it exists
1091
+ if (playerMarkers[playerMarkerName]) {
1092
+ map.removeLayer(playerMarkers[playerMarkerName]);
 
1093
  }
1094
+
1095
+ // Create new marker
1096
+ playerMarkers[playerMarkerName] = L.marker(pos(position), {
1097
+ icon: markerIcon,
1098
+ position: position,
1099
+ title: `Koordinaten: ${x}, ${y}, ${z}`,
1100
+ riseOnHover: true,
1101
+ zIndexOffset: 400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1102
  });
1103
+
1104
+ // Popup mit Informationen erstellen
1105
+ const popupContent = `
1106
+ <div class="marker-popup">
1107
+ ${playername ? `<strong>Player: ${playername}</strong>` : ''}
1108
+ <strong>Koordinaten:</strong>
1109
+ <span>X: ${x} Y: ${y} Z: ${z}</span>
1110
+ ${preview ? `<img class="preview-image" src="${preview}"><br>` : ''}
1111
+ <small>${timestamp}</small>
1112
+ </div>
1113
+ `;
1114
+ playerMarkers[playerMarkerName].bindPopup(popupContent, {
1115
+ maxWidth: 250,
1116
+ minWidth: 150,
1117
+ autoClose: true,
1118
+ closeOnClick: true
1119
+ });
1120
+
1121
+ // Marker zur Karte hinzufügen
1122
+ playerMarkers[playerMarkerName].addTo(map);
1123
+
1124
+ // Karte auf Marker zentrieren
1125
+ map.setView(pos(position), map.getZoom(), {
1126
+ animate: true,
1127
+ duration: 0.5
1128
+ });
1129
+
1130
+ console.log("Neuer Marker gesetzt für " + playername + ": " + position);
1131
  }
1132
+
1133
+ document.onload = loadLocalData()
1134
+
1135
+
1136
+ // MODAL Trigger
1137
+ document.addEventListener('DOMContentLoaded', () => {
1138
+ // Functions to open and close a modal
1139
+ function openModal($el) {
1140
+ $el.classList.add('is-active');
1141
  }
1142
+
1143
+ function closeModal($el) {
1144
+ $el.classList.remove('is-active');
1145
  }
1146
+
1147
+ function closeAllModals() {
1148
+ (document.querySelectorAll('.modal') || []).forEach(($modal) => {
1149
+ closeModal($modal);
1150
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1151
  }
1152
+
1153
+ // Add a click event on buttons to open a specific modal
1154
+ (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
1155
+ const modal = $trigger.dataset.target;
1156
+ const $target = document.getElementById(modal);
1157
+
1158
+ $trigger.addEventListener('click', () => {
1159
+ openModal($target);
1160
+ });
1161
+ });
1162
+
1163
+ // Add a click event on various child elements to close the parent modal
1164
+ (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
1165
+ const $target = $close.closest('.modal');
1166
+
1167
+ $close.addEventListener('click', () => {
1168
+ closeModal($target);
1169
+ });
1170
+ });
1171
+
1172
+ // Add a keyboard event to close all modals
1173
+ document.addEventListener('keydown', (event) => {
1174
+ if (event.key === "Escape") {
1175
+ closeAllModals();
1176
+ }
1177
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1178
  });
1179
+ };
 
 
 
 
 
 
 
 
1180
  </script>
1181
  </body>
1182