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.point import Point 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): # print("Trip already exists") # return trip trip.startTime = markers[0].timestamp trip.endTime = markers[-1].timestamp total_distance = 0 # m topSpeed = 0 # km/h ascendHeight = 0 # m descendHeight = 0 # m movementTime = timedelta(0) 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) 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.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)