points2trips.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import os
  2. import json
  3. import gzip
  4. import random
  5. from datetime import datetime, timedelta
  6. import colorsys
  7. from math import radians, cos, sin, asin, sqrt
  8. from django.contrib.gis.geos import Point, LineString
  9. import requests
  10. from ..models import Marker, Trip
  11. from website.settings import VIRTUALEARTH_API_KEY
  12. max_time_diff = timedelta(hours=6)
  13. max_distance = 3000 # m
  14. class TripConverter:
  15. trips: list[Trip]
  16. def __init__(self, markers: list[Marker]):
  17. self.trips = []
  18. first_index = 0
  19. for i, point in enumerate(markers[1:]):
  20. prev_point = markers[i-1]
  21. if point.timestamp - prev_point.timestamp > max_time_diff or \
  22. Distance(point.location, prev_point.location) > max_distance:
  23. if i - first_index > 2:
  24. self.trips.append(create_trip(markers[first_index:i]))
  25. first_index = i
  26. if first_index < len(markers) - 2:
  27. self.trips.append(create_trip(markers[first_index:]))
  28. def save(self):
  29. for trip in self.trips:
  30. trip.save()
  31. def create_trip(markers: list[Marker]) -> Trip:
  32. print(len(markers), markers[0].timestamp, markers[-1].timestamp)
  33. trip = Trip.objects.filter(startTime__lte=markers[-1].timestamp, endTime__gte=markers[0].timestamp).first()
  34. if not trip:
  35. trip = Trip.objects.create(
  36. startTime = markers[0].timestamp,
  37. endTime = markers[-1].timestamp,
  38. name = f"Trip {markers[0].timestamp}",
  39. color = get_path_color(markers[0].timestamp)
  40. )
  41. elif trip.startTime == markers[0].timestamp and trip.endTime == markers[-1].timestamp:
  42. print("Trip already exists")
  43. return trip
  44. if not trip.name or "None" in trip.name:
  45. start = get_location_name(markers[0].location)
  46. end = get_location_name(markers[-1].location)
  47. if start != end:
  48. trip.name = f"{start} - {end}"
  49. else:
  50. trip.name = start
  51. trip.startTime = markers[0].timestamp
  52. trip.endTime = markers[-1].timestamp
  53. trip.totalTime = trip.endTime - trip.startTime
  54. center = markers[len(markers)//2].location
  55. trip.center = Point(center.x, center.y)
  56. total_distance = 0 # m
  57. topSpeed = 0 # km/h
  58. ascendHeight = 0 # m
  59. descendHeight = 0 # m
  60. movementTime = timedelta(0)
  61. minLat = minLon = maxLat = maxLon = None
  62. lastSpeed = 0
  63. i = 1
  64. while i < len(markers):
  65. point = markers[i]
  66. prev_point = markers[i-1]
  67. dist = Distance(point.location, prev_point.location)
  68. if point.speed is not None and point.speed > 0:
  69. speed = point.speed
  70. else:
  71. speed = dist / abs(point.timestamp - prev_point.timestamp).seconds * 3.6
  72. if abs(speed - lastSpeed) / abs(point.timestamp - prev_point.timestamp).seconds > 10: # m/s²
  73. markers.remove(point)
  74. continue
  75. if abs(speed - lastSpeed) > 50: # m/s
  76. markers.remove(point)
  77. continue
  78. total_distance += dist
  79. topSpeed = max(topSpeed, speed)
  80. minLat = min(minLat, point.location.y) if minLat is not None else point.location.y
  81. minLon = min(minLon, point.location.x) if minLon is not None else point.location.x
  82. maxLat = max(maxLat, point.location.y) if maxLat is not None else point.location.y
  83. maxLon = max(maxLon, point.location.x) if maxLon is not None else point.location.x
  84. if speed > 2.0: # km/h
  85. movementTime += abs(point.timestamp - prev_point.timestamp)
  86. if point.alt is not None and prev_point.alt is not None:
  87. if point.alt > prev_point.alt:
  88. ascendHeight += point.alt - prev_point.alt
  89. else:
  90. descendHeight += prev_point.alt - point.alt
  91. i += 1
  92. trip.distance = round(total_distance, 1) # m
  93. trip.topSpeed = round(topSpeed, 1) # km/h
  94. trip.avgSpeed = round(total_distance / (movementTime or trip.endTime - trip.startTime).total_seconds() * 3.6, 1) # km/h
  95. trip.ascendHeight = round(ascendHeight, 1) # m
  96. trip.descendHeight = round(descendHeight, 1) # m
  97. trip.movementTime = movementTime
  98. trip.line = LineString((markers[0].location.x, markers[0].location.y), (markers[-1].location.x, markers[-1].location.y))
  99. trip.path = points_to_blob(markers)
  100. return trip
  101. def Distance(point1: Point, point2: Point) -> float:
  102. lon1 = radians(point1.x)
  103. lon2 = radians(point2.x)
  104. lat1 = radians(point1.y)
  105. lat2 = radians(point2.y)
  106. # Haversine formula
  107. dlon = lon2 - lon1
  108. dlat = lat2 - lat1
  109. a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
  110. c = 2 * asin(sqrt(a))
  111. r = 6371000 # Radius of earth in meters. Use 3956 for miles
  112. return c * r
  113. def get_location_name(point: Point) -> str:
  114. url = f"https://dev.virtualearth.net/REST/v1/LocationRecog/{round(point.y, 6)},{round(point.x, 6)}?radius=2&top=1&c=de-DE&includeEntityTypes=address&key={VIRTUALEARTH_API_KEY}&output=json"
  115. response = requests.get(url)
  116. if response.status_code == 200:
  117. data = response.json()
  118. if data["resourceSets"][0]["estimatedTotal"] > 0:
  119. address = data["resourceSets"][0]["resources"][0]["addressOfLocation"][0]
  120. if address["locality"] and address["neighborhood"]:
  121. txt = f"{address['locality']} {address['neighborhood']}"
  122. else:
  123. txt = address["adminDivision"]
  124. if address["countryIso2"]:
  125. txt += f" {address['countryIso2']}"
  126. return txt
  127. return None
  128. def get_path_color(time: datetime) -> str:
  129. random.seed(int(time.timestamp()))
  130. hue = random.random()
  131. saturation = 0.5 + random.random() / 2
  132. value = 0.5 + random.random() / 2
  133. rgb = colorsys.hsv_to_rgb(hue, saturation, value)
  134. return f"#{int(rgb[0]*255):02x}{int(rgb[1]*255):02x}{int(rgb[2]*255):02x}"
  135. def convert_points_to_trips():
  136. points = list(Marker.objects.all())
  137. converter = TripConverter(points)
  138. converter.save()
  139. def points_to_blob(markers) -> bytes:
  140. arr = []
  141. for marker in markers:
  142. arr.append({
  143. "lat": marker.location.y,
  144. "lng": marker.location.x,
  145. "alt": marker.alt,
  146. "hdop": marker.hdop,
  147. "speed": marker.speed,
  148. "timestamp": marker.timestamp.timestamp(),
  149. })
  150. data = json.dumps(arr).encode('utf-8')
  151. return gzip.compress(data)