Ưu ĐãiƯu đãi tháng: Tặng ngay 25.000 lượt gọi API miễn phí trải nghiệm cho doanh nghiệp!
ViGoMapsBản đồ số Việt Nam
Tài liệu tích hợp

Tài liệu ViGo Maps API

Tìm đường, ma trận khoảng cách, gợi ý địa điểm và geocode cho toàn Việt Nam, tất cả sau một endpoint duy nhất.

https://routing.vigodev.onlineThử trực tiếp ở Playground
Mục lục

Tổng quan

API bản đồ cho Việt Nam: tìm đường, ma trận khoảng cách, gợi ý địa điểm và geocode, tất cả sau một endpoint duy nhất.

text
https://routing.vigodev.online
NhómLàm được gì
Tìm kiếmGợi ý địa điểm theo từng ký tự, geocode xuôi và ngược
Định tuyếnTìm đường kèm chỉ dẫn từng bước tiếng Việt, tuyến thay thế
Ma trậnKhoảng cách và thời gian giữa nhiều điểm trong một request
Lộ trìnhSắp thứ tự điểm dừng tối ưu
Ảnh tĩnhẢnh PNG có vẽ sẵn tuyến, cho thông báo và biên lai
Hành chínhĐổi tên đơn vị hành chính cũ ↔ mới (sáp nhập 2025)
Nâng caoKhớp chuỗi GPS vào đường
Thông sốGiá trị
Vùng phủToàn Việt Nam
Phương tiệnÔ tô
Ngôn ngữTiếng Việt (tên địa điểm và chỉ dẫn)
Định dạngJSON theo chuẩn RESTful
Nguồn dữ liệuNhiều nguồn mở, cập nhật theo chu kỳ

Thử ngay các endpoint ở khu Playground trên trang chủ, mỗi thao tác đều hiện request và response thật.

Bắt đầu

Gọi thử

bash
curl 'https://routing.vigodev.online/v2/place/autocomplete?input=ben+xe+my+dinh&api_key=YOUR_API_KEY'

Trong ứng dụng

javascript
const BASE = 'https://routing.vigodev.online';
const KEY  = process.env.VIGO_MAPS_API_KEY;

const res  = await fetch(
  `${BASE}/Direction?origin=21.0285,105.8542&destination=21.2187,105.8047&api_key=${KEY}`
);
const data = await res.json();

if (data.status !== 'OK') throw new Error(data.status);
const leg = data.routes[0].legs[0];
console.log(leg.distance.text, leg.duration.text);   // "25,8 km"  "27 phút"

Xác thực

Mọi endpoint đều yêu cầu khoá API. Gửi qua query api_key, hoặc header X-API-Key, hoặc tham số key nếu bạn đang dùng client viết cho Google Maps.

bash
curl 'https://routing.vigodev.online/Geocode?latlng=21.0278,105.8342&api_key=vg_…'

Khoá có phạm vi. Phạm vi geo đủ cho toàn bộ API trong tài liệu này. Nhóm endpoint nâng cao cần phạm vi osrm riêng, thiếu thì nhận REQUEST_DENIED kèm thông báo “api_key không có quyền gọi endpoint này”, khác hẳn thông báo khoá sai.

Đừng nhúng khoá vào app người dùng cuối

Khoá đặt trong mã client là công khai, ai cũng đọc được. Hãy gọi qua backend của bạn để kiểm soát lưu lượng và không phơi khoá ra ngoài.

Lưu ý khi tích hợp

1. Thứ tự toạ độ

API này nhận lat,lng, vĩ độ trước, giống Google Maps. Riêng nhóm endpoint nâng cao lại nhận lon,lat. Đảo nhầm vẫn trả status "OK" với quãng đường sai hoàn toàn, không nổ lỗi ở đâu cả.

2. compound.province là tên trần

Trả về "Hà Nội", không phải "Thành phố Hà Nội"; "Ninh Bình" chứ không phải "Tỉnh Ninh Bình". Tiền tố hành chính luôn được bỏ, ở cả ba cấp province / district / commune.

Nếu hệ thống của bạn so khớp tên tỉnh theo chuỗi chính xác hoặc theo slug, hãy đối chiếu với dạng trần. Trộn hai dạng là nguồn sai lệch âm thầm, không có lỗi nào được ném ra.

3. Ô ma trận không có đường nối

Trả status "ZERO_RESULTS" và không có trường distance. Đừng mặc định thiếu giá trị là 0: hai điểm không nối được với nhau khác hẳn hai điểm cách nhau 0 km, và nhầm chỗ này thường dẫn thẳng tới sai số tiền.

4. Gửi location khi gợi ý địa điểm

Không có bias thì kết quả xếp theo độ nổi tiếng toàn quốc. App luôn biết vị trí người dùng, gửi lên thì kết quả sát hơn hẳn:

Truy vấn "benh vien da khoa Hung Yen"Kết quả đầu
không biasBệnh viện Đa khoa Hưng Hà
location=20.6464,106.0511Bệnh viện Đa khoa tỉnh Hưng Yên

Đừng đặt bias cố định vào một thành phố ở phía server: mọi truy vấn nhắc tên tỉnh khác sẽ bị bóp méo.

Gợi ý địa điểm

http
GET /v2/place/autocomplete?input=&location=lat,lng&limit=
Tham sốBắt buộcGhi chú
inputGõ không dấu vẫn khớp. Gõ dở cũng khớp.
locationkhônglat,lng — ưu tiên kết quả gần đây
limitkhôngMặc định 10, trần 50
api_keyThiếu sẽ nhận REQUEST_DENIED
json
{
  "status": "OK",
  "predictions": [{
    "description": "Bến xe Mỹ Đình, 20 Đường Phạm Hùng, Mỹ Đình 2, Hà Nội",
    "place_id": "vg1_eyJuIjoi…",
    "structured_formatting": {
      "main_text": "Bến xe Mỹ Đình",
      "secondary_text": "20 Đường Phạm Hùng, Mỹ Đình 2, Hà Nội"
    },
    "compound": { "province": "Hà Nội", "district": "Nam Từ Liêm", "commune": "Mỹ Đình 2" },
    "geometry": { "location": { "lat": 21.0284, "lng": 105.7783 } }
  }]
}

geometry và compound là phần mở rộng so với API tiêu chuẩn. Nhờ chúng, luồng chọn điểm đón/trả bỏ được hẳn một vòng gọi /v2/place/detail.

Chi tiết địa điểm

http
GET /v2/place/detail?place_id=

place_id tự chứa dữ liệu (base64url). Endpoint này chỉ giải mã nên rất nhanh và không bao giờ hết hạn. place_id do nhà cung cấp khác cấp sẽ trả ZERO_RESULTS.

POI quanh một điểm

http
GET /v2/place/nearby?latlng=&radius=&limit=&category=
Tham sốBắt buộcGhi chú
latlnglat,lng
radiuskhôngmét. Mặc định 500, trần 5000
limitkhôngMặc định 10, trần 50
categorykhôngLọc theo loại, ví dụ school

Trả về đúng shape predictions như autocomplete, đã sắp theo khoảng cách. Đây là câu hỏi “chỗ này có gì”, khác hẳn autocomplete vốn là “tìm giúp tôi chuỗi này”.

Dùng khi người dùng rê ghim trên bản đồ. Reverse geocode chỉ trả một địa chỉ chuẩn của điểm đó chứ không trả danh sách, nên trước đây nhiều app phải lấy chuỗi địa chỉ đó làm từ khoá cho autocomplete, và kết quả không bao giờ có POI người dùng đang trỏ vào.

Rỗng thì server tự nới bán kính một lần lên 1500 m rồi thôi.

Geocode & reverse

http
GET /Geocode?latlng=lat,lng     # toạ độ → địa chỉ
GET /Geocode?address=…          # địa chỉ → toạ độ
GET /v2/geocode?latlng=lat,lng  # alias
json
{
  "status": "OK",
  "results": [{
    "formatted_address": "79 Phố Đinh Tiên Hoàng, Hàng Bạc, Hoàn Kiếm, Hà Nội",
    "name": "Uỷ ban nhân dân thành phố Hà Nội",
    "geometry": { "location": { "lat": 21.0285, "lng": 105.8542 } },
    "compound": { "province": "Hà Nội", "district": "Hoàn Kiếm", "commune": "Hàng Bạc" }
  }]
}

Reverse geocode gộp nhiều kết quả rồi mới trả về: điểm gần nhất thường là ranh giới tỉnh/thành không có chi tiết, phải lấy thêm các đối tượng lân cận mới đủ compound.

Chỉ đường

http
GET /Direction?origin=lat,lng&destination=lat,lng&vehicle=car
      &waypoints=lat,lng|lat,lng      # tuỳ chọn, điểm trung gian
      &alternatives=true              # tuỳ chọn, tuyến thay thế
json
{
  "status": "OK",
  "routes": [{
    "legs": [{
      "distance": { "value": 25838, "text": "25,8 km" },
      "duration": { "value": 1640,  "text": "27 phút" },
      "steps": [{
        "html_instructions": "Rẽ phải vào Phố Đinh Tiên Hoàng",
        "maneuver": "turn-right",
        "distance": { "value": 359, "text": "359 m" },
        "polyline": { "points": "…" }
      }]
    }],
    "overview_polyline": { "points": "…" }
  }]
}

html_instructions là text thuần tiếng Việt, không có thẻ HTML, render thẳng được. Phủ đủ 17 loại thao tác: rẽ, nhập làn, vòng xuyến, đường dẫn, cuối đường, quay đầu…

polyline mã hoá theo chuẩn precision 5, giải mã bằng đúng thư viện bạn đang dùng cho Google Maps.

duration không tính kẹt xe

duration là thời gian chạy tự do, suy từ tốc độ giới hạn của đường. Muốn ETA sát thực tế phải nhân hệ số theo khung giờ.

Ma trận khoảng cách

http
GET /v2/distancematrix?origins=lat,lng|lat,lng&destinations=lat,lng&vehicle=car
json
{
  "rows": [{
    "elements": [
      { "status": "OK", "distance": {"value": 25838, "text": "25,8 km"},
                        "duration": {"value": 1640,  "text": "27 phút"} },
      { "status": "ZERO_RESULTS" }
    ]
  }]
}

Nhiều điểm ngăn cách bằng | hoặc ;. Một request ma trận rẻ hơn N request /Direction rất nhiều vì dùng chung quá trình tìm kiếm, luôn ưu tiên nó khi cần so nhiều cặp.

Chi phí tăng theo bình phương

500 điểm nghĩa là 250.000 cặp mỗi request. Endpoint này bị giới hạn nhịp gọi chặt hơn phần còn lại vì lý do đó.

Sắp thứ tự điểm dừng

http
GET /trip?origin=lat,lng&waypoints=lat,lng;lat,lng&destination=lat,lng&vehicle=car

Sắp lại thứ tự ghé sao cho tổng quãng đường ngắn nhất. Bỏ destination thì lộ trình quay về điểm đầu.

json
{
  "code": "Ok",
  "trips": [{
    "distance": 263123, "duration": 12960, "geometry": "…",
    "legs": [{ "distance": 100, "duration": 60, "summary": "QL1A", "steps": [ … ] }]
  }],
  "waypoints": [
    { "waypoint_index": 0, "trips_index": 0, "location": [105.85, 21.02], "place_id": "vg1_…" }
  ]
}

waypoint_index là thứ tự ghé tối ưu của điểm đó, không phải thứ tự bạn gửi lên. Các bước trong legs[].steps[] cũng có html_instructions tiếng Việt như /Direction.

Khớp vệt GPS vào đường

http
GET /v2/match?path=&radius=&timestamps=
Tham sốBắt buộcGhi chú
pathChuỗi lat,lng ngăn bởi ; hoặc |. Từ 2 tới 500 điểm
radiuskhôngBán kính bám đường, mét. Mặc định 25, cho 1–200
timestampskhôngMốc thời gian từng điểm, giúp loại phương án phi lý về tốc độ

Trả matchings[] (có confidence, overview_polyline) và snapped_points[]. Điểm không bám được vào đường nào thì là null nhưng giữ nguyên vị trí trong mảng, chỉ số luôn khớp thứ tự điểm bạn gửi lên, đừng lọc bỏ.

Dùng endpoint này, đừng gọi /match/v1/. Đường thô thuộc phạm vi khoá osrm, cùng nhóm với /table/v1/ vốn tốn CPU theo N². Khoá chỉ có phạm vi geo gọi đường thô sẽ nhận REQUEST_DENIED.

confidence thấp không phải lỗi: nó phản ánh vệt GPS thưa hoặc không bám theo một tuyến đường có thật.

Ảnh bản đồ tĩnh

http
GET /staticmap/route?origin=lat,lng&destination=lat,lng&width=600&height=400&color=%232563eb

Trả về ảnh PNG (không phải JSON) có vẽ sẵn tuyến đường, khung hình tự khớp theo tuyến. Dùng cho thông báo đẩy, biên lai, email, những chỗ không nhúng được bản đồ tương tác.

Tham sốMặc địnhGhi chú
width / height600 × 400Tối đa 1280
color#2563ebMàu nét vẽ tuyến

Endpoint này gọi định tuyến rồi render ảnh nên đắt hơn hẳn một request JSON, bị giới hạn nhịp gọi chặt hơn. Ảnh được cache 5 phút.

Đổi tên hành chính cũ ↔ mới

http
GET /migrate-address?address=…&migrateType=1

Đợt sáp nhập 2025 gộp 63 tỉnh thành 34 và đổi tên hàng nghìn phường/xã. Endpoint này chuyển địa chỉ giữa hai dạng, dựa trên danh mục hành chính chính thức (10.972 dòng ánh xạ).

Tham sốGhi chú
addressChuỗi địa chỉ tự do. Gõ không dấu vẫn nhận
migrateType1 = cũ→mới (mặc định), 2 = mới→cũ
json
{
  "status": "OK",
  "result": {
    "display": "An Hội Tây, Thành phố Hồ Chí Minh",
    "compound": { "province": "Thành phố Hồ Chí Minh", "district": "", "commune": "An Hội Tây" },
    "matched":  { "ward": "Phường 12", "district": "Quận Gò Vấp", "province": "Thành phố Hồ Chí Minh" },
    "candidates": [ … ],
    "partial": false
  }
}

Hai chiều không đối xứng. Cũ→mới gần như luôn một-một. Chiều ngược lại thì 3.172/3.320 phường mới gộp từ nhiều phường cũ, đó chính là bản chất của sáp nhập, nên candidates luôn là mảng và không thể có một đáp án duy nhất.

partial: true nghĩa là chỉ tra được tới cấp tỉnh (không nhận ra phường/xã trong chuỗi). Vẫn hữu ích, vì tên tỉnh mới là phần hay dùng để phân vùng và định tuyến.

Endpoint nâng cao

Những thao tác nằm ngoài bộ API tiêu chuẩn nên không được bọc lại, gọi dạng gốc trên cùng hostname.

Nhóm này nhận lon,lat

Kinh độ trước, ngược với toàn bộ phần trên của tài liệu. Đảo nhầm vẫn ra kết quả hợp lệ nhưng sai hoàn toàn.

ViệcEndpoint
Khớp chuỗi GPS vào đường/match/v1/driving/{lon,lat};…
Sắp thứ tự ghé tối ưu/trip/v1/driving/{lon,lat};…
Bám toạ độ vào đoạn đường/nearest/v1/driving/{lon,lat}?number=3
Tìm đường, dạng gốc/route/v1/driving/{lon,lat};…
Ma trận, dạng gốc/table/v1/driving/{lon,lat};…

Đoạn driving trong URL bị bỏ qua hoàn toàn: dịch vụ hiện chỉ phục vụ ô tô. Gõ /bike/ vẫn ra kết quả xe hơi.

Chuyển từ Google Maps API

Đổi base URL là xong. Các đường dưới đây nhận đúng tham số của Google và trả đúng cấu trúc của Google, kể cả address_components[], terms[] và matched_substrings[].

Đường của GoogleTương ứng
/maps/api/place/autocomplete/jsonGợi ý địa điểm
/maps/api/place/details/jsonChi tiết địa điểm
/maps/api/place/nearbysearch/jsonPOI quanh một điểm
/maps/api/geocode/jsonGeocode & reverse
/maps/api/directions/jsonChỉ đường
/maps/api/distancematrix/jsonMa trận khoảng cách
javascript
// Trước
const BASE = 'https://maps.googleapis.com';
// Sau
const BASE = 'https://routing.vigodev.online';

const r = await fetch(`${BASE}/maps/api/geocode/json?latlng=21.0278,105.8342&key=${KEY}`);
const d = await r.json();
d.results[0].address_components   // street_number · route · administrative_area_level_1…

Khác biệt cần biết

  • Cấp phường/xã mang hai loại cùng lúc. Việt Nam có ba cấp hành chính, còn Google không thống nhất một loại cho cấp thấp nhất, nên component của phường/xã được gắn cả administrative_area_level_3 lẫn sublocality_level_1.
  • Đường Google không bao giờ trả mã HTTP khác 200. Kể cả khi lỗi, response vẫn là 200 kèm trường status đúng kiểu Google, vì thư viện client của Google ném lỗi trước khi đọc tới thân response.
  • Không có photos, reviews, rating, opening_hours. Đó là dữ liệu Google tự thu thập. Trường nào không có thì vắng hẳn chứ không trả rỗng giả.
  • Lớp tương thích Google chỉ thêm vào, không thay thế: hai bộ đường chạy song song trên cùng một dịch vụ và dùng chung một khoá.

Tham số khoá dùng tên key của Google, nhưng api_key cũng được chấp nhận.

Mã lỗi

statusNghĩaNên làm gì
OKThành công
ZERO_RESULTSKhông có kết quả, nhưng request hợp lệHiện “không tìm thấy”, đừng retry
INVALID_REQUESTThiếu hoặc sai tham số (HTTP 400)Sửa code gọi
REQUEST_DENIEDThiếu khoá, khoá sai, hoặc khoá không đủ phạm viKiểm tra api_key
UNKNOWN_ERRORDịch vụ phía sau lỗi hoặc quá hạn (HTTP 502)Retry, hoặc rơi về nhà cung cấp khác
NOT_FOUNDSai đường dẫn (HTTP 404)Sửa code gọi
HTTP 429Vượt rate-limit, giãn nhịp gọi

Phân biệt ZERO_RESULTS với UNKNOWN_ERROR là quan trọng: cái đầu là câu trả lời, cái sau là sự cố cần retry.

Hiệu năng

EndpointGọi nội bộQua internet
/v2/place/autocomplete38 ms240 ms
/Geocode (reverse)28 ms
/Direction18 ms124 ms
/v2/distancematrix18 ms
Endpoint nâng cao5–14 ms115 ms

Backend chạy cùng máy nên gọi qua địa chỉ nội bộ để tránh vòng ra internet. Chênh lệch khoảng 100 ms là round-trip mạng, không phải do dịch vụ chậm.

Giới hạn

Giới hạnGiá trị
Số toạ độ mỗi request ma trận500
Số toạ độ mỗi request tìm đường500
Số toạ độ mỗi request khớp GPS500
Số điểm dừng mỗi request sắp thứ tự100
Số tuyến thay thế3
Vùng phủViệt Nam

Vượt giới hạn trả INVALID_REQUEST. Gọi quá nhanh trả HTTP 429, các endpoint ma trận bị siết chặt hơn phần còn lại.

Khi có sự cố

Triệu chứngNguyên nhân thường gặp
Quãng đường sai hoàn toàn nhưng status: OKĐảo lat,lng ↔ lon,lat
ZERO_RESULTS ở mọi truy vấnToạ độ ngoài vùng dữ liệu Việt Nam
So khớp tên tỉnh không raCode đang chờ tên có tiền tố; API trả tên trần
Khoảng cách ra 0Ô ZERO_RESULTS của ma trận bị coi là 0 km
Gợi ý địa điểm lệch tỉnhThiếu location, hoặc bias cố định phía server
REQUEST_DENIEDThiếu api_key, hoặc khoá không đủ phạm vi
HTTP 429Vượt rate-limit
Bị chặn CORSGọi thẳng cổng nội bộ thay vì qua hostname công khai

Khu Playground trên trang chủ hiện request và response thật của từng thao tác, là cách nhanh nhất để đối chiếu xem client gọi sai ở đâu.

Cần khoá API hoặc hỗ trợ tích hợp?

Đăng ký nhận 25.000 lượt gọi miễn phí mỗi tháng, hoặc gọi hotline để kỹ sư hỗ trợ trực tiếp quá trình chuyển đổi.