/** * plugin-markers.js – Ortsmarker auf der Weltkarte * ==================================================== * Abhängigkeit: earth-core.js * * Erstellt: #markersCanvas (z-index 8) * * Jeden Marker in der MARKERS-Liste eintragen. * Unterstützt: Icon-PNG, Fallback-Punkt, Label, Klick-Popup. */ (function () { const userLocation = window.GeocronUserLocation; const userLocationMarker = userLocation && Number.isFinite(Number(userLocation.latitude)) && Number.isFinite(Number(userLocation.longitude)) ? { name: userLocation.name || "Dein Standort", lat: Number(userLocation.latitude), lon: Number(userLocation.longitude), color: "rgba(255, 215, 90, 0.98)", glow: "rgba(255, 215, 90, 0.75)", radius: 6, showLabel: true, popup: `${userLocation.name || "Dein Standort"}\n${Number(userLocation.latitude).toFixed(4)}° / ${Number(userLocation.longitude).toFixed(4)}°` + (userLocation.elevation !== null && userLocation.elevation !== undefined ? `\n${Number(userLocation.elevation).toFixed(0)} m` : ""), } : null; // ═══════════════════════════════════════════════════════════════ // MARKER-LISTE – hier eintragen // ═══════════════════════════════════════════════════════════════ const MARKERS = [ ...(userLocationMarker ? [userLocationMarker] : []), // ═══════════════════════════════════════════════════════════ // EIGENE STANDORTE // ═══════════════════════════════════════════════════════════ { name: "Astronomiemuseum", lat: 50.377222, lon: 11.189722, icon: "../images/logoAM.png", iconW: 67, iconH: 45, showLabel: false, popup: "Astronomiemuseum\nSternwarte Sonneberg", }, { name: "Antipode Astronomiemuseum", lat: -50.377222, lon: -168.810278, color: "rgba(255, 100, 180, 0.95)", radius: 5, showLabel: true, popup: "Antipode des Astronomiemuseums\nGenau gegenüber auf der Erde\n50.38°S / 168.81°W\nSüdpazifik – ca. 800 km südöstlich von Neuseeland", }, // ═══════════════════════════════════════════════════════════ // GEOGRAFISCHE BESONDERHEITEN // ═══════════════════════════════════════════════════════════ { name: "Null Island", lat: 0, lon: 0, color: "rgba(255, 255, 100, 0.95)", radius: 5, showLabel: true, popup: "Null Island (0°/0°)\nSchnittpunkt Äquator & Nullmeridian\nHier liegt eine echte GPS-Referenzboje", }, { name: "Nullmeridian – Greenwich", lat: 51.4769, lon: -0.0005, color: "rgba(100, 200, 255, 0.95)", radius: 5, showLabel: true, popup: "Royal Observatory Greenwich\nDefiniert seit 1884 den Nullmeridian (0° Länge)\nLondon, England", }, { name: "Pol der Unzugänglichkeit", lat: -48.873, lon: -123.393, color: "rgba(200, 100, 255, 0.95)", radius: 5, showLabel: true, popup: "Pol der Unzugänglichkeit (Pazifik)\nAm weitesten von jeder Küste entfernt\n2.688 km bis zur nächsten Landmasse", }, { name: "Erdmittelpunkt der Landmasse", lat: 39.0, lon: 34.0, color: "rgba(255, 160, 60, 0.95)", radius: 5, showLabel: true, popup: "Erdmittelpunkt der Landmasse\nGeografisches Zentrum aller Kontinente\nTürkei", }, { name: "Magnetischer Nordpol", lat: 86.5, lon: -150.0, color: "rgba(100, 255, 200, 0.95)", radius: 5, showLabel: true, popup: "Magnetischer Nordpol (ca. 2024)\nWandert jährlich ~50 km Richtung Sibirien\nPosition ändert sich kontinuierlich", }, { name: "Magnetischer Südpol", lat: -64.1, lon: 136.0, color: "rgba(100, 255, 200, 0.95)", radius: 5, showLabel: true, popup: "Magnetischer Südpol (ca. 2024)\nDerzeit nahe der Küste der Antarktis\nPosition ändert sich kontinuierlich", }, // ═══════════════════════════════════════════════════════════ // HISTORISCHE OBSERVATORIEN // ═══════════════════════════════════════════════════════════ { name: "Sternwarte Paris", lat: 48.8365, lon: 2.3363, color: "rgba(180, 220, 255, 0.95)", radius: 5, showLabel: true, popup: "Observatoire de Paris\nDefinierte bis 1884 den Nullmeridian\nGegründet 1667 – älteste Staatssternwarte", }, { name: "Uraniborg", lat: 55.9, lon: 12.7, color: "rgba(180, 220, 255, 0.95)", radius: 5, showLabel: true, popup: "Uraniborg – Tycho Brahes Observatorium\nVen, Dänemark (heute Schweden)\nErbaut 1576 – präziseste vorteleskopische Messungen", }, { name: "Jantar Mantar Jaipur", lat: 26.9245, lon: 75.8236, color: "rgba(255, 200, 100, 0.95)", radius: 5, showLabel: true, popup: "Jantar Mantar – Jaipur, Indien\nGrößte steinerne Sonnenuhren der Welt\nErbaut 1724 – UNESCO Weltkulturerbe", }, { name: "Göbekli Tepe", lat: 37.2232, lon: 38.9224, color: "rgba(255, 200, 100, 0.95)", radius: 5, showLabel: true, popup: "Göbekli Tepe – Türkei\nÄlteste bekannte astronomische Anlage\nCa. 10.000 v. Chr. – vor Stonehenge & den Pyramiden", }, // ═══════════════════════════════════════════════════════════ // MODERNE TELESKOPE & OBSERVATORIEN // ═══════════════════════════════════════════════════════════ { name: "VLT – Very Large Telescope", lat: -24.6275, lon: -70.4044, color: "rgba(120, 200, 255, 0.95)", radius: 6, showLabel: true, popup: "VLT – Very Large Telescope (ESO)\nCerro Paranal, Atacama-Wüste, Chile\n4 Teleskope à 8,2 m Spiegel\nEins der leistungsfähigsten optischen Observatorien der Welt", }, { name: "Mauna Kea", lat: 19.8228, lon: -155.4683, color: "rgba(120, 200, 255, 0.95)", radius: 6, showLabel: true, popup: "Mauna Kea Observatory – Hawaii, USA\nGrößte Teleskopkonzentration der Welt\n4.205 m Höhe – ideale Beobachtungsbedingungen\nHeimat von Keck, Subaru, UKIRT u.v.m.", }, { name: "Atacama (ALMA)", lat: -23.0229, lon: -67.7540, color: "rgba(120, 200, 255, 0.95)", radius: 5, showLabel: true, popup: "ALMA – Atacama Large Millimeter Array\nChile, 5.058 m Höhe\n66 Radioteleskop-Antennen\nBeobachtet die kühlsten Objekte des Universums", }, { name: "Arecibo (Standort)", lat: 18.3463, lon: -66.7528, color: "rgba(180, 180, 180, 0.85)", radius: 5, showLabel: true, popup: "Arecibo Observatory – Puerto Rico\n305 m Radioteleskop (1963–2020)\nKollabiert im Dez. 2020\nWar 53 Jahre lang das größte Radioteleskop der Welt", }, { name: "FAST Radioteleskop", lat: 25.6531, lon: 106.8566, color: "rgba(120, 200, 255, 0.95)", radius: 5, showLabel: true, popup: "FAST – Five-hundred-meter Aperture\nSpherical Telescope, Guizhou, China\n500 m Durchmesser – größtes Radioteleskop der Welt\nIn Betrieb seit 2016", }, // ═══════════════════════════════════════════════════════════ // METEORITEN & EINSCHLAGKRATER // ═══════════════════════════════════════════════════════════ { name: "Chicxulub-Krater", lat: 21.4, lon: -89.5, color: "rgba(255, 80, 50, 0.95)", radius: 8, showLabel: true, popup: "Chicxulub-Krater – Yucatán, Mexiko\n~180 km Durchmesser\nEinschlag vor 66 Mio. Jahren\nAuslöser des Massen­aussterbens der Dinosaurier\nGrößter bekannter Einschlag der Erdgeschichte", }, { name: "Vredefort-Krater", lat: -27.0, lon: 27.5, color: "rgba(255, 80, 50, 0.95)", radius: 7, showLabel: true, popup: "Vredefort-Krater – Südafrika\n~300 km Durchmesser (ursprünglich)\nÄltester bekannter Einschlagkrater: vor 2,02 Mrd. Jahren\nUNESCO Weltkulturerbe", }, { name: "Sudbury-Krater", lat: 46.6, lon: -81.2, color: "rgba(255, 80, 50, 0.95)", radius: 7, showLabel: true, popup: "Sudbury Basin – Ontario, Kanada\n~130 km Durchmesser\nEinschlag vor 1,85 Mrd. Jahren\nEiner der größten und ältesten Krater\nHeute bedeutende Nickel- und Kupfermine", }, { name: "Popigai-Krater", lat: 71.65, lon: 111.0, color: "rgba(255, 80, 50, 0.95)", radius: 6, showLabel: true, popup: "Popigai-Krater – Sibirien, Russland\n~100 km Durchmesser\nEinschlag vor 35,7 Mio. Jahren\nEnthält Billionen Karat Impact-Diamanten\nViertgrößter bekannter Krater", }, { name: "Manicouagan-Krater", lat: 51.37, lon: -68.7, color: "rgba(255, 80, 50, 0.95)", radius: 6, showLabel: true, popup: "Manicouagan-Krater – Québec, Kanada\n~100 km Durchmesser\nEinschlag vor 215 Mio. Jahren\nRing-See gut sichtbar auf Satellitenbildern\n'Auge Quebecs'", }, { name: "Acraman-Krater", lat: -32.0, lon: 135.45, color: "rgba(255, 80, 50, 0.95)", radius: 6, showLabel: true, popup: "Acraman-Krater – Südaustralien\n~90 km Durchmesser\nEinschlag vor 580 Mio. Jahren\nHeute ein ausgetrockneter Salzsee", }, { name: "Tunguska", lat: 60.917, lon: 101.95, color: "rgba(255, 160, 50, 0.95)", radius: 5, showLabel: true, popup: "Tunguska-Ereignis – Sibirien, Russland\n30. Juni 1908\nLuftexplosion eines ~50 m Asteroiden\nStärke ~1.000 Hiroshima-Bomben\n2.000 km² Wald niedergewalzt\nKein Krater – Objekt verglühte in der Atmosphäre", }, { name: "Meteor Crater (Barringer)", lat: 35.0272, lon: -111.0225, color: "rgba(255, 80, 50, 0.95)", radius: 5, showLabel: true, popup: "Barringer Meteor Crater – Arizona, USA\n1,2 km Durchmesser, 170 m tief\nEinschlag vor ~50.000 Jahren\nBester erhaltener Einschlagkrater der Erde\nEisenmeteorit, ca. 50 m groß", }, { name: "Hoba-Meteorit", lat: -19.5936, lon: 17.9347, color: "rgba(200, 180, 140, 0.95)", radius: 5, showLabel: true, popup: "Hoba-Meteorit – Namibia\nGrößter bekannter Meteorit der Erde\n60 Tonnen, 2,7 × 2,7 m\nEntdeckt 1920 – liegt noch am Fundort\nEisenmeteorit, vor ~80.000 Jahren gefallen", }, { name: "Tscheljabinsk", lat: 55.1644, lon: 61.4168, color: "rgba(255, 160, 50, 0.95)", radius: 5, showLabel: true, popup: "Tscheljabinsk-Meteorit – Russland\n15. Februar 2013\n~20 m großer Asteroid\nLuftexplosion in 30 km Höhe\n~1.500 Verletzte durch Druckwelle\nHellster Meteor seit Tunguska", }, { name: "Nördlingen (Ries-Krater)", lat: 48.8566, lon: 10.6167, color: "rgba(255, 80, 50, 0.95)", radius: 5, showLabel: true, popup: "Nördlinger Ries – Bayern, Deutschland\n~24 km Durchmesser\nEinschlag vor 14,6 Mio. Jahren\nStadtmauer Nördlingen besteht aus Suevit (Impaktgestein)\nKirche St. Georg enthält ~5.000 Karat Mikro-Diamanten", }, { name: "Sikhote-Alin", lat: 46.1, lon: 134.7, color: "rgba(200, 180, 140, 0.95)", radius: 5, showLabel: true, popup: "Sikhote-Alin-Meteorit – Russland\n12. Februar 1947\nEisenmeteorit, ~23 Tonnen (gefundene Fragmente)\nZerbrach in der Atmosphäre in tausende Stücke\nHinterließ ~100 kleine Krater im Primorsky Krai\nGrößter beobachteter Eisenmeteorit-Einschlag", }, { name: "Campo del Cielo", lat: -27.5, lon: -61.7, color: "rgba(200, 180, 140, 0.95)", radius: 5, showLabel: true, popup: "Campo del Cielo – Argentinien\nEinschlag vor ca. 4.000–5.000 Jahren\nGruppe von ~26 Kratern auf 60 km²\nBisher ~100 Tonnen Eisenmeteoriten gefunden\nGróncio: 30,8 Tonnen – zweitgrößter Meteorit der Erde", }, { name: "Siljan-Krater", lat: 61.0, lon: 14.9, color: "rgba(255, 80, 50, 0.95)", radius: 5, showLabel: true, popup: "Siljan-Krater – Schweden\n~52 km Durchmesser\nEinschlag vor 376 Mio. Jahren\nGrößter Einschlagkrater Europas\nHeute malerische Seenlandschaft", }, // ═══════════════════════════════════════════════════════════ { name: "Baikonur", lat: 45.9200, lon: 63.3420, color: "rgba(255, 120, 120, 0.95)", radius: 5, showLabel: true, popup: "Baikonur Kosmodrom – Kasachstan\nÄltester und größter Raumfahrtbahnhof\nErster Satellit (Sputnik 1957)\nErster Mensch im All (Gagarin 1961)", }, { name: "Cape Canaveral", lat: 28.3922, lon: -80.6077, color: "rgba(255, 120, 120, 0.95)", radius: 5, showLabel: true, popup: "Kennedy Space Center – Florida, USA\nNASA-Hauptstartplatz seit 1958\nMond-Missionen Apollo, Space Shuttle\nHeute auch SpaceX Falcon & Starship", }, { name: "Kourou (ESA)", lat: 5.2390, lon: -52.7684, color: "rgba(255, 120, 120, 0.95)", radius: 5, showLabel: true, popup: "Centre Spatial Guyanais – Kourou\nFranzösisch-Guyana, ESA-Startplatz\nIdeal: nur 5° vom Äquator → Erdrotation spart Treibstoff\nHeimstart von Ariane-Raketen", }, { name: "Jiuquan (CNSA)", lat: 40.9605, lon: 100.2913, color: "rgba(255, 120, 120, 0.95)", radius: 5, showLabel: true, popup: "Jiuquan Satellite Launch Center\nGobi-Wüste, China\nErster chinesischer Taikonaut ins All (2003)\nStart der Shenzhou-Missionen", }, // ═══════════════════════════════════════════════════════════ // POLARLICHT-HOTSPOTS // ═══════════════════════════════════════════════════════════ { name: "Tromsø", lat: 69.6496, lon: 18.9560, color: "rgba(80, 255, 160, 0.95)", radius: 5, showLabel: true, popup: "Tromsø – Norwegen\n69°N – mitten im Aurora-Oval\nPolarlicht-Hauptstadt Europas\nBeste Sichtbarkeit: Oktober–März", }, ]; // ═══════════════════════════════════════════════════════════════ // KONFIGURATION – globale Darstellungsoptionen // ═══════════════════════════════════════════════════════════════ const CFG = { // ── Fallback-Punkt (wenn kein Icon angegeben) ────────────── DOT_RADIUS_PX: 5, DOT_COLOR: "rgba(255, 200, 80, 0.95)", DOT_BORDER: "rgba(255, 255, 255, 0.6)", DOT_BORDER_W: 1.2, DOT_GLOW: "rgba(255, 200, 80, 0.6)", DOT_GLOW_BLUR: 6, // ── Label ────────────────────────────────────────────────── LABEL_FONT: "13px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif", LABEL_OFFSET_X: 10, LABEL_OFFSET_Y: -10, LABEL_TEXT: "rgba(255, 255, 255, 0.95)", LABEL_BG: "rgba(0, 0, 0, 0.55)", LABEL_PAD_X: 6, LABEL_PAD_Y: 3, LABEL_RADIUS: 5, // ── Popup ────────────────────────────────────────────────── POPUP_BG: "rgba(10, 15, 30, 0.92)", POPUP_BORDER: "rgba(180, 160, 255, 0.6)", POPUP_TEXT: "#e8f0ff", POPUP_FONT: "13px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif", }; // ═══════════════════════════════════════════════════════════════ // INTERNER STATE // ═══════════════════════════════════════════════════════════════ const { toX, toY, createCanvas, syncCanvas, roundRectPath } = EarthCore; const markersCanvas = createCanvas("markersCanvas", 8, false); // pointerEvents: auto const ctx = markersCanvas.getContext("2d"); let popup = null; // { x, y, text } oder null // ── Icons vorladen ──────────────────────────────────────────── const iconCache = new Map(); // marker-index → HTMLImageElement | null MARKERS.forEach((m, i) => { if (!m.icon) { iconCache.set(i, null); return; } const img = new Image(); img.onload = () => { iconCache.set(i, img); draw(EarthCore.size); }; img.onerror = () => { iconCache.set(i, null); }; img.src = m.icon; }); // ═══════════════════════════════════════════════════════════════ // ZEICHNEN // ═══════════════════════════════════════════════════════════════ function drawMarker(m, i, w, h) { const x = toX(m.lon, w); const y = toY(m.lat, h); const img = iconCache.get(i); if (img && img.complete && img.naturalWidth > 0) { // ── Icon ───────────────────────────────────────────────── const hw = (m.iconW || 32) / 2; const hh = (m.iconH || 32) / 2; ctx.drawImage(img, x - hw, y - hh, m.iconW || 32, m.iconH || 32); // Wrap an Datumsgrenze if (x - hw < 0) ctx.drawImage(img, x - hw + w, y - hh, m.iconW || 32, m.iconH || 32); if (x + hw > w) ctx.drawImage(img, x - hw - w, y - hh, m.iconW || 32, m.iconH || 32); } else { // ── Fallback: farbiger Leuchtpunkt ──────────────────────── const r = m.radius ?? CFG.DOT_RADIUS_PX; const fc = m.color ?? CFG.DOT_COLOR; ctx.save(); ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fillStyle = fc; ctx.shadowColor = m.glow ?? CFG.DOT_GLOW; ctx.shadowBlur = CFG.DOT_GLOW_BLUR; ctx.fill(); ctx.strokeStyle = CFG.DOT_BORDER; ctx.lineWidth = CFG.DOT_BORDER_W; ctx.stroke(); ctx.restore(); } // ── Label ───────────────────────────────────────────────── if (m.showLabel && m.name) drawLabel(x, y, m.name); } function drawLabel(x, y, text) { ctx.save(); ctx.font = CFG.LABEL_FONT; ctx.textBaseline = "middle"; ctx.textAlign = "left"; const tx = x + CFG.LABEL_OFFSET_X; const ty = y + CFG.LABEL_OFFSET_Y; const boxW = ctx.measureText(text).width + CFG.LABEL_PAD_X * 2; const boxH = 13 + CFG.LABEL_PAD_Y * 2; ctx.fillStyle = CFG.LABEL_BG; roundRectPath(ctx, tx, ty - boxH / 2, boxW, boxH, CFG.LABEL_RADIUS); ctx.fill(); ctx.fillStyle = CFG.LABEL_TEXT; ctx.fillText(text, tx + CFG.LABEL_PAD_X, ty); ctx.restore(); } function drawPopup({ x, y, text }) { const lines = text.split("\n"); ctx.save(); ctx.font = CFG.POPUP_FONT; ctx.textBaseline = "top"; const lh = 18, padX = 10, padY = 8; const bw = Math.max(...lines.map(l => ctx.measureText(l).width)) + padX * 2; const bh = lines.length * lh + padY * 2; const w = markersCanvas.width, h = markersCanvas.height; let px = x + 12, py = y - bh / 2; if (px + bw > w - 5) px = x - bw - 12; if (py < 5) py = 5; if (py + bh > h - 5) py = h - bh - 5; roundRectPath(ctx, px, py, bw, bh, 7); ctx.fillStyle = CFG.POPUP_BG; ctx.fill(); ctx.strokeStyle = CFG.POPUP_BORDER; ctx.lineWidth = 1; ctx.stroke(); ctx.fillStyle = CFG.POPUP_TEXT; lines.forEach((line, i) => { if (i === 0) ctx.font = "bold " + CFG.POPUP_FONT; else ctx.font = CFG.POPUP_FONT; ctx.fillText(line, px + padX, py + padY + i * lh); }); ctx.restore(); } function draw({ w, h }) { if (!w || !h) return; syncCanvas(markersCanvas, w, h); ctx.clearRect(0, 0, w, h); MARKERS.forEach((m, i) => drawMarker(m, i, w, h)); if (popup) drawPopup(popup); } // ═══════════════════════════════════════════════════════════════ // INTERAKTION – Klick öffnet Popup // ═══════════════════════════════════════════════════════════════ function hitTest(cx, cy) { const w = markersCanvas.width, h = markersCanvas.height; for (const m of MARKERS) { if (!m.popup) continue; const x = toX(m.lon, w); const y = toY(m.lat, h); const hw = (m.iconW ?? CFG.DOT_RADIUS_PX * 2) / 2 + 4; const hh = (m.iconH ?? CFG.DOT_RADIUS_PX * 2) / 2 + 4; if (Math.abs(cx - x) <= hw && Math.abs(cy - y) <= hh) return { m, x, y }; } return null; } markersCanvas.addEventListener("click", e => { const rect = markersCanvas.getBoundingClientRect(); const scaleX = markersCanvas.width / rect.width; const scaleY = markersCanvas.height / rect.height; const mx = (e.clientX - rect.left) * scaleX; const my = (e.clientY - rect.top) * scaleY; const hit = hitTest(mx, my); popup = hit ? { x: mx, y: my, text: hit.m.popup } : null; draw(EarthCore.size); }); markersCanvas.addEventListener("mousemove", e => { const rect = markersCanvas.getBoundingClientRect(); const scaleX = markersCanvas.width / rect.width; const scaleY = markersCanvas.height / rect.height; const mx = (e.clientX - rect.left) * scaleX; const my = (e.clientY - rect.top) * scaleY; markersCanvas.style.cursor = hitTest(mx, my) ? "pointer" : "default"; }); // ═══════════════════════════════════════════════════════════════ // EVENTS // ═══════════════════════════════════════════════════════════════ window.addEventListener("earth:slow", e => draw(e.detail)); window.addEventListener("earth:resize", e => draw(e.detail)); }());