GIS Beginnings

Topo

Having patiently been awaiting attention on the always growing list of to-dos, I’ve finally directed focus towards GIS (geographic information system). Making sense of maps has been a big component in many of my personal pursuits and merging that with the extensibility of the digital world is an obvious next step.

I’m going to start with Mapbox to practice making basic requests to their API:

import os
import requests
import urllib.parse
import mercantile

ACCESS_TOKEN = os.environ['MAPBOX_ACCESS_TOKEN']
USER_NAME = os.environ['MAPBOX_USER_NAME']
BASE_PATH = "https://api.mapbox.com"

STYLES_ENDPOINT = "/styles/v1/"
TILESET_ENDPOINT = "/v4/"

if __name__ == "__main__":

    # List Styles ####################################################

    # Format: /styles/v1/{username}/{style_id}

    query_string = {'access_token': ACCESS_TOKEN}
    query_string = urllib.parse.urlencode(query_string)

    request_url = f"{BASE_PATH}{STYLES_ENDPOINT}{USER_NAME}?{query_string}"

    try:
        response = requests.get(request_url)
    except requests.exceptions.RequestException as error:
        print(error)
    else:
        print(request_url)
        print("Status: ", response.status_code)
        print(response.json())

A quick whip-up, no doubt, but here we test pulling custom styles first, not too interesting. I used their Outdoor map style and made a few tweaks to make a custom style.

Second, we pull vector tiles in mvt format. The tileset referenced was from my Bridger Ridge Run race in 2019, the good ol’ pre-pandemic days.

Ridge Run

The tileset was made by first taking the GPX file and running it through a Mapbox provided JavaScript tool togeojson to obtain, well, a GeoJSON file. Installing via NPM, you can feed in a GPX file and direct stdout to the required file type.

Once in the GeoJSON format, we can use tippecanoe to convert to an MBTiles database and upload to Mapbox either via Studio or the Uploads API. To save time for now though, I uploaded through Studio.

Ridge Run Studio

Now that a tileset exists in Studio, we can pull the associated tiles using the Vector Tiles API:

# Retrieve custom tileset #################################################
    
    # Format: /v4/{tileset_id}/{zoom}/{x}/{y}.{format}

    tileset_id = "kevstewa.2jgs4lck"
    zoom = "16"

    tile = mercantile.tile(-110.947, 45.848, 16)
    x = tile.x
    y = tile.y

    tile_format = "mvt"

    query_string = {'access_token': ACCESS_TOKEN}
    query_string = urllib.parse.urlencode(query_string)

    request_url = f"{BASE_PATH}{TILESET_ENDPOINT}{tileset_id}/{zoom}/{x}/{y}.{tile_format}?{query_string}"

    try:
        response = requests.get(request_url)
    except requests.exceptions.RequestException as error:
        print(error)
    else:
        print(request_url)
        print("Status: ", response.status_code)
        print(response.content)

Here we use mercantile to get a specific tile’s X and Y based on the coordinates and zoom level provided. That is passed along with the zoom level and format to the endpoint and back we get our data.

Many of these steps can be skipped by uploading the original formats to Studio for automatic conversion to a tileset, but that wasn’t the purpose of this exercise. Next I’ll be looking at creating a heatmap of all my Strava activities, this time using the uploads API and other features provided by Mapbox. Their documentation is very good and can be found at docs.mapbox.com.