
Bạn nhận một bảng vài nghìn địa chỉ khách hàng và cần đưa lên bản đồ. Cách làm nhanh là gửi từng dòng vào API chuyển đổi địa chỉ sang tọa độ, lấy kết quả đầu tiên rồi lưu lại. Nhưng khi nhân viên mở bản đồ, một số ghim nằm giữa đường hoặc ở trung tâm phường. Tệp có đủ kinh độ, vĩ độ vẫn chưa trả lời được câu hỏi quan trọng: đây có đúng nơi khách cần đến hay không?
Với địa chỉ đã có tương đối đầy đủ, có thể bắt đầu bằng Search v2 của TrackAsia: gửi chuỗi query, nhận các kết quả rồi đánh giá vị trí trước khi chấp nhận.[1] Bài này đi theo luồng xử lý một tệp địa chỉ có thể kiểm tra và chạy tiếp sau gián đoạn. Nếu cần gợi ý khi người dùng đang gõ, đó là luồng Autocomplete khác; không cần biến một lần nhập tệp thành hàng loạt phiên gợi ý.
Cần tọa độ của khu vực, tòa nhà hay cổng nhận hàng?
Trước khi viết vòng lặp gọi API, hãy hỏi người sử dụng sẽ làm gì với điểm đó. Bản đồ thống kê khách theo khu vực có thể chấp nhận kết quả cấp địa bàn. Trang tìm chi nhánh cần đúng cơ sở. Điều phối giao hàng còn có thể cần đúng cổng, trong khi quầy tiếp nhận lại nằm bên trong một khu phức hợp. Cùng một kết quả tìm kiếm có thể đủ cho việc đầu tiên và thiếu cho việc cuối cùng.
Một quy tắc dễ triển khai là: chỉ chuyển bản ghi sang trạng thái được phép điều phối khi người phụ trách xác nhận đúng tòa nhà và lối tiếp cận. Quy tắc “lấy tọa độ chính xác” nghe hợp lý nhưng không hướng dẫn được cách xử lý kết quả chỉ khớp tên đường. Nên tách tọa độ API trả về khỏi điểm đã được nhân viên xác nhận, tránh lần nhập sau ghi đè mất phần hiệu chỉnh.
TrackAsia cung cấp khả năng tìm vị trí theo dữ liệu dịch vụ. Ứng dụng của bạn vẫn phải xác định ứng viên có thuộc khách hàng này không, hỏi lại khi thiếu thông tin và ghi nhận việc phê duyệt. Có geometry.location không đồng nghĩa có một điểm đã được doanh nghiệp xác nhận.
Làm sạch chuỗi nhưng đừng xóa mất địa chỉ
Mỗi dòng cần một mã nội bộ ổn định. Giữ nguyên rawAddress, sau đó tạo query riêng bằng cách bỏ khoảng trắng thừa ở hai đầu và gộp khoảng trắng liên tiếp. Không tùy tiện bỏ dấu gạch trong số nhà, xóa tên tòa nhà hoặc chuyển mọi chuỗi không dấu rồi dùng nó thay dữ liệu gốc. Những chi tiết đó có thể phân biệt hai nơi khác nhau.
Tên người nhận, số căn hộ, lời dặn “gọi bảo vệ khi tới” nên có trường nghiệp vụ riêng. Lời dặn hữu ích với người giao chưa chắc là truy vấn tìm kiếm tốt. Nếu bỏ lời dặn khỏi chuỗi gửi API, hãy lưu lại phép sửa để người kiểm tra biết hệ thống thực sự tìm bằng thông tin nào.
Cũng cần quyết định dữ liệu nào được phép gửi ra ngoài. Một yêu cầu tìm tọa độ thường không cần giá trị đơn hàng, số điện thoại hay toàn bộ hồ sơ khách. Chỉ gửi phần cần thiết; phân quyền tệp kết quả và quy định thời gian lưu trước khi xuất hàng nghìn địa chỉ ra một thư mục dùng chung.
Đọc một phản hồi trước khi chạy hàng nghìn dòng
Search v2 dùng endpoint Text Search dưới đây. Tài liệu mô tả results, địa chỉ formatted_address, vị trí geometry.location, loại đối tượng types và thông tin partial_match; địa chỉ hành chính cũ có thể được trả kèm khi yêu cầu.[1] Khóa public_key trong ví dụ có giới hạn và chỉ dành cho thử nghiệm; khi vận hành cần khóa của tài khoản được cấu hình phù hợp.[1]
curl -G 'https://maps.track-asia.com/api/v2/place/textsearch/json' \
--data-urlencode 'query=Landmark 81, Ho Chi Minh City' \
--data-urlencode 'new_admin=true' \
--data-urlencode 'include_old_admin=true' \
--data-urlencode 'key=public_key'
HTTP thành công chỉ cho biết đã nhận được phản hồi. Cần đọc tiếp status của dịch vụ, kiểm tra results có đúng dạng mảng và từng ứng viên có tọa độ dạng số hữu hạn. Không tìm thấy phải trở thành một ngoại lệ có thể xử lý, không được đổi thành (0,0) để bảng dữ liệu trông như không thiếu ô nào.
Đặt địa chỉ trả về cạnh dữ liệu đầu vào. Tên đường có khớp không? Tỉnh, thành có nhất quán không? Kết quả là tòa nhà, đường hay đơn vị hành chính? partial_match là dấu hiệu cần xem xét, nhưng thiếu cờ này cũng không phải chứng nhận điểm nằm đúng mái nhà hoặc cổng nhận hàng. Khi còn mơ hồ, để người có trách nhiệm chọn thay vì mặc định phần tử đầu tiên.
Thử với mã nguồn: Địa chỉ hàng loạt: hàng chờ duyệt
Dán mỗi địa chỉ trên một dòng; giữ từng truy vấn và các ứng viên để duyệt, không tự phê duyệt tọa độ.
Ví dụ gọi API dùng public_key giới hạn; ảnh chụp phiên chạy của mã nguồn bên dưới.
Giải nén ZIP, chạy python3 -m http.server 8080 trong thư mục vừa giải nén, rồi mở http://localhost:8080/index-vi.html. README tiếng Việt và tiếng Anh nằm trong ZIP; không cần npm hay bước build.

Một tệp Node.js có thể chạy và tiếp tục
Lưu đoạn sau thành geocode.mjs, dùng Node.js 20 trở lên rồi chạy node geocode.mjs. Chương trình dùng sẵn fetch và hàm đọc ghi tệp, không cần cài thư viện. Hai bản ghi bên trong là dữ liệu minh họa. Có thể thử bằng khóa công khai giới hạn; nếu dùng khóa tài khoản, đặt biến môi trường TRACKASIA_KEY trên máy và không đưa giá trị đó vào kho mã nguồn.
import { readFile, appendFile } from 'node:fs/promises';
const rows = [
{ id: 'demo-001', rawAddress: 'Landmark 81, Ho Chi Minh City' },
{ id: 'demo-002', rawAddress: ' Landmark 81, Ho Chi Minh City ' }
];
const output = 'geocode-results.jsonl';
let previous = '';
try { previous = await readFile(output, 'utf8'); }
catch (e) { if (e.code !== 'ENOENT') throw e; }
const done = new Set(previous.trim().split('\n').filter(Boolean)
.map(line => JSON.parse(line))
.filter(row => row.state !== 'retry')
.map(row => row.id));
for (const row of rows) {
if (done.has(row.id)) continue;
const query = row.rawAddress.trim().replace(/\s+/g, ' ');
const record = { ...row, query, checkedAt: new Date().toISOString() };
try {
const url = new URL('https://maps.track-asia.com/api/v2/place/textsearch/json');
url.search = new URLSearchParams({ query,
key: process.env.TRACKASIA_KEY || 'public_key',
new_admin: 'true', include_old_admin: 'true' });
const response = await fetch(url, { signal: AbortSignal.timeout(15000) });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const body = await response.json();
record.providerStatus = body.status;
if (body.status !== 'OK' && body.status !== 'ZERO_RESULTS') {
throw new Error(`API ${body.status || 'unknown'}`);
}
record.candidates = (body.results || []).map(result => ({
placeId: result.place_id,
address: result.formatted_address,
oldAddress: result.old_formatted_address ?? null,
location: result.geometry?.location,
types: result.types || [],
partial: result.partial_match ?? null
})).filter(result => Number.isFinite(result.location?.lat)
&& Number.isFinite(result.location?.lng));
record.state = record.candidates.length ? 'review' : 'unresolved';
} catch (error) {
record.state = 'retry';
record.error = error.message;
process.exitCode = 1;
}
await appendFile(output, JSON.stringify(record) + '\n');
}
console.log(`Review ${output}; candidates are not automatically approved.`);
Mỗi dòng đầu ra nối mã nguồn với truy vấn đã gửi và danh sách ứng viên. address lấy từ formatted_address, location lấy từ geometry.location, còn oldAddress chỉ giữ khi dịch vụ có trả. Các trạng thái review, unresolved, retry do ứng dụng ví dụ đặt ra, không phải mã trạng thái của TrackAsia. Không bản ghi nào tự trở thành “đã xác nhận”.
Chạy lần thứ hai sẽ bỏ qua mã đã xử lý xong. Nếu sửa một địa chỉ không tìm thấy, cần mở lại công việc tương ứng hoặc tạo mã phiên bản mới; thay nội dung dưới cùng một ID không làm tệp đánh dấu này tự biết dữ liệu đã đổi. Trong hệ thống thật, nên dùng cơ sở dữ liệu có phiên bản đầu vào và hàng đợi công việc ghi nhận kết quả theo giao dịch.
Giữ tên hành chính cũ và mới ở đâu?
Search v2 mô tả new_admin=true và include_old_admin=true để yêu cầu thông tin theo cơ cấu mới đồng thời nhận thông tin cũ khi có.[1] Hãy giữ chuỗi khách nhập tách khỏi địa chỉ hiển thị hiện tại. Nhờ vậy, nhân viên vẫn tra được hồ sơ dùng tên cũ mà không phải ép mọi màn hình mới tiếp tục dùng tên cũ.
Một bản ghi nên có địa chỉ hiển thị, địa chỉ cũ nếu được trả về, mã địa điểm theo nhà cung cấp, tọa độ và thời điểm tìm. Nếu nhân viên chỉnh điểm, lưu riêng điểm đã duyệt và người duyệt. Trường tùy chọn phải được phép thiếu; không tự dựng old_formatted_address hoặc suy rằng mọi số nhà đều có mã hành chính riêng.
Đổi tên đơn vị hành chính không làm tòa nhà dịch chuyển. Ngược lại, một tên cơ sở quen thuộc cũng không chứng minh doanh nghiệp vẫn hoạt động ở địa chỉ cũ. Vì vậy, cập nhật nhãn địa chỉ và xác minh vị trí nên là hai thao tác riêng, dù cùng xuất hiện trên một màn hình kiểm tra.
Lỗi nào thử lại, lỗi nào phải hỏi người dùng?
Timeout hoặc HTTP 5xx có thể thử lại có giới hạn và có thời gian chờ. Lỗi xác thực cần sửa khóa hay cấu hình; gửi liên tục cùng một yêu cầu không giải quyết được. Không tìm thấy cần bổ sung dữ liệu. Nhiều ứng viên cần người quyết định. Ba tình huống này không nên bị gom thành một nút “chạy lại tất cả”.
Ví dụ xử lý tuần tự để dễ theo dõi, chỉ ghi lỗi thay vì tự tạo vòng lặp gửi dồn. Trước khi tăng số luồng, xác nhận hạn mức tài khoản, thêm số lần thử tối đa và khả năng tạm dừng. Tránh ghi URL đầy đủ vào nhật ký vì khóa nằm trong tham số truy vấn.
Nếu chương trình dừng đúng lúc ghi một dòng, kiểm tra tệp trước khi chạy tiếp. Mẫu sẽ báo lỗi khi gặp JSON hỏng thay vì âm thầm bỏ qua. Với hàng đợi thật, cần ghi kết quả nguyên vẹn và có màn hình công việc thất bại. Hai khách cùng một tòa nhà cũng không được gộp thành một khách; bộ nhớ tạm kết quả vị trí và bảng khách hàng có vai trò khác nhau.
Đánh giá theo nơi điểm sẽ được sử dụng
Lấy mẫu từ vùng hoạt động thật: địa chỉ đủ số nhà, tên tòa nhà, địa chỉ nông thôn, tên hành chính cũ và dòng thiếu thông tin. Đừng chỉ thử địa danh nổi tiếng. Màn hình đánh giá nên đặt đầu vào, ứng viên và bản đồ cạnh nhau để người duyệt giải thích được lý do chọn hoặc từ chối.
Tách số dòng không tìm được, còn mơ hồ, phải chỉnh tay và đã xác nhận. Nếu cần cổng giao hàng, kiểm tra cổng riêng; tỷ lệ có số lat/lng không đại diện cho chất lượng giao nhận. Giữ kết quả kiểm tra nối với ID nguồn để hiệu chỉnh quay về CRM hoặc hệ thống đơn hàng.
Khi mẫu nhỏ chạy hợp lý, mới tăng kích thước công việc và đối chiếu số lời gọi thực tế với dự báo. Gửi TrackAsia tải dự kiến để xác nhận tốc độ, đơn vị phí và quyền lưu kết quả. Đầu ra đáng dùng là một quy trình xác định vị trí có thể lặp lại, thấy rõ ngoại lệ và người xử lý; không chỉ là một bảng dài các con số thập phân.
Sources
[1] https://docs.track-asia.com/api-integration/search/v2