続:NOAA の Magnetic Declination Estimated Value 計算サービスを python から使う

これの続き。

ちょっと無頓着すぎたのね、前回の。

実際頭をかすめてはいたものの安直に考えてとりあえずやってみたってノリで android 機で取れる GPS 情報と一緒に使って、当たり前だが猛烈にアクセスしてしまってハングアップした。そりゃそーだ、1m動くたんびに WEB サービス呼び出していいはずがない。ちなみに公式にこう説明されてる:

A maximum of 50 connections/second from all the users is allowed at any time. If this is exceeded, the back-end calculator stops taking requests for 10 seconds. Bulk calculations should be requested serially and not in parallel. If the requests start returning with errors then your script should go to sleep for 5-10 minutes before trying the next request.

この API の公式に言われている制限も含め、実際複数のことを考えなければならない:

  • (平均)磁偏差の変化は現実にはどの程度の位置差でどのくらい変化しうるものなのか?
  • (平均)磁偏差の変化は現実にはどの程度の時刻差でどのくらい変化しうるものなのか?
  • (平均)磁偏差の変化はモデルではどの程度の位置差でどのくらい変化しうるものなのか?
  • (平均)磁偏差の変化はモデルではどの程度の時刻差でどのくらい変化しうるものなのか?
  • 実用的にどの程度の磁偏差の変化に価値があるのか?

上二つは、実際の磁場の話。下二つはモデルの解像度の話。最後のは、コンパスの方位がどのくらい「狂ったら」困るのか、の話。45度違ったら話にならないのは誰でもわかるが、じゃぁ1度はどうか? 30秒はどうか?

30秒30分違ったら困る! という用途にはもともと向かない:

Accuracy for the declination is generally within 30 minutes (0.5 degrees) of arc.

現実、の方はそれこそ正確な地図と磁石を実際に持ち歩いてでも計測してみないとわからない世界なので、一般人としてはひとまずモデルを信じるしかないが、モデル計算の解像度に関しては、情報が見つからない。国土地理院では 3 分グリッドを使っているようだ。3 分というと、日本付近の緯度では 4km とか 5km くらい。WMM、IGRF も同じかどうかが良くわからないけれど、これ以上細かくみても仕方がないってことかもしれない。ちなみに NOAA でもグリッド版の WEB UI が用意されていて、デフォルトでは 1度グリッドみたい。1度は無論デカい。緯度方向で 111km。

1度はデカい、としても、「1度違ったくらいでは磁偏差は(欲しい精度としては)全然変わらない」なら全然1度グリッドでも良いのだが…? 少なくとも API の使用例:

http://www.ngdc.noaa.gov/geomag-web/calculators/calculateDeclination?lat1=40&lon1=-105.25&resultFormat=xml

が .25 度 == 15 分を与えているわけなので、最低でもこのくらいの精度は期待しても良い、てことなのかも。(結果のフォーマットからは緯度経度の小数点5桁で返ってくるので、一秒未満の解像度を持っている可能性があるとも言えるんだけれど。

時刻の解像度のほうは結構考えやすい。実際の計算結果をみてみると、「-4分 per year」ずつ変化していく(declination_sv=-0.05923 とか)という計算になってるので、これから考えると要求精度によっては「なんなら1年間同じ値を使いまわしたっていいくらい」とさえなるはず。

ちなみに GPS を使って、てことになると、「ワタシの移動速度」にも左右される。地上で歩いてるんなら 5km なんか 1 時間の世界なんだから、3 分解像度で考えるなら 5km 範囲内で 1回 WEB サービスに取りに行けばいいだけ。航空機なんぞは、たとえば 400 ノットとすると、1分間に 12km も進んでしまうわけだから、つまり 3分解像度でみると 1分間に 2 回情報を取りに行かなければならない。

というわけで、「どんな解像度を期待して良いのか、どんな解像度で意味を持つのか」はあまり良くはわからない(続けて色々使ってみないとわからない)んだけれども、少なくともその「グリッド(2D + 時刻の3次元ね)」単位でキャッシュして、WEB アクセスをある程度は少なくなるようにしないといけない。愚直にやるならこんなかなぁ:

 1 # -*- coding: utf-8 -*-
 2 import urllib
 3 import urllib2
 4 import xml.etree.ElementTree as ET
 5 from datetime import datetime
 6 
 7 _URL = 'http://www.ngdc.noaa.gov/geomag-web/calculators/calculateDeclination'
 8 _LOC_RESOL_MINS = 3  # minutes
 9 _CACHE = {}
10 
11 def get_magnetic_declination(lon, lat):
12     resol = 3600. / (_LOC_RESOL_MINS * 60.)
13     values = {
14         'model': 'WMM', # 'WMM' or 'IGRF'
15         'resultFormat': 'xml',  # 'html', 'csv', 'xml', or 'pdf'
16         'lon1': "%.5f" % (int(lon * resol) / resol),
17         'lat1': "%.5f" % (int(lat * resol) / resol),
18         #'startYear': 2015,
19         #'startMonth': 7,
20         #'startDay': 26,
21         }
22     query_value = urllib.urlencode(values)
23     cache_key = (datetime.now().strftime("%Y%m%d"), values['lon1'], values['lat1'])
24     if cache_key in _CACHE:
25         return _CACHE[cache_key]
26 
27     req = urllib2.Request(_URL, query_value)
28     response = urllib2.urlopen(req)
29     the_page = response.read()
30     root = ET.fromstring(the_page)
31     result = dict(
32         ((r.tag, float(r.text.strip())) for r in root.find('result')))
33     _CACHE[cache_key] = result
34     return result

一応日々違う、というていのコードだけれども、先の検証から「日々の変化はほとんど無視できる」ということでいいなら、キャッシュデータを永続化して持っておくことも考えればいい。

念のため現実の結果例:

1 {
2     'elevation': 0.0,
3     'longitude': 139.8,
4     'latitude': 35.65,
5     'declination': -7.37462,
6     'date': 2016.57377,
7     'declination_sv': -0.05932,
8     'declination_uncertainty': 0.29975
9 }