Tự Dựng Map Server với OpenStreetMap Tile Server và Nominatim: Thay Thế Google Maps API trong HomeLab

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

Chi Phí Thực Sự Khi Phụ Thuộc vào Google Maps

Mỗi lần gọi geocoding, mỗi lần tải tile, mỗi bản đồ nhúng trong ứng dụng của bạn đều đi qua server của Google. Ở quy mô nhỏ, gói miễn phí vẫn đủ dùng. Nhưng chi phí tăng rất nhanh: tải bản đồ động tốn $7 cho mỗi 1.000 request, geocoding thêm $5 cho mỗi 1.000 request nữa. Khi dự án của bạn bắt đầu có lượng truy cập — hoặc khi bạn bắt đầu quan tâm đến việc dữ liệu vị trí của người dùng đi đâu — Google Maps không còn là sự tiện lợi mà trở thành một rủi ro.

Vấn đề sâu xa hơn không chỉ là hóa đơn. Google xem mỗi query là một điểm dữ liệu. Nếu bạn đang xây dựng ứng dụng theo dõi đội xe, dashboard home automation có tính năng vị trí, hay bất kỳ công cụ nội bộ nào mà dữ liệu vị trí là nhạy cảm — thì việc gửi dữ liệu đó ra ngoài là rủi ro cần loại bỏ.

Ba Hướng Tiếp Cận Cho Hạ Tầng Bản Đồ

Khi cần tích hợp bản đồ, có ba hướng thực tế:

API Quản Lý Sẵn (Google Maps, Mapbox, HERE)

Dễ bắt đầu, chất lượng dữ liệu tốt, nhưng bạn trả tiền theo từng lượt gọi, bị giới hạn rate limit, và mọi request đều rời khỏi mạng của bạn. Với dự án HomeLab hay ứng dụng yêu cầu bảo mật cao, khó biện minh cho lựa chọn này.

Tiles Công Khai Trực Tiếp từ OpenStreetMap

OpenStreetMap cung cấp tile miễn phí tại tile.openstreetmap.org, nhưng chính sách sử dụng của họ cấm dùng nặng trong ứng dụng production. Ổn cho phát triển, không dùng được trong thực tế.

Tự Dựng OSM Stack

Tự chạy tile rendering server và geocoder bằng dữ liệu OSM. Không tốn phí theo lượt gọi, dữ liệu không rời khỏi mạng, không có rate limit. Đánh đổi là công sức cài đặt và phần cứng — nhưng với HomeLab, đó là sự đánh đổi xứng đáng.

Stack này gồm hai thành phần chính:

  • Tile server — render ảnh bản đồ từ dữ liệu OSM, dùng openstreetmap-tile-server (xây dựng trên mod_tile + renderd + Mapnik)
  • Nominatim — xử lý geocoding (địa chỉ → tọa độ) và reverse geocoding (tọa độ → địa chỉ)

Ưu và Nhược Điểm: Bạn Thực Sự Đánh Đổi Gì

Những gì bạn nhận được

  • Không tốn phí API sau khi đầu tư phần cứng
  • Dữ liệu vị trí không bao giờ rời khỏi mạng của bạn
  • Không có rate limit — phục vụ bao nhiêu request tùy phần cứng cho phép
  • Hoạt động offline hoàn toàn
  • Kiểm soát hoàn toàn style bản đồ và dữ liệu

Những gì bạn phải chịu

  • Import dữ liệu ban đầu tốn thời gian — 30 phút đến vài giờ tùy kích thước khu vực
  • Dung lượng ổ đĩa đáng kể: dữ liệu một quốc gia cần 10–50GB, toàn thế giới cần ~1TB
  • Bạn phải tự cập nhật và bảo trì
  • Hiệu năng render phụ thuộc vào phần cứng

Con số về ổ đĩa và thời gian nghe có vẻ đáng sợ hơn thực tế. Hãy bắt đầu với dữ liệu khu vực thay vì toàn cầu. Nhật Bản chẳng hạn, chỉ cần tải file PBF khoảng 900MB và import gọn gàng trên phần cứng bình thường. Bạn có được 95% những gì cần với một phần nhỏ dung lượng.

Cấu Hình Khuyến Nghị

Tôi đã chạy stack này trong môi trường thực tế cho ứng dụng theo dõi xe phủ một khu vực đô thị. Trên máy có 8GB RAM và SSD, tile server xử lý hàng trăm tile request mỗi phút mà không gặp vấn đề gì — và đã chạy ổn định như vậy hàng tháng trời.

Cấu hình phần cứng tối thiểu thực sự hoạt động được:

  • RAM: Tối thiểu 8GB, 16GB để Nominatim hoạt động thoải mái
  • Lưu trữ: Bắt buộc dùng SSD — ổ cứng cơ học làm import chậm hơn 5–10 lần
  • CPU: Tối thiểu 4 nhân cho render tile

Stack phần mềm: Docker + Docker Compose, overv/openstreetmap-tile-server để render tile, và mediagis/nominatim để geocoding.

Hướng Dẫn Triển Khai

Bước 1: Tải OSM Extract Theo Khu Vực

Geofabrik cung cấp dữ liệu khu vực miễn phí, cập nhật hàng ngày. Truy cập https://download.geofabrik.de và chọn khu vực của bạn.

mkdir -p ~/homelab/maps/data
cd ~/homelab/maps/data

# Ví dụ Nhật Bản — thay URL bằng khu vực của bạn
wget https://download.geofabrik.de/asia/japan-latest.osm.pbf

Bước 2: Viết docker-compose.yml

version: "3"
services:
  tile-server:
    image: overv/openstreetmap-tile-server:2.4.0
    volumes:
      - osm-data:/data/database
      - ./data/japan-latest.osm.pbf:/data/region.osm.pbf
    ports:
      - "8080:80"
    environment:
      - THREADS=4
    command: import
    restart: "no"

  nominatim:
    image: mediagis/nominatim:4.4
    volumes:
      - nominatim-data:/var/lib/postgresql/14/main
      - ./data/japan-latest.osm.pbf:/nominatim/data.osm.pbf
    ports:
      - "8081:8080"
    environment:
      - PBF_PATH=/nominatim/data.osm.pbf
      - REPLICATION_URL=https://download.geofabrik.de/asia/japan-updates/
      - NOMINATIM_PASSWORD=thay_bang_mat_khau_manh_hon
    shm_size: 1gb
    restart: unless-stopped

volumes:
  osm-data:
  nominatim-data:

Bước 3: Import Dữ Liệu Tile (Chạy Một Lần)

Lệnh này chuyển đổi dữ liệu OSM thô thành cơ sở dữ liệu PostGIS mà renderer đọc. Chạy một lần và chờ đợi:

docker compose up tile-server
# Chờ thấy "INFO: import done" trong logs, sau đó dừng lại
docker compose stop tile-server

Sau khi import xong, đổi command trong docker-compose.yml từ import sang run, rồi khởi động lại như một service liên tục:

docker compose up -d tile-server

Bước 4: Import Dữ Liệu Nominatim

Lần khởi động đầu tiên, Nominatim tự phát hiện file PBF và bắt đầu import toàn bộ tự động. Nhật Bản mất khoảng 1–2 tiếng. Tranh thủ pha cà phê:

docker compose up nominatim
# Theo dõi logs; quá trình import sẽ tự chuyển sang chế độ service

Bước 5: Kiểm Tra Cả Hai Service

# Kiểm tra tile server — nên lưu được file PNG hợp lệ
curl -o /tmp/test.png "http://localhost:8080/tile/10/909/403.png"
file /tmp/test.png  # Kết quả mong đợi: PNG image data

# Kiểm tra geocoding
curl "http://localhost:8081/search?q=Tokyo+Station&format=json&limit=1"

# Kiểm tra reverse geocoding
curl "http://localhost:8081/reverse?lat=35.6812&lon=139.7671&format=json"

Bước 6: Kết Nối Leaflet.js với Tile Server

Leaflet là thư viện bản đồ mã nguồn mở phổ biến nhất. Chỉ cần thay một URL là đủ để trỏ vào server của bạn:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
  <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
  <style>#map { height: 600px; }</style>
</head>
<body>
  <div id="map"></div>
  <script>
    const map = L.map('map').setView([35.6812, 139.7671], 13);
    L.tileLayer('http://YOUR_SERVER_IP:8080/tile/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; OpenStreetMap contributors'
    }).addTo(map);
  </script>
</body>
</html>

Bước 7: Geocoding từ Python

import requests

NOMINATIM_BASE = "http://YOUR_SERVER_IP:8081"

def geocode(address: str) -> dict:
    resp = requests.get(
        f"{NOMINATIM_BASE}/search",
        params={"q": address, "format": "json", "limit": 1},
        headers={"User-Agent": "MyApp/1.0"},
    )
    results = resp.json()
    if results:
        return {"lat": float(results[0]["lat"]), "lon": float(results[0]["lon"])}
    return {}

def reverse_geocode(lat: float, lon: float) -> str:
    resp = requests.get(
        f"{NOMINATIM_BASE}/reverse",
        params={"lat": lat, "lon": lon, "format": "json"},
        headers={"User-Agent": "MyApp/1.0"},
    )
    return resp.json().get("display_name", "")

Cập Nhật Dữ Liệu Thường Xuyên

REPLICATION_URL của Nominatim tự động kéo và áp dụng OSM diff updates từ Geofabrik — không cần import lại toàn bộ. Kiểm tra trạng thái replication bất cứ lúc nào:

curl "http://localhost:8081/status"

Dữ liệu tile hoạt động khác. Tile đã render được cache trên ổ đĩa, và dữ liệu OSM mới không tự động làm cache cũ hết hạn — bạn cần import lại để cập nhật thay đổi bản đồ. Với hầu hết kịch bản HomeLab, làm theo quý là đủ. Quá trình import chạy nền trong khi server vẫn tiếp tục phục vụ tile đã cache.

Tùy Chọn: Nginx Reverse Proxy

Muốn URL gọn hơn hay HTTPS qua Certbot? Thêm cấu hình này:

server {
    listen 80;
    server_name maps.homelab.local;

    location /tiles/ {
        proxy_pass http://localhost:8080/tile/;
        proxy_cache_valid 200 7d;
        add_header Cache-Control "public, max-age=604800";
    }

    location /nominatim/ {
        proxy_pass http://localhost:8081/;
    }
}

Khi Nào Stack Này Phù Hợp

Theo dõi đội xe, dashboard nhà thông minh có giao diện bản đồ, ứng dụng giao hàng nội bộ — bất cứ thứ gì mà việc gửi dữ liệu vị trí cho bên thứ ba là điều không thể chấp nhận. Đó là lúc setup này thể hiện giá trị.

Bỏ ra vài giờ import ban đầu, rồi stack cứ thế chạy. Tile render, geocoding phản hồi, không có gì rời khỏi mạng. Không hóa đơn, không lỗi rate limit, không câu hỏi về dữ liệu đi đâu.

Share: