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内のcommandをimportから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: '© 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/;
}
}
このスタックが活きる場面
フリートトラッキング、マップビューを持つスマートホームダッシュボード、社内配送アプリ——位置データを第三者に送ることが許されないあらゆる場面。そういったケースでこのセットアップは真価を発揮します。
最初に数時間のインポート作業さえこなせば、あとはスタックが動き続けます。タイルはレンダリングされ、ジオコーディングは応答し、データはネットワーク外に出ません。請求書も、レート制限エラーも、データの行方への疑問も、一切不要です。

