地図と気圧高度グラフあわせ技(kivy.garden.mapview + kivy.garden.graph)

いつもの通り皆様にはがっかりネタでしょうな。

ワタシには楽しい。「気圧補正と気温補正で気圧高度は結構ぴったり合う」を、「GPS の位置情報と精度を「本気で」可視化」と同じノリで。graph についてはこれ

プログラムはこんなである:

  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 import numpy as np
  8 from kivy.lang import Builder
  9 from kivy.app import App
 10 from kivy.properties import StringProperty
 11 from kivy.uix.boxlayout import BoxLayout
 12 from kivy.clock import Clock
 13 from kivy.garden.mapview import MapView, MapMarker, MapSource
 14 from kivy.garden.graph import Graph, LinePlot
 15 
 16 ###
 17 import math
 18 
 19 # values bellow are at base=0 (<11km)
 20 _P0 = 1013.25  # static pressure (Pa) at MSL
 21 _T0 = 273.15 + 15  # standard temperature (K) at MSL
 22 _L0 = 6.49 / 1000.  # standard temperature lapse rate (K/m) in ISA
 23 _R = 8.31432  # universal gas constant in N·m /(mol·K)
 24 _g0 = 9.80665   # gravitational acceleration in m/s**2
 25 _M = 0.0289644  # molar mass of Earth's air in  kg/mol
 26 
 27 #
 28 def a2p(h, delta_T=0):
 29     t = _T0 + delta_T
 30     return _P0 * math.pow((t / (t + _L0 * h)), _g0*_M/(_R*_L0))
 31 #
 32 def p2a(p, hA, pA, delta_T=0):
 33     delta_p = pA - a2p(hA, delta_T)
 34 
 35     t = _T0 + delta_T
 36     return (t / _L0) * (np.power((p - delta_p) / _P0, -_R*_L0/(_g0*_M)) - 1)###
 37 
 38 Builder.load_string('''
 39 <GardenMapviewDemo>:
 40     orientation: 'vertical'
 41 
 42     elev_graph: elev_graph
 43     map: map
 44 
 45     Graph:
 46         id: elev_graph
 47         size_hint: (1, None)
 48         height: 500
 49         background_color: (0.5, 0.5, 0.5, 1)
 50         xlabel: 'time'
 51         ylabel: 'elevation'
 52         x_grid: True
 53         y_grid: True
 54         x_grid_label: True
 55         y_grid_label: True
 56         y_ticks_major: 25
 57         x_ticks_major: 300
 58 
 59     MapView:
 60         id: map
 61         zoom: 17
 62 ''')
 63 
 64 class GardenMapviewDemo(BoxLayout):
 65     def __init__(self):
 66         super(GardenMapviewDemo, self).__init__()
 67         self.map.map_source = MapSource(
 68             url='http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
 69             attribution='http://maps.gsi.go.jp/development/ichiran.html')
 70 
 71         temp = []
 72         self._index = 0
 73         self._added_markers = []
 74         with open("./2016-08-10-with-DEM5.csv") as fi:
 75             fi.readline()  # skip header
 76             hA, pA = None, None
 77             for i, line in enumerate(fi.readlines()):
 78                 t = list(map(float, line.split(",")))[:7]
 79                 if not hA:
 80                     hA, pA = t[6], t[4]
 81                 t.append(p2a(t[4], hA, pA, 15))
 82                 #
 83                 # 0: time
 84                 # 1, 2: lon, lat
 85                 # 3: alt(GPS)
 86                 # 4: pressure
 87                 # 5: alt(pressure)
 88                 # 6: elev(DEM5)
 89                 # 7: alt(pressure with corr)
 90                 #
 91                 temp.append(t)
 92         self._data_all = np.array(temp)
 93         self._data_all[:,0] -= self._data_all[0,0]
 94         #
 95         self.elev_graph.xmax = float(self._data_all[-1,0])
 96         elev_max = max(
 97             self._data_all[:,3].max(),
 98             self._data_all[:,6].max(),
 99             self._data_all[:,7].max(),
100             )
101         self.elev_graph.ymax = float(elev_max + 10)
102         self.plot = [
103             LinePlot(color=[0, 1, 0, 1], line_width=1),
104             LinePlot(color=[1, 0, 0, 1], line_width=1),
105             LinePlot(color=[1, 1, 0, 1], line_width=1),
106             ]
107         for p in self.plot:
108             self.elev_graph.add_plot(p)
109         #
110         #self.update_view()
111         Clock.schedule_once(self.update_view, 30)
112 
113     def update_view(self, *arg, **kwarg):
114         if self._added_markers:
115             for m in self._added_markers:
116                 self.map.remove_marker(m)
117             self._added_markers = []
118 
119         # kivy's xxxProperty can't treat numpy types...
120         cur = map(float, list(self._data_all[self._index]))
121 
122         self.map.center_on(cur[2], cur[1])
123         m = MapMarker(lon=cur[1], lat=cur[2])
124         self.map.add_marker(m)
125         self._added_markers.append(m)
126         #
127         self.plot[0].points.append((cur[0], cur[3]))
128         self.plot[1].points.append((cur[0], cur[6]))
129         self.plot[2].points.append((cur[0], cur[7]))
130         #
131 
132         if self._index < len(self._data_all) - 1:
133             elapse = self._data_all[self._index + 1][0] - \
134                 self._data_all[self._index][0]
135             self._index += 1
136             Clock.schedule_once(self.update_view, elapse / 50)
137 
138 class GardenMapviewDemoApp(App):
139     def build(self):
140         return GardenMapviewDemo()
141 
142 
143 if __name__ == '__main__':
144     GardenMapviewDemoApp().run()

mapview が futures と requests を必要とすることをお忘れなく。データの 2016-08-10-with-DEM5.csv は「気圧補正と気温補正で気圧高度は結構ぴったり合う」のものと内容は同じだけれど、時刻データが文字列でダルかったので UNIX time_t な float に直して使ってたりする。そんだけ。

うーん、mapview も graph も手を入れたいこといっぱいあって、結構ストレスだわ。こういうデータの見方って(特にフィールド系の自然科学やってるヒトたちには)すごくわかりやすくていいと思うのね。だからこんなんが手軽にちゃちゃっと作れるのっていいと思うんだ。だとすればもちっと問題が少なくないと困る。今のままだとちょっと「自力で何でもどうにか出来てしまう人」にしか薦められない。