히트맵은 좌표 포인트의 구름을 한눈에 읽을 수 있는 밀도 표면으로 변환합니다. 뜨거운 색상은 이벤트가 집중된 곳을 표시하고, 차가운 색상은 희박한 곳을 표시합니다. 사용자는 단 하나의 라벨을 읽기 전에 패턴을 파악합니다. 이것이 보행자 트래픽 분석, 배송 수요 예측, 범죄 신고 도구 및 부동산 가격 조사의 기본 선택이 히트맵인 이유입니다.
이 튜토리얼은 MapAtlas SDK를 사용하여 처음부터 인터랙티브 히트맵을 구축합니다. 포인트 데이터를 가중 GeoJSON으로 구조화하고, 사용자 정의 색상 그래디언트로 밀도 레이어를 렌더링하며, 강도 슬라이더를 연결하고, 실제 배송 수요 예제로 마무리합니다. 끝내면 JavaScript 또는 React 프로젝트에 직접 적용할 수 있는 재사용 가능한 패턴을 가지게 됩니다.
MapAtlas SDK를 처음 사용한다면, 먼저 웹사이트에 인터랙티브 맵을 추가하는 방법을 읽으세요. 설치, 맵 초기화 및 마커를 다룹니다. 이 튜토리얼은 그곳에서 계속됩니다.
히트맵을 사용할 때
히트맵은 개별 포인트 위치의 큰 세트를 가지고 있고 개인 정보가 아닌 밀도를 전달하려고 할 때 올바른 도구입니다. 개별 마커는 수백 개 포인트를 넘으면 의미를 잃습니다. 히트맵은 어떤 양의 핀도 드러낼 수 없는 구조를 드러냅니다.
일반적인 사용 사례:
- 보행자 트래픽 분석: 소매점 위치 선정, 도시 계획, 행사 군중 모델링. 문을 통과한 모든 고객에 대해 GPS 핑을 드롭하면 히트맵은 도시, 쇼핑센터 또는 장소의 어느 영역이 가장 많은 사람을 끌어들이는지 보여줍니다.
- 배송 수요: 주문 출처를 우편번호 또는 원시 좌표로 집계하여 배송자에게 수요가 집중된 곳을 보여줍니다. 이는 직접 구역 계획 및 운전자 배정으로 전달됩니다.
- 범죄 및 사건 데이터: 경찰 분석 대시보드, 보험 위험 맵 및 공공 안전 보고 도구는 모두 밀도 히트맵을 사용하여 사용자에게 개별 마커를 과도하게 제시하지 않고 공간 위험을 전달합니다.
- 부동산 가격 그래디언트: 가중 값과 결합하면, 히트맵은 도시 전역에서 가격이 가장 높은 곳을 표시할 수 있습니다. 부동산 중심의 맵 도구 구축에 대한 자세한 내용은 부동산 속성 맵 튜토리얼을 참조하세요.
질문이 "가장 많이 일어나는 곳은 어디인가?"라면 히트맵이 답입니다.
데이터 형식: 가중 GeoJSON 포인트
MapAtlas 히트맵 레이어는 Point 기능의 표준 GeoJSON FeatureCollection을 읽습니다. 각 기능은 밀도 표면에 대한 기여도를 조정하는 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 속성이 없으면 모든 포인트가 동등하게 기여하고 히트맵은 순수 개수 밀도를 반영합니다.
필수 조건
시작하기 전에:
- MapAtlas API 키 (무료 가입, 신용카드 불필요)
- npm 기반 프로젝트의 경우 Node.js 18+, 또는 CDN을 선호하는 경우 순수 HTML 페이지
단계 1: 맵 설치 및 초기화
SDK 설치:
npm install @mapmetrics/mapmetrics-gl
또는 순수 HTML 파일에서 CDN을 통해 로드:
<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://tiles.mapatlas.eu/styles/dark/style.json?key=YOUR_API_KEY',
center: [4.9041, 52.3676], // Amsterdam
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 each point by its 'weight' property (defaults to 1 if absent)
'heatmap-weight': [
'interpolate', ['linear'],
['get', 'weight'],
0, 0,
12, 1
],
// Radius in pixels; larger = smoother but less precise
'heatmap-radius': 30,
// Overall opacity of the heatmap layer
'heatmap-opacity': 0.85,
},
});
});
이 시점에서 작동하는 히트맵이 있습니다. 파란색-녹색 영역은 낮은 밀도이고 기본 그래디언트는 높은 밀도에서 노란색과 빨간색으로 이동합니다. 다음 단계는 기본 팔레트를 의도적인 색상 체계로 바꿉니다.
단계 3: 색상 그래디언트 사용자 정의
heatmap-color 속성은 MapAtlas 페인트 시스템의 나머지 부분에서 사용되는 동일한 interpolate 표현을 사용하여 밀도 값(0에서 1)을 색상에 매핑합니다. 밀도 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)', // transparent at zero density
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)' // bright red at peak density
],
},
});
이 그래디언트는 파란색(희박)에서 녹색과 노란색을 통해 빨간색(밀도)으로 실행되며, 대부분의 사용자가 날씨 레이더 맵과 열 화상에서 가져오는 정신 모델과 일치합니다. 앱에 브랜드 색상 팔레트가 있다면 RGB 값을 자신의 정지점으로 바꿉니다. 보간은 부드러운 전환을 자동으로 처리합니다.
설계 팁: 전문 대시보드 및 분석 도구의 경우 낮은 밀도 끝을 거의 투명하게 유지하세요. 조용한 영역에서 기본 맵의 가독성을 유지하고 눈을 자연스럽게 핫 존으로 끌어들입니다.
단계 4: 인터랙티브 제어를 위한 반경 슬라이더 추가
히트맵 반경은 각 포인트가 얼마나 멀리 "영향"을 미치는지 제어합니다. 작은 반경(1015px)은 세밀한 클러스터를 표시합니다. 큰 반경(5080px)은 더 넓고 부드러운 표면을 생성합니다. 다양한 사용 사례는 다양한 기본값을 필요로 합니다. 밀도가 높은 도시 중심의 보행자 트래픽은 작은 반경이 필요하고, 전국에 걸친 배송 수요는 큰 반경이 필요합니다.
사용자가 조정할 수 있도록 슬라이더를 제공하세요:
<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;">
Radius: <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>
Wire the slider to setPaintProperty, which updates the layer without re-adding it:
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: {
// ...other paint properties...
'heatmap-intensity': [
'interpolate', ['linear'],
['zoom'],
8, 1, // low zoom: normal intensity
14, 3 // high zoom: boost intensity to compensate for point spread
],
'heatmap-radius': [
'interpolate', ['linear'],
['zoom'],
8, 20, // small radius at city scale
14, 50 // larger radius at street scale
],
},
두 속성 모두 zoom 위에 동일한 interpolate를 사용합니다. 정지점 사이의 값은 선형으로 보간되므로 사용자가 줌할 때 전환이 매끄럽습니다.
실제 예제: 배송 수요 히트맵
배송 수요 대시보드를 위한 완전하고 자체 포함된 구현은 다음과 같습니다. 주문은 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://tiles.mapatlas.eu/styles/dark/style.json?key=${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(); // returns a GeoJSON FeatureCollection
}
map.on('load', async () => {
const orders = await loadOrders(8, 12); // morning peak
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,
},
});
// Time-of-day filter buttons
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을 바꿉니다. 히트맵은 자동으로 다시 렌더링합니다. 이 패턴은 시간 기반 필터로 확장됩니다: 시간, 요일, 날씨 조건.
일반적으로 배송 수요 히트맵을 동반하는 경로 계획 레이어는 경로 최적화 API 튜토리얼을 참조하세요. 수요 히트맵 위에 최적화된 배송 경로를 오버레이하면 배송자에게 한 보기에서 완전한 운영 그림을 제공합니다.
큰 데이터세트에 대한 성능 팁
MapAtlas 히트맵 레이어는 WebGL을 사용하고 빠르게 렌더링하지만, 이를 공급하는 데이터 파이프라인은 규모가 클 때 병목이 될 수 있습니다.
매우 큰 데이터세트의 경우 서버에서 사전 집계합니다. 수백만 개의 GPS 핑이 있다면 모두 브라우저로 보내지 마세요. 100만 개의 원시 포인트를 count 및 weight 필드가 있는 10,000개의 그리드 셀로 줄이는 서버 측 공간 집계(H3 육각형 그리드, 쿼드트리 또는 간단한 그리드 반올림)를 실행합니다. 히트맵은 사용자에게 동일하게 보이고 훨씬 더 빠르게 로드됩니다.
점진적으로 업데이트를 스트리밍합니다. 라이브 데이터(실시간 보행자 트래픽, 라이브 주문 배치)의 경우, 계속 증가하는 GeoJSON 객체를 누적하지 않고 최근 포인트의 롤링 윈도우를 사용하여 setData를 사용합니다. 소스를 고정 최대 포인트 개수로 유지하고 오래된 레코드를 제거합니다.
소스에서 maxzoom을 사용합니다. addSource 호출에 maxzoom: 14를 추가하면 SDK에 줌 14 이상에서 타일 데이터 요청을 중지하도록 지시합니다. 히트맵의 경우 히트맵 레이어가 타일 데이터가 아닌 단일 평탄 GeoJSON 소스를 읽기 때문에 거의 중요하지 않지만, 높은 줌 수준에서 불필요한 재처리를 방지합니다.
페인트 속성 복잡성을 줄입니다. 페인트 표현의 각 추가 interpolate 정지는 프레임당 GPU 평가 비용을 추가합니다. 모바일 대상 앱의 경우 색상 그래디언트를 3개 또는 4개 정지로 단순화하고 낮은 우선순위 보기에서 줌 연결 반경/강도 스케일링을 제거합니다.
맵을 지연 초기화합니다. 전체 맵 초기화를 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://tiles.mapatlas.eu/styles/dark/style.json?key=${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]);
// Update radius without remounting the map
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: {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에서 서버 측 렌더링 오류를 피합니다.
다음으로 구축할 것
이제 가중 데이터, 사용자 정의 색상 그래디언트, 반경 슬라이더 및 줌 연결 강도를 가진 작동하는 인터랙티브 히트맵이 있습니다. 다음은 이를 사용할 위치입니다:
- 수요 표면에 대해 계획된 배송 경로를 표시하기 위해 히트맵 위에 라우팅 레이어를 오버레이합니다. 경로 최적화 API 튜토리얼은 전체 구현을 안내합니다.
- 시간 애니메이션 추가:
setInterval루프를 사용하여 소스에서setData를 호출하여 시간별 스냅샷을 순환합니다. 이는 정적 밀도 맵을 수요가 하루 종일 어떻게 움직이는지 보여주는 타임랩스로 바꿉니다. - 부동산 가격 데이터와 결합하여 동네별 가격 그래디언트를 표시합니다. 부동산 속성 맵 튜토리얼은 부동산 플랫폼의 가중 데이터 패턴을 다룹니다.
- MapAtlas 가격 책정 페이지에서 프로덕션 트래픽에 대한 올바른 계획을 찾으세요.
전체 SDK 참고 및 추가 예제는 docs.mapatlas.xyz에서 사용할 수 있습니다.
Frequently Asked Questions
What is the difference between a heatmap and a choropleth map?
A heatmap visualises point density by blending nearby points into a continuous colour gradient, ideal for raw coordinate data like GPS pings or event locations. A choropleth map colours pre-defined geographic areas (countries, postcodes, census tracts) by a statistical value. Use a heatmap when you have many individual points; use a choropleth when your data is already aggregated by region.
How many data points can a JavaScript heatmap handle?
The MapAtlas heatmap layer renders on the GPU via WebGL, so it handles tens of thousands of points without frame drops at normal zoom levels. Above roughly 500,000 points, pre-aggregating your data server-side into a lower-resolution grid and switching to a GeoJSON fill-extrusion or circle layer gives better performance on low-end devices.
Can I use MapAtlas heatmaps for free?
Yes. MapAtlas has a free tier that includes map tile rendering, GeoJSON layer support, and heatmap layers. The free plan covers development and low-volume production use. See mapatlas.eu/pricing for full plan details.
Do heatmaps work on mobile browsers?
Yes. The MapAtlas SDK uses WebGL for rendering, which is supported in all modern mobile browsers including Safari on iOS and Chrome on Android. For very large datasets on low-end mobile hardware, reducing the point count or increasing the heatmap radius keeps frame rates smooth.
Related reading:

