Dẫn nhập: Nỗi ức chế mang tên “cửa hàng gần nhất”
Nếu bạn từng ghé thăm website của một chuỗi bán lẻ, siêu thị tiện lợi hay thương hiệu cà phê lớn tại Việt Nam, chắc hẳn bạn đã không ít lần sử dụng tính năng “Tìm cửa hàng gần bạn nhất” (Store Locator). Kịch bản thông thường rất quen thuộc: bạn nhấn cho phép trình duyệt truy cập định vị GPS, hệ thống tính toán vài phần trăm giây và gợi ý cho bạn một chi nhánh được gắn nhãn “Cách bạn chỉ 600 mét”.
Thế nhưng, khi bạn xỏ giày, dắt xe máy ra đường và bật ứng dụng chỉ đường để đi tới đó, thực tế lại hoàn toàn khác:
Chi nhánh đó nằm ở bên kia của một trục đại lộ có dải phân cách cứng (như đường Phạm Hùng, Nguyễn Trãi hay đại lộ Mai Chí Thọ). Bạn buộc phải đi xuôi chiều gần 1,5 km mới tìm thấy điểm mở dải phân cách để quay đầu xe, rồi lại đi ngược lại. Tổng quãng đường đi xe máy lên tới 2,4 km và ngốn mất gần 10 phút.
Trớ trêu hơn, ngay phía sau lưng bạn — trong một con ngõ thông thoáng cách đó chỉ 900 mét đi bộ hoặc xe máy — lại có một chi nhánh khác cùng chuỗi nhưng không hề được hệ thống gợi ý lên đầu, đơn giản vì theo “đường thẳng hình học”, nó cách bạn 800 mét!
Hiện tượng này bắt nguồn từ một sai lầm kiến trúc rất kinh điển mà rất nhiều đội ngũ phát triển web và ứng dụng tại Việt Nam vẫn đang mắc phải: sử dụng công thức tính khoảng cách đường chim bay (Haversine Formula) để quyết định cửa hàng gần nhất cho người dùng.
Bài viết này sẽ cùng bạn bóc tách bản chất hình học của vấn đề, phân tích các rào cản giao thông đô thị đặc thù tại Việt Nam (sông hồ, dải phân cách, đường một chiều, cấm rẽ), và hướng dẫn chi tiết cách triển khai tính năng Store Locator hiện đại bằng Mapvina GL JS kết hợp Mapvina Distance Matrix API và Directions API v1. Chúng tôi chia sẻ trọn vẹn cả mã nguồn chạy ngay một file HTML kèm backend proxy mẫu để bạn có thể áp dụng ngay cho dự án của doanh nghiệp mình.
1. Bản chất hình học: Vì sao công thức Haversine “bó tay” trước giao thông đô thị?
1.1. Công thức Haversine tính toán điều gì?
Về mặt toán học thuần túy, Trái Đất là một khối cầu (hoặc chính xác hơn là hình phỏng cầu elipsoid). Khi bạn có hai tọa độ địa lý (lat_1, lng_1) và (lat_2, lng_2), công thức Haversine sẽ tính toán độ dài cung tròn lớn nhất (Great-circle distance) nối liền hai điểm đó xuyên qua bề mặt quả địa cầu:
Δσ = 2 \arcsin\left(\sqrt{\sin^2\left(\frac{Δφ}{2}\right) + \cos(φ₁)\cos(φ₂)\sin^2\left(\frac{Δλ}{2}\right)}\right)
d = R · Δσ
(Trong đó φ là vĩ độ, λ là kinh độ tính bằng radian, và R ≈ 6.371 km là bán kính Trái Đất).
Ưu điểm duy nhất của công thức này là tốc độ cực nhanh: máy tính chỉ mất vài phép tính lượng giác cơ bản, hoàn thành trong chưa đầy 0,001 mili-giây trên CPU và không tốn một xu chi phí gọi API mạng nào. Do đó, các lập trình viên thường dùng nó để truy vấn cơ sở dữ liệu (ví dụ hàm ST_Distance_Sphere trong MySQL hoặc PostGIS) rồi sắp xếp kết quả ORDER BY distance ASC LIMIT 5.
Nhưng nhược điểm chết người của nó là: khách hàng của bạn không phải là loài chim biết bay. Khách hàng di chuyển bằng xe máy, ô tô, xe đạp hoặc đi bộ trên một mạng lưới đường sá chằng chịt, bị giới hạn bởi luật giao thông, địa hình tự nhiên và quy hoạch hạ tầng đô thị.

1.2. Ba cái bẫy giao thông đô thị kinh điển tại Việt Nam
Hãy cùng nhìn vào 3 trường hợp thực nghiệm thực tế mà bất kỳ ai sống tại Hà Nội hay TP. Hồ Chí Minh đều từng trải qua:
Kịch bản 1: Rào cản sông hồ tự nhiên (Ví dụ: Hoàn Kiếm ➔ Long Biên)
Khách hàng đang đứng tại khu vực Bờ Hồ (Hoàn Kiếm, Hà Nội). Nếu đo đường chim bay qua sông Hồng sang khu đô thị bên Long Biên, khoảng cách thẳng chỉ khoảng 3,5 km. Trong khi đó, một chi nhánh khác nằm ở phố Thái Hà (Đống Đa) có khoảng cách chim bay là 4,2 km.
* Nếu xếp hạng theo Haversine: Hệ thống sẽ chỉ định cửa hàng Long Biên là “gần hơn”.
* Nhưng trên thực tế di chuyển: Để sang Long Biên, khách phải đi vòng qua Cầu Chương Dương hoặc Cầu Vĩnh Tuy với tổng quãng đường bộ lên tới 7,8 km, chịu cảnh kẹt xe giờ cao điểm và mất hơn 22 phút. Trong khi đi tới Thái Hà qua đường thẳng Tràng Thi – Nguyễn Thái Học chỉ mất 12 phút (4,5 km).
* Kết quả: Khách hàng tin lời website, lái xe đi Long Biên và cảm thấy bị lừa dối!
Kịch bản 2: Dải phân cách cứng & Điểm quay đầu xe (Ví dụ: Trục đường Phạm Hùng / Vành Đai 3)
Các tuyến đường vành đai lớn tại Hà Nội và TP.HCM (như Phạm Hùng, Khuất Duy Tiến, Võ Văn Kiệt, Phạm Văn Đồng) đều có dải phân cách bê tông kiên cố ở giữa tim đường để đảm bảo an toàn giao thông tốc độ cao.
* Khách hàng đứng ở phía bến xe, nhìn sang đối diện đường thấy ngay biển hiệu cửa hàng cách mình vỏn vẹn 120 mét đường chim bay.
* Nhưng phương tiện không thể bay qua dải phân cách. Xe máy buộc phải chạy xuôi theo chiều đường hơn 1 km để đến nút giao Mai Dịch hoặc điểm mở quay đầu, sau đó mới đi ngược chiều lại để tiếp cận cửa hàng. Tổng cự ly thực tế là 2,4 km — dài gấp 20 lần (2.000%) khoảng cách thẳng!
Kịch bản 3: Mạng lưới đường một chiều và mê cung ngõ ngách (Ví dụ: Phố Cổ Hà Nội, Quận 1 / Quận 3 TP.HCM)
Tại các quận nội thành lịch sử, mật độ đường một chiều cực kỳ dày đặc (như Lý Thường Kiệt, Hai Bà Trưng, Hàng Bài, Pasteur, Lý Tự Trọng).
* Một cửa hàng nằm cách bạn đúng 300 mét theo hướng ngược lại.
* Vì là đường một chiều, bạn buộc phải rẽ phải qua 3 con phố khác tạo thành một hình chữ nhật khép kín mới có thể tiếp cận được cửa hàng, nâng quãng đường di chuyển lên 1,5 km và thời gian gấp 3 đến 4 lần.
1.3. Khái niệm “Detour Factor” (Hệ số đường vòng)
Trong ngành khoa học giao thông vận tải và hệ thống thông tin địa lý (GIS), tỷ lệ giữa quãng đường di chuyển thực tế trên mạng lưới đường bộ (D_network) và khoảng cách đường thẳng (D_euclidean) được gọi là Detour Factor (DF):
DF = \frac{D_network}{D_euclidean}
- Tại các vùng đồng bằng thưa thớt có quy hoạch ô bàn cờ (Grid network) như một số thành phố ở Bắc Mỹ, DF trung bình dao động từ 1,2 đến 1,3 (+20\% – +30\%).
- Nhưng tại các đô thị đặc thù nhiều sông hồ, đường cong tự nhiên và ngõ ngách chằng chịt như Hà Nội và TP.HCM, nghiên cứu thực nghiệm cho thấy DF nội đô thường xuyên chạm ngưỡng 1,6 đến 2,2 (+60\% – +120\%), cá biệt ở các điểm quay đầu trên đường đôi có thể vượt quá 5,0 đến 10,0!
Rõ ràng, việc dựa vào một thước đo có độ lệch lên tới hàng trăm phần trăm để ra quyết định kinh doanh và trải nghiệm khách hàng là một lỗ hổng nghiêm trọng.
2. Kiến trúc giải pháp: Mô hình lọc 2 tầng (Two-Tier Spatial Filtering)
Biết được nhược điểm của Haversine, nhiều lập trình viên nghĩ ngay đến giải pháp: “Vậy thì mỗi khi khách tìm cửa hàng, ta cứ gửi toàn bộ danh sách cửa hàng của chuỗi vào Distance Matrix API để tính toán!”.
Khoan đã! Đó là một cái bẫy nguy hiểm khác về chi phí và hiệu năng.
Giả sử chuỗi bán lẻ của bạn có 500 cửa hàng trên toàn quốc:
Nếu bạn gửi cả 500 cửa hàng vào API ma trận khoảng cách cho mỗi lượt khách truy cập, chi phí request sẽ tăng theo cấp số nhân (1 origin × 500 destinations = 500 phần tử ma trận).
Kích thước payload mạng rất lớn, độ trễ xử lý tăng cao, và lãng phí nghiêm trọng ngân sách hạ tầng (bởi vì một khách hàng ở Cầu Giấy – Hà Nội thì chắc chắn không bao giờ cần tính toán lộ trình đến cửa hàng ở Cần Thơ hay Đà Nẵng).
Giải pháp chuẩn công nghiệp được các nền tảng hàng đầu thế giới và tại Việt Nam áp dụng là Mô hình lọc 2 tầng (Two-Tier Spatial Filtering):

Cơ chế hoạt động của 2 tầng:
- Tầng 1 – Lọc thô bằng Bounding Box / Haversine (Offline / In-Memory):
- * Thực hiện trực tiếp trên trình duyệt của máy khách (Client-side) hoặc trong RAM/Redis của máy chủ backend.
- * Tính khoảng cách hình học thô từ vị trí khách tới 500 cửa hàng, chỉ giữ lại Top 5 đến 8 cửa hàng có khoảng cách chim bay gần nhất (thường nằm trong bán kính dưới 10 km).
- * Chi phí: 0 VNĐ. Thời gian xử lý: Chưa đầy 0,2 mili-giây.
- Tầng 2 – Lọc tinh bằng Mapvina Distance Matrix API:
- * Hệ thống chỉ gửi đúng 1 điểm xuất phát và 5-8 điểm đích ứng viên từ Tầng 1 lên Mapvina Distance Matrix API với profile phương tiện phù hợp (ví dụ
motocho xe máy hoặccarcho ô tô). - * API Mapvina trả về ma trận chứa chính xác quãng đường thực tế (tính bằng mét) và thời gian di chuyển dự kiến (ETA – tính bằng giây) dựa trên đồ thị giao thông thực tế của Việt Nam.
- * Hệ thống sắp xếp lại danh sách ứng viên theo tiêu chí: Thời gian di chuyển (
duration) thấp nhất xếp lên đầu. - * Chi phí: Chỉ tính đúng 1 request quy mô nhỏ (1 × 5). Độ trễ: Khoảng 100 – 150 ms.
Nhờ kiến trúc 2 tầng này, bạn vừa cắt giảm được 98% lượng tính toán mạng và chi phí API, vừa đảm bảo khách hàng luôn nhìn thấy chi nhánh đến nhanh nhất mà không lo ngại các “cái bẫy” dải phân cách hay sông ngòi cản trở.
3. Khám phá bộ đôi API của Mapvina phục vụ Store Locator
Để hiện thực hóa giải pháp trên, Mapvina cung cấp hai dịch vụ API trọng tâm nằm trong hệ sinh thái hạ tầng bản đồ số Việt Nam:
3.1. Mapvina Distance Matrix API v1 (Ma trận khoảng cách và thời gian)
Distance Matrix API cho phép tính toán khoảng cách và thời gian di chuyển giữa nhiều cặp điểm xuất phát (sources) và điểm đến (destinations) chỉ trong một lượt gọi HTTP GET duy nhất.
- Cấu trúc Endpoint:
- “`text
- GET https://maps.mapvina.com/distance-matrix/v1/{PROFILE}/{COORDINATES}?sources={SOURCES}&destinations={DESTINATIONS}&annotations=distance,duration&key={API_KEY}
- “`
- Các tham số then chốt:
{PROFILE}: Phương tiện lưu thông. Hỗ trợ cực tốt đặc thù Việt Nam vớimoto(xe máy – tính được ngõ hẻm và đường cấm ô tô),car(ô tô),truck(xe tải),walk(đi bộ).{COORDINATES}: Chuỗi tọa độ theo thứ tự{lng1},{lat1};{lng2},{lat2};...phân tách bằng dấu chấm phẩy (chú ý: kinh độlngviết trước, vĩ độlatviết sau).sources: Chỉ mục (index) của điểm xuất phát (trong bài toán Store Locator, điểm của khách là index0).destinations: Danh sách chỉ mục của các cửa hàng ứng viên, phân tách bằng dấu chấm phẩy (ví dụ1;2;3;4;5).annotations: Chỉ định dữ liệu cần trả về, bao gồmdistance(quãng đường theo mét) vàduration(thời gian theo giây).key: API Key của bạn (có thể dùngpublic_keycho môi trường thử nghiệm).
- Dữ liệu JSON phản hồi thực tế từ máy chủ Mapvina:
- “`json
- {
- “code”: “Ok”,
- “distances”: [
- [ 1034.2, 2150.8, 5420.1, 7840.5, 2410.0 ]
- ],
- “durations”: [
- [ 142.5, 310.2, 780.0, 1340.5, 480.1 ]
- ]
- }
- “`
Nhìn vào mảng durations[0], ta biết ngay cửa hàng thứ nhất chỉ mất 142 giây (~2,3 phút), trong khi cửa hàng thứ tư mất tới 1.340 giây (~22,3 phút).
3.2. Mapvina Directions API v1 (Chỉ đường từng bước)
Sau khi khách hàng chọn được cửa hàng ưng ý nhất, họ cần xem ngay tuyến đường di chuyển trực quan trên bản đồ. Directions API v1 sẽ tính toán chi tiết hình học lộ trình (Geometry Polyline) và danh sách chỉ dẫn từng ngã rẽ (Turn-by-turn steps).
- Cấu trúc Endpoint:
- “`text
- GET https://maps.mapvina.com/route/v1/{PROFILE}/{COORDINATES}.json?geometries=geojson&steps=true&overview=full&key={API_KEY}
- “`
- Tham số nổi bật:
geometries=geojson: Trả về hình học đường đi dưới định dạng GeoJSONLineString, giúp bạn nạp thẳng vào lớp layer của Mapvina GL JS mà không cần giải mã polyline phức tạp.steps=true: Trả về danh sách chi tiết các ngã rẽ (ví dụ: “Rẽ phải vào đường Xuân Thủy”, “Đi thẳng theo Phạm Hùng 900m”).
4. Xây dựng ứng dụng Store Locator thực tế: Chạy ngay một file HTML
Để bạn không phải tốn thời gian thiết lập môi trường phức tạp, chúng tôi đã đóng gói toàn bộ giao diện, bản đồ số vector tiles, thuật toán lọc 2 tầng, so sánh thời gian thực và vẽ lộ trình vào duy nhất một file HTML có tên quickstart-store-locator.html. Bạn có thể lưu về máy tính và nhấp đúp chuột để chạy ngay trên bất kỳ trình duyệt nào.

4.1. Mã nguồn hoàn chỉnh (Client-Side Quickstart)
Dưới đây là phần mã nguồn lõi bằng JavaScript thể hiện trọn vẹn quy trình lọc và hiển thị. Bạn có thể sao chép hoặc tải trọn gói file mẫu ở phần cuối bài:
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Mapvina Store Locator - Tìm Cửa Hàng Gần Nhất</title>
<!-- Nhúng SDK Mapvina GL JS và CSS chuẩn -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/mapvina-gl.css">
<script src="https://unpkg.com/[email protected]/dist/mapvina-gl.js"></script>
<style>
/* Bố cục 2 cột hiện đại: Cột trái danh sách (400px), Cột phải bản đồ (100%) */
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: flex; height: 100vh; }
#sidebar { width: 420px; border-right: 1px solid #e2e8f0; display: flex; flex-direction: column; background: #fff; }
#map { flex: 1; height: 100%; background: #f1f5f9; }
</style>
</head>
<body>
<div id="sidebar">
<div style="padding:16px; border-bottom:1px solid #e2e8f0; background:#f8fafc;">
<h3 style="margin:0 0 10px 0; font-size:16px;">Mapvina Store Locator</h3>
<div id="diffAlert" style="background:#fffbeb; border:1px solid #fde68a; padding:8px 12px; border-radius:6px; font-size:12px; color:#92400e;">
Đang phân tích sai lệch lộ trình...
</div>
</div>
<div id="storeList" style="flex:1; overflow-y:auto; padding:12px 16px;"></div>
</div>
<div id="map"></div>
<script>
const PUBLIC_KEY = 'public_key'; // Khóa dùng thử công khai của Mapvina
const STYLE_URL = `https://maps.mapvina.com/styles/v2/streets.json?key=${PUBLIC_KEY}`;
// 1. Dữ liệu cửa hàng mẫu tại Hà Nội
const STORES = [
{ id: 's1', name: 'Mapvina Tràng Tiền', address: '54 Tràng Tiền, Hoàn Kiếm', lng: 105.8570, lat: 21.0250 },
{ id: 's2', name: 'Mapvina Liễu Giai', address: '29 Liễu Giai, Ba Đình', lng: 105.8115, lat: 21.0335 },
{ id: 's3', name: 'Mapvina Cầu Giấy', address: '122 Xuân Thủy, Cầu Giấy', lng: 105.7890, lat: 21.0375 },
{ id: 's4', name: 'Mapvina Long Biên', address: '27 Cổ Linh, Long Biên (Qua sông)', lng: 105.8985, lat: 21.0265 }
];
// Vị trí khách hàng (Mặc định: Bờ Hồ Hoàn Kiếm)
let userLoc = { lng: 105.8524, lat: 21.0285 };
let map;
// 2. Khởi tạo bản đồ Mapvina GL JS
map = new mapvinagl.Map({
container: 'map',
style: STYLE_URL,
center: [userLoc.lng, userLoc.lat],
zoom: 13
});
map.on('load', () => {
// Thiết lập nguồn dữ liệu vẽ tuyến đường (GeoJSON LineString)
map.addSource('route', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
map.addLayer({
id: 'route-line',
type: 'line',
source: 'route',
paint: { 'line-color': '#0284c7', 'line-width': 5 }
});
calculateNearestStores();
});
// 3. Thuật toán Lọc 2 tầng & Gọi Distance Matrix API
async function calculateNearestStores() {
// TẦNG 1: Tính Haversine lọc sơ bộ
STORES.forEach(s => {
s.crowKm = getCrowDistance(userLoc.lng, userLoc.lat, s.lng, s.lat);
});
// TẦNG 2: Gọi Mapvina Distance Matrix API (xe máy: moto)
const coords = [`${userLoc.lng},${userLoc.lat}`].concat(STORES.map(s => `${s.lng},${s.lat}`)).join(';');
const destIdx = STORES.map((_, i) => i + 1).join(';');
const matrixUrl = `https://maps.mapvina.com/distance-matrix/v1/moto/${coords}?sources=0&destinations=${destIdx}&annotations=distance,duration&key=${PUBLIC_KEY}`;
try {
const resp = await fetch(matrixUrl);
const data = await resp.json();
if (data.code === 'Ok' && data.durations) {
STORES.forEach((s, idx) => {
s.realMeters = data.distances[0][idx];
s.realSeconds = data.durations[0][idx];
});
// SẮP XẾP: Cửa hàng có thời gian di chuyển (duration) ít nhất lên đầu!
STORES.sort((a, b) => a.realSeconds - b.realSeconds);
renderUI();
drawRouteToStore(STORES[0]); // Tự động vẽ lộ trình đến cửa hàng số 1
}
} catch (err) {
console.error('Lỗi gọi Matrix API:', err);
}
}
// 4. Vẽ lộ trình chi tiết bằng Directions API v1
async function drawRouteToStore(store) {
const routeUrl = `https://maps.mapvina.com/route/v1/moto/${userLoc.lng},${userLoc.lat};${store.lng},${store.lat}.json?geometries=geojson&steps=true&overview=full&key=${PUBLIC_KEY}`;
const res = await fetch(routeUrl);
const json = await res.json();
if (json.code === 'Ok' && json.routes[0]) {
map.getSource('route').setData({
type: 'Feature',
geometry: json.routes[0].geometry
});
}
}
function getCrowDistance(lon1, lat1, lon2, lat2) {
const R = 6371;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180) * Math.cos(lat2*Math.PI/180) * Math.sin(dLon/2)**2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
}
function renderUI() {
const container = document.getElementById('storeList');
container.innerHTML = STORES.map((s, idx) => `
<div style="border:1px solid #e2e8f0; padding:12px; margin-bottom:10px; border-radius:6px; cursor:pointer;" onclick="drawRouteToStore(STORES[${idx}])">
<strong>#${idx + 1}. ${s.name}</strong><br>
<span style="color:#059669; font-weight:bold;">⚡ ${Math.round(s.realSeconds/60)} phút (${(s.realMeters/1000).toFixed(1)} km thực tế)</span><br>
<small style="color:#64748b;">Đường thẳng: ${s.crowKm.toFixed(1)} km · ${s.address}</small>
</div>
`).join('');
}
</script>
</body>
</html>

5. Nâng cấp môi trường Production: Bảo mật API Key qua Backend Proxy
Trong môi trường thực tế của doanh nghiệp, bạn không nên nhúng trực tiếp API Key riêng tư có quyền hạn cao vào mã nguồn HTML/JS phía máy khách. Kẻ xấu có thể mở công cụ F12 (Inspect Element) để sao chép khóa và tiêu tốn hạn mức (quota) của bạn.
Mô hình kiến trúc khuyến nghị là dựng một Backend Proxy nhỏ gọn (bằng Node.js Express, Python FastAPI, Go hoặc PHP). Máy chủ của bạn sẽ giữ bí mật MAPVINA_SECRET_KEY, nhận tọa độ khách hàng gửi lên, thực hiện Tầng 1 lọc thô trên RAM, gọi Tầng 2 lên Mapvina, và chỉ trả về kết quả JSON đã được tính toán cho client:
/**
* Backend Proxy mẫu bằng Node.js Express
* Endpoint: GET /api/v1/nearest-stores?lat=21.0285&lng=105.8524&vehicle=moto
*/
const express = require('express');
const axios = require('axios');
const app = express();
const MAPVINA_SECRET_KEY = process.env.MAPVINA_SECRET_KEY; // Lưu trong file .env máy chủ
app.get('/api/v1/nearest-stores', async (req, res) => {
const { lat, lng, vehicle = 'moto' } = req.query;
const userLat = parseFloat(lat);
const userLng = parseFloat(lng);
// 1. TẦNG 1: Lọc thô lấy Top 5 cửa hàng gần nhất theo Haversine (0 VNĐ)
const candidates = STORES_DATABASE
.map(s => ({ ...s, crowKm: haversineKm(userLng, userLat, s.lng, s.lat) }))
.sort((a, b) => a.crowKm - b.crowKm)
.slice(0, 5);
// 2. TẦNG 2: Gọi Mapvina Distance Matrix API với Secret Key bảo mật
const coords = [`${userLng},${userLat}`]
.concat(candidates.map(s => `${s.lng},${s.lat}`))
.join(';');
const destIndices = candidates.map((_, i) => i + 1).join(';');
const matrixUrl = `https://maps.mapvina.com/distance-matrix/v1/${vehicle}/${coords}?sources=0&destinations=${destIndices}&annotations=distance,duration&key=${MAPVINA_SECRET_KEY}`;
try {
const apiRes = await axios.get(matrixUrl, { timeout: 3000 });
const { distances, durations } = apiRes.data;
// Gắn kết quả thực tế và sắp xếp theo duration
const rankedStores = candidates.map((s, idx) => ({
...s,
realDistanceMeters: distances[0][idx],
durationMinutes: Math.round(durations[0][idx] / 60)
})).sort((a, b) => a.durationMinutes - b.durationMinutes);
return res.json({ success: true, stores: rankedStores });
} catch (err) {
// Dự phòng an toàn: nếu API có sự cố mạng, fallback về danh sách Haversine
return res.json({ success: true, stores: candidates });
}
});
6. Bảng so sánh toàn diện: Đường chim bay vs Mapvina 2-Tier Locator
Dưới đây là bảng tổng hợp so sánh trực tiếp giữa hai cách tiếp cận để các nhà quản lý sản phẩm (Product Managers) và trưởng nhóm kỹ thuật (Tech Leads) có góc nhìn khách quan trước khi ra quyết định:
| Tiêu chí so sánh | Phương pháp cũ (Chỉ dùng Haversine) | Gọi Matrix thô toàn bộ cửa hàng | Mô hình chuẩn Mapvina (Lọc 2 tầng) |
|---|---|---|---|
| Độ chính xác trải nghiệm | Kém (Dễ sai lệch từ +60\% đến +1.900\% tại đô thị) | Rất cao (Chuẩn thời gian thực tế) | Tuyệt đối (Chuẩn thời gian thực tế) |
| Xử lý dải phân cách / quay đầu | Không thể nhận biết | Nhận diện chính xác 100% | Nhận diện chính xác 100% |
| Xử lý đường 1 chiều / sông hồ | Bị lỗi xếp hạng nghiêm trọng | Tính đúng theo cầu cống và chiều xe | Tính đúng theo cầu cống và chiều xe |
Hỗ trợ phương tiện xe máy (moto) |
Không phân biệt phương tiện | Có | Có (Ưu tiên mạng lưới ngõ hẻm Việt Nam) |
| Số lượng phần tử Matrix API | 0 request | 1 × N (500 phần tử cho 500 điểm) | 1 × 5 (Chỉ 5 phần tử ứng viên) |
| Chi phí gọi API | 0 VNĐ | Rất tốn kém (nhanh cạn quota) | Cực thấp (Tiết kiệm 98% chi phí) |
| Thời gian phản hồi (Latency) | < 0,1 ms | 400 – 800 ms | 100 – 150 ms (Cực mượt) |
7. Tải về trọn bộ mã nguồn mẫu (Source Code Package)
Nhằm giúp đội ngũ phát triển của bạn tiết kiệm tối đa thời gian tích hợp, chúng tôi đã đóng gói toàn bộ mã nguồn demo bao gồm:
1. File ứng dụng web trực quan quickstart-store-locator.html (chạy ngay không cần cài đặt).
2. File máy chủ backend proxy bảo mật server_proxy_example.js (Node.js Express).
3. Tài liệu hướng dẫn cấu hình và triển khai chi tiết README.md.
- Tải file nén
.ziptrực tiếp tại đây: Tải trọn bộ Mapvina Store Locator Quickstart (.zip)
Lời kết & Đề xuất hành động
Một trang Store Locator không đơn thuần chỉ là một tiện ích bổ sung trên website; đó là điểm chạm đầu tiên kết nối không gian số (Digital) với doanh thu bán hàng tại điểm vật lý (Offline Retail). Một gợi ý sai lệch khiến khách hàng phải lái xe vòng vèo trong nắng nóng hay tắc đường có thể làm mất đi một khách hàng tiềm năng trước khi họ kịp bước chân vào cửa.
Bằng việc kết hợp sức mạnh hiển thị vector tiles siêu tốc của Mapvina GL JS cùng trí thông minh định tuyến của Distance Matrix API, doanh nghiệp của bạn hoàn toàn có thể mang đến một trải nghiệm tìm cửa hàng mượt mà, chuẩn xác đến từng ngã rẽ và hoàn toàn tối ưu về mặt chi phí vận hành.
Hãy truy cập ngay Mapvina Account Portal để đăng ký tài khoản trải nghiệm, thiết lập bảo vệ API Key và nâng cấp tính năng Store Locator cho hệ sinh thái của bạn ngay hôm nay!
Trích dẫn & Nguồn gốc nội dung
Nội dung chuyên môn, cấu trúc giải pháp và thông số tham chiếu trong bài viết này được đối soát trực tiếp từ hệ thống dịch vụ của trang gốc: MapVina.com ↗.
Tìm hiểu giải pháp Maps API của MapVina
Nền tảng bản đồ số và dữ liệu địa chỉ tối ưu cho thị trường Việt Nam.
Khám phá MapVina ↗