Spaces:
Sleeping
Sleeping
| import json | |
| import matplotlib.pyplot as plt | |
| import polyline | |
| from PIL import Image | |
| from io import BytesIO | |
| from geopy import distance | |
| from openrouteservice.exceptions import ApiError | |
| from ors_client import get_ors_client, is_ors_configured | |
| from route_utils import ( | |
| generate_route_image_file, | |
| load_image_with_title, | |
| geocode_address, | |
| calculate_route_distance_km, | |
| calculate_route_time_minutes, | |
| get_poi_data | |
| ) | |
| 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: | |
| coords = geocode_address(address) | |
| if coords: | |
| lat, lon = coords | |
| 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'."}) | |
| if not is_ors_configured(): | |
| return json.dumps({"error": "ORS API key not configured"}) | |
| client_ors = get_ors_client() | |
| 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"] | |
| minutes = calculate_route_time_minutes(data) | |
| if minutes is None: | |
| return "Error calculating time" | |
| 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) | |
| if "error" in data: | |
| return data["error"] | |
| km = calculate_route_distance_km(data) | |
| if km is None: | |
| return "Error calculating distance" | |
| 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) | |
| def get_points_of_interest(lat: float, lon: float, radius_km: float = 10.0, categories: list = None) -> str: | |
| """ | |
| Find points of interest near given coordinates. | |
| Increase radius if not found (10km is default), try with more categories if not found any poi | |
| Args: | |
| lat (float): Latitude of the center point | |
| lon (float): Longitude of the center point | |
| radius_km (float): Search radius in kilometers (default 10.0), is recommended to be 10km | |
| categories (str): Comma-separated POI categories (e.g., "sustenance,tourism"), use many categories to find more poi | |
| Returns: | |
| str: Formatted text with POI information | |
| """ | |
| radius_m = int(radius_km * 1000) | |
| category_list = categories if categories else None | |
| poi_data = get_poi_data(lat, lon, radius_m, category_list) | |
| if "error" in poi_data: | |
| return f"Error: {poi_data['error']}" | |
| if "features" not in poi_data or not poi_data["features"]: | |
| return f"No points of interest found within {radius_km} km of the location." | |
| pois = poi_data["features"] | |
| result = f"Found {len(pois)} points of interest within {radius_km} km:\n\n" | |
| for poi in pois: | |
| props = poi.get("properties", {}) | |
| geom = poi.get("geometry", {}).get("coordinates", []) | |
| name = props.get("osm_tags", {}).get("name", "Unnamed location") | |
| category = props.get("category_ids", {}) | |
| category_name = next(iter(category.values())) if category else "Unknown" | |
| if geom and len(geom) >= 2: | |
| poi_lon, poi_lat = geom[0], geom[1] | |
| result += f"• {name} ({category_name})\n" | |
| result += f" Location: {poi_lat:.4f}, {poi_lon:.4f}\n" | |
| # Add additional details if available | |
| tags = props.get("osm_tags", {}) | |
| if "addr:street" in tags: | |
| result += f" Address: {tags.get('addr:street', '')}\n" | |
| if "phone" in tags: | |
| result += f" Phone: {tags['phone']}\n" | |
| if "website" in tags: | |
| result += f" Website: {tags['website']}\n" | |
| result += "\n" | |
| return result.strip() | |