Загрузка данных
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Звёздный глобус Ø1000 мм — 24 лепестка — J2000</title>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
background: #eeeeee;
color: #111;
}
.panel {
max-width: 1000px;
margin: 0 auto 20px;
padding: 20px;
background: white;
border-radius: 8px;
box-sizing: border-box;
}
h1,
h2 {
margin-top: 0;
}
button {
padding: 12px 18px;
margin: 5px;
border: 1px solid #333;
background: white;
border-radius: 5px;
cursor: pointer;
font-size: 15px;
}
button:hover {
background: #eeeeee;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#status {
margin-top: 15px;
padding: 12px;
background: #f5f5f5;
font-family: monospace;
white-space: pre-line;
border-radius: 4px;
}
#preview {
overflow: auto;
text-align: center;
}
#preview svg {
width: 100%;
max-width: 500px;
height: auto;
background: white;
border: 1px solid #aaa;
}
.small {
font-size: 13px;
color: #555;
line-height: 1.5;
}
.stats {
margin-top: 15px;
font-size: 14px;
color: #333;
}
</style>
</head>
<body>
<div class="panel">
<h1>Звёздный глобус Ø1000 мм</h1>
<p>
<b>24 лепестка</b> ·
<b>J2000</b> ·
звёзды до <b>5.0m</b>
</p>
<p class="small">
Чёрные звёзды на белом фоне.
Каждый лепесток охватывает 15° прямого восхождения.
SVG имеют физические размеры в миллиметрах.
Обводка лепестка — серая, 0,2 мм.
При печати необходимо использовать масштаб 100%.
Звёзды ярче 4.0m подписываются.
В лепестке №1 дополнительно выделены 5 самых ярких
звёзд северной части.
</p>
<button
id="downloadAllButton"
onclick="downloadAll()"
disabled>
Скачать 24 SVG
</button>
<button
id="downloadZipButton"
onclick="downloadZIP()"
disabled>
Скачать ZIP
</button>
<div id="status">
Загрузка каталога...
</div>
<div id="stats" class="stats"></div>
</div>
<div class="panel">
<h2>Предпросмотр лепестка №1</h2>
<div id="preview"></div>
</div>
<script>
/* =========================================================
ОСНОВНЫЕ ПАРАМЕТРЫ
========================================================= */
const DIAMETER_MM = 1000;
const RADIUS_MM =
DIAMETER_MM / 2;
const NUMBER_OF_GORES = 24;
const GORE_DEGREES =
360 / NUMBER_OF_GORES;
const MAX_MAGNITUDE = 5.0;
/*
* Порог для подписей.
*
* Все звёзды ярче 4.0m
* получают подпись.
*/
const LABEL_MAGNITUDE = 4.0;
/*
* Количество особо выделенных
* звёзд в верхней части
* первого лепестка.
*/
const HIGHLIGHTED_COUNT = 5;
/*
* Технический отступ.
*
* Клапанов нет.
*/
const MARGIN_MM = 12;
/*
* Обводка лепестка.
*/
const GORE_STROKE = "#999999";
const GORE_STROKE_WIDTH = 0.2;
/*
* Каталог Yale Bright Star Catalog.
*
* J2000 координаты.
*/
const CATALOG_URL =
"https://brettonw.github.io/YaleBrightStarCatalog/bsc5.json";
let stars = [];
let catalogLoaded = false;
/*
* Особо выделенные 5 звёзд
* первого лепестка.
*/
let highlightedStars = [];
let highlightedSet =
new Set();
/* =========================================================
ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
========================================================= */
function degToRad(deg) {
return deg * Math.PI / 180;
}
function radToDeg(rad) {
return rad * 180 / Math.PI;
}
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
function fmt(n) {
return Number(
Number(n).toFixed(3)
);
}
function setStatus(text) {
const element =
document.getElementById("status");
element.textContent = text;
}
/*
* Экранирование текста для SVG/XML.
*/
function escapeXML(value) {
return String(value ?? "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function normalizeRA(ra) {
let value =
Number(ra) % 360;
if (value < 0) {
value += 360;
}
return value;
}
/*
* Угол в диапазоне [-180, +180).
*/
function signedAngle(deg) {
return (
((deg + 180) % 360 + 360) % 360 - 180
);
}
/* =========================================================
ПАРСИНГ RA
========================================================= */
function parseRA(value) {
if (
value === undefined ||
value === null
) {
return null;
}
if (typeof value === "number") {
if (!Number.isFinite(value)) {
return null;
}
/*
* Числовое RA из каталога
* трактуем как часы,
* если оно находится
* в диапазоне 0...24.
*/
if (
value >= 0 &&
value <= 24
) {
return value * 15;
}
return normalizeRA(value);
}
const text =
String(value)
.trim()
.replace(/h/gi, " ")
.replace(/m/gi, " ")
.replace(/s/gi, " ")
.replace(/:/g, " ")
.replace(/,/g, ".")
.replace(/\s+/g, " ");
const parts =
text
.split(" ")
.filter(Boolean);
if (!parts.length) {
return null;
}
const h =
Number(parts[0]);
const m =
parts.length > 1
? Number(parts[1])
: 0;
const s =
parts.length > 2
? Number(parts[2])
: 0;
if (
!Number.isFinite(h) ||
!Number.isFinite(m) ||
!Number.isFinite(s)
) {
return null;
}
return normalizeRA(
15 * (
h +
m / 60 +
s / 3600
)
);
}
/* =========================================================
ПАРСИНГ DECLINATION
========================================================= */
function parseDec(value) {
if (
value === undefined ||
value === null
) {
return null;
}
if (typeof value === "number") {
if (!Number.isFinite(value)) {
return null;
}
if (
value < -90 ||
value > 90
) {
return null;
}
return value;
}
const text =
String(value)
.trim()
.replace(/°/g, " ")
.replace(/′/g, " ")
.replace(/″/g, " ")
.replace(/:/g, " ")
.replace(/,/g, ".")
.replace(/\s+/g, " ");
if (!text) {
return null;
}
const negative =
text.startsWith("-") ||
text.startsWith("−");
const cleaned =
text.replace(
/^[+\-−]/,
""
);
const parts =
cleaned
.split(" ")
.filter(Boolean);
const d =
Number(parts[0]);
const m =
parts.length > 1
? Number(parts[1])
: 0;
const s =
parts.length > 2
? Number(parts[2])
: 0;
if (
!Number.isFinite(d) ||
!Number.isFinite(m) ||
!Number.isFinite(s)
) {
return null;
}
let dec =
Math.abs(d) +
Math.abs(m) / 60 +
Math.abs(s) / 3600;
if (negative) {
dec = -dec;
}
if (
dec < -90 ||
dec > 90
) {
return null;
}
return dec;
}
/* =========================================================
ПОЛУЧЕНИЕ RA
========================================================= */
function getRA(record) {
if (
record.RA !== undefined &&
record.RA !== null
) {
const ra =
parseRA(record.RA);
if (ra !== null) {
return ra;
}
}
const h =
Number(record.RAh);
const m =
Number(record.RAm);
const s =
Number(record.RAs);
if (
Number.isFinite(h) &&
Number.isFinite(m) &&
Number.isFinite(s)
) {
return normalizeRA(
15 * (
h +
m / 60 +
s / 3600
)
);
}
return null;
}
/* =========================================================
ПОЛУЧЕНИЕ DECLINATION
========================================================= */
function getDec(record) {
if (
record.Dec !== undefined &&
record.Dec !== null
) {
const dec =
parseDec(record.Dec);
if (dec !== null) {
return dec;
}
}
const degrees =
Number(record.DEd);
const minutes =
Number(record.DEm);
const seconds =
Number(record.DEs);
if (
!Number.isFinite(degrees) ||
!Number.isFinite(minutes) ||
!Number.isFinite(seconds)
) {
return null;
}
let dec =
Math.abs(degrees) +
Math.abs(minutes) / 60 +
Math.abs(seconds) / 3600;
const sign =
record["DE-"];
if (
sign !== undefined &&
sign !== null
) {
const value =
String(sign)
.trim()
.toUpperCase();
if (
value === "-" ||
value === "−" ||
value === "S"
) {
dec = -dec;
}
}
else if (degrees < 0) {
dec = -dec;
}
return dec;
}
/* =========================================================
НАЗВАНИЕ ЗВЕЗДЫ
========================================================= */
function getStarName(star) {
/*
* В первую очередь Common.
*/
if (
star.name !== null &&
star.name !== undefined &&
String(star.name).trim() !== ""
) {
return String(star.name).trim();
}
/*
* Если Common отсутствует,
* используем HR.
*/
if (
star.hr !== null &&
star.hr !== undefined &&
String(star.hr).trim() !== ""
) {
return "HR " + String(star.hr).trim();
}
/*
* Последний резервный вариант.
*/
return "Star";
}
/* =========================================================
ЗАГРУЗКА КАТАЛОГА
========================================================= */
async function loadCatalog() {
if (catalogLoaded) {
return true;
}
setStatus(
"Загрузка Yale Bright Star Catalog..."
);
try {
const response =
await fetch(
CATALOG_URL,
{
cache: "no-cache"
}
);
if (!response.ok) {
throw new Error(
"HTTP " +
response.status +
" " +
response.statusText
);
}
const data =
await response.json();
if (!Array.isArray(data)) {
throw new Error(
"Каталог имеет неожиданный формат: " +
"ожидался JSON-массив."
);
}
const result = [];
for (const record of data) {
const mag =
Number(record.Vmag);
if (!Number.isFinite(mag)) {
continue;
}
if (
mag > MAX_MAGNITUDE
) {
continue;
}
const ra =
getRA(record);
if (ra === null) {
continue;
}
const dec =
getDec(record);
if (dec === null) {
continue;
}
if (
ra < 0 ||
ra >= 360 ||
dec < -90 ||
dec > 90
) {
continue;
}
result.push({
ra: ra,
dec: dec,
mag: mag,
hr:
record.HR ??
null,
name:
record.Common ??
null
});
}
stars = result;
if (!stars.length) {
throw new Error(
"После обработки каталога " +
"не осталось звёзд."
);
}
catalogLoaded = true;
/*
* Определяем 5 самых ярких
* звёзд верхней части
* первого лепестка.
*/
prepareHighlightedStars();
setStatus(
"Каталог загружен.\n" +
"Всего записей в JSON: " +
data.length +
"\n" +
"Звёзд с V ≤ " +
MAX_MAGNITUDE.toFixed(1) +
"m: " +
stars.length +
"\n" +
"Подписей V < " +
LABEL_MAGNITUDE.toFixed(1) +
"m: " +
countLabelStars() +
"\n" +
"Выделено звёзд в верхней части GORE 1: " +
highlightedStars.length
);
updateStats();
enableButtons();
renderPreview();
return true;
}
catch (error) {
console.error(
"Ошибка загрузки каталога:",
error
);
setStatus(
"ОШИБКА ЗАГРУЗКИ КАТАЛОГА\n\n" +
error.message +
"\n\n" +
"Если HTML открыт напрямую через file://,\n" +
"запустите его через локальный HTTP-сервер."
);
return false;
}
}
/* =========================================================
ПОДГОТОВКА 5 САМЫХ ЯРКИХ ЗВЁЗД
В ВЕРХНЕЙ ЧАСТИ ПЕРВОГО ЛЕПЕСТКА
========================================================= */
function prepareHighlightedStars() {
/*
* GORE 1:
*
* центр = 0°
*
* диапазон:
* -7.5° ... +7.5°
*
* Верхняя часть:
* положительная declination.
*/
const firstGoreStars =
starsForGore(0);
highlightedStars =
firstGoreStars
.filter(star => {
return star.dec > 0;
})
.sort((a, b) => {
return a.mag - b.mag;
})
.slice(
0,
HIGHLIGHTED_COUNT
);
highlightedSet =
new Set(highlightedStars);
}
/* =========================================================
КОЛИЧЕСТВО ПОДПИСЕЙ
========================================================= */
function countLabelStars() {
return stars.filter(star => {
return star.mag < LABEL_MAGNITUDE;
}).length;
}
/* =========================================================
СТАТИСТИКА
========================================================= */
function updateStats() {
const element =
document.getElementById("stats");
const counts = [];
for (
let i = 0;
i < NUMBER_OF_GORES;
i++
) {
counts.push(
starsForGore(i).length
);
}
element.textContent =
"Распределение звёзд по лепесткам: " +
counts.join(" / ");
}
/* =========================================================
РАЗМЕР ЗВЕЗДЫ
========================================================= */
function starRadius(mag) {
const brightness =
Math.max(
0,
Math.min(
5,
5 - mag
)
);
const originalRadius =
1.0 +
Math.pow(
brightness / 5,
0.65
) * 2.6;
const r =
originalRadius / 6;
return Math.max(
0.15,
Math.min(
0.6,
r
)
);
}
/*
* Размер специально выделенной звезды.
*/
function highlightedStarRadius(mag) {
const brightness =
Math.max(
0,
Math.min(
5,
5 - mag
)
);
const r =
0.9 +
Math.pow(
brightness / 5,
0.65
) * 1.8;
return Math.max(
0.8,
Math.min(
2.2,
r
)
);
}
/* =========================================================
ЦЕНТР ЛЕПЕСТКА
========================================================= */
function goreCenterRA(index) {
return (
index *
GORE_DEGREES
);
}
/* =========================================================
ЗВЁЗДЫ ЛЕПЕСТКА
========================================================= */
function starsForGore(index) {
const center =
goreCenterRA(index);
const half =
GORE_DEGREES / 2;
const EPS =
1e-8;
return stars.filter(star => {
const delta =
signedAngle(
star.ra -
center
);
return (
delta >=
-half - EPS &&
delta <
half - EPS
);
});
}
/* =========================================================
ГЕОМЕТРИЯ ЛЕПЕСТКА
========================================================= */
/*
* Сферическая sinusoidal-gore геометрия.
*
* x = R * cos(phi) * sin(lambda)
*
* y = R * phi
*
* phi = широта
*
* lambda = отклонение от
* центрального меридиана
*/
function projectPoint(
deltaRA,
dec
) {
const lambda =
degToRad(deltaRA);
const phi =
degToRad(dec);
const x =
RADIUS_MM *
Math.cos(phi) *
Math.sin(lambda);
const y =
RADIUS_MM *
phi;
return {
x: x,
y: y
};
}
/* =========================================================
ГРАНИЦА ЛЕПЕСТКА
========================================================= */
function goreBoundary() {
const half =
degToRad(
GORE_DEGREES / 2
);
const points = [];
/*
* Левая граница:
*
* северный полюс →
* южный полюс.
*/
for (
let i = 0;
i <= 720;
i++
) {
const phi =
Math.PI / 2 -
Math.PI * i / 720;
const x =
RADIUS_MM *
Math.cos(phi) *
Math.sin(-half);
const y =
RADIUS_MM *
phi;
points.push({
x: x,
y: y
});
}
/*
* Правая граница:
*
* южный полюс →
* северный полюс.
*/
for (
let i = 720;
i >= 0;
i--
) {
const phi =
Math.PI / 2 -
Math.PI * i / 720;
const x =
RADIUS_MM *
Math.cos(phi) *
Math.sin(half);
const y =
RADIUS_MM *
phi;
points.push({
x: x,
y: y
});
}
return points;
}
/* =========================================================
PATH
========================================================= */
function pointsToPath(points) {
if (!points.length) {
return "";
}
let d =
"M " +
fmt(points[0].x) +
" " +
fmt(points[0].y);
for (
let i = 1;
i < points.length;
i++
) {
d +=
" L " +
fmt(points[i].x) +
" " +
fmt(points[i].y);
}
d += " Z";
return d;
}
/* =========================================================
SVG ОДНОГО ЛЕПЕСТКА
========================================================= */
function createGoreSVG(index) {
const starsHere =
starsForGore(index);
const halfWidth =
RADIUS_MM *
Math.sin(
degToRad(
GORE_DEGREES / 2
)
);
const goreWidth =
halfWidth * 2;
/*
* Клапанов нет.
*/
const totalWidth =
goreWidth +
MARGIN_MM * 2;
const totalHeight =
Math.PI *
RADIUS_MM +
MARGIN_MM * 2;
const viewMinX =
-totalWidth / 2;
const viewMinY =
-totalHeight / 2;
const boundary =
goreBoundary();
const boundaryPath =
pointsToPath(boundary);
let svg = "";
svg += `<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="${fmt(totalWidth)}mm"
height="${fmt(totalHeight)}mm"
viewBox="${fmt(viewMinX)}
${fmt(viewMinY)}
${fmt(totalWidth)}
${fmt(totalHeight)}">
<rect
x="${fmt(viewMinX)}"
y="${fmt(viewMinY)}"
width="${fmt(totalWidth)}"
height="${fmt(totalHeight)}"
fill="white"/>
<g>`;
/*
* ОСНОВНАЯ ПОВЕРХНОСТЬ
*/
svg += `<path
d="${boundaryPath}"
fill="white"
stroke="${GORE_STROKE}"
stroke-width="${GORE_STROKE_WIDTH}"
stroke-linejoin="round"/>`;
/* =====================================================
СНАЧАЛА РИСУЕМ ОБЫЧНЫЕ ЗВЁЗДЫ
===================================================== */
for (
const star of starsHere
) {
/*
* Особо выделенные звёзды
* первого лепестка пропускаем.
*
* Они будут нарисованы
* отдельным блоком ниже.
*/
if (
index === 0 &&
highlightedSet.has(star)
) {
continue;
}
const delta =
signedAngle(
star.ra -
goreCenterRA(index)
);
const point =
projectPoint(
delta,
star.dec
);
const r =
starRadius(
star.mag
);
/*
* Защита от выхода
* за границу лепестка.
*/
if (
Math.abs(point.x) >
halfWidth + 0.001
) {
continue;
}
/*
* ЗВЕЗДА
*/
svg += `<circle
cx="${fmt(point.x)}"
cy="${fmt(point.y)}"
r="${fmt(r)}"
fill="black"/>`;
/* =================================================
ПОДПИСЬ ЗВЁЗДЫ ЯРЧЕ 4.0m
================================================= */
if (
star.mag <
LABEL_MAGNITUDE
) {
const label =
escapeXML(
getStarName(star)
);
/*
* Подпись располагается
* с внешней стороны звезды.
*/
const labelOffsetX =
point.x >= 0
? -3.5
: 3.5;
const anchor =
point.x >= 0
? "end"
: "start";
/*
* Линия-указатель.
*/
svg += `<line
x1="${fmt(point.x)}"
y1="${fmt(point.y)}"
x2="${fmt(
point.x +
labelOffsetX
)}"
y2="${fmt(
point.y - 2
)}"
stroke="#555555"
stroke-width="0.12"/>`;
/*
* Название.
*/
svg += `<text
x="${fmt(
point.x +
labelOffsetX
)}"
y="${fmt(
point.y - 2.5
)}"
text-anchor="${anchor}"
font-family="Arial, sans-serif"
font-size="3.2"
fill="black">
${label}
</text>`;
/*
* Звёздная величина.
*/
svg += `<text
x="${fmt(
point.x +
labelOffsetX
)}"
y="${fmt(
point.y + 1.2
)}"
text-anchor="${anchor}"
font-family="Arial, sans-serif"
font-size="2.2"
fill="#555555">
${star.mag.toFixed(2)}m
</text>`;
}
}
/* =====================================================
5 САМЫХ ЯРКИХ ЗВЁЗД ПЕРВОГО ЛЕПЕСТКА
===================================================== */
if (index === 0) {
for (
let i = 0;
i < highlightedStars.length;
i++
) {
const star =
highlightedStars[i];
const delta =
signedAngle(
star.ra -
goreCenterRA(index)
);
const point =
projectPoint(
delta,
star.dec
);
const r =
highlightedStarRadius(
star.mag
);
if (
Math.abs(point.x) >
halfWidth + 0.001
) {
continue;
}
/*
* Выделенная звезда.
*/
svg += `<circle
cx="${fmt(point.x)}"
cy="${fmt(point.y)}"
r="${fmt(r)}"
fill="black"
stroke="white"
stroke-width="0.35"/>`;
/*
* Название.
*/
const label =
escapeXML(
getStarName(star)
);
/*
* Подпись смещаем
* наружу от центра.
*/
const labelOffsetX =
point.x >= 0
? -5
: 5;
const anchor =
point.x >= 0
? "end"
: "start";
/*
* Линия-указатель.
*/
svg += `<line
x1="${fmt(point.x)}"
y1="${fmt(point.y)}"
x2="${fmt(
point.x +
labelOffsetX
)}"
y2="${fmt(
point.y - 3
)}"
stroke="#333333"
stroke-width="0.15"/>`;
/*
* Крупное название.
*/
svg += `<text
x="${fmt(
point.x +
labelOffsetX
)}"
y="${fmt(
point.y - 3
)}"
text-anchor="${anchor}"
font-family="Arial, sans-serif"
font-size="4"
font-weight="bold"
fill="black">
${label}
</text>`;
/*
* Величина.
*/
svg += `<text
x="${fmt(
point.x +
labelOffsetX
)}"
y="${fmt(
point.y + 2
)}"
text-anchor="${anchor}"
font-family="Arial, sans-serif"
font-size="2.7"
fill="#444444">
${star.mag.toFixed(2)}m
</text>`;
}
}
/* =====================================================
ЭКВАТОР
===================================================== */
svg += `<line
x1="${fmt(-halfWidth)}"
y1="0"
x2="${fmt(halfWidth)}"
y2="0"
stroke="#999999"
stroke-width="0.15"
stroke-dasharray="2,2"/>`;
/* =====================================================
ПОЛЮСА
===================================================== */
const poleY =
Math.PI *
RADIUS_MM /
2;
svg += `<circle
cx="0"
cy="${fmt(-poleY)}"
r="1.5"
fill="none"
stroke="#999999"
stroke-width="0.2"/>
<circle
cx="0"
cy="${fmt(poleY)}"
r="1.5"
fill="none"
stroke="#999999"
stroke-width="0.2"/>`;
/* =====================================================
НОМЕР ЛЕПЕСТКА
===================================================== */
svg += `<text
x="0"
y="${fmt(
-poleY - 5
)}"
text-anchor="middle"
font-family="Arial, sans-serif"
font-size="5"
fill="black">
GORE ${index + 1}
</text>`;
/* =====================================================
RA ДИАПАЗОН
===================================================== */
const raStart =
normalizeRA(
goreCenterRA(index) -
GORE_DEGREES / 2
);
const raEnd =
normalizeRA(
goreCenterRA(index) +
GORE_DEGREES / 2
);
svg += `<text
x="0"
y="${fmt(
poleY + 7
)}"
text-anchor="middle"
font-family="Arial, sans-serif"
font-size="3.5"
fill="black">
RA ${formatRA(raStart)}
— ${formatRA(raEnd)}
</text>`;
/* =====================================================
ТЕХНИЧЕСКАЯ ИНФОРМАЦИЯ
===================================================== */
svg += `<text
x="${fmt(
viewMinX + 3
)}"
y="${fmt(
viewMinY + 5
)}"
font-family="Arial, sans-serif"
font-size="2.5"
fill="black">
Ø1000 mm · J2000 ·
V≤${MAX_MAGNITUDE.toFixed(1)} ·
${starsHere.length} stars
</text>`;
svg += `
</g>
</svg>`;
return svg;
}
/* =========================================================
FORMAT RA
========================================================= */
function formatRA(degrees) {
const hours =
normalizeRA(degrees) / 15;
return (
hours.toFixed(2) +
"h"
);
}
/* =========================================================
PREVIEW
========================================================= */
function renderPreview() {
if (!stars.length) {
return;
}
const svg =
createGoreSVG(0);
const container =
document.getElementById(
"preview"
);
container.innerHTML =
svg;
}
/* =========================================================
АКТИВАЦИЯ КНОПОК
========================================================= */
function enableButtons() {
document.getElementById(
"downloadAllButton"
).disabled = false;
document.getElementById(
"downloadZipButton"
).disabled = false;
}
/* =========================================================
СКАЧАТЬ 24 SVG
========================================================= */
async function downloadAll() {
if (!catalogLoaded) {
const success =
await loadCatalog();
if (!success) {
return;
}
}
setStatus(
"Создание 24 SVG-файлов..."
);
for (
let i = 0;
i < NUMBER_OF_GORES;
i++
) {
const svg =
createGoreSVG(i);
const filename =
"star_globe_" +
String(i + 1)
.padStart(2, "0") +
"_of_24.svg";
downloadText(
svg,
filename
);
setStatus(
"Создание SVG...\n" +
"Лепесток " +
(i + 1) +
" из " +
NUMBER_OF_GORES
);
await sleep(250);
}
setStatus(
"ГОТОВО\n\n" +
"Создано: 24 SVG\n" +
"Диаметр: 1000 мм\n" +
"Лепестков: 24\n" +
"Угол лепестка: 15°\n" +
"Система координат: J2000\n" +
"Предел звёзд: V ≤ " +
MAX_MAGNITUDE.toFixed(1) +
"m\n" +
"Подписаны звёзды: V < " +
LABEL_MAGNITUDE.toFixed(1) +
"m"
);
}
/* =========================================================
СКАЧИВАНИЕ ТЕКСТА
========================================================= */
function downloadText(
text,
filename
) {
const blob =
new Blob(
[text],
{
type:
"image/svg+xml;charset=utf-8"
}
);
const url =
URL.createObjectURL(blob);
const a =
document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(
() => {
URL.revokeObjectURL(url);
},
1000
);
}
/* =========================================================
ЗАГРУЗКА JSZIP
========================================================= */
function loadScript(src) {
return new Promise(
(resolve, reject) => {
if (
typeof JSZip !==
"undefined"
) {
resolve();
return;
}
const script =
document.createElement(
"script"
);
script.src = src;
script.async = true;
script.onload = () => {
resolve();
};
script.onerror = () => {
reject(
new Error(
"Не удалось загрузить JSZip."
)
);
};
document.head.appendChild(
script
);
}
);
}
/* =========================================================
ZIP
========================================================= */
async function downloadZIP() {
if (!catalogLoaded) {
const success =
await loadCatalog();
if (!success) {
return;
}
}
setStatus(
"Загрузка библиотеки ZIP..."
);
try {
await loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"
);
setStatus(
"Создание ZIP-файла..."
);
const zip =
new JSZip();
for (
let i = 0;
i < NUMBER_OF_GORES;
i++
) {
const svg =
createGoreSVG(i);
const filename =
"star_globe_" +
String(i + 1)
.padStart(2, "0") +
"_of_24.svg";
zip.file(
filename,
svg
);
setStatus(
"Создание ZIP...\n" +
"Лепесток " +
(i + 1) +
" из " +
NUMBER_OF_GORES
);
}
const blob =
await zip.generateAsync({
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 6
}
});
const url =
URL.createObjectURL(blob);
const a =
document.createElement("a");
a.href = url;
a.download =
"star_globe_1000mm_J2000_24_gores.zip";
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(
() => {
URL.revokeObjectURL(url);
},
2000
);
setStatus(
"ZIP-ФАЙЛ ГОТОВ\n\n" +
"Файл: " +
"star_globe_1000mm_J2000_24_gores.zip\n" +
"Лепестков: 24\n" +
"Диаметр: 1000 мм\n" +
"J2000\n" +
"V ≤ " +
MAX_MAGNITUDE.toFixed(1) +
"m\n" +
"Подписаны звёзды: V < " +
LABEL_MAGNITUDE.toFixed(1) +
"m"
);
}
catch (error) {
console.error(error);
setStatus(
"ОШИБКА СОЗДАНИЯ ZIP\n\n" +
error.message
);
}
}
/* =========================================================
ЗАПУСК
========================================================= */
loadCatalog();
</script>
</body>
</html>