C++ ときどき ごはん、わりとてぃーぶれいく☆

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

rust: Cities Skylines 向けの地形ジェネレーターツール "Cities Heightfield from GSI" を Rust で作り直したはなし

今回は「はなし」記事です。若干技術的な内容も含まれますが、基本的には随筆のようなものです。

↑このアプリを Rust で作り直したのでした。元は C++boost::unitsboost::asio のサンプルを兼ねて書いたものでした。

"Cities Heightfield from GSI" を Rust で作り直した切っ掛け

きっかけは週末に Facebook 経由でちょっとした要望を頂いた事と、それと、ちょうど Rust の再学習をしたいなーと思っていた事です。その重なりから、週末の遊びとしてドドドドドーっと基礎的な機能要素ごとの試作をしつつ rust-1.29.0-nightly を rust-0.9 ぶり(およそ4年ぶりだったらしい)にとりあえず書ける程度に再学習しつつ、完全に Rust で書き直して 1.0.0 として公開、 Rust の crate.io にも登録しました。😃

"Cities Heightfield from GSI" は何をしてくれるアプリなのか

このアプリは Cities Skylines というゲーム向けのツールです。このゲームではユーザーがゲーム内のマップを作成して、そのマップを使って都市を発展させて楽しむ事ができます。このツールではゲーム内のマップを現実世界の地球の日本の地形データを、経緯度を与えるだけでよしなに「国土地理院」のオープンデータから必要な領域分の地図データを取得し、 Cities Skylines が読み込める形式・縮尺へ変換したデータを生成します。

国土地理院のオープンデータの地図「地理院地図」は Google Maps 由来で電子地図データの形式としてはデファクト・スタンダードで採用されるようになった Web Mercator 形式です。Web Mercator 法では地球を正方形の面からなる UV 球で分割します(この基本方針でできるだけ整合性を保つため、緯度±85°くらいから90°までは扱わないなど副作用というか、工夫があったりします)。この形式ではタイル状に分割されたデータについて、タイルそれぞれの分解能は全て同じに統一されています。2Dの地図として、街角の店がわかる程度から、都道府県や、せいぜい日本の地方がわかる程度までの縮尺で並べて使う用途には便利です。

ところが、この便利でたくさん応用されている形式にも苦手な事があります。例えば沖縄の石垣のタイル1枚と、北海道の礼文のタイル1枚、どちらも 256x256 px の分解能のタイルなので、2枚を並べて比べて見るような用途ではそのままでは実寸に対する縮尺が異なってしまいます。

f:id:USAGI-WRP:20180731085008p:plain f:id:USAGI-WRP:20180731084949p:plain

↑の Google Mapsスクリーンショットは、どちらも {Z(Level of Detail)}=14 ですが、右下の縮尺を確認すると、かなりの差があると気付くと思います。 Google Maps では URL を見ると、@45.3016037,141.0326741,14z のような部分が見つかります。この部分の数値が、順に「緯度」、「経度」「WebMercator法のZ(レベルオブディティール、ズームレベル、と呼ばれたりもします)」です。この部分を見ながら、ホイール操作などで拡大縮小をすると、現在は 0.25 刻みで変化します。この Z が一致するタイル同士は、地球をUV球を元に平面のテクスチャーへ同じ細分割レベルで投影したタイル同士です。緯度が同じ場合には縮尺も同じです。しかし、緯度の絶対値が大きくなるにつれ、同じ分解能へ投影される土地はより狭い範囲になります。よって、 Web Mercator 法では、緯度の異なる地図同士の目視によるスケーリングの比較はできず、慎重に縮尺を確認して比較する必要が生じます。

Cities Skylines では1つのゲームマップとして 1081x1081 px の画像に 17,290 m × 17,290 m の範囲を収めたデータを扱います。リアルスケールをゲームへ適用したい場合、地理院地図からタイルデータを拾う際に緯度に応じたデータ分解能を計算した上で、中心地から 17,290 / 2 m 四方の領域のデータが含まれる範囲のタイルを回収し、ゲームの縮尺に併せて変換して出力します。

また、 Cities Skylines は都市を発展させるゲームであって、山登りゲームや海底探索ゲーム、あるいは土木工学や建築向けのシミュレーターではありません。マチュピチュ遺跡はじめ、世界には 1024 m を超える山や、 0m 未満の土地にも都市があったり、あるいはせめて景観用としてマップの中に置きたい気持ちになる事はありますが、残念ながら Cities Skylines では地形の標高値は [ 0.0 .. 1024.0 ] m という事になっています。加えて、地形のデータは PNG/Gray16 形式で標高を表す事になっていて、 [ 0.0 .. 1024.0 ] m を [ 0 .. 65535 ] [-] へ正規化する必要もあります。

さて、このような一連の作業を手で操作するのはそれなりに面倒です。そんなわけで、 "Cities Heightfield from GSI" は作られました。😃 処理内容に興味のある方は MIT ライセンスのオープンソースソフトウェアなので、ソースも御覧くださいませ。🍝🍝🍝

UI系への機能要望はどうなったのか

ちなみに、今回の 1.0.0 をリリースする切っ掛けの1つ、「 Facebook で頂いた要望」は「端末エミュレーターWindows ではコマンドプロンプト、など)での操作は経験がなくよくわからないけれど、どうにかこのアプリを使って Cities Skylines で楽しんでみたい」的なものでした。

そこで、当初はごく簡単な GUI の搭載を考えました。しかし、この考えは比較的短い調査で後回しにする事にしました。 Rust の GUI フレームワークは、少なくとも私にはまだあまり発展しきれていないように見えました。 Qt の QML のようにさっくりと扱いやすいものが cargo add してデザインを JSON なり XML なりで書いてイベンドを関連付ければはいできあたり…というようなものがあればすぐに採用したかったのですが、 Rust は現状ではデスクトップ向けの GUI フレームワークでそこまで高度に発達したものはまだまだ開発途上に感じました。

そこで、さしあたっては、このご要望にお応えする方法として CUI ベースでも、実行ファイルを Windowsエクスプローラーからダブルクリックで実行したとしても、使う気があればまあ使って頂けるだろう程度の「対話型の実行形態」を機能追加する事にしたのでした。 GUI はまだ後ほどに( Rust の勉強と併せて何かしら遊んでみるつもりではあります)。

でっていう

かくかくしかじか、アプリが動作するようにはなったので github や crate へ公開しましたが、事実上はじめての Rust アプリという事も免罪符に、あちらこちらで私自身が手慣れている C++ ならば、こんな無駄なコストを払う実装・設計はしないなー…と感じるところができてしまいました。

例えば、ヒープから大きなバッファーを何度か再確保していて、最終的に必要なデータの生成に対して処理としてはやや冗長気味な実装や、ピクセルイテレーションのループやアクセス速度効率…。それから crate imagePNG 出力と [u16] / [u8]reinterpret_cast 的な部分のもっさり感や、バイトオーダーのトリックの必要性もどうにかしたい。

それから、対話型動作の標準入出力まわりの実装・動作もややぎこちなさの残るものですし、標準で --verbose でもついているのかと思うほどログを吐かせていますが、これももう少しスマートにしたい気がします。

とはいえ、 --release ビルド版の実行速度的には "気にする必要" は無さそうなので、引き続き、趣味の勉強としてコードを少しずつ改良して楽しむ材料にできれば、それでいいかな。😏

だそく

このアプリも国土地理院のオープンデータを応用しているアプリとして 「地理院地図パートナーネットワーク」にも登録、ご紹介いただいています。まだ未登録の、もっとたくさんの方が地理院地図を既に応用されていると思います。ぜひ、登録して活用・情報交換に役立てましょう。 😃

それと発売からもう年単位になりますが Cities Skylines はいわゆるシム系好きには今もなおたいへんオススメな楽しいゲームの1つです。まだ遊んだ事のない方、興味が少しでもわきましたら、ぜひ遊んでみてくださいませ。♥