nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

magnetic_declination_for_nakarte.py (3517B)


      1 #!/usr/bin/env python3
      2 # coding: utf-8
      3 import argparse
      4 import base64
      5 import csv
      6 import datetime
      7 import json
      8 import math
      9 import re
     10 from urllib.parse import urlencode
     11 from urllib.request import urlopen
     12 
     13 MIN_LAT = -58
     14 MAX_LAT = 78
     15 GRID_STEP = 2
     16 OUTPUT_VALUE_SCALE = 2
     17 
     18 
     19 def fetch_magnetic_declination_grid(date: datetime.date) -> bytes:
     20     # API documentation: https://www.ngdc.noaa.gov/geomag/CalcSurveyFin.shtml
     21     timeout = 30
     22     url = "https://www.ngdc.noaa.gov/geomag-web/calculators/calculateIgrfgrid"
     23     request_params = {
     24         "key": "gFE5W",
     25         "lat1": MIN_LAT,
     26         "lat2": MAX_LAT,
     27         "latStepSize": GRID_STEP,
     28         "lon1": "-180",
     29         "lon2": "180",
     30         "lonStepSize": GRID_STEP,
     31         "magneticComponent": "d",
     32         "model": "WMM",
     33         "startYear": date.year,
     34         "startMonth": date.month,
     35         "startDay": date.day,
     36         "endYear": date.year,
     37         "endMonth": date.month,
     38         "endDay": date.day,
     39         "resultFormat": "csv",
     40     }
     41     resp = urlopen(url + "?" + urlencode(request_params), timeout=timeout)
     42     assert resp.code == 200
     43     return resp.read()
     44 
     45 
     46 def parse_declination_csv(data: bytes) -> dict[tuple[float, float], float]:
     47     declinations = {}
     48     csv_lines = [
     49         line for line in data.decode().splitlines() if not line.startswith("#")
     50     ]
     51     for row in csv.reader(csv_lines):
     52         assert len(row) == 7
     53         lat = float(row[1])
     54         lon = float(row[2])
     55         declination = float(row[4])
     56         declinations[(lat, lon)] = declination
     57     return declinations
     58 
     59 
     60 def get_model_name(data: bytes) -> str:
     61     m = re.search(r"Magnetic Model:\s*([^(+]+)", data.decode())
     62     return m.group(1).strip()
     63 
     64 
     65 def write_output(
     66     filename: str,
     67     declinations: dict[tuple[float, float] : float],
     68     model_name: str,
     69     model_date: datetime.date,
     70 ) -> None:
     71     value_offset = -math.floor(min(declinations.values()))
     72     packed_values = bytes(
     73         int(round(declinations[(lat, lon)] + value_offset) * OUTPUT_VALUE_SCALE)
     74         for lat in range(MIN_LAT, MAX_LAT + 1, GRID_STEP)
     75         for lon in range(-180, 180, GRID_STEP)
     76     )
     77 
     78     data = {
     79         "array": base64.standard_b64encode(packed_values).decode(),
     80         "minLat": MIN_LAT,
     81         "maxLat": MAX_LAT,
     82         "step": GRID_STEP,
     83         "rowsCount": (MAX_LAT - MIN_LAT) // GRID_STEP + 1,
     84         "colsCount": 360 // GRID_STEP,
     85         "valueScale": OUTPUT_VALUE_SCALE,
     86         "valueOffset": value_offset,
     87         "model": model_name,
     88         "dateYMD": [model_date.year, model_date.month, model_date.day],
     89     }
     90     with open(filename, "w") as f:
     91         json.dump(data, f, indent=2)
     92 
     93 
     94 def main() -> None:
     95     parser = argparse.ArgumentParser()
     96     parser.add_argument("output", help="Output filename")
     97     parser.add_argument(
     98         "--date",
     99         help="Date for calculating magnetic declination. Format is YYYY-MM-DD. Default current date",
    100     )
    101     conf = parser.parse_args()
    102     if conf.date is not None:
    103         date = datetime.datetime.strptime(conf.date, "%Y-%m-%d")
    104     else:
    105         date = datetime.date.today()
    106 
    107     csv_data = fetch_magnetic_declination_grid(date)
    108 
    109     # store/load file for debugging
    110     # with open('data.csv', 'wb') as f:
    111     #     f.write(csv_data)
    112     # with open("data.csv", "rb") as f:
    113     #     csv_data = f.read()
    114 
    115     declinations = parse_declination_csv(csv_data)
    116     model_name = get_model_name(csv_data)
    117     write_output(conf.output, declinations, model_name, date)
    118 
    119 
    120 if __name__ == "__main__":
    121     main()