DuckDB-Wasmで簡単な地理空間情報分析アプリを作る


:::note info
これは [MIERUNE AdventCalendar 2024](https://qiita.com/advent-calendar/2024/mierune)7日目の記事です。
昨日は @KEI_YAMA  さんによる [QGIS for Facility Management Applications - QGISを用いた施設管理アプリケーションの作成](https://qiita.com/KEI_YAMA/items/356d4efb2eb83439c85e) でした。
:::

## はじめに
実は前からgeojsonの重心座標を一発で取れるツールが欲しいなと思っており、今まではQGISを使っていたのですが、自身の技術向上も兼ね、DuckDB-Wasmにて構築を行いました

結論から言いますと、完璧なものが出来たとは言い難いのですが、かなり勉強になりましたので紹介します

## DuckDB-Wasmとは
Webブラウザ上で動作するDuckDBのWebAssembly版で、主なポイントとしては
1. Webブラウザ上でSQLクエリを実行できる
1. サーバー側の処理は不要で、クライアントサイドのみでデータベース操作が可能
1. クエリ処理をブラウザのメモリ内で完結し、ローカルデータ分析に最適
1. 標準SQLクエリをサポートし、CSVやParquet形式のデータを扱える
1. ネットワーク接続がなくても動作するため、オフラインでのデータ分析が可能

というようなものになります

DuckDBにはSpatial Extensionという地理空間データを処理するための拡張機能があり、これがほぼPostGISと同じことが出来る
というのが今回学ぼうと思ったきっかけになります

## やったこと
簡単なWEBアプリケーションを作りました
主な利用ライブラリーは以下です
- [SvelteKit](https://github.com/sveltejs/kit)(TailwindCSS付きでバージョンは2、Svelteのバージョンは5になります）
- [svelte-maplibre-gl](https://github.com/MIERUNE/svelte-maplibre-gl)（MIERUNE内で@ciscornさんが中心となって構築しているもの、~~そのうち~~[記事](https://qiita.com/ciscorn/items/6f97f681f31cafe513bd)が~~出ると思います~~出ました）
- [DuckDB-Wasm](https://github.com/duckdb/duckdb-wasm)

主な構成やDuckDB-Wasmへの接続などは以下のサンプルのものをそのまま持ってきたり、改変したりして利用しました
サンプルは2年前のものですので、結構前からあったのだなと気付かされます

https://github.com/duckdb-wasm-examples/sveltekit-typescript

エラー内容を見たかったので、importを `duckdb-eh.wasm` に変更しています

```javascript:lib/duckdb.ts
import * as duckdb from '@duckdb/duckdb-wasm';
import duckdb_wasm from '/node_modules/@duckdb/duckdb-wasm/dist/duckdb-eh.wasm?url';
import duckdb_worker from '/node_modules/@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js?worker';
```

SvelteKitで構築する上でのポイントはここでしょう
利用するページではSSRしないようにします
```javascript:routes/page.ts
export const prerender = true;
export const ssr = false;
```
DuckDBのSQLは以下のリファレンスを読みながら実装しました

https://duckdb.org/docs/extensions/spatial/functions

重心を取得するのは `ST_Centroid` なのですが、組み立てたMULTIPOINTを送り込むようにしたのと、返り値がPOINT2Dのオブジェクトだったので、Javascriptで文字列として扱うためにGeoJSON形式へ変換していますので、ややこしいSQL文になっています
このあたりは方法を間違っているかもしれないので知見のある方からご指摘があるかもしれません
```sql
SELECT ST_AsGeoJSON(ST_Centroid(ST_GeomFromText('${pointsMultiString}'))) as geom;
```
処理の順番としては
1. GeoJSONからDuckDBの一時テーブルを作る
1. 読み出してMULTIPOINTの文字列を作る
1. ST_Centroidで重心を取得する

という感じです
サラッと書いていますが、結構複雑になっちゃったなあと思っています

メインの実装は以下を御覧ください（雑ですみません）

https://github.com/northprint/get-centroid/blob/main/src/routes/%2Bpage.svelte

## 完成したもの
[マルチポイント重心計算くん](https://get-centroid.vercel.app/)
※初回表示で地図がちゃんと表示されないバグが発生しております、その場合は何度かリロードしてみてください

![Kapture 2024-12-07 at 08.48.11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/0b8cbbbd-093d-c4aa-af7b-aca714c30cea.gif)

ソースコード一式はこちら

https://github.com/northprint/get-centroid

改修の予定はあるのですが、マルチポイントのGeoJSONファイルのURLを入れるというところのハードルが高いと思います
[G空間情報センター](https://front.geospatial.jp/)に置いてある国土数値情報あたりが良いかもしれません
ちなみに上記デモは北海道の学校のポイントデータで、　`https://www.geospatial.jp/ckan/dataset/8a1f5eb3-dca4-4ba5-8d84-cf6a31a56bff/resource/1a6f4420-2f9a-4f7c-9043-52b7cca64df9/download/p29-13_01.geojson`です

また、以下の記事も参考になるかと思います

https://blog.geolonia.com/2019/06/04/opendata-visualization.html


## 感想
なんといっても、WEBでかつクライアント側の実装だけで地理空間分析が出来るのというのが最高です
諸事情でバックエンドが組めない、若しくは組む余裕がない、みたいな場面で生きそうです
ハマりポイントとしては、やりとりしている今データがどんな状況か（テキストなのか、DBのオブジェクトなのか）みたいなところを強く意識しないといけなかったところですね、気をつけましょう

ちなみに、SSRの構成でデプロイしているのに一切SSR使っていないところが微妙だなと思っています、wasmなのでそうなるよなあとは思いつつ
そして、実際計測したわけでは無いのですが、やっぱり速いなと感じました
[Turf.js](https://turfjs.org/)を使ったほうがもっと便利なものを作れそうだなと思いましたが、大量のポイントの処理はちょっと怖いかも

## 今後
現状、データをURL指定でしか読み込めないので、手元のGeoJSONを読み込めるように大改修の予定でおります

ここではサラッとしか触れなかったDuckDBの事や、svelte-maplibre-glの事は、他のアドベントカレンダーの記事として出す予定です
特に[svelte-maplibre-gl](https://github.com/MIERUNE/svelte-maplibre-gl)が便利すぎてやばかったですね、おかげでDuckDBとのやりとりの構築に集中できました、神ライブラリーです

# 構築の上で参考にさせていただいた記事達
これらの記事が無かったら、確実に間に合わなかったでしょう
ありがとうございます

https://tech.newmo.me/entry/duckdb-wasm-typescript

https://zenn.dev/yuiseki/articles/d89aaba0eb80c6

https://zenn.dev/otwn/articles/f11d37685caf93

https://zenn.dev/ktz_alias/scraps/52885776c91835

:::note info
明日は@nkmr_RLさんによる「pyqgisでQGISの標準機能を呼び出す」です！お楽しみにー
:::