LacQuantLACQUANT

Blog

Công cụ & Thực hành

Làm thế nào để truyền tải chủ quyền Việt Nam khi dùng bản đồ Opensource?

Các thư viện bản đồ mã nguồn mở như Leaflet và OpenStreetMap đặt ra nhiều câu hỏi thực tế khi bạn cần thể hiện lập trường chủ quyền rõ ràng — đặc biệt với Hoàng Sa và Trường Sa. Bài viết chia sẻ cách chúng tôi giải quyết vấn đề này trong LacQuant.

10 tháng 1, 2025

·

7 min read

Khi xây dựng Bản đồ Xung đột Toàn cầu trong LacQuant, chúng tôi phải đối mặt với một câu hỏi không đơn giản: làm sao để một bản đồ Opensource thể hiện đúng lập trường chủ quyền của Việt Nam đối với Hoàng Sa và Trường Sa?

Bản đồ Xung đột LacQuant — layer Hoàng Sa & Trường SaBản đồ Xung đột LacQuant — layer Hoàng Sa & Trường Sa

Câu trả lời không nằm ở việc chọn đúng một thư viện hay một file GeoJSON. Nó nằm ở hàng loạt quyết định kỹ thuật nhỏ, mỗi quyết định đều có lý do rõ ràng.

Layer Hoàng Sa & Trường Sa — Bản đồ Xung đột LacQuant

Một trong 14 layer của Conflict Map, hiển thị 12 thực thể địa lý thuộc chủ quyền Việt Nam tại Biển Đông

12

Thực thể địa lý được đánh dấu

2

Quần đảo: Hoàng Sa & Trường Sa

3

Thực thể TQ chiếm đóng trái phép

Vấn đề với OSM tile chuẩn

OpenStreetMap tile (tile.openstreetmap.org) render tên theo ngôn ngữ của khu vực địa lý. Biển Đông và các đảo tranh chấp sẽ hiện tên Trung Quốc: 南海, 西沙群岛, 南沙群岛.

Đây không phải lỗi của OSM — cộng đồng OSM không có đồng thuận về cách gán tên ở các vùng tranh chấp, và render engine của họ chọn tên theo nguyên tắc "ngôn ngữ địa phương". Với Biển Đông, điều đó có nghĩa là tên Trung Quốc.

Ngoài ra, OSM tile chuẩn có nhiều label, màu sắc và chi tiết đường xá — phù hợp cho navigation nhưng tạo nền quá bận cho bản đồ phân tích địa chính trị. Các layer conflict, tension, military spots sẽ bị "chìm" vào nền.

Tại sao chọn CartoDB thay vì OSM thuần túy?

CartoDB (basemaps.cartocdn.com) vẫn dùng dữ liệu OpenStreetMap nhưng render lại thành tile tối giản với label tiếng Anh — trung lập hơn và phù hợp làm nền bản đồ phân tích. Không cần API key, hỗ trợ 4 subdomain (a/b/c/d) để browser tải song song, và có sẵn dark mode tile.

Conflict map — CartoDB dark tile với layer Hoàng Sa & Trường SaConflict map — CartoDB dark tile với layer Hoàng Sa & Trường Sa

Tiêu chíCartoDBOSM thuần túy
Label ngôn ngữTiếng Anh (trung lập)Theo khu vực địa lý
Nền thị giácTối giản, phù hợp overlayBận, nhiều chi tiết
Dark modedark_all tile sẵn cóCần CSS filter
API keyKhông cầnKhông cần
Dữ liệu nguồnOSM data, CartoDB renderOSM data, OSM render

Attribution vẫn bắt buộc

CartoDB dùng dữ liệu OSM nên bạn vẫn phải giữ attribution cho cả OpenStreetMap contributors và CartoDB trong tile layer config.

Tại sao không dùng GeoJSON để vẽ ranh giới đảo?

Câu hỏi hợp lý tiếp theo: tại sao không vẽ polygon ranh giới từng đảo bằng GeoJSON?

1

Không có dữ liệu GeoJSON chất lượng cao và trung lập

Hoàng Sa và Trường Sa gồm hàng chục thực thể — đảo san hô, bãi đá, cồn cát, nhiều thực thể chỉ rộng vài trăm mét vuông và ngập khi thủy triều lên. Natural Earth thiếu nhiều thực thể nhỏ và tên thường thiên về tên Trung Quốc. OSM có dữ liệu nhưng không nhất quán và chưa có đồng thuận về metadata chủ quyền.

2

Zoom range làm polygon vô nghĩa

Bản đồ được giới hạn maxZoom: 6 — đây là công cụ phân tích vĩ mô, không phải bản đồ điều hướng. Ở zoom 4–6, một đảo rộng 1km² chỉ hiển thị khoảng 1–2 pixel. Polygon chi tiết ở zoom đó sẽ render thành một dot nhỏ không đọc được.

3

Dot marker + popup là lựa chọn phù hợp

Mục tiêu là annotation địa chính trị, không phải vẽ bản đồ địa lý. Dot marker truyền đạt thông tin rõ ràng hơn ở zoom tổng quan, đơn giản hơn để triển khai và bảo trì.

Giải pháp: Layer annotation tự xây dựng

Thay vì phụ thuộc vào dữ liệu có sẵn, chúng tôi xây dựng một layer riêng (vnIslandsLayer) với data model kiểm soát hoàn toàn:

type VNIsland = {
  lat: number;
  lng: number;
  name_vi: string;   // Tên tiếng Việt chính thức — luôn đứng trước
  name_en: string;   // Tên quốc tế — đứng sau
  occupied: boolean; // true = đang bị Trung Quốc chiếm đóng trái phép
  claimants: string;
  note_vi: string;
  note_en: string;
};
type VNIsland = {
  lat: number;
  lng: number;
  name_vi: string;   // Tên tiếng Việt chính thức — luôn đứng trước
  name_en: string;   // Tên quốc tế — đứng sau
  occupied: boolean; // true = đang bị Trung Quốc chiếm đóng trái phép
  claimants: string;
  note_vi: string;
  note_en: string;
};

Conflict map — layer Hoàng Sa & Trường Sa ở zoom khu vựcConflict map — layer Hoàng Sa & Trường Sa ở zoom khu vực

12 thực thể được đánh dấu, chia thành hai nhóm rõ ràng — Hoàng Sa (3 điểm, toàn bộ bị chiếm từ 1974) và Trường Sa (9 điểm: 6 Việt Nam kiểm soát + 3 Trung Quốc chiếm đóng: Đá Chữ Thập, Đá Vành Khăn, Đá Xu Bi).

Nguyên tắc trình bày: trung tính thị giác, rõ ràng nội dung

Tại sao chọn màu xám cho marker?

Layer dùng màu xám (#666666 light / #999999 dark) — không phải để né tránh lập trường, mà để không gây nhiễu với các layer khác (conflict đỏ, tension vàng, stable xanh lá). Tone xám trùng với label đất liền trên CartoDB tile, khiến layer đảo trông như một phần tự nhiên của base map.

Lập trường chủ quyền được thể hiện qua popup và badge, không qua màu sắc marker. Badge luôn song ngữ Việt-Anh:

const statusBadge = island.occupied
  ? `<span style="background:rgba(239,68,68,0.15);color:#ef4444">
       Trung Quốc chiếm đóng trái phép · Vietnam sovereignty disputed
     </span>`
  : `<span style="background:rgba(16,185,129,0.12);color:#10b981">
       Việt Nam kiểm soát · Vietnam-administered
     </span>`;
const statusBadge = island.occupied
  ? `<span style="background:rgba(239,68,68,0.15);color:#ef4444">
       Trung Quốc chiếm đóng trái phép · Vietnam sovereignty disputed
     </span>`
  : `<span style="background:rgba(16,185,129,0.12);color:#10b981">
       Việt Nam kiểm soát · Vietnam-administered
     </span>`;

Các chi tiết kỹ thuật đáng chú ý

Zoom-aware visibility

< 4

Layer ẩn

Zoom toàn cầu, Biển Đông quá nhỏ

≥ 4

Layer hiện

Biển Đông chiếm ~1/3 màn hình

map.on("zoomend", () => {
  const show = map.getZoom() >= 4;
  for (const m of vnAllMarkers) {
    const el = m.getElement();
    if (el) el.style.display = show ? "" : "none";
  }
});
map.on("zoomend", () => {
  const show = map.getZoom() >= 4;
  for (const m of vnAllMarkers) {
    const el = m.getElement();
    if (el) el.style.display = show ? "" : "none";
  }
});

Dark mode tự động

Khi data-theme="dark" trên <html>, bản đồ tự động switch sang dark_all tile qua MutationObserver:

const observer = new MutationObserver(() => {
  tile.setUrl(isDarkMode() ? TILE_DARK : TILE_LIGHT);
});
observer.observe(document.documentElement, {
  attributes: true,
  attributeFilter: ["data-theme"],
});
const observer = new MutationObserver(() => {
  tile.setUrl(isDarkMode() ? TILE_DARK : TILE_LIGHT);
});
observer.observe(document.documentElement, {
  attributes: true,
  attributeFilter: ["data-theme"],
});

L.divIcon thay vì L.circleMarker

Mỗi đảo được vẽ bằng L.divIcon (HTML div) thay vì L.circleMarker. Lý do: dễ style bằng CSS custom properties, không bị ảnh hưởng bởi Webpack icon path issue trong Next.js, và nhẹ hơn SVG.

Translation override cho tên địa danh

const TRANSLATION_OVERRIDES = {
  "paracel islands":  { vi: "Quần đảo Hoàng Sa",  ja: "西沙諸島" },
  "spratly islands":  { vi: "Quần đảo Trường Sa", ja: "南沙諸島" },
};
const TRANSLATION_OVERRIDES = {
  "paracel islands":  { vi: "Quần đảo Hoàng Sa",  ja: "西沙諸島" },
  "spratly islands":  { vi: "Quần đảo Trường Sa", ja: "南沙諸島" },
};

Override này đảm bảo dù content API trả về tên tiếng Anh, các thuật ngữ địa lý quan trọng sẽ luôn được dịch đúng — không phụ thuộc vào MyMemory API.

Bài học chính

Đừng tin tưởng dữ liệu có sẵn về những vùng tranh chấp. Nếu bạn cần truyền đạt lập trường chủ quyền rõ ràng, hãy kiểm soát dữ liệu của bạn — tên gọi, mô tả, trạng thái — và kiểm soát hoàn toàn cách nó được render.


Vấn đề "chủ quyền trên bản đồ Opensource" không có một giải pháp duy nhất. Nhưng có một nguyên tắc chỉ đường: về mặt kỹ thuật, điều đó không khó — về mặt lập trường, nó cần rõ ràng ngay từ đầu.

Phân tích rủi ro danh mục của bạn với LacQuant

Công cụ quant + AI cho nhà đầu tư cá nhân Việt Nam.

Dùng thử miễn phí