Files
thrillwiki_django_no_react/memory-bank/documentation/location_model_design.md
2025-08-15 12:24:20 -04:00

8.2 KiB

Location Model Design Document

ParkLocation Model

from django.contrib.gis.db import models as gis_models
from django.db import models
from parks.models import Park

class ParkLocation(models.Model):
    park = models.OneToOneField(
        Park,
        on_delete=models.CASCADE,
        related_name='location'
    )
    
    # Geographic coordinates
    point = gis_models.PointField(
        srid=4326,  # WGS84 coordinate system
        null=True,
        blank=True,
        help_text="Geographic coordinates as a Point"
    )
    
    # Address components
    street_address = models.CharField(max_length=255, blank=True, null=True)
    city = models.CharField(max_length=100, blank=True, null=True)
    state = models.CharField(max_length=100, blank=True, null=True, help_text="State/Region/Province")
    country = models.CharField(max_length=100, blank=True, null=True)
    postal_code = models.CharField(max_length=20, blank=True, null=True)
    
    # Road trip metadata
    highway_exit = models.CharField(
        max_length=100, 
        blank=True, 
        null=True,
        help_text="Nearest highway exit (e.g., 'Exit 42')"
    )
    parking_notes = models.TextField(
        blank=True,
        null=True,
        help_text="Parking information and tips"
    )
    
    # OSM integration
    osm_id = models.BigIntegerField(
        blank=True,
        null=True,
        help_text="OpenStreetMap ID for this location"
    )
    osm_data = models.JSONField(
        blank=True,
        null=True,
        help_text="Raw OSM data snapshot"
    )
    
    class Meta:
        indexes = [
            models.Index(fields=['city']),
            models.Index(fields=['state']),
            models.Index(fields=['country']),
            models.Index(fields=['city', 'state']),
        ]
        # Spatial index will be created automatically by PostGIS
    
    def __str__(self):
        return f"{self.park.name} Location"
    
    @property
    def coordinates(self):
        """Returns coordinates as a tuple (latitude, longitude)"""
        if self.point:
            return (self.point.y, self.point.x)
        return None
    
    def get_formatted_address(self):
        """Returns a formatted address string"""
        components = []
        if self.street_address:
            components.append(self.street_address)
        if self.city:
            components.append(self.city)
        if self.state:
            components.append(self.state)
        if self.postal_code:
            components.append(self.postal_code)
        if self.country:
            components.append(self.country)
        return ", ".join(components) if components else ""

RideLocation Model

from django.contrib.gis.db import models as gis_models
from django.db import models
from parks.models import ParkArea
from rides.models import Ride

class RideLocation(models.Model):
    ride = models.OneToOneField(
        Ride,
        on_delete=models.CASCADE,
        related_name='location'
    )
    
    # Optional coordinates
    point = gis_models.PointField(
        srid=4326,
        null=True,
        blank=True,
        help_text="Precise ride location within park"
    )
    
    # Park area reference
    park_area = models.ForeignKey(
        ParkArea,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='ride_locations'
    )
    
    class Meta:
        indexes = [
            models.Index(fields=['park_area']),
        ]
    
    def __str__(self):
        return f"{self.ride.name} Location"
    
    @property
    def coordinates(self):
        """Returns coordinates as a tuple (latitude, longitude) if available"""
        if self.point:
            return (self.point.y, self.point.x)
        return None

CompanyHeadquarters Model

from django.db import models
from parks.models import Company

class CompanyHeadquarters(models.Model):
    company = models.OneToOneField(
        Company,
        on_delete=models.CASCADE,
        related_name='headquarters'
    )
    
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=100, help_text="State/Region/Province")
    
    class Meta:
        verbose_name_plural = "Company headquarters"
        indexes = [
            models.Index(fields=['city']),
            models.Index(fields=['state']),
            models.Index(fields=['city', 'state']),
        ]
    
    def __str__(self):
        return f"{self.company.name} Headquarters"

Shared Functionality Protocol

from typing import Protocol, Optional, Tuple

class LocationProtocol(Protocol):
    def get_coordinates(self) -> Optional[Tuple[float, float]]:
        """Get coordinates as (latitude, longitude) tuple"""
        ...
    
    def get_location_name(self) -> str:
        """Get human-readable location name"""
        ...
    
    def distance_to(self, other: 'LocationProtocol') -> Optional[float]:
        """Calculate distance to another location in meters"""
        ...

Index Strategy

  1. ParkLocation:

    • Spatial index on point (PostGIS GiST index)
    • Standard indexes on city, state, country
    • Composite index on (city, state) for common queries
    • Index on highway_exit for road trip searches
  2. RideLocation:

    • Spatial index on point (PostGIS GiST index)
    • Index on park_area for area-based queries
  3. CompanyHeadquarters:

    • Index on city
    • Index on state
    • Composite index on (city, state)

OSM Integration Plan

  1. Data Collection:

    • Store OSM ID in ParkLocation.osm_id
    • Cache raw OSM data in ParkLocation.osm_data
  2. Geocoding:

    • Implement Nominatim geocoding service
    • Create management command to geocode existing parks
    • Add geocoding on ParkLocation save
  3. Road Trip Metadata:

    • Map OSM highway data to highway_exit field
    • Extract parking information to parking_notes

Migration Strategy

Phase 1: Add New Models

  1. Create new models (ParkLocation, RideLocation, CompanyHeadquarters)
  2. Generate migrations
  3. Deploy to production

Phase 2: Data Migration

  1. Migrate existing Location data:

    for park in Park.objects.all():
        if park.location.exists():
            loc = park.location.first()
            ParkLocation.objects.create(
                park=park,
                point=loc.point,
                street_address=loc.street_address,
                city=loc.city,
                state=loc.state,
                country=loc.country,
                postal_code=loc.postal_code
            )
    
  2. Migrate company headquarters:

    for company in Company.objects.exclude(headquarters=''):
        city, state = parse_headquarters(company.headquarters)
        CompanyHeadquarters.objects.create(
            company=company,
            city=city,
            state=state
        )
    

Phase 3: Update References

  1. Update Park model to use ParkLocation
  2. Update Ride model to use RideLocation
  3. Update Company model to use CompanyHeadquarters
  4. Remove old Location model

Phase 4: OSM Integration

  1. Implement geocoding command
  2. Run geocoding for all ParkLocations
  3. Extract road trip metadata from OSM data

Relationship Diagram

classDiagram
    Park "1" --> "1" ParkLocation
    Ride "1" --> "1" RideLocation
    Company "1" --> "1" CompanyHeadquarters
    RideLocation "1" --> "0..1" ParkArea
    
    class Park {
        +name: str
    }
    
    class ParkLocation {
        +point: Point
        +street_address: str
        +city: str
        +state: str
        +country: str
        +postal_code: str
        +highway_exit: str
        +parking_notes: str
        +osm_id: int
        +get_coordinates()
        +get_formatted_address()
    }
    
    class Ride {
        +name: str
    }
    
    class RideLocation {
        +point: Point
        +get_coordinates()
    }
    
    class Company {
        +name: str
    }
    
    class CompanyHeadquarters {
        +city: str
        +state: str
    }
    
    class ParkArea {
        +name: str
    }

Rollout Timeline

  1. Week 1: Implement models and migrations
  2. Week 2: Migrate data in staging environment
  3. Week 3: Deploy to production, migrate data
  4. Week 4: Implement OSM integration
  5. Week 5: Optimize queries and indexes