「空間ID」という地球上の場所を一意のキーで特定するための概念があるんですが、緯度経度や標高からこのIDを算出するためのツールをRustで作成しました!
このRustで作ったツールをPythonで動かせるようになったので、次はTypeScriptから呼び出していこうかと思います。
呼び出し方は色々あるのかもしれませんが、RustにはWasmへビルドするコマンドが標準で備わっているため、Wasmに変換してTypeScriptで読んでみましょう。
最終的なコードはここに置いているので、いつでも参考にしてください!
- ロジック本体:
spatial-id-core
- wasm:
spatial-id-wasm
- 動作確認用:
examples/typescript
準備
Rustのインストールは簡単なので、導入されている前提で進めます。
Install Rust
RustのパッケージをWasmに変換するためにはwasm-pack
が必要なのでインストールしましょう。
cargo install wasm-pack
次に、プロジェクトを作成します。
cargo new --lib spatial-id-wasm
Cargo.toml
は以下のようにしましょう!
spatial-id-core = { path = "../spatial-id-core" }
という記述がありますが、今回は上記リポジトリのspatial-id-core
が親ディレクトリに存在して、それをWasmで利用する、という想定で話を進めていきます。
[package]
name = "spatial-id-wasm"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
spatial-id-core = { path = "../spatial-id-core" }
RustでWebAssembly用のコードを書く
プロジェクトの構造はこんな感じになっている想定です
spatial-id-rs/
├─ spatial-id-core/ # Rustのメインロジック
└─ spatial-id-wasm/ # 今回作成するWasm用プロジェクト
spatial-id-wasm/src/lib.rs
に以下のようなコードを書きます。
use spatial_id_core::cell::zfxy::ZFXY;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct WasmLatLon {
pub lat: f64,
pub lon: f64,
}
#[wasm_bindgen]
impl WasmLatLon {
#[wasm_bindgen(constructor)]
pub fn new(lat: f64, lon: f64) -> WasmLatLon {
WasmLatLon { lat, lon }
}
}
#[wasm_bindgen]
pub fn generate_spatial_id(lat: f64, lon: f64, alt: f64, zoom: u8) -> String {
let cell = ZFXY::from_lat_lon_alt(lat, lon, alt, zoom);
cell.to_spatial_id_str()
}
コアモジュールからspatial_id_core::cell::zfxy::ZFXY
(空間ID生成)をインポートし、それを利用して空間IDを生成するコードです。
#[wasm_bindgen]
を付けることで、この関数がJavaScriptやTypeScriptから呼び出せるようになります。
WebAssemblyにビルドする
spatial-id-wasm
フォルダで以下のコマンドを入力し、RustのコードをWasmに変換します。
wasm-pack build --target bundler --release
これで pkg
フォルダにWasmファイルとTypeScript用のラッパーファイルが生成されます。
TypeScriptで使う
新しくTypeScriptのプロジェクトを作成します。
pnpm create vite@latest examples/typescript --template vanilla-ts
cd examples/typescript
pnpm add -D vite-plugin-wasm "file:../../spatial-id-wasm/pkg"
pnpm i
examples/typescript/vite.config.ts
を作成しましょう。
import { defineConfig } from "vite";
import wasm from "vite-plugin-wasm";
export default defineConfig({
plugins: [wasm()],
});
プロジェクトの examples/typescript/src/main.ts
を以下の内容に変更します。
console.log
で空間IDの生成結果を表示するだけでもいいんですが、面白みがないので動的に生成できるようにしてみましょう。
examples/typescript/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Spatial ID Demo</title>
</head>
<body>
<div id="app">
<h1>Spatial ID Generator</h1>
<form id="spatial-form">
<label>
緯度 (lat): <input type="number" step="any" name="lat" required value="0">
</label><br>
<label>
経度 (lon): <input type="number" step="any" name="lon" required value="0">
</label><br>
<label>
標高 (alt): <input type="number" step="any" name="alt" required value="10">
</label><br>
<label>
ズームレベル (zoom): <input type="number" name="zoom" min="0" max="26" required value="25">
</label><br>
<button type="submit">空間ID生成</button>
</form>
<div id="result"></div>
<div id="error" style="color:red;"></div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
examples/typescript/src/main.ts
import { generate_spatial_id } from "spatial-id-wasm";
async function main() {
const form = document.getElementById("spatial-form") as HTMLFormElement;
const resultDiv = document.getElementById("result")!;
const errorDiv = document.getElementById("error")!;
form.onsubmit = (e) => {
e.preventDefault();
errorDiv.textContent = "";
resultDiv.textContent = "";
const formData = new FormData(form);
const lat = Number(formData.get("lat"));
const lon = Number(formData.get("lon"));
const alt = Number(formData.get("alt"));
const zoom = Number(formData.get("zoom"));
if (
isNaN(lat) || isNaN(lon) || isNaN(alt) || isNaN(zoom) ||
zoom < 0 || zoom > 26
) {
errorDiv.textContent = "入力値が不正です。";
return;
}
try {
const spatialId = generate_spatial_id(lat, lon, alt, zoom);
resultDiv.textContent = `Spatial ID: ${spatialId}`;
} catch (err) {
errorDiv.textContent = `エラー: ${(err as Error).message}`;
}
};
}
main().catch((err) => {
document.querySelector<HTMLDivElement>("#app")!.innerHTML =
`<div style="color:red;">初期化エラー: ${(err as Error).message}</div>`;
});
動作確認してみましょう!
pnpm run dev
ブラウザで http://localhost:5173
にアクセスして、「Spatial ID」が正しく表示されれば成功です!
まとめ
RustのコードをWebAssembly経由でTypeScriptから呼び出す方法を紹介しました。これにより、ブラウザ上でもRustの高速な処理が使えるようになります。
ぜひ色々な機能を追加して試してみてください!