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