MapillaryClient#

class rapidtools.data_sources.MapillaryClient(access_token: str | None = None, save_dir: str | Path = 'mapillary_images', *, api_key: str | None = None, save_directory: str | Path | None = None)[source]#

Bases: object

Fetch and store Mapillary images by ID or geographic bounding box.

The client resolves image metadata through the Mapillary Graph API, discovers images inside an area through Mapillary vector tiles, and optionally downloads image files and rasterizes segmentation detections into masks. A Mapillary API access token is required.

Variables:
  • access_token (str) – The Mapillary API token sent with every request.

  • save_dir (Path) – Directory where downloaded images and masks are written.

  • session (requests.Session) – A pre-configured session with retry logic and default headers used for image downloads.

  • AVAILABLE_IMAGE_FIELDS (frozenset[str]) – The set of metadata fields that may be requested from the Graph API image endpoint.

Example

>>> from rapidtools.data_sources.mapillary_client import MapillaryClient
>>>
>>> client = MapillaryClient('MLY|123|abc', save_dir='panos')
>>> asset = client.fetch_image(
...     '1234567890',
...     fields=['captured_at', 'compass_angle'],
...     save_to_disk=False,
... )
>>> asset.properties['compass_angle']
182.4

Methods

decode_detection_polygons

Decode a detection's vector-tile geometry into normalised polygons.

fetch_detections

Fetch Mapillary's segmentation detections for one image as metadata.

fetch_image

Fetch image metadata using its Mapillary ID and optionally download it.

fetch_images_by_ids

Get multiple images by ID and return them as a collection.

fetch_images_in_bbox

Download and parse Mapillary vector tiles covering a bbox in parallel.

fetch_map_features

Fetch Mapillary's pre-triangulated point features inside a bounding box.

fetch_sequence_lines

Read the survey sequences drawn in one Mapillary coverage tile.

get_image_url

Return a (time-limited) download URL for an image at a given size.

fetch_image(image_id: str, fields: list[str] | None = None, save_to_disk: bool = True, process_masks: list[Literal['semantic', 'instance']] | None = None) → ImageAsset | None[source]#

Fetch image metadata using its Mapillary ID and optionally download it.

This method:
  • Validates the requested metadata fields.

  • Fetches image metadata from the API.

  • Extracts the download URL from the metadata.

  • Downloads the image file to the configured save directory.

  • Optionally generates and saves segmentation masks.

  • Returns an ImageAsset containing the file path, ID, and metadata.

Parameters:
  • image_id (str) – The identifier of the image to fetch from the API.

  • fields (list[str] | None) – Optional list of metadata fields to request. If None, all fields defined in AVAILABLE_IMAGE_FIELDS are requested. Invalid fields are ignored with a warning.

  • save_to_disk (bool) – If True, downloads the image to the configured save directory. Defaults to True.

  • process_masks (list[str] | None) – A list of mask types to generate. Options are 'semantic' and 'instance'. Pass None or [] to skip segmentation. Example: ['semantic'] or ['semantic', 'instance'].

Returns:

ImageAsset | None – An ImageAsset whose path is the local path of the downloaded file, whose id is the API id if present (otherwise the requested image_id), and whose properties hold the remaining metadata fields returned by the API (excluding the id). Returns None if validation fails, metadata retrieval fails, no download URL is present, or the download itself fails.

Example

>>> client = MapillaryClient('MLY|123|abc')
>>> asset = client.fetch_image(
...     '1234567890',
...     fields=['width', 'height'],
...     save_to_disk=False,
...     process_masks=['semantic'],
... )
>>> asset.semantic_map
{0: 'void--unlabeled', 1: 'nature--sky', 2: 'construction--flat--road'}
fetch_detections(image_id: str, values: Iterable[str] | None = None) → list[dict[str, Any]][source]#

Fetch Mapillary’s segmentation detections for one image as metadata.

No pixels are downloaded: each detection is a label and a base64-encoded vector-tile polygon, decodable with decode_detection_polygons(). This is what makes region-scale object discovery cheap.

Parameters:
  • image_id (str) – The Mapillary image key.

  • values (Iterable[str] | None) – Labels to keep (exact matches, e.g. ['object--vehicle--car']). None keeps every detection.

Returns:

list[dict[str, Any]] – {'value': label, 'geometry': base64_mvt} entries, or an empty list when the request fails or nothing matches.

Example

>>> client = MapillaryClient('MLY|123|abc')
>>> cars = client.fetch_detections(
...     '1234567890', values=['object--vehicle--car']
... )
>>> cars[0]['value']
'object--vehicle--car'
static decode_detection_polygons(b64_geometry: str) → list[list[tuple[float, float]]][source]#

Decode a detection’s vector-tile geometry into normalised polygons.

Parameters:

b64_geometry (str) – The base64-encoded Mapbox Vector Tile carried by a detection.

Returns:

list[list[tuple[float, float]]] – Exterior rings with x and y in [0, 1], y growing downwards like image pixels. Multi-polygons yield one ring per part; malformed geometry yields an empty list.

Example

>>> import base64, mapbox_vector_tile
>>> tile = mapbox_vector_tile.encode([{
...     'name': 'mpy-or',
...     'features': [{
...         'geometry': 'POLYGON((0 0, 2048 0, 2048 2048, 0 2048, 0 0))',
...         'properties': {},
...     }],
... }])
>>> rings = MapillaryClient.decode_detection_polygons(
...     base64.encodebytes(tile).decode()
... )
>>> sorted(rings[0])[:2]
[(0.0, 0.5), (0.0, 1.0)]
fetch_sequence_lines(z: int, x: int, y: int, filter_rapid_only: bool = True, start_date: str = '', end_date: str = '') → dict[str, Any][source]#

Read the survey sequences drawn in one Mapillary coverage tile.

Mapillary’s coverage tiles carry a sequence layer at zoom 6 to 14 (the routes driven, as polylines) so a map can show where street-level imagery exists without listing images. Coordinates are returned in the tile’s own pixel space, y growing downwards, so a client can draw them straight onto the tile.

Parameters:
  • z (int) – Tile zoom, 6 to 14.

  • x (int) – Tile column.

  • y (int) – Tile row.

  • filter_rapid_only (bool) – Keep only sequences uploaded by the RAPID Facility. Defaults to True.

  • start_date (str) – Inclusive lower bound on the capture date (YYYY-MM-DD); empty for none.

  • end_date (str) – Inclusive upper bound; empty for none.

Returns:

dict[str, Any] –

extent (tile size in local units, normally

4096), lines as flat [x0, y0, x1, y1, ...] lists, count of sequences kept, first / last capture dates and ok. A tile that cannot be read after three attempts returns ok=False with an error message and no lines, so callers can tell a failure from an empty tile.

Example

>>> client = MapillaryClient('MLY|123|abc')
>>> tile = client.fetch_sequence_lines(10, 178, 365)
>>> tile['count'], tile['lines'][0][:4]
(12, [2048, 102, 2051, 130])
fetch_map_features(bbox: BoundingBox, object_values: Sequence[str], start_date: str = '', end_date: str = '', fields: Sequence[str] | None = None, limit: int = 2000, max_depth: int = 6) → list[dict[str, Any]][source]#

Fetch Mapillary’s pre-triangulated point features inside a bounding box.

Map features are objects Mapillary has already located on the map by triangulating detections across several images: utility poles, fire hydrants, street lights, traffic signs, benches, trash cans, manholes, catch basins and so on (see MAP_FEATURE_VALUES). Each comes back as a single WGS84 point, so no imagery has to be downloaded or segmented to inventory them.

The endpoint caps the number of features per response at limit and offers no pagination for bounding-box queries, so whenever a response is full the box is split into four quadrants that are queried recursively (down to max_depth levels) and the results are merged by feature id.

Parameters:
  • bbox (BoundingBox) – The area of interest in WGS84 coordinates.

  • object_values (Sequence[str]) – Mapillary feature values to request, e.g. ['object--support--utility-pole', 'object--fire-hydrant'].

  • start_date (str) – Inclusive lower bound (YYYY-MM-DD) of the observation window. A feature is kept when its [first_seen_at, last_seen_at] span overlaps [start_date, end_date]; a missing date on either side counts as overlapping. Empty for no bound.

  • end_date (str) – Inclusive upper bound of the observation window; empty for no bound.

  • fields (Sequence[str] | None) – Graph API fields to request. Defaults to ('id', 'object_value', 'geometry', 'aligned_direction', 'first_seen_at', 'last_seen_at'). Add 'images' to also receive the ids of the images that saw each feature.

  • limit (int) – Maximum features per request. Mapillary documents 2000 as the maximum for this endpoint. Defaults to 2000.

  • max_depth (int) – Maximum number of quadrant splits applied to a saturated box. Defaults to 6 (4096 sub-boxes at most).

Returns:

list[dict[str, Any]] – One dict per feature with the keys id, object_value, lon, lat, aligned_direction (degrees or None), first_seen_at and last_seen_at (ISO strings or None) and image_ids (populated only when 'images' is requested, otherwise []), plus any other requested field copied verbatim. HTTP errors on a (sub-)box are logged as warnings and that box is skipped, so a partial result is returned rather than an exception raised.

Example

>>> from rapidtools.core import BoundingBox
>>> client = MapillaryClient('MLY|123|abc')
>>> bbox = BoundingBox(-117.50, 47.71, -117.49, 47.72)
>>> poles = client.fetch_map_features(
...     bbox, ['object--support--utility-pole']
... )
>>> poles[0]['object_value'], poles[0]['lon']
('object--support--utility-pole', -117.49456)
get_image_url(image_id: str, size: str = '2048') → str | None[source]#

Return a (time-limited) download URL for an image at a given size.

Parameters:
  • image_id (str) – The Mapillary image key.

  • size (str) – '256', '1024', '2048' or 'original'.

Returns:

str | None – The URL, or None when the request fails.

Example

>>> client = MapillaryClient('MLY|123|abc')
>>> client.get_image_url('1234567890', size='1024')
'https://scontent.../1234567890_1024.jpg'
fetch_images_by_ids(image_ids: list[str], fields: list[str] | None, save_to_disk: bool = True, process_masks: list[Literal['semantic', 'instance']] | None = None, max_workers: int = 10, show_progress: bool = True) → ImageCollection[source]#

Get multiple images by ID and return them as a collection.

This method:
  • Submits one fetch task per image ID to a thread pool.

  • Fetches metadata and downloads each image (via fetch_image).

  • Aggregates successfully fetched ImageAsset objects into a list.

  • Logs any exceptions that occur per image without aborting the entire batch.

Parameters:
  • image_ids (list[str]) – A list of image IDs to fetch and download.

  • fields (list[str] | None) – Optional list of metadata fields to request for each image. If None, all available fields are requested.

  • save_to_disk (bool) – If True, downloads image files to self.save_dir. If False, only metadata is retrieved. Defaults to True.

  • process_masks (list[str] | None) – A list of mask types to generate. Options are 'semantic' and 'instance'. Pass None or [] to skip segmentation. Example: ['semantic'] or ['semantic', 'instance'].

  • max_workers (int) – Maximum number of parallel worker threads used for fetching images. Defaults to 10.

  • show_progress (bool) – Show a progress bar. Callers that fetch in batches pass False to keep their own bar. Defaults to True.

Returns:

ImageCollection – An ImageCollection containing all successfully fetched ImageAsset instances. Any images that fail to download or process are excluded; errors are logged per image ID.

Example

>>> client = MapillaryClient('MLY|123|abc')
>>> collection = client.fetch_images_by_ids(
...     ['111', '222'], fields=['captured_at'], save_to_disk=False
... )
>>> collection.get_ids()
['111', '222']
fetch_images_in_bbox(bbox: BoundingBox, fields: list[str] | None = None, save_to_disk: bool = False, process_masks: list[Literal['semantic', 'instance']] | None = None, start_date: str = '', end_date: str = '', filter_rapid_only: bool = True, max_workers: int = 10) → ImageCollection[source]#

Download and parse Mapillary vector tiles covering a bbox in parallel.

The bounding box is first converted into the set of zoom-14 XYZ tiles that cover it. Each tile is downloaded and decoded to discover image locations. If richer metadata or file downloads are requested, the Graph API is then queried for every discovered image ID and the results are merged back into the tile-derived collection.

Parameters:
  • bbox (BoundingBox) – The geographic rectangular area to search. Must be an instance of rapidtools.core.BoundingBox.

  • fields (list[str] | None) – A list of specific metadata fields to fetch (e.g., ['captured_at', 'compass_angle']). If None and save_to_disk is False, only the lightweight tile metadata (location, date) is returned and the Graph API is not queried.

  • save_to_disk (bool) – If True, downloads the images to the configured save directory. Defaults to False.

  • process_masks (list[str] | None) – A list of mask types to generate. Options are 'semantic' and 'instance'. Pass None or [] to skip segmentation.

  • start_date (str) – Inclusive lower bound on capture date in YYYY-MM-DD format. An empty string disables the lower bound.

  • end_date (str) – Inclusive upper bound on capture date in YYYY-MM-DD format. An empty string disables the upper bound.

  • filter_rapid_only (bool) – If True, only images uploaded by the RAPID facility account are returned. Defaults to True.

  • max_workers (int) – Number of parallel download threads. Defaults to 10.

Returns:

ImageCollection – A new ImageCollection populated with assets from all tiles. Empty if the bounding box yields no tiles or no matching images.

Example

>>> from rapidtools.core import BoundingBox
>>>
>>> client = MapillaryClient('MLY|123|abc')
>>> bbox = BoundingBox(-118.15, 34.18, -118.14, 34.19)
>>> images = client.fetch_images_in_bbox(
...     bbox, start_date='2025-01-01', filter_rapid_only=False
... )
>>> images[0].properties['capture_date']
'2025-02-14'