Spaces:
Sleeping
Sleeping
import os | |
import uuid | |
import tempfile | |
import staticmaps | |
from PIL import Image | |
from geopy.geocoders import Nominatim | |
from ors_client import get_ors_client, is_ors_configured | |
TEMP_DIR = tempfile.mkdtemp(prefix="geocalc_") | |
def generate_route_image_file(coords, start_lat, start_lon, end_lat, end_lon): | |
"""Generate route image and save to temp file.""" | |
context = staticmaps.Context() | |
context.set_tile_provider(staticmaps.tile_provider_OSM) | |
# Create route line | |
line = staticmaps.Line( | |
[staticmaps.create_latlng(lat, lng) for lat, lng in coords], | |
color=staticmaps.BLUE, | |
width=4 | |
) | |
context.add_object(line) | |
# Add start marker (green) | |
start_marker = staticmaps.Marker( | |
staticmaps.create_latlng(start_lat, start_lon), | |
color=staticmaps.GREEN, | |
size=12 | |
) | |
context.add_object(start_marker) | |
# Add end marker (red) | |
end_marker = staticmaps.Marker( | |
staticmaps.create_latlng(end_lat, end_lon), | |
color=staticmaps.RED, | |
size=12 | |
) | |
context.add_object(end_marker) | |
# Generate image and save to temp file | |
image = context.render_pillow(500, 375) | |
# Create unique temp file path | |
filename = f"route_{uuid.uuid4().hex[:8]}.webp" | |
filepath = os.path.join(TEMP_DIR, filename) | |
image.save(filepath, format='WEBP', quality=85, optimize=True) | |
return filepath | |
def load_image_with_title(image_path, custom_title=None): | |
"""Load image from path and optionally add custom title.""" | |
image = Image.open(image_path) | |
if custom_title: | |
from PIL import ImageDraw, ImageFont | |
draw = ImageDraw.Draw(image) | |
# Try to use a nice font, scaled to image size | |
font_size = max(16, image.width // 25) # Responsive font size | |
try: | |
font = ImageFont.truetype("Arial.ttf", font_size) | |
except: | |
font = ImageFont.load_default() | |
# Add title at top center | |
text_bbox = draw.textbbox((0, 0), custom_title, font=font) | |
text_width = text_bbox[2] - text_bbox[0] | |
x = (image.width - text_width) // 2 | |
y = 5 # Reduced margin for smaller images | |
# Add background rectangle for better readability | |
padding = 3 # Smaller padding for smaller images | |
draw.rectangle([x-padding, y-padding, x+text_width+padding, y+text_bbox[3]+padding], | |
fill="white", outline="black") | |
draw.text((x, y), custom_title, fill="black", font=font) | |
return image | |
def geocode_address(address): | |
"""Convert address to coordinates.""" | |
geolocator = Nominatim(user_agent="geocalc_mcp_app_hackathon") | |
location = geolocator.geocode(address) | |
if location: | |
return round(location.latitude, 4), round(location.longitude, 4) | |
return None | |
def calculate_route_distance_km(route_data): | |
"""Extract distance in km from route data.""" | |
if "error" in route_data: | |
return None | |
meters = route_data['summary']['distance'] | |
return round(meters / 1000, 1) | |
def calculate_route_time_minutes(route_data): | |
"""Extract time in minutes from route data.""" | |
if "error" in route_data: | |
return None | |
seconds = route_data['summary']['duration'] | |
return int(seconds / 60) | |
POI_CATEGORIES = { | |
"accommodation": 108, # hotel | |
"restaurants": 570, # restaurant | |
"bars": 561, # bar | |
"cafes": 564, # café | |
"healthcare": 208, # pharmacy | |
"shopping": 518, # supermarket | |
"attractions": 622, # attraction | |
"museums": 134, # museum | |
"transport": 588, # bus_stop | |
"banks": 192 # bank | |
} | |
POI_CATEGORY_LIST = list(POI_CATEGORIES.keys()) | |
def get_poi_data(lat, lon, radius_m=1000, categories=None): | |
"""Get POI data from OpenRouteService.""" | |
if not is_ors_configured(): | |
return {"error": "ORS API key not configured"} | |
client_ors = get_ors_client() | |
# Create circular geometry around the point | |
import math | |
# Calculate circle geometry in lat/lon degrees | |
earth_radius_m = 6371000 | |
lat_rad = math.radians(lat) | |
lon_rad = math.radians(lon) | |
# Calculate degree offsets for the radius | |
lat_offset = radius_m / earth_radius_m | |
lon_offset = radius_m / (earth_radius_m * math.cos(lat_rad)) | |
# Create a rough circle geometry | |
circle_points = [] | |
for angle in range(0, 360, 30): # 12 points for circle | |
angle_rad = math.radians(angle) | |
point_lat = lat + lat_offset * math.sin(angle_rad) | |
point_lon = lon + lon_offset * math.cos(angle_rad) | |
circle_points.append([point_lon, point_lat]) | |
circle_points.append(circle_points[0]) # Close the polygon | |
geojson_geometry = { | |
"type": "Polygon", | |
"coordinates": [circle_points] | |
} | |
params = { | |
"request": "pois", | |
"geojson": geojson_geometry, | |
"sortby": "distance" | |
} | |
if categories: | |
category_ids = [POI_CATEGORIES.get(cat) for cat in categories if cat in POI_CATEGORIES] | |
if category_ids: | |
# Limit to maximum 5 categories as per API requirement | |
params["filter_category_ids"] = category_ids[:5] | |
try: | |
result = client_ors.places(**params) | |
return result | |
except Exception as e: | |
return {"error": f"POI API request failed: {str(e)}"} |