Тепловая карта превращает облако точек с координатами в поверхность плотности, которая читается с первого взгляда. Тёплые цвета показывают, где события скапливаются, холодные показывают, где их становится меньше. Пользователь видит закономерность раньше, чем прочитает хотя бы одну подпись. Поэтому тепловые карты стали стандартным выбором для анализа пешеходного трафика, прогноза спроса на доставку, сервисов учёта происшествий и исследования цен на недвижимость.
В этом туториале интерактивная тепловая карта строится с нуля на SDK MapAtlas. Вы оформите точки как взвешенный GeoJSON, отрисуете слой плотности с собственным цветовым градиентом, подключите слайдер интенсивности и закончите реальным примером со спросом на доставку. В итоге получится переиспользуемый шаблон, который можно встроить в любой проект на JavaScript или React.
Если вы ещё не работали с SDK MapAtlas, сначала прочитайте статью Как добавить интерактивные карты на сайт. В ней разобраны установка, инициализация карты и маркеры. Этот туториал продолжает её с того места, где она заканчивается.
Когда нужна тепловая карта
Тепловая карта подходит, когда у вас большой набор отдельных точек и нужно показать плотность, а не конкретные объекты. Отдельные маркеры теряют смысл уже после нескольких сотен точек, а тепловая карта выявляет структуру, которую не покажет никакое количество булавок.
Типичные сценарии:
- Анализ пешеходного трафика: выбор места для магазина, городское планирование, моделирование потоков посетителей на мероприятиях. Добавьте GPS-отметку для каждого посетителя, прошедшего через вход, и тепловая карта покажет, какие части города, торгового центра или площадки притягивают больше всего людей.
- Спрос на доставку: агрегируйте точки заказов по почтовому индексу или по сырым координатам, чтобы диспетчеры видели, где сосредоточен спрос. Эти данные напрямую используются при планировании зон и распределении водителей.
- Преступления и происшествия: аналитические панели полиции, карты рисков страховых компаний и сервисы общественной безопасности используют тепловые карты плотности, чтобы показать пространственный риск и не перегружать пользователя отдельными маркерами.
- Градиенты цен на недвижимость: со взвешенными значениями тепловая карта показывает, где в городе цены выше всего. Подробнее о картографических инструментах для недвижимости рассказано в туториале по карте объектов недвижимости.
Если ваш вопрос звучит как «где это происходит чаще всего», ответом будет тепловая карта.
Формат данных: взвешенные точки GeoJSON
Слой тепловой карты MapAtlas читает стандартную GeoJSON-коллекцию FeatureCollection из объектов Point. У каждого объекта может быть свойство weight, которое масштабирует его вклад в поверхность плотности. Заказ на 10 позиций даёт больше «тепла», чем заказ на одну позицию, а крупный транспортный узел создаёт больше пешеходного трафика, чем тихая боковая улица.
const demandData = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8952, 52.3702] },
properties: { weight: 8, zone: "centrum" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9123, 52.3601] },
properties: { weight: 3, zone: "oost" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8801, 52.3780] },
properties: { weight: 12, zone: "west" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9041, 52.3540] },
properties: { weight: 1, zone: "south" }
}
]
};
Значение weight безразмерно. Нормализуйте его относительно диапазона вашего набора данных. Если самая загруженная зона даёт 500 заказов, а самая тихая 10, переведите эти значения в шкалу от 1 до 10 перед передачей в слой. Так тепловая карта остаётся наглядной даже для наборов данных с очень разными абсолютными значениями.
Без свойства weight все точки вносят одинаковый вклад, и тепловая карта отражает только количество точек.
Что понадобится
Перед началом подготовьте:
- API-ключ MapAtlas (бесплатная регистрация, банковская карта не нужна)
- Node.js 18+ для проектов на npm или обычную HTML-страницу, если удобнее подключение через CDN
Шаг 1: установка и инициализация карты
Установите SDK:
npm install @mapmetrics/mapmetrics-gl
Или подключите его через CDN в обычном HTML-файле:
<link
rel="stylesheet"
href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css"
/>
<script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
Добавьте контейнер с заданной высотой:
<div id="map" style="width: 100%; height: 600px;"></div>
Инициализируйте карту с тёмным стилем из вашего аккаунта. На тёмной основе цветовые градиенты тепловой карты хорошо выделяются на фоне, поэтому именно её чаще берут за основу для визуализации плотности:
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const map = new mapmetricsgl.Map({
container: 'map',
style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=YOUR_API_KEY',
center: [4.9041, 52.3676], // Амстердам
zoom: 12,
});
Шаг 2: добавление слоя тепловой карты
Зарегистрируйте GeoJSON как источник, затем добавьте слой тепловой карты, который читает из него данные. Обработчик map.on('load', ...) гарантирует, что стиль полностью загрузился до того, как вы начнёте его менять.
map.on('load', () => {
map.addSource('demand', {
type: 'geojson',
data: demandData,
});
map.addLayer({
id: 'demand-heatmap',
type: 'heatmap',
source: 'demand',
paint: {
// Вес каждой точки берётся из свойства 'weight' (по умолчанию 1)
'heatmap-weight': [
'interpolate', ['linear'],
['get', 'weight'],
0, 0,
12, 1
],
// Радиус в пикселях: больше значит плавнее, но менее точно
'heatmap-radius': 30,
// Общая непрозрачность слоя тепловой карты
'heatmap-opacity': 0.85,
},
});
});
На этом этапе тепловая карта уже работает. Сине-зелёные области означают низкую плотность, а при высокой плотности стандартный градиент смещается к жёлтому и красному. Следующий шаг заменяет стандартную палитру на продуманную цветовую схему.
Шаг 3: настройка цветового градиента
Свойство heatmap-color сопоставляет значения плотности (от 0 до 1) с цветами через то же выражение interpolate, что используется во всей системе стилей отрисовки MapAtlas. При плотности 0 цвет прозрачный, поэтому в пустых областях видна базовая карта.
map.addLayer({
id: 'demand-heatmap',
type: 'heatmap',
source: 'demand',
paint: {
'heatmap-weight': [
'interpolate', ['linear'],
['get', 'weight'],
0, 0,
12, 1
],
'heatmap-radius': 30,
'heatmap-opacity': 0.85,
'heatmap-color': [
'interpolate', ['linear'],
['heatmap-density'],
0, 'rgba(0, 0, 255, 0)', // прозрачный при нулевой плотности
0.2, 'rgba(0, 128, 255, 0.6)',
0.4, 'rgba(0, 230, 200, 0.7)',
0.6, 'rgba(100, 230, 0, 0.8)',
0.8, 'rgba(255, 200, 0, 0.9)',
1.0, 'rgba(255, 50, 0, 1)' // ярко-красный при максимальной плотности
],
},
});
Этот градиент идёт от синего (редко) через зелёный и жёлтый к красному (плотно). Такая схема совпадает с привычной картиной, которую пользователи знают по метеорадарам и тепловизорам. Если в приложении есть фирменная палитра, замените значения RGB своими опорными точками: интерполяция сама обеспечит плавные переходы.
Совет по дизайну: в профессиональных панелях и аналитических инструментах оставляйте нижний конец шкалы плотности почти прозрачным. Так базовая карта остаётся читаемой в спокойных районах, а взгляд естественно переходит к горячим зонам.
Шаг 4: слайдер радиуса для интерактивного управления
Радиус тепловой карты определяет, насколько далеко распространяется влияние каждой точки. Малый радиус (от 10 до 15 px) показывает мелкие кластеры, большой радиус (от 50 до 80 px) даёт более широкую и гладкую поверхность. Для разных задач подходят разные значения по умолчанию: пешеходному трафику в плотном центре города нужен малый радиус, спросу на доставку по всей стране нужен большой.
Дайте пользователям слайдер, чтобы они могли менять радиус на лету:
<div id="controls" style="position: absolute; top: 16px; left: 16px; z-index: 1;
background: white; padding: 12px 16px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.2);">
<label style="font-size: 14px; font-weight: 600;">
Радиус: <span id="radius-value">30</span>px
</label>
<br />
<input id="radius-slider" type="range" min="5" max="80" value="30" style="width: 180px; margin-top: 6px;" />
</div>
Свяжите слайдер с setPaintProperty: этот метод обновляет слой, не добавляя его заново:
const slider = document.getElementById('radius-slider');
const radiusLabel = document.getElementById('radius-value');
slider.addEventListener('input', () => {
const radius = Number(slider.value);
radiusLabel.textContent = radius;
map.setPaintProperty('demand-heatmap', 'heatmap-radius', radius);
});
setPaintProperty обновляет слой на лету и без мерцания. Тепловая карта перерисовывается на GPU в том же кадре. Этот приём работает для любого свойства отрисовки: непрозрачности, интенсивности, опорных точек цвета.
Шаг 5: масштабирование интенсивности по уровню зума
На малых уровнях зума (вид на весь город) соседние точки сильно перекрываются, и тепловая карта может выглядеть равномерно насыщенной. На большом зуме (уровень улицы) те же точки расходятся, и поверхность плотности выглядит разреженной. Компенсация интенсивности в зависимости от зума сохраняет читаемость визуализации на любом масштабе.
paint: {
// ...другие свойства отрисовки...
'heatmap-intensity': [
'interpolate', ['linear'],
['zoom'],
8, 1, // малый зум: обычная интенсивность
14, 3 // большой зум: интенсивность выше, чтобы компенсировать разброс точек
],
'heatmap-radius': [
'interpolate', ['linear'],
['zoom'],
8, 20, // малый радиус в масштабе города
14, 50 // больший радиус в масштабе улицы
],
},
Оба свойства используют одно и то же выражение interpolate по zoom. Значения между опорными точками интерполируются линейно, поэтому переход при изменении зума получается плавным.
Реальный пример: тепловая карта спроса на доставку
Ниже полная самостоятельная реализация для панели спроса на доставку. Заказы приходят из API в виде GeoJSON FeatureCollection. Тепловая карта обновляется каждый раз, когда пользователь меняет фильтр по времени.
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const API_KEY = 'YOUR_API_KEY';
const map = new mapmetricsgl.Map({
container: 'map',
style: `https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=${API_KEY}`,
center: [4.9041, 52.3676],
zoom: 11,
});
async function loadOrders(hourFrom, hourTo) {
const res = await fetch(
`/api/orders/geojson?hour_from=${hourFrom}&hour_to=${hourTo}`
);
return res.json(); // возвращает GeoJSON FeatureCollection
}
map.on('load', async () => {
const orders = await loadOrders(8, 12); // утренний пик
map.addSource('orders', { type: 'geojson', data: orders });
map.addLayer({
id: 'order-heatmap',
type: 'heatmap',
source: 'orders',
paint: {
'heatmap-weight': [
'interpolate', ['linear'], ['get', 'items'],
0, 0, 20, 1
],
'heatmap-color': [
'interpolate', ['linear'], ['heatmap-density'],
0, 'rgba(0, 0, 255, 0)',
0.2, 'rgba(0, 128, 255, 0.5)',
0.5, 'rgba(0, 230, 150, 0.8)',
0.8, 'rgba(255, 200, 0, 0.9)',
1.0, 'rgba(255, 50, 0, 1)'
],
'heatmap-radius': [
'interpolate', ['linear'], ['zoom'],
8, 20, 14, 50
],
'heatmap-intensity': [
'interpolate', ['linear'], ['zoom'],
8, 1, 14, 3
],
'heatmap-opacity': 0.9,
},
});
// Кнопки фильтра по времени суток
document.querySelectorAll('[data-hour-range]').forEach((btn) => {
btn.addEventListener('click', async () => {
const [from, to] = btn.dataset.hourRange.split('-').map(Number);
const newOrders = await loadOrders(from, to);
map.getSource('orders').setData(newOrders);
});
});
});
Вызов setData у существующего источника заменяет GeoJSON без повторного добавления слоя. Тепловая карта перерисовывается автоматически. Этот шаблон подходит для любого фильтра по времени: часа суток, дня недели, погодных условий.
Слой с маршрутами, который обычно дополняет тепловую карту спроса на доставку, разобран в руководстве по оптимизации маршрутов. Оптимизированный маршрут доставки поверх тепловой карты спроса даёт диспетчерам полную картину работы в одном окне.
Производительность на больших наборах данных
Слой тепловой карты MapAtlas использует WebGL и отрисовывается быстро, но конвейер данных, который его питает, на больших объёмах может стать узким местом.
Агрегируйте очень большие наборы данных на сервере. Если у вас миллионы GPS-отметок, не отправляйте их все в браузер. Выполните пространственную агрегацию на сервере (гексагональная сетка H3, квадродерево или простое округление до ячеек сетки), которая сократит 1 миллион исходных точек до 10 000 ячеек с полями count и weight. На типичных уровнях зума тепловая карта будет выглядеть почти так же, а загружаться будет в разы быстрее.
Передавайте обновления инкрементально. Для живых данных (пешеходный трафик в реальном времени, поступающие заказы) вызывайте setData со скользящим окном последних точек, а не накапливайте постоянно растущий объект GeoJSON. Держите количество точек в источнике в фиксированных пределах и удаляйте старые записи.
Задайте maxzoom для источника. Параметр maxzoom: 14 в вызове addSource сообщает SDK, что тайловые данные выше 14-го уровня зума запрашивать не нужно. Для тепловых карт это редко важно, ведь слой читает один плоский источник GeoJSON, а не тайлы, но параметр избавляет от лишней повторной обработки на крупных масштабах.
Не усложняйте выражения отрисовки. Градиент heatmap-color один раз запекается в текстуру цветовой шкалы при его установке, поэтому дополнительные цветовые точки почти не добавляют нагрузки в каждом кадре. На мобильных устройствах больше ресурсов отнимают большой heatmap-radius и большое количество точек, поэтому настраивайте в первую очередь их.
Инициализируйте карту отложенно. Оберните всю инициализацию карты в обработчик IntersectionObserver, чтобы она запускалась только тогда, когда контейнер карты появляется в области просмотра. Так бандл SDK не попадает в первоначальную загрузку страницы, что особенно полезно на маркетинговых страницах, где карта расположена ниже первого экрана.
Подробнее о приёмах оптимизации карт, включая отложенную загрузку и кластеризацию, рассказано в чек-листе для продакшена в статье Как добавить интерактивные карты на сайт.
Интеграция с React
Тепловая карта в React-компоненте строится по той же схеме, что и любая карта MapAtlas: инициализация в useEffect, состояние слайдера и фильтра через useState, очистка при размонтировании.
import { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
export function DeliveryHeatmap({ geojson, apiKey }) {
const containerRef = useRef(null);
const mapRef = useRef(null);
const [radius, setRadius] = useState(30);
useEffect(() => {
const map = new mapmetricsgl.Map({
container: containerRef.current,
style: `https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=${apiKey}`,
center: [4.9041, 52.3676],
zoom: 11,
});
mapRef.current = map;
map.on('load', () => {
map.addSource('orders', { type: 'geojson', data: geojson });
map.addLayer({
id: 'order-heatmap',
type: 'heatmap',
source: 'orders',
paint: {
'heatmap-radius': radius,
'heatmap-opacity': 0.9,
},
});
});
return () => map.remove();
}, [apiKey]);
// Обновление радиуса без повторного монтирования карты
useEffect(() => {
const map = mapRef.current;
if (!map || !map.getLayer('order-heatmap')) return;
map.setPaintProperty('order-heatmap', 'heatmap-radius', radius);
}, [radius]);
return (
<div>
<div style={{ padding: '8px 0' }}>
<label>
Радиус: {radius}px
<input
type="range" min={5} max={80} value={radius}
onChange={e => setRadius(Number(e.target.value))}
style={{ marginLeft: 8, width: 160 }}
/>
</label>
</div>
<div ref={containerRef} style={{ width: '100%', height: '600px' }} />
</div>
);
}
В Next.js импортируйте этот компонент через dynamic(() => import('./DeliveryHeatmap'), { ssr: false }), чтобы избежать ошибок серверного рендеринга: SDK работает только в браузере.
Что построить дальше
Теперь у вас есть рабочая интерактивная тепловая карта со взвешенными данными, собственным цветовым градиентом, слайдером радиуса и интенсивностью, привязанной к зуму. Куда её развивать:
- Наложите слой маршрутов поверх тепловой карты, чтобы показать запланированные маршруты доставки на фоне поверхности спроса. В руководстве по оптимизации маршрутов разобраны решатель, входные данные и ограничения.
- Добавьте анимацию по времени: перебирайте почасовые срезы в цикле
setInterval, вызываяsetDataу источника. Статичная карта плотности превратится в таймлапс того, как спрос перемещается в течение дня. - Объедините карту с данными о ценах на недвижимость, чтобы показать ценовые градиенты по районам. Приёмы работы со взвешенными данными для платформ недвижимости разобраны в туториале по карте объектов недвижимости.
- Загляните на страницу тарифов MapAtlas, чтобы подобрать план под трафик вашего продакшена.
Полный справочник по SDK и дополнительные примеры доступны на docs.mapatlas.xyz.
Часто задаваемые вопросы
Чем тепловая карта отличается от картограммы?
Тепловая карта показывает плотность точек, смешивая соседние точки в непрерывный цветовой градиент. Она идеально подходит для сырых координат, например GPS-отметок или мест событий. Картограмма (choropleth) окрашивает заранее заданные географические области (страны, почтовые индексы, переписные участки) по статистическому показателю. Используйте тепловую карту, когда у вас много отдельных точек, и картограмму, когда данные уже агрегированы по регионам.
Сколько точек выдерживает тепловая карта на JavaScript?
Слой тепловой карты MapAtlas отрисовывается на GPU через WebGL, поэтому десятки тысяч точек плавно обрабатываются на типичном оборудовании. Для гораздо более крупных наборов данных, от сотен тысяч точек и выше, лучше агрегировать данные на сервере в сетку с меньшим разрешением и перейти на слой GeoJSON типа fill-extrusion или circle: так производительность на слабых устройствах будет выше.
Можно ли использовать тепловые карты MapAtlas бесплатно?
Да. У MapAtlas есть бесплатный тариф, который включает отрисовку картографических тайлов, поддержку слоёв GeoJSON и слои тепловых карт. Бесплатного плана хватает для разработки и продакшена с небольшой нагрузкой. Подробности о тарифах на mapatlas.eu/pricing.
Работают ли тепловые карты в мобильных браузерах?
Да. SDK MapAtlas использует для отрисовки WebGL, который поддерживается во всех современных мобильных браузерах, включая Safari на iOS и Chrome на Android. Для очень больших наборов данных на слабых мобильных устройствах сократите количество точек или увеличьте радиус тепловой карты, чтобы частота кадров оставалась стабильной.
Читайте также:
- Как добавить интерактивные карты на сайт
- Карта объектов недвижимости с кластеризацией
- Что такое оптимизация маршрутов?
- Начните бесплатно
Часто задаваемые вопросы
Чем тепловая карта отличается от картограммы?
Тепловая карта показывает плотность точек, смешивая соседние точки в непрерывный цветовой градиент. Она идеально подходит для сырых координат, например GPS-отметок или мест событий. Картограмма (choropleth) окрашивает заранее заданные географические области (страны, почтовые индексы, переписные участки) по статистическому показателю. Используйте тепловую карту, когда у вас много отдельных точек, и картограмму, когда данные уже агрегированы по регионам.
Сколько точек выдерживает тепловая карта на JavaScript?
Слой тепловой карты MapAtlas отрисовывается на GPU через WebGL, поэтому десятки тысяч точек плавно обрабатываются на типичном оборудовании. Для гораздо более крупных наборов данных, от сотен тысяч точек и выше, лучше агрегировать данные на сервере в сетку с меньшим разрешением и перейти на слой GeoJSON типа fill-extrusion или circle: так производительность на слабых устройствах будет выше.
Можно ли использовать тепловые карты MapAtlas бесплатно?
Да. У MapAtlas есть бесплатный тариф, который включает отрисовку картографических тайлов, поддержку слоёв GeoJSON и слои тепловых карт. Бесплатного плана хватает для разработки и продакшена с небольшой нагрузкой. Подробности о тарифах на mapatlas.eu/pricing.
Работают ли тепловые карты в мобильных браузерах?
Да. SDK MapAtlas использует для отрисовки WebGL, который поддерживается во всех современных мобильных браузерах, включая Safari на iOS и Chrome на Android. Для очень больших наборов данных на слабых мобильных устройствах сократите количество точек или увеличьте радиус тепловой карты, чтобы визуально объединить больше точек и сохранить стабильную частоту кадров.

