H3インデックスは、地球の表面を六角形のセルで区切り、各セルに64bitのIDを振る地理空間インデックスです。Uberが開発し、2018年に公開しました。緯度経度と解像度(0〜15)を渡すと 892f5a32d97ffff のようなセルIDが返り、そのIDでGROUP BYすれば位置データをエリア単位で集計できます。
この記事では、解像度ごとのセルの大きさ、h3-py 4.5.0で動かしたコードと出力、v4での関数名の変更、SQLエンジンごとの引数順、親子セルのずれ、日本の地域メッシュとの比較を扱います。
まとめ:H3インデックスの要点
- H3は正二十面体を土台にした六角形の階層グリッドで、解像度は0〜15の16段階です。解像度0は122セル(六角形110個+五角形12個)で、1段細かくなるごとに面積はおよそ7分の1になります。
- 現行版はC core・h3-py・h3-jsとも4.5.0です。2022年8月23日のv4.0.0で関数名が変わり、
geoToH3はlatLngToCell、kRingはgridDiskになりました。v3時代の記事のコードはそのままでは動きません。 - 緯度と経度を逆に渡してもエラーにならず、別の場所のセルIDが返ります。ClickHouse・Databricks・BigQuery(CARTO)はエンジンごとに引数順が違います。
- 子セルは親セルの内側に完全には収まりません。解像度9のセルから親(解像度7)を引く集計と、解像度7で直接割り当てる集計は一致しません。
- H3は等積のグリッドではありません。解像度8のセル面積は国内でも福岡0.580 km²と仙台0.694 km²で約2割違い、地域メッシュ(3次メッシュ)の緯度による差と同じ程度です。密度を比べるときはどちらもセルごとの面積で割ります。
以下、仕組み、解像度の選び方、実装、落とし穴の順に説明します。
H3インデックスの仕組み:正二十面体・122基底セル・64bitのセルID
H3の公式ドキュメント(h3geo.org)によると、六角形のグリッドは球に外接する正二十面体の各面の上に作られ、それを球面に投影しています。解像度0の土台になる基底セルは122個です。解像度1以降は aperture 7 で細分化され、1段細かい解像度のセルの面積はおよそ7分の1になります。
六角形を選んだ理由と、避けられない12個の五角形
Uber Engineeringの紹介記事(2018年6月27日、Isaac Brodsky)は、六角形を選んだ理由を「中心から隣接セルまでの距離が1種類しかない」点に置いています。正方形なら辺で接する隣と角で接する隣で2種類、三角形なら3種類の距離があります。距離が1種類なので、周囲のセルを平均する平滑化や、需要の勾配を扱う計算が単純になります。
ただし、球面を六角形だけで隙間なく覆うことはできません。H3は正二十面体の12個の頂点に五角形を置いており、どの解像度でも五角形はちょうど12個あります。h3-pyで解像度9の五角形 89080000003ffff の近傍を取ると、六角形なら7セル返る grid_disk(cell, 1) が6セルしか返しません。公式ドキュメントによると、正二十面体の向きは12個の頂点(五角形の中心)がすべて海上に来るように固定されています。ただし海上にあるのは中心だけで、粗い解像度では五角形のセルが陸地にかかります。たとえばオスロを含む解像度0のセル 8009fffffffffff は五角形です。粗い解像度や全球を扱う処理では、近傍の数を7と決め打ちしないようにします。
セルIDの64bit構造と15桁の16進表記
セルIDは64bitの整数です。上位から、予約1bit、モード4bit、予約3bit、解像度4bit、基底セル番号7bitと続き、残り45bitに解像度1〜15の各桁が3bitずつ入ります。使わない下位の桁は 111(7)で埋めるため、解像度が粗いIDほど末尾に f が並びます。
東京駅の解像度9のセルは 892f5a32d97ffff(整数では 617826180096589823)、その親の解像度7は 872f5a32dffffff です。2文字目が解像度を表し、親子で先頭部分が共通しています。セルID(モード1)は最上位の4bitが0になるため、ゼロ埋めしない小文字の16進表記で15文字になります。DBには15文字の文字列のほか、8バイトの BIGINT/UInt64 でも保存でき、Snowflake・Databricks・ClickHouseの変換関数は整数型で返します。
解像度0〜15の選び方:主な解像度のセル面積とエッジ長
解像度は「集計したい現象の空間的な広がり」に合わせて決めます。下の表は16段階のうち主な7段階を抜き出したもので、値は h3geo.org の解像度表と、h3-py 4.5.0 の average_hexagon_area・average_hexagon_edge_length・get_num_cells の値です。
| 解像度 | 平均六角形面積 | 平均エッジ長 | 全球のセル数 |
|---|---|---|---|
| 0 | 4,357,449 km² | 1,281 km | 122 |
| 5 | 252.9 km² | 9.85 km | 2,016,842 |
| 7 | 5.161 km² | 1.41 km | 98,825,162 |
| 8 | 0.7373 km² | 531 m | 691,776,122 |
| 9 | 0.1053 km² | 201 m | 4,842,432,842 |
| 10 | 0.01505 km² | 75.9 m | 33,897,029,882 |
| 15 | 0.895 m² | 0.584 m | 569,707,381,193,162 |
エッジ長は六角形の1辺の長さで、向かい合う辺どうしの距離はその約1.7倍です。解像度9なら差し渡し約350mで、駅前の数ブロック分にあたります。
用途別の解像度の目安と、細かくしすぎる失敗
市区町村より広い範囲の傾向を見るなら解像度5〜6、都市内の需給やエリア別の売上なら解像度7〜8、徒歩圏や街区なら解像度9〜10が出発点です。
細かくしすぎると、1セルあたりの件数が0件か1件ばかりになり、ヒートマップがまだらになるだけで傾向が読めません。解像度を1段上げるとセル数は約7倍になるので、件数の少ないセルが目立つなら1〜2段粗くします。表示用の解像度と保存用の解像度を分け、細かい解像度で保存しておき、cell_to_parent で粗い解像度へ集約する運用が扱いやすい構成です。ただし、この集約には後述するずれがあります。
PythonでH3を使う:h3-py 4.5.0の基本操作と実行結果
h3-pyは pip install h3 で入ります。以下のコードと出力は、pip install h3==4.5.0 で入れた h3-py 4.5.0(Python 3.14)で実行して得たものです。
緯度経度からセルIDへの変換と境界の取得
import h3
lat, lng = 35.681236, 139.767125 # 東京駅
cell = h3.latlng_to_cell(lat, lng, 9)
print(cell)
print(h3.get_resolution(cell), h3.str_to_int(cell))
print(h3.cell_to_latlng(cell))
print(len(h3.cell_to_boundary(cell)))
print(round(h3.cell_area(cell, "km^2"), 4))
892f5a32d97ffff
9 617826180096589823
(35.67988860818612, 139.76797364575577)
6
0.0969
cell_to_latlng が返すのはセルの中心で、入力した座標そのものではありません。この例でも中心は東京駅から約170mずれています。地図にはセルの中心ではなく、cell_to_boundary の頂点で多角形を描きます。頂点は通常6個ですが、五角形や正二十面体の面の境目をまたぐセルでは7〜10個になります(解像度3の全41,162セルのうち2,082セル)。このセルの面積 0.0969 km² は、解像度9の平均 0.1053 km² より8%ほど小さい値です。
近傍セル・グリッド距離・親子変換とcompact
import h3
cell = "892f5a32d97ffff"
ring1 = h3.grid_disk(cell, 1)
print(len(ring1), len(h3.grid_disk(cell, 2)))
neighbor = sorted(c for c in ring1 if c != cell)[0]
print(h3.grid_distance(cell, neighbor))
parent = h3.cell_to_parent(cell, 7)
children = h3.cell_to_children(parent, 9)
print(parent, len(children))
print(h3.compact_cells(children))
7 19
1
872f5a32dffffff 49
['872f5a32dffffff']
grid_disk(cell, k) は中心を含めて k 段目までのセルを返し、近くに五角形が無ければ k=1 で7個、k=2 で19個です(五角形に隣接する六角形 8908000000bffff では k=2 が18個でした)。k はセルの段数であってメートルではないため、「半径500m以内の店舗」の検索では、grid_disk のセル集合との IN 結合で候補を絞り、最後に実際の距離で判定します。compact_cells は、同じ親の子が全部そろっていれば親1個にまとめる関数で、解像度9の49セルが解像度7の1セルに縮みました。配達エリアのような多角形を大量のセルで持つとき、保存件数を減らせます。
v3からv4への関数名の対応:古いコードが動かない理由
2022年8月23日のv4.0.0で、H3は関数名を大きく変えました。公式の移行ガイドにある対応のうち、よく使うものを抜き出します。h3-pyではこれをスネークケースにした名前(latlng_to_cell など)になります。
| v3.x(旧) | v4.x(現行) | h3-py 4.x |
|---|---|---|
| geoToH3 | latLngToCell | latlng_to_cell |
| h3ToGeo | cellToLatLng | cell_to_latlng |
| h3ToGeoBoundary | cellToBoundary | cell_to_boundary |
| kRing | gridDisk | grid_disk |
| h3Distance | gridDistance | grid_distance |
| h3ToParent | cellToParent | cell_to_parent |
| h3ToChildren | cellToChildren | cell_to_children |
| compact | compactCells | compact_cells |
| polyfill | polygonToCells | polygon_to_cells |
| h3IsValid | isValidCell | is_valid_cell |
h3-py 4.5.0 には geo_to_h3 が残っておらず、呼ぶと AttributeError になります。v4.0.0(2022年8月)より前に書かれた解説記事のコードを動かすときは、先にこの表で関数名を置き換えます。関数名だけでなく引数の型が変わったものもあります。h3-py 4.5.0 の polygon_to_cells はGeoJSONの辞書を受け付けず(ValueError)、辞書を渡すなら geo_to_cells を使います。v3系に固定する場合は pip install "h3<4" で入れられますが、新規のコードをv3で書く理由はありません。
SQLでH3を使う:DuckDB・Snowflake・Databricks・BigQuery・ClickHouse
主要なDWHとOLAPエンジンはH3をSQL関数として持っています。関数名は似ていますが、緯度と経度のどちらを先に渡すかがエンジンごとに違います。
| エンジン | セルIDへの変換関数 | 先に渡す値 |
|---|---|---|
| DuckDB(h3拡張) | h3_latlng_to_cell | 緯度 |
| Snowflake | H3_LATLNG_TO_CELL | 緯度 |
| PostgreSQL(h3-pg 4.5.0) | h3_latlng_to_cell | point型・geometry型 |
| ClickHouse 25.5以降 | geoToH3 | 緯度 |
| ClickHouse 25.4以前 | geoToH3 | 経度 |
| Databricks(11.3 LTS以降) | h3_longlatash3 | 経度 |
| BigQuery(CARTO) | H3_FROMLONGLAT | 経度 |
ClickHouseはv25.5で geoToH3 の引数を「経度, 緯度」から「緯度, 経度」へ入れ替えました。公式ドキュメントには、旧挙動に戻す設定 geotoh3_argument_order = 'lon_lat' が載っています。ClickHouseを25.4以前から上げるときは、既存クエリの結果が静かに変わる点に注意が要ります。列指向エンジンとしての ClickHouse の特徴はClickHouseの仕組みとMergeTreeの解説にまとめています。
DuckDBでの実行例
DuckDBでは community extension の h3 を入れると、ローカルのParquetやCSVをそのままH3で集計できます。以下は DuckDB 1.4.5 の Python API で実行した結果です。
INSTALL h3 FROM community;
LOAD h3;
SELECT h3_latlng_to_cell_string(35.681236, 139.767125, 9) AS cell9,
h3_h3_to_string(
h3_cell_to_parent(h3_latlng_to_cell(35.681236, 139.767125, 9), 7)
) AS parent7,
len(h3_grid_disk(h3_latlng_to_cell(35.681236, 139.767125, 9), 1)) AS disk1;
-- 892f5a32d97ffff | 872f5a32dffffff | 7
h3-pyと同じセルIDが返ります。実データなら GROUP BY h3_latlng_to_cell(lat, lng, 8) で件数を数え、結果をそのまま地図ライブラリへ渡せます。DuckDB自体の特徴はDuckDBの特徴とSQLite・PostgreSQLとの違いで、Spatial拡張でベクタタイルを作る手順はDuckDBのST_AsMVTによるベクタタイル作成で扱っています。
緯度経度の取り違えと親子セルのずれ:集計が狂う2つの落とし穴
次の2つはどちらもエラーが出ないため、結果の地図を見るまで気づけません。
緯度と経度の取り違え:h3-py・DuckDB・Snowflakeで例外が出ない挙動
import h3
# 緯度と経度を逆に渡してもエラーにならない
print(h3.latlng_to_cell(139.767125, 35.681236, 9))
print(h3.cell_to_latlng("89377602b83ffff"))
89377602b83ffff
(40.23250532465461, -144.320638696267)
緯度に139.77を渡しても、h3-pyは例外を出さずにセルIDを返しました。その中心は北緯40度・西経144度、北太平洋の真ん中です。DuckDBの h3_latlng_to_cell でも同じIDが返ります。Snowflakeの H3_LATLNG_TO_CELL はドキュメントに「範囲外の緯度は[-90, 90]へ折り返す」と明記しており、やはりエラーにはなりません。
前節の表のとおり、Databricksと BigQuery(CARTO)は経度が先です。複数のエンジンをまたいでセルIDを突き合わせるなら、ロード時に「日本の緯度は20〜46、経度は122〜154の範囲に収まるか」を検査するSQLを1本入れておくと、取り違えをその場で止められます。
親子セルの幾何学的なずれ:境界付近の点の6%が別の親へ
H3の親子関係は、IDの上では正確ですが、形の上では近似です。Uberの紹介記事も「六角形は7つの六角形にぴったり分割できないため、細かいセルは親セルにおおよそ含まれるにすぎない」と書いています。
h3-py 4.5.0で、東京駅を含む解像度7のセルとその子(解像度8の7セル)を比べると、子セルの頂点42個のうち10個が親の六角形の外側にありました。さらに、親の六角形を囲む緯度経度の矩形に一様乱数で2万点を置き(random.seed(1))、六角形の内側に入った13,734点を解像度9のセル経由で cell_to_parent(…, 7) に通すと、864点(6.29%)が別の解像度7セルに割り当てられました。
実務上の結論は1つです。解像度7の集計値は、座標から latlng_to_cell(lat, lng, 7) で直接作ったものと、解像度9から cell_to_parent で集約したものを混ぜない。どちらの作り方でもよいので、1つのダッシュボードやテーブルの中では片方に揃えます。境界の数%がずれても困らない需要予測ならロールアップで構いません。一方、「このエリアに含まれる店舗数」を契約や料金計算に使うなら、H3のセルではなく実際の行政界ポリゴンで判定します。
地域メッシュ・Geohash・S2との比較:H3を選ぶ条件と選ばない条件
日本で面的な集計をするとき、最初に比較対象になるのはH3ではなく標準地域メッシュです。国勢調査や経済センサスのメッシュ統計は、昭和48年行政管理庁告示第143号の地域メッシュで提供されています(総務省統計局)。
標準地域メッシュとH3解像度8の面積比較
基準地域メッシュ(3次メッシュ)は、緯度30秒×経度45秒で区切った「約1km」四方の区画です。経度の幅は北へ行くほど狭まるので、面積は緯度によって変わります。30秒×45秒の区画の面積を球面近似で計算し、同じ地点のH3解像度8のセル面積(h3-py 4.5.0の cell_area)と並べました。
| 地点 | 3次メッシュの面積 | H3解像度8の面積 |
|---|---|---|
| 那覇(北緯26.2度) | 1.155 km² | 0.681 km² |
| 福岡(北緯33.6度) | 1.073 km² | 0.580 km² |
| 大阪(北緯34.7度) | 1.059 km² | 0.619 km² |
| 東京駅(北緯35.7度) | 1.046 km² | 0.678 km² |
| 仙台(北緯38.3度) | 1.011 km² | 0.694 km² |
| 札幌(北緯43.1度) | 0.941 km² | 0.692 km² |
3次メッシュは南の那覇が最大、北の札幌が最小で、差は約19%です。H3解像度8も福岡0.580 km²と仙台0.694 km²で約2割違い、ばらつきの大きさは同程度でした。違うのはばらつき方です。3次メッシュは緯度だけで面積が決まり、北ほど小さくなります。H3は緯度の順に並ばず、福岡と大阪は札幌より南にありながら、セルは札幌より小さくなっています。H3の面積はメッシュコードのように計算で予測できないので、セルごとに求めるしかありません。
地球全体ではさらに差が開きます。h3-py 4.5.0で解像度4の全288,122セルの面積を計算すると、最小896.6 km²、最大2,136.0 km²で、2.38倍でした(五角形を除いても1.97倍)。「H3はセルの大きさが均一」という説明を見かけますが、面積は均一ではありません。国内の分析でも、密度を比べるときは件数を cell_area で割った値を使います。
Geohash・S2との違い
H3の公式比較ページによると、Geohashは位置を文字列に符号化する四分木の正方形グリッドで、緯度によってセル面積が大きく変わります。S2も正方形系のセルですが、親は4つの子にぴったり分割されるため、子に含まれる点は必ず親にも含まれます。正方形には辺で接する隣と角で接する隣の2種類がありますが、H3の隣接は辺で接する1種類だけです。
選び方の判断は次のとおりです。
- 国の統計(人口・事業所数)と重ねるなら、地域メッシュを使います。公的データがメッシュコードで配られているため、H3へ変換すると境界で按分の誤差が出ます。
- 移動・配車・配送のように「隣のエリアへの広がり」を計算するなら、H3を使います。隣接距離が1種類で、
grid_diskがそのまま使えます。 - 親子の包含が厳密でなければならない処理(階層的な権限範囲や課金エリアの判定)では、H3を使いません。S2か、実ポリゴンで判定します。
- 半径検索だけが目的で、RedisやValkeyのGEOコマンドで足りるなら、H3を持ち込む必要はありません。GEOコマンドは内部でGeohashを使い、隣接する区画まで含めて半径内を探します。前方一致1つだけでは区画の境目の向こうにある近い点を取りこぼす点には注意が要ります。
座標系やデータ形式の選び方全般はGISデータの形式・座標系・取り込み設計で解説しています。
UberでのH3の使われ方とオープンソース化の経緯
Uberの紹介記事には、各都市の需要と供給を六角形ごとに測ってサージプライシング(混雑時の価格上昇)を計算している、と書かれています。乗車リクエストやドライバーの位置をセル単位に束ね、セルごとの需給の偏りを見て価格と配車を調整する使い方です。記事は2018年6月27日の公開で、「同年の初めにGitHubでH3をオープンソース化した」と述べています。
ライセンスは Apache License 2.0 です。コアはC言語で書かれ、Python(h3-py)、JavaScript(h3-js)、Java などのバインディングが公式・コミュニティから出ています。2026年9月時点の最新はC core v4.5.0(2026年5月21日)、h3-py 4.5.0(PyPI 2026年5月30日)、h3-js 4.5.0(2026年7月1日)で、3つの版番号が揃っています。
地図上にセルを描くツールとしては、公式サイトのh3geo.orgのほか、Web地図ライブラリの deck.gl が H3HexagonLayer を持っています。タイル配信やライブラリの選び方はWeb GISのタイル配信とライブラリ選定を、背景地図に使うOpenStreetMapの利用条件はOpenStreetMapの特徴・API・商用利用を参照してください。
よくある質問
H3インデックス(h3 index)とは何ですか?
地球の表面を六角形のセルに区切り、各セルに64bitのIDを振る地理空間インデックスです。Uberが開発し、2018年にApache License 2.0で公開しました。緯度経度と解像度(0〜15)からセルIDを求め、そのIDで集計や結合を行います。解像度9なら1セルの平均面積は約0.105 km²、解像度7なら約5.16 km²です。IDは 892f5a32d97ffff のような15文字の16進文字列、またはBIGINTとして扱えます。
Uber H3の解像度はどれを選べばよいですか?
都市内の需給やエリア別売上なら解像度7〜8、徒歩圏や街区の分析なら9〜10から始めます。解像度を1段上げるとセル数は約7倍になるので、1セルあたりの件数が0〜1件ばかりになったら1〜2段粗くします。細かい解像度で保存して表示時に cell_to_parent で粗くする方法もありますが、その集計は座標から直接作った粗い解像度の集計とは一致しない点に注意してください。
geo_to_h3やkRingが使えないのはなぜですか?
2022年8月23日のv4.0.0で関数名が変わったためです。geoToH3 は latLngToCell(h3-pyでは latlng_to_cell)、kRing は gridDisk(grid_disk)、h3ToParent は cellToParent になりました。h3-py 4.5.0 には旧名が残っていないため、AttributeErrorになります。古いサンプルを動かすときは、公式の移行ガイドの対応表で関数名を置き換えてください。
H3のセルはすべて同じ面積ですか?
同じではありません。H3は等積グリッドではなく、解像度4の全セルでは最小と最大で約2.4倍の差があります。また、各解像度に12個ずつ五角形が含まれます。日本国内でも、解像度8のセルは福岡で0.580 km²、仙台で0.694 km²と約2割違います。差は緯度の順にも並ばないため、密度を比べるときは件数を cell_area で割って比べます。
H3のセルを地図で確認するには(h3 index viewer)?
手元のセルIDを確認するだけなら、h3-pyの cells_to_geo でGeoJSONのポリゴンに変換して地図に載せるのが確実です。cell_to_boundary は(緯度, 経度)の順で頂点を返すので、自分でGeoJSONを組むときは(経度, 緯度)へ並べ替えます。多数のセルを色分けして描くなら、deck.gl の H3HexagonLayer にセルIDと値の列を渡します。IDが北太平洋など想定外の場所に出る場合は、緯度と経度を逆に渡している可能性が高いので、変換関数の引数順を確認してください。