points2trips.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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. center = markers[len(markers)//2].location
  35. if not trip:
  36. trip = Trip.objects.create(
  37. startTime = markers[0].timestamp,
  38. endTime = markers[-1].timestamp,
  39. name = f"Trip {markers[0].timestamp}",
  40. color = get_path_color(markers[0].timestamp),
  41. center = Point(center.x, center.y)
  42. )
  43. elif trip.startTime == markers[0].timestamp and trip.endTime == markers[-1].timestamp:
  44. print("Trip already exists")
  45. return trip
  46. if not trip.name or "None" in trip.name or trip.name.startswith("Trip "):
  47. start = get_location_name(markers[0].location)
  48. end = get_location_name(markers[-1].location)
  49. if start != end:
  50. trip.name = f"{start} - {end}"
  51. else:
  52. trip.name = start
  53. trip.startTime = markers[0].timestamp
  54. trip.endTime = markers[-1].timestamp
  55. trip.totalTime = trip.endTime - trip.startTime
  56. trip.center = Point(center.x, center.y)
  57. total_distance = 0 # m
  58. topSpeed = 0 # km/h
  59. ascendHeight = 0 # m
  60. descendHeight = 0 # m
  61. movementTime = timedelta(0)
  62. minLat = minLon = maxLat = maxLon = None
  63. lastSpeed = 0
  64. i = 1
  65. while i < len(markers):
  66. point = markers[i]
  67. prev_point = markers[i-1]
  68. dist = Distance(point.location, prev_point.location)
  69. if point.speed is not None and point.speed > 0:
  70. speed = point.speed
  71. else:
  72. speed = dist / abs(point.timestamp - prev_point.timestamp).seconds * 3.6
  73. if abs(speed - lastSpeed) / abs(point.timestamp - prev_point.timestamp).seconds > 10: # m/s²
  74. markers.remove(point)
  75. continue
  76. if abs(speed - lastSpeed) > 50: # m/s
  77. markers.remove(point)
  78. continue
  79. total_distance += dist
  80. topSpeed = max(topSpeed, speed)
  81. minLat = min(minLat, point.location.y) if minLat is not None else point.location.y
  82. minLon = min(minLon, point.location.x) if minLon is not None else point.location.x
  83. maxLat = max(maxLat, point.location.y) if maxLat is not None else point.location.y
  84. maxLon = max(maxLon, point.location.x) if maxLon is not None else point.location.x
  85. if speed > 2.0: # km/h
  86. movementTime += abs(point.timestamp - prev_point.timestamp)
  87. if point.alt is not None and prev_point.alt is not None:
  88. if point.alt > prev_point.alt:
  89. ascendHeight += point.alt - prev_point.alt
  90. else:
  91. descendHeight += prev_point.alt - point.alt
  92. i += 1
  93. trip.distance = round(total_distance, 1) # m
  94. trip.topSpeed = round(topSpeed, 1) # km/h
  95. trip.avgSpeed = round(total_distance / (movementTime or trip.endTime - trip.startTime).total_seconds() * 3.6, 1) # km/h
  96. trip.ascendHeight = round(ascendHeight, 1) # m
  97. trip.descendHeight = round(descendHeight, 1) # m
  98. trip.movementTime = movementTime
  99. trip.line = LineString((markers[0].location.x, markers[0].location.y), (markers[-1].location.x, markers[-1].location.y))
  100. trip.path = points_to_blob(markers)
  101. return trip
  102. def Distance(point1: Point, point2: Point) -> float:
  103. lon1 = radians(point1.x)
  104. lon2 = radians(point2.x)
  105. lat1 = radians(point1.y)
  106. lat2 = radians(point2.y)
  107. # Haversine formula
  108. dlon = lon2 - lon1
  109. dlat = lat2 - lat1
  110. a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
  111. c = 2 * asin(sqrt(a))
  112. r = 6371000 # Radius of earth in meters. Use 3956 for miles
  113. return c * r
  114. def get_location_name(point: Point) -> str:
  115. 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"
  116. response = requests.get(url)
  117. if response.status_code == 200:
  118. data = response.json()
  119. if data["resourceSets"][0]["estimatedTotal"] > 0:
  120. address = data["resourceSets"][0]["resources"][0]["addressOfLocation"][0]
  121. if address["locality"] and address["neighborhood"]:
  122. txt = f"{address['locality']} {address['neighborhood']}"
  123. else:
  124. txt = address["adminDivision"]
  125. if address["countryIso2"]:
  126. txt += f" {address['countryIso2']}"
  127. return txt
  128. return None
  129. def get_path_color(time: datetime) -> str:
  130. random.seed(int(time.timestamp()))
  131. hue = random.random()
  132. saturation = 0.5 + random.random() / 2
  133. value = 0.5 + random.random() / 2
  134. rgb = colorsys.hsv_to_rgb(hue, saturation, value)
  135. return f"#{int(rgb[0]*255):02x}{int(rgb[1]*255):02x}{int(rgb[2]*255):02x}"
  136. def convert_points_to_trips():
  137. points = list(Marker.objects.all())
  138. converter = TripConverter(points)
  139. converter.save()
  140. def points_to_blob(markers) -> bytes:
  141. arr = []
  142. for marker in markers:
  143. arr.append({
  144. "lat": marker.location.y,
  145. "lng": marker.location.x,
  146. "alt": marker.alt,
  147. "hdop": marker.hdop,
  148. "speed": marker.speed,
  149. "timestamp": marker.timestamp.timestamp(),
  150. })
  151. data = json.dumps(arr).encode('utf-8')
  152. return gzip.compress(data)