KunYu
h3geohashs2spatial-indexgis-basics

H3 vs Geohash vs S2:空間インデックスの選び方

H3・Geohash・S2の空間インデックスを徹底比較。精度テーブル、緯度による歪み、境界バグ、JSとPythonのコード例、そして選択の指針をまとめました。

KunYu TeamMarch 12, 202621分で読めます

数百万件のGPSポイントに対して近接検索を実装しようとすると、必ず同じ壁にぶつかります。ブルートフォースの距離スキャンは遅すぎる、そして生のlat/lng座標はインデックス効率が悪い。空間グリッドシステムはこの問題を解決します。座標を事前に計算されたセルにスナップすることで、高速な検索、きれいな集計、共有可能な識別子を実現します。Geohashは2008年からこれをやっています。H3はUberが2018年にオープンソース化してから六角形グリッドを一般に広めました。S2はGoogle Mapsが内部で使っているシステムです。この3つは互換性がなく、プロトタイプ段階では些細に見える違いが、データがセル境界をまたいだり複数の大陸にわたったりするときに大きな問題になります。

空間グリッドインデックスシステムとは

空間グリッドインデックスシステムは地球の表面を離散的なセルに分割し、各セルに一意な識別子を割り当てます。生の座標の代わりにセルIDを保存することで、文字列のプレフィックスや整数値を比較するだけで高速な検索、範囲クエリ、データ集計が可能になります。核心的なトレードオフはセルの均一性、実装のシンプルさ、クエリパフォーマンスのバランスです。

この3つのシステムは、それぞれ異なるエンジニアリング文化から生まれました。Geohashは2008年にGustavo Niemeyerがパブリックドメインのジオコーディングシステムとして発明したもので、設計上もっともシンプルです。S2はGoogleが2011年頃に内部の地図インフラ向けのジオメトリライブラリとして開発しました。H3はUberが都市スケールでのドライバーと乗客のマッチング問題を解くために開発し、2018年にオープンソース化しました。

セルの形状はこれらの出自を反映しています。Geohashは長方形、S2は球面四辺形(球面上のほぼ正方形)、H3は六角形(球を閉じるための12枚の五角形を含む)を生成します。

各システムの仕組み

各システムのエンコード方式は、その長所と本番環境での失敗パターンの両方を規定します。

H3は地球を正二十面体(20面の多面体)に投影し、各面を六角形に細分化します。結果として15段階の解像度レベルがあり、各セルはおよそ7つの子セルに分割されます。セルIDは64ビット整数です。六角形という形状により、すべてのセルはちょうど6つの辺共有の隣接セルを持ちます。コーナー共有による曖昧さはありません。

Geohashは緯度と経度のビット列を交互に並べ、その結果をBase32文字列としてエンコードします。これがGeohashの文字列が人間に読みやすい理由です。u4pruydqqvjはパリ中心部の3m×3mのエリアを一意に識別します。精度は文字列の長さ(1〜12文字)で制御します。プレフィックスの性質——u4pruは常にu4prの内部にある——により、範囲クエリが簡単です。

S2は地球の表面を立方体の6面に展開し、各面にヒルベルト空間充填曲線を適用して、位置を64ビット整数にマッピングします。ヒルベルト曲線は空間的な局所性を保持します。地球上で近くにある点は、インデックス上でも近い整数になります。S2には31段階の解像度レベルがあり、S2RegionCovererを使って任意のポリゴンを最小セル数で近似できます。

精度とカバレッジの比較表

この3つのシステムは互換性のない精度スケールを使っています。H3の15段階、Geohashの12文字、S2の31レベルは互いに対応しません。以下の表では、実用的な3つのスケール——都市レベルの集計、近隣ルーティング、建物レベルの精度——でセル面積を揃えて比較しています。

経度1度あたりの距離はコサイン曲線に沿って縮小し、多くの開発者が思う以上に急激に影響が出ます。緯度30°(ロサンゼルス、カイロ、上海)では、経度1度は96.5kmに対し、緯度1度は110.6km——東西方向で14.9%の差があります。緯度60°(ストックホルム、アンカレッジ、サンクトペテルブルク)になると比率は2:1になり、経度1度はわずか55.7kmの幅しかありません。赤道で約0.74km²をカバーする6文字のGeohashセルは、緯度60°Nでは約0.37km²しかカバーしません。均一な近接閾値(例:「500m以内」)でグローバルサービスを構築する場合、固定精度のGeohashは緯度によって結果がばらつきます——低緯度では長いハッシュ、極付近では短いハッシュを使うなどの対応が必要になり、クエリロジックが複雑になります。H3とS2はいずれも球面歪みを補正しており、解像度9のH3は全緯度にわたってセル面積の差が3%未満です。

スケール H3解像度 H3セル面積 Geohash文字数 Geohashセル面積 S2レベル S2セル面積
都市 5 約253 km² 4文字 約780 km² 10 約325 km²
近隣 9 約0.11 km² 6文字 約0.74 km² 15 約0.32 km²
建物 11 約0.0022 km² 7文字 約0.023 km² 18 約0.005 km²

隣接検索と境界効果

隣接クエリは空間インデックスで最も一般的な操作です。「このセルに隣接するすべてのセルを探す」という操作です。3つのシステムは、それぞれ大きく異なる使い勝手でこれを処理します。

H3は最もクリーンな隣接モデルを持ちます。すべての六角形はちょうど6つの辺共有の隣接セルを持ちます。コーナー隣接も曖昧さもありません。gridDisk(cell, k)関数は1回の呼び出しでkリング以内のすべてのセルを返します。k=1のディスクは7セル(中心を含む)、k=2は19セルを返します。六角形はすべての辺の長さが等しいため、リング数を使った距離クエリは幾何学的に一貫しています。

Geohashは8つの隣接セルを持ちます(4辺+4コーナー)。ほとんどのセルではこれで問題ありません。有名な問題はグリッド境界でのものです。地理的に隣接する2つのセルが、共通のプレフィックスをまったく持たないGeohash文字列を持つことがあります。たとえば、本初子午線(経度0°)や赤道をまたぐセルは、まったく異なるハッシュを持つことがあります。典型的な症状:クエリが境界の片側の結果は返すが、境界をまたいだ直後の点が静かに欠落する。

実際にドキュメントされた事例があります。フランス南西部のLa Roche-ChalaisはGeohashプレフィックスがu000で、わずか30km離れたPomerolはezzzです。2つの近傍地点の間に大きなセル境界が走っているため、どの長さのプレフィックスでも共通部分がありません。「La Roche-Chalais付近のポイント」というプレフィックスベースの近接クエリは、Pomerolの結果をまったく返しません。本初子午線(経度0°)でも同じ問題が起きます。本初子午線の両側に10m離れて存在する2点のGPSポイントは、e(西側)とs(東側)で始まるハッシュを持ちます——共通プレフィックスなし、共通インデックス範囲なし。標準的な対策は、対象セルとその8隣接セルを常にクエリし、実際のハーバサイン距離でフィルタリングすることです。RedisのGEOSEARCHコマンドはこれを内部で処理しており、自前のGeohash近接ロジックよりもデータベースのネイティブサポートを使う方が望ましい理由のひとつです。

S2はヒルベルト曲線を使用しており、強力な局所性の保証を提供します——地球上で近い点はインデックス上でも近い整数になります。ただし「ヒルベルト空間で近い」と「空間的に隣接している」は必ずしも同じではありません。6つの立方体面の継ぎ目があり、面境界をまたぐ隣接セルの計算には注意が必要です。S2のS2CellId::EdgeNeighbors()はこれを正しく処理しますが、H3のgridDiskよりも実装の手間がかかります。

海岸線、国境、日付変更線など地理的境界をまたぐデータを扱う場合、H3の均一な六角形トポロジーがより安全なデフォルト選択です。Geohashのような長方形グリッドシステムは予測可能な経度・緯度に継ぎ目があり、本番環境で境界の欠落をデバッグするのは苦痛です。

各システムをいつ使うか

すでにRedisやElasticsearchを使っているなら、Geohashを使いましょう——追加コードなしで近接検索がすぐに動きます。グローバルな一貫性や六角形のビジュアライゼーションが必要ならH3を。任意のポリゴンをインデックスする必要がある場合や、既存のGoogleインフラと連携する場合のみS2を検討してください。以下の表で一般的なシナリオをまとめます。

ユースケース 推奨 理由
Redis GEOコマンド / Elasticsearch geo_point Geohash ネイティブサポート、設定ゼロ
ライドシェア・フードデリバリーの需要ヒートマップ H3 均一なセルサイズ+六角形ビジュアライゼーション
グローバルデータ集計(気象、テレメトリ) H3 極付近の緯度歪みを解消
任意ポリゴンのリージョンインデックス S2 S2RegionCovererで任意形状を近似
ゲームのグリッドシステム(Pokémon GOが使用) S2 Nianticの元インフラ選択
クイックプロトタイプと簡単なジオフェンシング Geohash 学習コスト最小、人間が読めるID
マルチ解像度分析(ズームイン/アウト) H3 クリーンな親子階層
PostGIS / 空間データベース Geohash 標準GISエコシステムのサポート

エコシステムの成熟度が、ジオメトリよりも先に選択を決めることがよくあります。GeohashはRedisに2015年から組み込まれており(GEOADDGEORADIUS)、Elasticsearchのgeo_pointフィールドタイプはグリッド集計に内部でGeohashを使っています。すでにこれらのスタックを使っているなら、Geohashの統合はほぼ追加作業なしで済みます。

H3にはPython(h3)とJavaScript(h3-js)の充実したライブラリ、成長中のビジュアライゼーションツールのエコシステム(Deck.glのH3HexagonLayer)、そしてDuckDB空間拡張でのファーストクラスサポートがあります。S2はもっとも強力なジオメトリ操作を持ちますが、JavaScriptサポートが最も弱く——メインライブラリはC++で、他言語へのポートはコミュニティメンテナンスです。

GeoHash変換

GeoHash、H3、Plus Code間の変換。

Try it now

H3・Geohash・S2のコード実例

3つのシステムすべてにJavaScriptとPythonのライブラリがあります。以下の例では、最もよく使う3つの操作をカバーします。座標のエンコード、隣接セルの取得、セルの境界ポリゴンの取得です。h3-jsとngeohashのnpmパッケージの詳細な比較(バンドルサイズ、エンコードベンチマーク、本番環境での注意点)については、h3-js vs ngeohashガイドをご覧ください。

出荷前に知っておくべきライブラリの落とし穴が2つあります。

ngeohash整数精度encode_int / decode_int関数はJavaScript(標準のIEEE 754 float64安全整数範囲)では52ビットで上限があります。ほとんどのユースケースでは問題ありませんが、bitDepthを52より大きく設定すると、結果が静かに精度を失います。整数を使う特別な理由がない限り、文字列API(encode / decode)を使い、エンコードとデコードの両方で同じbitDepth値を一貫して渡してください。

h3-jsのセルID表現:H3のセルIDは64ビット整数で、JavaScriptの安全整数範囲(2^53 − 1)を超えます。h3-jsライブラリはデフォルトで文字列として返します(例:"8928308280fffff")。よくある本番バグ:APIがH3 IDをJSON文字列ではなく数値としてシリアライズすると、一部のJSONパーサーが値を静かに丸めてセルIDが壊れます。データベーススキーマ、APIレスポンス、フロントエンドコードにわたって、H3 IDは常に文字列として扱いましょう。

JavaScript

import { latLngToCell, gridDisk, cellToBoundary } from "h3-js";
import ngeohash from "ngeohash";

// --- H3 ---
// Encode coordinate to H3 cell (resolution 9 ≈ 0.1 km²)
const h3Cell = latLngToCell(37.7749, -122.4194, 9);
// → "8928308280fffff"

// Get all cells within 1 ring (7 cells including center)
const h3Neighbors = gridDisk(h3Cell, 1);

// Get cell boundary polygon (array of [lat, lng] pairs)
const h3Boundary = cellToBoundary(h3Cell);

// --- Geohash ---
// Encode coordinate to Geohash (precision 6 ≈ 0.74 km²)
const hash = ngeohash.encode(37.7749, -122.4194, 6);
// → "9q8yyk"

// Decode back to coordinate
const { latitude, longitude } = ngeohash.decode(hash);

// Get 8 neighbors (N, NE, E, SE, S, SW, W, NW)
const geohashNeighbors = ngeohash.neighbors(hash);

// Get cell bounding box [minLat, minLon, maxLat, maxLon]
const bbox = ngeohash.decode_bbox(hash);

Python

import h3
import geohash  # pip install python-geohash

# --- H3 ---
cell = h3.latlng_to_cell(37.7749, -122.4194, 9)
neighbors = h3.grid_disk(cell, 1)
boundary = h3.cell_to_boundary(cell)  # list of (lat, lng) tuples

# --- Geohash ---
hash_str = geohash.encode(37.7749, -122.4194, precision=6)
decoded_lat, decoded_lng = geohash.decode(hash_str)
neighbors = geohash.neighbors(hash_str)  # dict with N/NE/E/SE/S/SW/W/NW keys

JavaScriptのS2はコミュニティポート(s2-geometry npmパッケージ)が必要で、C++ライブラリから遅れています。JS環境での本格的なS2作業には、Python/Javaサービスを呼び出すか、WASMを使ったCloudflare Workersを利用するのが現実的なアプローチです。Pythonではs2sphereライブラリがクリーンなインターフェースを提供します。

import s2sphere

# Encode coordinate to S2 cell (level 15 ≈ 0.32 km²)
latlng = s2sphere.LatLng.from_degrees(37.7749, -122.4194)
cell_id = s2sphere.CellId.from_lat_lng(latlng).parent(15)

# Get 4 edge neighbors
neighbors = cell_id.edge_neighbors()

# Cover an arbitrary polygon with S2 cells
region_coverer = s2sphere.RegionCoverer()
region_coverer.min_level = 10
region_coverer.max_level = 15
covering = region_coverer.get_covering(some_s2_region)

KunYuのGeoHashエンコーダーツールを使えば、ブラウザ上でGeohashとH3のエンコードを試せます——ライブラリのインストール不要です。

GeoHash変換

GeoHash、H3、Plus Code間の変換。

Try it now

よくある質問

H3はGeohashより優れていますか?

既存のインフラ制約がない新規プロジェクトなら、H3がより良いデフォルト選択です。均一なセルサイズ、クリーンな隣接クエリ、境界バグなし。一方、Geohashが有利なのは、RedisのGEOコマンドやElasticsearchの集計をそのまま使いたい場合、または人間が読めるIDが重要な場合です——GeohashはログでID 8928308280fffffのようなH3 IDより読みやすいです。

RedisはH3をサポートしていますか?

RedisにはネイティブのH3サポートはありません。組み込みのGEOコマンド(GEOADDGEORADIUSGEOSEARCH)は内部でGeohashを使っています。RedisでH3を使うには、アプリケーション層で座標をH3セルIDにエンコードし、通常の文字列キーやソート済みセットとして保存します。Redis 7.x時点では、コアサーバーにH3プリミティブを追加する計画はありません。

S2は何の略ですか?

S2はSphere²(球の二乗)の略で、球を二次元面にマッピングするという数学的概念を指します。このライブラリはGoogleで開発され、Google Maps、Google Earth、その他のGoogleジオ製品で内部的に使用されています。

解像度5でH3セルは地球全体をいくつカバーしますか?

解像度5では、2,016,842個のH3セルが地球の表面を覆います(正二十面体を閉じるための12枚の五角形を含む)。各セルは約252.9km²をカバーするため、国レベルや大都市圏の分析に適した解像度です。

H3とGeohashの間で変換できますか?

直接的な数学的変換はありません。2つのグリッドは独立しており、解像度9のH3六角形と6文字のGeohashセルは異なるエリアをカバーするため、近似的にしかマッピングできません。実用的なアプローチは、どちらの形式もいったんlat/lng座標にデコードし、目的の形式に再エンコードすることです。サイズと形状の違いにより、変換後のセルが元のセルを完全に包含しない場合があることに注意してください。

Geohashの最大精度はどれくらいですか?

Geohashの仕様では最大12文字をサポートし、約3.7cm×1.9cmのセルになります。実際のアプリケーションではほとんどの場合6〜9文字(1km²から約7m²)を使います。9文字を超えると、精度はほとんどのGPSハードウェアが提供できる精度を超えており、長い文字列キーが不要なストレージとインデックスのオーバーヘッドを生み出します。