すべてのプロダクト
Search
ドキュメントセンター

PolarDB:GanosBase H3 地理グリッド:機能とベストプラクティス

最終更新日:Mar 28, 2026

GanosBase には、H3 の階層型六角形グリッドシステムを基盤とする地理空間グリッドエンジンが組み込まれています。この機能により、外部ミドルウェアや前処理パイプラインを経由せずに、データベース内で大規模な空間ポイントデータを直接エンコード、クエリ、集約、および可視化できます。

背景情報

地理グリッドは、粗いスケールから細かいスケールまで、地球表面を多段階のポリゴンセルに分割します。各セルには一意のコードが割り当てられ、空間フィーチャーを他の時空間データと連携して表現、インデックス化、結合することが可能になります。三次元地理グリッドでは、緯度・経度に加えて、高さも分割およびエンコーディング範囲に含めます。

GanosBase は以下の 2 種類のグリッドシステムをサポートしています:

  • GeoSOT:中国で開発された地理空間分割理論に基づく、多段階の地域位置識別システムです。

  • H3:Uber 社が開発した二次元グローバル六角形グリッドシステムです。H3 は、統一的かつ多層構造の六角形で地球表面全体をカバーします。

H3 の六角形セルは、空間分析に適した以下の 3 つの特性を持ちます:均一な隣接セル間距離、固定された 6 方向隣接関係、方向バイアスの欠如。これらの特性により、正方形または三角形グリッドと比較して、経路計画、ジオコーディング、ジオフェンシング、密度分析などの処理が簡素化されます。

GanosBase は、H3 を拡張し、複数の階層レベルにわたる最小限のグリッドセル集合で空間範囲を表現する「劣化グリッド計算(degenerated grid computing)」を実装しています。これにより、エンコーディングのストレージコストを削減できます。また、GanosBase には、グリッドコードの高速クエリおよび集約計算を実現する独自開発のグリッドインデックスも同梱されています。

H3 degenerated grid computing diagram

適用範囲/利用シーン/ユースケース

H3 グリッドは、空間データワークロード全般で活用されています:

  • 物流および旅行サービス:経路計画、エリアカバレッジ分析、配達範囲の明確化、ホットスポットの発見。

  • データ分析:人口密度分析、モバイルユーザー行動分析、地理的市場セグメンテーション。

  • IoT(モノのインターネット):スマートシティ、環境モニタリング、資産追跡などのシナリオにおけるモニタリングデータの空間分布分析。

  • ソーシャルネットワーク:位置情報ベースサービス(LBS)、友人位置共有、イベント通知。

  • 緊急対応および公共サービス:災害ディストリビューション分析、早期警戒のためのヒートマップ、緊急リソース割り当て、救助エリアの明確化。

機能

GanosBase H3 は、以下の操作をサポートしています:

  • ポイント、ライン、ポリゴンを H3 グリッドコードとしてエンコード

  • グリッドコードを空間ジオメトリへデコード

  • 親子グリッド関係の判定

  • グリッドパス距離およびトラバーサルの計算

  • 空間範囲、レベル、近接性に基づくグリッドクエリ

  • H3 グリッドコードを GanosBase Geometry 型へ変換し、ベクトルデータとの空間分析を実行

  • H3 グリッドとラスターデータを組み合わせたピクセル統計の実行

  • 劣化グリッド計算を適用し、エンコーディングのストレージを最小化

全関数リファレンスについては、「」をご参照ください。

技術的優位性

GanosBase H3 は、オープンソースの H3 ライブラリに加え、以下の追加機能を提供します:

  • 豊富なエンコーディング入力タイプ:緯度・経度座標だけでなく、GanosBase の Point、LineString、Polygon ジオメトリ型を直接 H3 グリッドコードとしてエンコード可能です。

  • パフォーマンス最適化:大規模データセット向けに、エンコーディングスループットおよびグリッドクエリ効率が最適化されています。

  • クロスモデルクエリ分析:H3 グリッドを GanosBase の他の空間モデル(例:ジオメトリから H3 エンコーディングへの変換、ラスターデータに対するピクセル統計)と結合できます。

  • 大規模データ向けの階層ストレージ:PolarDB の多態階層ストレージ機能を活用し、大規模ポイントデータを Object Storage Service (OSS) にエンコード・保存することで、ストレージコストを大幅に削減します。

ベストプラクティス

本セクションでは、GanosBase H3 を用いた空間ポイントデータの格納、エンコード、クエリ、および可視化に至る一連のワークフローを完全に解説します。使用するデータセットは、Uber 社が公開した 2023 年ニューヨーク市タクシー乗車データ(FOIL)です。

前提条件

開始前に、以下の準備が完了していることを確認してください:

  • GanosBase が有効化された PolarDB クラスター

  • FOIL CSV データを格納する OSS バケット

  • 拡張およびテーブル作成権限を持つデータベース認証情報

データのインポート

ステップ 1:GeomGrid 拡張のインストール

CREATE EXTENSION ganos_geomgrid CASCADE;

ステップ 2:`h3grid` フィールド型を含むテーブルの作成

GeomGrid は、H3 グリッドコードを格納するための h3grid 型を提供します。h3_lev13 フィールドにはレベル 13 の H3 コードが格納されます。異なる H3 レベルは異なる空間分解能に対応しており、分析の粒度に合ったレベルを選択してください。各 H3 レベルに対応する空間分解能については、「H3 解像度表」をご参照ください。

-- h3_lev13 にはレベル 13 の H3 コードを格納
CREATE TABLE FOIL2013 (
  id text,
  lon float,
  lat float,
  h3_lev13 h3grid
);

ステップ 3:FDW 拡張のインストールおよび OSS 上の CSV データの読み込み

GanosBase FDW(外部データラッパー)を使用すると、OSS 上の CSV ファイルをあたかもローカルテーブルであるかのようにクエリでき、その結果を直接挿入できます。

  1. FDW 拡張のインストール

    CREATE EXTENSION ganos_fdw CASCADE;
  2. OSS 上の CSV ファイルを指すサーバーの作成。プレースホルダー部分を実際の認証情報およびパスに置き換えてください。datasource のフォーマットについては、「」をご参照ください。

    CREATE SERVER csvserver
      FOREIGN DATA WRAPPER ganos_fdw
      OPTIONS (
        datasource 'OSS://<access_id>:<secrect_key>@[<Endpoint>]/<bucket>/path_to/file.csv',
        format 'CSV' );
    
    CREATE USER MAPPING FOR CURRENT_USER SERVER csvserver OPTIONS (user '<access_id>', password '<secrect_key>');
  3. CSV ファイルを外部テーブルにマッピング。列 medallionpickup_longitudepickup_latitude を外部テーブル trip_data_1 にマッピングします。

    CREATE FOREIGN TABLE trip_data_1 (
      medallion varchar,
      pickup_longitude varchar,
      pickup_latitude varchar)
      SERVER csvserver
      OPTIONS ( layer 'trip_data_1' );
  4. 外部テーブルへのアクセスを確認

    SELECT * FROM trip_data_1;
  5. データを FOIL2013 に挿入

    INSERT INTO FOIL2013
    SELECT medallion AS id,
           CAST(pickup_longitude AS double precision) AS lon,
           CAST(pickup_latitude AS double precision) AS lat
    FROM trip_data_1;
  6. インポートの確認

    SELECT * FROM FOIL2013;

ポイントを H3 グリッドコードとしてエンコード

GanosBase H3 は、複数のエンコーディング手法をサポートしています:

関数入力使用タイミング
緯度、経度、レベル生の座標をエンコードする場合
標準 H3 文字列既存の H3 文字列をインポートする場合
整数形式の H3 エンコーディング整数形式の H3 ID を扱う場合
ポイントジオメトリGanosBase Point 型から直接変換する場合

本例では、ST_H3FromLatLng を使用して、lat および lon 列からレベル 13 の H3 コードを生成し、結果を h3_lev13 に格納します。

-- 緯度・経度からレベル 13 の H3 コードを生成
UPDATE FOIL2013 SET h3_lev13 = ST_H3FromLatLng(lat, lon, 13);

-- 確認:H3 コードを文字列形式で表示
SELECT id, lon, lat, ST_AsText(h3_lev13) AS h3
FROM FOIL2013
LIMIT 100;

グリッドデータの集約

H3 グリッドは空間集約(ポイントをグリッドセル単位でグループ化し、ヒートマップやテーママップを生成)に最適です。以下の例では、レベル 13 の各グリッドセルごとのタクシー乗車回数をカウントします。

-- H3 セルごとのポイント数をカウントし、結果を格納
CREATE TABLE h3_count_lev13 AS
SELECT ST_AsText(h3_lev13) AS h3code, COUNT(*) AS count
FROM FOIL2013
GROUP BY h3_lev13;

-- 乗車回数上位のセルをクエリ
SELECT ST_AsText(h3_lev13), ST_AsText(geometry), count
FROM h3_count_lev13
ORDER BY count DESC;

グリッド近接性によるクエリ

を使用すると、ターゲット位置からのグリッドステップ数(セルホップ数)に基づいてポイントをフィルターできます。以下のクエリは、レベル 13 において座標 (40.71481749, -73.99100368) から 10 ステップ以内のすべての乗車ポイントを返します。

SELECT *
FROM foil2013
WHERE ST_GridDistance(
  ST_H3FromLatLng(40.71481749, -73.99100368, 13),
  h3_lev13
) < 10;

グリッド距離とは、同一レベルの 2 つの H3 セル間のセルホップ数であり、ユークリッド距離ではありません。

H3 グリッドの可視化

GanosBase は、データベースから直接 Mapbox Vector Tiles(MVT)形式で H3 グリッドを提供できます。Python バックエンドが現在の地図ビューポートに基づいてデータベースをクエリし、ベクタータイルをブラウザへストリーム送信します。専用のタイルサーバーは不要です。

GanosBase は、可視化時に H3 グリッドを動的に生成することもサポートしています。たとえば、テーブルにレベル 13 のデータが格納されているが、地図がレベル 10 のタイルを要求している場合、ST_AsMVTGeom(ST_H3FromLatLng(lat, lon, 10), ...) を使用してオンザフライで生成できます。事前計算済みのグリッドは高速ですが、動的生成は複数のグリッドレベルを格納する必要がなくなります。

以下の手順では、h3_count_lev13 から事前計算済みのグリッドを使用します。

ステップ 1:タイルクエリの高速化のためのグリッドインデックスの作成

CREATE INDEX ON h3_count_lev13 USING GIST(h3_lev13);

ステップ 2:タイル座標によるベクタータイルのクエリ

以下のクエリは、タイル (z=14, x=4826, y=6157) の MVT を返します。タイル座標は、ユーザーのビューポートおよびズームレベルに基づき、地図フロントエンドから取得されます。

SELECT ST_AsMVT(tile)
FROM (
  SELECT ST_AsMVTGeom(
           h3_lev13,
           ST_Transform(ST_TileEnvelope(14, 4826, 6157), 4326)
         ) AS grid,
         count
  FROM h3_count_lev13
  WHERE h3_lev13 && ST_Transform(ST_TileEnvelope(14, 4826, 6157), 4326)
) AS tile;

ステップ 3:フロントエンドの実行

可視化フロントエンドには、Python(Quart + asyncpg)バックエンドと、Mapbox GL JS を用いた HTML ページを使用します。Python スクリプトを起動し、ブラウザで localhost:5100 を開きます。スクリプトは、地図の現在位置およびズームレベルに基づきタイルクエリ SQL を動的に生成し、各セルを乗車回数に応じて色付けします。

H3 grid visualization animation
フロントエンドには Mapbox アクセストークンが必要です。YOUR_TOKENindex.html 内で自身のトークンに置き換えてください。

まとめ

地理グリッドは、モバイルオブジェクト関連のアプリケーションシナリオにおいて不可欠であり、軌道、ベクトル、ラスターなどのデータ型と統合することで、顕著なビジネス価値を実現します。世界で初めてモバイルオブジェクト(MOD)をサポートするデータベースとして、GanosBase は、交通、物流、旅行、自動車など多様な業界の顧客において、その地理空間グリッド機能を実証済みです。従来のミドルウェアや業務コードによる実装と比較して、GanosBase はデータベースシステムレベルで大規模モバイルオブジェクト向けの時空間処理フレームワークを提供し、計算効率を大幅に向上させるとともに、総合的なコストを削減します。

付録

以下は、H3 グリッド可視化のための完全なフロントエンド実装コードです。

Python バックエンド(`app.py`)

from quart import Quart, send_file, render_template
import asyncpg
import io
import re

## Database connection parameters
CONNECTION = {"host": "YOUR-HOST-NAME-OR-IP", "port": PORT_NO, "database": "DATABASE_NAME",
              "user": "USER_NAME", "password": "PASSWORD"}

## Target table name/field/ID
TABLE = "h3_count_lev13"
H3_COL = "h3_lev13"
H3_GEOM_COL = "geometry"
AGG_VAL_COL = "count"
COL_SRID = 4326

app = Quart(__name__, template_folder='./')


@app.before_serving
async def create_db_pool():
    app.db_pool = await asyncpg.create_pool(**CONNECTION)


@app.after_serving
async def close_db_pool():
    await app.db_pool.close()


@app.route("/")
async def home():
    sql = f'''
    SELECT ST_Extent(ST_Transform(ST_Envelope({H3_GEOM_COL}), 4326))
    FROM {TABLE};
    '''
    async with app.db_pool.acquire() as connection:
        box = await connection.fetchval(sql)
        box = re.findall('BOX\((.*?) (.*?),(.*?) (.*?)\)', box)[0]
        min_x, min_y, max_x, max_y = list(map(float, box))
        bounds = [[min_x, min_y], [max_x, max_y]]
        center = [(min_x + max_x) / 2, (min_y + max_y) / 2]
        return await render_template('./index.html', center=str(center), bounds=str(bounds))


@app.route("/h3_mvt/<int:z>/<int:x>/<int:y>")
async def h3_mvt(z, x, y):
    sql = f'''
    SELECT ST_AsMVT(tile.*)
    FROM
      (SELECT ST_AsMVTGeom({H3_COL}, ST_Transform(ST_TileEnvelope($1,$2,$3),{COL_SRID}), 4096, 512, true) geometry,
       {AGG_VAL_COL} count
      FROM {TABLE}
      WHERE ({H3_COL} && ST_Transform(ST_TileEnvelope($1,$2,$3),{COL_SRID}))) tile'''
    async with app.db_pool.acquire() as connection:
        tile = await connection.fetchval(sql, z, x, y)
        return await send_file(io.BytesIO(tile), mimetype='application/vnd.mapbox-vector-tile')

if __name__ == "__main__":
    app.run(port=5100)

HTML フロントエンド(`index.html`)

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>map viewer</title>
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <link href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css" rel="stylesheet">
  <script src="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.4.2/chroma.min.js"></script>
</head>

<body>
  <div id="map" style="position: absolute;left:0; top: 0; bottom: 0; width: 100%;cursor:pointer;"></div>
  <div class="counter"
    style="position: absolute;left:2%;font-size: 20px;padding: .1em .1em;text-shadow: 3px 3px 3px black;">
    

  </div>
  <script>
    let YOUR_TOKEN = "pk.eyJ1Ijoia3pmaWxlIiwiYSI6ImNqbHZueXdlZjB2cG4zdnFucGl1OHJsMjkifQ.kW_Utrh8ETQltRk6fnpa_A"

    mapboxgl.accessToken = YOUR_TOKEN;
    const map = new mapboxgl.Map({
      container: "map",
      style: "mapbox://styles/mapbox/navigation-night-v1",
      center: {{ center }},
      zoom: 1
    })

    map.on("load", () => {
      map.fitBounds({{ bounds }})

      map.on('mousemove', 'h3', (e) => {
        map.getCanvas().style.cursor = "default";
        if (e.features.length > 0)
          document.getElementById('count').innerText = e.features[0].properties.count
      })
      map.on('mouseleave', 'h3', () => {
        map.getCanvas().style.cursor = "grab";
        document.getElementById('count').innerText = 0
      })
      map.addSource("h3_source", {
        type: "vector",
        tiles: [`${window.location.href}h3_mvt/{z}/{x}/{y}`],
        tileSize: 512
      });

      // make color map
      const MIN = 1
      const MAX = 600
      const STEP = 10
      color_map = chroma.scale(["#536edb", "#5d96a5", "#68be70", "#91d54d", "#cddf37", "#fede28", "#fda938", "#fb7447", "#f75a40", "#f24734", "#e9352a", "#da2723", "#cb181d"])
        .domain([MIN, MAX]);
      let colors = []
      for (let i = MIN; i < MAX; i += STEP)
        colors.push(color_map(i).hex(), i)
      colors.push(color_map(MAX).hex())

      map.addLayer({
        id: "h3",
        type: "fill",
        source: "h3_source",
        "source-layer": "default",
        paint: {
          "fill-color": [
            "step", ["get", "count"],
            ...colors
          ],
          "fill-opacity": 0.8
        }
      });

    });
  </script>
</body>

</html>