Sternhimmel mit Messierbildern versehen
This commit is contained in:
+184
-23
@@ -1140,17 +1140,175 @@ function drawAltazGrid() {
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// FARBE AUS B-V
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
function bvToColor(bv) {
|
||||
if (isNaN(bv) || bv === undefined) return '#ffffff';
|
||||
if (bv < -0.30) return '#9bb0ff';
|
||||
if (bv < -0.10) return '#aabfff';
|
||||
if (bv < 0.00) return '#cad7ff';
|
||||
if (bv < 0.20) return '#f8f7ff';
|
||||
if (bv < 0.40) return '#fff4e8';
|
||||
if (bv < 0.60) return '#ffddb4';
|
||||
if (bv < 0.80) return '#ffcc6f';
|
||||
if (bv < 1.20) return '#ffaa44';
|
||||
return '#ff6622';
|
||||
function clamp01(v) {
|
||||
return Math.max(0, Math.min(1, v));
|
||||
}
|
||||
|
||||
function toHexByte(v) {
|
||||
return Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0');
|
||||
}
|
||||
|
||||
function rgbToHex(r, g, b) {
|
||||
return '#' + toHexByte(r) + toHexByte(g) + toHexByte(b);
|
||||
}
|
||||
|
||||
function hexToRgb(hex) {
|
||||
const value = String(hex || '').trim().replace('#', '');
|
||||
if (!/^[0-9a-f]{6}$/i.test(value)) return { r: 255, g: 255, b: 255 };
|
||||
return {
|
||||
r: parseInt(value.slice(0, 2), 16),
|
||||
g: parseInt(value.slice(2, 4), 16),
|
||||
b: parseInt(value.slice(4, 6), 16),
|
||||
};
|
||||
}
|
||||
|
||||
function colorWithAlpha(hex, alpha) {
|
||||
const rgb = hexToRgb(hex);
|
||||
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${clamp01(alpha)})`;
|
||||
}
|
||||
|
||||
function temperatureToColor(tempK) {
|
||||
const temp = Math.max(1000, Math.min(40000, tempK)) / 100;
|
||||
let r, g, b;
|
||||
|
||||
if (temp <= 66) {
|
||||
r = 255;
|
||||
g = 99.4708025861 * Math.log(temp) - 161.1195681661;
|
||||
b = temp <= 19 ? 0 : 138.5177312231 * Math.log(temp - 10) - 305.0447927307;
|
||||
} else {
|
||||
r = 329.698727446 * Math.pow(temp - 60, -0.1332047592);
|
||||
g = 288.1221695283 * Math.pow(temp - 60, -0.0755148492);
|
||||
b = 255;
|
||||
}
|
||||
|
||||
// Leicht entsättigen, damit die Darstellung natürlicher und weniger "neonhaft" wirkt.
|
||||
const mix = 0.18;
|
||||
r = r * (1 - mix) + 255 * mix;
|
||||
g = g * (1 - mix) + 245 * mix;
|
||||
b = b * (1 - mix) + 235 * mix;
|
||||
|
||||
return rgbToHex(r, g, b);
|
||||
}
|
||||
|
||||
function bvToTemperature(bv) {
|
||||
const x = Math.max(-0.4, Math.min(2.0, Number(bv)));
|
||||
return 4600 * (
|
||||
1 / (0.92 * x + 1.7) +
|
||||
1 / (0.92 * x + 0.62)
|
||||
);
|
||||
}
|
||||
|
||||
function spectralClassToColor(spect) {
|
||||
const code = String(spect || '').trim().toUpperCase();
|
||||
if (!code) return '#ffffff';
|
||||
const type = code[0];
|
||||
|
||||
if (type === 'O') return temperatureToColor(32000);
|
||||
if (type === 'B') return temperatureToColor(18000);
|
||||
if (type === 'A') return temperatureToColor(9000);
|
||||
if (type === 'F') return temperatureToColor(7200);
|
||||
if (type === 'G') return temperatureToColor(5800);
|
||||
if (type === 'K') return temperatureToColor(4500);
|
||||
if (type === 'M') return temperatureToColor(3200);
|
||||
return '#ffffff';
|
||||
}
|
||||
|
||||
function bvToColor(bv, spect) {
|
||||
if (Number.isFinite(Number(bv))) {
|
||||
return temperatureToColor(bvToTemperature(bv));
|
||||
}
|
||||
return spectralClassToColor(spect);
|
||||
}
|
||||
|
||||
const STAR_SPRITE_CACHE = new Map();
|
||||
|
||||
function getStarSprite(radius, color, magnitude) {
|
||||
const quantRadius = Math.max(0.28, Math.round(radius * 8) / 8);
|
||||
const brightFactor = clamp01((2.2 - magnitude) / 4.5);
|
||||
const brightBucket = Math.round(brightFactor * 4);
|
||||
const spikeMode = magnitude < 0.5 ? 2 : (magnitude < 1.4 ? 1 : 0);
|
||||
const key = [quantRadius.toFixed(2), color, brightBucket, spikeMode].join('|');
|
||||
if (STAR_SPRITE_CACHE.has(key)) return STAR_SPRITE_CACHE.get(key);
|
||||
|
||||
const outerHaloRadius = Math.max(quantRadius * 3.4, 1.15);
|
||||
const innerHaloRadius = Math.max(quantRadius * 1.65, 0.72);
|
||||
const coreRadius = Math.max(quantRadius * 0.34, 0.18);
|
||||
const spikeLength = spikeMode ? Math.max(quantRadius * (7 + brightFactor * 8), 4.5) : 0;
|
||||
const pad = Math.ceil(Math.max(outerHaloRadius, spikeLength) + 3);
|
||||
const size = pad * 2;
|
||||
|
||||
const spriteCanvas = document.createElement('canvas');
|
||||
spriteCanvas.width = size;
|
||||
spriteCanvas.height = size;
|
||||
const sctx = spriteCanvas.getContext('2d');
|
||||
const cxSprite = pad;
|
||||
const cySprite = pad;
|
||||
|
||||
const outerHalo = sctx.createRadialGradient(cxSprite, cySprite, 0, cxSprite, cySprite, outerHaloRadius);
|
||||
outerHalo.addColorStop(0.00, colorWithAlpha(color, 0.05 + brightFactor * 0.05));
|
||||
outerHalo.addColorStop(0.20, colorWithAlpha(color, 0.02 + brightFactor * 0.03));
|
||||
outerHalo.addColorStop(0.55, colorWithAlpha(color, 0.006 + brightFactor * 0.012));
|
||||
outerHalo.addColorStop(1.00, 'transparent');
|
||||
sctx.fillStyle = outerHalo;
|
||||
sctx.beginPath();
|
||||
sctx.arc(cxSprite, cySprite, outerHaloRadius, 0, Math.PI * 2);
|
||||
sctx.fill();
|
||||
|
||||
const innerHalo = sctx.createRadialGradient(cxSprite, cySprite, 0, cxSprite, cySprite, innerHaloRadius);
|
||||
innerHalo.addColorStop(0.00, colorWithAlpha(color, 0.20));
|
||||
innerHalo.addColorStop(0.35, colorWithAlpha(color, 0.05 + brightFactor * 0.04));
|
||||
innerHalo.addColorStop(1.00, 'transparent');
|
||||
sctx.fillStyle = innerHalo;
|
||||
sctx.beginPath();
|
||||
sctx.arc(cxSprite, cySprite, innerHaloRadius, 0, Math.PI * 2);
|
||||
sctx.fill();
|
||||
|
||||
if (spikeMode) {
|
||||
const spikeAlpha = 0.02 + brightFactor * 0.07;
|
||||
sctx.strokeStyle = colorWithAlpha(color, spikeAlpha);
|
||||
sctx.lineWidth = 0.55 + brightFactor * 0.35;
|
||||
sctx.lineCap = 'round';
|
||||
sctx.beginPath();
|
||||
sctx.moveTo(cxSprite - spikeLength, cySprite);
|
||||
sctx.lineTo(cxSprite + spikeLength, cySprite);
|
||||
sctx.moveTo(cxSprite, cySprite - spikeLength);
|
||||
sctx.lineTo(cxSprite, cySprite + spikeLength);
|
||||
if (spikeMode === 2) {
|
||||
const diag = spikeLength * 0.72;
|
||||
sctx.moveTo(cxSprite - diag, cySprite - diag);
|
||||
sctx.lineTo(cxSprite + diag, cySprite + diag);
|
||||
sctx.moveTo(cxSprite - diag, cySprite + diag);
|
||||
sctx.lineTo(cxSprite + diag, cySprite - diag);
|
||||
}
|
||||
sctx.stroke();
|
||||
}
|
||||
|
||||
sctx.fillStyle = colorWithAlpha(color, 0.96);
|
||||
sctx.beginPath();
|
||||
sctx.arc(cxSprite, cySprite, quantRadius, 0, Math.PI * 2);
|
||||
sctx.fill();
|
||||
|
||||
sctx.fillStyle = colorWithAlpha('#ffffff', 0.56 + brightFactor * 0.16);
|
||||
sctx.beginPath();
|
||||
sctx.arc(cxSprite, cySprite, coreRadius, 0, Math.PI * 2);
|
||||
sctx.fill();
|
||||
|
||||
const sprite = { canvas: spriteCanvas, pad };
|
||||
STAR_SPRITE_CACHE.set(key, sprite);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
function drawStarSprite(x, y, radius, color, magnitude) {
|
||||
if (magnitude > 3.4) {
|
||||
ctx.fillStyle = colorWithAlpha(color, 0.92);
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, Math.max(radius * 0.72, 0.32), 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
return;
|
||||
}
|
||||
|
||||
const sprite = getStarSprite(radius, color, magnitude);
|
||||
ctx.drawImage(sprite.canvas, x - sprite.pad, y - sprite.pad);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1390,19 +1548,9 @@ function render() {
|
||||
|
||||
const t = (magLimit - s.mag) / (magLimit + 1.5);
|
||||
const radius = Math.max(0.4, t * 3.6);
|
||||
const color = bvToColor(s.ci);
|
||||
const color = bvToColor(s.ci, s.spect);
|
||||
|
||||
if (s.mag < CFG.stars.glowBelowMag) {
|
||||
const glow = createSafeRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, radius * 6);
|
||||
if (glow) {
|
||||
glow.addColorStop(0, color + 'bb'); glow.addColorStop(0.3, color + '33');
|
||||
glow.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath(); ctx.arc(pt.x, pt.y, radius * 6, 0, Math.PI * 2); ctx.fill();
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath(); ctx.arc(pt.x, pt.y, radius, 0, Math.PI * 2); ctx.fill();
|
||||
drawStarSprite(pt.x, pt.y, radius, color, s.mag);
|
||||
|
||||
if (labelStars && s.proper && s.mag < CFG.stars.labelBelowMag) {
|
||||
ctx.fillStyle = CFG.stars.colorLabel;
|
||||
@@ -1928,6 +2076,17 @@ document.getElementById('fileInput').addEventListener('change', function(e) {
|
||||
// TOOLTIP
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
let _mouseDownPos = null;
|
||||
|
||||
function enhanceMessierTooltip(tip, obj) {
|
||||
const image = tip.querySelector('.tt-image');
|
||||
if (!image) return;
|
||||
image.addEventListener('error', function handleError() {
|
||||
const media = tip.querySelector('.tt-media');
|
||||
if (media) media.remove();
|
||||
}, { once: true });
|
||||
image.src = 'images/messier/m' + obj.m + '.png';
|
||||
}
|
||||
|
||||
canvas.addEventListener('mousedown', function(e) {
|
||||
_mouseDownPos = { x: e.clientX, y: e.clientY };
|
||||
}, true);
|
||||
@@ -1998,12 +2157,14 @@ canvas.addEventListener('click', function(e) {
|
||||
tip.innerHTML =
|
||||
'<span class="tt-name ' + ('tt-messier-' + String(obj.type || 'As').toLowerCase()) + '">M' + obj.m +
|
||||
(obj.name ? ' – ' + obj.name : '') + '</span>' +
|
||||
'<div class="tt-media"><img class="tt-image" alt="Messier M' + obj.m + '"></div>' +
|
||||
'Typ: ' + (typeNames[obj.type] || obj.type) + '<br>' +
|
||||
'Mag: ' + obj.mag.toFixed(1) + '<br>' +
|
||||
'Größe: '+ obj.size + '′<br>' +
|
||||
'Alt: ' + obj._alt.toFixed(1) + '°<br>' +
|
||||
'Az: ' + obj._az.toFixed(1) + '°<br>' +
|
||||
'RA: ' + obj.ra.toFixed(4) + ' h Dec: ' + obj.dec.toFixed(3) + '°';
|
||||
enhanceMessierTooltip(tip, obj);
|
||||
tip.className = 'visible'; positionTooltip(e); return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user