CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-contextily

Context geo-tiles in Python for retrieving and working with basemap tiles from the internet

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

tile-operations.mddocs/

Tile Operations

Core functionality for downloading, caching, and processing map tiles from web services. These functions provide direct access to tile data for custom applications and analysis workflows.

Capabilities

Image Tile Retrieval

Download map tiles for specified geographic bounds and return as numpy arrays with extent information for direct use in applications.

def bounds2img(w, s, e, n, zoom='auto', source=None, ll=False, 
              wait=0, max_retries=2, n_connections=1, use_cache=True, 
              zoom_adjust=None):
    """
    Download tiles for bounding box and return as image array with extent.

    Parameters:
    - w: float - West edge coordinate
    - s: float - South edge coordinate  
    - e: float - East edge coordinate
    - n: float - North edge coordinate
    - zoom: int or 'auto' - Zoom level (calculated automatically if 'auto')
    - source: TileProvider, str, or None - Tile source (defaults to OpenStreetMap HOT)
    - ll: bool - If True, coordinates are lon/lat (EPSG:4326), otherwise Web Mercator (EPSG:3857)
    - wait: int - Seconds to wait between failed requests and retries
    - max_retries: int - Maximum number of retries for failed requests  
    - n_connections: int - Number of parallel connections (default 1, max recommended 16)
    - use_cache: bool - Whether to cache downloaded tiles
    - zoom_adjust: int - Adjustment to automatically calculated zoom level

    Returns:
    tuple: (img: ndarray, extent: tuple)
        - img: 3D array (height, width, bands) of RGB values
        - extent: (minX, maxX, minY, maxY) bounding box in Web Mercator
    """

Usage Examples:

import contextily as ctx
import matplotlib.pyplot as plt

# Download tiles for NYC area (lon/lat coordinates)
west, south, east, north = -74.1, 40.7, -73.9, 40.8  # NYC bounds 
img, extent = ctx.bounds2img(west, south, east, north, zoom=12, ll=True)

# Display the downloaded image
fig, ax = plt.subplots(figsize=(10, 8))
ax.imshow(img, extent=extent)
ax.set_title("NYC Basemap")
plt.show()

# Use different tile provider
img, extent = ctx.bounds2img(west, south, east, north, 
                           source=ctx.providers.Stamen.Toner, 
                           zoom=10, ll=True)

# High resolution with parallel downloads (be respectful of tile servers)
img, extent = ctx.bounds2img(west, south, east, north, 
                           zoom=15, n_connections=2, ll=True)

# Web Mercator coordinates (ll=False, default)
w_merc, s_merc, e_merc, n_merc = -8238310, 4969450, -8235310, 4972450
img, extent = ctx.bounds2img(w_merc, s_merc, e_merc, n_merc, zoom=14)

Raster File Output

Download map tiles and save directly to geospatial raster files with proper georeferencing for use in GIS applications.

def bounds2raster(w, s, e, n, path, zoom='auto', source=None, ll=False,
                 wait=0, max_retries=2, n_connections=1, use_cache=True):
    """
    Download tiles for bounding box and write to raster file in Web Mercator CRS.

    Parameters:
    - w: float - West edge coordinate
    - s: float - South edge coordinate
    - e: float - East edge coordinate  
    - n: float - North edge coordinate
    - path: str - Output raster file path
    - zoom: int or 'auto' - Zoom level (calculated automatically if 'auto')
    - source: TileProvider, str, or None - Tile source (defaults to OpenStreetMap HOT)
    - ll: bool - If True, coordinates are lon/lat, otherwise Web Mercator
    - wait: int - Seconds to wait between failed requests and retries
    - max_retries: int - Maximum number of retries for failed requests
    - n_connections: int - Number of parallel connections for downloading
    - use_cache: bool - Whether to cache downloaded tiles

    Returns:
    tuple: (img: ndarray, extent: tuple)
        - img: 3D array (height, width, bands) of RGB values  
        - extent: (minX, maxX, minY, maxY) bounding box in Web Mercator
    """

Usage Examples:

import contextily as ctx

# Download and save basemap for analysis area  
west, south, east, north = -122.5, 37.7, -122.3, 37.9  # San Francisco
img, extent = ctx.bounds2raster(west, south, east, north, 
                              'sf_basemap.tiff', zoom=13, ll=True)

# High-resolution satellite imagery
img, extent = ctx.bounds2raster(west, south, east, north,
                              'sf_satellite.tiff',
                              source=ctx.providers.ESRI.WorldImagery,
                              zoom=16, ll=True)

# Use the saved raster in other applications
import rasterio
with rasterio.open('sf_basemap.tiff') as src:
    print(f"CRS: {src.crs}")  # EPSG:3857
    print(f"Bounds: {src.bounds}")
    basemap_data = src.read()

Tile Count Estimation

Calculate the number of tiles required for a given area and zoom level to estimate download time and data usage.

def howmany(w, s, e, n, zoom, verbose=True, ll=False):
    """
    Calculate number of tiles required for bounding box and zoom level.

    Parameters:
    - w: float - West edge coordinate
    - s: float - South edge coordinate
    - e: float - East edge coordinate
    - n: float - North edge coordinate  
    - zoom: int - Zoom level (integer value required)
    - verbose: bool - Whether to print informative message
    - ll: bool - If True, coordinates are lon/lat, otherwise Web Mercator

    Returns:
    int - Number of tiles required
    """

Usage Examples:

import contextily as ctx

# Check tile count before downloading
west, south, east, north = -74.1, 40.7, -73.9, 40.8  # NYC
tile_count = ctx.howmany(west, south, east, north, zoom=15, ll=True)
# Output: "Using zoom level 15, this will download 64 tiles"

# Estimate for different zoom levels
for zoom in range(10, 17):
    count = ctx.howmany(west, south, east, north, zoom, verbose=False, ll=True)
    print(f"Zoom {zoom}: {count} tiles")

# Auto-calculated zoom estimation
count = ctx.howmany(west, south, east, north, zoom='auto', ll=True)

Cache Management

Control tile caching behavior for performance optimization and storage management.

def set_cache_dir(path):
    """
    Set custom cache directory for tile downloads.

    Parameters:
    - path: str - Path to cache directory

    Returns:
    None
    """

Usage Examples:

import contextily as ctx
import os

# Set persistent cache directory
cache_dir = os.path.expanduser('~/contextily_cache')
ctx.set_cache_dir(cache_dir)

# Now all tile downloads use persistent cache
img, extent = ctx.bounds2img(-74.1, 40.7, -73.9, 40.8, zoom=12, ll=True)

# Disable caching for specific downloads
img, extent = ctx.bounds2img(-74.1, 40.7, -73.9, 40.8, 
                           zoom=12, ll=True, use_cache=False)

Tile Sources and Providers

Default Provider

# Default source is OpenStreetMap Humanitarian tiles
img, extent = ctx.bounds2img(w, s, e, n, zoom=12, ll=True)
# Equivalent to:
img, extent = ctx.bounds2img(w, s, e, n, zoom=12, ll=True, 
                           source=ctx.providers.OpenStreetMap.HOT)

Popular Providers

# Satellite imagery
img, extent = ctx.bounds2img(w, s, e, n, zoom=14, ll=True,
                           source=ctx.providers.ESRI.WorldImagery)

# Terrain map
img, extent = ctx.bounds2img(w, s, e, n, zoom=12, ll=True,
                           source=ctx.providers.Stamen.Terrain)

# High contrast
img, extent = ctx.bounds2img(w, s, e, n, zoom=11, ll=True,
                           source=ctx.providers.Stamen.Toner)

Custom Tile URLs

# Custom tile server
custom_url = "https://tiles.example.com/{z}/{x}/{y}.png"
img, extent = ctx.bounds2img(w, s, e, n, zoom=12, ll=True, source=custom_url)

# TileProvider object
from xyzservices import TileProvider
provider = TileProvider(
    url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
    attribution="© Esri"
)
img, extent = ctx.bounds2img(w, s, e, n, zoom=12, ll=True, source=provider)

Performance and Rate Limiting

Parallel Downloads

# Single connection (default, most respectful)
img, extent = ctx.bounds2img(w, s, e, n, zoom=14, ll=True, n_connections=1)

# Multiple connections (check provider's terms of service)
# OpenStreetMap allows max 2 connections
img, extent = ctx.bounds2img(w, s, e, n, zoom=14, ll=True, n_connections=2)

# Higher concurrency for servers that allow it
img, extent = ctx.bounds2img(w, s, e, n, zoom=12, ll=True, n_connections=8)

Rate Limiting and Retries

# Handle rate-limited APIs
img, extent = ctx.bounds2img(w, s, e, n, zoom=14, ll=True,
                           wait=1,        # Wait 1 second between retries
                           max_retries=5) # Try up to 5 times

# Disable retries for fast failure
img, extent = ctx.bounds2img(w, s, e, n, zoom=12, ll=True, max_retries=0)

Memory Management

# Disable caching for memory-constrained environments
img, extent = ctx.bounds2img(w, s, e, n, zoom=15, ll=True, 
                           use_cache=False, n_connections=4)

# Process large areas in chunks
def download_large_area(w, s, e, n, zoom, chunk_size=0.01):
    """Download large area in smaller chunks to manage memory."""
    chunks = []
    for lat in np.arange(s, n, chunk_size):
        for lon in np.arange(w, e, chunk_size):
            chunk_w, chunk_s = lon, lat
            chunk_e, chunk_n = min(lon + chunk_size, e), min(lat + chunk_size, n)
            img, extent = ctx.bounds2img(chunk_w, chunk_s, chunk_e, chunk_n, 
                                       zoom=zoom, ll=True, use_cache=False)
            chunks.append((img, extent))
    return chunks

Error Handling

Common Error Scenarios

import contextily as ctx
import requests

try:
    img, extent = ctx.bounds2img(w, s, e, n, zoom=18, ll=True)
except requests.HTTPError as e:
    if "404" in str(e):
        print("Tiles not available at this zoom level")
    else:
        print(f"HTTP error: {e}")
except ValueError as e:
    print(f"Invalid parameters: {e}")
except Exception as e:
    print(f"Download failed: {e}")

# Validate zoom level for provider
try:
    # Some providers have max zoom limits
    img, extent = ctx.bounds2img(w, s, e, n, zoom=20, ll=True,
                               source=ctx.providers.OpenStreetMap.HOT)
except ValueError as e:
    print(f"Zoom level error: {e}")
    # Retry with lower zoom
    img, extent = ctx.bounds2img(w, s, e, n, zoom=18, ll=True)

Graceful Fallbacks

def robust_tile_download(w, s, e, n, zoom=12, ll=True):
    """Download tiles with fallback providers."""
    providers_to_try = [
        ctx.providers.OpenStreetMap.HOT,
        ctx.providers.CartoDB.Positron,
        ctx.providers.Stamen.Toner
    ]
    
    for provider in providers_to_try:
        try:
            return ctx.bounds2img(w, s, e, n, zoom=zoom, ll=ll, source=provider)
        except Exception as e:
            print(f"Provider {provider} failed: {e}")
            continue
    
    raise Exception("All tile providers failed")

# Usage
img, extent = robust_tile_download(-74.1, 40.7, -73.9, 40.8, zoom=12)

Install with Tessl CLI

npx tessl i tessl/pypi-contextily

docs

basemap-integration.md

coordinate-transformation.md

index.md

place-mapping.md

tile-operations.md

tile.json