Mini Tokyo 3D バージョン 3.4.0 リリース

Mini Tokyo 3D バージョン 3.4.0 がリリースされました (GitHub)。3.3.0 からの追加・修正機能を見ていきましょう。

実際の太陽の位置に合わせた光源の設定

建築物の光の反射はこれまで固定ライトを使っていましたが、太陽の位置に合わせた光の反射に変更しました。時間と共に、建築物の見え方が変化してリアリティが少し向上しています。太陽の天球上の位置は正確に計算しているので、季節によっても影のつき方が変わります!

遮蔽による光の減衰を考慮した建物の影の描写

建築物のシェーディング(陰影の付与)にアンビエントオクルージョン(遮蔽による光の減衰を考慮して影を描写する手法)を適用しました。左が適用前、右が適用後です。効果は大きく、かなり風景のリアリティが増しています。

路線のライン形状の改善

地下鉄路線の形状データを更新しました。線形がだいぶ改善され、無理のない形状になっています。これは OpenStreetMap のデータ品質が3年前から大きく向上した成果です。

地下駅出口情報の更新

地下駅出口のバリアフリー施設情報、利用可能時間の情報の追加を着々と進めています。今回は、東京メトロ有楽町線半蔵門線南北線副都心線と、都営浅草線三田線新宿線大江戸線の各路線の駅に対応しました。また、西新宿駅虎ノ門ヒルズ駅、神谷町駅などの開発が進んでいるエリアに新設された出口も追加しています。

ダイヤ改正対応

りんかい線の 2023/3/18 ダイヤ改正東京メトロ銀座線の 2023/4/29 ダイヤ改正に対応。また、流鉄流山線の 2023/7/1 ダイヤ改正に対応。さらに、都営浅草線京急、京成、北総鉄道芝山鉄道の 2023/11/25 ダイヤ改正に対応しています。

ライブカメラの拡充

ライブカメラプラグインで表示される、沿線のライブカメラがさらに追加されました。

WebGL 2.0 対応

デフォルトで WebGL 2.0 を使った描画を行うようになりました。基本的には描画品質や性能は変わらないと思いますが、開発者がレイヤーの追加を行う場合には WebGL 2.0 ベースの gl コンテキストやレンダラー、GLSL ES 3.0 などを利用することができるようになります。

Bluesky に画像をポストするボットのコード

一般公開が始まった Bluesky、ユーザー数が一気に増えたので自動でポストを行うボットのニーズも増えてきたかと思います。いずれ誰かがもっと体系的な情報を整備すると思いますが、とりあえずボットを動かすことができたので、2024年2月時点でのポストを行うコードを公開しておこうと思います。ほんとに簡単です。

Japan EQ LocatorWorld EQ Locator では地震が発生するたびに、テキスト、サイトの URL、地震スクリーンショットの画像をポストしています。以下では、これらのデータはすでに準備ができているものとして、Bluesky へのポストの部分のみに注目します。なお、使用言語は Python です。

まず AT Protocol SDK atproto と画像ライブラリ Pillow をインストールしておきます。

pip install atproto Pillow

そして、コードの先頭でこれらのパッケージのクラスやモジュールをインポートします。なお、BytesIO は変換した画像のバッファとして使用します。

from atproto import Client, client_utils
from PIL import Image
from io import BytesIO

次に、AT Protocol クライアントを作成します。Client のコンストラクタの引数には base_url として 'https://bsky.social' を指定します。そして、自分の Bluesky ハンドル名とパスワードを使ってログインします。

client = Client(base_url='https://bsky.social')
client.login('<handle>.bsky.social', '<password>')

で、こちらがポストのテキストを用意する部分。text には投稿内容、url にはリンクが格納されている前提です。ポイントは、client_utils.TextBuilder を使ってテキストを組み立てている点です。X と異なり、Bluesky ではテキスト中の URL は自動ではリンクにならないため、client_utils.TextBuilder を使ってリンクや装飾などが付いたリッチテキストを組み立てる必要があります。

text = f'{year}年{month}月{day}日{hour}時{minute}分頃、' \
    f'{location}を震源とする地震がありました。' \
    f'震源の深さは{depth}、地震の規模は{magnitude}。'
url = 'https://nagix.github.io/japan-eq-locator/?' \
    + urllib.parse.urlencode(quake)

text_builder = client_utils.TextBuilder() \
    .text(text + '\n') \
    .link('nagix.github.io/japan-eq-loc...', url)

続いて画像を用意します。img_file に画像のパス名が入っている前提です。画像を未加工でポストするなら、ファイルをバイナリとして open して read() で返ってくる bytes オブジェクトを取得するだけて良いのですが、このケースでは読み込んだ PNG ファイルのサイズが Bluesky でポストできる画像のサイズの上限 1,000,000 バイトを超える恐れがあったため、Pillow を使って読み込み、RGB モードに変換(アルファ成分を除去)した上で JPEG としてバッファに書き出し、bytes オブジェクトを取得しています。

img_file = 'screenshot.png'

img = Image.open(img_file).convert('RGB')
with BytesIO() as f:
    img.save(f, format='JPEG')
    jpg = f.getvalue()

最後に、引数に組み立てたテキストデータ、画像データ、画像の代替文字列(ALT テキスト、ここではリンクを除いた本文を使用)、言語を指定して send_image() で送ります。

client.send_image(text_builder, jpg, text, langs=['ja'])

以上です!思ったより簡単ですね?もし詳しい SDK の解説を見たい方は、こちらのリファレンスが参考になると思います。

atproto.blue

Mini Tokyo 3D バージョン 3.3.0 リリース

Mini Tokyo 3D バージョン 3.3.0 がリリースされました (GitHub)。3.2.0 からの追加・修正機能を見ていきましょう。

地下駅出口情報の更新とバリアフリー設備の表示

地下駅出口のバリアフリー施設情報、利用可能時間の表示に対応しました。駅を選択した時に、階段、エスカレーター、エレベーター、車いすスロープの有無が確認できたり、現在時刻でその出口が閉まっているとオレンジ色の表示で区別されます。さらに、マップ上でもアイコンが表示されるため、どの出口がバリアフリーになっているかがよりわかりやすくなっています。今のところ、対応しているのは東京メトロ銀座線、丸の内線、日比谷線東西線、千代田線の各路線です。

ところでここ数年、東京メトロではエレベータの統一されたサインシステムを導入していることに気づきました。エレベータのアイコンに rbv といった文字と色で区別するようです。今回判明したものはその情報も付けてあります。

f:id:nagixx:20210130000806j:plain

PLATEAU プラグイン

プロジェクト PLATEAU が提供する東京の3D都市モデルを Mini Tokyo 3D と組み合わせて表示します。詳細な建築物の形状データとテクスチャーを利用して、非常にリアルな都市の景観を表示することができます。画面右側の下から3つ目「レイヤー設定」ボタンを押して「PLATEAU」レイヤーを選択してみてください。非常に重くてスマホ等では動かないので、GPU 搭載 PC での利用をお薦めします。

PLATEAU では東京都23区に加え、首都圏の他の都市モデルもありますが、負荷軽減のため現時点では千代田区中央区、港区、新宿区、品川区、渋谷区、豊島区のみに絞っています。建築物が表示されるまでに結構時間がかかるので気長にお待ちくださいね。

高リフレッシュレートのディスプレイに対応

Mini Tokyo 3D は 60Hz のリフレッシュレートを想定して実装されていましたが、最近は高リフレッシュレートのディスプレイやモバイルデバイスがよく見られるようになり、その場合、視点移動アニメーションや花火プラグインのアニメーションが速過ぎる問題がありました。リフレッシュレートに関わらずアニメーション速度が一定になるように修正を加え、高リフレッシュレートのディスプレイではより滑らかなアニメーション動作になりました。

フランス語サポート

コミュニティーのコントリビューションにより、フランス語に対応しました。

ダイヤ改正対応

東京モノレールの 2022/11/7 ダイヤ改正に対応。都営浅草線京急、京成、北総、芝山鉄道新京成の 2022/11/26 ダイヤ改正に対応。また、JR 東日本、東京メトロ都営地下鉄横浜市営地下鉄東武、西武、京王、小田急、東急、相鉄、横浜高速鉄道埼玉高速鉄道東葉高速鉄道つくばエクスプレス江ノ電関東鉄道秩父鉄道小湊鉄道多摩モノレール、千葉モノレールの 2022/3/12 ダイヤ改正に対応。さらに、金沢シーサイドラインの 2023/3/25 ダイヤ改正ユーカリが丘線の 2023/4/1 ダイヤ改正に対応しています。

旅客機のライブ発着の再開

2022年2月以降しばらくの間、羽田空港・成田空港に発着する旅客機は JAL 便と ANA 便しか表示されていませんでしたが、他の航空会社含め全便の表示を再開しました。

路線のラインが表示されない不具合の修正

カメラを地平線の見える角度まで傾けた時に、遠方の路線のラインが表示されない不具合を修正しました。

新しいレイヤーインターフェースの追加

カスタマイズに利用可能なインターフェースが追加されました。いずれも、開発者が独自の情報を地図上に重ねて表示したいときに、手軽にカスタムレイヤーを作るための方法を定義しています。

インターフェース 説明
GeoJsonLayerInterface GeoJSON データを表示するためのインターフェース
Tile3DLayerInterface 3D Tiles 仕様に準拠した形式の 3D タイルデータを表示するためのレイヤーインターフェース

詳細は API リファレンスをご覧ください。

Mapbox GL JS + deck.gl で高度な 3D データ表現

この記事は Mapbox Advent Calendar 2022 の12日目の記事です。

Mapbox の弱点として、3D のデータ表現が弱いということが言われます。確かに、3D 地図の上空に何かを配置したり、3D の点群を地図に重ね合わせて表示するための標準的な機能はありません。もちろん、カスタムスタイルレイヤーを使って、直接 WebGL もしくは Three.js のようなライブラリで 3D オブジェクトを配置することもできますが、あまり手軽ではありません。あるいは fill-extrusion レイヤーfill-extrusion-base プロパティを使って、宙に浮いた押し出しポリゴンの物体を表示するという方法もありますが、何となく無理やり感が拭いきれません。

そこで便利な方法が、Mapbox GL JS と deck.gl の組み合わせです。deck.gl の GeoJsonLayer は Mapbox とは異なり、GeoJSON の高さパラメータ(GeoJSON 仕様 RFC 7946 ではオプショナル)をちゃんと解釈して 3D 空間に GeoJSON の要素を配置してくれるため、データを用意するだけで表示できるので手軽です。また、LineLayerPathLayerScatterplotLayerSolidPolygonLayer を使ってプリミティブな要素を表示することもできますし、地点間の繋がりを 3D の弧で表現できる ArcLayer や、loaders.gl でロードした 3D オブジェクトや luma.gl の 3D オブジェクトを表示する SimpleMeshLayer など、表現力の高いレイヤーを利用することもできます。

deck.gl の各レイヤーを Mapbox に組み込むには主に3つの方法があります(オーバーレイ、インターリーブの違いはこちらのページの図がわかりやすいです)。

1. MapboxOverlay を使ったオーバーレイ

Mapbox のレイヤーは1つの Canvas に、deck.gl のレイヤーは別の Canvas に描画し、最後に重ね合わせます。2D マップに適しています。

2. MapboxOverlay を使ったインターリーブ

Mapbox の各レイヤーと deck.gl の各レイヤーを共通の WebGL コンテキストで描画します。deck.gl のレイヤーはまとめて Mapbox のコントロールとして追加します。3D マップに適しています。

3. MapboxLayer を使ったインターリーブ

Mapbox の各レイヤーと deck.gl の各レイヤーを共通の WebGL コンテキストで描画します。deck.gl の各レイヤーは個別に Mapbox のカスタムスタイルレイヤーとして追加します。3D マップに適しています。

おすすめは「2. MapboxOverlay を使ったインターリーブ」です。共通の WebGL コンテキストを使って各レイヤーを描画するため、レイヤーをまたがって 3D オブジェクトの深度テストが行われ、物体の前後関係がきちんと反映されます。また、オブジェクトピッキング(マウス座標のオブジェクトの特定)にも対応します。

次のコードは、deck.gl の ScatterplotLayer を MapboxOverlay を使って Mapbox にインターリーブする例です。

import {Map} from 'mapbox-gl';
import {MapboxOverlay} from '@deck.gl/mapbox';
import {ScatterplotLayer} from '@deck.gl/layers';

const map = new Map({
    center: [139.7670, 35.6814],
    zoom: 14,
    pitch: 60
});

const overlay = new MapboxOverlay({
    interleaved: true,
    layers: [
        new ScatterplotLayer({
            id: 'my-scatterplot',
            data: [
                {position: [139.7670, 35.6814, 1000], size: 100}
            ],
            getPosition: d => d.position,
            getRadius: d => d.size,
            getFillColor: [255, 0, 0],

            beforeId: 'admin_labels' // この Mapbox レイヤー ID の直下に挿入する
        })
    ]
});

map.addControl(overlay);

地下に物体を配置した時の問題

さて、これで一件落着というところですが、このままでは地下に物体を配置した時に問題が起きる場合があります。具体的には、ズームレベルを変えたり、ピッチを変えたりしたときに地下のオブジェクトが欠けたり見えなくなることがあります。これはいったい何でしょう?

通常 3D 表示を行うアプリケーションは、描画パフォーマンス最適化のために、カメラを向けた空間を視錐台(Viewing frustum)と呼ばれる領域に区切り、その内部にある物体のみを描画します。Mapbox や deck.gl も例に漏れずこれに従っていますが、地表面にあるものの表示に最適化しているためか、基本的に地下の領域は見えないものとして扱っているフシがあります。

次の図は、視錐台を横から見た様子です。実装のコードを見てみると、視錐台の後方クリップ面は視野内の地表全体が見えるぎりぎりのところまで視点寄りになるように計算されています。視点から後方クリップ面までの距離(farZ)は、視野内の地表の最も遠い距離(furthestDistance)と、視界が届くものと仮定する距離(horizonDistance)のいずれか小さい方になります。そして horizonDistance は、カメラから地表までの距離(cameraToSeaLevelDistance)の10倍という固定値になっています。

真上から見た場合、furthestDistance は地表までの距離と同じになるため、地下の物体はまったく表示されません。水平線が見えない程度のピッチの場合、furthestDistance は画面上端に当たる部分の地表までの距離になりますが、この場合見える物体と見えない物体が出ますし、地下深くのものはいずれにしろ見えないでしょう。さらに、画面に水平線が見えている場合は、furthestDistance は無限大(実際にはクリップされて非常に大きい値)になりますので horizonDistance が支配的になりますが、cameraToSeaLevelDistance の10倍より遠くの物体は表示されなくなるため、特にズームインした時の地下の見える範囲が狭まります。

ビューポートの farZ パラメータを調整する

対策はどのようになるでしょうか。直感的に思いつくのは、地下の物体が見えるように後方クリップ面を大きく後方にずらすことです。しかし、これの弊害は、表示する 3D オブジェクトの数が増える可能性があり、最悪の場合描画パフォーマンスが大幅に落ちること、そして、深度テストを行う際の計算誤差が出やすくなり、ポリゴンのちらつき(z-fighting)が起こる可能性が増えることです。このため、ずらすにしてもできる限り影響の少ない量にする必要があります。

例えば、地下1kmまでの何らかの分布データを表示することを想定しましょう。まず、furthestDistance による判定は、不要なので考慮から外します。そして新たな farZ の値は、horizonDistance か、(cameraToSeaLevelDistance + 1km) のいずれか大きい方、とすればおおよそ良いのではないかと思います。

では、視錐台を決定するビューポートの farZ の設定はどこで行なっているでしょうか。Mapbox では src/geo/projection/far_z.js の 26行目

return Math.min(furthestDistance * 1.01, horizonDistance);

というところを

return Math.max(horizonDistance, cameraToSeaLevelDistance + (1000m));

のように変えてやれば良いはずです。しかし注意が必要です。ここでの距離の単位はメートルではなく「スクリーンピクセル」です。メートルをスクリーンピクセルに単位変換するには、tr.pixelsPerMeter を掛けてやれば良さそうです。

return Math.max(horizonDistance, cameraToSeaLevelDistance + 1000 * tr.pixelsPerMeter);

一方、deck.gl は math.gl の modules/web-mercator/src/web-mercator-utils.ts の 334行目

const farZ = Math.min(furthestDistance * farZMultiplier, horizonDistance);

というところを

const farZ = Math.max(horizonDistance, cameraToSeaLevelDistance + (1000m));

と変えれば良さそうです。こちらは単位は「スクリーン(スクリーンの高さを1とする)」です。しかし、math.gl には単位変換するための係数がないため、このコードが含まれる getProjectionParameters を呼び出している deck.gl の modules/core/src/viewports/web-mercator-viewport.ts の 157-167行目に次の赤字の行の unitsPerMeter というパラメータを加えてやります。

projectionParameters = getProjectionParameters({
    width,
    height,
    scale,
    center: position && [0, 0, position[2] * unitsPerMeter(latitude)],
    offset,
    pitch,
    fovy,
    nearZMultiplier,
    farZMultiplier,
    unitsPerMeter: scale * unitsPerMeter(latitude) / height
});

そして math.gl の modules/web-mercator/src/web-mercator-utils.ts の 276-298行目でその unitsPerMeter を受け取ってやり、

export function getProjectionParameters(options: {
    width: number;
    height: number;
    scale?: number;
    center?: number[];
    offset?: [number, number];
    fovy?: number;
    altitude?: number;
    pitch?: number;
    nearZMultiplier?: number;
    farZMultiplier?: number;
    unitsPerMeter:? number;
}): ProjectionParameters {
    const {
        width,
        height,
        altitude,
        pitch = 0,
        offset,
        center,
        scale,
        nearZMultiplier = 1,
        farZMultiplier = 1,
        unitsPerMeter
  } = options;

先ほどの334行目を次のように変えてやります。

const farZ = Math.max(horizonDistance, cameraToSeaLevelDistance + 1000 * unitsPerMeter);

最後に

さて、以上の通り、地下に物体を配置するために Mapbox GL JS、deck.gl の両方のコード本体に手を入れるというハッキングを行いました。ビューポートはそれぞれで設定しているのですが、結果は正確に一致していないと深度テストの結果が正しくなくなり表示がおかしくなるので解明に苦労しました。描画パフォーマンスとのトレードオフなので、本体にコントリビュートするというのはちょっとないかな、と思っています。

この手法は、Mini Tokyo 3D の地下鉄路線や列車の表示、そして Japan EQ Locator の地下の震源の表示でも使われています。

Mini Tokyo 3D × PLATEAU

この記事は 3D都市モデル Project PLATEAU Advent Calendar 2022 の17日目の記事です(既に終了しておりますが、空いている日付にエントリーさせてもらいます!)。

ここ数年開発を続けている東京の公共交通のリアルタイム3Dマップ Mini Tokyo 3D ですが、2020年末に国土交通省Project PLATEAU を発表して以来、何か面白いデータの活用ができないかと考えを巡らせておりました。今回 PLATEAU の精巧な3D都市モデルを Mini Tokyo 3D のマップ上に組み込んで表示する Mini Tokyo 3D プラグイン PLATEAU plugin for Mini Tokyo 3D の公開に至ったので、概要をご紹介したいと思います。

使い方

まずは https://minitokyo3d.com にアクセスし、画面右側の下から3つ目の「レイヤー設定」ボタンを押して、「PLATEAU」レイヤーを選択して有効にしてください。PLATEAU レイヤーは非常に負荷が高く多くのリソースを消費するため、おそらくモバイルデバイス等ではうまく動かないと思います(デフォルトで機能を無効にしているのはこのため)。快適に利用するには、ハイエンドの GPU を搭載した PC をご利用ください。あと、建築物のデータが表示されるまでにだいぶ時間がかかるので、気長にお待ちくださいね。

あとは通常の Mini Tokyo 3D の操作同様、マップ上を自由に移動したり、列車をクリックして追跡しながら周囲の風景をお楽しみください!

PLATEAU では東京都23区全域に加え、首都圏のいくつかの都市のモデルも含まれているのですが、全部表示しようとすると非常に重たくなってしまうため、現時点では千代田区中央区、港区、新宿区、品川区、渋谷区、豊島区のデータのみに絞っています。

しくみ

Mini Tokyo 3D は Mapbox GL JS をベースマップとして使用し、deck.glGeoJsonLayer を Mapbox にオーバーレイする形で地下の構造や駅のハイライトの表示に使っています。幸いなことに、Project PLATEAU では PLATEAU配信サービス(試験運用)-チュートリアルにて建築物モデル等の 3DTiles データ、地形モデルの Terraindb データ、航空写真オルソ画像タイルデータが Mapbox および deck.gl(もしくは Cesium 等の他のマップライブラリ)ですぐに使える形で配信されているため、これらをデータソースとして利用しています。いや、これは素晴らしいオープンデータの公開の取り組みですよ。その筋のプロの犯行ですね。

プラグインでは、Mapbox のラスターレイヤーで航空写真オルソ画像を表示し、deck.gl の Tile3DLayer を新たに組み込んでテクスチャ付きの建築物を表示しています。

課題と対応

少し実装上の課題と、それらへの対応についても書いておきたいと思います。

まず、PLATEAU で公開されている3D都市モデルの空間参照系は「日本測地系2011における経緯度座標系」であり、高さは「東京湾平均海面を基準とする標高」です。Mapbox や deck.gl が前提とする WGS84日本測地系2011はほぼ同一であるため、緯度経度は問題ありません。しかし、Mini Tokyo 3D では地形(Terrain)データを使わず、建築物は全て同じ平面上に配置することを前提としているため、それぞれの建築物に高さ情報を含む3D都市モデルを表示しようとすると問題が起こります。

具体的には、次の記事でも指摘されているように「モデルが浮く問題」として顕在化します。そこで、モデルの高さをマップ平面に合うように調整する必要があります。記事中では、タイルロード時に呼び出される Tile3DLayeronTileLoad コールバックの中で cartographicOrigin.z から一律40mを引くことで対処しています。しかし、これはあくまで都心沿岸部での見た目をましにするための対策なので、内陸の丘陵部では依然モデルが浮いたままです。

一つの対処法としては、標高データを使ってロードされたタイルの位置の標高分の高さを下げる、という方法が考えられます。しかし、実際やってみたところまだビルが浮いていたり、逆に沈み込んでいるのが目立ったりしていて、満足できる感じではありませんでした。タイルの中心座標の標高を使って調整するのですが、これだとタイルが表現する領域内の起伏をうまく反映できるとは限らず、たまたま中心座標の標高が周囲より突出している場合にモデル全体が浮いてしまうという状況が起こりやすくなります。

そこで、やや手間がかかるのですが、onTileLoad の中でアクセスできるバイナリデータ content.batchTableBinary.buffer の中から、建築物毎の最小の高さが入っている部分の数列を読み出し、ソートした上で中央値を取り出します。これにより、タイルに含まれる建築物の基礎の統計的な標高の目安がわかります。さらに、タイルロード時にセットされている cartographicOrigin.z は標高ではなく楕円体高のようなので、建築物の基礎の標高に加えて東京駅付近のジオイド高 36.6641m を引いてやります。

onTileLoad: d => {
    const {content} = d;
    const buffer = content.batchTableBinary.buffer;
    const key = content.batchTableJson;
    const len = key._gml_id.length;
    const zMinView = new DataView(buffer, key._zmin.byteOffset, len * 8);
    const zMins = [];

    for (let i = 0; i < len; i++) {
        zMins.push(zMinView.getFloat64(i * 8, true));
    }
    zMins.sort((a, b) => a - b);
    content.cartographicOrigin.z -= 36.6641 + zMins[Math.floor(len / 2)];
}

するとどうでしょう、赤レンガの東京駅が地面にぴったり。少し内陸の渋谷のハチ公前もばっちりです。もちろんジオイド高も場所によって変わりますが、首都圏平野部ならあまり目立たない誤差の範囲に収まるかと思います。

次に課題になったのは、Tile3DLayer で単純に建築物を表示しただけだと、全体的に暗い色調になってしまうことです。これは deck.gl のグローバルなライティングの設定によるものだろう、ということで deck.gl の初期値を調べてみると

  • AmbientLight(環境光): 強さ 1.0
  • DirectionalLight 1(平行ライト): 強さ 1.0
  • DirectionalLight 2(平行ライト): 強さ 0.9

ということだったので、これはもっと強力なライトを当てないといかん、そして Mini Tokyo 3D では時間経過とともに色合いが変わる地図にしているため、ライトの色もそれに合わせないといかん、ということでそれを実装してみました。調整の結果、最終的には

  • AmbientLight: 強さ 3.0
  • DirectionalLight: 強さ 9.0

になり、これに加えて航空写真オルソのラスターレイヤーも、時間に応じて raster-brightness-max プロパティを変化させて明るさを調整しています。

さらに、Mini Tokyo 3D では Mapbox の Sky レイヤーを使って空に太陽を表示しているため、上の DirectionalLight の光の方向が太陽の位置と同期していたら良さげになるはずです。ということで、SunCalc ライブラリを使って Unix Time、緯度経度から天球上の太陽の位置を取得し、DirectionalLight の方向を都度設定するようにしました。

const now = Date.now();
const {lat, lng} = map.getCenter();
const sunPos = SunCalc.getPosition(now, lat, lng);
const azimuth = Math.PI + sunPos.azimuth;
const altitude = -sunPos.altitude;

directionalLight.direction = [
    Math.sin(azimuth) * Math.cos(altitude),
    Math.cos(azimuth) * Math.cos(altitude),
    -Math.sin(altitude)
];

都心から望む夕日の雰囲気もパーフェクトです。ちゃんとビル群に夕日の光の反射が見えます。

Mini Tokyo 3D、PLATEAU Plugin for Mini Tokyo 3D は GitHub で公開されているので、チェックしてね!

Mini Tokyo 3D バージョン 3.2.0 リリース

Mini Tokyo 3D バージョン 3.2.0 がリリースされました (GitHub)。3.1.0 からの追加・修正機能を見ていきましょう。

新しい追跡モードと追跡モード設定パネルの追加

列車・旅客機の追跡モードを大幅に強化しました。画面右側のビデオカメラのアイコンボタンを押すと表示される追跡モード設定パネルで、「位置のみ」「前方」「前方上空」「後方」「後方上空」「ヘリコプター」「ドローン」「鳥」の8種類の視点を選べます。

おすすめは「鳥」追跡モード。方角、ズーム、ピッチ全てがスムーズかつダイナミックに変化するため、時間を忘れて列車の旅を楽しむことができるでしょう。

API からも新しい追跡モードの指定ができるようになっています。

列車・旅客機の高度に応じたスケーリングとズームの調整

地下を走る列車のスケーリングおよび追跡時のズームレベルの計算を修正しました(左: 修正前、右: 修正後)。修正前は地下で列車がやや小さく見えてしまっていましたが、列車位置の標高に応じた見え方の一貫性が保たれるようになりました。また、スケーリングの計算を GPU で行うように最適化し、性能への影響を最小限に抑えています。

追跡前の視点への復帰

列車や旅客機をクリックまたはタップすると追跡が開始され、前述の追跡モードの設定によっては対象の列車や旅客機にズームインしていきますが、これまでは追跡を解除した時に現在位置を見失いがちで不便に感じることがあったかと思います。

そこで追跡前の視点を記憶しておき、追跡が解除された際には元の視点に戻るようにしました。駅やライブカメラを選択した場合も同様に、選択解除後に元の視点に戻ります。

動的な車両形式情報のローディング

車両形式データを動的に読み込むことに対応しました。何かのイベント開催時や臨時列車が走るときに、サーバ側のデータを更新することで簡単に特定の車両を走らせることができるようになります。

この機能を利用し、鉄道開業150年記念「黒い山手線」の現在位置表示に対応。150年前に新橋〜横浜間で運行した1号機関車をモチーフにした黒塗りの車両が山手線を走ります(運用は毎日変わりますが、実際の運用状況を反映しています!)。2022年12月31日までの期間限定。

アイコンスタイル更新

内部で利用しているアイコンフォントライブラリのアップデートにより、一部のアイコンスタイルが更新されました。これまでと比べて見た目の統一感が増し、視認性が上がっているのがわかりますかね?

ダイヤ改正対応

東京メトロ銀座線・丸ノ内線東西線・千代田線の 2022/8/27 ダイヤ改正に対応。また、ふかや花園プレミアム・アウトレット開業に伴う秩父鉄道の 2022/10/1 ダイヤ改正にも対応。さらに、特急成田エクスプレスの 2022/10/1 からの全列車の運転再開に対応し、東京〜新宿間が日中1時間に2本ペースになりました。

ライブカメラの拡充

ライブカメラプラグインで表示される、沿線のライブカメラがさらに追加されました。

Mini Tokyo 3D バージョン 3.1.0 リリース

Mini Tokyo 3D バージョン 3.1.0 がリリースされました (GitHub)。

Mini Tokyo 3D は昨年の第4回東京公共交通オープンデータチャレンジに応募しておりましたが、第3回の受賞に引き続き、完成度の高さを評価いただいて今回は審査員特別賞を受賞することができました。東京公共交通オープンデータチャレンジは今回で終了になりましたが、Mini Tokyo 3D は引き続き進化を続けていきます。

それでは 3.0.0 からの追加・修正機能を見ていきましょう。

公共交通データソースの切り替え

東京公共交通オープンデータチャレンジの終了に伴い、これまで東京公共交通オープンデータチャレンジのデータを利用していたところを、公共交通オープンデータセンターのデータに切り替えました。これにより、一部のリアルタイム遅延情報が利用できなくなっており、遅延マーカーが表示されません。また、旅客機の表示も国内線に限定されています。

開発者が Mini Tokyo 3D をビルドする際には、東京公共交通オープンデータチャレンジのトークンは不要になりました。

ダイヤ改正対応

ゆりかもめの 2021/12/13 ダイヤ改正に対応。さらに、都営浅草線京急、京成、北総、芝山鉄道の 2022/2/26 ダイヤ改正、JR 東日本、りんかい線東京メトロ、都営、横浜市営、京王、東武、西武、小田急、東急、みなとみらい線、相鉄、埼玉高速鉄道つくばエクスプレス東葉高速鉄道関東鉄道小湊鐵道いすみ鉄道ニューシャトル多摩モノレールの 2022/3/12 ダイヤ改正に対応しています。

列車情報の修正

データソースの切り替えとダイヤ改正対応に伴い、データ全体の検証を強化したところ、多くの不具合が見つかりました。次のデータの修正を行い、データの正確性が向上しました。

  • 京急モーニング・ウィング三浦海岸〜横須賀中央間が非表示だった問題を修正
  • 京急逗子線から京急本線への直通列車の追跡が中断してしまう問題を修正
  • 新京成線鎌ヶ谷鎌ヶ谷大仏を全列車通過していた問題を修正
  • いすみ鉄道城見ヶ丘を全列車が通過していた問題を修正
  • 千葉モノレール1号線の土休日の列車の行き先が一部表示されていなかった問題を修正
  • 湘南モノレールで全列車が富士見町を通過していた問題を修正
  • 東急こどもの国線は多客期ダイヤだったのを通常ダイヤに変更
  • 東武東上線の一部列車で和光市〜志木間で列車が表示されていなかった問題を修正
  • 西武秩父線の一部列車で行き先が間違っていた問題を修正
  • 西武多摩川線の白糸台始発の列車が表示されていなかった問題を修正
列車名表示の追加

京急ウィング号の列車名と号数、ホリデー快速おくたまホリデー快速あきがわの列車名が表示されるようになりました。そして京王ライナー Mt.TAKAO 号が臨時便から通年運行になったので、Mini Tokyo 3D にも現れるようになっています。

ボタンを大きくして操作性改善

ボタンのサイズを1.3倍に拡大して操作性を改善しました。最近のスマホの高解像度化でボタンサイズが相対的に小さくなっているため、Mapbox のデフォルトのボタンサイズだと少しタップしにくかったので改良を加えました。

ポルトガル語サポート

コミュニティーのコントリビューションにより、ポルトガル語(ブラジル)に対応しました。

アプリ起動の高速化

アプリ起動時のデータローディングの並列化を実装し、起動時間の短縮を図りました。

降水プラグインが雪のアニメーションに対応

内部で使用している Mapbox プラグインのアップデートに伴い、Mini Tokyo 3D 降水プラグインでも降雪アニメーションの表示に対応しました。

ライブカメラの拡充

ライブカメラプラグインで、東京近郊鉄道沿線のライブカメラを大幅に追加し、現在の東京の様子がより身近に感じられるようになりました。