Sebastiankay commited on
Commit
bd5ea82
·
verified ·
1 Parent(s): b99c3f7

Update static/map.html

Browse files
Files changed (1) hide show
  1. static/map.html +629 -1105
static/map.html CHANGED
@@ -4,30 +4,81 @@
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
-
8
- <!--title>Tarkov Map with friends</title-->
9
- <title>Map with friends</title>
10
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
11
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
12
- <!--link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet-groupedlayercontrol/0.6.1/leaflet.groupedlayercontrol.css"/-->
13
  <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
14
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" />
15
- <!--link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css" /-->
16
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
17
  <link rel="stylesheet" href="/static/style.css" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </head>
19
 
20
  <body data-bs-theme="dark">
21
  <nav class="navbar navbar-expand-sm navbar-dark fixed-top">
22
  <div class="container-fluid">
23
- <a class="navbar-brand" href="#">Tarkov Map</a>
24
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
25
  <span class="navbar-toggler-icon"></span>
26
  </button>
27
  <div class="collapse navbar-collapse" id="navbarNavDropdown">
28
- <ul class="navbar-nav">
29
  <li class="nav-item">
30
- <a class="nav-link active" aria-current="page" href="#">Home</a>
31
  </li>
32
  <li class="nav-item dropdown">
33
  <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
@@ -46,76 +97,16 @@
46
  </ul>
47
  </li>
48
  </ul>
 
 
 
 
 
 
49
  </div>
50
  </div>
51
  </nav>
52
- <!--nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
53
- <div class="navbar-brand">
54
- Tarkov Map
55
- </div>
56
-
57
- <div id="navbarBasicExample" class="navbar-menu">
58
- <div class="navbar-start">
59
- <a class="navbar-item">
60
- Home
61
- </a>
62
- <div class="navbar-item has-dropdown is-hoverable">
63
- <a class="navbar-link">Maps</a>
64
- <div class="navbar-dropdown is-boxed">
65
-
66
- </div>
67
- </div>
68
- </div>
69
-
70
- <div class="navbar-end">
71
- <div class="navbar-item">
72
- <button class="button is-dark js-modal-trigger" data-target="settings-modal">
73
- <span class="icon is-small">
74
- <i class="fas fa-cog"></i>
75
- </span>
76
- </button>
77
- </div>
78
- </div>
79
- </div>
80
- </nav-->
81
-
82
- <div id="map"></div>
83
-
84
-
85
- <div id="settings-modal" class="modal">
86
- <div class="modal-background"></div>
87
-
88
- <div class="modal-content container">
89
- <div class="box">
90
- <section class="hero">
91
- <div class="hero-body">
92
- <p class="title has-text-weight-bold">EINSTELLUNGEN</p>
93
- <hr class="title-line">
94
- <p class="subtitle">Subtitle</p>
95
- </div>
96
- </section>
97
- <!-- Your content -->
98
- <section class="section">
99
- <div class="content">
100
- <p>
101
- Hier die Einstellungen basierend auf der config.ini file.
102
-
103
- {#
104
- [mainconfig]
105
- eft_log_older_path = E:/Battlestate Games/Escape from Tarkov/Logs/
106
- eft_screenshots_folder_path = C:/Users/Sebastian/Documents/Escape from Tarkov/Screenshots/
107
- delete_screenshots = True
108
- eft_show_exits_key = o
109
- eft_take_screenshot_key = del #}
110
- </p>
111
- </div>
112
- </section>
113
- </div>
114
- </div>
115
-
116
- <button class="modal-close is-large" aria-label="close"></button>
117
- </div>
118
-
119
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script>
120
  <script src="/static/js/leaflet.controlGroupedlayer.js"></script>
121
  <script src="/static/js/leaflet.controlCoordinates.js"></script>
@@ -124,1059 +115,592 @@
124
  const playerMarkers = {}
125
  let currentMarker = null
126
  const client_id = Date.now()
127
- const pathSegments = window.location.pathname.split('/');
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
 
 
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title id="pageTitle">Tarkov Map with friends</title>
 
 
8
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
9
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
 
10
  <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
11
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" />
 
12
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
13
  <link rel="stylesheet" href="/static/style.css" />
14
+ <style>
15
+ /* Stil für das Verbindungsformular in der Navbar */
16
+ .connection-form {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 0.5rem;
20
+ }
21
+
22
+ .connection-form .form-control {
23
+ width: 120px;
24
+ /* Begrenze die Breite des Eingabefelds */
25
+ padding: 0.25rem 0.5rem;
26
+ font-size: 0.875rem;
27
+ }
28
+
29
+ .connection-form .btn {
30
+ padding: 0.25rem 0.5rem;
31
+ font-size: 0.875rem;
32
+ }
33
+
34
+ /* Statusanzeige */
35
+ #connectionStatus {
36
+ font-size: 0.8rem;
37
+ padding: 0.25rem 0.5rem;
38
+ border-radius: 0.2rem;
39
+ }
40
+
41
+ .status-connected {
42
+ background-color: #d4edda;
43
+ color: #155724;
44
+ }
45
+
46
+ .status-disconnected {
47
+ background-color: #f8d7da;
48
+ color: #721c24;
49
+ }
50
+
51
+ .status-connecting {
52
+ background-color: #cce7ff;
53
+ color: #004085;
54
+ }
55
+
56
+ /* Anpassung für kleine Bildschirme */
57
+ @media (max-width: 768px) {
58
+ .connection-form {
59
+ flex-direction: column;
60
+ align-items: flex-start;
61
+ gap: 0.25rem;
62
+ }
63
+
64
+ .connection-form .form-control {
65
+ width: 100%;
66
+ }
67
+ }
68
+ </style>
69
  </head>
70
 
71
  <body data-bs-theme="dark">
72
  <nav class="navbar navbar-expand-sm navbar-dark fixed-top">
73
  <div class="container-fluid">
74
+ <a class="navbar-brand" href="/">Tarkov Map</a>
75
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
76
  <span class="navbar-toggler-icon"></span>
77
  </button>
78
  <div class="collapse navbar-collapse" id="navbarNavDropdown">
79
+ <ul class="navbar-nav me-auto">
80
  <li class="nav-item">
81
+ <a class="nav-link" href="/">Home</a>
82
  </li>
83
  <li class="nav-item dropdown">
84
  <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
 
97
  </ul>
98
  </li>
99
  </ul>
100
+ <!-- Verbindungsformular in der Navbar -->
101
+ <form id="groupConnectForm" class="d-flex connection-form">
102
+ <input type="text" id="groupNameInput" class="form-control" placeholder="Gruppe" aria-label="Gruppenname">
103
+ <button class="btn btn-outline-success" type="submit">Verbinden</button>
104
+ <span id="connectionStatus" class="status-disconnected">Nicht verbunden</span>
105
+ </form>
106
  </div>
107
  </div>
108
  </nav>
109
+ <div id="map" style="margin-top: 56px; height: calc(100vh - 56px);"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script>
111
  <script src="/static/js/leaflet.controlGroupedlayer.js"></script>
112
  <script src="/static/js/leaflet.controlCoordinates.js"></script>
 
115
  const playerMarkers = {}
116
  let currentMarker = null
117
  const client_id = Date.now()
118
+ let mapData = null;
119
+ let wsgroup = "";
120
+ let ws = null; // WebSocket connection variable
121
+
122
+ // Elemente für das Verbindungsformular
123
+ const groupConnectForm = document.getElementById('groupConnectForm');
124
+ const groupNameInput = document.getElementById('groupNameInput');
125
+ const connectionStatus = document.getElementById('connectionStatus');
126
+ const pageTitle = document.getElementById('pageTitle');
127
+
128
+ // Funktion zum Aktualisieren des Verbindungsstatus
129
+ function updateConnectionStatus(status, message) {
130
+ connectionStatus.textContent = message;
131
+ connectionStatus.className = ''; // Entferne alle Klassen
132
+ connectionStatus.classList.add('status-' + status); // Füge die entsprechende Statusklasse hinzu
133
+ }
134
+
135
+ // Funktion zum Herstellen der WebSocket-Verbindung
136
+ function connectToWebSocket(groupName) {
137
+ if (ws) {
138
+ ws.close(); // Schließe bestehende Verbindung
139
+ }
140
+
141
+ updateConnectionStatus('connecting', 'Verbinde...');
142
+
143
+ try {
 
 
 
 
 
144
  const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`);
145
+ //ws = new WebSocket(`ws://127.0.0.1:8000/ws`);
146
+
147
+ ws.onopen = function (event) {
148
+ console.log("WebSocket connected");
149
+ updateConnectionStatus('connected', `Verbunden: ${groupName}`);
150
+ wsgroup = groupName;
151
+ // Identifiziere sich als Web-Client
152
+ ws.send(JSON.stringify({ type: "join", group: groupName, client_type: "web" }));
153
+ };
154
+
155
  ws.onmessage = function (event) {
156
+ console.log("WebSocket message received:", event.data);
157
  const data = JSON.parse(event.data);
158
+ handleWebSocketMessage(data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  };
160
+
161
+ ws.onclose = function (event) {
162
+ console.log("WebSocket closed");
163
+ updateConnectionStatus('disconnected', 'Verbindung getrennt');
164
+ // Versuche nicht automatisch erneut zu verbinden, um Verwirrung zu vermeiden
165
  };
166
+
167
+ ws.onerror = function (error) {
168
+ console.error("WebSocket error:", error);
169
+ updateConnectionStatus('disconnected', 'Verbindungsfehler');
170
+ };
171
+
172
+ } catch (error) {
173
+ console.error("Failed to connect to WebSocket:", error);
174
+ updateConnectionStatus('disconnected', 'Verbindungsfehler');
175
+ }
176
+ }
177
+
178
+ // Event Listener für das Verbindungsformular
179
+ groupConnectForm.addEventListener('submit', function (e) {
180
+ e.preventDefault(); // Verhindere das Standard-Formular-Submit-Verhalten
181
+ const groupName = groupNameInput.value.trim();
182
+ if (groupName) {
183
+ connectToWebSocket(groupName);
184
+ } else {
185
+ updateConnectionStatus('disconnected', 'Gruppenname erforderlich');
186
+ }
187
+ });
188
+
189
+ // WebSocket event handlers
190
+ function handleWebSocketMessage(data) {
191
+ console.log(data);
192
+ switch (data.type) {
193
+ case "coordinates":
194
+ const parsedData = data.data;
195
+ localStorage.setItem("last_marker", JSON.stringify(parsedData));
196
+ loadLocalData();
197
+ break;
198
+ case "location_map":
199
+ const map_name = data.data.map.toLowerCase().replaceAll(" ", "-");
200
+ localStorage.setItem("last_map_name", map_name);
201
+ localStorage.removeItem("last_marker");
202
+ removeAllMarkers(); // Clear all markers when the map changes
203
+ if (!location.pathname.includes(map_name)) {
204
+ location.pathname = `/map/${map_name}`;
205
  } else {
206
+ location.reload();
207
+ }
208
+ break;
209
+ case "new_rade_data":
210
+ console.log(data.data);
211
+ const radeData = JSON.parse(data.data);
212
+ localStorage.setItem("rade_data", JSON.stringify(radeData));
213
+ loadLocalData();
214
+ break;
215
+ case "joined":
216
+ console.log(`Erfolgreich Gruppe '${data.group}' beigetreten`);
217
+ updateConnectionStatus('connected', `Verbunden: ${data.group}`);
218
+ break;
219
+ case "error":
220
+ console.error("WebSocket Fehler:", data.message);
221
+ updateConnectionStatus('disconnected', `Fehler: ${data.message}`);
222
+ break;
223
+ case "client_joined":
224
+ console.log(`Neuer Client (${data.client_type}) ist der Gruppe beigetreten`);
225
+ // Optional: Benachrichtigung anzeigen, dass ein neuer Client beigetreten ist
226
+ break;
227
+ }
228
+ };
229
+
230
+ // Funktion zum Entfernen aller Marker
231
+ function removeAllMarkers() {
232
+ for (const playername in playerMarkers) {
233
+ if (playerMarkers.hasOwnProperty(playername)) {
234
+ map.removeLayer(playerMarkers[playername]);
235
  }
236
+ }
237
+ Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]);
238
+ console.log("Alle Marker wurden entfernt.");
239
+ }
240
+
241
+ // Funktion zum Laden und Anzeigen lokaler Markerdaten
242
+ function loadLocalData() {
243
+ const map_name = localStorage.getItem("last_map_name");
244
+ if (map_name) {
245
+ if (!location.pathname.includes(map_name)) {
246
+ localStorage.removeItem("last_marker");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  }
248
+ } else {
249
+ localStorage.removeItem("last_marker");
250
+ }
251
+ const markerData = localStorage.getItem("last_marker");
252
+ if (markerData) {
253
+ const parsedData = JSON.parse(markerData);
254
+ // Use the new addMarker function with all required parameters
255
+ addMarker(
256
+ parsedData.x,
257
+ parsedData.y,
258
+ parsedData.z,
259
+ parsedData.timestamp,
260
+ parsedData.preview || false,
261
+ parsedData.actualmap || "Unbekannte Map",
262
+ parsedData.playername || "Unnamed Player",
263
+ parsedData.markercolor || false
264
+ );
265
+ }
266
+ }
267
+
268
+ // Funktion zum Laden der Kartendaten vom API-Endpunkt
269
+ async function loadMapData() {
270
+ try {
271
+ // Extrahiere den Kartennamen aus der URL
272
+ const pathParts = window.location.pathname.split('/');
273
+ const mapName = pathParts[pathParts.length - 1];
274
+
275
+ if (!mapName) {
276
+ console.error("Kein Kartenname in der URL gefunden");
277
+ return;
278
  }
279
+
280
+ const response = await fetch(`/api/map/${mapName}`);
281
+ if (!response.ok) {
282
+ throw new Error(`HTTP error! status: ${response.status}`);
283
  }
284
+
285
+ mapData = await response.json();
286
+ console.log("Kartendaten geladen:", mapData);
287
+
288
+ // Aktualisiere den Seitentitel
289
+ if (mapData.name) {
290
+ pageTitle.textContent = `${mapData.name} | Map with friends`;
 
 
 
 
291
  }
292
+
293
+ // Initialisiere die Karte mit den geladenen Daten
294
+ initializeMap();
295
+ } catch (error) {
296
+ console.error("Fehler beim Laden der Kartendaten:", error);
297
+ updateConnectionStatus('disconnected', 'Fehler beim Laden der Kartendaten');
298
+ }
299
+ }
300
+
301
+ // ... (Rest des bestehenden JavaScript-Codes für Kartenfunktionalität) ...
302
+ // Die folgenden Funktionen bleiben größtenteils unverändert,
303
+ // aber sie verwenden jetzt die von der API geladenen mapData
304
+
305
+ const images = {
306
+ 'container_bank-cash-register': 'container_cash-register',
307
+ 'container_bank-safe': 'container_safe',
308
+ 'container_buried-barrel-cache': 'container_buried-barrel-cache',
309
+ 'container_cash-register': 'container_cash-register',
310
+ 'container_cash-register-tar2-2': 'container_cash-register',
311
+ 'container_dead-civilian': 'container_dead-scav',
312
+ 'container_dead-scav': 'container_dead-scav',
313
+ 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate',
314
+ 'container_pmc-body': 'container_dead-scav',
315
+ 'container_civilian-body': 'container_dead-scav',
316
+ 'container_drawer': 'container_drawer',
317
+ 'container_duffle-bag': 'container_duffle-bag',
318
+ 'container_grenade-box': 'container_grenade-box',
319
+ 'container_ground-cache': 'container_ground-cache',
320
+ 'container_jacket': 'container_jacket',
321
+ 'container_lab-technician-body': 'container_dead-scav',
322
+ 'container_medbag-smu06': 'container_medbag-smu06',
323
+ 'container_medcase': 'container_medcase',
324
+ 'container_medical-supply-crate': 'container_crate',
325
+ 'container_pc-block': 'container_pc-block',
326
+ 'container_plastic-suitcase': 'container_plastic-suitcase',
327
+ 'container_ration-supply-crate': 'container_crate',
328
+ 'container_safe': 'container_safe',
329
+ 'container_scav-body': 'container_dead-scav',
330
+ 'container_shturmans-stash': 'container_weapon-box',
331
+ 'container_technical-supply-crate': 'container_crate',
332
+ 'container_toolbox': 'container_toolbox',
333
+ 'container_weapon-box': 'container_weapon-box',
334
+ 'container_wooden-ammo-box': 'container_wooden-ammo-box',
335
+ 'container_wooden-crate': 'container_wooden-crate',
336
+ 'extract_pmc': 'extract_pmc',
337
+ 'extract_scav': 'extract_scav',
338
+ 'extract_shared': 'extract_shared',
339
+ 'extract_transit': 'extract_transit',
340
+ 'hazard': 'hazard',
341
+ 'hazard_mortar': 'hazard_mortar',
342
+ 'hazard_minefield': 'hazard',
343
+ 'hazard_sniper': 'hazard',
344
+ 'key': 'key',
345
+ 'lock': 'lock',
346
+ 'loose_loot': 'loose_loot',
347
+ 'quest_item': 'quest_item',
348
+ 'quest_objective': 'quest_objective',
349
+ 'spawn_sniper_scav': 'spawn_sniper_scav',
350
+ 'spawn_bloodhound': 'spawn_bloodhound',
351
+ 'spawn_boss': 'spawn_boss',
352
+ 'spawn_cultist-priest': 'spawn_cultist-priest',
353
+ 'spawn_pmc': 'spawn_pmc',
354
+ 'spawn_rogue': 'spawn_rogue',
355
+ 'spawn_scav': 'spawn_scav',
356
+ 'stationarygun': 'stationarygun',
357
+ 'switch': 'switch',
358
+ };
359
+
360
+ const categories = {
361
+ 'extract_pmc': 'PMC',
362
+ 'extract_shared': 'Shared',
363
+ 'extract_scav': 'Scav',
364
+ 'extract_transit': 'Transit',
365
+ 'spawn_sniper_scav': 'Sniper Scav',
366
+ 'spawn_pmc': 'PMC',
367
+ 'spawn_scav': 'Scav',
368
+ 'spawn_boss': 'Boss',
369
+ 'quest_item': 'Item',
370
+ 'quest_objective': 'Objective',
371
+ 'lock': 'Locks',
372
+ 'lever': 'Lever',
373
+ 'stationarygun': 'Stationary Gun',
374
+ 'switch': 'Switch',
375
+ 'place-names': 'Place Names',
376
+ };
377
+
378
+ function getCRS(transform) {
379
+ let scaleX = 1;
380
+ let scaleY = 1;
381
+ let marginX = 0;
382
+ let marginY = 0;
383
+ if (transform) {
384
+ scaleX = transform[0];
385
+ scaleY = transform[2] * -1;
386
+ marginX = transform[1];
387
+ marginY = transform[3];
388
+ }
389
+ return L.extend({}, L.CRS.Simple, {
390
+ transformation: new L.Transformation(scaleX, marginX, scaleY, marginY),
391
+ projection: L.extend({}, L.Projection.LonLat, {
392
+ project: latLng => {
393
+ return L.Projection.LonLat.project(applyRotation(latLng, mapData.coordinateRotation || 0));
394
+ },
395
+ unproject: point => {
396
+ return applyRotation(L.Projection.LonLat.unproject(point), (mapData.coordinateRotation || 0) * -1);
397
+ },
398
+ }),
399
+ });
400
+ }
401
+
402
+ function applyRotation(latLng, rotation) {
403
+ if (!latLng.lng && !latLng.lat) {
404
+ return L.latLng(0, 0);
405
+ }
406
+ if (!rotation) {
407
+ return latLng;
408
+ }
409
+ const angleInRadians = (rotation * Math.PI) / 180;
410
+ const cosAngle = Math.cos(angleInRadians);
411
+ const sinAngle = Math.sin(angleInRadians);
412
+ const { lng: x, lat: y } = latLng;
413
+ const rotatedX = x * cosAngle - y * sinAngle;
414
+ const rotatedY = x * sinAngle + y * cosAngle;
415
+ return L.latLng(rotatedY, rotatedX);
416
+ }
417
+
418
+ function pos(position) {
419
+ return [position.z, position.x];
420
+ }
421
+
422
+ function addElevation(item, popup) {
423
+ if (!mapData.showElevation) { // Verwende showElevation aus mapData
424
+ return;
425
+ }
426
+ const elevationContent = L.DomUtil.create('div', undefined, popup);
427
+ elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`;
428
+ if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) {
429
+ const heightContent = L.DomUtil.create('div', undefined, popup);
430
+ heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`;
431
+ }
432
+ }
433
+
434
+ function markerIsOnLayer(marker, layer) {
435
+ if (!layer) {
436
+ return true;
437
+ }
438
+ var top = marker.options.top || marker.options.position.y;
439
+ var bottom = marker.options.bottom || marker.options.position.y;
440
+ for (const extent of layer.options.extents) {
441
+ if (top >= extent.height[0] && bottom < extent.height[1]) {
442
+ let containedType = 'partial';
443
+ if (bottom >= extent.height[0] && top <= extent.height[1]) {
444
+ containedType = 'full';
445
+ }
446
+ if (extent.bounds) {
447
+ for (const boundsArray of extent.bounds) {
448
+ const bounds = getBounds(boundsArray);
449
+ if (bounds.contains(pos(marker.options.position))) {
450
  return containedType;
451
  }
452
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  } else {
454
+ return containedType;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  }
456
  }
457
+ }
458
+ return false;
459
+ }
460
+
461
+ function markerIsOnActiveLayer(marker) {
462
+ if (!marker.options.position) {
463
+ return true;
464
+ }
465
+ const map = marker._map;
466
+ // check if marker is completely contained by inactive layer
467
+ const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay);
468
+ for (const layer of overlays) {
469
+ for (const extent of layer.options.extents) {
470
+ if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) {
471
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  }
473
  }
474
+ }
475
+ // check if marker is on active overlay
476
+ const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay);
477
+ if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) {
478
+ return true;
479
+ }
480
+ // check if marker is on base layer
481
+ const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !l.options?.overlay);
482
+ if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) {
483
+ return true;
484
+ }
485
+ return false;
486
+ }
487
+
488
+ function checkMarkerForActiveLayers(event) {
489
+ const marker = event.target || event;
490
+ const outline = marker.options.outline;
491
+ const onLevel = markerIsOnActiveLayer(marker);
492
+ if (onLevel) {
493
+ marker._icon?.classList.remove('off-level');
494
+ if (outline) {
495
+ outline._path?.classList.remove('off-level');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  }
497
+ } else {
498
+ marker._icon?.classList.add('off-level');
499
+ if (outline) {
500
+ outline._path?.classList.add('off-level');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  }
502
+ }
503
+ }
504
+
505
+ function activateMarkerLayer(event) {
506
+ const marker = event.target || event;
507
+ if (markerIsOnActiveLayer(marker)) {
508
+ return;
509
+ }
510
+ const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
511
+ for (const layer of activeLayers) {
512
+ layer.removeFrom(marker._map);
513
+ }
514
+ const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
515
+ for (const layer of heightLayers) {
516
+ if (markerIsOnLayer(marker, layer)) {
517
+ layer.addTo(marker._map);
518
+ break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  }
520
+ }
521
+ }
522
+
523
+ const getALink = (path, contents) => {
524
+ const a = L.DomUtil.create('a');
525
+ a.setAttribute('href', path);
526
+ a.setAttribute('target', '_blank');
527
+ a.append(contents);
528
+ return a;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  };
530
+
531
+ function getScaledBounds(bounds, scaleFactor) {
532
+ // Calculate the center point of the bounds
533
+ const centerX = (bounds[0][0] + bounds[1][0]) / 2;
534
+ const centerY = (bounds[0][1] + bounds[1][1]) / 2;
535
+ // Calculate the new width and height
536
+ const width = bounds[1][0] - bounds[0][0];
537
+ const height = bounds[1][1] - bounds[0][1];
538
+ const newWidth = width * scaleFactor;
539
+ const newHeight = height * scaleFactor;
540
+ // Update the coordinates of the two points defining the bounds
541
+ const newBounds = [
542
+ [centerY - newHeight / 2, centerX - newWidth / 2],
543
+ [centerY + newHeight / 2, centerX + newWidth / 2]
544
+ ];
545
+ return newBounds;
546
+ }
547
+
548
+ // Funktion zur Initialisierung der Karte mit den geladenen Daten
549
+ function initializeMap() {
550
+ if (!mapData) {
551
+ console.error("Keine Kartendaten verfügbar");
552
+ return;
553
+ }
554
+
555
+ // Erstelle die Karte
556
+ const map = L.map('map', {
557
+ maxBounds: getScaledBounds(mapData.svgBounds || mapData.bounds, 1.5),
558
+ center: [0, 0],
559
+ zoom: 2,
560
+ minZoom: mapData.minZoom || 1,
561
+ maxZoom: mapData.maxZoom || 6,
562
+ zoomSnap: 0.1,
563
+ scrollWheelZoom: true,
564
+ wheelPxPerZoomLevel: 120,
565
+ crs: getCRS(mapData.transform),
566
+ attributionControl: false,
567
+ id: `${mapData.name.replace(/\s+/g, '')}Map`,
568
+ });
569
+
570
+ window.map = map; // Mache die Karte global verfügbar
571
+
572
+ const layerControl = L.control.groupedLayers(null, null, {
573
+ position: 'topleft',
574
+ collapsed: true,
575
+ groupCheckboxes: true,
576
+ groupsCollapsable: true,
577
+ exclusiveOptionalGroups: ['Levels'],
578
+ }).addTo(map);
579
+
580
+ // ... (Rest der Karteninitialisierung) ...
581
+ // Hier würden die restlichen Kartenfunktionen folgen,
582
+ // die auf den von der API geladenen mapData basieren
583
+
584
+ // Beispiel für das Hinzufügen des Image-Overlays:
585
+ const bounds = mapData.bounds;
586
+ const getBounds = (bounds) => {
587
+ if (!bounds) return undefined;
588
+ return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]);
589
+ };
590
+
591
+ const overlay = L.imageOverlay(mapData.svgPath, getBounds(bounds));
592
+ overlay.addTo(map);
593
+
594
+ // Weitere Karteninitialisierung...
595
+ // (Die folgenden Funktionen wie addLayer, etc. bleiben größtenteils unverändert)
596
+ }
597
+
598
+ // Funktion zum Erstellen eines benutzerdefinierten Markers
599
+ function createCustomMarkerIcon(markercolor) {
600
+ let markerColor;
601
+ if (markercolor) {
602
+ markerColor = '#' + markercolor;
603
+ } else {
604
+ markerColor = 'currentColor';
605
+ }
606
+ const svgString = `
607
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg">
608
+ <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"/>
609
+ <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"/>
610
+ </svg>
611
+ `;
612
+
613
+ return L.divIcon({
614
+ html: svgString,
615
+ className: 'custom-marker',
616
+ iconSize: [32, 32],
617
+ iconAnchor: [16, 32],
618
+ popupAnchor: [0, -32]
619
+ });
620
+ }
621
+
622
+ //MARK: ADDMARKER
623
+ // Funktion zum Hinzufügen eines Markers
624
+ function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) {
625
+ if (!window.map) {
626
+ console.error("Karte nicht initialisiert");
627
+ return;
628
+ }
629
+
630
+ // Validate position
631
+ const position = {
632
+ x: parseFloat(x),
633
+ y: parseFloat(y),
634
+ z: parseFloat(z)
635
+ };
636
+
637
+ const getBounds = (bounds) => {
638
+ if (!bounds) return undefined;
639
+ return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]);
640
+ };
641
+
642
+ const positionIsInBounds = (position) => {
643
+ return getBounds(mapData.bounds).contains(pos(position));
644
+ };
645
+
646
+ if (!positionIsInBounds(position)) {
647
+ console.error("Position außerhalb der Karte:", position);
648
+ return;
649
+ }
650
+
651
+ // DivIcon mit dynamischem SVG erstellen
652
+ const markerIcon = createCustomMarkerIcon(markercolor);
653
+ const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-");
654
+
655
+ // Remove old marker for this player, if it exists
656
+ if (playerMarkers[playerMarkerName]) {
657
+ window.map.removeLayer(playerMarkers[playerMarkerName]);
658
+ }
659
+
660
+ // Create new marker
661
+ playerMarkers[playerMarkerName] = L.marker(pos(position), {
662
+ icon: markerIcon,
663
+ position: position,
664
+ title: `Koordinaten: ${x}, ${y}, ${z}`,
665
+ riseOnHover: true,
666
+ zIndexOffset: 400
667
+ });
668
+
669
+ // Popup mit Informationen erstellen
670
+ const popupContent = `
671
+ <div class="marker-popup">
672
+ ${playername ? `<strong>Player: ${playername}</strong>` : ''}
673
+ <strong>Koordinaten:</strong>
674
+ <span>X: ${x} Y: ${y} Z: ${z}</span>
675
+ ${preview ? `<img class="preview-image" src="${preview}" style="max-width: 100%; height: auto; margin-top: 5px;"><br>` : ''}
676
+ <small>${timestamp}</small>
677
+ </div>
678
+ `;
679
+
680
+ playerMarkers[playerMarkerName].bindPopup(popupContent, {
681
+ maxWidth: 250,
682
+ minWidth: 150,
683
+ autoClose: true,
684
+ closeOnClick: true
685
+ });
686
+
687
+ // Marker zur Karte hinzufügen
688
+ playerMarkers[playerMarkerName].addTo(window.map);
689
+
690
+ // Karte auf Marker zentrieren
691
+ window.map.setView(pos(position), window.map.getZoom(), {
692
+ animate: true,
693
+ duration: 0.5
694
+ });
695
+
696
+ console.log("Neuer Marker gesetzt für " + playername + ": " + position);
697
+ }
698
+
699
+ // Lade die Kartendaten, wenn die Seite geladen ist
700
+ document.addEventListener('DOMContentLoaded', function () {
701
+ loadMapData();
702
+ loadLocalData();
703
+ });
704
  </script>
705
  </body>
706