accuracy が取れることがわかったのですぐに試してみたくなり。
四の五の言わずに先に結果の動画:
これだけでもいい人はこれだけでも良かろう。じっくり観察するとかなり役に立つんじゃないかと思う。
基本的に現実の時間の10倍早回しで、時間が止まって見える箇所は以下のいずれかが理由:
- 実際に動いてない(駅で飯食ってたりとか)
- GPS センサがロケーション変化を検出出来てない (例: 横須賀線の東京品川間は地下なので全然ダメ)
- 稀: 後述の可視化に使ったプログラムが少し遅延している (タイルのキャッシュの関係)
- さらに稀: 取れたはずのデータを記録出来なかった (後述)
一応この可視化に使ったプログラムはこんな:
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 datetime import datetime
7 from kivy.lang import Builder
8 from kivy.app import App
9 from kivy.properties import StringProperty
10 from kivy.uix.boxlayout import BoxLayout
11 from kivy.clock import Clock
12 from kivy.garden.mapview import MapView, MapMarker, MapSource
13
14 from geographiclib.geodesic import Geodesic
15 _WGS84 = Geodesic.WGS84
16
17 Builder.load_string('''
18 <GardenMapviewDemo>:
19 orientation: 'vertical'
20
21 detail: detail
22 map: map
23
24 Label:
25 id: detail
26 text: root.detail_text
27 size_hint: (1, None)
28 font_size: 40
29 height: 150
30
31 MapView:
32 id: map
33 zoom: 17
34 ''')
35
36 class GardenMapviewDemo(BoxLayout):
37 detail_text = StringProperty()
38
39 def __init__(self):
40 super(GardenMapviewDemo, self).__init__()
41 self.map.map_source = MapSource(
42 url='http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
43 attribution='http://maps.gsi.go.jp/development/ichiran.html')
44
45 self._data_all = []
46 self._index = 0
47 self._added_markers = []
48 with open("location_all.csv") as fi:
49 fi.readline() # skip header
50 for i, line in enumerate(fi.readlines()):
51 self._data_all.append(
52 tuple(map(float, line.split(",")[:4])))
53
54 self.update_view()
55
56 def update_view(self, *arg, **kwarg):
57 if self._added_markers:
58 for m in self._added_markers:
59 self.map.remove_marker(m)
60 self._added_markers = []
61
62 cur = self._data_all[self._index]
63
64 self.map.center_on(cur[2], cur[1])
65 m = MapMarker(lon=cur[1], lat=cur[2], source="marker18.png")
66 self.map.add_marker(m)
67 self._added_markers.append(m)
68 if cur[3] < 50:
69 s = "mymarkerS.png"
70 elif cur[3] < 100:
71 s = "mymarkerM.png"
72 else:
73 s = "mymarkerL.png"
74 for az in range(0, 360, 15):
75 dr = _WGS84.Direct(cur[2], cur[1], az, cur[3])
76 m = MapMarker(lon=dr['lon2'], lat=dr['lat2'], source=s)
77 self.map.add_marker(m)
78 self._added_markers.append(m)
79
80 self.detail_text = "[{}] accuracy = {:5.1f} [m]".format(
81 datetime.fromtimestamp(cur[0]).strftime("%H:%M:%S"),
82 cur[3])
83
84 if self._index < len(self._data_all) - 1:
85 elapse = self._data_all[self._index + 1][0] - self._data_all[self._index][0]
86 self._index += 1
87 Clock.schedule_once(self.update_view, elapse / 10)
88
89 class GardenMapviewDemoApp(App):
90 def build(self):
91 return GardenMapviewDemo()
92
93
94 if __name__ == '__main__':
95 GardenMapviewDemoApp().run()
であるが、前提としてこれへの措置であるとか、そもそも導入が一癖ある、というだけでなく、これまで説明してない問題もあって、騙しながら使う必要がある。(高速にアクセスし過ぎるとタイルのキャッシュが取れなくて死ぬ。のでキャッシュが揃うまで何度も動かした。)
コード中の「location_all.csv」が今日記録した、GPS の位置情報変化イベントの「ほぼ全て」を記録したデータで、こんな感じ:
1 time, lon, lat, accuracy, bearing, speed
2 1471325896.17,139.708407,35.558779,13.000,68.30,1.08
3 1471325896.19,139.708407,35.558779,13.000,68.30,1.08
4 1471325896.29,139.708290,35.558816,19.130,0.00,0.00
5 1471325896.29,139.708290,35.558816,19.130,0.00,0.00
6 1471325897.12,139.708404,35.558801,11.000,74.80,1.41
7 1471325897.13,139.708404,35.558801,11.000,74.80,1.41
8 1471325899.11,139.708447,35.558741,7.000,99.60,1.33
一応…こんなんで記録したわけね:
1 # -*- coding: utf-8 -*-
2 # ...snip...
3 from plyer import gps
4 # ...snip...
5
6 class Observer(object):
7 # ...snip...
8 def __init__(self):
9 # ...snip...
10 gps.configure(on_location=self.on_location,
11 on_status=self.on_status)
12 gps.start()
13 #
14 with open("location_all.csv", "w") as fo:
15 fo.write("time, lon, lat, accuracy, bearing, speed\n")
16 # ...snip...
17
18 def on_location(self, **kwargs):
19 #
20 with open("location_all.csv", "a") as fo:
21 fo.write("""\
22 {time:.2f},{lon:.6f},{lat:.6f},{accuracy:.3f},{bearing:.2f},{speed:.2f}
23 """.format(time=(datetime.utcnow() - _ERA).total_seconds(),
24 lon=kwargs['lon'],
25 lat=kwargs['lat'],
26 accuracy=kwargs['accuracy'],
27 bearing=kwargs['bearing'],
28 speed=kwargs['speed']))
29 # ...snip...
ただし sqlite3 と同じく I/O エラーがたまに起こって少し取りこぼした:
1 Traceback (most recent call last):
2 File "jnius/jnius_proxy.pxi", line 47, in jnius.jnius.PythonJavaClass.invoke (jnius/jnius.c:29223)
3 File "jnius/jnius_proxy.pxi", line 73, in jnius.jnius.PythonJavaClass._invoke (jnius/jnius.c:29924)
4 File "/home/tito/code/python-for-android-upstream/build/python-install/lib/python2.7/site-packages/plyer/platforms/android/gps.py", line 30, in onLocationChanged
5 File "/storage/emulated/0/kivy/simple_altimeter/service/observer.py", line 76, in on_location
6 with open("location_all.csv", "a") as fo:
7 IOError: [Errno 4] Interrupted system call: 'location_all.csv'
10057レコードが記録されていたが、この I/O エラーは 11回だけなので、GPS 精度の評価をするのに致命的なものではないと思う。
というわけで、実際に可視化してみて色々わかった:
- accuracy は滑らかに大きくなったり小さくなったりを繰り返しているのね(生き物みたいで面白い)
- たとえば accuracy=1800m なんてのは確かに捨ててもいいかもしれない
- けれども、accuracy が小さな値でも実際は大きく外していることもある(錦糸町なんか通ってない、実際は)
あんまし単純に信頼するわけにもいかなくて、「accuracy を信じて処理してもなおゴミ掃除が必要」てことがはっきりわかったってこと。でもまぁどう考えてもゴミ、てのが一応デカすぎる accuracy をみれば結構わかるのはありがたいとは思う。
01:15追記:
書くつもりで忘れてた。
一応 accuracy の分布的なものも。シンプルにヒストグラムで:
1 import numpy as np
2 import matplotlib.pyplot as plt
3 import matplotlib.mlab as mlab
4
5 _data_all = []
6 with open("location_all.csv") as fi:
7 fi.readline() # skip header
8 for i, line in enumerate(fi.readlines()):
9 acc = float(line.split(",")[3])
10 _data_all.append(acc)
11
12 fig, ax = plt.subplots()
13 plt.yscale("log") # 右肩が小さすぎて読み取れないので対数で。
14 ax.grid(True)
15 n, bins, patches = ax.hist(
16 _data_all,
17 100,
18 facecolor='blue',
19 alpha=0.9)
20
21 plt.savefig("hist_accuracies.png", bbox_inches="tight")
22 #plt.show()
なかなかにヘンチクリンな分布である。計算上の作為が入ってるんじゃないだろうか。(何かの閾値を超えたら何かしら諦める、のような特殊処理でも入ってんじゃないのかな。)