docs
0
# Geospatial Operations
1
2
Redis geospatial operations for location-based data with geographic indexing and radius queries. Geospatial commands enable storage and querying of geographic coordinates using sorted sets, providing efficient proximity searches and distance calculations for location-aware applications.
3
4
## Capabilities
5
6
### Adding Geographic Data
7
8
Functions for storing geographic coordinates associated with members in a geospatial index.
9
10
```python { .api }
11
def geoadd(
12
self,
13
name: KeyT,
14
values: Sequence[Union[Tuple[float, float, EncodableT], Tuple[EncodableT, float, float]]],
15
nx: bool = False,
16
xx: bool = False,
17
ch: bool = False
18
) -> int: ...
19
```
20
21
### Geographic Queries
22
23
Operations for retrieving coordinates, calculating distances, and encoding geographic hashes.
24
25
```python { .api }
26
def geopos(self, name: KeyT, *values: EncodableT) -> List[Optional[Tuple[float, float]]]: ...
27
28
def geodist(
29
self,
30
name: KeyT,
31
place1: EncodableT,
32
place2: EncodableT,
33
unit: Optional[str] = None
34
) -> Optional[float]: ...
35
36
def geohash(self, name: KeyT, *values: EncodableT) -> List[Optional[str]]: ...
37
```
38
39
### Radius Searches
40
41
Proximity search functions for finding members within specified geographic areas.
42
43
```python { .api }
44
def georadius(
45
self,
46
name: KeyT,
47
longitude: float,
48
latitude: float,
49
radius: float,
50
unit: str = "m",
51
withdist: bool = False,
52
withcoord: bool = False,
53
withhash: bool = False,
54
count: Optional[int] = None,
55
sort: Optional[str] = None,
56
store: Optional[KeyT] = None,
57
store_dist: Optional[KeyT] = None
58
) -> List[Union[bytes, List[Union[bytes, float, Tuple[float, float], int]]]]: ...
59
60
def georadiusbymember(
61
self,
62
name: KeyT,
63
member: EncodableT,
64
radius: float,
65
unit: str = "m",
66
withdist: bool = False,
67
withcoord: bool = False,
68
withhash: bool = False,
69
count: Optional[int] = None,
70
sort: Optional[str] = None,
71
store: Optional[KeyT] = None,
72
store_dist: Optional[KeyT] = None
73
) -> List[Union[bytes, List[Union[bytes, float, Tuple[float, float], int]]]]: ...
74
```
75
76
### Advanced Geospatial Search (Redis 6.2+)
77
78
Modern geospatial search commands with enhanced filtering and sorting capabilities.
79
80
```python { .api }
81
def geosearch(
82
self,
83
name: KeyT,
84
member: Optional[EncodableT] = None,
85
longitude: Optional[float] = None,
86
latitude: Optional[float] = None,
87
unit: str = "m",
88
radius: Optional[float] = None,
89
width: Optional[float] = None,
90
height: Optional[float] = None,
91
sort: Optional[str] = None,
92
count: Optional[int] = None,
93
any: bool = False,
94
withdist: bool = False,
95
withcoord: bool = False,
96
withhash: bool = False
97
) -> List[Union[bytes, List[Union[bytes, float, Tuple[float, float], int]]]]: ...
98
99
def geosearchstore(
100
self,
101
dest: KeyT,
102
name: KeyT,
103
member: Optional[EncodableT] = None,
104
longitude: Optional[float] = None,
105
latitude: Optional[float] = None,
106
unit: str = "m",
107
radius: Optional[float] = None,
108
width: Optional[float] = None,
109
height: Optional[float] = None,
110
sort: Optional[str] = None,
111
count: Optional[int] = None,
112
any: bool = False,
113
storedist: bool = False
114
) -> int: ...
115
```
116
117
## Usage Examples
118
119
### Basic Geospatial Operations
120
121
```python
122
import fakeredis
123
124
client = fakeredis.FakeRedis()
125
126
# Add locations to a geospatial index
127
# Format: (longitude, latitude, member_name)
128
locations = [
129
(-74.0059, 40.7128, "New York"), # NYC coordinates
130
(-118.2437, 34.0522, "Los Angeles"), # LA coordinates
131
(-87.6298, 41.8781, "Chicago"), # Chicago coordinates
132
(-122.4194, 37.7749, "San Francisco") # SF coordinates
133
]
134
135
added = client.geoadd("cities", locations)
136
print(f"Added {added} cities to geospatial index")
137
138
# Get coordinates for specific cities
139
positions = client.geopos("cities", "New York", "Los Angeles", "NonExistent")
140
for i, pos in enumerate(positions):
141
city = ["New York", "Los Angeles", "NonExistent"][i]
142
if pos:
143
lon, lat = pos
144
print(f"{city}: {lon:.4f}, {lat:.4f}")
145
else:
146
print(f"{city}: Not found")
147
148
# Calculate distance between cities
149
distance_km = client.geodist("cities", "New York", "Los Angeles", unit="km")
150
distance_mi = client.geodist("cities", "New York", "Los Angeles", unit="mi")
151
print(f"NYC to LA: {distance_km:.2f} km ({distance_mi:.2f} miles)")
152
```
153
154
### Geographic Hash Encoding
155
156
```python
157
import fakeredis
158
159
client = fakeredis.FakeRedis()
160
161
# Add some landmarks
162
landmarks = [
163
(-0.1276, 51.5074, "London"), # London, UK
164
(2.3522, 48.8566, "Paris"), # Paris, France
165
(13.4050, 52.5200, "Berlin"), # Berlin, Germany
166
(12.4964, 41.9028, "Rome") # Rome, Italy
167
]
168
169
client.geoadd("landmarks", landmarks)
170
171
# Get geohashes for the landmarks
172
hashes = client.geohash("landmarks", "London", "Paris", "Berlin", "Rome")
173
for i, city in enumerate(["London", "Paris", "Berlin", "Rome"]):
174
if hashes[i]:
175
print(f"{city}: {hashes[i]}")
176
177
# Geohashes can be used for:
178
# 1. Approximate location representation
179
# 2. Hierarchical spatial indexing
180
# 3. Location-based sharding
181
print(f"\nGeohash precision example:")
182
london_hash = hashes[0]
183
if london_hash:
184
for precision in [1, 3, 5, 7, 9, 11]:
185
if precision <= len(london_hash):
186
truncated = london_hash[:precision]
187
print(f" Precision {precision}: {truncated}")
188
```
189
190
### Radius Searches
191
192
```python
193
import fakeredis
194
195
client = fakeredis.FakeRedis()
196
197
# Add restaurants in a city area
198
restaurants = [
199
(-73.9857, 40.7484, "Restaurant A"), # Times Square area
200
(-73.9776, 40.7505, "Restaurant B"), # Near Times Square
201
(-74.0059, 40.7128, "Restaurant C"), # Downtown
202
(-73.9712, 40.7831, "Restaurant D"), # Upper West Side
203
(-73.9442, 40.8176, "Restaurant E") # Harlem
204
]
205
206
client.geoadd("restaurants", restaurants)
207
208
# Find restaurants within 2km of Times Square
209
times_square_lon, times_square_lat = -73.9857, 40.7484
210
211
nearby_simple = client.georadius("restaurants", times_square_lon, times_square_lat, 2, unit="km")
212
print("Restaurants within 2km of Times Square:")
213
for restaurant in nearby_simple:
214
print(f" - {restaurant.decode()}")
215
216
# Find restaurants with distances and coordinates
217
nearby_detailed = client.georadius(
218
"restaurants",
219
times_square_lon, times_square_lat,
220
2,
221
unit="km",
222
withdist=True,
223
withcoord=True,
224
sort="ASC" # Sort by distance
225
)
226
227
print("\nDetailed results (with distances and coordinates):")
228
for result in nearby_detailed:
229
name = result[0].decode()
230
distance = result[1]
231
coords = result[2]
232
print(f" - {name}: {distance:.3f}km at ({coords[0]:.4f}, {coords[1]:.4f})")
233
```
234
235
### Radius Search by Member
236
237
```python
238
import fakeredis
239
240
client = fakeredis.FakeRedis()
241
242
# Add coffee shops
243
coffee_shops = [
244
(-73.9857, 40.7484, "Starbucks Times Square"),
245
(-73.9776, 40.7505, "Local Cafe A"),
246
(-73.9712, 40.7831, "Local Cafe B"),
247
(-74.0059, 40.7128, "Downtown Coffee"),
248
(-73.9442, 40.8176, "Uptown Roasters")
249
]
250
251
client.geoadd("coffee_shops", coffee_shops)
252
253
# Find coffee shops within 1.5km of "Starbucks Times Square"
254
nearby_coffee = client.georadiusbymember(
255
"coffee_shops",
256
"Starbucks Times Square",
257
1.5,
258
unit="km",
259
withdist=True,
260
count=5,
261
sort="ASC"
262
)
263
264
print("Coffee shops within 1.5km of Starbucks Times Square:")
265
for result in nearby_coffee:
266
name = result[0].decode()
267
distance = result[1]
268
if name != "Starbucks Times Square": # Exclude the reference point
269
print(f" - {name}: {distance:.3f}km away")
270
```
271
272
### Advanced Geospatial Search (Redis 6.2+)
273
274
```python
275
import fakeredis
276
277
# Use Redis 6.2+ for advanced geosearch features
278
client = fakeredis.FakeRedis(version=(6, 2))
279
280
# Add hotels in a city
281
hotels = [
282
(-73.9857, 40.7484, "Hotel Plaza"),
283
(-73.9776, 40.7505, "Boutique Inn"),
284
(-74.0059, 40.7128, "Business Hotel"),
285
(-73.9712, 40.7831, "Luxury Resort"),
286
(-73.9442, 40.8176, "Budget Lodge"),
287
(-73.9900, 40.7300, "Downtown Suites"),
288
(-73.9600, 40.7700, "Midtown Hotel")
289
]
290
291
client.geoadd("hotels", hotels)
292
293
# Search in circular area from coordinates
294
circular_search = client.geosearch(
295
"hotels",
296
longitude=-73.9857,
297
latitude=40.7484,
298
radius=1,
299
unit="km",
300
withdist=True,
301
withcoord=True,
302
sort="ASC",
303
count=3
304
)
305
306
print("Hotels within 1km (circular search):")
307
for result in circular_search:
308
name = result[0].decode()
309
distance = result[1]
310
coords = result[2]
311
print(f" - {name}: {distance:.3f}km at ({coords[0]:.4f}, {coords[1]:.4f})")
312
313
# Search in rectangular area from a member
314
rectangular_search = client.geosearch(
315
"hotels",
316
member="Hotel Plaza",
317
width=2,
318
height=1,
319
unit="km",
320
withdist=True,
321
sort="ASC"
322
)
323
324
print("\nHotels within 2km x 1km rectangle from Hotel Plaza:")
325
for result in rectangular_search:
326
name = result[0].decode()
327
distance = result[1]
328
print(f" - {name}: {distance:.3f}km away")
329
330
# Store search results in another key
331
stored_count = client.geosearchstore(
332
"nearby_hotels", # Destination key
333
"hotels", # Source key
334
member="Hotel Plaza",
335
radius=1.5,
336
unit="km",
337
storedist=True # Store distances as scores
338
)
339
340
print(f"\nStored {stored_count} hotels in 'nearby_hotels' with distances as scores")
341
342
# Retrieve stored results (they're stored as a sorted set)
343
stored_results = client.zrange("nearby_hotels", 0, -1, withscores=True)
344
print("Stored nearby hotels with distances:")
345
for member, distance in stored_results:
346
print(f" - {member.decode()}: {distance:.3f}km")
347
```
348
349
### Geographic Data Management
350
351
```python
352
import fakeredis
353
354
client = fakeredis.FakeRedis()
355
356
# Add initial locations
357
locations = [
358
(-73.9857, 40.7484, "Store_1"),
359
(-73.9776, 40.7505, "Store_2"),
360
(-74.0059, 40.7128, "Store_3")
361
]
362
363
added = client.geoadd("stores", locations)
364
print(f"Initially added {added} stores")
365
366
# Add new locations (won't overwrite existing)
367
new_locations = [
368
(-73.9857, 40.7484, "Store_1"), # Duplicate - won't be added
369
(-73.9712, 40.7831, "Store_4"), # New location
370
(-73.9442, 40.8176, "Store_5") # New location
371
]
372
373
added_new = client.geoadd("stores", new_locations, nx=True) # Only add if not exists
374
print(f"Added {added_new} new stores (nx=True)")
375
376
# Update existing locations
377
updated_locations = [
378
(-73.9860, 40.7480, "Store_1"), # Slightly different coordinates
379
(-73.9780, 40.7500, "Store_2") # Slightly different coordinates
380
]
381
382
updated = client.geoadd("stores", updated_locations, xx=True) # Only update existing
383
print(f"Updated {updated} existing stores (xx=True)")
384
385
# Get count of changes
386
change_locations = [
387
(-73.9850, 40.7490, "Store_1"), # Change existing
388
(-73.9000, 40.8000, "Store_6") # Add new
389
]
390
391
changes = client.geoadd("stores", change_locations, ch=True) # Return count of changes
392
print(f"Total changes made: {changes}")
393
394
# Verify final positions
395
all_positions = client.geopos("stores", "Store_1", "Store_2", "Store_3", "Store_4", "Store_5", "Store_6")
396
for i, pos in enumerate(all_positions):
397
store_name = f"Store_{i+1}"
398
if pos:
399
lon, lat = pos
400
print(f"{store_name}: ({lon:.4f}, {lat:.4f})")
401
else:
402
print(f"{store_name}: Not found")
403
```
404
405
### Pattern: Location-Based Services
406
407
```python
408
import fakeredis
409
import time
410
import math
411
from dataclasses import dataclass
412
from typing import List, Tuple, Optional
413
414
@dataclass
415
class Location:
416
id: str
417
name: str
418
longitude: float
419
latitude: float
420
category: str
421
rating: float = 0.0
422
423
@dataclass
424
class SearchResult:
425
location: Location
426
distance_km: float
427
428
class LocationService:
429
def __init__(self, client: fakeredis.FakeRedis):
430
self.client = client
431
432
def add_location(self, location: Location) -> bool:
433
"""Add a location to the geospatial index"""
434
# Store location in geospatial index
435
geo_result = self.client.geoadd(
436
f"locations:{location.category}",
437
[(location.longitude, location.latitude, location.id)]
438
)
439
440
# Store location metadata
441
self.client.hset(f"location:{location.id}", mapping={
442
"name": location.name,
443
"category": location.category,
444
"longitude": str(location.longitude),
445
"latitude": str(location.latitude),
446
"rating": str(location.rating)
447
})
448
449
return geo_result > 0
450
451
def find_nearby(
452
self,
453
longitude: float,
454
latitude: float,
455
radius_km: float,
456
category: Optional[str] = None,
457
limit: Optional[int] = None
458
) -> List[SearchResult]:
459
"""Find locations within radius of coordinates"""
460
461
categories = [category] if category else self._get_all_categories()
462
all_results = []
463
464
for cat in categories:
465
geo_key = f"locations:{cat}"
466
467
# Search for locations in this category
468
results = self.client.georadius(
469
geo_key,
470
longitude,
471
latitude,
472
radius_km,
473
unit="km",
474
withdist=True,
475
sort="ASC",
476
count=limit
477
)
478
479
# Convert to SearchResult objects
480
for result in results:
481
location_id = result[0].decode()
482
distance = result[1]
483
484
# Get location metadata
485
location_data = self.client.hgetall(f"location:{location_id}")
486
if location_data:
487
location = Location(
488
id=location_id,
489
name=location_data[b'name'].decode(),
490
longitude=float(location_data[b'longitude'].decode()),
491
latitude=float(location_data[b'latitude'].decode()),
492
category=location_data[b'category'].decode(),
493
rating=float(location_data[b'rating'].decode())
494
)
495
496
all_results.append(SearchResult(location, distance))
497
498
# Sort by distance and apply limit
499
all_results.sort(key=lambda x: x.distance_km)
500
return all_results[:limit] if limit else all_results
501
502
def find_nearby_location(
503
self,
504
reference_location_id: str,
505
radius_km: float,
506
category: Optional[str] = None,
507
limit: Optional[int] = None
508
) -> List[SearchResult]:
509
"""Find locations near another location"""
510
511
# Get reference location data
512
ref_data = self.client.hgetall(f"location:{reference_location_id}")
513
if not ref_data:
514
return []
515
516
ref_category = ref_data[b'category'].decode()
517
518
# Search in the reference location's category
519
results = self.client.georadiusbymember(
520
f"locations:{ref_category}",
521
reference_location_id,
522
radius_km,
523
unit="km",
524
withdist=True,
525
sort="ASC",
526
count=limit + 1 if limit else None # +1 to account for reference location
527
)
528
529
search_results = []
530
for result in results:
531
location_id = result[0].decode()
532
distance = result[1]
533
534
# Skip the reference location itself
535
if location_id == reference_location_id:
536
continue
537
538
# Get location metadata
539
location_data = self.client.hgetall(f"location:{location_id}")
540
if location_data:
541
location = Location(
542
id=location_id,
543
name=location_data[b'name'].decode(),
544
longitude=float(location_data[b'longitude'].decode()),
545
latitude=float(location_data[b'latitude'].decode()),
546
category=location_data[b'category'].decode(),
547
rating=float(location_data[b'rating'].decode())
548
)
549
550
search_results.append(SearchResult(location, distance))
551
552
return search_results[:limit] if limit else search_results
553
554
def get_location_stats(self, category: str) -> dict:
555
"""Get statistics for locations in a category"""
556
geo_key = f"locations:{category}"
557
558
# Get all members in the geospatial index
559
all_members = self.client.zrange(geo_key, 0, -1)
560
561
if not all_members:
562
return {"total": 0, "average_rating": 0.0}
563
564
total_rating = 0.0
565
count = 0
566
567
for member in all_members:
568
location_data = self.client.hgetall(f"location:{member.decode()}")
569
if location_data:
570
rating = float(location_data[b'rating'].decode())
571
total_rating += rating
572
count += 1
573
574
return {
575
"total": count,
576
"average_rating": total_rating / count if count > 0 else 0.0
577
}
578
579
def _get_all_categories(self) -> List[str]:
580
"""Get all location categories"""
581
keys = self.client.keys("locations:*")
582
return [key.decode().split(":")[1] for key in keys]
583
584
# Usage example
585
client = fakeredis.FakeRedis()
586
location_service = LocationService(client)
587
588
# Add sample locations
589
locations = [
590
Location("rest_1", "Pizza Palace", -73.9857, 40.7484, "restaurant", 4.2),
591
Location("rest_2", "Burger Joint", -73.9776, 40.7505, "restaurant", 3.8),
592
Location("rest_3", "Sushi Bar", -73.9712, 40.7831, "restaurant", 4.5),
593
Location("hotel_1", "Grand Hotel", -73.9850, 40.7480, "hotel", 4.0),
594
Location("hotel_2", "Budget Inn", -73.9780, 40.7500, "hotel", 3.2),
595
Location("shop_1", "Fashion Store", -73.9860, 40.7490, "shopping", 4.1),
596
Location("shop_2", "Electronics Hub", -73.9770, 40.7510, "shopping", 3.9)
597
]
598
599
for location in locations:
600
location_service.add_location(location)
601
602
print("Added all locations to the service")
603
604
# Find restaurants within 1km of Times Square
605
times_square = (-73.9857, 40.7484)
606
nearby_restaurants = location_service.find_nearby(
607
times_square[0], times_square[1],
608
radius_km=1.0,
609
category="restaurant",
610
limit=5
611
)
612
613
print(f"\nRestaurants within 1km of Times Square:")
614
for result in nearby_restaurants:
615
loc = result.location
616
print(f" - {loc.name}: {result.distance_km:.3f}km (Rating: {loc.rating})")
617
618
# Find locations near Pizza Palace
619
nearby_pizza_palace = location_service.find_nearby_location(
620
"rest_1", # Pizza Palace
621
radius_km=0.5,
622
limit=3
623
)
624
625
print(f"\nLocations within 0.5km of Pizza Palace:")
626
for result in nearby_pizza_palace:
627
loc = result.location
628
print(f" - {loc.name} ({loc.category}): {result.distance_km:.3f}km")
629
630
# Get statistics for each category
631
for category in ["restaurant", "hotel", "shopping"]:
632
stats = location_service.get_location_stats(category)
633
print(f"\n{category.title()} stats:")
634
print(f" Total locations: {stats['total']}")
635
print(f" Average rating: {stats['average_rating']:.2f}")
636
```
637
638
### Pattern: Delivery Zone Management
639
640
```python
641
import fakeredis
642
import math
643
from typing import List, Tuple, Dict
644
from dataclasses import dataclass
645
646
@dataclass
647
class DeliveryZone:
648
id: str
649
name: str
650
center_longitude: float
651
center_latitude: float
652
radius_km: float
653
delivery_fee: float
654
min_order: float
655
656
class DeliveryService:
657
def __init__(self, client: fakeredis.FakeRedis):
658
self.client = client
659
660
def add_delivery_zone(self, zone: DeliveryZone):
661
"""Add a delivery zone"""
662
# Store zone center in geospatial index
663
self.client.geoadd(
664
"delivery_zones",
665
[(zone.center_longitude, zone.center_latitude, zone.id)]
666
)
667
668
# Store zone metadata
669
self.client.hset(f"zone:{zone.id}", mapping={
670
"name": zone.name,
671
"center_longitude": str(zone.center_longitude),
672
"center_latitude": str(zone.center_latitude),
673
"radius_km": str(zone.radius_km),
674
"delivery_fee": str(zone.delivery_fee),
675
"min_order": str(zone.min_order)
676
})
677
678
def check_delivery_availability(
679
self,
680
customer_longitude: float,
681
customer_latitude: float
682
) -> List[Dict]:
683
"""Check which delivery zones serve a customer location"""
684
685
# Find all delivery zones within a reasonable search radius (e.g., 50km)
686
nearby_zones = self.client.georadius(
687
"delivery_zones",
688
customer_longitude,
689
customer_latitude,
690
50, # Search within 50km
691
unit="km",
692
withdist=True
693
)
694
695
available_zones = []
696
697
for result in nearby_zones:
698
zone_id = result[0].decode()
699
distance_to_center = result[1]
700
701
# Get zone details
702
zone_data = self.client.hgetall(f"zone:{zone_id}")
703
if zone_data:
704
zone_radius = float(zone_data[b'radius_km'].decode())
705
706
# Check if customer is within this zone's delivery radius
707
if distance_to_center <= zone_radius:
708
available_zones.append({
709
"zone_id": zone_id,
710
"zone_name": zone_data[b'name'].decode(),
711
"delivery_fee": float(zone_data[b'delivery_fee'].decode()),
712
"min_order": float(zone_data[b'min_order'].decode()),
713
"distance_from_center": distance_to_center
714
})
715
716
# Sort by delivery fee (cheapest first)
717
available_zones.sort(key=lambda x: x['delivery_fee'])
718
return available_zones
719
720
def get_optimal_delivery_zone(
721
self,
722
customer_longitude: float,
723
customer_latitude: float,
724
order_value: float
725
) -> Dict:
726
"""Get the best delivery zone for a customer order"""
727
728
available_zones = self.check_delivery_availability(
729
customer_longitude, customer_latitude
730
)
731
732
# Filter zones by minimum order requirement
733
eligible_zones = [
734
zone for zone in available_zones
735
if order_value >= zone['min_order']
736
]
737
738
if not eligible_zones:
739
return {"available": False, "reason": "No delivery zones available or order below minimum"}
740
741
# Return the zone with lowest delivery fee
742
best_zone = eligible_zones[0]
743
return {
744
"available": True,
745
"zone": best_zone,
746
"total_delivery_fee": best_zone['delivery_fee']
747
}
748
749
def update_zone_radius(self, zone_id: str, new_radius_km: float):
750
"""Update delivery zone radius"""
751
return self.client.hset(f"zone:{zone_id}", "radius_km", str(new_radius_km))
752
753
def get_zone_coverage_stats(self) -> List[Dict]:
754
"""Get statistics for all delivery zones"""
755
# Get all zones
756
all_zones = self.client.zrange("delivery_zones", 0, -1)
757
758
stats = []
759
for zone_member in all_zones:
760
zone_id = zone_member.decode()
761
zone_data = self.client.hgetall(f"zone:{zone_id}")
762
763
if zone_data:
764
stats.append({
765
"zone_id": zone_id,
766
"name": zone_data[b'name'].decode(),
767
"radius_km": float(zone_data[b'radius_km'].decode()),
768
"delivery_fee": float(zone_data[b'delivery_fee'].decode()),
769
"min_order": float(zone_data[b'min_order'].decode()),
770
"coverage_area_km2": math.pi * (float(zone_data[b'radius_km'].decode()) ** 2)
771
})
772
773
return stats
774
775
# Usage example
776
client = fakeredis.FakeRedis()
777
delivery_service = DeliveryService(client)
778
779
# Add delivery zones for a food delivery service
780
zones = [
781
DeliveryZone("zone_downtown", "Downtown", -73.9857, 40.7484, 2.0, 3.99, 15.0),
782
DeliveryZone("zone_midtown", "Midtown", -73.9776, 40.7505, 1.5, 2.99, 12.0),
783
DeliveryZone("zone_uptown", "Uptown", -73.9712, 40.7831, 3.0, 4.99, 20.0),
784
DeliveryZone("zone_financial", "Financial District", -74.0059, 40.7128, 1.0, 1.99, 10.0)
785
]
786
787
for zone in zones:
788
delivery_service.add_delivery_zone(zone)
789
790
print("Delivery zones configured:")
791
zone_stats = delivery_service.get_zone_coverage_stats()
792
for stat in zone_stats:
793
print(f" - {stat['name']}: {stat['radius_km']}km radius, ${stat['delivery_fee']} fee, ${stat['min_order']} minimum, {stat['coverage_area_km2']:.1f}km² coverage")
794
795
# Test delivery availability for different customer locations
796
test_locations = [
797
{"name": "Customer A", "lon": -73.9850, "lat": 40.7480, "order_value": 25.0}, # Downtown area
798
{"name": "Customer B", "lon": -73.9780, "lat": 40.7510, "order_value": 8.0}, # Midtown, low order
799
{"name": "Customer C", "lon": -74.0050, "lat": 40.7130, "order_value": 15.0}, # Financial district
800
{"name": "Customer D", "lon": -73.9000, "lat": 40.8000, "order_value": 30.0}, # Far location
801
]
802
803
print(f"\nDelivery availability check:")
804
for customer in test_locations:
805
print(f"\n{customer['name']} (Order: ${customer['order_value']}):")
806
807
# Check all available zones
808
available = delivery_service.check_delivery_availability(customer['lon'], customer['lat'])
809
if available:
810
print(f" Available zones:")
811
for zone in available:
812
print(f" - {zone['zone_name']}: ${zone['delivery_fee']} fee, ${zone['min_order']} min, {zone['distance_from_center']:.2f}km from center")
813
814
# Get optimal zone
815
optimal = delivery_service.get_optimal_delivery_zone(customer['lon'], customer['lat'], customer['order_value'])
816
if optimal['available']:
817
zone = optimal['zone']
818
print(f" ✅ Best option: {zone['zone_name']} (${optimal['total_delivery_fee']} delivery)")
819
else:
820
print(f" ❌ {optimal['reason']}")
821
```