""" 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()