3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustで書いた小さなツール(空間ID生成)をWasmにしてTypeScriptから呼び出してみる!

Posted at

image.png
以前こんな記事を書きました!

「空間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」が正しく表示されれば成功です!

image.png

まとめ

RustのコードをWebAssembly経由でTypeScriptから呼び出す方法を紹介しました。これにより、ブラウザ上でもRustの高速な処理が使えるようになります。

ぜひ色々な機能を追加して試してみてください!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?