from django.contrib.gis.db import models as gis_models from django.db import models from django.contrib.gis.geos import Point import pghistory @pghistory.track() class RideLocation(models.Model): """ Lightweight location tracking for individual rides within parks. Optional coordinates with focus on practical navigation information. """ # Relationships ride = models.OneToOneField( "rides.Ride", on_delete=models.CASCADE, related_name="ride_location" ) # Optional Spatial Data - keep it simple with single point point = gis_models.PointField( srid=4326, null=True, blank=True, help_text="Geographic coordinates for ride location (longitude, latitude)", ) # Park Area Information park_area = models.CharField( max_length=100, blank=True, db_index=True, help_text=( "Themed area or land within the park (e.g., 'Frontierland', 'Tomorrowland')" ), ) # General notes field to match database schema notes = models.TextField(blank=True, help_text="General location notes") # Navigation and Entrance Information entrance_notes = models.TextField( blank=True, help_text="Directions to ride entrance, queue location, or navigation tips", ) # Accessibility Information accessibility_notes = models.TextField( blank=True, help_text="Information about accessible entrances, wheelchair access, etc.", ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @property def latitude(self): """Return latitude from point field for backward compatibility.""" if self.point: return self.point.y return None @property def longitude(self): """Return longitude from point field for backward compatibility.""" if self.point: return self.point.x return None @property def coordinates(self): """Return (latitude, longitude) tuple.""" if self.point: return (self.latitude, self.longitude) return (None, None) @property def has_coordinates(self): """Check if coordinates are set.""" return self.point is not None def set_coordinates(self, latitude, longitude): """ Set the location's point from latitude and longitude coordinates. Validates coordinate ranges. """ if latitude is None or longitude is None: self.point = None return if not -90 <= latitude <= 90: raise ValueError("Latitude must be between -90 and 90.") if not -180 <= longitude <= 180: raise ValueError("Longitude must be between -180 and 180.") self.point = Point(longitude, latitude, srid=4326) def distance_to_park_location(self): """ Calculate distance to parent park's location if both have coordinates. Returns distance in kilometers. """ if not self.point: return None park_location = getattr(self.ride.park, "location", None) if not park_location or not park_location.point: return None # Use geodetic distance calculation which returns meters, convert to km distance_m = self.point.distance(park_location.point) return distance_m / 1000.0 def __str__(self): area_str = f" in {self.park_area}" if self.park_area else "" return f"Location for {self.ride.name}{area_str}" class Meta: verbose_name = "Ride Location" verbose_name_plural = "Ride Locations" ordering = ["ride__name"] indexes = [ models.Index(fields=["park_area"]), # Spatial index will be created automatically for PostGIS # PointField ]