588 lines
25 KiB
JavaScript
588 lines
25 KiB
JavaScript
/**
|
||
* 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_sw.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 Massenaussterbens 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));
|
||
|
||
}());
|