OpenStreetMapとA-FrameでWebVR会津観光をしよう


Qiita投稿3つ目の初心者です。よろしくお願いいたします。

前書き

弊社の地図ガチ勢上司より、OpenStreetMapの誕生日が本日8月9日なので、OpenStreetMapかつ地元観光資源で記事を書くのだフハハハハ…という話があり、今回の記事を書くに至りました。

OpenStreetMap の誕生日ってなんだろうと思い調べてみると、 OpenStreetMap.org のドメイン名取得の記念日とのことです。
参考:
Birthday - OpenStreetMap Wiki
Celebrate the 16th OSM anniversary!
OpenStreetMapさん、お誕生日おめでとうございます!

記事の内容としては、OpenStreetMap(OSM)の3DマップとA-Frameを使ってWebVRを作ってみたと同じようなことをしていきますが、osmファイルのファイルフォーマット変換に別のツールを使った方法があるので、それぞれの変換結果を比較して、どちらを使うか選択しようと思います。また、今回2つのマップモデルを一つの空間に設置するので、移動を簡単にするため瞬間移動ギミックを追加したWebVRを構築していこうと思います。

前提環境

  • OS:Windows 10
  • Webブラウザ:FireFox 55以降 記事中では78.0.2を使用
  • Blender:2.8X 記事中では2.83.2を使用(今回は比較のための参照使用のみです)

1. OpenStreetMapから3Dマップデータを取得

取得方法の詳細はOpenStreetMap(OSM)の3DマップとA-Frameを使ってWebVRを作ってみたをご参照ください。

今回は、会津若松鶴ヶ城、飯盛山さざえ堂を取り出していきます。

1.1. 会津若松鶴ヶ城

1.2. 飯盛山さざえ堂

2. OSMファイルを変換

A-FrameではOSMファイルは直接読み込めないため、glTFファイルもしくはOBJファイルへ変換する必要があります。
※A-Frameの公式ドキュメントによると、glTFが推奨されています。理由としては、表現力が高いこと、アニメーションがつけれること…など。
参考:3D Models - A-Frame

2.1. OBJファイルとMTLファイルへ変換

OpenStreetMap公式から紹介されているOSM2Worldを使用して、OSMファイルからOBJファイルとMTLファイルへ変換が行えます。
OBJファイルがモデルの形状(ジオメトリ)情報を持ち、MTLファイルがモデル表面の色など質感(マテリアル)情報を持ちます。

2.1.1. OSM2Worldの使い方

OSM2World Downloadの0.2.0をダウンロードします。

ダウンロードしたZIPを解凍し、 OSM2World.jar をダブルクリックで起動します。

OSM2World はJRE(Java Runtime Environment) 1.6 以降を必要とします。

とのことなので、起動できなかった場合はJREの導入をしましょう。
起動できた画面は下記です。

起動出来たら、上部メニューのFile>Open OSM fileからOSMファイルを開きます。読み込み完了まで少し時間がかかります。

鶴ヶ城を読み込んだ画面は下記です。

上部メニューのFile>Export OBJ fileから保存先を指定すると、OBJファイルと同じフォルダに対応するMTLファイルが出力されます。出力完了まで少し時間がかかります。

さざえ堂も同様に出力しました。

2.2. Blenderアドオンで読み込んだものと比較

下記が、Blenderアドオン blender-osm で読み込んだもの
blender-osm の使用方法はOpenStreetMap(OSM)の3DマップとA-Frameを使ってWebVRを作ってみたをご参照ください。

下記が、 OSM2World で変換したOBJ、MTLファイルを読み込んだもの

木が生えましたね。用途によって使い分けができそうです。
今回のようにVR観賞用としては、にぎやかな方がいいと思うので、 OSM2World の結果を採用です。この前はglTFファイルの読み込みを行っていたので、今回はOBJファイルをそのまま使ってみます。

3. WebVRの構築

鶴ヶ城とさざえ堂の2つのマップモデルを読み込み、そのまま移動するのは大変なので、それぞれを瞬時に行き来できる瞬間移動ギミックを追加します。

3.1. HTMLコード

index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Hello, VR Aizu!</title>
        <!-- aframe-extras requires 0.9.X aframe -->
        <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
        <!-- https://github.com/donmccurdy/aframe-extras -->
        <script src="https://cdn.jsdelivr.net/gh/donmccurdy/[email protected]/dist/aframe-extras.min.js"></script>
        <script src="script/teleport.js"></script>
    </head>
    <body>
        <a-scene background="color: #EEEEEE">
            <a-assets>
                <a-asset-item id="shiro-obj" src="models/shiro.obj"></a-asset-item>
                <a-asset-item id="shiro-mtl" src="models/shiro.obj.mtl"></a-asset-item>
                <a-asset-item id="sazaedo-obj" src="models/sazaedo.obj"></a-asset-item>
                <a-asset-item id="sazaedo-mtl" src="models/sazaedo.obj.mtl"></a-asset-item>
            </a-assets>

            <!-- light -->
            <a-light color="#FFEEEE" position="-1 1 0"></a-light>
            <a-light color="#333" type="ambient"></a-light>

            <!-- objects -->
            <a-entity id="shiro" 
                    position="0 0 0" 
                    rotation="0 0 0" 
                    obj-model="obj: #shiro-obj; mtl: #shiro-mtl" 
                    scale="1 1 1" 
                    shadow="">
            </a-entity>
            <a-entity id="sazaedo" 
                    position="0 60 -640" 
                    rotation="0 0 0" 
                    obj-model="obj: #sazaedo-obj; mtl: #sazaedo-mtl" 
                    scale="1 1 1" 
                    shadow="">
            </a-entity>

            <!-- movement control -->
            <a-entity id="rig"
                movement-controls
                position="0 0 0">
                <a-entity camera
                            position="0 1.6 0"
                            look-controls="pointerLockEnabled: true">
                </a-entity>
                <a-entity id="ctlL" laser-controls="hand: left"></a-entity>
                <a-entity id="ctlR" laser-controls="hand: right"></a-entity>
            </a-entity>

        </a-scene>
    </body>
</html>

index.html のフォルダ階層に、 models フォルダを作成し、上記で出力したOBJファイル、MTLファイルを入れています。同様にして script フォルダを作成し、 teleport.js ファイルを入れています。

3.2. JavaScriptコード

teleport.js
window.addEventListener('load', function (event){
    const ctlL = document.getElementById("ctlL");
    const ctlR = document.getElementById("ctlR");
    const rig = document.getElementById("rig");

    //Trigger Released
    ctlL.addEventListener('triggerup', function (event) {
        rig.setAttribute('position', {x: 0, y: 0, z: 0});
    });
    ctlR.addEventListener('triggerup', function (event) {
        rig.setAttribute('position', {x: 0, y: 60, z: -640});
    });
});

コントローラーのトリガーボタンが離されたタイミングで発生するイベントに、プレイヤーの位置を変更するギミックを設定しています。

  • 左手のコントローラーのトリガーアップ:{x: 0, y: 0, z: 0}地点(鶴ヶ城)へ移動
  • 右手のコントローラーのトリガーアップ:{x: 0, y: 60, z: -640}地点(さざえ堂)へ移動

3.3. 配置関係


実際の配置関係とは異なりますが、さざえ堂のマップからお城を眺められたら面白いかなぁ…という思いでさざえ堂マップの配置を少し高い位置へ配置してみました。

4. VRで実行した様子

レッツバーチャル会津観光!

ライセンス

記事中で使用した、オープンソースライブラリ、3Dモデルのライセンスです。

コード

地図データ

参考