Files
vrobbler/vrobbler/apps/trails/models.py
Colin Powell cb50de13c0
All checks were successful
build & deploy / test (push) Successful in 1m51s
build & deploy / build-and-deploy (push) Successful in 32s
[trails] Add auto GPX and FIT file importing
2026-05-21 10:52:23 -04:00

105 lines
3.2 KiB
Python

from dataclasses import dataclass
from math import asin, cos, radians, sin, sqrt
from typing import Optional
from django.apps import apps
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from locations.models import GeoLocation
from scrobbles.dataclasses import BaseLogData, WithPeopleLogData
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
BNULL = {"blank": True, "null": True}
def haversine(lat1, lon1, lat2, lon2):
R = 6371000
dlat = radians(lat2 - lat1)
dlon = radians(lon2 - lon1)
a = sin(dlat / 2) ** 2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon / 2) ** 2
return R * 2 * asin(sqrt(a))
@dataclass
class TrailLogData(BaseLogData, WithPeopleLogData):
effort: Optional[str] = None
difficulty: Optional[str] = None
class Trail(ScrobblableMixin):
class PrincipalType(models.TextChoices):
WOODS = "WOODS"
ROAD = "ROAD"
BEACH = "BEACH"
MOUNTAIN = "MOUNTAIN"
class ActivityType(models.TextChoices):
WALK = "WALK"
HIKE = "HIKE"
RUN = "RUN"
BIKE = "BIKE"
description = models.TextField(**BNULL)
trailhead_location = models.ForeignKey(
GeoLocation,
related_name="trailheads",
on_delete=models.DO_NOTHING,
**BNULL,
)
trail_terminus_location = models.ForeignKey(
GeoLocation,
related_name="trail_termini",
on_delete=models.DO_NOTHING,
**BNULL,
)
strava_id = models.CharField(max_length=255, **BNULL)
trailforks_id = models.CharField(max_length=255, **BNULL)
alltrails_id = models.CharField(max_length=255, **BNULL)
gaiagps_id = models.CharField(max_length=255, **BNULL)
principal_type = models.CharField(
max_length=10, choices=PrincipalType.choices, **BNULL
)
default_activity_type = models.CharField(
max_length=10, choices=ActivityType.choices, **BNULL
)
def get_absolute_url(self):
return reverse("trails:trail_detail", kwargs={"slug": self.uuid})
@property
def logdata_cls(self):
return TrailLogData
@property
def strings(self) -> ScrobblableConstants:
return ScrobblableConstants(verb="Moving", tags="runner")
@classmethod
def find_or_create(cls, title: str) -> "Trail":
trail = cls.objects.filter(title__iexact=title).first()
if not trail:
trail = cls.objects.create(title=title)
return trail
@classmethod
def find_by_trailhead(cls, lat, lon, tolerance_m=100):
candidates = cls.objects.filter(
trailhead_location__isnull=False,
).select_related("trailhead_location")
best = None
best_dist = float("inf")
for trail in candidates:
loc = trail.trailhead_location
d = haversine(lat, lon, loc.lat, loc.lon)
if d < best_dist and d <= tolerance_m:
best = trail
best_dist = d
return best
def scrobbles(self, user_id):
Scrobble = apps.get_model("scrobbles", "Scrobble")
return Scrobble.objects.filter(user_id=user_id, life_event=self).order_by(
"-timestamp"
)