kivy-garden.mapview は使ってみると結構いい, but 色々条件付き

昨日の話のちゃんと整理した版。

kivy-garden.mapview について

前提

今回の話は android の kivyLauncher 固有の話でもなく、たとえば Windows でも通用するが、そちらはそちらで若干面倒な話があるので、ひとまず動作環境やセットアップの説明は kivyLauncher での話に限定する。

最初に軽く OpenStreetMap について紹介

なんで OpenStreetMap について紹介しなければならないのか?

  1. GoogleMaps ほどには誰もが日常的に目にしてない
  2. のでこれを知っているかどうかで kivy-garden.mapview への満足度がかなり違う

裏を返せば、「kivy-garden.mapview への文句は実は OpenStreetMap へのもの、かもしれない」という話。地図好きか、地図関係のソフトウェアに少しでも関わったことがない限りは、GoogleMaps ほどにはよく知られてはいないであろうと。

本当に軽くだけ。

  1. OpenStreetMap は一言でいえば「地図版 WikiPedia」である。
  2. OpenStreetMap は「Google なんかに束縛されてたまるもんか」のためのプロジェクトである。
  3. OpenStreetMap はそのために、インフラ開発と実際に動作するサーバを整備している。
  4. OpenStreetMap のインフラの主なものは Mapnik など。
  5. openlayes.js は OpenStreetMap 地図にアクセス出来るクライアントライブラリ。(OpenStreetMap との直接の関係性については、すいません、わからない。)

OpenStreetMap 方式の地図管理と GoogleMaps のそれがどの程度違うのか、ワタシは後者をほぼ知らないのでわからないが、おそらく結構似ているところもある。例えば zoomlevel は完全に共通してる。けど主としては前者しか知らないといっていいので、前者のシカケ部分についてちょっとだけ:

  1. zoomlevel は、地球を正方形で表現した場合の分割数を表現していて、1~19 (API によっては 0~18)。(zoomlevel が 3 の場合、23 x 23 = 64 のタイルになる。)
  2. 地図サーバはこの分割の各「タイル」を描画済みの状態で蓄えている(全部かどうかはサーバの設計次第)
  3. クライアントはサーバに興味範囲(bounding-box、緯度経度)と zoomlevel のセットで問い合わせることで、その興味範囲に収まったタイルの一覧を受け取れる
  4. クライアントはこのタイルを適切に描画(並べる)。タイルの切れ目は固定なので、描画したい範囲でクリッピングするとかはクライアントの責務。
  5. これら一連のクライアント操作をやってくれるライブラリが openlayers.js など。

実際 OpenStreetMap を使ってみればすぐに GoogleMaps ほどには快適ではないし完全ではないことはわかるはず。それでもなおこれを使いたいとすればそれは「自由」のためだ。OpenStreetMap のインフラを使うと自分でデータを揃えて地図サーバを立ててしまう事だって出来るし、クライアント API のために特別な手続きを踏んだり(API キーを取得したりとか)クライアント API のために使用料を払ったりする必要もない。

kivy-garden.mapview の kivyLauncher 環境への導入

ポイントは2点。ひとつは 素の kivyLauncher に入っていないライブラリ2つへの依存があること。二つ目が「これが kivy-garden である」こと。

前者については、requestsPython 3.2 からの concurrent.futures のバックポートの二つ。kivyLaunchr には標準的な site-packages のような場所がなく、またそのような管理をするパッケージマネージャもない。幸いどちらも pure Python なので、ライブラリ部分を置くだけで動く。ワタシは /sdcard/kivy-my-site-packages なんて場所を作ってそこに置いている。あとはたとえば main.py のファイルの上の方に:

main.py
1 # -*- coding: utf-8 -*-
2 import sys
3 # please edit next line for your environment.
4 sys.path.append("/sdcard/kivy-my-site-packages")

などとする。(これも外からコントロールする術はないのでおそらくこれでも最善の方法。まっとうなアプリケーションの場合はこのテクニックは出来るだけ避けたほうがいいのだけれども。)

後者、「これが kivy-garden である」については少しここで触れた。要するに「kivy-garden に含まれるアレコレは分離独立した配布をしたいが利用者には全て import kivy.garden の一味として使わせる」のために特殊なことになっている。これは特別なローダがこれを実現させているんだけど、正直言って気持ち悪いし迷惑にも感じる。kivyLauncher で使う場合はこういう構造:

 1 android.txt
 2 libs/
 3     (以下一部省略)
 4     garden/
 5         garden.mapview/
 6             __init__.py
 7             mapview/
 8                 downloader.py
 9                 geojson.py
10                 mbtsource.py
11                 source.py
12                 types.py
13                 utils.py
14                 view.py
15                 __init__.py
16                 icons/
17                     marker.png
18 main.py

とする。(libs の下に置く。) 個人的には requests とかと同じように「pure Python だ、わーい」として普通に管理出来るほうが嬉しいのだが、garden 方式から外れようとすると「from kivy.garden.mapview import MapView, MapMarkerPopup のようにして使うんだぜ、やーい」って「標準説明」が全部崩れるんで、自分さえ良けりゃそれでいい、んでない限りは、garden 流儀にひとまず従うのが残念ながら吉。

やっと使ってみる

実際 android 機に上述の依存ライブラリをぶち込むだけでも結構時間かかるだろう。そこまで出来れば「一応簡単」:

 1 # -*- coding: utf-8 -*-
 2 import sys
 3 # please edit next line for your environment.
 4 sys.path.append("/sdcard/kivy-my-site-packages")
 5 
 6 from kivy.lang import Builder
 7 from kivy.app import App
 8 from kivy.properties import StringProperty
 9 from kivy.uix.boxlayout import BoxLayout
10 from kivy.garden.mapview import MapView, MapMarkerPopup
11 
12 Builder.load_string('''
13 <GardenMapviewDemo>:
14     orientation: 'vertical'
15 
16     detail: detail
17     map: map
18 
19     RstDocument:
20         id: detail
21         text: root.detail_text
22         size_hint: (1, None)
23         height: 300
24 
25     MapView:
26         id: map
27         zoom: 17
28 
29     Button:
30         id: draw_btn
31         text: 'draw'
32         on_press: root.draw()
33         size_hint: (1, None)
34         height: self.texture_size[1] * 2
35 ''')
36 
37 class GardenMapviewDemo(BoxLayout):
38     detail_text = StringProperty()
39 
40     def __init__(self):
41         super(GardenMapviewDemo, self).__init__()
42 
43     def draw(self, *args):
44         lat, lon = 35.624720, 139.775467
45         self.map.center_on(lat, lon)
46         self.map.add_marker(
47             MapMarkerPopup(lon=lon, lat=lat))
48         self.detail_text = """
49 Mapview Demo
50 ============
51 center_on({}, {})
52 """.format(lat, lon)
53 
54 class GardenMapviewDemoApp(App):
55     def build(self):
56         return GardenMapviewDemo()
57 
58 
59 if __name__ == '__main__':
60     GardenMapviewDemoApp().run()

usage での例は画面全体をマップが占めちゃうパターンだけど、今アタシが書いた方はほかのウィジットと組み合わせる例ね。だってこれがしたいんでしょ、フツー。

そしてやったね、と思ってて見つけたのがこれなのであった。

簡潔に、結論

kivy.garden.mapview は openlayers.js 相当のもの、と理解すればいい。ので openlayers.js を多少でも知っていれば、その知識は使えるはず。実際タイルのキャッシュフォルダを制御したかったりするんだけどこれが簡単にできなかったりとか、出来ることもかなり限られてるとか、現状の出来に対する満足度は今ひとつである。

そうなんだけれども、Google Maps 埋め込みは現実にはほぼムリ? なことに疲れ果てていると kivy.garden.mapview はかなりの朗報、なのである。ノリは openlayers.js とほぼ等価なことを全部 Python (というか kivy) でまかなってるのね。だから今が多少不満でも、kivy.garden.mapview に手を入れることを厭わないのであれば、まぁほとんど全てのことが出来るだろうと思う。

今「回転とか出来ないの?」なんて質問があがってるが、自力でやるつもりなら出来るはず。

てわけで kivy.garden.mapview、オススメでっす (自力でライブラリ破壊・再構築出来る人なら特に)。