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" >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> | |
| https://lucifer7210-maps.hf.space/api/stores/map?lat=18.9701&lon=72.8330&radius=0.5 | |
| </pre> | |
| <p>Shows map centered at Main Street with 500m radius</p> | |
| <h2>3. View All Store Locations with Color Coding</h2> | |
| <pre> | |
| https://lucifer7210-maps.hf.space/api/stores/locations?lat=18.9685&lon=72.8325&radius=2 | |
| </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) |