Spaces:
Sleeping
Sleeping
| import json | |
| import matplotlib.pyplot as plt | |
| import polyline | |
| from PIL import Image | |
| from io import BytesIO | |
| from geopy.geocoders import Nominatim | |
| from geopy import distance | |
| import openrouteservice | |
| from openrouteservice.exceptions import ApiError | |
| import os | |
| from dotenv import load_dotenv | |
| from route_utils import generate_route_image_file, load_image_with_title | |
| load_dotenv() | |
| ORS_API_KEY = os.getenv("ORS_API_KEY") | |
| def get_coords_from_address(address: str) -> str: | |
| """ | |
| Converts a street address into latitude and longitude coordinates. | |
| Args: | |
| address (str): The address to search for (e.g., "Eiffel Tower, Paris"). | |
| Returns: | |
| str: A formatted string with the coordinates "Lat: XX.XXXX, Lon: YY.YYYY" | |
| or an error message if the address is not found. | |
| """ | |
| try: | |
| geolocator = Nominatim(user_agent="geocalc_mcp_app_hackathon") | |
| location = geolocator.geocode(address) | |
| if location: | |
| # If a location is found, format the latitude and longitude into a string. | |
| lat = round(location.latitude, 4) | |
| lon = round(location.longitude, 4) | |
| return f"Lat: {lat}, Lon: {lon}" | |
| else: | |
| return "Address not found. Please try being more specific. E.g., '1600 Amphitheatre Parkway, Mountain View, CA'" | |
| except Exception as e: | |
| print(f"An error occurred: {e}") | |
| return "An error occurred while trying to contact the geocoding service." | |
| def calculate_direct_distance(lat1: float, lon1: float, lat2: float, lon2: float, unit: str = "km") -> str: | |
| """ | |
| Calculates the distance between two points on the Earth's surface using the Haversine formula. | |
| Args: | |
| lat1 (float): Latitude of the first point. | |
| lon1 (float): Longitude of the first point. | |
| lat2 (float): Latitude of the second point. | |
| lon2 (float): Longitude of the second point. | |
| unit (str, optional): Unit of measurement for the distance. Default is "km". | |
| Returns: | |
| str: The distance between the two points in kilometers. | |
| """ | |
| print("calculate_distance", lat1, lon1, lat2, lon2, unit) | |
| if unit == "km": | |
| return str(round(distance.distance((lat1, lon1), (lat2, lon2)).km, 2)) | |
| else: | |
| return str(round(distance.distance((lat1, lon1), (lat2, lon2)).miles, 2)) | |
| # This is now a standard, synchronous function | |
| def get_route_data(start_lat: float, start_lon: float, end_lat: float, end_lon: float, mode: str) -> str: | |
| """ | |
| Fetches optimized route data from the OpenRouteService API for a given start/end point and travel mode. | |
| This is the primary data-gathering tool. Its output is a compact JSON string meant to be used by other tools. | |
| Args: | |
| start_lat (float): Latitude of the starting point. | |
| start_lon (float): Longitude of the starting point. | |
| end_lat (float): Latitude of the ending point. | |
| end_lon (float): Longitude of the ending point. | |
| mode (str): The mode of transportation (e.g., "car", "walk", "bike"). | |
| Returns: | |
| A compact JSON string containing optimized route details with decoded coordinates. | |
| """ | |
| profile_map = { | |
| "car": "driving-car", | |
| "walk": "foot-walking", | |
| "bike": "cycling-road" | |
| } | |
| if mode not in profile_map: | |
| return json.dumps({"error": "Invalid mode. Please use 'car', 'walk', or 'bike'."}) | |
| client_ors = openrouteservice.Client(key=ORS_API_KEY) | |
| coords = ((start_lon, start_lat), (end_lon, end_lat)) | |
| try: | |
| routes = client_ors.directions(coordinates=coords, profile=profile_map[mode], geometry='true') | |
| route_data = routes['routes'][0] | |
| # Decode full polyline geometry for image generation | |
| decoded_coords = polyline.decode(route_data['geometry']) | |
| # Generate route image with full detail | |
| image_path = generate_route_image_file(decoded_coords, start_lat, start_lon, end_lat, end_lon) | |
| # Return optimized JSON with image path | |
| optimized_data = { | |
| "summary": route_data['summary'], | |
| "map_image_path": image_path, | |
| "start_point": [start_lat, start_lon], | |
| "end_point": [end_lat, end_lon] | |
| } | |
| return json.dumps(optimized_data) | |
| except ApiError as e: | |
| return json.dumps({"error": f"Could not find a route. API Error: {e}"}) | |
| except Exception as e: | |
| return json.dumps({"error": f"An unexpected error occurred: {e}"}) | |
| def extract_route_time(route_json: str) -> str: | |
| """ | |
| Extract travel time from route data. | |
| Args: | |
| route_json (str): JSON string containing route data | |
| Returns: | |
| Time in human format like '15 min' or '1 h 23 min' | |
| """ | |
| data = json.loads(route_json) | |
| if "error" in data: | |
| return data["error"] | |
| seconds = data['summary']['duration'] | |
| minutes = int(seconds / 60) | |
| if minutes < 60: | |
| return f"{minutes} min" | |
| else: | |
| hours = minutes // 60 | |
| mins = minutes % 60 | |
| return f"{hours} h {mins} min" | |
| def extract_route_distance(route_json: str) -> str: | |
| """ | |
| Extract distance from route data. | |
| Args: | |
| route_json (str): JSON string containing route data | |
| Returns: | |
| Distance like '5.2 km' | |
| """ | |
| data = json.loads(route_json) | |
| print("extract_route_distance", data) | |
| if "error" in data: | |
| return data["error"] | |
| meters = data['summary']['distance'] | |
| km = round(meters / 1000, 1) | |
| return f"{km} km" | |
| def generate_route_image(route_json: str, custom_title: str = None) -> Image.Image: | |
| """ | |
| Extract route image from JSON data and optionally add custom title. | |
| Args: | |
| route_json (str): JSON string containing route data | |
| custom_title (str): Optional title to add to the image | |
| Returns: | |
| PIL.Image.Image: The generated route image | |
| """ | |
| data = json.loads(route_json) | |
| if "error" in data: | |
| # Create error image | |
| fig, ax = plt.subplots(1, 1, figsize=(8, 6)) | |
| ax.text(0.5, 0.5, f"Error: {data['error']}", | |
| ha='center', va='center', fontsize=12, color='red') | |
| ax.axis('off') | |
| buf = BytesIO() | |
| fig.savefig(buf, format='png', dpi=150, bbox_inches='tight') | |
| buf.seek(0) | |
| img = Image.open(buf) | |
| plt.close(fig) | |
| return img | |
| # Load image from path and add title if provided | |
| image_path = data['map_image_path'] | |
| return load_image_with_title(image_path, custom_title) | |