mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 03:31:08 -05:00
392 lines
12 KiB
Python
392 lines
12 KiB
Python
"""
|
||
Demonstration script showing practical usage of the RoadTripService.
|
||
|
||
This script demonstrates real-world scenarios for using the OSM Road Trip Service
|
||
in the ThrillWiki application.
|
||
"""
|
||
|
||
from apps.parks.models import Park
|
||
from apps.parks.services import RoadTripService
|
||
import os
|
||
import django
|
||
|
||
# Setup Django
|
||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thrillwiki.settings")
|
||
django.setup()
|
||
|
||
# New small helpers and constant to simplify functions and avoid repeated literals
|
||
MAGIC_KINGDOM = "Magic Kingdom"
|
||
|
||
|
||
def _format_coords(coords):
|
||
"""
|
||
Return (lat, lon) tuple or None for a coords object/sequence.
|
||
Accepts objects with .latitude/.longitude or indexable (lat, lon).
|
||
"""
|
||
if not coords:
|
||
return None
|
||
lat = getattr(coords, "latitude", None)
|
||
lon = getattr(coords, "longitude", None)
|
||
if lat is not None and lon is not None:
|
||
return (lat, lon)
|
||
# Fallback to indexable
|
||
try:
|
||
return (coords[0], coords[1])
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def _print_route_summary(route, indent=" "):
|
||
"""Safely print route summary fields if route is present."""
|
||
if not route:
|
||
return
|
||
# Use attributes with fallback to dict keys if needed
|
||
formatted_distance = getattr(route, "formatted_distance", None) or route.get(
|
||
"formatted_distance", "N/A"
|
||
) if isinstance(route, dict) else getattr(route, "formatted_distance", "N/A")
|
||
formatted_duration = getattr(route, "formatted_duration", None) or route.get(
|
||
"formatted_duration", "N/A"
|
||
) if isinstance(route, dict) else getattr(route, "formatted_duration", "N/A")
|
||
print(f"{indent}{formatted_distance}, {formatted_duration}")
|
||
|
||
|
||
def demo_florida_theme_park_trip():
|
||
"""
|
||
Demonstrate planning a Florida theme park road trip.
|
||
"""
|
||
print("🏖️ Florida Theme Park Road Trip Planner")
|
||
print("=" * 50)
|
||
|
||
service = RoadTripService()
|
||
|
||
# Define Florida theme parks with addresses
|
||
florida_parks = [
|
||
(MAGIC_KINGDOM, "Magic Kingdom Dr, Orlando, FL 32830"),
|
||
("Universal Studios Florida", "6000 Universal Blvd, Orlando, FL 32819"),
|
||
("SeaWorld Orlando", "7007 Sea World Dr, Orlando, FL 32821"),
|
||
("Busch Gardens Tampa", "10165 McKinley Dr, Tampa, FL 33612"),
|
||
]
|
||
|
||
print("Planning trip for these Florida parks:")
|
||
park_coords = {}
|
||
|
||
# small helper to geocode and store
|
||
def _geocode_and_store(name, address):
|
||
print(f"\n📍 Geocoding {name}...")
|
||
coords = service.geocode_address(address)
|
||
if coords:
|
||
latlon = _format_coords(coords)
|
||
if latlon:
|
||
park_coords[name] = coords
|
||
print(f" ✅ Located at {latlon[0]:.4f}, {latlon[1]:.4f}")
|
||
return True
|
||
print(f" ❌ Could not geocode {address}")
|
||
return False
|
||
|
||
for name, address in florida_parks:
|
||
_geocode_and_store(name, address)
|
||
|
||
if len(park_coords) < 2:
|
||
print("❌ Need at least 2 parks to plan a trip")
|
||
return
|
||
|
||
# Calculate distances between all parks
|
||
print("\n🗺️ Distance Matrix:")
|
||
park_names = list(park_coords.keys())
|
||
|
||
for i, park1 in enumerate(park_names):
|
||
for j, park2 in enumerate(park_names):
|
||
if i < j: # Only calculate each pair once
|
||
route = service.calculate_route(park_coords[park1], park_coords[park2])
|
||
if route:
|
||
print(f" {park1} ↔ {park2}")
|
||
_print_route_summary(route, indent=" ")
|
||
|
||
# Find central park for radiating searches
|
||
print(f"\n🎢 Parks within 100km of {MAGIC_KINGDOM}:")
|
||
magic_kingdom_coords = park_coords.get(MAGIC_KINGDOM)
|
||
if magic_kingdom_coords:
|
||
for name, coords in park_coords.items():
|
||
if name != MAGIC_KINGDOM:
|
||
route = service.calculate_route(magic_kingdom_coords, coords)
|
||
if route:
|
||
_print_route_summary(route, indent=f" {name}: ")
|
||
|
||
|
||
def demo_cross_country_road_trip():
|
||
"""
|
||
Demonstrate planning a cross-country theme park road trip.
|
||
"""
|
||
print("\n\n🇺🇸 Cross-Country Theme Park Road Trip")
|
||
print("=" * 50)
|
||
|
||
service = RoadTripService()
|
||
|
||
# Major theme parks across the US
|
||
major_parks = [
|
||
("Disneyland", "1313 Disneyland Dr, Anaheim, CA 92802"),
|
||
("Cedar Point", "1 Cedar Point Dr, Sandusky, OH 44870"),
|
||
("Six Flags Magic Mountain", "26101 Magic Mountain Pkwy, Valencia, CA 91355"),
|
||
("Walt Disney World", "Walt Disney World Resort, Orlando, FL 32830"),
|
||
]
|
||
|
||
print("Geocoding major US theme parks:")
|
||
park_coords = {}
|
||
|
||
for name, address in major_parks:
|
||
print(f"\n📍 {name}...")
|
||
coords = service.geocode_address(address)
|
||
if coords:
|
||
park_coords[name] = coords
|
||
latlon = _format_coords(coords)
|
||
if latlon:
|
||
print(f" ✅ {latlon[0]:.4f}, {latlon[1]:.4f}")
|
||
|
||
if len(park_coords) >= 3:
|
||
# Calculate an optimized route if we have DB parks
|
||
print("\n🛣️ Optimized Route Planning:")
|
||
print("Note: This would work with actual Park objects from the database")
|
||
|
||
# Show distances for a potential route
|
||
route_order = [
|
||
"Disneyland",
|
||
"Six Flags Magic Mountain",
|
||
"Cedar Point",
|
||
"Walt Disney World",
|
||
]
|
||
total_distance = 0
|
||
total_time = 0
|
||
|
||
for i in range(len(route_order) - 1):
|
||
from_park = route_order[i]
|
||
to_park = route_order[i + 1]
|
||
|
||
if from_park in park_coords and to_park in park_coords:
|
||
route = service.calculate_route(park_coords[from_park], park_coords[to_park])
|
||
if route:
|
||
total_distance += getattr(route, "distance_km", 0) or route.get("distance_km", 0) if isinstance(route, dict) else getattr(route, "distance_km", 0)
|
||
total_time += getattr(route, "duration_minutes", 0) or route.get("duration_minutes", 0) if isinstance(route, dict) else getattr(route, "duration_minutes", 0)
|
||
print(f" {i + 1}. {from_park} → {to_park}")
|
||
_print_route_summary(route, indent=" ")
|
||
|
||
print("\n📊 Trip Summary:")
|
||
print(f" Total Distance: {total_distance:.1f}km")
|
||
hours = total_time // 60
|
||
mins = total_time % 60
|
||
print(f" Total Driving Time: {hours}h {mins}min")
|
||
# avoid division by zero
|
||
legs = max(1, len(route_order) - 1)
|
||
print(f" Average Distance per Leg: {total_distance / legs:.1f}km")
|
||
|
||
|
||
def demo_database_integration():
|
||
"""
|
||
Demonstrate working with actual parks from the database.
|
||
"""
|
||
print("\n\n🗄️ Database Integration Demo")
|
||
print("=" * 50)
|
||
|
||
service = RoadTripService()
|
||
|
||
# Get parks that have location data
|
||
parks_with_location = Park.objects.filter(location__point__isnull=False).select_related("location")[:5]
|
||
|
||
if not parks_with_location:
|
||
print("❌ No parks with location data found in database")
|
||
return
|
||
|
||
print(f"Found {len(parks_with_location)} parks with location data:")
|
||
|
||
for park in parks_with_location:
|
||
coords = getattr(park, "coordinates", None)
|
||
latlon = _format_coords(coords)
|
||
if latlon:
|
||
print(f" 🎢 {park.name}: {latlon[0]:.4f}, {latlon[1]:.4f}")
|
||
|
||
# Demonstrate nearby park search
|
||
if len(parks_with_location) >= 1:
|
||
center_park = parks_with_location[0]
|
||
print(f"\n🔍 Finding parks within 500km of {center_park.name}:")
|
||
|
||
nearby_parks = service.get_park_distances(center_park, radius_km=500)
|
||
|
||
if nearby_parks:
|
||
print(f" Found {len(nearby_parks)} nearby parks:")
|
||
for result in nearby_parks[:3]: # Show top 3
|
||
park = result.get("park") if isinstance(result, dict) else getattr(result, "park", None)
|
||
# use safe formatted strings
|
||
formatted_distance = result.get("formatted_distance", "N/A") if isinstance(result, dict) else getattr(result, "formatted_distance", "N/A")
|
||
formatted_duration = result.get("formatted_duration", "N/A") if isinstance(result, dict) else getattr(result, "formatted_duration", "N/A")
|
||
if park:
|
||
print(f" 📍 {park.name}: {formatted_distance} ({formatted_duration})")
|
||
else:
|
||
print(" No nearby parks found (may need larger radius)")
|
||
|
||
# Demonstrate multi-park trip planning
|
||
if len(parks_with_location) >= 3:
|
||
selected_parks = list(parks_with_location)[:3]
|
||
print("\n🗺️ Planning optimized trip for 3 parks:")
|
||
|
||
for park in selected_parks:
|
||
print(f" - {park.name}")
|
||
|
||
trip = service.create_multi_park_trip(selected_parks)
|
||
|
||
if trip:
|
||
print("\n✅ Optimized Route:")
|
||
print(f" Total Distance: {getattr(trip, 'formatted_total_distance', 'N/A')}")
|
||
print(f" Total Duration: {getattr(trip, 'formatted_total_duration', 'N/A')}")
|
||
print(" Route:")
|
||
|
||
for i, leg in enumerate(getattr(trip, "legs", []) or [], 1):
|
||
from_park = getattr(leg, "from_park", None)
|
||
to_park = getattr(leg, "to_park", None)
|
||
route = getattr(leg, "route", None)
|
||
if from_park and to_park:
|
||
print(f" {i}. {from_park.name} → {to_park.name}")
|
||
_print_route_summary(route, indent=" ")
|
||
else:
|
||
print(" ❌ Could not optimize trip route")
|
||
|
||
|
||
def demo_geocoding_fallback():
|
||
"""
|
||
Demonstrate geocoding parks that don't have coordinates.
|
||
"""
|
||
print("\n\n🌍 Geocoding Demo")
|
||
print("=" * 50)
|
||
|
||
service = RoadTripService()
|
||
|
||
# Get parks without location data
|
||
parks_without_coords = Park.objects.filter(location__point__isnull=True).select_related("location")[:3]
|
||
|
||
if not parks_without_coords:
|
||
print("✅ All parks already have coordinates")
|
||
return
|
||
|
||
print(f"Found {len(parks_without_coords)} parks without coordinates:")
|
||
|
||
for park in parks_without_coords:
|
||
print(f"\n🎢 {park.name}")
|
||
|
||
location = getattr(park, "location", None)
|
||
if location:
|
||
# use getattr to avoid attribute errors
|
||
address_parts = [
|
||
getattr(park, "name", None),
|
||
getattr(location, "street_address", None),
|
||
getattr(location, "city", None),
|
||
getattr(location, "state", None),
|
||
getattr(location, "country", None),
|
||
]
|
||
address = ", ".join(part for part in address_parts if part)
|
||
print(f" Address: {address}")
|
||
|
||
# Try to geocode
|
||
success = service.geocode_park_if_needed(park)
|
||
if success:
|
||
coords = getattr(park, "coordinates", None)
|
||
latlon = _format_coords(coords)
|
||
if latlon:
|
||
print(f" ✅ Geocoded to: {latlon[0]:.4f}, {latlon[1]:.4f}")
|
||
else:
|
||
print(" ✅ Geocoded but coordinates unavailable")
|
||
else:
|
||
print(" ❌ Geocoding failed")
|
||
else:
|
||
print(" ❌ No location data available")
|
||
|
||
|
||
def demo_cache_performance():
|
||
"""
|
||
Demonstrate caching performance benefits.
|
||
"""
|
||
print("\n\n⚡ Cache Performance Demo")
|
||
print("=" * 50)
|
||
|
||
service = RoadTripService()
|
||
|
||
import time
|
||
|
||
# Test address for geocoding
|
||
test_address = "Disneyland, Anaheim, CA"
|
||
|
||
print(f"Testing cache performance with: {test_address}")
|
||
|
||
# First request (cache miss)
|
||
print("\n1️⃣ First request (cache miss):")
|
||
start_time = time.time()
|
||
coords1 = service.geocode_address(test_address)
|
||
first_duration = time.time() - start_time
|
||
|
||
if coords1:
|
||
latlon = _format_coords(coords1)
|
||
if latlon:
|
||
print(f" ✅ Result: {latlon[0]:.4f}, {latlon[1]:.4f}")
|
||
else:
|
||
print(" ✅ Result obtained")
|
||
print(f" ⏱️ Duration: {first_duration:.2f} seconds")
|
||
|
||
# Second request (cache hit)
|
||
print("\n2️⃣ Second request (cache hit):")
|
||
start_time = time.time()
|
||
coords2 = service.geocode_address(test_address)
|
||
second_duration = time.time() - start_time
|
||
|
||
if coords2:
|
||
latlon2 = _format_coords(coords2)
|
||
if latlon2:
|
||
print(f" ✅ Result: {latlon2[0]:.4f}, {latlon2[1]:.4f}")
|
||
else:
|
||
print(" ✅ Result obtained")
|
||
print(f" ⏱️ Duration: {second_duration:.2f} seconds")
|
||
|
||
if first_duration > second_duration and second_duration > 0:
|
||
speedup = first_duration / second_duration
|
||
print(f" 🚀 Cache speedup: {speedup:.1f}x faster")
|
||
|
||
# Compare coordinates if both present
|
||
if coords1 and coords2:
|
||
latlon1 = _format_coords(coords1)
|
||
latlon2 = _format_coords(coords2)
|
||
if latlon1 and latlon2 and latlon1 == latlon2:
|
||
print(" ✅ Results identical (cache working)")
|
||
|
||
|
||
def main():
|
||
"""
|
||
Run all demonstration scenarios.
|
||
"""
|
||
print("🎢 ThrillWiki Road Trip Service Demo")
|
||
print("This demo shows practical usage scenarios for the OSM Road Trip Service")
|
||
|
||
try:
|
||
demo_florida_theme_park_trip()
|
||
demo_cross_country_road_trip()
|
||
demo_database_integration()
|
||
demo_geocoding_fallback()
|
||
demo_cache_performance()
|
||
|
||
print("\n" + "=" * 50)
|
||
print("🎉 Demo completed successfully!")
|
||
print("\nThe Road Trip Service is ready for integration into ThrillWiki!")
|
||
print("\nKey Features Demonstrated:")
|
||
print("✅ Geocoding theme park addresses")
|
||
print("✅ Route calculation with distance/time")
|
||
print("✅ Multi-park trip optimization")
|
||
print("✅ Database integration with Park models")
|
||
print("✅ Caching for performance")
|
||
print("✅ Rate limiting for OSM compliance")
|
||
print("✅ Error handling and fallbacks")
|
||
|
||
except Exception as e:
|
||
print(f"\n❌ Demo failed with error: {e}")
|
||
import traceback
|
||
|
||
traceback.print_exc()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|