Skip to main content
Ban do duong dang giai thich: Cach hien thi nhung gi co the dat duoc
Tutorials

Ban do duong dang giai thich: Cach hien thi nhung gi co the dat duoc

Xay dung ban do duong dang voi MapAtlas Routing API. Hien thi vung giao hang, khu vuc thu hut va da giac thoi gian lai xe trong JavaScript voi vi du code day du.

Brent van der Heiden12 min read
#isochrone map#travel time analysis#catchment area#routing api#drive time polygon#delivery zone

Một vòng tròn bán kính trên bản đồ là một cái nói dối. Nó cho bạn biết rằng mọi điểm cách kho lưu trữ của bạn 10 km đều có thể truy cập được trong cùng một khoảng thời gian. Trong thực tế, 10 km dọc theo đường cao tốc mất 8 phút và 10 km qua trung tâm thành phố mất 35 phút. Vòng tròn đối xứng về địa lý; du lịch không phải.

Bản đồ isochrone nói sự thật. Isochrone là một đa giác bao quanh mọi điểm có thể tiếp cận từ một gốc trong một khoảng thời gian du lịch nhất định, theo các mạng đường thực tế và giới hạn tốc độ. Nơi đường nhanh, đa giác nhô ra ngoài. Nơi đường chậm hoặc vắng mặt, nó hòa chính nước. Kết quả là một biểu diễn không đều, chính xác của khả năng tiếp cập thế giới thực.

Loại phân tích này đã được sử dụng bởi các nhà bán lẻ lớn và công ty logistic trong nhiều năm, IKEA sử dụng phân tích bắt giữ thời gian di chuyển để xác định kích thước cửa hàng, Deliveroo sử dụng các đường isochrone giao hàng để đặt ranh giới vùng, các nền tảng bất động sản hiển thị các khu vực bắt giữ thời gian đi làm cho các địa chỉ được liệt kê. MapAtlas Routing API cung cấp khả năng tương tự cho bất kỳ nhà phát triển nào có khóa API.

Hướng dẫn này giải thích isochrone là gì, bao gồm các trường hợp sử dụng chính và cung cấp hướng dẫn qua một triển khai JavaScript hoàn chỉnh hiển thị các đường isochrone đa thời gian (5, 10 và 15 phút) trên bản đồ vectơ MapAtlas. Mã tổng cộng: khoảng 60 dòng.

Cách Hoạt Động của API Isochrone

Điểm cuối isochrone MapAtlas chấp nhận một điểm xuất phát (kinh độ, vĩ độ), một hoặc nhiều giới hạn thời gian (tính bằng giây) và một chế độ di chuyển. Nó trả về FeatureCollection GeoJSON với một đa giác cho mỗi giới hạn thời gian.

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, mảng các điểm gốc [kinh độ, vĩ độ]
  • range, mảng các giới hạn thời gian tính bằng giây (900 = 15 phút, 600 = 10 phút, 300 = 5 phút). Liệt kê cái lớn nhất trước để các đa giác lồng nhau chính xác.
  • range_type, "time" để phân tích thời gian di chuyển, "distance" để các isochrone dựa trên khoảng cách
  • profile, chế độ di chuyển: "driving-car", "cycling-regular", "foot-walking", "driving-hgv" (phương tiện vận chuyển hàng nặng)

Phản Hồi:

{
  "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, ... }
    }
  ]
}

Thuộc tính value của mỗi tính năng là giới hạn thời gian tính bằng giây. Sử dụng điều này để gán màu khi hiển thị nhiều đa giác lồng nhau.

Các Trường Hợp Sử Dụng Thực Tế

Ánh xạ vùng giao hàng. Các nền tảng thương mại điện tử và giao hàng thực phẩm xác định các vùng dựa trên thời gian di chuyển, không phải khoảng cách. Vùng giao hàng 30 phút từ nhà bếp tối bao gồm một khu vực rất khác nhau tùy thuộc vào việc nó nằm ở trung tâm thành phố dày đặc hay công viên công nghiệp vùng ngoại ô. Isochrones phản ánh điều này một cách chính xác.

Phân tích khu vực bắt giữ cửa hàng. Lựa chọn vị trí bán lẻ bắt đầu với "bao nhiêu người sống trong bán kính 20 phút lái xe từ vị trí này?" Isochrones cho đa giác, dữ liệu điều tra dân số cung cấp dân số trong đó. Sự kết hợp này thúc đẩy các quyết định lựa chọn vị trí cho các nhà bán lẻ lớn.

Tìm kiếm bất động sản theo thời gian đi làm. Các nền tảng bất động sản cho phép người dùng tìm kiếm các bất động sản trong X phút từ nơi làm việc. Đây là một UX tốt hơn đáng kể so với tìm kiếm dựa trên bán kính vì nó phù hợp với cách mọi người thực sự đánh giá các vị trí.

Bảo phủ bệnh viện và chăm sóc sức khỏe. Các nhà lập kế hoạch chăm sóc sức khỏe sử dụng isochrones để xác định các dân số xa hơn thời gian ngưỡng từ các dịch vụ khẩn cấp. Isochrones từ nhiều bệnh viện có thể được kết hợp để hiển thị các khoảng trống bảo phủ.

Khu vực bắt giữ trường học. Chính quyền địa phương và cha mẹ sử dụng các đường isochrone thời gian di chuyển (thường đi bộ, không lái xe) để hiểu các khu vực bắt giữ thực tế cho các chỗ học.

Tiếp thị sự kiện. Hiển thị cho những người tham dự hội nghị những nhà hàng, khách sạn và điểm tham quan có thể tiếp cận được bằng cách đi bộ trong vòng 10 phút từ địa điểm tổ chức.

Real Estate Property Map tutorialRoute Optimization tutorial đều sử dụng phân tích isochrone như một thành phần của các quy trình làm việc tương ứng của họ.

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 /isochrone endpoint with a list of time limits
  • Receives GeoJSON feature collection with one polygon per time limit
  • Renders each polygon as a fill and line layer 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.

Thấy hữu ích? Chia sẻ nhé.

Về tác giả

Brent van der Heiden

Viết bởi

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. He writes about geospatial infrastructure, AI search visibility, and how location data powers the products people rely on every day.

Xem tất cả bài viết
Quay lại blog