models.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. from decimal import Decimal
  2. import logging
  3. from typing import Dict
  4. from uuid import uuid4
  5. from django.contrib.auth import get_user_model
  6. from django.conf import settings
  7. from django.db import models
  8. from django.urls import reverse
  9. from django_extensions.db.models import TimeStampedModel
  10. from scrobbles.mixins import ScrobblableMixin
  11. logger = logging.getLogger(__name__)
  12. BNULL = {"blank": True, "null": True}
  13. User = get_user_model()
  14. GEOLOC_ACCURACY = int(getattr(settings, "GEOLOC_ACCURACY", 4))
  15. GEOLOC_PROXIMITY = Decimal(getattr(settings, "GEOLOC_ACCURACY", "0.0001"))
  16. class GeoLocation(ScrobblableMixin):
  17. COMPLETION_PERCENT = getattr(settings, "LOCATION_COMPLETION_PERCENT", 100)
  18. uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
  19. lat = models.FloatField()
  20. lon = models.FloatField()
  21. truncated_lat = models.FloatField(**BNULL)
  22. truncated_lon = models.FloatField(**BNULL)
  23. altitude = models.FloatField(**BNULL)
  24. class Meta:
  25. unique_together = [["lat", "lon", "altitude"]]
  26. def __str__(self):
  27. if self.title:
  28. return self.title
  29. return f"{self.lat} x {self.lon}"
  30. def get_absolute_url(self):
  31. return reverse(
  32. "locations:geo_location_detail", kwargs={"slug": self.uuid}
  33. )
  34. @classmethod
  35. def find_or_create(cls, data_dict: Dict) -> "GeoLocation":
  36. """Given a data dict from GPSLogger, does the heavy lifting of looking up
  37. the location, creating if if doesn't exist yet.
  38. """
  39. # TODO Add constants for all these data keys
  40. if "lat" not in data_dict.keys() or "lon" not in data_dict.keys():
  41. logger.error("No lat or lon keys in data dict")
  42. return
  43. int_lat, r_lat = str(data_dict.get("lat", "")).split(".")
  44. int_lon, r_lon = str(data_dict.get("lon", "")).split(".")
  45. try:
  46. trunc_lat = r_lat[0:GEOLOC_ACCURACY]
  47. except IndexError:
  48. trunc_lat = r_lat
  49. try:
  50. trunc_lon = r_lon[0:GEOLOC_ACCURACY]
  51. except IndexError:
  52. trunc_lon = r_lon
  53. data_dict["lat"] = float(f"{int_lat}.{trunc_lat}")
  54. data_dict["lon"] = float(f"{int_lon}.{trunc_lon}")
  55. int_alt, r_alt = str(data_dict.get("alt", "")).split(".")
  56. data_dict["altitude"] = float(int_alt)
  57. location = cls.objects.filter(
  58. lat=data_dict.get("lat"),
  59. lon=data_dict.get("lon"),
  60. ).first()
  61. if not location:
  62. location = cls.objects.create(
  63. lat=data_dict.get("lat"),
  64. lon=data_dict.get("lon"),
  65. altitude=data_dict.get("altitude"),
  66. )
  67. return location
  68. def loc_diff(self, old_lat_lon: tuple) -> tuple:
  69. return (
  70. abs(Decimal(old_lat_lon[0]) - Decimal(self.lat)),
  71. abs(Decimal(old_lat_lon[1]) - Decimal(self.lon)),
  72. )
  73. def has_moved(self, past_points: list["GeoLocation"]) -> bool:
  74. has_moved = False
  75. for point in past_points:
  76. loc_diff = self.loc_diff((point.lat, point.lon))
  77. logger.info(f"[locations] {self} is {loc_diff} from {point}")
  78. if (
  79. loc_diff[0] < GEOLOC_PROXIMITY
  80. or loc_diff[1] < GEOLOC_PROXIMITY
  81. ):
  82. logger.info(
  83. f"[locations] {loc_diff} is less than proximity setting {GEOLOC_PROXIMITY}, we've moved"
  84. )
  85. has_moved = True
  86. return has_moved
  87. def named_in_proximity(self):
  88. lat_min = Decimal(self.lat) - GEOLOC_PROXIMITY
  89. lat_max = Decimal(self.lat) + GEOLOC_PROXIMITY
  90. lon_min = Decimal(self.lon) - GEOLOC_PROXIMITY
  91. lon_max = Decimal(self.lon) + GEOLOC_PROXIMITY
  92. return GeoLocation.objects.filter(
  93. title__isnull=False,
  94. lat__lte=lat_max,
  95. lat__gte=lat_min,
  96. lon__lte=lon_max,
  97. lon__gte=lon_min,
  98. ).first()
  99. class RawGeoLocation(TimeStampedModel):
  100. user = models.ForeignKey(User, on_delete=models.CASCADE)
  101. lat = models.FloatField()
  102. lon = models.FloatField()
  103. altitude = models.FloatField(**BNULL)
  104. speed = models.FloatField(**BNULL)
  105. timestamp = models.DateTimeField(**BNULL)