import json import gzip import random from datetime import datetime, timedelta import colorsys from math import radians, cos, sin, asin, sqrt from django.contrib.gis.geos import Point, LineString from ..models import Marker, Trip max_time_diff = timedelta(hours=6) max_distance = 3000 # m class TripConverter: trips: list[Trip] def __init__(self, markers: list[Marker]): self.trips = [] first_index = 0 for i, point in enumerate(markers[1:]): prev_point = markers[i-1] if point.timestamp - prev_point.timestamp > max_time_diff or \ Distance(point.location, prev_point.location) > max_distance: if i - first_index > 2: self.trips.append(create_trip(markers[first_index:i])) first_index = i if first_index < len(markers) - 2: self.trips.append(create_trip(markers[first_index:])) def save(self): for trip in self.trips: trip.save() def create_trip(markers: list[Marker]) -> Trip: print(len(markers), markers[0].timestamp, markers[-1].timestamp) trip = Trip.objects.filter(startTime__lte=markers[-1].timestamp, endTime__gte=markers[0].timestamp).first() if not trip: trip = Trip.objects.create( startTime = markers[0].timestamp, endTime = markers[-1].timestamp, name = f"Trip {markers[0].timestamp}", color = get_path_color(markers[0].timestamp) ) elif trip.startTime == markers[0].timestamp and trip.endTime == markers[-1].timestamp: data = json.loads(gzip.decompress(trip.path)) if len(data) > len(markers) * 0.9 and len(data) <= len(markers): print("Trip already exists") return trip trip.startTime = markers[0].timestamp trip.endTime = markers[-1].timestamp trip.totalTime = trip.endTime - trip.startTime center = markers[len(markers)//2].location trip.center = Point(center.x, center.y) total_distance = 0 # m topSpeed = 0 # km/h ascendHeight = 0 # m descendHeight = 0 # m movementTime = timedelta(0) minLat = minLon = maxLat = maxLon = None lastSpeed = 0 i = 1 while i < len(markers): point = markers[i] prev_point = markers[i-1] dist = Distance(point.location, prev_point.location) if point.speed is not None and point.speed > 0: speed = point.speed else: speed = dist / abs(point.timestamp - prev_point.timestamp).seconds * 3.6 if abs(speed - lastSpeed) / abs(point.timestamp - prev_point.timestamp).seconds > 10: # m/s² markers.remove(point) continue if abs(speed - lastSpeed) > 50: # m/s markers.remove(point) continue total_distance += dist topSpeed = max(topSpeed, speed) minLat = min(minLat, point.location.y) if minLat is not None else point.location.y minLon = min(minLon, point.location.x) if minLon is not None else point.location.x maxLat = max(maxLat, point.location.y) if maxLat is not None else point.location.y maxLon = max(maxLon, point.location.x) if maxLon is not None else point.location.x if speed > 2.0: # km/h movementTime += abs(point.timestamp - prev_point.timestamp) if point.alt is not None and prev_point.alt is not None: if point.alt > prev_point.alt: ascendHeight += point.alt - prev_point.alt else: descendHeight += prev_point.alt - point.alt i += 1 trip.distance = round(total_distance, 1) # m trip.topSpeed = round(topSpeed, 1) # km/h trip.avgSpeed = round(total_distance / (movementTime or trip.endTime - trip.startTime).total_seconds() * 3.6, 1) # km/h trip.ascendHeight = round(ascendHeight, 1) # m trip.descendHeight = round(descendHeight, 1) # m trip.movementTime = movementTime trip.line = LineString((markers[0].location.x, markers[0].location.y), (markers[-1].location.x, markers[-1].location.y)) trip.path = points_to_blob(markers) return trip def Distance(point1: Point, point2: Point) -> float: lon1 = radians(point1.x) lon2 = radians(point2.x) lat1 = radians(point1.y) lat2 = radians(point2.y) # Haversine formula dlon = lon2 - lon1 dlat = lat2 - lat1 a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 c = 2 * asin(sqrt(a)) r = 6371000 # Radius of earth in meters. Use 3956 for miles return c * r def get_path_color(time: datetime) -> str: random.seed(int(time.timestamp())) hue = random.random() saturation = 0.5 + random.random() / 2 value = 0.5 + random.random() / 2 rgb = colorsys.hsv_to_rgb(hue, saturation, value) return f"#{int(rgb[0]*255):02x}{int(rgb[1]*255):02x}{int(rgb[2]*255):02x}" def convert_points_to_trips(): points = list(Marker.objects.all()) converter = TripConverter(points) converter.save() def points_to_blob(markers) -> bytes: arr = [] for marker in markers: arr.append({ "lat": marker.location.y, "lng": marker.location.x, "alt": marker.alt, "hdop": marker.hdop, "speed": marker.speed, "timestamp": marker.timestamp.timestamp(), }) data = json.dumps(arr).encode('utf-8') return gzip.compress(data)