__init__.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import datetime as dt
  2. import json
  3. import math
  4. import os
  5. import pytz
  6. import subprocess
  7. import sys
  8. import yaml
  9. def to_iso6801_basic(ts):
  10. return ts.isoformat().replace(':', '').replace('-', '') \
  11. .replace('+0000', 'Z')
  12. class Location:
  13. EARTH_RADIUS_METRES = 6.371e6
  14. def __init__(self, latitude, longitude, altitude):
  15. self.latitude = latitude
  16. self.longitude = longitude
  17. self.altitude = altitude
  18. @property
  19. def latitude_radian(self):
  20. return math.radians(self.latitude)
  21. @property
  22. def longitude_radian(self):
  23. return math.radians(self.longitude)
  24. @staticmethod
  25. def haversine(angle_rad):
  26. # http://mathworld.wolfram.com/Haversine.html
  27. return math.sin(angle_rad / 2) ** 2
  28. @staticmethod
  29. def haversine_inv(hav):
  30. # return math.atan2(math.sqrt(hav), math.sqrt(1-hav)) * 2
  31. return math.asin(math.sqrt(hav)) * 2
  32. @staticmethod
  33. def haversine_distance_metres(a, b):
  34. # https://en.wikipedia.org/wiki/Haversine_formula
  35. hav = Location.haversine(b.latitude_radian - a.latitude_radian) \
  36. + math.cos(a.latitude_radian) * math.cos(b.latitude_radian) \
  37. * Location.haversine(b.longitude_radian - a.longitude_radian)
  38. return Location.haversine_inv(hav) * Location.EARTH_RADIUS_METRES
  39. class Position(Location):
  40. def __init__(self, timestamp, latitude, longitude, altitude):
  41. self.timestamp = timestamp
  42. super().__init__(
  43. latitude=latitude,
  44. longitude=longitude,
  45. altitude=altitude,
  46. )
  47. @classmethod
  48. def from_termux_location(cls, provider='network'):
  49. loc_json = subprocess.check_output(['termux-location', '-p', provider]) \
  50. .decode(sys.stdout.encoding)
  51. req_ts = dt.datetime.now(pytz.utc)
  52. loc_attr = yaml.load(loc_json)
  53. if not loc_attr:
  54. return None
  55. else:
  56. return cls(
  57. timestamp=req_ts -
  58. dt.timedelta(milliseconds=loc_attr['elapsedMs']),
  59. latitude=loc_attr['latitude'],
  60. longitude=loc_attr['longitude'],
  61. altitude=loc_attr['altitude'],
  62. )
  63. @classmethod
  64. def load_yaml(cls, path):
  65. name = os.path.basename(path)
  66. assert name.endswith('Z.yml'), path
  67. ts = pytz.utc.localize(dt.datetime.strptime(
  68. name[:-5],
  69. '%Y%m%dT%H%M%S.%f',
  70. ))
  71. with open(path, 'r') as f:
  72. attr = yaml.load(f.read())
  73. return cls(
  74. timestamp=ts,
  75. latitude=attr['lat'],
  76. longitude=attr['lon'],
  77. altitude=attr['alt'],
  78. )
  79. def save_yaml(self, target_dir_path):
  80. path = os.path.join(
  81. target_dir_path,
  82. '{}.yml'.format(to_iso6801_basic(self.timestamp)),
  83. )
  84. attr = {
  85. 'lat': self.latitude,
  86. 'lon': self.longitude,
  87. 'alt': self.altitude,
  88. }
  89. with open(path, 'w') as f:
  90. f.write(yaml.dump(attr, default_flow_style=False))
  91. return path