LoginSignup
17
11

FastAPIとWebSocketで位置情報共有アプリを作ってみた

Posted at

リアルタイム位置情報共有アプリWhere am I in Sapporo

複数のユーザーがブラウザから自分の位置を入力し、他のユーザーとの間にリアルタイムで共有することができるアプリを作ってみました。選択できる位置は札幌市内の主要観光スポットを事例としています。
ユーザーごとにIDが付与され、ネコの写真もアイコンとしてランダムで付与されます。

スクリーンショット 2023-07-19 15.52.32.png

ソースコードは以下リポジトリで公開しています。
https://github.com/xinmiao1995/share_now_location

利用するツール

  • WebAPI配信
    • FastAPI
    • WebSocket
    • ngrok
  • マップビュワー
    • MapLibre GL JS
      • OpenStreetMapベースマップの表示
      • マーカーの表示
      • 地図ズーム
    • Turf.js
      • 地図ズーム領域の取得
  • アイコン画像フリー素材

リアルタイム通信を実現するために

FastAPIについて

FastAPIはPython(バージョン3.6以降対応)製の軽量Webフレームワークです。非同期処理が対応可能との特徴もありますので、今回は主にこちらを利用しています。また、PythonのパッケージではWebSocketも内容されているので、fastapiをインストールするだけで、WebSocketの機能も使えます。

  • インストール
pip install fastapi
  • Pythonコードの書き方
from fastapi import FastAPI, WebSocket

WebSocketについて

WebSocketについては常時接続が可能なプロトコルです。接続トラフィックの低減と非同期処理によって、安定した双方向通信が実現できます。それによって、今回の目標である複数クライアント間のリアルタイム通信が可能になリます。
スクリーンショット 2023-07-19 16.18.23.png
出典:https://atmarkit.itmedia.co.jp/ait/articles/1111/11/news135.html

システムフロー

今回のアプリケーションでは、このように複数のクライエントとサーバー間の通信ができることを目指しています。
スクリーンショット 2023-07-19 16.03.14.png

ソースコード

  • サーバーサイド
    サーバーサイドではクライエントの情報を保存したり、加工したり、クライエントに情報を送付したりしています。本来はデータベースを利用するべきですが、今回は簡易的にPythonのリストでユーザー情報を保管します。サーバーを再起動すればリセットされますので要注意です。

  • WebSocket接続の成立と切断

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
    
    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
    
  • クライエントにメッセージの送付

    async def broadcast(self, client_id: int, coordinate: str):
        self.client_ids.append(client_id)
        self.markers[client_id] = {
            "coordinate": coordinate,
            "marker_size": self.client_ids.index(client_id) + 40,
        }
    
        for connection in self.active_connections:
            await connection.send_json({
                "message": f"user {client_id}'s location: {coordinate}",
                "markers": self.markers
            })
    
    @app.websocket("/ws/{client_id}")
    async def websocket_endpoint(websocket: WebSocket, client_id: int):
        await manager.connect(websocket)
        try:
            while True:
                text = await websocket.receive_text()
                await manager.broadcast(client_id, text)
    
        except WebSocketDisconnect:
            manager.disconnect(websocket)
            del manager.markers[client_id]
    
    

こちらではjsonを送っていますが、WebSocketではsend,receiveできる型はそれぞれtext,bytes,jsonがあります。async、awaitは非同期処理を意味しています。

  • フロントエンド
    クライエントでは、ユーザーIDの生成や、サーバーから受け取った情報をマップ上の常時をしています。

    • 定義したURLにアクセスしたらWebSocketインスタンスが宣言される
    let ws = new WebSocket(`wss://${hostName}/ws/${client_id}`);
    
    • ユーザーが選択した位置をサーバーに送る
    function sendMessage(event) {
        ws.send(loc_coordinat[document.getElementById("myDropdown").value])
        event.preventDefault()
    }
    
    • サーバーからメッセージが届いたらマップ上にマーカーを表示させる
    ws.onmessage = function (event) {
        Object.keys(JSON.parse(event.data).markers).forEach((key, index) => {
            let coordinate = JSON.parse(event.data).markers[key].coordinate;
            let marker_size = JSON.parse(event.data).markers[key].marker_size;
            ...
            // add marker to map
            new maplibregl.Marker(el)
                .setLngLat(coordinate.split(','))
                .addTo(map);
            points.push(coordinate.split(','));
    
            // Zoom to points
            const pointsCollection = turf.featureCollection(points.map(point => turf.point(point)));
            const bbox = turf.bbox(pointsCollection);
            map.fitBounds([
                [bbox[0], bbox[1]],
                [bbox[2], bbox[3]]
            ]);
        });
    };
    
    
  • 起動するには

# ローカルホストを起動する(ローカルマシンから接続可能)
uvicorn main:app --reload
# ngrokで公開(任意のIPから接続可能)
ngrok http 8000

参考資料

動かして学ぶ!Python FastAPI開発入門
FastAPI公式ドキュメント
双方向通信を実現! WebSocketを使いこなそう
MapLibre公式ドキュメント

17
11
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
17
11