qgmap 開発メモ – 縮尺表示その2

知恵熱にやられながらも、ついに任意の緯度の緯線1周の長さを求めることができた!

高校生の自分ならもっと早く解けたんだろうな・・・。時間は残酷だぜクールフッ

結果から言うと、緯度θの半径は、こうなった。

x = a2cosθ/√(a2+(b2-a2)sin2θ)

aは地球の長半径(中心と赤道の距離)、bは短半径(中心と北極・南極点の距離)を表している。

半径がわかれば2πr で円周の長さ=緯線の長さがわかるので、後は地球1周分の地図のドット数で割れば、1ドット当たりの距離が求まる!これで縮尺表示の目処が立った。笑う

せっかく錆付いた頭をフル回転させて導いたので、記念に過程も晒しておこう。以下は自己満の世界なので読まなくていいですよ。


楕円の式は以下のようになる。

x2/a2+y2/b2 = 1 ・・・①

ここでも、aは地球の長半径、bは短半径を表す。

まずこの楕円の法線ベクトルを求める。法線ベクトルvは以下の式で計算できる。

v=(∂f/∂x, ∂f/∂y)

楕円の方程式より法線ベクトルを求める。

f(x,y) = x2/a2+y2/b2-1
v=(∂f/∂x, ∂f/∂y)=(2x/a2, 2y/b2

方向が重要で、長さはどうでもいいので、扱いやすいように、a2b2/2をかける。

v=(xb2, ya2

この法線ベクトルを自身の長さで割って単位ベクトル化すると、

v‘=(xb2/|v|, ya2/|v|)

となる。ただし、

|v| =√(x2b4+y2a4)

である。

この単位法線ベクトルと赤道面がなす角度が緯度θなので、

v‘=(xb2/|v|, ya2/|v|) = (cosθ, sinθ)
∴ cosθ = xb2/|v| , sinθ = ya2/|v|

上の2つの式と、楕円の式①を連立方程式として、x,y を θの式で表す。

式①を変形

x2b2+y2a2 = a2b2
y2a2 = a2b2– x2b2 = b2(a2– x2) ・・・②

sinθの式を両辺2乗する。

|v|2sin2θ = y2a4 = a2(y2a2)

式②を代入

|v|2sin2θ = a2(b2(a2– x2))

x2について整理すると、

x2 = a2-(|v|2sin2θ)/a2b2 ・・・③

ここで |v|2 を求める。

|v| 2= x2b4+y2a4

式②を代入

|v| 2=x2b4+a2(b2(a2-x2))

整理すると

|v| 2=b2(x2(b2-a2)+a4)

これを式③に代入

x2 = a2-(b2(x2(b2-a2)+a4)*sin2θ)/a2b2

x2について整理すると

x2 = a4(1-sin2θ)/(a2+(b2-a2)sin2θ)

1-sin2θ = cos2θなので、

x2= a4cos2θ/(a2+(b2-a2)sin2θ)

平方根を取って

x = a2cosθ/√(a2+(b2-a2)sin2θ)

同様にyも求めて、θを0~πまで回してグラフを書いたところ、楕円の図と一致したので、たぶんあってるのでしょう。

ふぅ。

qgmap 開発メモ – 縮尺表示→緯度ってなんだ?

今度は地図表示に必要な縮尺の表示を考える。

縮尺表示
↑こういうやつ

最初は単純にこのズームレベルなら、この縮尺画像みたいに、ズームレベル分画像用意しとけばいいだろうと思っていたけど、実は真面目にやると大変なことが分かった。

というのも、Google Maps は メルカトル図法(あぁ、懐かしい響き ^^;) なので、赤道上の地図の1ドットと、北極・南極に近い所の地図の1ドットは、縮尺が異なるわけだ。極に近い方が拡大されて描画されているので。

ということは、表示している緯度によって、縮尺表示も柔軟に変化させる必要が出てくると。

しかも、厳密にやるなら、標高も関わってくるのか。同じ緯度1度でも、標高が高い方が距離が長いはず。まぁ、これは標高0mで考えればいいか。

さらにややこしいのは、地球が楕円体だということ。完全な球なら、まだ計算しやすいんだけどなぁ。

Google Maps では緯度経度には世界測地系が使われている。世界測地系は GRS80 楕円体とよばれる、国際的な標準として使われている(?) 楕円体を使った測地系らしい。GPS では WGS84 楕円体を使っているらしいが、ほとんど同じと見ていいらしい。

高校以来の楕円の方程式とかをいろいろいじっていると、どんどんワケが分からなくなってくる悲しい

色々考えてるうちに、緯度ってなんだ?と、素朴な疑問が・・・。

緯度って?
緯度ってなんだ?

個人的には、楕円の中心(上図のO)と赤道(R)と、任意の地点(P)がなす角PORの角度θが緯度だと思っていた。

でも、地表上で星や太陽の高さから求まる緯度は、水平線と、地軸のなす角度φとなるはず。

あ、でも、天体の角度から緯度を求める際に水平器で水平を取るだろうから、そうすると、水平線が基準ではなく、重力の方向に対して水平が基準になるから、天体を使ってもθが求まるのか?

混乱しながらいろいろなページを見てみると、どうやらφが緯度らしい。ここのページの図がわかりやすかった。楕円体の法線(地表から垂直に出た線)と、赤道面がなす角度が緯度らしい。

これらを踏まえて、緯度から1ドットの距離(縮尺)を求めると・・・ って、想像しただけで頭から湯気が出てくる ^^;

うーん。任意の緯度の楕円体上1周の長さが計算できればいいんだけどなぁ。

qgmap alpha3 リリース

お知らせ:新しいバージョンが出ています。最新バージョンはダウンロードページ

リリースしますと宣言してからだいぶ時間が経ってしまいましたが、qgmap の alpha3 をリリースします。

SL-C1000で動作確認してます。SDカード等にもインストール可能です。

今回の修正点は主に動作の高速化です。

  • 拡大処理を独自実装して高速化
  • QPixmapCache のキャッシュサイズ変更 (1MB->8MB)
  • 拡大前の QImage をキャッシュするように変更

最後の拡大前画像のキャッシュ化は開発メモには書いてませんでしたが、QPixmapCache だけでは拡大後の画像しかキャッシュしないため、拡大する度に前の同じ画像を何度もロードし直していました。

そのため、VGA画面全部が4倍拡大の場合、ちょうどいい場所であれば1枚の画像だけロードして、それを4倍拡大すればいいものを、律儀に同じ拡大前画像を何度もロードしていました。

ただ、今回の修正の1番目と3番目は拡大補完時の高速化で、等倍地図での高速化は2番目の修正のみなので、地図の必要領域の全縮尺をダウンロードしている人にはあまり高速化は体感できないかもしれません。

私みたいに地図の容量をケチるためにダウンロード範囲を最低限までチマチマ削っている人には効果大かも (^^;

そろそろデバッグコードも綺麗に消して、バージョン 0.1 としてリリースしてもいいかなぁ。

qgmap 開発メモ – 拡大処理を実装

QImage の scanLine を直接書き換えることで拡大する処理を書いてみた。整数倍だけなので簡単に書けた。

時間を測ってみると、倍率によらず 6~7ms 位。 QPixmap::xForm() は 36ms~122msなので比較にならないくらい速い。

拡大の有無にかかわらず、画像1枚あたり 80 ~ 120ms 程度でロードできるようになった。

あと、QPixmapCache を使ってキャッシュしてるつもりだったんだけど、キャッシュサイズがデフォルトでは 1MB らしい。地図1枚で 256x256x4byte=256kB だから、4枚しかキャッシュできてなかった。VGA1画面では最大 12枚の地図が必要なので、全くキャッシュの意味がなかったということに。照れる

とりあえず 8MB (=地図32枚分) に増やしてみたところ、確かに動作が軽くなった。

拡大処理の独自実装と、キャッシュの拡大で体感で分かるほどスクロールがスムーズに。まぁ、一応実用レベルなのかなぁ。個人的にはまだ引っかかる感じは気に入らないけど。

ソースを整理したらリリースします。

qgmap 開発メモ – 画像読み込み関係の処理時間

qgmap の画像読み込みや拡大処理を高速化したい。

スタイラスで地図をグリグリやってると、画像読み込みのタイミングで、カクッ、カクッと引っかかるような感じがする。できれば PC で Google Map を使ったときみたいになめらかにしたいなぁと。特に拡大補完する場合は体感でわかるくらい重くなるので、拡大処理も見直したい。

ということで、画像読み込みや拡大処理の時間を測ってみた。

Google Map の画像は 256×256 の 8bit パレットの PNG 画像となっている。少しだけ 4bit, 2bit, 1bit PNG も混じっているみたいだけど。

今の qgmap では、QPixmap::load() を使って画像を読み込んで、拡大時は QPixmap::xForm() で拡大している。この xForm() は、QMatrix というクラスで座標変換の行列を設定して変換するんだけど、行列の係数はもちろん浮動小数点で指定するので、ここがすごく重そうな感じ。

とりあえず、QPixmap::load() と、QPixmap::xForm() の時間を測ってみる。

Qtには QTime という時間関係のクラスがあり、これで簡単にある区間の時間が測れるらしい。便利じゃのう。

QTime t;
t.start();
測りたい処理
printf("%dms\n", t.elapsed());

こんな感じ。printf 使うあたりが C++ に染まれていない証拠。

適当に測って平均するとこんな感じになった。

  • QPixmap::load()   70.0ms
  • QPixmap::xForm()  2倍拡大 (128×128→256×256)  122.1ms
  • QPixmap::xForm()  4倍拡大 (64×64→256×256)  69.4ms
  • QPixmap::xForm()  8倍拡大 (32×32→256×256)  50.3ms
  • QPixmap::xForm()  16倍拡大 (16×16→256×256)  36.2ms

拡大処理は拡大元の面積に比例するようだ。思ってたよりも遅いなぁ。

ついでに、QImage::load() と、QPixmap::convertFromImage() の時間も測ってみた。

  • QImage::load()   22.5ms
  • QPixmap::convertFromImage()  58.5ms

Qimage::load() は PNGのデコード、convertFromImage() でスクリーンの32bppへ変換とローテーションをしてるのかな。両方合わせて QPixmap::load() よりちょっと長いくらいか。

これくらいなら、拡大する時は、QImage::load() → 独自拡大処理 → QPixmap::convertFromImage() の方が速そうだ。拡大しない時は QPixmap::load() で直接ロードで。

それにしても画像1枚読むだけで 0.1 秒近くかかってるんだな。縦方向にスクロールさせると最大4枚同時にロードするから、0.4秒以上止まっちゃうわけで。そりゃ引っかかる感じがするわけだ。根本的にはスレッド化してバックグラウンドでロードしないと解決しなそうだ。