Creating Animations with Folium

This article was first published on The Pleasure of Finding Things Out: A blog by James Triveri , and kindly contributed to python-bloggers. (You can report issue about the content on this page here)
Want to share your content on python-bloggers? click here.

TimestampedGeoJson is a Folium plugin that facilitates the visualization of geospatial data that evolves over time using the GeoJSON format with timestamps. This is useful for tracking vehicle trajectories, satellites, pedestrian traffic, changes in weather patterns or any other geospatial phenomena with time dependent characteristics. TimestampedGeoJson leverages Leaflet.js’s leaflet-timestamped plugin and provides controls for play, pause, and speed adjustment. When added to a Folium map, it animates the appearance of features that evolve with time.

In what follows, I demonstrate how to create an animatio representing the trajectory of the International Space Station over the course of an hour using folium’s TimestampedGeoJson extension.

We start by obtaining the coordinates of the International Space station every minute for one hour using Open Notify, a simple api which returns the current location of the ISS relative to the surface of the earth. The get_iss_position function queries the api and returns the latitude, longitude and timestamp as a dictionary:

from datetime import datetime
import pickle
import requests
import time


def get_iss_position():
    """
    Get timestamped coordinates of International Space Station relative 
    to the surface of the Earth.   

    Returns
    -------
    dict
        Dictionary with keys "latitude", "longitude" and 
        "timestamp" indicating time and position of ISS. 
    """
    dpos = {}
    resp = requests.get("http://api.open-notify.org/iss-now.json").json()
    dpos["timestamp"] = resp["timestamp"]
    dpos["latitude"]  = float(resp["iss_position"]["latitude"])
    dpos["longitude"] = float(resp["iss_position"]["longitude"])
    return dpos

The coords list of dictionaries is created by querying the api once every minute for an hour:

coords = []

for ii in range(100):
    try:
        tstmp = datetime.now().strftime("%c")
        p = get_iss_position()
        coords.append(p)
       
    except Exception as ee:
        print(f"Error: {ee}")

    finally:
        time.sleep(60)

with open("coords.pkl", "wb") as fpkl:
    pickle.dump(coords, fpkl, protocol=-1)

TimestampedGeoJson features expect coordinates and timestamps to be structured as GeoJSON. To do so, we first create time_pos which converts unix timestamps to a string representation (“YYYY-mm-dd HH:MM:ss”). The start and end points of the ISS position by are grouped together by shifting time_pos by a single index (see end_pts and time_pos), resulting in a LineString geometry for each time step.

lines is a list of dictionaries with start and end coordinates (longitude first), start and end timestamps, and line segment color. In our animation, the color of the line segment will alternate between red and blue at each update.

The features list is what ultimately gets passed into TimestampedGeoJson. features is also a list of dictionaries, each element structured as:

{
    "type": "Feature",
    "geometry": {
        "type": "LineString",
        "coordinates": [[LON1, LAT1], [LON2, LAT2]]
    },
    "properties": {
        "times": [TIMESTAMP1, TIMESTAMP2],
        "style": {
            "color": COLOR
            "weight": 3
        }
    }
}
# Unpack coords list of dicts. Use lon-lat ordering. 
time_pos = [[
    datetime.fromtimestamp(d["timestamp"]).strftime("%Y-%m-%d %H:%M:%S"),
    [d["longitude"], d["latitude"]]
 ] for d in coords
]

end_pts = [tt for tt in time_pos[1:]]
time_pos = list(zip(time_pos[:-1], end_pts))

# Combine timestamps and coordinates in separate lists for each time step. 
lines = [{
    "coordinates": [tp[0][1], tp[1][1]], 
    "dates": [tp[0][0], tp[1][0]], 
    "color": "red" if idx % 2 == 0 else "blue"
    } for idx, tp in enumerate(time_pos)
]

# Create features list. Must be valid GeoJSON.
features = [
    {
        "type": "Feature",
        "geometry": {
            "type": "LineString",
            "coordinates": line["coordinates"]
        },
        "properties": {
            "times": line["dates"],
            "style": {
                "color": line["color"],
                "weight": 3
            }
        },
    }
    for line in lines
]

A brief mention of relevant TimestampedGeoJson’s arguments:

  • transition_time: The duration in milliseconds between transitions. I set this to 500, but can also be adjusted from the map interface.

  • period: Specifies the amount of time between observations in your data. By default, this is set to “P1D”, which represents one day. When I tried using the default, all of my points were displayed at once since I only had data that spanned one hour. Since the ISS location queries were separated by roughly 60 seconds, setting period to “PT60S” gave me what I was looking for.

  • date_options accepts a string representing the format of your timestamps.

Once the features list has been created, it is straightforward to display the animation. Initialize a folium map as usual, the call plugins.TimestampedGeoJson as demonstrated in the next cell:

import folium
import folium.plugins as plugins

# Center map.
dc = coords[35] 
mid_lat, mid_lon = dc["latitude"], dc["longitude"]

f = folium.Figure(width=900, height=650)
m = folium.Map(location=[mid_lat, mid_lon], zoom_start=2).add_to(f)

plugins.TimestampedGeoJson(
    {"type": "FeatureCollection", "features": features},
    transition_time=500,
    period='PT60S',
    date_options='YYYY-MM-DD HH:mm:ss',
).add_to(m)

m

The visual field appears too cluttered with the inclusion of markers. We can remove them by setting add_last_point = False:

import folium
import folium.plugins as plugins

# Center map.
dc = coords[35] 
mid_lat, mid_lon = dc["latitude"], dc["longitude"]

f = folium.Figure(width=900, height=650)
m = folium.Map(location=[mid_lat, mid_lon], zoom_start=2).add_to(f)

plugins.TimestampedGeoJson(
    {
        "type": "FeatureCollection",
        "features": features,
    },
    transition_time=500,
    add_last_point=False,
    period='PT60S',
    date_options='YYYY-MM-DD HH:mm:ss',
).add_to(m)

m
`); // facebook page document_write_mobile_aware(`

Sponsors

Archives