Загрузка данных


<!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>