streamlitで現在の座標を取得しようと思ったのですが、軽く調べた感じでは少し複雑なコードしか出てきませんでした。
ただjavascriptを実行してstreamlitから取り出したいだけなのにbokehいらないと思ったので模索してみたらstreamlitコンポーネントを作る方法にいきつきました。
import streamlit as st
from bokeh.models.widgets import Button
from bokeh.models import CustomJS
from streamlit_bokeh_events import streamlit_bokeh_events
loc_button = Button(label="Get Location")
loc_button.js_on_event("button_click", CustomJS(code="""
navigator.geolocation.getCurrentPosition(
(loc) => {
document.dispatchEvent(new CustomEvent("GET_LOCATION", {detail: {lat: loc.coords.latitude, lon: loc.coords.longitude}}))
}
)
"""))
result = streamlit_bokeh_events(
loc_button,
events="GET_LOCATION",
key="get_location",
refresh_on_update=False,
override_height=75,
debounce_time=0)
streamlit=>javascriptのパターンはあってもjavascript=>streamlitのパターンがなかったので大変でした。
できあがったリポジトリがこちらです。
pypiにも登録しました。
pip install streamlit streamlit_current_location
例
src.tsに受け渡したいデータをStreamlit.setComponentValue()で囲んでStreamlit.setComponentReady()するスクリプトを書き、npm run build
でstreamlitが読み込められるようなコンポーネントを作ります。
そこからは通常のstreamlitコンポーネントと同じように扱います。
例えばWeb Speech APIを使って色の音声認識をするにはsrc.tsに以下のスクリプトを書きます。
const grammar =
"#JSGF V1.0; grammar colors; public <color> = aqua | azure | beige | bisque | black | blue | brown | chocolate | coral | crimson | cyan | fuchsia | ghostwhite | gold | goldenrod | gray | green | indigo | ivory | khaki | lavender | lime | linen | magenta | maroon | moccasin | navy | olive | orange | orchid | peru | pink | plum | purple | red | salmon | sienna | silver | snow | tan | teal | thistle | tomato | turquoise | violet | white | yellow ;";
const recognition = new SpeechRecognition();
const speechRecognitionList = new SpeechGrammarList();
speechRecognitionList.addFromString(grammar, 1);
recognition.grammars = speechRecognitionList;
recognition.continuous = false;
recognition.lang = "en-US";
recognition.interimResults = false;
recognition.maxAlternatives = 1;
window.onload = () => {
recognition.start();
recognition.onresult = (event) => {
const color = event.results[0][0].transcript;
Streamlit.setComponentValue(coordsToObject(color));
};
Streamlit.setComponentReady();
};
解説
python部分
streamlitに直接読み出される部分です。
特になにもしないので最小限のライブラリ読み出して終わりです。
dist以下に本処理が書かれたファイルを置きます。
import os
import streamlit.components.v1 as components
parent_dir = os.path.dirname(os.path.abspath(__file__))
build_dir = os.path.join(parent_dir, "dist")
def current_position():
component = components.declare_component("current_position", path=build_dir)
value = component(name="current_position")
return value
index.html
こちらも特に何もしません。
スクリプトの名前はindex.jsにしておきました。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Streamlit Component</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Streamlit Component" />
<script src="index.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
</body>
</html>
dist/index.js(src.ts)
本処理の部分です。
streamlitはjsonにできる純粋なオブジェクトじゃないとだめなのでオブジェクトにする関数を作っています。
domが読み込まれたらnavigator.geolocation.getCurrentPositionで現在の座標をとってきてStreamlit.setComponentValueでstreamlitに伝えるだけの簡単なスクリプトです。
テンプレートの方ではわざわざビルド用の設定が入っていましたが今はesbuildがありますので
esbuild src.ts --bundle --minify --sourcemap --outfile=dist/index.js
で十分です。
--watch
オプションをつけてあげればデバッグも簡単です。
現代フロントエンドバンザイ!
import { Streamlit } from "streamlit-component-lib";
const coordsToObject = ({
accuracy,
altitude,
altitudeAccuracy,
heading,
latitude,
longitude,
speed,
}: GeolocationCoordinates) => ({
accuracy,
altitude,
altitudeAccuracy,
heading,
latitude,
longitude,
speed,
});
window.onload = () => {
navigator.geolocation.getCurrentPosition(({ coords }) => {
Streamlit.setComponentValue(coordsToObject(coords));
}, console.log);
Streamlit.setComponentReady();
};