OpenStreetMapタイルサーバーとNominatimによるセルフホスト型マップサーバー:HomeLabでGoogle Maps APIを置き換える

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

Google Mapsに依存する本当のコスト

ジオコーディングの呼び出し、タイルの読み込み、アプリへのマップ埋め込み——すべてGoogleのサーバーを経由します。小規模なら無料枠で収まりますが、コストはすぐに跳ね上がります。動的マップの読み込みは1,000リクエストあたり7ドル、ジオコーディングはさらに1,000件あたり5ドル。プロジェクトが軌道に乗ってきたとき、あるいはユーザーの位置データがどこへ向かうのか気にし始めたとき、Google Mapsはもはや「便利なツール」ではなく「リスク」に変わります。

本質的な問題は料金だけではありません。Googleはすべてのクエリをデータポイントとして扱います。フリートトラッカー、位置情報機能を持つホームオートメーションダッシュボード、あるいは位置データが機密となる社内ツールを構築している場合、そのデータを外部に送ることは排除すべきリスクです。

マップインフラへの3つのアプローチ

マップ機能が必要な場合、現実的な選択肢は3つあります:

マネージドAPI(Google Maps、Mapbox、HERE)

導入は簡単でデータ品質も優れていますが、呼び出しごとに課金され、レート制限の範囲内で動作し、すべてのリクエストがネットワーク外に出ます。HomeLabプロジェクトやプライバシーが重要なアプリケーションには正直向きません。

OpenStreetMapの公開タイルを直接利用

OpenStreetMapはtile.openstreetmap.orgで無料タイルを提供していますが、利用ポリシーで本番環境での大量利用は禁止されています。開発用には問題ありませんが、実際のサービスには使えません。

OSMスタックのセルフホスト

OSMデータを使って独自のタイルレンダリングサーバーとジオコーダーを運用します。呼び出し単位のコストなし、データがネットワーク外に出ることもなし、レート制限もなし。セットアップの手間とハードウェアが必要になりますが、HomeLabでの利用なら十分見合うトレードオフです。

このスタックは2つのコアコンポーネントで構成されます:

  • タイルサーバー — OSMデータからマップ画像をレンダリング。openstreetmap-tile-server(mod_tile + renderd + Mapnikベース)を使用
  • Nominatim — ジオコーディング(住所 → 座標)と逆ジオコーディング(座標 → 住所)を担当

メリットとデメリット:実際のトレードオフ

得られるもの

  • ハードウェア投資後のAPIコストはゼロ
  • 位置データがネットワーク外に出ることは一切ない
  • レート制限なし——ハードウェアが許す限りリクエストを処理できる
  • 完全なオフライン対応
  • マップのスタイルとデータを自分でコントロールできる

引き受けるもの

  • 初期データインポートに時間がかかる——地域の大きさに応じて30分から数時間
  • ディスク容量が必要:国単位のデータで10〜50GB、全世界データは約1TB
  • 更新とメンテナンスは自分で行う
  • レンダリング性能はハードウェアに依存する

ディスクと時間の数字は、見た目ほど悪くありません。全世界データではなく、地域データから始めましょう。たとえば日本は約900MBのPBFファイルでダウンロードでき、それほど高性能でないハードウェアでも問題なくインポートできます。ストレージのほんの一部で、必要な機能の95%が手に入ります。

推奨セットアップ

私はこのスタックを、都市圏をカバーする車両追跡アプリケーションの本番環境で運用してきました。8GB RAMとSSDを搭載したマシンで、タイルサーバーは1分あたり数百件のタイルリクエストを問題なく処理し、数か月間驚くほど安定して稼働しています。

実際に機能するハードウェアの最低要件:

  • RAM: 最低8GB、Nominatimを快適に動かすには16GB推奨
  • ストレージ: SSD必須——HDDだとインポートが5〜10倍遅くなる
  • CPU: タイルレンダリングには最低4コア

ソフトウェアスタック:Docker + Docker Compose、タイルレンダリングにoverv/openstreetmap-tile-server、ジオコーディングにmediagis/nominatimを使用します。

実装ガイド

ステップ1:地域のOSMデータをダウンロードする

Geofabrikは毎日更新される無料の地域データを提供しています。https://download.geofabrik.deにアクセスして、必要な地域を選択しましょう。

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

# 日本の例 — URLをお好みの地域に変更してください
wget https://download.geofabrik.de/asia/japan-latest.osm.pbf

ステップ2: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=changeme_use_something_strong
    shm_size: 1gb
    restart: unless-stopped

volumes:
  osm-data:
  nominatim-data:

ステップ3:タイルデータをインポートする(初回のみ)

これは生のOSMデータをレンダラーが読み取るPostGISデータベースに変換します。一度実行して待ちましょう:

docker compose up tile-server
# ログに "INFO: import done" が表示されたら停止する
docker compose stop tile-server

インポートが完了したら、docker-compose.yml内のcommandimportからrunに変更し、常駐サービスとして起動します:

docker compose up -d tile-server

ステップ4:Nominatimデータをインポートする

初回起動時、NominatimはPBFファイルを検出して自動的にフルインポートを開始します。日本のデータなら約1〜2時間かかります。コーヒーでも飲みながら待ちましょう:

docker compose up nominatim
# ログを確認;インポート後、自動的にサービスモードに切り替わる

ステップ5:両方のサービスを確認する

# タイルサーバーのテスト — 有効なPNGが保存されるはず
curl -o /tmp/test.png "http://localhost:8080/tile/10/909/403.png"
file /tmp/test.png  # 期待値: PNG image data

# ジオコーディングのテスト
curl "http://localhost:8081/search?q=Tokyo+Station&format=json&limit=1"

# 逆ジオコーディングのテスト
curl "http://localhost:8081/reverse?lat=35.6812&lon=139.7671&format=json"

ステップ6:Leaflet.jsをタイルサーバーに接続する

Leafletは標準的なオープンソースマップライブラリです。サーバーへの接続はURLを1か所変更するだけです:

<!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>

ステップ7: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", "")

データを最新の状態に保つ

NominatimのREPLICATION_URLは、Geofabrikからの差分更新を自動的に処理します——フルインポートなしで変更ファイルを取得・適用します。レプリケーションのステータスはいつでも確認できます:

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

タイルデータの更新は少し異なります。レンダリング済みのタイルはディスクにキャッシュされており、新しいOSMデータが追加されても自動的に無効化されません——マップの変更を反映するには再インポートが必要です。ほとんどのHomeLabシナリオでは、四半期に一度のペースで十分です。インポートはバックグラウンドで実行され、その間もサーバーはキャッシュされたタイルを提供し続けます。

オプション:Nginxリバースプロキシ

きれいなURLやCertbot経由のHTTPSが必要なら、これを追加してください:

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/;
    }
}

このスタックが活きる場面

フリートトラッキング、マップビューを持つスマートホームダッシュボード、社内配送アプリ——位置データを第三者に送ることが許されないあらゆる場面。そういったケースでこのセットアップは真価を発揮します。

最初に数時間のインポート作業さえこなせば、あとはスタックが動き続けます。タイルはレンダリングされ、ジオコーディングは応答し、データはネットワーク外に出ません。請求書も、レート制限エラーも、データの行方への疑問も、一切不要です。

Share: