Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/envs/310-conda-forge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
- requests
- joblib
- xyzservices
- tqdm
# testing
- pip
- pytest
Expand Down
1 change: 1 addition & 0 deletions ci/envs/311-conda-forge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
- requests
- joblib
- xyzservices
- tqdm
# testing
- pip
- pytest
Expand Down
1 change: 1 addition & 0 deletions ci/envs/312-latest-conda-forge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
- requests
- joblib
- xyzservices
- tqdm
# testing
- pip
- pytest
Expand Down
1 change: 1 addition & 0 deletions ci/envs/313-latest-conda-forge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
- requests
- joblib
- xyzservices
- tqdm
# testing
- pip
- pytest
Expand Down
1 change: 1 addition & 0 deletions contextily/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .place import Place, plot_map
from .tile import *
from .plotting import add_basemap, add_attribution
from .progress import set_progress_bar

from importlib.metadata import PackageNotFoundError, version

Expand Down
56 changes: 56 additions & 0 deletions contextily/progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from tqdm import tqdm as _default_progress_bar
from contextlib import nullcontext

# Default progress bar class (can be changed by set_progress_bar)
_progress_bar = _default_progress_bar

def set_progress_bar(progress_bar=None):
"""
Set the progress bar class to be used for downloading tiles.

Parameters
----------
progress_bar : class, optional
A tqdm-compatible progress bar class. If None, progress bar is disabled.
The progress bar class should have the same interface as tqdm.
Common alternatives include:
- tqdm.notebook.tqdm for Jupyter notebooks
- custom implementations with the same interface
"""
global _progress_bar
_progress_bar = progress_bar


def get_progress_bar():
"""
Returns the progress bar class to be used for downloading tiles.
If progress bars are disabled (set to None), returns a no-op context
manager that doesn't display progress.

Returns
----------
progress_bar : callable
A callable that returns either a tqdm-compatible progress bar or a
no-op context manager with update/close methods if progress bars
are disabled.
"""
if _progress_bar is None:
class NoOpProgress:
def __init__(self, *args, **kwargs):
self.context = nullcontext()

def __enter__(self):
self.context.__enter__()
return self

def __exit__(self, *args):
self.context.__exit__(*args)

def update(self, n=1):
pass

def close(self):
pass

return lambda *args, **kwargs: NoOpProgress(*args, **kwargs)
return _progress_bar
33 changes: 23 additions & 10 deletions contextily/tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
import rasterio as rio
from PIL import Image, UnidentifiedImageError
from joblib import Memory as _Memory
from joblib import Parallel, delayed
from concurrent.futures import ThreadPoolExecutor, as_completed
from rasterio.transform import from_origin
from .progress import get_progress_bar
from rasterio.io import MemoryFile
from rasterio.vrt import WarpedVRT
from rasterio.enums import Resampling
Expand Down Expand Up @@ -273,16 +274,28 @@ def bounds2img(
# download tiles
if n_connections < 1 or not isinstance(n_connections, int):
raise ValueError(f"n_connections must be a positive integer value.")
# Use threads for a single connection to avoid the overhead of spawning a process. Use processes for multiple
# connections if caching is enabled, as threads lead to memory issues when used in combination with the joblib
# memory caching (used for the _fetch_tile() function).
preferred_backend = (
"threads" if (n_connections == 1 or not use_cache) else "processes"
)

fetch_tile_fn = memory.cache(_fetch_tile) if use_cache else _fetch_tile
arrays = Parallel(n_jobs=n_connections, prefer=preferred_backend)(
delayed(fetch_tile_fn)(tile_url, wait, max_retries) for tile_url in tile_urls
)

arrays = [None] * len(tile_urls) # Pre-allocate result list
with get_progress_bar()(total=len(tile_urls), desc="Downloading tiles") as pbar:
with ThreadPoolExecutor(max_workers=n_connections) as executor:
# Submit all tasks and store futures with their indices
future_to_index = {
executor.submit(fetch_tile_fn, url, wait, max_retries): idx
for idx, url in enumerate(tile_urls)
}

# Process completed futures as they finish
for future in as_completed(future_to_index):
idx = future_to_index[future]
try:
arrays[idx] = future.result()
except Exception as e:
# Re-raise any exceptions from the worker
raise e from None
pbar.update(1)

# merge downloaded tiles
merged, extent = _merge_tiles(tiles, arrays)
# lon/lat extent --> Spheric Mercator
Expand Down
Loading
Loading