Загрузка данных
<!DOCTYPE html><html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Развёртка глобуса Ø1000 мм — 24 лепестка</title><style>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 20px;
background: #eeeeee;
font-family: Arial, sans-serif;
color: #333;
}
h1 {
font-size: 22px;
margin: 0 0 8px;
}
p {
margin: 5px 0;
font-size: 14px;
}
.controls {
position: sticky;
top: 0;
z-index: 10;
background: white;
padding: 15px;
margin: -20px -20px 20px;
border-bottom: 1px solid #ccc;
}
button {
padding: 8px 14px;
margin: 5px 5px 0 0;
border: 1px solid #999;
background: #fafafa;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #e8e8e8;
}
.gore {
background: white;
margin: 20px auto;
padding: 15px;
width: fit-content;
box-shadow: 0 1px 5px #bbb;
}
.gore h2 {
font-size: 15px;
font-weight: normal;
margin: 0 0 8px;
}
.svg-wrapper {
overflow: visible;
}
svg {
display: block;
background: white;
}
/*
* На экране уменьшаем очень высокий лепесток
* только по высоте, сохраняя пропорции.
*
* ВАЖНО:
* max-width больше НЕ используется,
* иначе вертикальный лепесток может отображаться
* некорректно.
*/
@media screen {
.gore svg {
max-height: 85vh;
width: auto;
height: auto;
}
}
@media print {
@page {
size: auto;
margin: 0;
}
body {
background: white;
padding: 0;
margin: 0;
}
.controls {
display: none;
}
.gore {
box-shadow: none;
margin: 0;
padding: 0;
page-break-after: always;
break-after: page;
}
.gore h2,
.gore button {
display: none;
}
.gore svg {
width: auto;
height: auto;
max-width: none;
max-height: none;
}
}
</style></head><body><div class="controls"><h1>Развёртка глобуса Ø1000 мм</h1>
<p>
24 лепестка × 15° долготы.
Диаметр сферы: 1000 мм.
Радиус: 500 мм.
</p>
<p>
Параллели: каждые 5°.
Припусков на склейку нет.
Масштаб SVG: 1:1.
</p>
<button onclick="downloadAll()">
Скачать все 24 SVG
</button>
<button onclick="window.print()">
Печать 1:1
</button>
</div><div id="goreContainer"></div><script>
/* ============================================================
ПАРАМЕТРЫ
============================================================ */
const DIAMETER = 1000;
const R = DIAMETER / 2;
const GORES = 24;
const GORE_DEG = 360 / GORES;
const HALF_GORE_DEG = GORE_DEG / 2;
const DEG = Math.PI / 180;
/*
* Запас вокруг контура.
*/
const MARGIN = 10;
/* ============================================================
ГЕОМЕТРИЯ
============================================================ */
/*
* Длина лепестка от северного полюса
* до южного полюса.
*
* Половина окружности:
*
* π × R
*
* Для R = 500:
*
* 1570.796 мм
*/
const GORE_HEIGHT = Math.PI * R;
/*
* Максимальное расстояние от центрального
* меридиана до края на экваторе.
*
* y = R × tan(7.5°)
*/
const MAX_HALF_WIDTH =
R * Math.tan(HALF_GORE_DEG * DEG);
/*
* Полная максимальная ширина на экваторе.
*/
const GORE_WIDTH =
MAX_HALF_WIDTH * 2;
/*
* Реальный размер SVG.
*/
const SVG_WIDTH =
GORE_WIDTH + MARGIN * 2;
const SVG_HEIGHT =
GORE_HEIGHT + MARGIN * 2;
/* ============================================================
ФОРМАТИРОВАНИЕ
============================================================ */
function f(n) {
return Number(n.toFixed(3));
}
/* ============================================================
ТОЧКА ЛЕПЕСТКА
============================================================ */
/*
* latDeg:
*
* +90 = северный полюс
* 0 = экватор
* -90 = южный полюс
*
* lonOffsetDeg:
*
* 0 = центральный меридиан
* ±7.5 = края лепестка
*
* Координаты SVG:
*
* X = сверху вниз
* Y = слева направо
*/
function gorePoint(latDeg, lonOffsetDeg) {
const lat =
latDeg * DEG;
const lon =
lonOffsetDeg * DEG;
/*
* Угловое расстояние от северного полюса.
*
* Север:
* theta = 0
*
* Экватор:
* theta = π/2
*
* Юг:
* theta = π
*/
const theta =
Math.PI / 2 - lat;
/*
* Расстояние от северного полюса
* по поверхности сферы.
*/
const x =
R * theta;
/*
* Поперечное смещение.
*
* На полюсах sin(theta)=0,
* поэтому оба края сходятся точно в одной точке.
*
* На экваторе sin(theta)=1,
* поэтому ширина максимальна.
*/
const y =
R *
Math.tan(lon) *
Math.sin(theta);
return {
x: x + MARGIN,
y: SVG_WIDTH / 2 + y
};
}
/* ============================================================
КОНТУР
============================================================ */
function makeContour() {
const N = 720;
let d = "";
/*
* Левая граница:
*
* север → юг
*/
for (let i = 0; i <= N; i++) {
const lat =
90 - 180 * i / N;
const p =
gorePoint(
lat,
-HALF_GORE_DEG
);
if (i === 0) {
d =
`M ${f(p.x)} ${f(p.y)}`;
} else {
d +=
` L ${f(p.x)} ${f(p.y)}`;
}
}
/*
* Правая граница:
*
* юг → север
*/
for (let i = N; i >= 0; i--) {
const lat =
90 - 180 * i / N;
const p =
gorePoint(
lat,
HALF_GORE_DEG
);
d +=
` L ${f(p.x)} ${f(p.y)}`;
}
d += " Z";
return d;
}
/* ============================================================
ГРАНИЦА / МЕРИДИАН
============================================================ */
function makeMeridian(offsetDeg) {
const N = 720;
let d = "";
for (let i = 0; i <= N; i++) {
const lat =
90 - 180 * i / N;
const p =
gorePoint(
lat,
offsetDeg
);
if (i === 0) {
d =
`M ${f(p.x)} ${f(p.y)}`;
} else {
d +=
` L ${f(p.x)} ${f(p.y)}`;
}
}
return d;
}
/* ============================================================
ПАРАЛЛЕЛЬ
============================================================ */
function makeParallel(latDeg) {
const left =
gorePoint(
latDeg,
-HALF_GORE_DEG
);
const right =
gorePoint(
latDeg,
HALF_GORE_DEG
);
return `
M ${f(left.x)} ${f(left.y)}
L ${f(right.x)} ${f(right.y)}
`;
}
/* ============================================================
СОЗДАНИЕ SVG
============================================================ */
function createGoreSVG(index) {
const contour =
makeContour();
/*
* Параллели каждые 5°.
*/
let parallels = "";
for (
let lat = -85;
lat <= 85;
lat += 5
) {
parallels += `
<path
d="${makeParallel(lat)}"
fill="none"
stroke="#f0f0f0"
stroke-width="0.35"
/>
`;
}
/*
* Экватор.
*/
const equator =
makeParallel(0);
/*
* Центральный меридиан.
*/
const centerMeridian =
makeMeridian(0);
/*
* Номер лепестка.
*/
const number =
index + 1;
/*
* Центральный меридиан.
*/
const centerLon =
index * GORE_DEG + HALF_GORE_DEG;
/*
* Нормализация долготы:
*
* -180 ... +180
*/
const normalizedLon =
((centerLon + 180) % 360) - 180;
return `
<svg
id="gore-${number}"
xmlns="http://www.w3.org/2000/svg"
width="${f(SVG_WIDTH)}mm"
height="${f(SVG_HEIGHT)}mm"
viewBox="
0
0
${f(SVG_WIDTH)}
${f(SVG_HEIGHT)}
"
>
<!-- =====================================================
БЕЛЫЙ ФОН
===================================================== -->
<rect
x="0"
y="0"
width="${f(SVG_WIDTH)}"
height="${f(SVG_HEIGHT)}"
fill="white"
/>
<!-- =====================================================
ПАРАЛЛЕЛИ КАЖДЫЕ 5°
===================================================== -->
<g>
${parallels}
</g>
<!-- =====================================================
ЭКВАТОР
===================================================== -->
<path
d="${equator}"
fill="none"
stroke="#dddddd"
stroke-width="0.45"
/>
<!-- =====================================================
ЦЕНТРАЛЬНЫЙ МЕРИДИАН
===================================================== -->
<path
d="${centerMeridian}"
fill="none"
stroke="#dddddd"
stroke-width="0.4"
/>
<!-- =====================================================
КОНТУР ЛЕПЕСТКА
===================================================== -->
<path
d="${contour}"
fill="none"
stroke="#555555"
stroke-width="0.65"
/>
</svg>
`;
}
/* ============================================================
ОТОБРАЖЕНИЕ 24 ЛЕПЕСТКОВ
============================================================ */
const container =
document.getElementById(
"goreContainer"
);
const svgStrings = [];
for (
let i = 0;
i < GORES;
i++
) {
const svg =
createGoreSVG(i);
svgStrings.push(svg);
const wrapper =
document.createElement("div");
wrapper.className =
"gore";
const centerLon =
i * GORE_DEG + HALF_GORE_DEG;
const normalizedLon =
((centerLon + 180) % 360) - 180;
wrapper.innerHTML = `
<h2>
Лепесток ${i + 1} из 24
— центральный меридиан
${normalizedLon.toFixed(1)}°
— размер
${SVG_WIDTH.toFixed(1)}
×
${SVG_HEIGHT.toFixed(1)}
мм
</h2>
<div class="svg-wrapper">
${svg}
</div>
<button
onclick="downloadGore(${i})"
>
Скачать SVG
</button>
`;
container.appendChild(wrapper);
}
/* ============================================================
СКАЧАТЬ ОДИН SVG
============================================================ */
function downloadGore(index) {
const blob =
new Blob(
[svgStrings[index]],
{
type:
"image/svg+xml;charset=utf-8"
}
);
const url =
URL.createObjectURL(blob);
const a =
document.createElement("a");
a.href = url;
a.download =
`globe_1000mm_gore_${
String(index + 1).padStart(2, "0")
}.svg`;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(
() => URL.revokeObjectURL(url),
1000
);
}
/* ============================================================
СКАЧАТЬ ВСЕ 24 SVG
============================================================ */
function downloadAll() {
svgStrings.forEach(
(svg, index) => {
setTimeout(
() => {
const blob =
new Blob(
[svg],
{
type:
"image/svg+xml;charset=utf-8"
}
);
const url =
URL.createObjectURL(
blob
);
const a =
document.createElement(
"a"
);
a.href = url;
a.download =
`globe_1000mm_gore_${
String(index + 1).padStart(2, "0")
}.svg`;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(
() =>
URL.revokeObjectURL(url),
1000
);
},
index * 250
);
}
);
}
</script></body>
</html>