Загрузка данных
<!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>
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 {
display: block;
background: white;
}
@media screen {
.gore svg {
max-width: 90vw;
height: auto;
}
}
@media print {
body {
background: white;
padding: 0;
}
.controls {
display: none;
}
.gore {
box-shadow: none;
margin: 0;
padding: 0;
page-break-after: always;
}
.gore h2,
.gore button {
display: none;
}
}
</style>
</head>
<body>
<div class="controls">
<h1>Развёртка глобуса Ø1000 мм</h1>
<p>
24 лепестка × 15° долготы.
Диаметр сферы: 1000 мм.
Радиус: 500 мм.
Припусков для склейки нет.
</p>
<p>
Параллели: каждые 5°.
Цвет сетки: #f8f8f8.
</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; // 500 мм
const GORES = 24;
const GORE_DEG = 360 / GORES; // 15°
const DEG = Math.PI / 180;
/* ============================================================
ГЕОМЕТРИЯ ЛЕПЕСТКА
Используется классическая геометрия globe gore.
theta = угловое расстояние от северного полюса.
x = R * theta
Половина ширины лепестка:
y = R * tan(GORE_DEG / 2) * sin(theta)
Поэтому границы:
- сходятся точно в полюсах;
- имеют максимальную ширину на экваторе;
- имеют правильную синусоидальную форму.
При 24 лепестках:
угол одного лепестка = 15°
половина = 7.5°
============================================================ */
const HALF_GORE_RAD = (GORE_DEG / 2) * DEG;
const GORE_HALF_WIDTH =
R * Math.tan(HALF_GORE_RAD);
const GORE_WIDTH_EQUATOR =
2 * GORE_HALF_WIDTH;
const GORE_HEIGHT =
Math.PI * R;
/* ============================================================
ТЕХНИЧЕСКИЙ ЗАПАС SVG
============================================================ */
const MARGIN = 8;
const SVG_WIDTH =
GORE_WIDTH_EQUATOR + 2 * MARGIN;
const SVG_HEIGHT =
GORE_HEIGHT + 2 * MARGIN;
/* ============================================================
ФОРМАТИРОВАНИЕ ЧИСЕЛ
============================================================ */
function f(n) {
return Number(n.toFixed(3));
}
/* ============================================================
ТОЧКА ЛЕПЕСТКА
latDeg:
широта от -90° до +90°
lonOffsetDeg:
отклонение от центрального меридиана.
ВАЖНО:
Для физического gore координата X определяется
расстоянием вдоль меридиана:
x = R * theta
а поперечная координата:
y = R * tan(lambda) * sin(theta)
Для границы lambda = ±7.5°.
============================================================ */
function point(latDeg, lonOffsetDeg) {
const lat = latDeg * DEG;
const lon = lonOffsetDeg * DEG;
// Угловое расстояние от северного полюса
const theta = Math.PI / 2 - lat;
const x = R * theta;
const y =
R *
Math.tan(lon) *
Math.sin(theta);
return {
x: x + MARGIN,
y: y + SVG_WIDTH / 2
};
}
/* ============================================================
ТОЧКА С НОРМАЛЬНЫМ ПОРЯДКОМ КООРДИНАТ
SVG:
X = от северного полюса к южному
Y = поперёк лепестка
============================================================ */
function gorePoint(latDeg, lonOffsetDeg) {
const lat = latDeg * DEG;
const lon = lonOffsetDeg * DEG;
const theta = Math.PI / 2 - lat;
const x = R * theta + MARGIN;
const y =
SVG_WIDTH / 2 +
R * Math.tan(lon) * Math.sin(theta);
return {
x: x,
y: y
};
}
/* ============================================================
ГРАНИЦА ЛЕПЕСТКА
============================================================ */
function makeEdge(offsetDeg) {
const N = 360;
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;
}
/* ============================================================
ПАРАЛЛЕЛЬ
Каждая широта соответствует:
theta = 90° - latitude
На этой широте поперечная ширина лепестка:
y = ± R * tan(7.5°) * sin(theta)
Линия параллели в развёртке получается прямой.
Это соответствует классическому gore-шаблону:
при сборке края соседних лепестков образуют
соответствующую широту сферы.
============================================================ */
function makeParallel(latDeg) {
const left =
gorePoint(latDeg, -GORE_DEG / 2);
const right =
gorePoint(latDeg, GORE_DEG / 2);
return `
M ${f(left.x)} ${f(left.y)}
L ${f(right.x)} ${f(right.y)}
`;
}
/* ============================================================
СОЗДАНИЕ SVG ОДНОГО ЛЕПЕСТКА
============================================================ */
function createGoreSVG(index) {
const leftLon =
-GORE_DEG / 2;
const rightLon =
GORE_DEG / 2;
/* --------------------------------------------------------
КОНТУР
-------------------------------------------------------- */
const N = 360;
let contour = "";
// Левая граница:
// от северного полюса к южному
for (let i = 0; i <= N; i++) {
const lat =
90 - 180 * i / N;
const p =
gorePoint(lat, leftLon);
if (i === 0) {
contour +=
`M ${f(p.x)} ${f(p.y)}`;
} else {
contour +=
` L ${f(p.x)} ${f(p.y)}`;
}
}
// Правая граница:
// от южного полюса обратно к северному
for (let i = N; i >= 0; i--) {
const lat =
90 - 180 * i / N;
const p =
gorePoint(lat, rightLon);
contour +=
` L ${f(p.x)} ${f(p.y)}`;
}
contour += " Z";
/* --------------------------------------------------------
ПАРАЛЛЕЛИ КАЖДЫЕ 5°
-------------------------------------------------------- */
let parallels = "";
for (let lat = -85; lat <= 85; lat += 5) {
const d =
makeParallel(lat);
parallels += `
<path
d="${d}"
fill="none"
stroke="#f8f8f8"
stroke-width="0.35"
vector-effect="non-scaling-stroke"
/>
`;
}
/* --------------------------------------------------------
ЭКВАТОР
Остаётся тем же цветом #f8f8f8,
но немного заметнее.
-------------------------------------------------------- */
const equator =
makeParallel(0);
/* --------------------------------------------------------
МЕРИДИАНЫ ВНУТРИ ЛЕПЕСТКА
Для наглядности добавляем центральный меридиан.
Он является линией сгиба/ориентиром.
0° относительно центра лепестка.
-------------------------------------------------------- */
const centerMeridian =
makeEdge(0);
/* --------------------------------------------------------
ИНФОРМАЦИЯ
-------------------------------------------------------- */
const centerLon =
index * GORE_DEG;
const centerLonNormalized =
((centerLon + 180) % 360) - 180;
const hour =
index + 1;
/* --------------------------------------------------------
SVG
-------------------------------------------------------- */
return `
<svg
id="gore-${hour}"
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="#f8f8f8"
stroke-width="0.45"
vector-effect="non-scaling-stroke"
/>
<!-- =====================================================
ЦЕНТРАЛЬНЫЙ МЕРИДИАН
===================================================== -->
<path
d="${centerMeridian}"
fill="none"
stroke="#eeeeee"
stroke-width="0.35"
vector-effect="non-scaling-stroke"
/>
<!-- =====================================================
КОНТУР ЛЕПЕСТКА
===================================================== -->
<path
d="${contour}"
fill="none"
stroke="#777777"
stroke-width="0.65"
vector-effect="non-scaling-stroke"
/>
</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 + GORE_DEG / 2 + 180) % 360) - 180;
wrapper.innerHTML = `
<h2>
Лепесток ${i + 1} из 24
— 15° долготы
— центральный меридиан
${centerLon.toFixed(1)}°
</h2>
${svg}
<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();
URL.revokeObjectURL(url);
}
/* ============================================================
СКАЧИВАНИЕ ВСЕХ 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();
URL.revokeObjectURL(url);
},
index * 150
);
}
);
}
</script>
</body>
</html>