Zane Falcao commited on
Commit
1e337d7
Β·
1 Parent(s): 063c8a6
Files changed (4) hide show
  1. README.md +37 -0
  2. app.py +887 -5
  3. dataset of 50 stores.csv +36 -0
  4. requirements.txt +11 -0
README.md CHANGED
@@ -7,4 +7,41 @@ sdk: docker
7
  pinned: false
8
  ---
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
7
  pinned: false
8
  ---
9
 
10
+ Based on your uploaded dataset and deployed API, here are example API calls for your Kotlin client:
11
+
12
+ 1. **Find Nearby Stores (JSON Response)**
13
+ ```
14
+ https://maps-yiv5.onrender.com/api/stores/nearby?lat=18.9695&lon=72.8320&radius=1
15
+ ```
16
+ Use this to get store details near Market Road area within 1km
17
+
18
+ 2. **View Basic Store Map**
19
+ ```
20
+ https://maps-yiv5.onrender.com/api/stores/map?lat=18.9701&lon=72.8330&radius=0.5
21
+ ```
22
+ Shows map centered at Main Street with 500m radius
23
+
24
+ 3. **View All Store Locations with Color Coding**
25
+ ```
26
+ https://maps-yiv5.onrender.com/api/stores/locations?lat=18.9685&lon=72.8325&radius=2
27
+ ```
28
+ Shows detailed map with color-coded stores within 2km
29
+
30
+ 4. **Get Route Between Points**
31
+ Example routes:
32
+ ```
33
+ # Route from Park Avenue to Hill Road stores
34
+ https://maps-yiv5.onrender.com/api/stores/route?user_lat=18.9710&user_lon=72.8335&store_lat=18.9705&store_lon=72.8345&viz_type=simple
35
+
36
+ # Route from Main Street to Market Road stores
37
+ https://maps-yiv5.onrender.com/api/stores/route?user_lat=18.9701&user_lon=72.8330&store_lat=18.9695&store_lon=72.8320&viz_type=simple
38
+ ```
39
+
40
+ Key Location Points in Dataset:
41
+ - Main Street Area: 18.9701, 72.8330
42
+ - Park Avenue: 18.9710, 72.8335
43
+ - Market Road: 18.9695, 72.8320
44
+ - Shopping Center: 18.9670, 72.8300
45
+ - Commercial Street: 18.9690, 72.8340
46
+
47
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,7 +1,889 @@
1
- from fastapi import FastAPI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- app = FastAPI()
4
 
5
- @app.get("/")
6
- def greet_json():
7
- return {"Hello": "World!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Query, HTTPException, Response
2
+ from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ import pandas as pd
5
+ import numpy as np
6
+ from geopy.distance import geodesic
7
+ import folium
8
+ from folium import plugins
9
+ import osmnx as ox
10
+ import networkx as nx
11
+ from datetime import datetime
12
+ import json
13
+ import matplotlib.pyplot as plt
14
+ import plotly.express as px
15
+ import os
16
+ import time
17
+ from functools import lru_cache
18
+ from rtree import index
19
+ import gc
20
+ import shutil
21
+ from typing import Optional, List, Dict, Any, Union
22
+ from pydantic import BaseModel, Field
23
 
24
+ app = FastAPI(title="falcao-maps API", description="Store locator and route planning API")
25
 
26
+ # Add CORS middleware
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=["*"],
30
+ allow_credentials=True,
31
+ allow_methods=["*"],
32
+ allow_headers=["*"],
33
+ )
34
+
35
+ # Create temp directory for files
36
+ os.makedirs('temp', exist_ok=True)
37
+
38
+ # Custom JSON encoder for NumPy types
39
+ class NumpyEncoder(json.JSONEncoder):
40
+ def default(self, obj):
41
+ if isinstance(obj, np.integer):
42
+ return int(obj)
43
+ elif isinstance(obj, np.floating):
44
+ return float(obj)
45
+ elif isinstance(obj, np.ndarray):
46
+ return obj.tolist()
47
+ return super(NumpyEncoder, self).default(obj)
48
+
49
+ # Load and prepare the store data
50
+ stores_df = pd.read_csv('dataset of 50 stores.csv')
51
+
52
+ # Define Pydantic models for API responses
53
+ class Location(BaseModel):
54
+ lat: float
55
+ lon: float
56
+
57
+ class Store(BaseModel):
58
+ store_name: str
59
+ address: str
60
+ contact: str
61
+ distance: float
62
+ estimated_delivery_time: int
63
+ product_categories: str
64
+ location: Location
65
+
66
+ class StoresResponse(BaseModel):
67
+ status: str
68
+ stores: List[Store]
69
+
70
+ class ErrorResponse(BaseModel):
71
+ status: str
72
+ message: str
73
+
74
+ class StoreLocator:
75
+ def __init__(self, stores_dataframe):
76
+ self.stores_df = stores_dataframe
77
+ self.network_graph = None
78
+ self.graph_cache = {} # Cache for network graphs
79
+ self.spatial_index = self._build_spatial_index()
80
+
81
+ @lru_cache(maxsize=50)
82
+ def initialize_graph(self, center_point, dist=10000): # Reduced distance for memory optimization
83
+ """Initialize road network graph with caching"""
84
+ cache_key = f"{center_point[0]}_{center_point[1]}"
85
+ if cache_key in self.graph_cache:
86
+ self.network_graph = self.graph_cache[cache_key]
87
+ return True
88
+ try:
89
+ # Use simplify=True and increased tolerance for lower memory usage
90
+ self.network_graph = ox.graph_from_point(
91
+ center_point,
92
+ dist=dist,
93
+ network_type="drive",
94
+ simplify=True,
95
+ retain_all=False
96
+ )
97
+ self.network_graph = ox.add_edge_speeds(self.network_graph)
98
+ self.network_graph = ox.add_edge_travel_times(self.network_graph)
99
+
100
+ # Store in cache
101
+ self.graph_cache[cache_key] = self.network_graph
102
+
103
+ # Force garbage collection
104
+ gc.collect()
105
+
106
+ return True
107
+ except Exception as e:
108
+ print(f"Error initializing graph: {str(e)}")
109
+ return False
110
+
111
+ def _build_spatial_index(self):
112
+ idx = index.Index()
113
+ for i, row in self.stores_df.iterrows():
114
+ idx.insert(i, (row['Latitude'], row['Longitude'],
115
+ row['Latitude'], row['Longitude']))
116
+ return idx
117
+
118
+ def calculate_distance(self, lat1, lon1, lat2, lon2):
119
+ """Calculate direct distance between two points"""
120
+ return geodesic((lat1, lon1), (lat2, lon2)).kilometers
121
+
122
+ def estimate_delivery_time(self, distance, current_time=None):
123
+ """Estimate delivery time based on distance and current time"""
124
+ if current_time is None:
125
+ current_time = datetime.now()
126
+
127
+ # Base time: 5 mins base + 2 mins per km
128
+ base_minutes = 5 + (distance * 2)
129
+
130
+ # Apply traffic multiplier based on time of day
131
+ hour = current_time.hour
132
+ if hour in [8, 9, 10, 17, 18, 19]: # Peak hours
133
+ multiplier = 1.5
134
+ elif hour in [23, 0, 1, 2, 3, 4]: # Off-peak hours
135
+ multiplier = 0.8
136
+ else: # Normal hours
137
+ multiplier = 1.0
138
+
139
+ return round(base_minutes * multiplier)
140
+
141
+ def find_nearby_stores(self, lat, lon, radius=5):
142
+ """Find stores within radius using spatial index"""
143
+ nearby_stores = []
144
+ bbox = (lat - radius/111.0, lon - radius/111.0,
145
+ lat + radius/111.0, lon + radius/111.0)
146
+
147
+ for store_id in self.spatial_index.intersection(bbox):
148
+ store = self.stores_df.iloc[store_id]
149
+ distance = self.calculate_distance(lat, lon,
150
+ store['Latitude'],
151
+ store['Longitude'])
152
+ if distance <= radius:
153
+ delivery_time = self.estimate_delivery_time(distance)
154
+ nearby_stores.append({
155
+ 'store_name': store['Store Name'],
156
+ 'address': store['Address'],
157
+ 'contact': str(store['Contact Number']), # Convert to string to avoid int64 issues
158
+ 'distance': round(distance, 2),
159
+ 'estimated_delivery_time': int(delivery_time), # Ensure integer type
160
+ 'product_categories': store['Product Categories'],
161
+ 'location': {
162
+ 'lat': float(store['Latitude']), # Ensure float type
163
+ 'lon': float(store['Longitude']) # Ensure float type
164
+ }
165
+ })
166
+
167
+ return sorted(nearby_stores, key=lambda x: x['distance'])
168
+
169
+ def create_store_map(self, center_lat, center_lon, radius=5):
170
+ """Create an interactive map with store locations - optimized for memory"""
171
+ # Create base map
172
+ m = folium.Map(
173
+ location=[center_lat, center_lon],
174
+ zoom_start=13,
175
+ tiles="cartodbpositron"
176
+ )
177
+
178
+ # Create marker cluster for better performance with many markers
179
+ marker_cluster = plugins.MarkerCluster().add_to(m)
180
+
181
+ # Add stores to map
182
+ nearby_stores = self.find_nearby_stores(center_lat, center_lon, radius)
183
+
184
+ # Limit the number of stores to reduce memory usage
185
+ max_stores = min(len(nearby_stores), 50) # Cap at 50 stores
186
+
187
+ for store in nearby_stores[:max_stores]:
188
+ # Prepare popup content
189
+ popup_content = f"""
190
+ <div style='width: 200px'>
191
+ <b>{store['store_name']}</b><br>
192
+ Address: {store['address']}<br>
193
+ Distance: {store['distance']} km<br>
194
+ Est. Delivery: {store['estimated_delivery_time']} mins<br>
195
+ Categories: {store['product_categories']}
196
+ </div>
197
+ """
198
+
199
+ # Add store marker
200
+ folium.Marker(
201
+ location=[store['location']['lat'], store['location']['lon']],
202
+ popup=folium.Popup(popup_content, max_width=300),
203
+ icon=folium.Icon(color='red', icon='info-sign')
204
+ ).add_to(marker_cluster)
205
+
206
+ # Add line to show distance from center (only for closer stores)
207
+ if store['distance'] <= nearby_stores[min(9, len(nearby_stores)-1)]['distance']:
208
+ folium.PolyLine(
209
+ locations=[[center_lat, center_lon],
210
+ [store['location']['lat'], store['location']['lon']]],
211
+ weight=2,
212
+ color='blue',
213
+ opacity=0.3
214
+ ).add_to(m)
215
+
216
+ # Add current location marker
217
+ folium.Marker(
218
+ location=[center_lat, center_lon],
219
+ popup='Your Location',
220
+ icon=folium.Icon(color='green', icon='home')
221
+ ).add_to(m)
222
+
223
+ # Add layer control only
224
+ folium.LayerControl().add_to(m)
225
+
226
+ return m
227
+
228
+ # Initialize store locator
229
+ store_locator = StoreLocator(stores_df)
230
+
231
+ # Helper functions for cleaning temporary files
232
+ def cleanup_temp_files():
233
+ temp_dir = 'temp'
234
+ if os.path.exists(temp_dir):
235
+ for file in os.listdir(temp_dir):
236
+ file_path = os.path.join(temp_dir, file)
237
+ try:
238
+ if os.path.isfile(file_path) and file.endswith('.html'):
239
+ # Delete files older than 1 hour
240
+ if os.path.getmtime(file_path) < time.time() - 3600:
241
+ os.remove(file_path)
242
+ except Exception as e:
243
+ print(f"Error cleaning up temp files: {e}")
244
+
245
+ # Register cleanup on startup and shutdown
246
+ @app.on_event("startup")
247
+ async def startup_event():
248
+ cleanup_temp_files()
249
+
250
+ @app.on_event("shutdown")
251
+ async def shutdown_event():
252
+ # Clean up all temporary files on shutdown
253
+ try:
254
+ shutil.rmtree('temp')
255
+ except Exception as e:
256
+ print(f"Error cleaning up temp directory: {e}")
257
+
258
+ # Routes
259
+ @app.get("/", response_class=HTMLResponse)
260
+ async def home():
261
+ """API Documentation Homepage"""
262
+ html_content = f"""
263
+ <!DOCTYPE html>
264
+ <html lang="en">
265
+ <head>
266
+ <meta charset="UTF-8">
267
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
268
+ <title>falcao-maps API Documentation</title>
269
+ <style>
270
+ body {{
271
+ font-family: Arial, sans-serif;
272
+ margin: 20px;
273
+ }}
274
+ h1, h2 {{
275
+ color: #333;
276
+ }}
277
+ pre {{
278
+ background-color: #f4f4f4;
279
+ padding: 10px;
280
+ border: 1px solid #ddd;
281
+ border-radius: 5px;
282
+ }}
283
+ </style>
284
+ </head>
285
+ <body>
286
+ <h1>Welcome to falcao-maps</h1>
287
+ <p>Based on your uploaded dataset and deployed API, here are example API calls for your client:</p>
288
+
289
+ <h2>1. Find Nearby Stores (JSON Response)</h2>
290
+ <pre>
291
+ /api/stores/nearby?lat=18.9695&lon=72.8320&radius=1
292
+ </pre>
293
+ <p>Use this to get store details near Market Road area within 1km</p>
294
+
295
+ <h2>2. View Basic Store Map</h2>
296
+ <pre>
297
+ /api/stores/map?lat=18.9701&lon=72.8330&radius=0.5
298
+ </pre>
299
+ <p>Shows map centered at Main Street with 500m radius</p>
300
+
301
+ <h2>3. View All Store Locations with Color Coding</h2>
302
+ <pre>
303
+ /api/stores/locations?lat=18.9685&lon=72.8325&radius=2
304
+ </pre>
305
+ <p>Shows detailed map with color-coded stores within 2km</p>
306
+
307
+ <h2>4. Get Route Between Points</h2>
308
+ <p>Example routes:</p>
309
+ <pre>
310
+ # Route from Park Avenue to Hill Road stores (use simple visualization for memory optimization)
311
+ /api/stores/route?user_lat=18.9710&user_lon=72.8335&store_lat=18.9705&store_lon=72.8345&viz_type=simple
312
+
313
+ # Route from Main Street to Market Road stores
314
+ /api/stores/route?user_lat=18.9701&user_lon=72.8330&store_lat=18.9695&store_lon=72.8320&viz_type=simple
315
+ </pre>
316
+
317
+ <h2>Key Location Points in Dataset:</h2>
318
+ <ul>
319
+ <li>Main Street Area: 18.9701, 72.8330</li>
320
+ <li>Park Avenue: 18.9710, 72.8335</li>
321
+ <li>Market Road: 18.9695, 72.8320</li>
322
+ <li>Shopping Center: 18.9670, 72.8300</li>
323
+ <li>Commercial Street: 18.9690, 72.8340</li>
324
+ </ul>
325
+
326
+ <h2>API Documentation</h2>
327
+ <p>You can view the interactive API documentation at: <a href="/docs">/docs</a></p>
328
+ </body>
329
+ </html>
330
+ """
331
+ return html_content
332
+
333
+ @app.get("/api/stores/nearby", response_model=StoresResponse, responses={400: {"model": ErrorResponse}})
334
+ async def get_nearby_stores(
335
+ lat: float = Query(..., description="Latitude of user location"),
336
+ lon: float = Query(..., description="Longitude of user location"),
337
+ radius: float = Query(5.0, description="Search radius in kilometers")
338
+ ):
339
+ """Get nearby stores based on user location"""
340
+ try:
341
+ nearby_stores = store_locator.find_nearby_stores(lat, lon, radius)
342
+ return {"status": "success", "stores": nearby_stores}
343
+ except Exception as e:
344
+ raise HTTPException(status_code=400, detail=str(e))
345
+
346
+ @app.get("/api/stores/map", response_class=HTMLResponse, responses={400: {"model": ErrorResponse}})
347
+ async def get_stores_map(
348
+ lat: float = Query(..., description="Latitude of center point"),
349
+ lon: float = Query(..., description="Longitude of center point"),
350
+ radius: float = Query(5.0, description="Search radius in kilometers")
351
+ ):
352
+ """Get HTML map with store locations"""
353
+ try:
354
+ # Clean up temp files before creating new ones
355
+ cleanup_temp_files()
356
+
357
+ store_map = store_locator.create_store_map(lat, lon, radius)
358
+
359
+ # Create complete HTML content
360
+ html_content = f"""
361
+ <!DOCTYPE html>
362
+ <html>
363
+ <head>
364
+ <meta charset="utf-8">
365
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
366
+ <title>Stores Map</title>
367
+ <style>
368
+ body {{
369
+ margin: 0;
370
+ padding: 0;
371
+ width: 100vw;
372
+ height: 100vh;
373
+ overflow: hidden;
374
+ }}
375
+ #map {{
376
+ width: 100%;
377
+ height: 100%;
378
+ }}
379
+ </style>
380
+ </head>
381
+ <body>
382
+ {store_map.get_root().render()}
383
+ <script>
384
+ window.onload = function() {{
385
+ setTimeout(function() {{
386
+ window.dispatchEvent(new Event('resize'));
387
+ }}, 1000);
388
+ }};
389
+ </script>
390
+ </body>
391
+ </html>
392
+ """
393
+
394
+ # Save the HTML to a file
395
+ file_path = 'temp/stores_map.html'
396
+ with open(file_path, 'w', encoding='utf-8') as f:
397
+ f.write(html_content)
398
+
399
+ # Return the file as HTML response
400
+ return html_content
401
+
402
+ except Exception as e:
403
+ raise HTTPException(status_code=400, detail=str(e))
404
+
405
+ @app.get("/api/stores/route", response_class=HTMLResponse, responses={400: {"model": ErrorResponse}, 404: {"model": ErrorResponse}})
406
+ async def get_store_route(
407
+ user_lat: float = Query(..., description="User location latitude"),
408
+ user_lon: float = Query(..., description="User location longitude"),
409
+ store_lat: float = Query(..., description="Store location latitude"),
410
+ store_lon: float = Query(..., description="Store location longitude"),
411
+ viz_type: str = Query("simple", description="Visualization type (simple or advanced)")
412
+ ):
413
+ """Get route between user and store locations with visualization"""
414
+ try:
415
+ # Clean up temp files before creating new ones
416
+ cleanup_temp_files()
417
+
418
+ # Initialize graph if not already initialized
419
+ # Use a smaller distance to reduce memory usage
420
+ if store_locator.network_graph is None:
421
+ success = store_locator.initialize_graph((user_lat, user_lon), dist=10000)
422
+ if not success:
423
+ raise HTTPException(status_code=400, detail="Unable to initialize graph, try a different location")
424
+
425
+ # Get nearest nodes
426
+ start_node = ox.distance.nearest_nodes(
427
+ store_locator.network_graph, user_lon, user_lat)
428
+ end_node = ox.distance.nearest_nodes(
429
+ store_locator.network_graph, store_lon, store_lat)
430
+
431
+ try:
432
+ # Calculate path using the travel_time weight
433
+ path_time = nx.shortest_path(
434
+ store_locator.network_graph,
435
+ start_node,
436
+ end_node,
437
+ weight='travel_time'
438
+ )
439
+
440
+ if viz_type == "simple":
441
+ # Create a simple folium map for low-resource environments
442
+ m = folium.Map(
443
+ location=[(user_lat + store_lat) / 2, (user_lon + store_lon) / 2],
444
+ zoom_start=15,
445
+ tiles="cartodbpositron"
446
+ )
447
+
448
+ # Add markers for start and end points
449
+ folium.Marker(
450
+ [user_lat, user_lon],
451
+ popup='Your Location',
452
+ icon=folium.Icon(color='green', icon='home')
453
+ ).add_to(m)
454
+
455
+ folium.Marker(
456
+ [store_lat, store_lon],
457
+ popup='Store Location',
458
+ icon=folium.Icon(color='red', icon='info-sign')
459
+ ).add_to(m)
460
+
461
+ # Extract coordinates from the path
462
+ path_coords = []
463
+ for node in path_time:
464
+ x = store_locator.network_graph.nodes[node]['x']
465
+ y = store_locator.network_graph.nodes[node]['y']
466
+ path_coords.append([y, x]) # Note the y, x order for folium
467
+
468
+ # Add the route line
469
+ folium.PolyLine(
470
+ locations=path_coords,
471
+ weight=5,
472
+ color='blue',
473
+ opacity=0.7
474
+ ).add_to(m)
475
+
476
+ # Add distance and time estimate
477
+ total_distance = 0
478
+ total_time = 0
479
+
480
+ for i in range(len(path_time) - 1):
481
+ a, b = path_time[i], path_time[i + 1]
482
+ total_distance += store_locator.network_graph.edges[(a, b, 0)]['length']
483
+ total_time += store_locator.network_graph.edges[(a, b, 0)]['travel_time']
484
+
485
+ # Convert to km and minutes
486
+ total_distance_km = round(total_distance / 1000, 2)
487
+ total_time_min = round(total_time / 60, 1)
488
+
489
+ # Add info box
490
+ html_content = f"""
491
+ <div style="position: fixed; top: 10px; left: 50px; z-index: 9999;
492
+ background-color: white; padding: 10px; border-radius: 5px;
493
+ box-shadow: 0 0 10px rgba(0,0,0,0.3);">
494
+ <h4 style="margin: 0 0 5px 0;">Route Information</h4>
495
+ <p><b>Distance:</b> {total_distance_km} km<br>
496
+ <b>Est. Time:</b> {total_time_min} minutes</p>
497
+ </div>
498
+ """
499
+
500
+ m.get_root().html.add_child(folium.Element(html_content))
501
+
502
+ # Add layer control
503
+ folium.LayerControl().add_to(m)
504
+
505
+ # Create complete HTML content
506
+ html_content = f"""
507
+ <!DOCTYPE html>
508
+ <html>
509
+ <head>
510
+ <meta charset="utf-8">
511
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
512
+ <title>Simple Route Map</title>
513
+ <style>
514
+ body {{
515
+ margin: 0;
516
+ padding: 0;
517
+ width: 100vw;
518
+ height: 100vh;
519
+ overflow: hidden;
520
+ }}
521
+ #map {{
522
+ width: 100%;
523
+ height: 100%;
524
+ }}
525
+ </style>
526
+ </head>
527
+ <body>
528
+ {m.get_root().render()}
529
+ <script>
530
+ window.onload = function() {{
531
+ setTimeout(function() {{
532
+ window.dispatchEvent(new Event('resize'));
533
+ }}, 1000);
534
+ }};
535
+ </script>
536
+ </body>
537
+ </html>
538
+ """
539
+
540
+ # Save the HTML to a file
541
+ file_path = 'temp/simple_route_map.html'
542
+ with open(file_path, 'w', encoding='utf-8') as f:
543
+ f.write(html_content)
544
+
545
+ # Return the HTML content
546
+ return html_content
547
+
548
+ else:
549
+ # WARNING: Advanced visualization - may cause memory issues on limited resources
550
+
551
+ # Limit the path nodes to reduce memory usage
552
+ # Only include every Nth node
553
+ step = max(1, len(path_time) // 30) # Maximum 30 points
554
+ simplified_path = path_time[::step]
555
+ if path_time[-1] not in simplified_path:
556
+ simplified_path.append(path_time[-1])
557
+
558
+ # Create animation data (simplified)
559
+ lst_start, lst_end = [], []
560
+ start_x, start_y = [], []
561
+ end_x, end_y = [], []
562
+ lst_length, lst_time = [], []
563
+
564
+ for a, b in zip(simplified_path[:-1], simplified_path[1:]):
565
+ lst_start.append(a)
566
+ lst_end.append(b)
567
+
568
+ # Calculate accumulated length and time between simplified points
569
+ segment_length = 0
570
+ segment_time = 0
571
+ path_segment = nx.shortest_path(
572
+ store_locator.network_graph, a, b, weight='travel_time')
573
+
574
+ for i in range(len(path_segment) - 1):
575
+ u, v = path_segment[i], path_segment[i + 1]
576
+ segment_length += store_locator.network_graph.edges[(u, v, 0)]['length']
577
+ segment_time += store_locator.network_graph.edges[(u, v, 0)]['travel_time']
578
+
579
+ lst_length.append(round(segment_length))
580
+ lst_time.append(round(segment_time))
581
+ start_x.append(store_locator.network_graph.nodes[a]['x'])
582
+ start_y.append(store_locator.network_graph.nodes[a]['y'])
583
+ end_x.append(store_locator.network_graph.nodes[b]['x'])
584
+ end_y.append(store_locator.network_graph.nodes[b]['y'])
585
+
586
+ df = pd.DataFrame(
587
+ list(zip(lst_start, lst_end, start_x, start_y, end_x, end_y,
588
+ lst_length, lst_time)),
589
+ columns=["start", "end", "start_x", "start_y",
590
+ "end_x", "end_y", "length", "travel_time"]
591
+ ).reset_index().rename(columns={"index": "id"})
592
+
593
+ # Create animation using plotly (reduced complexity)
594
+ df_start = df[df["start"] == lst_start[0]]
595
+ df_end = df[df["end"] == lst_end[-1]]
596
+
597
+ fig = px.scatter_mapbox(
598
+ data_frame=df,
599
+ lon="start_x",
600
+ lat="start_y",
601
+ zoom=15,
602
+ width=800, # Reduced size
603
+ height=600, # Reduced size
604
+ animation_frame="id",
605
+ mapbox_style="carto-positron"
606
+ )
607
+
608
+ # Basic visualization elements only
609
+ fig.data[0].marker = {"size": 12}
610
+
611
+ # Add start point
612
+ fig.add_trace(
613
+ px.scatter_mapbox(
614
+ data_frame=df_start,
615
+ lon="start_x",
616
+ lat="start_y"
617
+ ).data[0]
618
+ )
619
+ fig.data[1].marker = {"size": 15, "color": "red"}
620
+
621
+ # Add end point
622
+ fig.add_trace(
623
+ px.scatter_mapbox(
624
+ data_frame=df_end,
625
+ lon="start_x",
626
+ lat="start_y"
627
+ ).data[0]
628
+ )
629
+ fig.data[2].marker = {"size": 15, "color": "green"}
630
+
631
+ # Add route
632
+ fig.add_trace(
633
+ px.line_mapbox(
634
+ data_frame=df,
635
+ lon="start_x",
636
+ lat="start_y"
637
+ ).data[0]
638
+ )
639
+
640
+ # Simplified layout with fewer options to reduce complexity
641
+ fig.update_layout(
642
+ showlegend=False,
643
+ margin={"r":0,"t":0,"l":0,"b":0},
644
+ autosize=True,
645
+ height=None
646
+ )
647
+
648
+ # Create complete HTML content
649
+ html_content = f"""
650
+ <!DOCTYPE html>
651
+ <html>
652
+ <head>
653
+ <meta charset="utf-8">
654
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
655
+ <title>Route Map</title>
656
+ <style>
657
+ body {{
658
+ margin: 0;
659
+ padding: 0;
660
+ width: 100vw;
661
+ height: 100vh;
662
+ overflow: hidden;
663
+ }}
664
+ #map-container {{
665
+ width: 100%;
666
+ height: 100%;
667
+ }}
668
+ </style>
669
+ </head>
670
+ <body>
671
+ <div id="map-container">
672
+ {fig.to_html(include_plotlyjs=True, full_html=False, config={'staticPlot': True})}
673
+ </div>
674
+ <script>
675
+ window.onload = function() {{
676
+ setTimeout(function() {{
677
+ window.dispatchEvent(new Event('resize'));
678
+ }}, 1000);
679
+ }};
680
+ </script>
681
+ </body>
682
+ </html>
683
+ """
684
+
685
+ # Save the HTML to a file
686
+ file_path = 'temp/route_map.html'
687
+ with open(file_path, 'w', encoding='utf-8') as f:
688
+ f.write(html_content)
689
+
690
+ # Return the HTML content
691
+ return html_content
692
+
693
+ except nx.NetworkXNoPath:
694
+ raise HTTPException(status_code=404, detail="No route found")
695
+
696
+ except HTTPException:
697
+ raise
698
+ except Exception as e:
699
+ raise HTTPException(status_code=400, detail=str(e))
700
+
701
+ @app.get("/api/stores/locations", response_class=HTMLResponse, responses={400: {"model": ErrorResponse}})
702
+ async def get_all_store_locations(
703
+ lat: float = Query(..., description="Latitude of center point"),
704
+ lon: float = Query(..., description="Longitude of center point"),
705
+ radius: float = Query(10.0, description="Search radius in kilometers")
706
+ ):
707
+ """Get a map showing all stores in the given radius with colors based on distance"""
708
+ try:
709
+ # Clean up temp files before creating new ones
710
+ cleanup_temp_files()
711
+
712
+ # Get nearby stores
713
+ nearby_stores = store_locator.find_nearby_stores(lat, lon, radius)
714
+
715
+ # Limit number of stores for memory optimization
716
+ max_stores = min(len(nearby_stores), 50)
717
+ nearby_stores = nearby_stores[:max_stores]
718
+
719
+ # Create base map centered on user location
720
+ m = folium.Map(
721
+ location=[lat, lon],
722
+ zoom_start=12,
723
+ tiles="cartodbpositron"
724
+ )
725
+
726
+ # Add user location marker
727
+ folium.Marker(
728
+ [lat, lon],
729
+ popup='Your Location',
730
+ icon=folium.Icon(color='green', icon='home')
731
+ ).add_to(m)
732
+
733
+ # Add markers for each store with color coding based on distance
734
+ for store in nearby_stores:
735
+ # Color code based on distance
736
+ if store['distance'] <= 2:
737
+ color = 'red' # Very close
738
+ elif store['distance'] <= 5:
739
+ color = 'orange' # Moderate distance
740
+ else:
741
+ color = 'blue' # Further away
742
+
743
+ # Create simplified popup content
744
+ popup_content = f"""
745
+ <div style='width: 200px; font-size: 14px;'>
746
+ <h4 style='color: {color}; margin: 0 0 8px 0;'>{store['store_name']}</h4>
747
+ <b>Distance:</b> {store['distance']} km<br>
748
+ <b>Est. Delivery:</b> {store['estimated_delivery_time']} mins<br>
749
+ <b>Categories:</b> {store['product_categories']}<br>
750
+ <button onclick="window.location.href='/api/stores/route?user_lat={lat}&user_lon={lon}&store_lat={store['location']['lat']}&store_lon={store['location']['lon']}&viz_type=simple'"
751
+ style='margin-top: 8px; padding: 8px; width: 100%; background-color: #007bff; color: white; border: none; border-radius: 4px;'>
752
+ Get Route
753
+ </button>
754
+ </div>
755
+ """
756
+
757
+ # Add store marker
758
+ folium.Marker(
759
+ location=[store['location']['lat'], store['location']['lon']],
760
+ popup=folium.Popup(popup_content, max_width=300),
761
+ icon=folium.Icon(color=color, icon='info-sign'),
762
+ tooltip=f"{store['store_name']} ({store['distance']} km)"
763
+ ).add_to(m)
764
+
765
+ # Add circle to show distance - only for closer stores to reduce complexity
766
+ if store['distance'] <= 5:
767
+ folium.Circle(
768
+ location=[store['location']['lat'], store['location']['lon']],
769
+ radius=store['distance'] * 100,
770
+ color=color,
771
+ fill=True,
772
+ opacity=0.1
773
+ ).add_to(m)
774
+
775
+ # Add distance circles from user location - reduced to save memory
776
+ for circle_radius, color in [(2000, 'red'), (5000, 'orange')]:
777
+ folium.Circle(
778
+ location=[lat, lon],
779
+ radius=circle_radius,
780
+ color=color,
781
+ fill=False,
782
+ weight=1,dash_array='5, 5'
783
+ ).add_to(m)
784
+
785
+ # Create mobile-friendly HTML content
786
+ html_content = f"""
787
+ <!DOCTYPE html>
788
+ <html>
789
+ <head>
790
+ <meta charset="utf-8">
791
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
792
+ <title>Nearby Stores</title>
793
+ <style>
794
+ body {{
795
+ margin: 0;
796
+ padding: 0;
797
+ width: 100vw;
798
+ height: 100vh;
799
+ overflow: hidden;
800
+ }}
801
+ #map {{
802
+ width: 100%;
803
+ height: 100%;
804
+ }}
805
+ .legend {{
806
+ position: fixed;
807
+ bottom: 20px;
808
+ right: 20px;
809
+ background: white;
810
+ padding: 10px;
811
+ border-radius: 5px;
812
+ box-shadow: 0 1px 5px rgba(0,0,0,0.2);
813
+ font-size: 12px;
814
+ z-index: 1000;
815
+ }}
816
+ .info-box {{
817
+ position: fixed;
818
+ top: 20px;
819
+ left: 20px;
820
+ background: white;
821
+ padding: 10px;
822
+ border-radius: 5px;
823
+ box-shadow: 0 1px 5px rgba(0,0,0,0.2);
824
+ font-size: 12px;
825
+ z-index: 1000;
826
+ }}
827
+ </style>
828
+ </head>
829
+ <body>
830
+ {m.get_root().render()}
831
+ <div class="legend">
832
+ <b>Distance Zones</b><br>
833
+ <span style="color: red;">●</span> &lt; 2 km<br>
834
+ <span style="color: orange;">●</span> 2-5 km<br>
835
+ <span style="color: blue;">●</span> &gt; 5 km
836
+ </div>
837
+ <div class="info-box">
838
+ <b>Search Radius:</b> {radius} km<br>
839
+ <b>Stores Found:</b> {len(nearby_stores)}
840
+ </div>
841
+ <script>
842
+ window.onload = function() {{
843
+ setTimeout(function() {{
844
+ window.dispatchEvent(new Event('resize'));
845
+ }}, 1000);
846
+ }};
847
+ </script>
848
+ </body>
849
+ </html>
850
+ """
851
+
852
+ # Save and return the file
853
+ file_path = 'temp/locations_map.html'
854
+ with open(file_path, 'w', encoding='utf-8') as f:
855
+ f.write(html_content)
856
+
857
+ # Return the HTML content
858
+ return html_content
859
+
860
+ except Exception as e:
861
+ raise HTTPException(status_code=400, detail=str(e))
862
+
863
+ # Add endpoint to serve static files directly
864
+ @app.get("/temp/{file_path:path}", response_class=FileResponse)
865
+ async def get_temp_file(file_path: str):
866
+ """Serve temporary files like HTML maps"""
867
+ full_path = os.path.join("temp", file_path)
868
+ if not os.path.exists(full_path):
869
+ raise HTTPException(status_code=404, detail="File not found")
870
+ return FileResponse(full_path)
871
+
872
+ # Add memory monitoring and management middleware
873
+ @app.middleware("http")
874
+ async def add_memory_management(request: Request, call_next):
875
+ # Cleanup before processing request
876
+ cleanup_temp_files()
877
+
878
+ # Process the request
879
+ response = await call_next(request)
880
+
881
+ # Cleanup after processing request
882
+ gc.collect()
883
+
884
+ return response
885
+
886
+ # For running the application directly (development mode)
887
+ if __name__ == "__main__":
888
+ import uvicorn
889
+ uvicorn.run(app, host="0.0.0.0", port=8000)
dataset of 50 stores.csv ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Store Name,Address,Contact Number,Latitude,Longitude,Product Categories
2
+ Kirana Store A,"123 Main Street, Mumbai",9876543210,18.9701,72.8330,"Groceries, Dairy, Beverages, Snacks"
3
+ Kirana Store B,"456 Market Road, Mumbai",9876543211,18.9695,72.8320,"Groceries, Dairy, Beverages, Snacks, Household"
4
+ Kirana Store C,"789 Shopping Center, Mumbai",9876543212,18.9670,72.8300,"Groceries, Dairy, Beverages, Snacks, Fruits, Vegetables"
5
+ Kirana Store D,"1011 Commercial Street, Mumbai",9876543213,18.9690,72.8340,"Groceries, Dairy, Beverages, Snacks, Household, Electronics"
6
+ Kirana Store E,"1213 Residential Lane, Mumbai",9876543214,18.9680,72.8290,"Groceries, Dairy, Beverages, Snacks, Fruits, Vegetables, Meat"
7
+ Kirana Store F,"1415 Park Avenue, Mumbai",9876543215,18.9710,72.8335,"Groceries, Dairy, Beverages, Snacks, Bakery"
8
+ Kirana Store G,"1617 Hill Road, Mumbai",9876543216,18.9705,72.8345,"Groceries, Dairy, Beverages, Snacks, Personal Care"
9
+ Kirana Store H,"1819 Beach Road, Mumbai",9876543217,18.9675,72.8315,"Groceries, Dairy, Beverages, Snacks, Seafood"
10
+ Kirana Store I,"2021 Market Lane, Mumbai",9876543218,18.9695,72.8325,"Groceries, Dairy, Beverages, Snacks, Pet Supplies"
11
+ Kirana Store J,"2223 Station Road, Mumbai",9876543219,18.9685,72.8295,"Groceries, Dairy, Beverages, Snacks, Stationery"
12
+ Kirana Store K,"2425 School Street, Mumbai",9876543220,18.9700,72.8340,"Groceries, Dairy, Beverages, Snacks, Toys"
13
+ Kirana Store L,"2627 Hospital Road, Mumbai",9876543221,18.9675,72.8320,"Groceries, Dairy, Beverages, Snacks, Pharmaceuticals"
14
+ Kirana Store M,"2829 College Street, Mumbai",9876543222,18.9690,72.8310,"Groceries, Dairy, Beverages, Snacks, Books"
15
+ Kirana Store N,"3031 Library Road, Mumbai",9876543223,18.9710,72.8345,"Groceries, Dairy, Beverages, Snacks, Gifts"
16
+ Kirana Store O,"3233 Park Lane, Mumbai",9876543224,18.9705,72.8355,"Groceries, Dairy, Beverages, Snacks, Flowers"
17
+ Kirana Store P,"3435 River Road, Mumbai",9876543225,18.9680,72.8330,"Groceries, Dairy, Beverages, Snacks, Sweets"
18
+ Kirana Store Q,"3637 Hill View, Mumbai",9876543226,18.9695,72.8340,"Groceries, Dairy, Beverages, Snacks, Organic Products"
19
+ Kirana Store R,"3839 Ocean Drive, Mumbai",9876543227,18.9670,72.8290,"Groceries, Dairy, Beverages, Snacks, Seafood"
20
+ Kirana Store S,"4041 Green Street, Mumbai",9876543228,18.9685,72.8335,"Groceries, Dairy, Beverages, Snacks, Home Decor"
21
+ Kirana Store T,"4243 Market Place, Mumbai",9876543229,18.9700,72.8325,"Groceries, Dairy, Beverages, Snacks, Cosmetics"
22
+ Kirana Store U,"4445 Town Square, Mumbai",9876543230,18.9665,72.8315,"Groceries, Dairy, Beverages, Snacks, Electronics"
23
+ Kirana Store V,"4647 City Center, Mumbai",9876543231,18.9680,72.8305,"Groceries, Dairy, Beverages, Snacks, Furniture"
24
+ Kirana Store W,"4849 Parkside Drive, Mumbai",9876543232,18.9695,72.8330,"Groceries, Dairy, Beverages, Snacks, Hardware"
25
+ Kirana Store X,"5051 Riverside Lane, Mumbai",9876543233,18.9675,72.8300,"Groceries, Dairy, Beverages, Snacks, Bakery"
26
+ Kirana Store Y,"5253 Hillcrest Road, Mumbai",9876543234,18.9705,72.8320,"Groceries, Dairy, Beverages, Snacks, Personal Care"
27
+ Kirana Store Z,"5455 Beach Walk, Mumbai",9876543235,18.9680,72.8295,"Groceries, Dairy, Beverages, Snacks, Seafood"
28
+ Kirana Store AA,"5657 Market Place, Mumbai",9876543236,18.9690,72.8335,"Groceries, Dairy, Beverages, Snacks, Pet Supplies"
29
+ Kirana Store BB,"5859 Station Square, Mumbai",9876543237,18.9675,72.8305,"Groceries, Dairy, Beverages, Snacks, Stationery"
30
+ Kirana Store CC,"6061 School Lane, Mumbai",9876543238,18.9700,72.8330,"Groceries, Dairy, Beverages, Snacks, Toys"
31
+ Kirana Store DD,"6263 Hospital Way, Mumbai",9876543239,18.9670,72.8310,"Groceries, Dairy, Beverages, Snacks, Pharmaceuticals"
32
+ Kirana Store EE,"6465 College Avenue, Mumbai",9876543240,18.9695,72.8320,"Groceries, Dairy, Beverages, Snacks, Books"
33
+ Kirana Store FF,"6667 Library Lane, Mumbai",9876543241,18.9710,72.8335,"Groceries, Dairy, Beverages, Snacks, Gifts"
34
+ Kirana Store GG,"6869 Parkside Drive, Mumbai",9876543242,18.9705,72.8345,"Groceries, Dairy, Beverages, Snacks, Flowers"
35
+ Kirana Store HH,"7071 Riverview Road, Mumbai",9876543243,18.9680,72.8325,"Groceries, Dairy, Beverages, Snacks, Sweets"
36
+ Kirana Store II,"7273 Hillside Drive, Mumbai",9876543244,18.9695,72.8330,"Groceries, Dairy, Beverages, Snacks, Organic Products"
requirements.txt CHANGED
@@ -1,2 +1,13 @@
1
  fastapi
2
  uvicorn[standard]
 
 
 
 
 
 
 
 
 
 
 
 
1
  fastapi
2
  uvicorn[standard]
3
+ pandas
4
+ numpy
5
+ geopy
6
+ folium
7
+ osmnx
8
+ networkx
9
+ plotly
10
+ requests
11
+ python-dotenv
12
+ rtree
13
+ matplotlib