Getting Started with the new Mapillary API v4

Mapillary's API v4 is now online and will replace API v3 which will continue to function for a limited time. Read on to see how you can make the most of our new API.
Chris Beddow
23 June 2021

The new Mapillary API v4 is now online, replacing API v3. The old API will continue to function until August 18th, 2021, however, from June 21 onwards it will not serve new images. If you are building a new app, or looking to migrate your existing one, then API v4 is the way to go!

API v4 comes with fresh documentation to help you get acquainted, but in addition, we are going to review some important aspects of the API and examples of how to get started.

Key changes

  1. Retrieving images or data near a geographic location means retrieving a vector tile that overlaps the point or bounding box, rather than a GeoJSON as before. Spatial queries are supported only through vector tile layers on the client side.
  2. Authentication is done via OAuth 2.0 authorization code flow.
  3. You must generate a new access token (formerly known as client ID) for API v4, as your v3 client ID will not have access to the new API. Click `Register Application` on the developer dashboard, and enter information about you and your app, then copy the client token to use it with the API.
  4. You may also generate a user access token through the OAuth flow, rather than using a client access token, granting access to each logged in user.
  5. All fields you want to retrieve for an entity such as an image must be explicitly listed in the API request, but you can reduce the size of the response by opting to not list these fields.
  6. Image thumbnail URLs are no longer stable, have a TTL (time-to-live, i.e. will expire) and now leverage new caching infrastructure, meaning you must make an API call for the image key to get a fresh URL each time you want to view or fetch an image.
  7. Note that API v3 image keys are incompatible with API v4 keys. In other words, under API v4 each image on the platform has a fresh key. If you have image keys from API v3, they will not return data if making a request in API v4.

Endpoints to know

There are two key endpoints that we will review with examples today. These are:
  1. Root endpoint for metadata: https://graph.mapillary.com
  2. Vector tile coverage endpoint: https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}
For other vector tile data, you can use the following endpoints, which we will not review in the examples today:
  1. Vector tile with computer vision corrected coverage locations endpoint: https://tiles.mapillary.com/maps/vtp/mly1_computed_public/2/{z}/{x}/{y}
  2. Vector tile map features (points) endpoint: https://tiles.mapillary.com/maps/vtp/mly_map_feature_point/2/{z}/{x}/{y}
  3. Vector tile map features (traffic signs) endpoint: https://tiles.mapillary.com/maps/vtp/mly_map_feature_traffic_sign/2/{z}/{x}/{y}
For all of these vector tiles, your access token must be included, and the best way to do this is through a query parameter. For example: https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token=XXX This differs from using the metadata endpoint, where the access token should be included in the request header.

Getting image metadata

If you already know an image key or a list of keys, it is a simple process to retrieve data about that image. First, think about what fields you want to you include, some options being:

  • camera_type - the type of camera used for taking the photo
  • captured_at - the capture time
  • compass_angle - the original compass angle of the image
  • computed_compass_angle - the compass angle after running image processing
  • geometry - the GeoJSON Point geometry
  • computed_geometry - the GeoJSON geometry after running image processing
  • thumb_1024_url - the URL to the 1024px wide thumbnail (also try 256 and 2048)
  • sequence - the ID of the sequence to which the image belongs
  • sfm_cluster - the URL to the point cloud

There are several more options in the documentation. Assuming we already have an access token `MLY|XXX`, and want to retrieve the image key `1933525276802129`, we can make the following API request in the command line:

Curl -H “Authorization OAuth: MLY|XXX” https://graph.mapillary.com/1933525276802129?fields=id,captured_at,compass_angle,sequence,geometry

This generates a JSON response:

{ "id": "1933525276802129", "captured_at": 1507560881000, "compass_angle": 200.93476466846, "sequence": "f1ihYvTZNTEhCCnRjyMAuQ", "geometry": { "type": "Point", "coordinates": [ -97.743279722222, 30.270651388889 ] } }

By default, the id property will be included in the response, but all others must be specified in the list of fields.

Getting map features

If we know the ID of a map feature, we can retrieve information about it using the same method as images. For example, with map feature ID `852766358956987` we can find out what type it is (traffic sign or point), the value, the timestamp of the first image to capture it, and the geometry of the point:

Curl -H “Authorization OAuth: MLY|XXX” https://graph.mapillary.com/852766358956987?fields=id,object_value,object_type,geometry,first_seen_at

The response is a JSON object:

{ "id": "852766358956987", "object_value": "object--street-light", "object_type": "mvd_fast", "geometry": { "type": "Point", "coordinates": [ 55.616435388977, 12.99796317955 ] }, "first_seen_at": "2013-11-01T10:41:56+0000" }

Getting object detections

With an image key in mind, you can also make a request to get all detections in the image. This is achieved by making the same basic API request as previously demonstrated, but adding `/detections`. For detections, we also should specify fields which we want in the response data, such as `value` which describes what the detection’s class is, and `created_at` which is the timestamp when the detection was derived from the image. If a field is not specified, it will be excluded from the response data. Recycling our last example, we can make this request:

Curl -H “Authorization OAuth: MLY|XXX” https://graph.mapillary.com/1933525276802129/detections?fields=id,value,created_at This will return a list of detections, each a JSON object: { "id": "1942105415944115", "value": "regulatory--no-parking--g2", "created_at": "2021-05-20T17:49:01+0000" } Similarly, you can make the same request with a map feature ID rather than an image ID, and get a list of detections that were used to compose the map feature.

Querying image tiles

If you have a known geographic location such as map coordinates where a user clicked, you may want to retrieve images nearby. This requires use of a third party library to find the `z/x/y` tile coordinates, retrieve that tile, then filter for images or map features in the tile that are in a specified radius of the input coordinates.

Similarly, you may want to get all Mapillary data in a bounding box. This can again be achieved by calculating the `z/x/y` coordinates of tiles that overlap this bounding box with a third party library, then selecting from this only the images that fall inside the bounding box.

In the following example, we will utilize Python with the Mercantile library and the Mapbox Vector Tile library to request utility poles and street lights within a bounding box.

import mercantile, mapbox_vector_tile, requests, json
from vt2geojson.tools import vt_bytes_to_geojson

# define an empty geojson as output
output= { "type": "FeatureCollection", "features": [] }

# Mapillary access token -- user should provide their own
access_token = 'MLY|XXX'
        
# a bounding box in [west_lng,_south_lat,east_lng,north_lat] format
west, south, east, north = [-80.13423442840576,25.77376933762778,-80.1264238357544,25.788608487732198]
filter_values = ['object--support--utility-pole','object--street-light']
# get the tiles with x and y coors intersecting bbox at zoom 14 only
tiles = list(mercantile.tiles(east, south, west, north, 14))

With this, we have a list of tiles that contain all the features we want to download, as well as extra features outside the bounding box. To download, we will use `vt2geojson` library to decode the data, filter it according to what we want, and then save what falls inside the bounding box:

# loop through all tiles to get IDs of Mapillary data
for tile in tiles:
    tile_url = 'https://tiles.mapillary.com/maps/vtp/mly_map_feature_point/2/{}/{}/{}?access_token={}'.format(tile.z,tile.x,tile.y,access_token)
     response = requests.get(tile_url)
     data = vt_bytes_to_geojson(response.content, tile.x, tile.y, tile.z)
     filtered_data = [feature for feature in data['features'] if feature['properties']['value'] in filter_values]
      for feature in filtered_data:
          if (feature['geometry']['coordinates'][0] > east and feature['geometry']['coordinates'][0] < west)\
          and (feature['geometry']['coordinates'][1] > south and feature['geometry']['coordinates'][1] < north):
              output['features'].append(feature)

Map features retrieved with a bounding box

Similarly, you can input a point and use Mercantile to find the tile it intersects. After retrieving that tile, you should use a geospatial function such as `haversine()` from the `haversine` library to calculate distance between point pairs. The following example demonstrates finding the tile intersecting a point, then filtering for data within a specified radius and with matching values:

import mercantile, mapbox_vector_tile, requests, json
from vt2geojson.tools import vt_bytes_to_geojson
from haversine import haversine

# define an empty geojson as output
output= { "type": "FeatureCollection", "features": [] }

# Mapillary access token -- in a library the user should provide their own
access_token = 'MLY|XXX'

# values we want in the output
filter_values = ['object--support--utility-pole','object--street-light']
              
input_coords = [13.01765352487564,55.60817207168841]
zoom = 14
filter_radius = 200

tile = mercantile.tile(input_coords[0],input_coords[1],zoom)
tile_url = 'https://tiles.mapillary.com/maps/vtp/mly_map_feature_point/2/{}/{}/{}?access_token={}'.format(zoom,tile[0],tile[1],access_token)
response = requests.get(tile_url)
              
data = vt_bytes_to_geojson(response.content, tile.x, tile.y, tile.z)
filtered_data = [feature for feature in data['features'] if feature['properties']['value'] in filter_values]
              
for feature in filtered_data:
    distance = haversine(input_coords,feature['geometry']['coordinates'],unit="m")
    if distance < filter_radius:
        output['features'].append(feature)
        print(len(output['features']))
           

Finally, you can save the `output` variable to a local file:

with open('mydata.geojson', 'w') as f:
          json.dump(output, f)

Map features retrieved with a 200 meter radius search

Vector tiles on the web

If you are creating a web map, you can use libraries like Leaflet.js, the Esri JS API, or MapLibre to add the Mapillary vector tiles.For example, in MapLibre JS GL, you might add a layer using the following code:

map.on('load', function() {
    map.addSource('mapillary', {
        type: 'vector',
        tiles: ['https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}'],
        minzoom: 6,
        maxzoom: 14
    });
    map.addLayer({
        'id': 'mapillary-sequences',
        'type': 'line',
        'source': 'mapillary',
        'source-layer': 'sequence',
        'layout': {
            'line-join': 'round',
            'line-cap': 'round'
        },
        'paint': {
            'line-color': '#05CB63',
            'line-width': 1
        }
    });
    map.addLayer({
        'id': 'mapillary-images',
        'type': 'circle',
        'source': 'mapillary',
        'source-layer': 'image',
        'paint': {
            'circle-color': '#05CB63',
            'circle-radius': 5,
        }
    });
}); 

The lines and points will load on the map, turning out like the image below:

Mapillary vector tiles showing image coverage in central Dublin

Building with Mapillary

If your use case is not covered in this post but you still have a question, feel free to share with the community in the Mapillary forum or reach out to Mapillary support. We will continue to share any news and updates about API v4 as we process user and developer feedback, as well as sharing new examples to help cover diverse use cases.

In addition to sending us questions, make sure to share examples of your developer work! We are happy to see integrations across multiple platforms upgrading to the v4 API, including RapiD and JOSM editors for OpenStreetMap, and hope you will find the new API helpful in developing your own projects going forward.


/ Chris