Перейти к основному содержимому

Динамическое освещение по времени суток и точке на карте

Освещение в стиле карты (light) — это глобальный параметр: его источники вычисляются один раз на кадр и не зависят от отдельных объектов. Поэтому «солнце над точкой» задаётся не положением объекта, а временем суток, которое вы вычисляете в стилевом выражении. Меняя это время при перемещении карты, можно получить реалистичную картину: в Москве день, а на востоке — вечер, с соответствующим положением теней.

В этой инструкции показано, как собрать такое освещение из двух глобальных стилевых переменных:

  • timeOffsetMinutes — смещение таймзоны относительно UTC (в минутах к востоку) для текущей геоточки;
  • dynamicLightingEnabled — флаг, включающий динамическое освещение.
примечание

Освещение — общий параметр кадра, поэтому «включить разное освещение одновременно для разных точек экрана» нельзя: свет соответствует центру карты. При перемещении карта полностью переключается в локальное время новой точки.

Как это работает​

Минуты суток в точке вычисляются комбинацией трёх выражений:

  • ["time", ["utc-time"], "day-minutes"] — текущие минуты от полуночи по UTC (диапазон 0…1439). Обратите внимание: utc-time обязательно оборачивается в time(...) — без обёртки выражение возвращает метку времени в миллисекундах, а не минуты суток.
  • ["global", "timeOffsetMinutes"] — смещение таймзоны точки, которое вы передаёте из кода карты.

Итоговый ключ времени:

["time", ["utc-time", ["global", "timeOffsetMinutes"]], "day-minutes"]

Знак смещения — минуты к востоку от UTC: Москва (UTC+3) → 180, Екатеринбург (UTC+5) → 300, Якутск (UTC+9) → 540.

Настройка стиля​

Все выражения освещения задаются в объекте light стиля. Ниже — пример для источников sun (направленный, отвечает в том числе за тени) и atmosphere (рассеянный). Возьмите эти фрагменты за основу в Редакторе стилей.

Цвет и интенсивность солнца меняются по времени суток, азимут и высота — положение солнца, которое определяет направление теней:

{
"sun": {
"type": "directional",
"color": ["interpolate", ["linear"], ["zoom"],
16, "#FFFFFF",
17,
["when", ["global", "dynamicLightingEnabled"],
["interpolate", ["linear"],
["time", ["utc-time", ["global", "timeOffsetMinutes"]], "day-minutes"],
0, "#8FA6D8",
360, "#FFB27A",
720, "#FFFFFF",
1080, "#FF9E6E",
1440, "#8FA6D8"
],
"#FFFFFF"
]
],
"azimuth": ["when", ["global", "dynamicLightingEnabled"],
["interpolate", ["linear"],
["time", ["utc-time", ["global", "timeOffsetMinutes"]], "day-minutes"],
0, 0,
360, 90,
720, 180,
1080, 270,
1440, 360
],
230
],
"altitude": ["when", ["global", "dynamicLightingEnabled"],
["interpolate", ["linear"],
["time", ["utc-time", ["global", "timeOffsetMinutes"]], "day-minutes"],
0, 5,
720, 50,
1440, 5
],
50
]
},
"atmosphere": {
"type": "ambient",
"color": ["interpolate", ["linear"], ["zoom"],
16, "#FFFFFF",
17,
["when", ["global", "dynamicLightingEnabled"],
["interpolate", ["linear"],
["time", ["utc-time", ["global", "timeOffsetMinutes"]], "day-minutes"],
0, "#AFC0DE",
360, "#FFD9C0",
720, "#FFFFFF",
1080, "#FFCBB0",
1440, "#AFC0DE"
],
"#FFFFFF"
]
]
}
}

Разбор конструкции:

  • interpolate по ["zoom"] сохраняет текущее поведение на малых зумах и включает динамику только на крупном масштабе (17 и выше). При необходимости уберите внешний interpolate и оставьте только ветку по времени.
  • when выбирает между динамическим (флаг true) и статичным (флаг false или глобальная переменная не задана) значением. when работает и как обёртка над всей веткой зума, и над одним свойством.
  • azimuth и altitude — положение солнца по времени. Азимут отсчитывается от севера: 90 — восток (восход), 180 — юг (полдень), 270 — запад (закат). Именно эти значения задают направление теней.
  • Тени включаются привязкой источника к теням в секции shadows ("shadows": { "source": "sun" }) — тогда направление теней следует за azimuth/altitude.

Расчёт смещения времени в коде​

Значение timeOffsetMinutes нужно вычислять для центра карты и передавать в стиль через метод map.patchStyleState(). Пересчитывайте его при завершении перемещения карты (moveend) и раз в минуту — на случай смены суток или перехода на летнее время.

Приближённо, по долготе​

Самый простой вариант без зависимостей: часовой пояс оценивается по долготе (15° = 1 час). Не учитывает переход на летнее время и «сдвинутые» зоны (Китай, Индия), но для визуального эффекта этого обычно достаточно.

function offsetMinutesForLng(lng) {
return Math.round(lng / 15) * 60;
}

function updatePointTimeOffset(map) {
const [lng] = map.getCenter();
map.patchStyleState({ timeOffsetMinutes: offsetMinutesForLng(lng) });
}

Точно, по IANA-зоне​

Если важны политические границы зон и переход на летнее время, определите IANA-зону по координатам (например, с помощью пакета tz-lookup) и получите смещение через Intl:

function zoneOffsetMinutes(timeZone, date = new Date()) {
const name = new Intl.DateTimeFormat('en-US', { timeZone, timeZoneName: 'longOffset' })
.formatToParts(date)
.find((part) => part.type === 'timeZoneName').value;
const match = /GMT([+-])(\d{2}):(\d{2})/.exec(name);
return match ? (match[1] === '-' ? -1 : 1) * (+match[2] * 60 + +match[3]) : 0;
}

function updatePointTimeOffset(map) {
const [lng, lat] = map.getCenter();
const timeZone = tzlookup(lat, lng);
map.patchStyleState({ timeOffsetMinutes: zoneOffsetMinutes(timeZone) });
}

Инициализация и подписки​

function enableDynamicLighting(map) {
let lastOffset = NaN;

const refresh = () => {
const [lng] = map.getCenter();
const offset = offsetMinutesForLng(lng);
if (offset === lastOffset) {
return;
}
lastOffset = offset;
map.patchStyleState({ timeOffsetMinutes: offset });
};

document.querySelector('#lighting-toggle').addEventListener('change', (event) => {
map.patchStyleState({ dynamicLightingEnabled: event.target.checked });
});

map.on('moveend', refresh);
setInterval(refresh, 60 * 1000);
refresh();
}

enableDynamicLighting(map);

Пример использования​

В примере чекбокс включает и выключает динамическое освещение через глобальную переменную dynamicLightingEnabled. Само освещение должно быть предварительно настроено в стиле (см. раздел Настройка стиля). Перемещая карту, вы увидите, как меняется свет и направление теней.

Ограничения​

  • Один свет на кадр. Освещение — глобальный параметр, поэтому одновременно «день в Москве и вечер в Якутске» на одном экране не отобразить. Свет всегда соответствует центру карты.
  • Точность часового пояса. Приближение по долготе (lng / 15) игнорирует границы зон и переход на летнее время. Для точного результата используйте IANA-зону и Intl (см. выше).