Spaces:
Sleeping
Sleeping
from fastapi import FastAPI, HTTPException, Query, Request, Response | |
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse | |
from fastapi.middleware.cors import CORSMiddleware | |
import pandas as pd | |
import numpy as np | |
from geopy.distance import geodesic | |
import folium | |
from folium import plugins | |
import osmnx as ox | |
import networkx as nx | |
from datetime import datetime | |
import json | |
import matplotlib.pyplot as plt | |
import plotly.express as px | |
import os | |
import time | |
from functools import lru_cache | |
from rtree import index | |
from pydantic import BaseModel, Field | |
from typing import List, Dict, Any, Optional | |
# Create app instance | |
app = FastAPI(title="Store Locator API") | |
# Add CORS middleware | |
app.add_middleware( | |
CORSMiddleware, | |
allow_origins=["*"], | |
allow_credentials=True, | |
allow_methods=["*"], | |
allow_headers=["*"], | |
) | |
# Create temp directory for files | |
os.makedirs('temp', exist_ok=True) | |
# Load and prepare the store data | |
stores_df = pd.read_csv('dataset of 50 stores.csv') | |
# Custom JSON encoder for numpy types | |
class NumpyJSONEncoder(json.JSONEncoder): | |
def default(self, obj): | |
if isinstance(obj, np.integer): | |
return int(obj) | |
elif isinstance(obj, np.floating): | |
return float(obj) | |
elif isinstance(obj, np.ndarray): | |
return obj.tolist() | |
return super().default(obj) | |
# Pydantic models for response validation | |
class StoreLocation(BaseModel): | |
lat: float | |
lon: float | |
class Store(BaseModel): | |
store_name: str | |
address: str | |
contact: str | |
distance: float | |
estimated_delivery_time: int | |
product_categories: str | |
location: StoreLocation | |
class StoresResponse(BaseModel): | |
status: str | |
stores: List[Store] | |
class ErrorResponse(BaseModel): | |
status: str | |
message: str | |
class StoreLocator: | |
def __init__(self, stores_dataframe): | |
self.stores_df = stores_dataframe | |
self.network_graph = None | |
self.graph_cache = {} # Cache for network graphs | |
self.spatial_index = self._build_spatial_index() | |
def initialize_graph(self, center_point, dist=20000): | |
"""Initialize road network graph with caching""" | |
cache_key = f"{center_point[0]}_{center_point[1]}" | |
if cache_key in self.graph_cache: | |
self.network_graph = self.graph_cache[cache_key] | |
return True | |
try: | |
self.network_graph = ox.graph_from_point(center_point, dist=dist, network_type="drive") | |
self.network_graph = ox.add_edge_speeds(self.network_graph) | |
self.network_graph = ox.add_edge_travel_times(self.network_graph) | |
return True | |
except Exception as e: | |
print(f"Error initializing graph: {str(e)}") | |
return False | |
def _build_spatial_index(self): | |
idx = index.Index() | |
for i, row in self.stores_df.iterrows(): | |
idx.insert(i, (row['Latitude'], row['Longitude'], | |
row['Latitude'], row['Longitude'])) | |
return idx | |
def calculate_distance(self, lat1, lon1, lat2, lon2): | |
"""Calculate direct distance between two points""" | |
return geodesic((lat1, lon1), (lat2, lon2)).kilometers | |
def estimate_delivery_time(self, distance, current_time=None): | |
"""Estimate delivery time based on distance and current time""" | |
if current_time is None: | |
current_time = datetime.now() | |
# Base time: 5 mins base + 2 mins per km | |
base_minutes = 5 + (distance * 2) | |
# Apply traffic multiplier based on time of day | |
hour = current_time.hour | |
if hour in [8, 9, 10, 17, 18, 19]: # Peak hours | |
multiplier = 1.5 | |
elif hour in [23, 0, 1, 2, 3, 4]: # Off-peak hours | |
multiplier = 0.8 | |
else: # Normal hours | |
multiplier = 1.0 | |
return round(base_minutes * multiplier) | |
def find_nearby_stores(self, lat, lon, radius=5): | |
"""Find stores within radius using spatial index""" | |
nearby_stores = [] | |
bbox = (lat - radius/111.0, lon - radius/111.0, | |
lat + radius/111.0, lon + radius/111.0) | |
for store_id in self.spatial_index.intersection(bbox): | |
store = self.stores_df.iloc[store_id] | |
distance = self.calculate_distance(lat, lon, | |
store['Latitude'], | |
store['Longitude']) | |
if distance <= radius: | |
delivery_time = self.estimate_delivery_time(distance) | |
nearby_stores.append({ | |
'store_name': store['Store Name'], | |
'address': store['Address'], | |
'contact': str(store['Contact Number']), # Convert to string to avoid int64 issues | |
'distance': round(distance, 2), | |
'estimated_delivery_time': int(delivery_time), # Ensure integer type | |
'product_categories': store['Product Categories'], | |
'location': { | |
'lat': float(store['Latitude']), # Ensure float type | |
'lon': float(store['Longitude']) # Ensure float type | |
} | |
}) | |
return sorted(nearby_stores, key=lambda x: x['distance']) | |
def create_store_map(self, center_lat, center_lon, radius=5): | |
"""Create an interactive map with store locations""" | |
# Create base map | |
m = folium.Map(location=[center_lat, center_lon], | |
zoom_start=13, | |
tiles="cartodbpositron", | |
prefer_canvas=True | |
) | |
# Create marker cluster for better performance with many markers | |
marker_cluster = plugins.MarkerCluster().add_to(m) | |
# Add stores to map | |
nearby_stores = self.find_nearby_stores(center_lat, center_lon, radius) | |
for store in nearby_stores: | |
# Prepare popup content | |
popup_content = f""" | |
<div style='width: 200px'> | |
<b>{store['store_name']}</b><br> | |
Address: {store['address']}<br> | |
Distance: {store['distance']} km<br> | |
Est. Delivery: {store['estimated_delivery_time']} mins<br> | |
Categories: {store['product_categories']} | |
</div> | |
""" | |
# Add store marker | |
folium.Marker( | |
location=[store['location']['lat'], store['location']['lon']], | |
popup=folium.Popup(popup_content, max_width=300), | |
icon=folium.Icon(color='red', icon='info-sign') | |
).add_to(marker_cluster) | |
# Add line to show distance from center | |
folium.PolyLine( | |
locations=[[center_lat, center_lon], | |
[store['location']['lat'], store['location']['lon']]], | |
weight=2, | |
color='blue', | |
opacity=0.3 | |
).add_to(m) | |
# Add current location marker | |
folium.Marker( | |
location=[center_lat, center_lon], | |
popup='Your Location', | |
icon=folium.Icon(color='green', icon='home') | |
).add_to(m) | |
# Add fullscreen option | |
plugins.Fullscreen().add_to(m) | |
# Add layer control | |
folium.LayerControl().add_to(m) | |
return m | |
# Initialize store locator | |
store_locator = StoreLocator(stores_df) | |
def create_animated_route(G, path, color, weight=3): | |
"""Create an animated route visualization""" | |
features = [] | |
timestamps = [] | |
# Convert path nodes to coordinates | |
route_coords = [ | |
(G.nodes[node]['y'], G.nodes[node]['x']) | |
for node in path | |
] | |
# Create features for each segment of the route | |
for i in range(len(route_coords) - 1): | |
segment = { | |
'type': 'Feature', | |
'geometry': { | |
'type': 'LineString', | |
'coordinates': [ | |
[route_coords[i][1], route_coords[i][0]], | |
[route_coords[i+1][1], route_coords[i+1][0]] | |
] | |
}, | |
'properties': { | |
'times': [datetime.now().isoformat()], | |
'style': { | |
'color': color, | |
'weight': weight, | |
'opacity': 0.8 | |
} | |
} | |
} | |
features.append(segment) | |
timestamps.append(datetime.now().isoformat()) | |
return features | |
def create_route_animation_data(G, path_time, path_length): | |
"""Create animation data for route visualization""" | |
lst_start, lst_end = [], [] | |
start_x, start_y = [], [] | |
end_x, end_y = [], [] | |
lst_length, lst_time = [], [] | |
# Process time-based path | |
for a, b in zip(path_time[:-1], path_time[1:]): | |
lst_start.append(a) | |
lst_end.append(b) | |
lst_length.append(round(G.edges[(a,b,0)]['length'])) | |
lst_time.append(round(G.edges[(a,b,0)]['travel_time'])) | |
start_x.append(G.nodes[a]['x']) | |
start_y.append(G.nodes[a]['y']) | |
end_x.append(G.nodes[b]['x']) | |
end_y.append(G.nodes[b]['y']) | |
# Create DataFrame | |
df = pd.DataFrame( | |
list(zip(lst_start, lst_end, start_x, start_y, end_x, end_y, | |
lst_length, lst_time)), | |
columns=["start", "end", "start_x", "start_y", "end_x", "end_y", | |
"length", "travel_time"] | |
).reset_index().rename(columns={"index": "id"}) | |
return df | |
# Function to clean up temporary files | |
async def cleanup_temp_files(request: Request, call_next): | |
temp_dir = 'temp' | |
if os.path.exists(temp_dir): | |
for file in os.listdir(temp_dir): | |
file_path = os.path.join(temp_dir, file) | |
try: | |
if os.path.isfile(file_path) and file.endswith('.html'): | |
# Delete files older than 1 hour | |
if os.path.getmtime(file_path) < time.time() - 3600: | |
os.remove(file_path) | |
except Exception as e: | |
print(f"Error cleaning up temp files: {e}") | |
response = await call_next(request) | |
return response | |
async def home(): | |
"""API documentation homepage""" | |
html_content = f""" | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>falcao-maps API Documentation</title> | |
<style> | |
body {{ | |
font-family: Arial, sans-serif; | |
margin: 20px; | |
}} | |
h1, h2 {{ | |
color: #333; | |
}} | |
pre {{ | |
background-color: #f4f4f4; | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
}} | |
</style> | |
</head> | |
<body> | |
<h1>Welcome to falcao-maps</h1> | |
<button><a href="https://lucifer7210-maps.hf.space" target="_blank" >Link</a></button> | |
<p>Based on your uploaded dataset and deployed API, here are example API calls for your client:</p> | |
<h2>1. Find Nearby Stores (JSON Response)</h2> | |
<pre> | |
<a href="https://lucifer7210-maps.hf.space/api/stores/nearby?lat=18.9695&lon=72.8320&radius=0.5">api/stores/nearby?lat=18.9695&lon=72.8320&radius=0.5</a> | |
</pre> | |
<p>Use this to get store details near Market Road area within 1km</p> | |
<h2>2. View Basic Store Map</h2> | |
<pre> | |
<a href="https://lucifer7210-maps.hf.space/api/stores/map?lat=18.9701&lon=72.8330&radius=0.5" target="_blank"> | |
https://lucifer7210-maps.hf.space/api/stores/map?lat=18.9701&lon=72.8330&radius=0.5 | |
</a> | |
</pre> | |
<p>Shows map centered at Main Street with 500m radius</p> | |
<h2>3. View All Store Locations with Color Coding</h2> | |
<pre> | |
<a href="https://lucifer7210-maps.hf.space/api/stores/locations?lat=18.9685&lon=72.8325&radius=2" target="_blank"> | |
https://lucifer7210-maps.hf.space/api/stores/locations?lat=18.9685&lon=72.8325&radius=2 | |
</a> | |
</pre> | |
<p>Shows detailed map with color-coded stores within 2km</p> | |
<h2>4. Get Route Between Points</h2> | |
<p>Example routes:</p> | |
<pre> | |
# Route from Park Avenue to Hill Road stores | |
https://lucifer7210-maps.hf.space/api/stores/route?user_lat=18.9710&user_lon=72.8335&store_lat=18.9705&store_lon=72.8345&viz_type=simple | |
# Route from Main Street to Market Road stores | |
https://lucifer7210-maps.hf.space/api/stores/route?user_lat=18.9701&user_lon=72.8330&store_lat=18.9695&store_lon=72.8320&viz_type=simple | |
</pre> | |
<h2>Key Location Points in Dataset:</h2> | |
<ul> | |
<li>Main Street Area: 18.9701, 72.8330</li> | |
<li>Park Avenue: 18.9710, 72.8335</li> | |
<li>Market Road: 18.9695, 72.8320</li> | |
<li>Shopping Center: 18.9670, 72.8300</li> | |
<li>Commercial Street: 18.9690, 72.8340</li> | |
</ul> | |
</body> | |
</html> | |
""" | |
return HTMLResponse(content=html_content) | |
async def get_nearby_stores( | |
lat: float = Query(..., description="Latitude of user location"), | |
lon: float = Query(..., description="Longitude of user location"), | |
radius: float = Query(5, description="Search radius in kilometers") | |
): | |
""" | |
Get nearby stores based on user location | |
""" | |
try: | |
nearby_stores = store_locator.find_nearby_stores(lat, lon, radius) | |
return {"status": "success", "stores": nearby_stores} | |
except Exception as e: | |
raise HTTPException(status_code=400, detail=str(e)) | |
async def get_stores_map( | |
lat: float = Query(..., description="Latitude of center point"), | |
lon: float = Query(..., description="Longitude of center point"), | |
radius: float = Query(5, description="Search radius in kilometers") | |
): | |
""" | |
Get HTML map with store locations | |
""" | |
try: | |
store_map = store_locator.create_store_map(lat, lon, radius) | |
# Create complete HTML content | |
html_content = f""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> | |
<title>Stores Map</title> | |
<style> | |
body {{ | |
margin: 0; | |
padding: 0; | |
width: 100vw; | |
height: 100vh; | |
overflow: hidden; | |
}} | |
#map {{ | |
width: 100%; | |
height: 100%; | |
}} | |
</style> | |
</head> | |
<body> | |
{store_map.get_root().render()} | |
<script> | |
window.onload = function() {{ | |
setTimeout(function() {{ | |
window.dispatchEvent(new Event('resize')); | |
}}, 1000); | |
}}; | |
</script> | |
</body> | |
</html> | |
""" | |
# Save the HTML to a file | |
file_path = 'temp/stores_map.html' | |
with open(file_path, 'w', encoding='utf-8') as f: | |
f.write(html_content) | |
# Return the file | |
return FileResponse(file_path, media_type='text/html') | |
except Exception as e: | |
raise HTTPException(status_code=400, detail=str(e)) | |
async def get_store_route( | |
user_lat: float = Query(..., description="Latitude of user location"), | |
user_lon: float = Query(..., description="Longitude of user location"), | |
store_lat: float = Query(..., description="Latitude of store location"), | |
store_lon: float = Query(..., description="Longitude of store location") | |
): | |
""" | |
Get route between user location and store | |
""" | |
try: | |
# Initialize graph if not already initialized | |
if store_locator.network_graph is None: | |
store_locator.initialize_graph((user_lat, user_lon)) | |
# Get nearest nodes | |
start_node = ox.distance.nearest_nodes( | |
store_locator.network_graph, user_lon, user_lat) | |
end_node = ox.distance.nearest_nodes( | |
store_locator.network_graph, store_lon, store_lat) | |
try: | |
# Calculate paths | |
path_time = nx.shortest_path( | |
store_locator.network_graph, | |
start_node, | |
end_node, | |
weight='travel_time' | |
) | |
# Create animation data | |
lst_start, lst_end = [], [] | |
start_x, start_y = [], [] | |
end_x, end_y = [], [] | |
lst_length, lst_time = [], [] | |
for a, b in zip(path_time[:-1], path_time[1:]): | |
lst_start.append(a) | |
lst_end.append(b) | |
lst_length.append(round(store_locator.network_graph.edges[(a,b,0)]['length'])) | |
lst_time.append(round(store_locator.network_graph.edges[(a,b,0)]['travel_time'])) | |
start_x.append(store_locator.network_graph.nodes[a]['x']) | |
start_y.append(store_locator.network_graph.nodes[a]['y']) | |
end_x.append(store_locator.network_graph.nodes[b]['x']) | |
end_y.append(store_locator.network_graph.nodes[b]['y']) | |
df = pd.DataFrame( | |
list(zip(lst_start, lst_end, start_x, start_y, end_x, end_y, | |
lst_length, lst_time)), | |
columns=["start", "end", "start_x", "start_y", | |
"end_x", "end_y", "length", "travel_time"] | |
).reset_index().rename(columns={"index": "id"}) | |
# Create animation using plotly | |
df_start = df[df["start"] == start_node] | |
df_end = df[df["end"] == end_node] | |
fig = px.scatter_mapbox( | |
data_frame=df, | |
lon="start_x", | |
lat="start_y", | |
zoom=15, | |
width=1000, | |
height=800, | |
animation_frame="id", | |
mapbox_style="carto-positron" | |
) | |
# Add driver marker | |
fig.data[0].marker = {"size": 12} | |
# Add start point | |
fig.add_trace( | |
px.scatter_mapbox( | |
data_frame=df_start, | |
lon="start_x", | |
lat="start_y" | |
).data[0] | |
) | |
fig.data[1].marker = {"size": 15, "color": "red"} | |
# Add end point | |
fig.add_trace( | |
px.scatter_mapbox( | |
data_frame=df_end, | |
lon="start_x", | |
lat="start_y" | |
).data[0] | |
) | |
fig.data[2].marker = {"size": 15, "color": "green"} | |
# Add route | |
fig.add_trace( | |
px.line_mapbox( | |
data_frame=df, | |
lon="start_x", | |
lat="start_y" | |
).data[0] | |
) | |
# Update layout with slower animation settings | |
fig.update_layout( | |
showlegend=False, | |
margin={"r":0,"t":0,"l":0,"b":0}, | |
autosize=True, | |
height=None, | |
updatemenus=[{ | |
"type": "buttons", | |
"showactive": False, | |
"y": 0, | |
"x": 0, | |
"xanchor": "left", | |
"yanchor": "bottom", | |
"buttons": [ | |
{ | |
"label": "Play", | |
"method": "animate", | |
"args": [ | |
None, | |
{ | |
"frame": {"duration": 1000, "redraw": True}, | |
"fromcurrent": True, | |
"transition": {"duration": 800} | |
} | |
] | |
}, | |
{ | |
"label": "Pause", | |
"method": "animate", | |
"args": [ | |
[None], | |
{ | |
"frame": {"duration": 0, "redraw": False}, | |
"mode": "immediate", | |
"transition": {"duration": 0} | |
} | |
] | |
} | |
] | |
}], | |
sliders=[{ | |
"currentvalue": {"prefix": "Step: "}, | |
"pad": {"t": 20}, | |
"len": 0.9, | |
"x": 0.1, | |
"xanchor": "left", | |
"y": 0.02, | |
"yanchor": "bottom", | |
"steps": [ | |
{ | |
"args": [ | |
[k], | |
{ | |
"frame": {"duration": 1000, "redraw": True}, | |
"transition": {"duration": 500}, | |
"mode": "immediate" | |
} | |
], | |
"label": str(k), | |
"method": "animate" | |
} | |
for k in range(len(df)) | |
] | |
}] | |
) | |
# Create complete HTML content | |
html_content = f""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> | |
<title>Route Map</title> | |
<style> | |
body {{ | |
margin: 0; | |
padding: 0; | |
width: 100vw; | |
height: 100vh; | |
overflow: hidden; | |
}} | |
#map-container {{ | |
width: 100%; | |
height: 100%; | |
}} | |
</style> | |
</head> | |
<body> | |
<div id="map-container"> | |
{fig.to_html(include_plotlyjs=True, full_html=False)} | |
</div> | |
<script> | |
window.onload = function() {{ | |
setTimeout(function() {{ | |
window.dispatchEvent(new Event('resize')); | |
}}, 1000); | |
}}; | |
</script> | |
</body> | |
</html> | |
""" | |
# Save the HTML to a file | |
file_path = 'temp/route_map.html' | |
with open(file_path, 'w', encoding='utf-8') as f: | |
f.write(html_content) | |
# Return the file | |
return FileResponse(file_path, media_type='text/html') | |
except nx.NetworkXNoPath: | |
raise HTTPException(status_code=404, detail="No route found") | |
except Exception as e: | |
raise HTTPException(status_code=400, detail=str(e)) | |
async def get_all_store_locations( | |
lat: float = Query(..., description="Latitude of center point"), | |
lon: float = Query(..., description="Longitude of center point"), | |
radius: float = Query(10, description="Search radius in kilometers") | |
): | |
""" | |
Get a map showing all stores in the given radius with colors based on distance | |
""" | |
try: | |
# Get nearby stores | |
nearby_stores = store_locator.find_nearby_stores(lat, lon, radius) | |
# Create base map centered on user location | |
m = folium.Map( | |
location=[lat, lon], | |
zoom_start=12, | |
tiles="cartodbpositron" | |
) | |
# Add user location marker | |
folium.Marker( | |
[lat, lon], | |
popup='Your Location', | |
icon=folium.Icon(color='green', icon='home') | |
).add_to(m) | |
# Add markers for each store with color coding based on distance | |
for store in nearby_stores: | |
# Color code based on distance | |
if store['distance'] <= 2: | |
color = 'red' # Very close | |
elif store['distance'] <= 5: | |
color = 'orange' # Moderate distance | |
else: | |
color = 'blue' # Further away | |
# Create detailed popup content with mobile-friendly styling | |
popup_content = f""" | |
<div style='width: 200px; font-size: 14px;'> | |
<h4 style='color: {color}; margin: 0 0 8px 0;'>{store['store_name']}</h4> | |
<b>Address:</b> {store['address']}<br> | |
<b>Distance:</b> {store['distance']} km<br> | |
<b>Est. Delivery:</b> {store['estimated_delivery_time']} mins<br> | |
<b>Contact:</b> {store['contact']}<br> | |
<b>Categories:</b> {store['product_categories']}<br> | |
<button onclick="window.location.href='/api/stores/route?user_lat={lat}&user_lon={lon}&store_lat={store['location']['lat']}&store_lon={store['location']['lon']}'" | |
style='margin-top: 8px; padding: 8px; width: 100%; background-color: #007bff; color: white; border: none; border-radius: 4px;'> | |
Get Route | |
</button> | |
</div> | |
""" | |
# Add store marker | |
folium.Marker( | |
location=[store['location']['lat'], store['location']['lon']], | |
popup=folium.Popup(popup_content, max_width=300), | |
icon=folium.Icon(color=color, icon='info-sign'), | |
tooltip=f"{store['store_name']} ({store['distance']} km)" | |
).add_to(m) | |
# Add circle to show distance | |
folium.Circle( | |
location=[store['location']['lat'], store['location']['lon']], | |
radius=store['distance'] * 100, | |
color=color, | |
fill=True, | |
opacity=0.1 | |
).add_to(m) | |
# Add distance circles from user location | |
for radius_circle, color in [(2000, 'red'), (5000, 'orange'), (radius * 1000, 'blue')]: | |
folium.Circle( | |
location=[lat, lon], | |
radius=radius_circle, | |
color=color, | |
fill=False, | |
weight=1, | |
dash_array='5, 5' | |
).add_to(m) | |
# Create mobile-friendly HTML content | |
html_content = f""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> | |
<title>Nearby Stores</title> | |
<style> | |
body {{ | |
margin: 0; | |
padding: 0; | |
width: 100vw; | |
height: 100vh; | |
overflow: hidden; | |
}} | |
#map {{ | |
width: 100%; | |
height: 100%; | |
}} | |
.legend {{ | |
position: fixed; | |
bottom: 20px; | |
right: 20px; | |
background: white; | |
padding: 10px; | |
border-radius: 5px; | |
box-shadow: 0 1px 5px rgba(0,0,0,0.2); | |
font-size: 12px; | |
z-index: 1000; | |
}} | |
.info-box {{ | |
position: fixed; | |
top: 20px; | |
left: 20px; | |
background: white; | |
padding: 10px; | |
border-radius: 5px; | |
box-shadow: 0 1px 5px rgba(0,0,0,0.2); | |
font-size: 12px; | |
z-index: 1000; | |
}} | |
</style> | |
</head> | |
<body> | |
{m.get_root().render()} | |
<div class="legend"> | |
<b>Distance Zones</b><br> | |
<span style="color: red;">β</span> < 2 km<br> | |
<span style="color: orange;">β</span> 2-5 km<br> | |
<span style="color: blue;">β</span> > 5 km | |
</div> | |
<div class="info-box"> | |
<b>Search Radius:</b> {radius} km<br> | |
<b>Stores Found:</b> {len(nearby_stores)} | |
</div> | |
<script> | |
window.onload = function() {{ | |
setTimeout(function() {{ | |
window.dispatchEvent(new Event('resize')); | |
}}, 1000); | |
}}; | |
</script> | |
</body> | |
</html> | |
""" | |
# Save and return the file | |
file_path = 'temp/locations_map.html' | |
with open(file_path, 'w', encoding='utf-8') as f: | |
f.write(html_content) | |
return FileResponse(file_path, media_type='text/html') | |
except Exception as e: | |
raise HTTPException(status_code=400, detail=str(e)) | |
# Add swagger UI customization | |
async def startup_event(): | |
app.title = "Store Locator API" | |
app.description = "API for locating nearby stores and generating routes" | |
app.version = "1.0.0" | |
# Entry point for running the application | |
if __name__ == "__main__": | |
import uvicorn | |
uvicorn.run(app, host="0.0.0.0", port=8000) |