105 lines
3.2 KiB
Python
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"
|
|
)
|