つい先日、こんな記事を書きました!
これは「空間ID」と呼ばれる位置情報を特定するためのキーを生成するRustのユーティリティを紹介したものなんですが、こういった全然外部ライブラリに依存してないとても小さなRustのプログラムであれば、他プログラム言語のラッパーを作成するのはすごい簡単です。
GIS(地理空間情報)の領域ではPythonのライブラリがとても充実しているため、こういったツールはPythonから利用したくなるケースが多いです。
なのでこの記事ではRustのライブラリをPythonから利用できるようにする手法をサクッと書いてみます。
まずはプログラムの準備
基本的には上記記事のこの辺りを見てRustでプログラムを構築してみてください。
経緯度・標高・ズームレベルから空間IDを算出する
一応、リポジトリも用意しているのでそちらを利用しても良いです。
Pythonで利用するためには、Rustで書いたプログラムのうち、どの関数(など)をPythonで利用するのか決めてあげる必要があります。
今回はシンプルに「経緯度・標高・ズームレベル」を渡すと、空間IDが返ってくるというシンプルな実装を作ってみます。
spatial-id-pyという名称でPythonラッパーの実装を保存しているので、細かいことはこちらをみてください。
https://github.com/nokonoko1203/spatial-id-rs/tree/main/spatial-id-py
Pythonのラッパープロジェクトを作成
まずはプロジェクトを作成します。
uv init --build-backend maturin spatial-id-py
そうするとRustプロジェクトのようなものが出来上がりますが、pyproject.tomlのようにPythonプロジェクトで利用されるファイルも格納されているのがわかるので、後で説明します。
まずはCargo.tomlを以下のように書きかえてください。
今回はspatial-id-py
という名称でインポートできるように名称などを設定します。
spatial-id-core
という名称でRustのバックエンドを書いているので、それも依存関係として指定しておきます。
[package]
name = "spatial-id-py"
version = "0.1.0"
edition = "2021"
description = "Python bindings for spatial-id-rs using PyO3"
license = "MIT"
[lib]
name = "spatial_id_py"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.21", features = ["extension-module"] }
spatial-id-core = { path = "../spatial-id-core" }
[package.metadata.maturin]
name = "spatial_id_py"
ここで重要なのは、crate-typeをcdylibとして指定し、PyO3とRustのコアロジックを依存関係に追加することです。
RustコードをPython用にラップ
spatial-id-py/src/lib.rs
に以下のように書き込みます。
たったこれだけでPythonからRustのロジックを利用することができます。
use pyo3::prelude::*;
use spatial_id_core::cell::zfxy::ZFXY;
#[pyfunction]
#[pyo3(text_signature = "(lat, lon, alt, zoom)")]
fn generate_spatial_id(lat: f64, lon: f64, alt: f64, zoom: u8) -> PyResult<String> {
let cell = ZFXY::from_lat_lon_alt(lat, lon, alt, zoom);
Ok(cell.to_spatial_id_str())
}
#[pymodule]
fn spatial_id_py(m: &Bound<PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(generate_spatial_id, m)?)?;
Ok(())
}
RustのコアロジックではZFXY
という空間IDを生成するための構造体が作成されています。
それを利用して、空間ID生成を行う関数を書いています。
pyo3を利用することで、Rustの関数を比較的簡単にPythonから利用することができます。
Pythonパッケージのビルドと利用
まずはmaturinでPythonパッケージにビルドします。
uv run maturin develop
Python側で利用する際には、プロジェクトのpyproject.tomlに次のように設定します。
[project]
name = "spatial-id-py-example"
version = "0.1.0"
description = "Example for using spatial-id-py as an external package."
requires-python = ">=3.8"
dependencies = [
"spatial-id-py",
]
[tool.maturin]
[tool.uv.sources]
spatial-id-py = { path = "../../spatial-id-py" }
パッケージは公開されていないので、パスを指定してspatial-id-pyを読み込んであげます。
パスを指定することで、ローカルのRustパッケージを簡単にPythonプロジェクトに取り込めます。
パッケージの内容が更新された後は、uv sync
コマンドなどでインストールします。
その後、任意の.pyファイルで以下のようなプログラムを書き、実行できます。
from spatial_id_py import generate_spatial_id
if __name__ == "__main__":
lat, lon, alt, zoom = 0.0, 0.0, 10.0, 25
spatial_id = generate_spatial_id(lat, lon, alt, zoom)
print(f"Spatial ID: {spatial_id}")
実行結果は次のようになります。
$ uv run python example_generate_id.p$ y
Built spatial-id-py @ file:///Users/...
Uninstalled 1 package in 0.86ms
Installed 1 package in 1ms
Spatial ID: /25/10/16777216/16777216
このような形で簡単に空間IDを生成することができました!
おわりに
現実的には、もっと複雑なライブラリを取り扱うことになると思うので、依存関係の整理がかなり大変だともいますが、既存のRustのプログラムにちょっとした設定を加えてあげるだけでRustの高速な空間ID生成ロジックを簡単にPythonから利用することができます!
今回書いたコードは全てここに保存してあるので、参考にしてみてください!
皆さんもぜひ活用してみてください!