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

place-mapping.mddocs/

Place-based Mapping

Geocoding and mapping functionality for location-based visualizations. These capabilities combine geocoding services with tile downloading to create maps from place names and addresses.

Capabilities

Place Class

Geocode locations by name and automatically retrieve corresponding map tiles with integrated plotting capabilities for location-based visualizations.

class Place:
    """
    Geocode a place by name and get its map.

    Parameters:
    - search: str - Location to be searched (city, address, landmark, etc.)
    - zoom: int or None - Zoom level for map detail (auto-calculated if None)
    - path: str or None - Path to save raster file (no file saved if None)
    - zoom_adjust: int or None - Adjustment to auto-calculated zoom level
    - source: TileProvider, str, or None - Tile source (defaults to OpenStreetMap HOT)
    - geocoder: geopy.geocoders object - Geocoding service (defaults to Nominatim)

    Attributes:
    - geocode: geopy object - Geocoding result with full location data
    - s, n, e, w: float - Bounding box edges (south, north, east, west)
    - im: ndarray - Image array of the retrieved map
    - bbox: list - Bounding box [minX, minY, maxX, maxY] in lon/lat
    - bbox_map: tuple - Bounding box in Web Mercator coordinates
    - place: str - Formatted place name from geocoding result
    - search: str - Original search string
    - latitude, longitude: float - Center coordinates of the place
    - zoom: int - Zoom level used for tile retrieval
    - n_tiles: int - Number of tiles downloaded for the map
    """

    def __init__(self, search, zoom=None, path=None, zoom_adjust=None, 
                source=None, geocoder=None): ...

    def plot(self, ax=None, zoom='auto', interpolation='bilinear', 
            attribution=None):
        """
        Plot the Place object on matplotlib axes.

        Parameters:
        - ax: AxesSubplot or None - Matplotlib axes (creates new figure if None)
        - zoom: int or 'auto' - Zoom level (ignored, uses Place's zoom)
        - interpolation: str - Image interpolation method ('bilinear', 'nearest', etc.)
        - attribution: str or None - Attribution text (defaults to source attribution)

        Returns:
        AxesSubplot - Matplotlib axes containing the map
        """

Usage Examples:

import contextily as ctx
import matplotlib.pyplot as plt

# Basic place geocoding and mapping
place = ctx.Place("Central Park, New York")
print(f"Found: {place.place}")
print(f"Coordinates: {place.latitude:.4f}, {place.longitude:.4f}")
print(f"Zoom level: {place.zoom}, Tiles: {place.n_tiles}")

# Display the map
place.plot()
plt.show()

# Custom zoom level
place_detailed = ctx.Place("Times Square, NYC", zoom=16)
place_detailed.plot()
plt.show()

# Save map to file while creating Place
place_saved = ctx.Place("Golden Gate Bridge", 
                       path="golden_gate.tiff", 
                       zoom=14)

# Use different tile provider
place_satellite = ctx.Place("Yellowstone National Park",
                          source=ctx.providers.ESRI.WorldImagery,
                          zoom=10)
place_satellite.plot()
plt.show()

# International locations
place_international = ctx.Place("Eiffel Tower, Paris, France")
print(f"Bbox: {place_international.bbox}")  # [lon_min, lat_min, lon_max, lat_max]
place_international.plot()
plt.show()

Advanced Place Usage

import contextily as ctx
import geopy

# Custom geocoder configuration
from geopy.geocoders import Nominatim
custom_geocoder = Nominatim(user_agent="my_app_v1.0", timeout=10)

place = ctx.Place("1600 Pennsylvania Avenue, Washington DC",
                 geocoder=custom_geocoder,
                 zoom=15)

# Access detailed geocoding information
print(f"Full address: {place.geocode.address}")
print(f"Raw data: {place.geocode.raw}")

# Fine-tune zoom level
place_adjusted = ctx.Place("Statue of Liberty", 
                         zoom_adjust=2)  # 2 levels more detailed

# Plot on existing axes
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

place1 = ctx.Place("San Francisco")
place1.plot(ax=ax1)

place2 = ctx.Place("Los Angeles") 
place2.plot(ax=ax2)

plt.suptitle("California Cities")
plt.show()

Place Data Access

import contextily as ctx
import numpy as np

# Create place and access data
place = ctx.Place("Grand Canyon National Park")

# Geographic information
print(f"Center: ({place.latitude}, {place.longitude})")
print(f"Bounding box (lon/lat): {place.bbox}")
print(f"Bounding box (Web Mercator): {place.bbox_map}")

# Image data
print(f"Image shape: {place.im.shape}")  # (height, width, bands)
print(f"Image data type: {place.im.dtype}")

# Use image data directly
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 8))
ax.imshow(place.im, extent=place.bbox_map)
ax.set_title(place.place)
plt.show()

# Combine with other geospatial data
# Convert to same coordinate system as needed
extent_4326 = place.bbox  # Already in WGS84
extent_3857 = place.bbox_map  # Web Mercator

Deprecated Plot Function

def plot_map(place, bbox=None, title=None, ax=None, axis_off=True, 
            latlon=True, attribution=None):
    """
    Plot map of given place (deprecated).

    Parameters:
    - place: Place instance or ndarray - Place object or image array to plot
    - bbox: tuple or None - Bounding box for display extent  
    - title: str or None - Plot title
    - ax: AxesSubplot or None - Matplotlib axes (creates new if None)
    - axis_off: bool - Whether to turn off axis border and ticks
    - latlon: bool - Whether bbox is in lat/lon coordinates
    - attribution: str or None - Attribution text

    Returns:
    AxesSubplot - Matplotlib axes containing the plot

    Note: This function is deprecated. Use add_basemap or Place.plot instead.
    """

Migration from plot_map:

import contextily as ctx
import warnings

# Old deprecated approach (avoid)
place = ctx.Place("Boston")
with warnings.catch_warnings():
    warnings.simplefilter("ignore", DeprecationWarning)
    ax = ctx.plot_map(place)

# New recommended approach
place = ctx.Place("Boston")
ax = place.plot()  # Preferred method

Geocoding Customization

Alternative Geocoding Services

import contextily as ctx
from geopy.geocoders import GoogleV3, Bing, ArcGIS

# Google Geocoding (requires API key)
google_geocoder = GoogleV3(api_key="your_api_key")
place_google = ctx.Place("Space Needle", geocoder=google_geocoder)

# Bing Geocoding (requires API key)  
bing_geocoder = Bing(api_key="your_api_key")
place_bing = ctx.Place("Pike Place Market", geocoder=bing_geocoder)

# ArcGIS Geocoding (free, rate limited)
arcgis_geocoder = ArcGIS(timeout=10)
place_arcgis = ctx.Place("Mount Rainier", geocoder=arcgis_geocoder)

# Compare results
places = [place_google, place_bing, place_arcgis]
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

for i, place in enumerate(places):
    place.plot(ax=axes[i])
    axes[i].set_title(f"{place.search} via {type(place.geocode.geocoder).__name__}")

plt.show()

Handling Geocoding Errors

import contextily as ctx
from geopy.exc import GeopyError, GeocoderTimedOut

def safe_place_creation(search_term, **kwargs):
    """Create Place with error handling."""
    try:
        return ctx.Place(search_term, **kwargs)
    except GeopyError as e:
        print(f"Geocoding failed for '{search_term}': {e}")
        return None
    except GeocoderTimedOut:
        print(f"Geocoding timed out for '{search_term}'")
        return None
    except Exception as e:
        print(f"Place creation failed for '{search_term}': {e}")
        return None

# Usage with error handling
places = []
search_terms = [
    "New York City",
    "Invalid Location XYZ123",  # Will fail
    "London, England"
]

for term in search_terms:
    place = safe_place_creation(term, zoom=10)
    if place:
        places.append(place)

# Plot successful results
if places:
    fig, axes = plt.subplots(1, len(places), figsize=(5*len(places), 5))
    if len(places) == 1:
        axes = [axes]
    
    for ax, place in zip(axes, places):
        place.plot(ax=ax)

Batch Processing

Multiple Locations

import contextily as ctx
import matplotlib.pyplot as plt

def create_place_grid(locations, cols=3):
    """Create a grid plot of multiple places."""
    n_places = len(locations)
    rows = (n_places + cols - 1) // cols
    
    fig, axes = plt.subplots(rows, cols, figsize=(5*cols, 5*rows))
    axes = np.array(axes).flatten() if n_places > 1 else [axes]
    
    places = []
    for i, location in enumerate(locations):
        try:
            place = ctx.Place(location, zoom=12)
            place.plot(ax=axes[i])
            places.append(place)
        except Exception as e:
            axes[i].text(0.5, 0.5, f"Failed to load\n{location}", 
                        ha='center', va='center', transform=axes[i].transAxes)
            print(f"Failed to create place for {location}: {e}")
    
    # Hide unused subplots
    for i in range(len(locations), len(axes)):
        axes[i].set_visible(False)
    
    plt.tight_layout()
    return places

# Example usage
major_cities = [
    "New York City",
    "Los Angeles", 
    "Chicago",
    "Houston",
    "Phoenix",
    "Philadelphia"
]

places = create_place_grid(major_cities, cols=3)
plt.show()

Route Visualization

import contextily as ctx
import matplotlib.pyplot as plt
import numpy as np

def create_route_map(start_location, end_location, zoom=10):
    """Create a map showing route between two locations."""
    
    # Geocode start and end points
    start_place = ctx.Place(start_location)
    end_place = ctx.Place(end_location)
    
    # Calculate combined bounding box
    all_lons = [start_place.longitude, end_place.longitude]
    all_lats = [start_place.latitude, end_place.latitude]
    
    bbox_combined = [
        min(all_lons) - 0.1,  # west
        min(all_lats) - 0.1,  # south  
        max(all_lons) + 0.1,  # east
        max(all_lats) + 0.1   # north
    ]
    
    # Download map for combined area
    img, extent = ctx.bounds2img(*bbox_combined, zoom=zoom, ll=True)
    
    # Create plot
    fig, ax = plt.subplots(figsize=(12, 8))
    ax.imshow(img, extent=extent)
    
    # Convert coordinates to Web Mercator for plotting
    import mercantile as mt
    start_x, start_y = mt.xy(start_place.longitude, start_place.latitude)
    end_x, end_y = mt.xy(end_place.longitude, end_place.latitude)
    
    # Plot points and line
    ax.scatter([start_x], [start_y], c='green', s=100, label=start_location, zorder=5)
    ax.scatter([end_x], [end_y], c='red', s=100, label=end_location, zorder=5)
    ax.plot([start_x, end_x], [start_y, end_y], 'b--', linewidth=2, alpha=0.7, zorder=4)
    
    ax.legend()
    ax.set_title(f"Route: {start_location} to {end_location}")
    plt.show()
    
    return start_place, end_place

# Example usage  
start, end = create_route_map("San Francisco", "Los Angeles")

Integration with Other Libraries

GeoPandas Integration

import contextily as ctx
import geopandas as gpd
import matplotlib.pyplot as plt

# Create place and convert to GeoDataFrame
place = ctx.Place("Washington DC", zoom=12)

# Create point geometry for the place center
from shapely.geometry import Point
point = Point(place.longitude, place.latitude)
gdf = gpd.GeoDataFrame([{'name': place.place, 'geometry': point}], crs='EPSG:4326')

# Convert to Web Mercator for basemap overlay
gdf_3857 = gdf.to_crs(epsg=3857)

# Plot with basemap
ax = gdf_3857.plot(figsize=(10, 8), color='red', markersize=100)
ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.HOT)
plt.title(f"Location: {place.place}")
plt.show()

Folium Integration

import contextily as ctx
import folium

# Geocode location
place = ctx.Place("Central Park, NYC")

# Create folium map centered on the place
m = folium.Map(
    location=[place.latitude, place.longitude],
    zoom_start=place.zoom
)

# Add marker
folium.Marker(
    [place.latitude, place.longitude],
    popup=place.place,
    tooltip=f"Zoom: {place.zoom}, Tiles: {place.n_tiles}"
).add_to(m)

# Add bounding box
folium.Rectangle(
    bounds=[[place.bbox[1], place.bbox[0]], [place.bbox[3], place.bbox[2]]],
    color='red',
    fill=False
).add_to(m)

# Display map
m.save('place_map.html')

Performance and Caching

Optimizing Place Creation

import contextily as ctx

# Set persistent cache for place images
ctx.set_cache_dir('~/contextily_places_cache')

# Reuse geocoding results
class PlaceCache:
    def __init__(self):
        self.geocode_cache = {}
    
    def get_place(self, search, **kwargs):
        if search in self.geocode_cache:
            # Reuse geocoding result
            cached_geocode = self.geocode_cache[search]
            place = ctx.Place.__new__(ctx.Place)
            place.geocode = cached_geocode
            place.search = search
            # Initialize other attributes...
            place._get_map()
            return place
        else:
            # Create new place and cache geocoding
            place = ctx.Place(search, **kwargs)
            self.geocode_cache[search] = place.geocode
            return place

# Usage
cache = PlaceCache()
place1 = cache.get_place("Paris, France")  # First time: geocodes
place2 = cache.get_place("Paris, France")  # Second time: uses cache

Bulk Location Processing

import contextily as ctx
from concurrent.futures import ThreadPoolExecutor
import time

def create_place_safe(location_data):
    """Thread-safe place creation."""
    location, zoom = location_data
    try:
        place = ctx.Place(location, zoom=zoom)
        return place
    except Exception as e:
        print(f"Failed to create place for {location}: {e}")
        return None

def batch_create_places(locations, max_workers=3):
    """Create multiple places in parallel (be respectful of geocoding services)."""
    location_data = [(loc, 10) for loc in locations]
    
    places = []
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        results = executor.map(create_place_safe, location_data)
        places = [place for place in results if place is not None]
    
    return places

# Example usage (be mindful of geocoding service rate limits)
locations = ["New York", "Boston", "Philadelphia", "Washington DC"]
places = batch_create_places(locations, max_workers=2)

# Plot results
if places:
    fig, axes = plt.subplots(2, 2, figsize=(12, 12))
    axes = axes.flatten()
    
    for i, place in enumerate(places[:4]):
        place.plot(ax=axes[i])
    
    plt.tight_layout()
    plt.show()

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