지도의 반경 원은 거짓입니다. 그것은 당신의 창고에서 10km 떨어진 모든 포인트가 동일한 시간에 동등하게 도달 가능함을 알려줍니다. 실제로 고속도로를 따라 10km는 8분이 걸리고 도시 중심을 통과하는 10km는 35분이 걸립니다. 원은 지리적으로 대칭이고 여행은 아닙니다.
등시선 지도는 진실을 말합니다. 등시선은 실제 도로 네트워크 및 속도 제한을 따르면서 주어진 여행 시간 내에 원점에서 도달할 수 있는 모든 포인트를 포함하는 다각형입니다. 도로가 빠른 곳에서는 다각형이 바깥쪽으로 팽창합니다. 도로가 느리거나 없는 곳에서는 안쪽으로 수축합니다. 그 결과는 실제 접근성의 불규칙하고 정확한 표현입니다.
이러한 종류의 분석은 수년 동안 대형 소매점 및 물류 회사에서 사용되었습니다. IKEA는 여행 시간 집수 분석을 사용하여 매장 크기를 조정하고, Deliveroo는 배송 등시선을 사용하여 구역 경계를 설정하며, 부동산 플랫폼은 나열된 주소에 대한 통근 시간 집수 영역을 표시합니다. MapAtlas 라우팅 API는 API 키를 가진 모든 개발자가 동일한 기능을 사용할 수 있게 합니다.
이 튜토리얼은 등시선이 무엇인지 설명하고, 주요 사용 사례를 다루며, MapAtlas 벡터 맵에서 다중 시간 등시선(5, 10, 15분)을 렌더링하는 완전한 JavaScript 구현을 안내합니다. 총 코드: 약 60줄.
등시선 API 작동 방식
MapAtlas 등시선 엔드포인트는 시작점(경도, 위도), 하나 이상의 시간 제한(초 단위) 및 여행 모드를 허용합니다. 시간 제한당 하나의 다각형이 있는 GeoJSON FeatureCollection을 반환합니다.
Endpoint:
POST https://api.mapatlas.eu/v1/isochrone
Request body:
{
"locations": [[4.9041, 52.3676]],
"range": [900, 600, 300],
"range_type": "time",
"profile": "driving-car"
}
locations, array of[longitude, latitude]origin pointsrange, array of time limits in seconds (900 = 15 min, 600 = 10 min, 300 = 5 min). List largest first so polygons nest correctly.range_type,"time"for travel-time analysis,"distance"for distance-based isochronesprofile, travel mode:"driving-car","cycling-regular","foot-walking","driving-hgv"(heavy goods vehicle)
Response:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[ [4.87, 52.34], [4.92, 52.41], ... ]]
},
"properties": {
"value": 900,
"center": [4.9041, 52.3676],
"group_index": 0
}
},
{
"type": "Feature",
"geometry": { "type": "Polygon", "coordinates": [...] },
"properties": { "value": 600, ... }
},
{
"type": "Feature",
"geometry": { "type": "Polygon", "coordinates": [...] },
"properties": { "value": 300, ... }
}
]
}
Each feature's value property is the time limit in seconds. Use this to assign colors when rendering multiple nested polygons.
Real-World Use Cases
Delivery zone mapping. E-commerce and food delivery platforms define zones based on travel time, not distance. A 30-minute delivery zone from a dark kitchen covers a very different area depending on whether it's in a dense city center or a suburban industrial park. Isochrones reflect this accurately.
Store catchment area analysis. Retail site selection starts with "how many people live within a 20-minute drive of this location?" Isochrones give the polygon; census data gives the population within it. The combination drives site selection decisions for major retailers.
Property search by commute time. Real estate platforms let users search for properties within X minutes of a workplace. This is a significantly better UX than radius-based search because it matches how people actually evaluate locations.
Hospital and healthcare coverage. Healthcare planners use isochrones to identify populations farther than a threshold time from emergency services. Isochrones from multiple hospitals can be combined to show coverage gaps.
School catchment areas. Local authorities and parents use travel-time isochrones (often walking, not driving) to understand actual catchment areas for school places.
Event marketing. Show conference attendees what restaurants, hotels, and attractions are reachable on foot within 10 minutes of the venue.
The Real Estate Property Map tutorial and the Route Optimization tutorial both use isochrone analysis as a component of their respective workflows.
Complete JavaScript Implementation
This implementation renders three nested isochrones (5, 10, 15 minutes) on a MapAtlas map. Click anywhere on the map to recalculate isochrones from that point.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Isochrone Map</title>
<link rel="stylesheet" href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css" />
<style>
body, html { margin: 0; padding: 0; height: 100%; }
#map { width: 100%; height: 100vh; }
#controls {
position: absolute; top: 12px; left: 12px; z-index: 10;
background: white; padding: 12px 16px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15); font-family: sans-serif; font-size: 14px;
}
#controls label { display: block; margin-bottom: 6px; }
#loading { color: #888; margin-top: 6px; display: none; }
</style>
</head>
<body>
<div id="controls">
<strong>Travel mode:</strong>
<label><input type="radio" name="mode" value="driving-car" checked> Driving</label>
<label><input type="radio" name="mode" value="foot-walking"> Walking</label>
<label><input type="radio" name="mode" value="cycling-regular"> Cycling</label>
<div id="loading">Calculating...</div>
<div style="margin-top:8px; color:#555;">Click the map to set origin.</div>
</div>
<div id="map"></div>
<script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
<script>
const API_KEY = 'YOUR_API_KEY';
const ISOCHRONE_URL = 'https://api.mapatlas.eu/v1/isochrone';
// Color palette for 5 / 10 / 15 minute zones
const ZONE_COLORS = {
900: { fill: '#e74c3c', opacity: 0.15, border: '#c0392b' }, // 15 min, red
600: { fill: '#f39c12', opacity: 0.20, border: '#d68910' }, // 10 min, orange
300: { fill: '#27ae60', opacity: 0.25, border: '#1e8449' }, // 5 min , green
};
// ── Initialize map ─────────────────────────────────────────────────────────────
const map = new mapmetricsgl.Map({
container: 'map',
style: `https://tiles.mapatlas.eu/styles/basic/style.json?key=${API_KEY}`,
center: [4.9041, 52.3676],
zoom: 11,
});
// ── Fetch isochrone from API ───────────────────────────────────────────────────
async function fetchIsochrone(lngLat, profile) {
const body = {
locations: [[lngLat.lng, lngLat.lat]],
range: [900, 600, 300], // 15, 10, 5 minutes in seconds
range_type: 'time',
profile,
};
const resp = await fetch(ISOCHRONE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Api-Key': API_KEY },
body: JSON.stringify(body),
});
if (!resp.ok) throw new Error(`Isochrone API error: ${resp.status}`);
return resp.json();
}
// ── Render isochrones on map ───────────────────────────────────────────────────
function renderIsochrones(geojson) {
// Remove previous layers and source if they exist
['iso-fill-900', 'iso-fill-600', 'iso-fill-300',
'iso-line-900', 'iso-line-600', 'iso-line-300'].forEach(id => {
if (map.getLayer(id)) map.removeLayer(id);
});
if (map.getSource('isochrones')) map.removeSource('isochrones');
// Add GeoJSON source with all three polygons
map.addSource('isochrones', { type: 'geojson', data: geojson });
// Render each zone, largest first (so smaller zones appear on top)
[900, 600, 300].forEach(seconds => {
const colors = ZONE_COLORS[seconds];
// Fill layer
map.addLayer({
id: `iso-fill-${seconds}`,
type: 'fill',
source: 'isochrones',
filter: ['==', ['get', 'value'], seconds],
paint: {
'fill-color': colors.fill,
'fill-opacity': colors.opacity,
},
});
// Border layer
map.addLayer({
id: `iso-line-${seconds}`,
type: 'line',
source: 'isochrones',
filter: ['==', ['get', 'value'], seconds],
paint: {
'line-color': colors.border,
'line-width': 2,
'line-dasharray': seconds === 900 ? [1] : [4, 2],
},
});
});
}
// ── Add origin marker ─────────────────────────────────────────────────────────
let originMarker = null;
function setOriginMarker(lngLat) {
if (originMarker) originMarker.remove();
originMarker = new mapmetricsgl.Marker({ color: '#2c3e50' })
.setLngLat(lngLat)
.setPopup(new mapmetricsgl.Popup({ offset: 25 }).setHTML('<strong>Origin</strong>'))
.addTo(map);
}
// ── Click handler ─────────────────────────────────────────────────────────────
map.on('click', async (e) => {
const profile = document.querySelector('input[name="mode"]:checked').value;
const loading = document.getElementById('loading');
setOriginMarker(e.lngLat);
loading.style.display = 'block';
try {
const geojson = await fetchIsochrone(e.lngLat, profile);
renderIsochrones(geojson);
} catch (err) {
console.error('Isochrone error:', err);
alert('Could not calculate isochrone. Check the console for details.');
} finally {
loading.style.display = 'none';
}
});
// ── Load initial isochrone for Amsterdam Centraal ─────────────────────────────
map.on('load', async () => {
const defaultOrigin = { lng: 4.9001, lat: 52.3791 };
setOriginMarker(defaultOrigin);
try {
const geojson = await fetchIsochrone(defaultOrigin, 'driving-car');
renderIsochrones(geojson);
} catch (err) {
console.error('Initial isochrone error:', err);
}
});
</script>
</body>
</html>
Save this as index.html, replace YOUR_API_KEY, open it in a browser, and you'll see three colored zones around Amsterdam Centraal. Click anywhere on the map to recalculate from that point. Switch the radio buttons to compare driving, walking, and cycling isochrones for the same origin.
Walking vs. Driving Comparison
The travel mode parameter dramatically changes the isochrone shape. Compare 15-minute isochrones from the same origin:
Driving: Stretches 15–20 km along motorways, contracts to 3–5 km in congested urban areas. The polygon reflects the road network's hierarchy, fast motorways create tentacle-like extensions.
Walking: Near-circular, typically 1–1.5 km radius at normal walking pace (5 km/h). Urban obstacles (rivers, railways, limited crossings) create dents in the otherwise regular shape.
Cycling: Intermediate range, typically 3–5 km radius. More directional than walking but follows bike-path networks rather than roads, which in well-connected cities like Amsterdam or Copenhagen can be surprisingly expansive.
The API returns accurate mode-specific isochrones because it uses actual road network data with mode-appropriate speeds, not a simple average velocity applied to straight-line distance.
Multiple Origins
To show isochrones from multiple origins simultaneously, for example, coverage from two warehouse locations, merge the results into a single GeoJSON FeatureCollection and add properties to distinguish origins:
async function fetchMultiOriginIsochrones(origins, profile) {
const requests = origins.map(origin =>
fetchIsochrone({ lng: origin[0], lat: origin[1] }, profile)
);
const results = await Promise.all(requests);
// Merge all features, tagging each with its origin index
const merged = {
type: 'FeatureCollection',
features: results.flatMap((geojson, i) =>
geojson.features.map(f => ({
...f,
properties: { ...f.properties, origin_index: i },
}))
),
};
return merged;
}
// Example: Two Amsterdam warehouses
const origins = [
[4.8720, 52.3531], // Warehouse A, West Amsterdam
[4.9441, 52.3599], // Warehouse B, East Amsterdam
];
map.on('load', async () => {
const geojson = await fetchMultiOriginIsochrones(origins, 'driving-car');
// Use origin_index in filter expressions to color each origin differently
renderIsochrones(geojson);
});
The union of overlapping isochrones shows your combined service area. The non-overlapping portions reveal which zones are served by only one origin, useful for identifying gaps or over-capacity areas.
Integration With Real Estate and Logistics Applications
For real estate applications, isochrones solve the commute-time property search problem elegantly. A user enters their workplace address, selects a maximum commute time, and the map shows the catchment polygon. Any property listing whose coordinates fall inside the polygon qualifies. This is far more useful than "within 10 km", it reflects how commuters actually think.
The Real Estate Property Map with Clustering tutorial shows how to render property listings as map markers. Adding isochrone-based filtering on top of that implementation is a natural extension: calculate the isochrone, then use a point-in-polygon test to filter which markers to show.
For logistics, isochrones define delivery zones. The Route Optimization API tutorial covers how to sequence multiple stops within a zone after the zone itself is defined. These two APIs are designed to work together: isochrones define where you serve, the routing API optimizes how you serve it.
The Travel Time Analysis capabilities page has additional documentation on the isochrone endpoint, including distance-based isochrones (useful when travel time data is unavailable), multi-modal analysis, and response format details.
For businesses considering delivery operations, the Logistics & Delivery solutions page covers how mapping APIs fit into delivery workflow architecture. And for property platforms, the Real Estate & Property solutions page explains typical integration patterns.
Performance Notes
Isochrone calculations are computationally intensive, the API processes the full road network graph to determine reachability. Response times are typically 200–800ms depending on the time limit (larger time limits = larger graph traversal) and travel mode (driving traverses more roads than walking).
For production applications:
- Cache isochrone results for common queries (fixed origin + time limit). A delivery zone from a fixed warehouse location doesn't change frequently, cache it for hours or days.
- Limit simultaneous requests. If your UI allows clicking rapidly, debounce click handlers by 500ms to avoid triggering multiple simultaneous API requests.
- Use shorter time limits for interactive maps. A 5-minute walking isochrone returns in under 200ms. A 60-minute driving isochrone may take 600–800ms. Match the time limit to the use case.
Summary
Isochrone maps show what's actually reachable in a given time from a given point, accounting for road networks, travel speeds, and mode of transport. A radius circle cannot.
The MapAtlas Routing API returns isochrones as GeoJSON polygons that you render as fill layers on a vector map. The implementation in this tutorial:
- Makes a POST request to the
/isochroneendpoint with a list of time limits - Receives GeoJSON feature collection with one polygon per time limit
- Renders each polygon as a
fillandlinelayer with distinct colors - Supports driving, walking, and cycling modes
- Handles map click events to recalculate from any origin
The complete code runs in a single HTML file, no build step, no dependencies beyond the MapAtlas SDK.
Sign up for a free MapAtlas API key to get started. The Routing and Isochrone APIs are included in all plans, no credit card required to start building.

