DuckDB Wasmで
地理空間情報を扱う

@northprint

話すこと

  • DuckDB Wasmの利点
  • DuckDBのSpecial Extension
  • たとえばどんなことができるのか

そもそもDuckDBとは?

分析用データベース

  • SQLiteの分析版のようなもの
  • 大量データの集計・分析に特化
  • インメモリで高速処理
  • 組み込み型(サーバー不要)

OLAP vs OLTP

  • OLTP (Online Transaction Processing)
    • MySQL, PostgreSQLなど
    • 個別のレコードの追加・更新・削除
    • 行指向のデータ構造
  • OLAP (Online Analytical Processing)
    • DuckDB, ClickHouse, BigQueryなど
    • 大量データの集計・分析
    • 列指向のデータ構造

一般的な使用例

  • CSVやParquetファイルの高速分析
  • データサイエンティストのローカル分析環境
  • データの下ごしらえを自動化する仕組み
  • ログ分析・時系列データ処理

なぜDuckDB Wasm?

地理空間処理が必要になった時

  • PostGISの環境構築から始めることが多い
  • サーバー処理の事は考える必要がある
  • 「とりあえず試す」のハードルが少し高い

DuckDB Wasmなら


// Svelteアダプターを使用
import { createDuckDB } from '@northprint/duckdb-wasm-adapter-svelte';

// データベースを初期化
const db = createDuckDB({ autoConnect: true });

// 空間分析を開始
await db.exec("INSTALL spatial");
await db.exec("LOAD spatial");
const result = db.query("SELECT ST_Distance(...)");
      

サーバー不要で空間分析ができる

ちなみにTurf.jsというものもあるが、今は考えない

実際の初期化


import * as duckdb from '@duckdb/duckdb-wasm';

// WASMファイルのパスを設定
const MANUAL_BUNDLES = {
  mvp: {
    mainModule: './duckdb-mvp.wasm',
    mainWorker: './duckdb-browser-mvp.worker.js',
  },
  eh: {
    mainModule: './duckdb-eh.wasm',
    mainWorker: './duckdb-browser-eh.worker.js',
  },
};

// Worker作成とDB初期化
const bundle = await duckdb.selectBundle(MANUAL_BUNDLES);
const worker = new Worker(bundle.mainWorker);
const logger = new duckdb.ConsoleLogger();
const db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
const conn = await db.connect();

// Spatial Extensionを読み込み
await conn.query("INSTALL spatial");
await conn.query("LOAD spatial");
      

mvpとehの違い

  • mvp (Minimum Viable Product)
    • 基本版、幅広いブラウザサポート
    • 例外処理をJavaScriptで実装
  • eh (Exception Handling)
    • WebAssembly Exception Handling対応版
    • ネイティブ例外処理で高速動作

selectBundleがブラウザの機能を検出して自動選択

Spatial Extension

  • DuckDBの拡張機能の1つ
  • PostGIS互換のSQL関数を提供
  • ST_Point, ST_Distance, ST_DWithinなど
  • GeoJSON, WKT形式のサポート

Spatial Functions

DuckDBの空間関数一覧

地理空間処理が身近に

  • そこそこお手軽にGIS分析
  • プロトタイプが数分で作れる
  • SQLを知っていれば空間分析もできる

よくあるシナリオ

「好きなブランドの自動販売機はどこにある?」

  • コカコーラの自販機を探したい
  • 現在地から一番近い自販機は?
  • この周辺にはどんな自販機がある?

DuckDB WASMによる解決

デモンストレーション

使用データ

  • OpenStreetMapの自動販売機データ(日本全国)
  • ファイルサイズ: 12MB(約5万件)
  • 含まれる情報: 位置、ブランド、販売商品、決済方法

実装例

基本的なクエリ


SELECT id, lat, lon, operator, vending,
  ST_Distance_Sphere(geom, ST_Point(139.7, 35.7)) as distance
FROM vending_machines 
WHERE operator LIKE '%コカ%'
ORDER BY distance 
LIMIT 10;
			

ブランド比較クエリ


SELECT operator,
  COUNT(*) as count,
  AVG(ST_Distance_Sphere(geom, ST_Point(139.7, 35.7))) as avg_distance
FROM vending_machines 
WHERE ST_DWithin(geom, ST_Point(139.7, 35.7), 10000)
  AND operator IN ('コカ・コーラ', 'サントリー', 'ダイドー')
GROUP BY operator;
			

実際の活用例: エリア分析


-- 5km圏内のブランド分布を集計
SELECT 
  operator,
  COUNT(*) as count
FROM vending_machines
WHERE ST_DWithin(
  ST_Point(lon, lat), 
  ST_Point(139.7, 35.7), 
  5000  -- 5km圏内
)
GROUP BY operator
HAVING COUNT(*) > 10
ORDER BY count DESC;
      

エリア分析の解説

  • ST_DWithin: 指定した距離内にあるかを判定
  • ST_Point(lon, lat): 経度緯度から点を生成
  • 5000: 半径5000m(5km)を指定
  • HAVING COUNT(*) > 10: 10件以上のブランドのみ表示

→ エリア内の主要ブランドを把握できる

実際の活用例: 競合分析


-- コカ・コーラから最も近いサントリーを探す
SELECT 
  a.id as coca_id,
  ST_Distance_Sphere(
    ST_Point(a.lon, a.lat),
    ST_Point(b.lon, b.lat)
  ) as distance_m
FROM vending_machines a, vending_machines b
WHERE a.operator = 'コカ・コーラ'
  AND b.operator = 'サントリー'
  AND a.id != b.id
ORDER BY distance_m
LIMIT 10;
      

競合分析の解説

  • ST_Distance_Sphere: 地球上の距離を計算
  • FROM a, b: 2つのテーブルを結合(自己結合)
  • ORDER BY distance_m: 近い順に並べ替え

→ 競合の配置状況を定量的に分析

パフォーマンス

  • 5万件のデータを数秒でロード
  • 空間検索: 10-50ms
  • 集計クエリ: 20-100ms
  • ブラウザメモリ使用量: 約200MB

制限事項

  • ブラウザのメモリ制限(通常2-4GB)
  • 大規模データセット(GB単位)はインメモリだと厳しめ(Wasm版)
  • 複数ユーザーでのデータ共有は要別途実装
  • 初回ロード時のWASMファイルサイズ(数MB)

まとめ

  • サーバーレスで高速な地理空間分析
  • SQLによる柔軟なクエリ
  • フロントエンドエンジニアでも扱いやすい
  • 特にプロトタイピング用途に向くと思う