0
# Async Support
1
2
Geopy provides comprehensive asynchronous geocoding support using aiohttp adapter for high-performance applications requiring concurrent geocoding requests. All geocoder classes support async operations with minimal code changes.
3
4
## Capabilities
5
6
### Async Adapter
7
8
HTTP adapter for asynchronous operations using aiohttp.
9
10
```python { .api }
11
from geopy.adapters import AioHTTPAdapter
12
13
class AioHTTPAdapter:
14
"""
15
HTTP adapter using aiohttp for asynchronous requests.
16
Requires: pip install "geopy[aiohttp]"
17
"""
18
19
def __init__(self, proxy_info=None, ssl_context=None):
20
"""
21
Initialize AioHTTP adapter.
22
23
Parameters:
24
- proxy_info: Proxy configuration (optional)
25
- ssl_context: SSL context for HTTPS requests (optional)
26
"""
27
28
async def get_text(self, url, timeout, headers):
29
"""
30
Perform async GET request returning text.
31
32
Parameters:
33
- url (str): Request URL
34
- timeout (float): Request timeout in seconds
35
- headers (dict): HTTP headers
36
37
Returns:
38
str: Response text
39
"""
40
41
async def get_json(self, url, timeout, headers):
42
"""
43
Perform async GET request returning parsed JSON.
44
45
Parameters:
46
- url (str): Request URL
47
- timeout (float): Request timeout in seconds
48
- headers (dict): HTTP headers
49
50
Returns:
51
dict: Parsed JSON response
52
"""
53
```
54
55
### Async Geocoder Usage
56
57
All geocoder classes support async operations when initialized with AioHTTPAdapter.
58
59
```python { .api }
60
# Example with Nominatim - same pattern applies to all geocoders
61
from geopy.geocoders import Nominatim
62
from geopy.adapters import AioHTTPAdapter
63
64
async def async_geocoding_example():
65
async with Nominatim(
66
user_agent="async_app",
67
adapter_factory=AioHTTPAdapter
68
) as geolocator:
69
# All geocoder methods become async when using async adapter
70
location = await geolocator.geocode("New York City")
71
reverse_location = await geolocator.reverse("40.7128, -74.0060")
72
return location, reverse_location
73
```
74
75
### Async Rate Limiter
76
77
Rate limiting functionality for asynchronous geocoding operations.
78
79
```python { .api }
80
from geopy.extra.rate_limiter import AsyncRateLimiter
81
82
class AsyncRateLimiter:
83
"""
84
Rate limiting wrapper for asynchronous geocoding functions.
85
"""
86
87
def __init__(self, func, min_delay_seconds=0.0, max_retries=2,
88
error_wait_seconds=5.0, swallow_exceptions=True,
89
return_value_on_exception=None):
90
"""
91
Initialize async rate limiter.
92
93
Parameters:
94
- func: Async function to wrap
95
- min_delay_seconds (float): Minimum delay between calls
96
- max_retries (int): Number of retry attempts on errors
97
- error_wait_seconds (float): Wait time after errors
98
- swallow_exceptions (bool): Whether to suppress final exceptions
99
- return_value_on_exception: Return value when exceptions are swallowed
100
"""
101
102
async def __call__(self, *args, **kwargs):
103
"""Execute rate-limited async function call"""
104
```
105
106
## Usage Examples
107
108
### Basic Async Geocoding
109
110
```python
111
import asyncio
112
from geopy.geocoders import Nominatim
113
from geopy.adapters import AioHTTPAdapter
114
115
async def basic_async_geocoding():
116
"""Basic async geocoding example"""
117
118
async with Nominatim(
119
user_agent="basic_async_app",
120
adapter_factory=AioHTTPAdapter
121
) as geolocator:
122
123
# Forward geocoding
124
location = await geolocator.geocode("Paris, France")
125
print(f"Forward: {location.address if location else 'Not found'}")
126
127
# Reverse geocoding
128
if location:
129
reverse = await geolocator.reverse(f"{location.latitude}, {location.longitude}")
130
print(f"Reverse: {reverse.address if reverse else 'Not found'}")
131
132
# Run async function
133
asyncio.run(basic_async_geocoding())
134
```
135
136
### Concurrent Geocoding
137
138
```python
139
import asyncio
140
from geopy.geocoders import Nominatim
141
from geopy.adapters import AioHTTPAdapter
142
143
async def concurrent_geocoding():
144
"""Geocode multiple addresses concurrently"""
145
146
addresses = [
147
"New York City, USA",
148
"London, UK",
149
"Tokyo, Japan",
150
"Sydney, Australia",
151
"São Paulo, Brazil",
152
"Mumbai, India",
153
"Cairo, Egypt",
154
"Berlin, Germany"
155
]
156
157
async with Nominatim(
158
user_agent="concurrent_app",
159
adapter_factory=AioHTTPAdapter
160
) as geolocator:
161
162
# Create tasks for concurrent execution
163
tasks = [
164
geolocator.geocode(address)
165
for address in addresses
166
]
167
168
# Execute all tasks concurrently
169
results = await asyncio.gather(*tasks, return_exceptions=True)
170
171
# Process results
172
for address, result in zip(addresses, results):
173
if isinstance(result, Exception):
174
print(f"Error geocoding '{address}': {result}")
175
elif result:
176
print(f"{address} -> {result.latitude:.4f}, {result.longitude:.4f}")
177
else:
178
print(f"No results for '{address}'")
179
180
asyncio.run(concurrent_geocoding())
181
```
182
183
### Async with Rate Limiting
184
185
```python
186
import asyncio
187
from geopy.geocoders import Nominatim
188
from geopy.adapters import AioHTTPAdapter
189
from geopy.extra.rate_limiter import AsyncRateLimiter
190
191
async def rate_limited_async_geocoding():
192
"""Async geocoding with rate limiting"""
193
194
addresses = [
195
"Times Square, New York",
196
"Eiffel Tower, Paris",
197
"Big Ben, London",
198
"Colosseum, Rome",
199
"Statue of Liberty, New York",
200
"Golden Gate Bridge, San Francisco",
201
"Sydney Opera House, Australia",
202
"Machu Picchu, Peru"
203
]
204
205
async with Nominatim(
206
user_agent="rate_limited_app",
207
adapter_factory=AioHTTPAdapter
208
) as geolocator:
209
210
# Wrap geocoder with rate limiter (1 second minimum delay)
211
rate_limited_geocode = AsyncRateLimiter(
212
geolocator.geocode,
213
min_delay_seconds=1.0,
214
max_retries=3,
215
error_wait_seconds=2.0
216
)
217
218
# Process addresses with rate limiting
219
results = []
220
for address in addresses:
221
try:
222
result = await rate_limited_geocode(address)
223
results.append((address, result))
224
print(f"✓ Geocoded: {address}")
225
except Exception as e:
226
print(f"✗ Failed: {address} - {e}")
227
results.append((address, None))
228
229
return results
230
231
asyncio.run(rate_limited_async_geocoding())
232
```
233
234
### Multiple Service Async Comparison
235
236
```python
237
import asyncio
238
from geopy.geocoders import Nominatim, Photon, OpenCage
239
from geopy.adapters import AioHTTPAdapter
240
241
async def compare_services_async():
242
"""Compare multiple geocoding services asynchronously"""
243
244
address = "Central Park, New York City"
245
246
# Initialize multiple geocoders
247
geocoders = {
248
'Nominatim': Nominatim(user_agent="comparison_app", adapter_factory=AioHTTPAdapter),
249
'Photon': Photon(user_agent="comparison_app", adapter_factory=AioHTTPAdapter),
250
# Add more services as needed (with proper API keys)
251
}
252
253
async def geocode_with_service(name, geocoder, query):
254
"""Geocode with a specific service"""
255
try:
256
async with geocoder:
257
result = await geocoder.geocode(query)
258
return name, result
259
except Exception as e:
260
return name, f"Error: {e}"
261
262
# Create tasks for all services
263
tasks = [
264
geocode_with_service(name, geocoder, address)
265
for name, geocoder in geocoders.items()
266
]
267
268
# Execute concurrently
269
results = await asyncio.gather(*tasks)
270
271
# Display results
272
print(f"Geocoding results for '{address}':")
273
for service_name, result in results:
274
if isinstance(result, str): # Error
275
print(f"{service_name}: {result}")
276
elif result:
277
print(f"{service_name}: {result.address}")
278
print(f" Coordinates: {result.latitude:.6f}, {result.longitude:.6f}")
279
else:
280
print(f"{service_name}: No results")
281
print()
282
283
asyncio.run(compare_services_async())
284
```
285
286
### Async Batch Processing
287
288
```python
289
import asyncio
290
from geopy.geocoders import Nominatim
291
from geopy.adapters import AioHTTPAdapter
292
from geopy.extra.rate_limiter import AsyncRateLimiter
293
import csv
294
import json
295
296
class AsyncBatchGeocoder:
297
"""Batch geocoding processor with async support"""
298
299
def __init__(self, geocoder_class, batch_size=10, delay=1.0, **geocoder_kwargs):
300
self.geocoder_class = geocoder_class
301
self.geocoder_kwargs = geocoder_kwargs
302
self.batch_size = batch_size
303
self.delay = delay
304
305
async def geocode_batch(self, addresses):
306
"""Geocode a batch of addresses"""
307
308
async with self.geocoder_class(
309
adapter_factory=AioHTTPAdapter,
310
**self.geocoder_kwargs
311
) as geolocator:
312
313
# Set up rate limiting
314
rate_limited_geocode = AsyncRateLimiter(
315
geolocator.geocode,
316
min_delay_seconds=self.delay,
317
max_retries=3
318
)
319
320
# Process in batches to avoid overwhelming the service
321
results = []
322
for i in range(0, len(addresses), self.batch_size):
323
batch = addresses[i:i + self.batch_size]
324
print(f"Processing batch {i//self.batch_size + 1}/{(len(addresses)-1)//self.batch_size + 1}")
325
326
# Process batch concurrently
327
batch_tasks = [
328
self._geocode_single(rate_limited_geocode, addr, idx + i)
329
for idx, addr in enumerate(batch)
330
]
331
332
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
333
results.extend(batch_results)
334
335
# Brief pause between batches
336
if i + self.batch_size < len(addresses):
337
await asyncio.sleep(1)
338
339
return results
340
341
async def _geocode_single(self, geocoder_func, address, index):
342
"""Geocode a single address with error handling"""
343
try:
344
result = await geocoder_func(address)
345
return {
346
'index': index,
347
'input_address': address,
348
'status': 'success' if result else 'no_results',
349
'result_address': result.address if result else None,
350
'latitude': result.latitude if result else None,
351
'longitude': result.longitude if result else None,
352
'raw': result.raw if result else None
353
}
354
except Exception as e:
355
return {
356
'index': index,
357
'input_address': address,
358
'status': 'error',
359
'error': str(e),
360
'result_address': None,
361
'latitude': None,
362
'longitude': None,
363
'raw': None
364
}
365
366
async def batch_geocoding_example():
367
"""Example of batch geocoding from CSV file"""
368
369
# Sample data (normally would read from CSV)
370
addresses = [
371
"1600 Amphitheatre Parkway, Mountain View, CA",
372
"1 Apple Park Way, Cupertino, CA",
373
"350 Fifth Avenue, New York, NY",
374
"Times Square, New York, NY",
375
"Golden Gate Bridge, San Francisco, CA",
376
"Space Needle, Seattle, WA",
377
"Willis Tower, Chicago, IL",
378
"Hollywood Sign, Los Angeles, CA"
379
]
380
381
# Initialize batch geocoder
382
batch_geocoder = AsyncBatchGeocoder(
383
Nominatim,
384
batch_size=3,
385
delay=1.0,
386
user_agent="batch_geocoding_app"
387
)
388
389
# Process addresses
390
print(f"Starting batch geocoding of {len(addresses)} addresses...")
391
results = await batch_geocoder.geocode_batch(addresses)
392
393
# Analyze results
394
successful = sum(1 for r in results if r['status'] == 'success')
395
no_results = sum(1 for r in results if r['status'] == 'no_results')
396
errors = sum(1 for r in results if r['status'] == 'error')
397
398
print(f"\nBatch geocoding completed:")
399
print(f" Successful: {successful}")
400
print(f" No results: {no_results}")
401
print(f" Errors: {errors}")
402
403
# Save results to file
404
with open('geocoding_results.json', 'w') as f:
405
json.dump(results, f, indent=2)
406
407
print("Results saved to geocoding_results.json")
408
409
return results
410
411
# Run batch processing
412
asyncio.run(batch_geocoding_example())
413
```
414
415
### Async Error Handling
416
417
```python
418
import asyncio
419
from geopy.geocoders import Nominatim
420
from geopy.adapters import AioHTTPAdapter
421
from geopy.exc import *
422
423
async def robust_async_geocoding():
424
"""Async geocoding with comprehensive error handling"""
425
426
async def safe_geocode(geolocator, query, max_retries=3):
427
"""Safely geocode with retries and error handling"""
428
429
for attempt in range(max_retries):
430
try:
431
result = await geolocator.geocode(query, timeout=10)
432
return {'status': 'success', 'result': result, 'query': query}
433
434
except GeocoderRateLimited as e:
435
if attempt == max_retries - 1:
436
return {'status': 'rate_limited', 'error': str(e), 'query': query}
437
438
wait_time = e.retry_after if e.retry_after else 2 ** attempt
439
print(f"Rate limited for '{query}', waiting {wait_time}s...")
440
await asyncio.sleep(wait_time)
441
442
except GeocoderTimedOut as e:
443
if attempt == max_retries - 1:
444
return {'status': 'timeout', 'error': str(e), 'query': query}
445
446
print(f"Timeout for '{query}', retrying...")
447
await asyncio.sleep(1)
448
449
except GeocoderServiceError as e:
450
return {'status': 'service_error', 'error': str(e), 'query': query}
451
452
except Exception as e:
453
return {'status': 'unexpected_error', 'error': str(e), 'query': query}
454
455
return {'status': 'max_retries_exceeded', 'query': query}
456
457
queries = [
458
"Valid Address, New York, NY",
459
"Invalid Address 123456789",
460
"London, UK",
461
"Tokyo, Japan"
462
]
463
464
async with Nominatim(
465
user_agent="robust_async_app",
466
adapter_factory=AioHTTPAdapter
467
) as geolocator:
468
469
# Process all queries concurrently
470
tasks = [safe_geocode(geolocator, query) for query in queries]
471
results = await asyncio.gather(*tasks)
472
473
# Analyze results
474
for result in results:
475
status = result['status']
476
query = result['query']
477
478
if status == 'success':
479
location = result['result']
480
if location:
481
print(f"✓ {query} -> {location.address}")
482
else:
483
print(f"○ {query} -> No results")
484
else:
485
error = result.get('error', 'Unknown error')
486
print(f"✗ {query} -> {status}: {error}")
487
488
asyncio.run(robust_async_geocoding())
489
```
490
491
### Performance Monitoring
492
493
```python
494
import asyncio
495
import time
496
from geopy.geocoders import Nominatim
497
from geopy.adapters import AioHTTPAdapter
498
499
async def async_performance_test():
500
"""Compare sync vs async performance"""
501
502
addresses = [
503
"New York, NY", "Los Angeles, CA", "Chicago, IL",
504
"Houston, TX", "Phoenix, AZ", "Philadelphia, PA",
505
"San Antonio, TX", "San Diego, CA", "Dallas, TX",
506
"San Jose, CA"
507
]
508
509
# Async performance test
510
start_time = time.time()
511
512
async with Nominatim(
513
user_agent="performance_test",
514
adapter_factory=AioHTTPAdapter
515
) as geolocator:
516
517
tasks = [geolocator.geocode(addr) for addr in addresses]
518
async_results = await asyncio.gather(*tasks, return_exceptions=True)
519
520
async_time = time.time() - start_time
521
522
# Sync performance test (for comparison)
523
start_time = time.time()
524
sync_geolocator = Nominatim(user_agent="performance_test")
525
sync_results = []
526
527
for addr in addresses:
528
try:
529
result = sync_geolocator.geocode(addr)
530
sync_results.append(result)
531
except Exception as e:
532
sync_results.append(e)
533
534
sync_time = time.time() - start_time
535
536
# Results
537
async_success = sum(1 for r in async_results if hasattr(r, 'address'))
538
sync_success = sum(1 for r in sync_results if hasattr(r, 'address'))
539
540
print(f"Performance Comparison:")
541
print(f"Async: {async_time:.2f}s ({async_success}/{len(addresses)} successful)")
542
print(f"Sync: {sync_time:.2f}s ({sync_success}/{len(addresses)} successful)")
543
print(f"Speedup: {sync_time/async_time:.2f}x")
544
545
asyncio.run(async_performance_test())
546
```