| | <!DOCTYPE html> |
| | <html lang="en"> |
| |
|
| | <head> |
| | <meta charset="UTF-8" /> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| | <title id="pageTitle">Tarkov Map with friends</title> |
| | <link rel="icon" type="image/x-icon" href="/static/favicon.ico" /> |
| | <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" /> |
| | <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" /> |
| | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> |
| | <link rel="stylesheet" href="/static/style.css" /> |
| | <style> |
| | |
| | .connection-form { |
| | display: flex; |
| | align-items: center; |
| | gap: 0.5rem; |
| | } |
| | |
| | .connection-form .form-control { |
| | width: 120px; |
| | |
| | padding: 0.25rem 0.5rem; |
| | font-size: 0.875rem; |
| | } |
| | |
| | .connection-form .btn { |
| | padding: 0.25rem 0.5rem; |
| | font-size: 0.875rem; |
| | } |
| | |
| | |
| | #connectionStatus { |
| | font-size: 0.8rem; |
| | padding: 0.25rem 0.5rem; |
| | border-radius: 0.2rem; |
| | } |
| | |
| | .status-connected { |
| | background-color: #d4edda; |
| | color: #155724; |
| | } |
| | |
| | .status-disconnected { |
| | background-color: #f8d7da; |
| | color: #721c24; |
| | } |
| | |
| | .status-connecting { |
| | background-color: #cce7ff; |
| | color: #004085; |
| | } |
| | |
| | |
| | @media (max-width: 768px) { |
| | .connection-form { |
| | flex-direction: column; |
| | align-items: flex-start; |
| | gap: 0.25rem; |
| | } |
| | |
| | .connection-form .form-control { |
| | width: 100%; |
| | } |
| | } |
| | </style> |
| | </head> |
| |
|
| | <body data-bs-theme="dark"> |
| | <nav class="navbar navbar-expand-sm navbar-dark fixed-top"> |
| | <div class="container-fluid"> |
| | <a class="navbar-brand" href="/">Tarkov Map</a> |
| | <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> |
| | <span class="navbar-toggler-icon"></span> |
| | </button> |
| | <div class="collapse navbar-collapse" id="navbarNavDropdown"> |
| | <ul class="navbar-nav me-auto"> |
| | <li class="nav-item"> |
| | <a class="nav-link" href="/">Home</a> |
| | </li> |
| | <li class="nav-item dropdown"> |
| | <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> |
| | Maps |
| | </a> |
| | <ul class="dropdown-menu"> |
| | <li><a href="/map/woods" class="dropdown-item">Woods</a></li> |
| | <li><a href="/map/shoreline" class="dropdown-item">Shoreline</a></li> |
| | <li><a href="/map/lighthouse" class="dropdown-item">Lighthouse</a></li> |
| | <li><a href="/map/customs" class="dropdown-item">Customs</a></li> |
| | <li><a href="/map/interchange" class="dropdown-item">Interchange</a></li> |
| | <li><a href="/map/streets-of-tarkov" class="dropdown-item">Streets of Tarkov</a></li> |
| | <li><a href="/map/ground-zero" class="dropdown-item">Ground Zero</a></li> |
| | <li><a href="/map/reserve" class="dropdown-item">Reserve</a></li> |
| | <li><a href="/map/factory" class="dropdown-item">Factory</a></li> |
| | </ul> |
| | </li> |
| | </ul> |
| | |
| | <form id="groupConnectForm" class="d-flex connection-form"> |
| | <input type="text" id="groupNameInput" class="form-control" placeholder="Gruppe" aria-label="Gruppenname"> |
| | <button class="btn btn-outline-success" type="submit">Verbinden</button> |
| | <span id="connectionStatus" class="status-disconnected">Nicht verbunden</span> |
| | </form> |
| | </div> |
| | </div> |
| | </nav> |
| | <div id="map" style="margin-top: 56px; height: calc(100vh - 56px);"></div> |
| | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script> |
| | <script src="/static/js/leaflet.controlGroupedlayer.js"></script> |
| | <script src="/static/js/leaflet.controlCoordinates.js"></script> |
| | <script> |
| | |
| | const playerMarkers = {} |
| | let currentMarker = null |
| | const client_id = Date.now() |
| | let mapData = null; |
| | let wsgroup = ""; |
| | let ws = null; |
| | |
| | |
| | const groupConnectForm = document.getElementById('groupConnectForm'); |
| | const groupNameInput = document.getElementById('groupNameInput'); |
| | const connectionStatus = document.getElementById('connectionStatus'); |
| | const pageTitle = document.getElementById('pageTitle'); |
| | |
| | |
| | function updateConnectionStatus(status, message) { |
| | connectionStatus.textContent = message; |
| | connectionStatus.className = ''; |
| | connectionStatus.classList.add('status-' + status); |
| | } |
| | |
| | |
| | function connectToWebSocket(groupName) { |
| | if (ws) { |
| | ws.close(); |
| | } |
| | |
| | updateConnectionStatus('connecting', 'Verbinde...'); |
| | |
| | try { |
| | const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`); |
| | |
| | |
| | ws.onopen = function (event) { |
| | console.log("WebSocket connected"); |
| | updateConnectionStatus('connected', `Verbunden: ${groupName}`); |
| | wsgroup = groupName; |
| | |
| | ws.send(JSON.stringify({ type: "join", group: groupName, client_type: "web" })); |
| | }; |
| | |
| | ws.onmessage = function (event) { |
| | console.log("WebSocket message received:", event.data); |
| | const data = JSON.parse(event.data); |
| | handleWebSocketMessage(data); |
| | }; |
| | |
| | ws.onclose = function (event) { |
| | console.log("WebSocket closed"); |
| | updateConnectionStatus('disconnected', 'Verbindung getrennt'); |
| | |
| | }; |
| | |
| | ws.onerror = function (error) { |
| | console.error("WebSocket error:", error); |
| | updateConnectionStatus('disconnected', 'Verbindungsfehler'); |
| | }; |
| | |
| | } catch (error) { |
| | console.error("Failed to connect to WebSocket:", error); |
| | updateConnectionStatus('disconnected', 'Verbindungsfehler'); |
| | } |
| | } |
| | |
| | |
| | groupConnectForm.addEventListener('submit', function (e) { |
| | e.preventDefault(); |
| | const groupName = groupNameInput.value.trim(); |
| | if (groupName) { |
| | connectToWebSocket(groupName); |
| | } else { |
| | updateConnectionStatus('disconnected', 'Gruppenname erforderlich'); |
| | } |
| | }); |
| | |
| | |
| | function handleWebSocketMessage(data) { |
| | console.log(data); |
| | switch (data.type) { |
| | case "coordinates": |
| | const parsedData = data.data; |
| | localStorage.setItem("last_marker", JSON.stringify(parsedData)); |
| | loadLocalData(); |
| | break; |
| | case "location_map": |
| | const map_name = data.data.map.toLowerCase().replaceAll(" ", "-"); |
| | localStorage.setItem("last_map_name", map_name); |
| | localStorage.removeItem("last_marker"); |
| | removeAllMarkers(); |
| | if (!location.pathname.includes(map_name)) { |
| | location.pathname = `/map/${map_name}`; |
| | } else { |
| | location.reload(); |
| | } |
| | break; |
| | case "new_rade_data": |
| | console.log(data.data); |
| | const radeData = JSON.parse(data.data); |
| | localStorage.setItem("rade_data", JSON.stringify(radeData)); |
| | loadLocalData(); |
| | break; |
| | case "joined": |
| | console.log(`Erfolgreich Gruppe '${data.group}' beigetreten`); |
| | updateConnectionStatus('connected', `Verbunden: ${data.group}`); |
| | break; |
| | case "error": |
| | console.error("WebSocket Fehler:", data.message); |
| | updateConnectionStatus('disconnected', `Fehler: ${data.message}`); |
| | break; |
| | case "client_joined": |
| | console.log(`Neuer Client (${data.client_type}) ist der Gruppe beigetreten`); |
| | |
| | break; |
| | } |
| | }; |
| | |
| | |
| | function removeAllMarkers() { |
| | for (const playername in playerMarkers) { |
| | if (playerMarkers.hasOwnProperty(playername)) { |
| | map.removeLayer(playerMarkers[playername]); |
| | } |
| | } |
| | Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]); |
| | console.log("Alle Marker wurden entfernt."); |
| | } |
| | |
| | |
| | function loadLocalData() { |
| | const map_name = localStorage.getItem("last_map_name"); |
| | if (map_name) { |
| | if (!location.pathname.includes(map_name)) { |
| | localStorage.removeItem("last_marker"); |
| | } |
| | } else { |
| | localStorage.removeItem("last_marker"); |
| | } |
| | const markerData = localStorage.getItem("last_marker"); |
| | if (markerData) { |
| | const parsedData = JSON.parse(markerData); |
| | |
| | addMarker( |
| | parsedData.x, |
| | parsedData.y, |
| | parsedData.z, |
| | parsedData.timestamp, |
| | parsedData.preview || false, |
| | parsedData.actualmap || "Unbekannte Map", |
| | parsedData.playername || "Unnamed Player", |
| | parsedData.markercolor || false |
| | ); |
| | } |
| | } |
| | |
| | |
| | async function loadMapData() { |
| | try { |
| | |
| | const pathParts = window.location.pathname.split('/'); |
| | const mapName = pathParts[pathParts.length - 1]; |
| | |
| | if (!mapName) { |
| | console.error("Kein Kartenname in der URL gefunden"); |
| | return; |
| | } |
| | |
| | const response = await fetch(`/api/map/${mapName}`); |
| | if (!response.ok) { |
| | throw new Error(`HTTP error! status: ${response.status}`); |
| | } |
| | |
| | mapData = await response.json(); |
| | console.log("Kartendaten geladen:", mapData); |
| | |
| | |
| | if (mapData.name) { |
| | pageTitle.textContent = `${mapData.name} | Map with friends`; |
| | } |
| | |
| | |
| | initializeMap(); |
| | } catch (error) { |
| | console.error("Fehler beim Laden der Kartendaten:", error); |
| | updateConnectionStatus('disconnected', 'Fehler beim Laden der Kartendaten'); |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | const images = { |
| | 'container_bank-cash-register': 'container_cash-register', |
| | 'container_bank-safe': 'container_safe', |
| | 'container_buried-barrel-cache': 'container_buried-barrel-cache', |
| | 'container_cash-register': 'container_cash-register', |
| | 'container_cash-register-tar2-2': 'container_cash-register', |
| | 'container_dead-civilian': 'container_dead-scav', |
| | 'container_dead-scav': 'container_dead-scav', |
| | 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate', |
| | 'container_pmc-body': 'container_dead-scav', |
| | 'container_civilian-body': 'container_dead-scav', |
| | 'container_drawer': 'container_drawer', |
| | 'container_duffle-bag': 'container_duffle-bag', |
| | 'container_grenade-box': 'container_grenade-box', |
| | 'container_ground-cache': 'container_ground-cache', |
| | 'container_jacket': 'container_jacket', |
| | 'container_lab-technician-body': 'container_dead-scav', |
| | 'container_medbag-smu06': 'container_medbag-smu06', |
| | 'container_medcase': 'container_medcase', |
| | 'container_medical-supply-crate': 'container_crate', |
| | 'container_pc-block': 'container_pc-block', |
| | 'container_plastic-suitcase': 'container_plastic-suitcase', |
| | 'container_ration-supply-crate': 'container_crate', |
| | 'container_safe': 'container_safe', |
| | 'container_scav-body': 'container_dead-scav', |
| | 'container_shturmans-stash': 'container_weapon-box', |
| | 'container_technical-supply-crate': 'container_crate', |
| | 'container_toolbox': 'container_toolbox', |
| | 'container_weapon-box': 'container_weapon-box', |
| | 'container_wooden-ammo-box': 'container_wooden-ammo-box', |
| | 'container_wooden-crate': 'container_wooden-crate', |
| | 'extract_pmc': 'extract_pmc', |
| | 'extract_scav': 'extract_scav', |
| | 'extract_shared': 'extract_shared', |
| | 'extract_transit': 'extract_transit', |
| | 'hazard': 'hazard', |
| | 'hazard_mortar': 'hazard_mortar', |
| | 'hazard_minefield': 'hazard', |
| | 'hazard_sniper': 'hazard', |
| | 'key': 'key', |
| | 'lock': 'lock', |
| | 'loose_loot': 'loose_loot', |
| | 'quest_item': 'quest_item', |
| | 'quest_objective': 'quest_objective', |
| | 'spawn_sniper_scav': 'spawn_sniper_scav', |
| | 'spawn_bloodhound': 'spawn_bloodhound', |
| | 'spawn_boss': 'spawn_boss', |
| | 'spawn_cultist-priest': 'spawn_cultist-priest', |
| | 'spawn_pmc': 'spawn_pmc', |
| | 'spawn_rogue': 'spawn_rogue', |
| | 'spawn_scav': 'spawn_scav', |
| | 'stationarygun': 'stationarygun', |
| | 'switch': 'switch', |
| | }; |
| | |
| | const categories = { |
| | 'extract_pmc': 'PMC', |
| | 'extract_shared': 'Shared', |
| | 'extract_scav': 'Scav', |
| | 'extract_transit': 'Transit', |
| | 'spawn_sniper_scav': 'Sniper Scav', |
| | 'spawn_pmc': 'PMC', |
| | 'spawn_scav': 'Scav', |
| | 'spawn_boss': 'Boss', |
| | 'quest_item': 'Item', |
| | 'quest_objective': 'Objective', |
| | 'lock': 'Locks', |
| | 'lever': 'Lever', |
| | 'stationarygun': 'Stationary Gun', |
| | 'switch': 'Switch', |
| | 'place-names': 'Place Names', |
| | }; |
| | |
| | function getCRS(transform) { |
| | let scaleX = 1; |
| | let scaleY = 1; |
| | let marginX = 0; |
| | let marginY = 0; |
| | if (transform) { |
| | scaleX = transform[0]; |
| | scaleY = transform[2] * -1; |
| | marginX = transform[1]; |
| | marginY = transform[3]; |
| | } |
| | return L.extend({}, L.CRS.Simple, { |
| | transformation: new L.Transformation(scaleX, marginX, scaleY, marginY), |
| | projection: L.extend({}, L.Projection.LonLat, { |
| | project: latLng => { |
| | return L.Projection.LonLat.project(applyRotation(latLng, mapData.coordinateRotation || 0)); |
| | }, |
| | unproject: point => { |
| | return applyRotation(L.Projection.LonLat.unproject(point), (mapData.coordinateRotation || 0) * -1); |
| | }, |
| | }), |
| | }); |
| | } |
| | |
| | function applyRotation(latLng, rotation) { |
| | if (!latLng.lng && !latLng.lat) { |
| | return L.latLng(0, 0); |
| | } |
| | if (!rotation) { |
| | return latLng; |
| | } |
| | const angleInRadians = (rotation * Math.PI) / 180; |
| | const cosAngle = Math.cos(angleInRadians); |
| | const sinAngle = Math.sin(angleInRadians); |
| | const { lng: x, lat: y } = latLng; |
| | const rotatedX = x * cosAngle - y * sinAngle; |
| | const rotatedY = x * sinAngle + y * cosAngle; |
| | return L.latLng(rotatedY, rotatedX); |
| | } |
| | |
| | function pos(position) { |
| | return [position.z, position.x]; |
| | } |
| | |
| | function addElevation(item, popup) { |
| | if (!mapData.showElevation) { |
| | return; |
| | } |
| | const elevationContent = L.DomUtil.create('div', undefined, popup); |
| | elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`; |
| | if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) { |
| | const heightContent = L.DomUtil.create('div', undefined, popup); |
| | heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`; |
| | } |
| | } |
| | |
| | function markerIsOnLayer(marker, layer) { |
| | if (!layer) { |
| | return true; |
| | } |
| | var top = marker.options.top || marker.options.position.y; |
| | var bottom = marker.options.bottom || marker.options.position.y; |
| | for (const extent of layer.options.extents) { |
| | if (top >= extent.height[0] && bottom < extent.height[1]) { |
| | let containedType = 'partial'; |
| | if (bottom >= extent.height[0] && top <= extent.height[1]) { |
| | containedType = 'full'; |
| | } |
| | if (extent.bounds) { |
| | for (const boundsArray of extent.bounds) { |
| | const bounds = getBounds(boundsArray); |
| | if (bounds.contains(pos(marker.options.position))) { |
| | return containedType; |
| | } |
| | } |
| | } else { |
| | return containedType; |
| | } |
| | } |
| | } |
| | return false; |
| | } |
| | |
| | function markerIsOnActiveLayer(marker) { |
| | if (!marker.options.position) { |
| | return true; |
| | } |
| | const map = marker._map; |
| | |
| | const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay); |
| | for (const layer of overlays) { |
| | for (const extent of layer.options.extents) { |
| | if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) { |
| | return false; |
| | } |
| | } |
| | } |
| | |
| | const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay); |
| | if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) { |
| | return true; |
| | } |
| | |
| | const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !l.options?.overlay); |
| | if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) { |
| | return true; |
| | } |
| | return false; |
| | } |
| | |
| | function checkMarkerForActiveLayers(event) { |
| | const marker = event.target || event; |
| | const outline = marker.options.outline; |
| | const onLevel = markerIsOnActiveLayer(marker); |
| | if (onLevel) { |
| | marker._icon?.classList.remove('off-level'); |
| | if (outline) { |
| | outline._path?.classList.remove('off-level'); |
| | } |
| | } else { |
| | marker._icon?.classList.add('off-level'); |
| | if (outline) { |
| | outline._path?.classList.add('off-level'); |
| | } |
| | } |
| | } |
| | |
| | function activateMarkerLayer(event) { |
| | const marker = event.target || event; |
| | if (markerIsOnActiveLayer(marker)) { |
| | return; |
| | } |
| | const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay); |
| | for (const layer of activeLayers) { |
| | layer.removeFrom(marker._map); |
| | } |
| | const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer); |
| | for (const layer of heightLayers) { |
| | if (markerIsOnLayer(marker, layer)) { |
| | layer.addTo(marker._map); |
| | break; |
| | } |
| | } |
| | } |
| | |
| | const getALink = (path, contents) => { |
| | const a = L.DomUtil.create('a'); |
| | a.setAttribute('href', path); |
| | a.setAttribute('target', '_blank'); |
| | a.append(contents); |
| | return a; |
| | }; |
| | |
| | function getScaledBounds(bounds, scaleFactor) { |
| | |
| | const centerX = (bounds[0][0] + bounds[1][0]) / 2; |
| | const centerY = (bounds[0][1] + bounds[1][1]) / 2; |
| | |
| | const width = bounds[1][0] - bounds[0][0]; |
| | const height = bounds[1][1] - bounds[0][1]; |
| | const newWidth = width * scaleFactor; |
| | const newHeight = height * scaleFactor; |
| | |
| | const newBounds = [ |
| | [centerY - newHeight / 2, centerX - newWidth / 2], |
| | [centerY + newHeight / 2, centerX + newWidth / 2] |
| | ]; |
| | return newBounds; |
| | } |
| | |
| | |
| | function initializeMap() { |
| | if (!mapData) { |
| | console.error("Keine Kartendaten verfügbar"); |
| | return; |
| | } |
| | |
| | |
| | const map = L.map('map', { |
| | maxBounds: getScaledBounds(mapData.svgBounds || mapData.bounds, 1.5), |
| | center: [0, 0], |
| | zoom: 2, |
| | minZoom: mapData.minZoom || 1, |
| | maxZoom: mapData.maxZoom || 6, |
| | zoomSnap: 0.1, |
| | scrollWheelZoom: true, |
| | wheelPxPerZoomLevel: 120, |
| | crs: getCRS(mapData.transform), |
| | attributionControl: false, |
| | id: `${mapData.name.replace(/\s+/g, '')}Map`, |
| | }); |
| | |
| | window.map = map; |
| | |
| | const layerControl = L.control.groupedLayers(null, null, { |
| | position: 'topleft', |
| | collapsed: true, |
| | groupCheckboxes: true, |
| | groupsCollapsable: true, |
| | exclusiveOptionalGroups: ['Levels'], |
| | }).addTo(map); |
| | |
| | |
| | |
| | |
| | |
| | |
| | const bounds = mapData.bounds; |
| | const getBounds = (bounds) => { |
| | if (!bounds) return undefined; |
| | return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]); |
| | }; |
| | |
| | const overlay = L.imageOverlay(mapData.svgPath, getBounds(bounds)); |
| | overlay.addTo(map); |
| | |
| | |
| | |
| | } |
| | |
| | |
| | function createCustomMarkerIcon(markercolor) { |
| | let markerColor; |
| | if (markercolor) { |
| | markerColor = '#' + markercolor; |
| | } else { |
| | markerColor = 'currentColor'; |
| | } |
| | const svgString = ` |
| | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg"> |
| | <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"/> |
| | </svg> |
| | `; |
| | |
| | return L.divIcon({ |
| | html: svgString, |
| | className: 'custom-marker', |
| | iconSize: [32, 32], |
| | iconAnchor: [16, 32], |
| | popupAnchor: [0, -32] |
| | }); |
| | } |
| | |
| | |
| | |
| | function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) { |
| | if (!window.map) { |
| | console.error("Karte nicht initialisiert"); |
| | return; |
| | } |
| | |
| | |
| | const position = { |
| | x: parseFloat(x), |
| | y: parseFloat(y), |
| | z: parseFloat(z) |
| | }; |
| | |
| | const getBounds = (bounds) => { |
| | if (!bounds) return undefined; |
| | return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]); |
| | }; |
| | |
| | const positionIsInBounds = (position) => { |
| | return getBounds(mapData.bounds).contains(pos(position)); |
| | }; |
| | |
| | if (!positionIsInBounds(position)) { |
| | console.error("Position außerhalb der Karte:", position); |
| | return; |
| | } |
| | |
| | |
| | const markerIcon = createCustomMarkerIcon(markercolor); |
| | const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-"); |
| | |
| | |
| | if (playerMarkers[playerMarkerName]) { |
| | window.map.removeLayer(playerMarkers[playerMarkerName]); |
| | } |
| | |
| | |
| | playerMarkers[playerMarkerName] = L.marker(pos(position), { |
| | icon: markerIcon, |
| | position: position, |
| | title: `Koordinaten: ${x}, ${y}, ${z}`, |
| | riseOnHover: true, |
| | zIndexOffset: 400 |
| | }); |
| | |
| | |
| | const popupContent = ` |
| | <div class="marker-popup"> |
| | ${playername ? `<strong>Player: ${playername}</strong>` : ''} |
| | <strong>Koordinaten:</strong> |
| | <span>X: ${x} Y: ${y} Z: ${z}</span> |
| | ${preview ? `<img class="preview-image" src="${preview}" style="max-width: 100%; height: auto; margin-top: 5px;"><br>` : ''} |
| | <small>${timestamp}</small> |
| | </div> |
| | `; |
| | |
| | playerMarkers[playerMarkerName].bindPopup(popupContent, { |
| | maxWidth: 250, |
| | minWidth: 150, |
| | autoClose: true, |
| | closeOnClick: true |
| | }); |
| | |
| | |
| | playerMarkers[playerMarkerName].addTo(window.map); |
| | |
| | |
| | window.map.setView(pos(position), window.map.getZoom(), { |
| | animate: true, |
| | duration: 0.5 |
| | }); |
| | |
| | console.log("Neuer Marker gesetzt für " + playername + ": " + position); |
| | } |
| | |
| | |
| | document.addEventListener('DOMContentLoaded', function () { |
| | loadMapData(); |
| | loadLocalData(); |
| | }); |
| | </script> |
| | </body> |
| |
|
| | </html> |