or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

bitmap-operations.mdcore-clients.mdgeneric-operations.mdgeospatial-operations.mdhash-operations.mdindex.mdlist-operations.mdlua-scripting.mdpubsub-operations.mdserver-management.mdserver-operations.mdset-operations.mdsorted-set-operations.mdstack-extensions.mdstream-operations.mdstring-operations.mdtransaction-operations.mdvalkey-support.md

geospatial-operations.mddocs/

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

```