-
Notifications
You must be signed in to change notification settings - Fork 0
/
demag.py
161 lines (132 loc) · 5.14 KB
/
demag.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import functools
import typing
import xml.etree.ElementTree as ET
import requests
class MagneticDeclination:
def __init__(self):
self.__providers = {
"NOAA_WMM": functools.partial(self.__noaa_provider, "WMM"),
"NOAA_IGRF": functools.partial(self.__noaa_provider, "IGRF"),
"BGS_WMM": functools.partial(self.__bgs_provider, "WMM"),
}
def md(
self,
lat=0.0,
lon=0.0,
elev=0.0,
year=2000,
mon=1,
day=1,
provider="NOAA_WMM",
) -> typing.Optional[float]:
"""
Returns magnetic declination for certain lat/lon and date.
"""
self.__check__all_args(lat, lon, elev, year, mon, day, provider)
provider_method = self.__provider_factory(provider)
return provider_method(lat, lon, elev, year, mon, day)
def __check__all_args(
self, lat, lon, elev, year, mon, day, provider
) -> typing.Optional[bool]:
"""
Check whether all conditions are met before starting getting MD from
the provider. Returns True if all conditions are met,
throws exception otherwise.
"""
lat = float(lat)
lon = float(lon)
elev = float(elev)
if any((lat > 90.0, lat < -90.0, lon > 180.0, lon < -180.0)):
raise ValueError(
f"Lat and/or Lon beyond limits lat:{lat} lon:{lon}"
)
if not self.__is_date_correct(year, mon, day):
raise ValueError(f"Incorrect date! {year}-{mon}-{day}")
if not self.__is_provider_registered(provider):
raise ValueError(f"Unregistered provider {provider}")
return True
def __all_ints(self, *args) -> bool:
"""Checks whether all args are integeres"""
return all(map(lambda x: isinstance(x, int), args)) if args else None
def __is_provider_registered(self, provider_name) -> bool:
"""
Returns True if the provider is registered and a callable object is
assigned to that provider. Otherwise False returned.
"""
return provider_name in self.__providers and callable(
self.__providers[provider_name]
)
def __is_date_correct(self, year, mon, day) -> bool:
"""Checks whether date is correct"""
return self.__all_ints(year, mon, day) and (
(mon in (1, 3, 5, 7, 8, 10, 12) and day in range(1, 32))
or (mon == 2 and day in range(1, 30))
or (mon in (4, 6, 9, 11) and day in range(1, 31))
)
def __provider_factory(self, provider_name: str) -> bool:
"""Returns callable according to the provider's name"""
return self.__providers[provider_name]
def __noaa_provider(self, model, lat, lon, elev, year, mon, date) -> float:
"""
Retrieves magnetic declination based on
National Centers for Environmental Information
National Oceanic and Atmospheric Administration.
Based on either WMM or IGRF model.
Different models lead to DIFFERENT RESULTS!!!
In case of error raises exception.
https://www.ngdc.noaa.gov/geomag/calculators/magcalc.shtml
"""
BASE_URL_NOAA = (
"https://www.ngdc.noaa.gov/geomag-web/"
"calculators/calculateDeclination?"
"lat1={lat:f}&lon1={lon:f}&startYear={year:d}&"
"startMonth={mon:02d}&startDay={day:02d}&resultFormat={form}&"
"model={model}"
)
FORMAT_NOAA = "xml"
md_request = requests.get(
BASE_URL_NOAA.format(
lat=lat,
lon=lon,
year=year,
mon=mon,
day=date,
form=FORMAT_NOAA,
model=model,
)
)
if md_request.status_code != requests.codes.ok:
raise RuntimeError(f"Unknown result")
md_xml_tree = ET.fromstring(md_request.text)
md = float(
md_xml_tree.find("./result/declination").text.strip("\n")
)
return md
def __bgs_provider(self, model, lat, lon, elev, year, mon, day) -> float:
"""
Calculates magnetic declination based on
British Geological Survey World Magnetic Model 2015 calculator.
Based on WMM model ONLY!
http://www.geomag.bgs.ac.uk/data_service/models_compass/wmm_calc.html
"""
BASE_URL_BGS = (
"http://geomag.bgs.ac.uk/web_service/GMModels/wmm/2020/?"
"latitude={lat:f}&longitude={lon:f}&altitude={elev:f}"
"&date={date:s}&format={form}"
)
FORMAT_BGS = "json"
date = "{0}-{1}-{2}".format(year, mon, day)
md_request = requests.get(
BASE_URL_BGS.format(
lat=lat, lon=lon, elev=elev, date=date, form=FORMAT_BGS
)
)
if md_request.status_code != requests.codes.ok:
raise RuntimeError(f"Unknown result")
md = float(
md_request.json()["geomagnetic-field-model-result"][
"field-value"
]["declination"]["value"]
)
# все ОК, возвращаем результат
return md