Подробные статьи о технологиях, определяющих будущее.

Вся Швейцария в вокселях: геоданные и пайплайн

Как воксельный пайплайн превратил открытые геоданные Швейцарии в мир Minecraft 1:1: тайлы, уровни детализации и стриминг пространственных данных.

Долина в альпийском стиле из вокселей с кубическим ледником и ступенчатыми кубическими пиками.
Один блок — один метр: целая страна, пересобранная в воксели из открытых государственных геоданных.

Где-то на портале открытых данных швейцарского правительства лежит карта высот северной стены Эйгера, настолько детальная, что по ней можно почувствовать камень. Один разработчик скормил подобные файлы пайплайну для вокселей и выпустил результат как играбельный продукт: всю Швейцарию в виде мира Minecraft в масштабе 1:1, где каждый блок — это метр, а Маттерхорн стоит ровно такой же высоты и крутизны, как настоящий. Архив весит 24 ГБ, а бесплатный демо-регион занимает примерно 2 400 км². Инженерия под капотом (тайлинг, уровни детализации, ленивая генерация) — это мастер-класс по работе с огромными пространственными наборами данных, при котором ничего не падает.

Проблема: страна из вокселей не влезет в память

Давайте посчитаем. Площадь Швейцарии — 41 285 км². При одном блоке на квадратный метр получается 41,3 миллиарда поверхностных колонн ещё до того, как вы выкопали хоть одну яму. Дайте каждой колонне скромные 100 блоков глубины (камень, грунт, дно озера), и выйдет четыре триллиона блоков. Даже при одном байте на блок это четыре терабайта сырой геометрии. Однако готовый мир сжимается в загрузку на 24 ГБ, что говорит о важном: почти вся эта геометрия хранится не как геометрия. Она хранится как рецепт.

Вертикаль тоже всё ломает. Ванильный Minecraft ограничивает высоту мира 384 блоками. Швейцария проходит от 193 м над уровнем моря у Лаго-Маджоре до 4 634 м на Дюфуршпитце: перепад 4 441 м, больше чем в одиннадцать раз сверх ванильного лимита, и это ещё без единой шахты. Поэтому миру нужны Cubic Chunks, мод, который заменяет колоночное хранение Minecraft кубами 16×16×16, складываемыми на любую нужную высоту. Этот мод стабильно работает только на Minecraft Java 1.12.2, поэтому проект, выходящий сегодня, зафиксирован на версии игры, которой уже много лет.

Память — третья стена. Построение блоков из исходных данных при первом визите игрока в область требует много вычислений: проект просит минимум 16 ГБ ОЗУ и тестируется на машинах вроде Ryzen 7 7800X3D с 32 ГБ. Любой, кто бился с бюджетами стриминга в играх или ГИС, узнает эту схему. Это та же сортировка, что вы делаете когда видеопамять GPU заканчивается: всё не удержать, поэтому нужно очень хорошо решать, что держать прямо сейчас.

Первая неудачная попытка: запечь весь мир заранее

Очевидный подход — один большой офлайн-процесс: читаем геоданные, пишем файлы регионов Minecraft (формат Anvil хранит мир в регионах по 512×512 блоков), упаковываем результат и отдаём. Дания в 2014 году сделала нечто подобное: Агентство геоданных Дании выпустило всю страну как карту Minecraft. Это был знаковый демо-проект открытых данных, доказавший, что карта размером с страну возможна. Так почему бы не сделать так?

Потому что предварительная запечка несёт структурные издержки, которые растут вместе с амбициями. Каждый блок каждого региона нужно сгенерировать, записать и сжать до того, как кто-то начнёт играть, поэтому каждое обновление данных превращается в полную пересборку и повторную раздачу десятков гигабайт. Каждый игрок скачивает горы ландшафта, который никогда не посетит, а большую часть из 41 000 км² не посетит вообще никто. Хуже всего то, что запечённый мир замораживается на снимке данных в момент публикации. Это артефакт «записал один раз». Он не улучшится, пока вы не перегенерируете и не перераспределите всё, а пользователи будут заново скачивать всю планету.

Вторая неудачная попытка: одна карта высот в одном разрешении

Второй соблазн — нормализация: привести все входные данные к единому глобальному растру с одним разрешением, чтобы в пайплайне был ровно один путь кода. Внутри Швейцарии есть swissALTI3D, высокоразрешённая модель рельефа. За границей — Copernicus GLO-30 DEM, одна точка высоты каждые 30 метров. Понизьте всё до 30 м для единообразия, и северная стена Эйгера превратится в ступенчатую песочницу. Поднимите всё до самого тонкого доступного разрешения, и хранилище с обработкой взорвётся, а приграничные регионы всё равно не будут содержать реальных данных: вы просто апсемплили собственное незнание.

Неоднородность бьёт по семантике так же сильно, как по высотам. swissBUILDINGS3D покрывает Швейцарию, поэтому генератор может поднять упрощённые воксельные здания на реальных контурах, но только внутри страны. Пересеките границу у Женевы, и слоя зданий просто нет, так что пайплайн делает единственный разумный запасной вариант: сажает деревья там, где должны быть дома. Воде нужен свой источник (swissBATHY3D для дна озёр), как и ледникам, лесам и виноградникам. Единого входного файла не существует. В геоданных его никогда и нет.

Урок выходит далеко за пределы Minecraft: разрешение — это метаданные тайла, а не глобальная константа. Пайплайн, который предполагает однородные входные данные, либо растратит детализацию, либо выдумает её, а выдуманная детализация хуже честной грубости, потому что изнутри никто не отличит одно от другого.

Сетка картографических тайлов, проходящих через воронку и превращающихся в собранный воксельный ландшафт.
Главная инверсия: храните данные как тайлы и рецепты, собирайте блоки только там, где кто-то реально появляется.

Что работает на самом деле: тайловый пайплайн с ленивой генерацией

Швейцарский проект работает, потому что переворачивает инстинкт «запечь всё». Вы скачиваете не готовый мир, а пакет мира: предобработанные геоданные плюс собственный генератор. Игра строит блоки в первый раз, когда вы действительно приходите в место. Первый визит в новую область занимает несколько минут возни, после чего это кэшированные чанки, как в любом другом мире Minecraft. Вычисления оплачиваются за визит, а не заранее, и вы никогда не генерируете рельеф, по которому никто не ходит. Тайл — это единица всего: хранения, кэширования, генерации и сбоев.

Тайлинг привязан к LV95, официальной системе координат Швейцарии для геодезии. Каждый тайл фиксирует, какие наборы данных его покрывают и с каким разрешением, а перевод координат съёмки в координаты блоков — это чистое аффинное преобразование. Проект даже публикует формулу, чтобы можно было взять координаты из национального картографического просмотрщика и телепортироваться в нужное место в игре:

def lv95_to_block(east: float, north: float, height: float) -> tuple[int, int, int]:
"""Convert Swiss LV95 survey coordinates to Minecraft block coordinates."""
x = int(east - 2_480_000)   # strip the LV95 easting offset
z = int(1_300_000 - north)  # Minecraft's z axis grows southward
y = int(height)             # one block = one meter above sea level
return x, y, z
# The summit of the Jungfrau, 4,158 m:
print(lv95_to_block(2_640_188, 1_154_072, 4158))
# -> (160188, 4158, 145928)
# x and z match the in-game teleport command exactly; the game just
# drops you a few meters higher so you land on the summit, not in it.

Поверх тайловой сетки генерация — это стек слоёв, вычисляемых по каждой колонне. Базовая высота берётся из swissALTI3D. Покрытие поверхности (вода, лес, дороги, железнодорожные линии, плотины, лавинные барьеры) — из swissTLM3D. Дно озёр — из swissBATHY3D, высоты деревьев — из swissSURFACE3D, контуры ледников — из инвентаризаций GLAMOS, восходящих к 1850 году, а толщина льда — из набора данных ETH Цюрих, поэтому ледник Алеч действительно вырезан во льду, а не нарисован белой текстурой на скале. Луга, осыпи и виноградники — из федеральной карты местообитаний; даже уличные деревья Цюриха взяты из городского кадастра деревьев. Всего около пятнадцати открытых наборов данных, все бесплатны для использования: swisstopo открыл свои данные, включая коммерческое использование, в 2021 году.

Воксельный мир — это не картинка рельефа. Это кэш миллионов мелких решений, которые принял ваш пайплайн, по одному на квадратный метр, и качество мира — это в точности качество этих решений.

Уровень детализации — проблема данных, а не трюк рендеринга

Стандартная дальность прорисовки в Minecraft показывает пару сотен метров. На скриншотах проекта ледник Алеч уходит вдаль на километры. Этот дальний вид («Fernsicht») — отдельное представление с пониженной детализацией, которое игра строит один раз при первом запуске. Это классический LOD, но важно то, где он живёт: он вычисляется из исходных данных на этапе ингестии, а не подгоняется рендерером на лету.

Если вы строили пирамиды картографических тайлов, вы знаете математику. Каждый уровень масштаба вдвое уменьшает разрешение по обеим осям, поэтому полная пирамида стоит 1 + 1/4 + 1/16 + … ≈ 4/3 от базового слоя: 33% наценки на хранение за плавное поведение на любом масштабе. LOD воксельного рельефа — та же пирамида, поставленная вертикально: дальний рельеф — грубый уровень масштаба, ближний — тонкий, а настоящее искусство в выборе схемы ключей, которая позволяет быстро достать нужную ячейку. С теми же вопросами мы сталкивались в компромиссах между геохешами и полноценными пространственными индексами: выбор ключа определяет стоимость поиска на всех последующих этапах.

Моё любимое решение по LOD вообще не визуальное. Бесплатная загрузка — это срез в 2 400 км²: регион Юнгфрау-Алеч под защитой ЮНЕСКО, Эйгер, Мёнх, Юнгфрау и самый большой ледник Альп, работающий по тому же пайплайну, что и платная вся страна. Это канареечный релиз как фича продукта. Он весит 1,1 ГБ вместо 24, не требует регистрации, и если работает на вашей машине, полная карта тоже заработает. Если вы выпускаете большие продукты на данных, берите этот приём без раздумий: ваш бесплатный тариф должен быть репрезентативным срезом самого сложного рельефа, а не урезанной демкой самого простого.

Воксельные горы, переходящие от мелких блоков на переднем плане к крупным грубым блокам в туманной дали.
Дальний рельеф — это просто грубый уровень масштаба той же пирамиды: LOD решается на этапе ингестии, а не рендерером.

Стриминг пространственных тайлов в браузере

Та же дисциплина видна на скромной веб-странице проекта. Его обзорная карта позволяет двигать ползунок, чтобы сравнить воксельный мир с реальным аэрофото, и изображения вообще не приходят с серверов проекта. Браузер загружает тайлы SWISSIMAGE напрямую из собственного сервиса swisstopo, и только когда вы двигаете ползунок. Ленивость, прямое обращение к источнику, дружелюбность к кэшу: основной трафик обслуживает общественная инфраструктура, созданная именно для этого, а счёт за хостинг проекта остаётся почти нулевым.

Адресация тайлов в вебе — давно решённая задача, и её стоит знать досконально, если вы когда-либо работаете с геопространственными данными:

import math
def latlon_to_tile(lat: float, lon: float, zoom: int) -> tuple[int, int]:
"""Standard slippy-map (XYZ) tile addressing used by OSM and most WMTS fronts."""
n = 2 ** zoom
x = int((lon + 180.0) / 360.0 * n)
lat_rad = math.radians(lat)
y = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
return x, y
# At zoom 15, any point on Earth lands in one of 1,073,741,824 tiles.
# Fetch the few that intersect the viewport. Skip the other billion.

Две детали важнее формулы. Во-первых, запрос откладывается до тех пор, пока намерение пользователя не станет очевидным (ползунок действительно сдвинут), и это одновременно оптимизация трафика и вежливость по отношению к бесплатному публичному сервису, работающему на государственные деньги. Во-вторых, атрибуция едет вместе с тайлами: страница указывает SWISSIMAGE, swissBOUNDARIES3D и swissNAMES3D прямо у карты. Когда ваш продукт — производная работа с открытыми данными, указание источника в точке показа — это не украшение. Это лицензия, работающая как задумано.

Что украсть у швейцарского пайплайна

Сначала контраргументы, потому что практики высказали хорошие. Ручные проекты вроде Build the Earth привлекают тысячи волонтёров к моделированию реальных зданий, и никакой автоматический пайплайн не достигает такой детализации: воксельный контур swissBUILDINGS3D — грубое приближение, и если вас интересует архитектура, экспорт только рельефа, возможно, окажется более чистым артефактом. Справедливо. Но охват и детализация прямо связаны: автоматизация даёт 41 285 км² уже сегодня, ручная работа даст прекрасный старый город когда-нибудь. Моя позиция: для продукта на данных выигрывает автоматическая генерация с опубликованной атрибуцией, а кураторские ручные достопримечательности могут надстраиваться там, где это важно.

А атрибуция — та часть, которую все пропускают. Этот проект публикует список известных дефектов: разрывы у границ, где доходит только 30-метровая сетка Copernicus, пятнистые или плоские участки, мосты и дороги, которые неверны или отсутствуют, контроль качества выборочный, а не метр за метром. Этот список делает реальную работу: он превращает отчёты «здесь мир неправильный» в документацию «да, этот набор данных тут заканчивается». Каждый продукт на производных данных должен публиковать матрицу происхождения и список известных ошибок. Те, кто этого не делает, не точнее, они просто менее честны.

  • Тайлите всё. Тайл — ваша единица хранения, кэширования, генерации и сбоев. Монолиты не деградируют изящно.
  • Никогда не понижайте разрешение при ингестии. Храните родное разрешение каждого источника и пересэмплируйте при рендеринге.
  • Генерируйте лениво. Когда мир больше, чем кто-либо может прожить за игровое время, вычисления за визит побеждают предварительную запечку.
  • Стройте пирамиду LOD при ингестии. Заложите около 33% накладных расходов и получите плавное масштабирование на любом расстоянии.
  • Выпускайте канареечный срез. Бесплатный репрезентативный кусок ваших самых сложных данных лучше любой спецификации.
  • Публикуйте происхождение данных и известные дефекты. Это часть продукта, а не извинение за него.

Полная страна стоит 15 CHF со всеми будущими версиями, и только для жителей Швейцарии по юридическим причинам, что для чисто цифрового товара выглядит на удивление забавным ограничением. Но настоящий продукт — не карта. Это демонстрация того, что один человек с открытыми данными, тайловым пайплайном и честной документацией может выпустить то, что превосходит геопространственную инфраструктуру большинства команд. Ваш датасет, скорее всего, не 41 285 км². И отговорок у вас тоже меньше.