Android センサーの TYPE_GRAVITY でも少し遊ぶ

個人で重力異常を測れます、きゃー。

なんて誰が思うんだ、てか。

Android の TYPE_GRAVITY、どうやら重力加速度そのものを測ってるらしい。おぉ、おもしろい。

デバイスの X, Y, Z 軸ごとに測るってことはたぶん:

\(
g = \sqrt{X^2 + Y^2 + Z^2}
\)

でよさげかね? ので:

gravity_sensor.py
 1 # -*- coding: utf-8 -*-
 2 import math
 3 from copy import deepcopy
 4 from jnius import PythonJavaClass, java_method, autoclass, cast
 5 from plyer.platforms.android import activity
 6 from kivy.logger import Logger
 7 
 8 Context = autoclass('android.content.Context')
 9 Sensor = autoclass('android.hardware.Sensor')
10 SensorManager = autoclass('android.hardware.SensorManager')
11 
12 class _SensorListener(PythonJavaClass):
13     __javainterfaces__ = ['android/hardware/SensorEventListener']
14 
15     def __init__(self, sensor_manager, sensor_type):
16         super(_SensorListener, self).__init__()
17         self.SensorManager = sensor_manager
18         self.sensor = self.SensorManager.getDefaultSensor(sensor_type)
19         self.values = None
20 
21     def enable(self):
22         self.SensorManager.registerListener(self, self.sensor,
23             SensorManager.SENSOR_DELAY_NORMAL)
24 
25     def disable(self):
26         self.SensorManager.unregisterListener(self, self.sensor)
27 
28     @java_method('(Landroid/hardware/SensorEvent;)V')
29     def onSensorChanged(self, event):
30         self.values = deepcopy(event.values)
31 
32     @java_method('(Landroid/hardware/Sensor;I)V')
33     def onAccuracyChanged(self, sensor, accuracy):
34         # Maybe, do something in future?
35         pass
36 
37 
38 class GravitySensor(object):
39     def __init__(self):
40         self.SensorManager = cast('android.hardware.SensorManager',
41             activity.getSystemService(Context.SENSOR_SERVICE))
42         self._bState = False
43 
44     def enable(self):
45         if not self._bState:
46             self._listener = _SensorListener(
47                 self.SensorManager, Sensor.TYPE_GRAVITY)
48             self._listener.enable()
49             #
50             self._bState = True
51 
52     def disable(self):
53         if self._bState:
54             self._bState = False
55             #
56             self._listener.disable()
57             del self._listener
58 
59     @property
60     def values(self):
61         if self._bState and self._listener.values:
62             return self._listener.values[:3]
63 
64     @property
65     def abs_value(self):
66         _v = self.values
67         if _v:
68             return math.sqrt(_v[0]**2 + _v[1]**2 + _v[2]**2)
69 
70     def __del__(self):
71         if self._bState:
72             self._disable()
73         super(self.__class__, self).__del__()

さて。緯度がわかればいわゆる「正規重力」がわかるわけである。パラメータや近似方法の違いで多少変種はあるけれど、wikipediaでのこれ:

\(
g(\phi)= 9.780327 \left(1+0.0053024\sin^2(\phi) – 0.0000058\sin^2(2 \phi) \right)\;[m/s^2]
\)

でひとまず遊んでみる。(φは緯度ね。) 正規重力と実測値との差が重力異常(Gravity anomaly)。

android.txt
1 title=Gravity Sensor Demo
2 author=hhsprings
3 orientation=all
main.py
 1 # -*- coding: utf-8 -*-
 2 import math
 3 from kivy.lang import Builder
 4 from kivy.app import App
 5 from kivy.clock import Clock, mainthread
 6 from kivy.properties import StringProperty
 7 from plyer import gps
 8 from gravity_sensor import GravitySensor
 9 grav_sensor = GravitySensor()
10 
11 kv = '''
12 BoxLayout:
13     RstDocument:
14         id: result
15         text: app.result
16 '''
17 class VariousAltitudeDemo(App):
18     result = StringProperty()
19     lon = None
20     lat = None
21     alt = None
22 
23     def build(self):
24         grav_sensor.enable()
25         gps.configure(on_location=self.on_location,
26                            on_status=self.on_status)
27         gps.start()
28         Clock.schedule_interval(self.do_interval, 1.)
29         return Builder.load_string(kv)
30 
31     @mainthread
32     def on_location(self, **kwargs):
33         #self.lon = float(kwargs['lon'])
34         self.lat = float(kwargs['lat'])
35         #self.alt = float(kwargs['altitude'])
36 
37     @mainthread
38     def on_status(self, stype, status):
39         pass
40 
41     def do_interval(self, *args, **kwargs):
42         if self.lat is None:
43             return
44 
45         g = grav_sensor.abs_value
46 
47         # Theoretical gravity (using GRS80)
48         # g(\phi)= 9.780327 \left(1+0.0053024\sin^2(\phi) - 0.0000058\sin^2(2 \phi) \right)\;[\frac{m}{s^2}]
49         gth = 9.780327 * (
50             1.0 + 0.0053024 * math.sin(math.radians(self.lat))**2 - \
51                 0.0000058 * math.sin(math.radians(2 * self.lat))**2)
52 
53         self.result = """
54 * latitude: {}
55 * observed gravity: {:.6f}
56 * theoretical gravity (using GRS80): {:.6f}
57 * gravity anomaly: {:.6f}
58 """.format(self.lat, g, gth, g - gth)
59 
60 
61 if __name__ == '__main__':
62     VariousAltitudeDemo().run()

ふーむ、こんなもんか。標準がどのくらいかがあんましわからんしなぁ。

なお、本来は高度も考慮する。Free Air Correction という。地球から離れるほど重力は小さくなることは知ってるよね? この項を加える。Web サービスもあるのでみてみて。


16:30追記:
GGMplus 200m-resolution maps
of Earth’s gravity field
. データダウンロードしても使えるみたいだ。ちょっと切れ目が中途半端で使いにくいが東京はこの辺

ところでこの GGMplus 200m-resolution maps of Earth’s gravity field を紹介している WIRED の説明…。一般人向けなのだから仕方ないのだが、かなり誤解を招くぞ。「しかし、この重力加速度は場所により、以前考えられていた値よりも大きく変化することが、オーストラリアとドイツの共同研究により明らかになった。」と言ってるがこれはあんまりな説明だ。「測った、世界的なグリッドを作った」ことは大きな成果だけれども、重力加速度が場所によって違うことなんぞ、地球科学系を専攻してればはっきりいって常識。当然国土地理院とかも計測して公開しとるぞ。(つーか高校地理で習うでしょ? アイソスタシー…。)


2017-08-07追記:
なんだか読む人が多いようなので、結構前から追記しようと思ってたことを追記しとく。

ちゃんと理解しきれているわけではないが、このセンサー、ひょっとしたら「成分分解してるだけ」の可能性がある。つまり、「重力加速度は定数」としてリポートしてくるのじゃないのか、ということ。だとしたら重力異常を求める目的には全然使えない。まぁ元々「傾き検知」が主たる目的のセンサーだからね、そうだとしても文句は言えまい。