Displaying Live ISS Location with Python

Plotly is one of many plotting libraries in Python, offering attractive plots of many kinds. For the purposes of this, we will be using maps to plot the live location of the international space station and update the map every 10 seconds.

First, we want to import the required libraries and if there isn’t a database already set up, set it up.

The database is optional but a clean way to plot the trajectory.

import sys
import time
import pandas as pd
import sqlite3
import requests
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# Set up the database connection
conn = sqlite3.connect('iss_location.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS iss_location (timestamp INTEGER, latitude REAL, longitude REAL)''')
conn.commit()
conn.close()

Then a simple function to collect the data from the Open Notify API

# Function to fetch ISS location data
def get_iss_location():
    url = "http://api.open-notify.org/iss-now.json"
    req = requests.get(url)
    obj = req.json()
    return obj

So that the database doesn’t consume too much memory on the server, there’s also a function to delete the oldest data

def purge_old_data():
    # delete rows where more than 5000 rows exist
    conn = sqlite3.connect('iss_location.db')
    c = conn.cursor()
    c.execute("DELETE FROM iss_location WHERE timestamp NOT IN (SELECT timestamp FROM iss_location ORDER BY timestamp DESC LIMIT 5000)")
    conn.commit()
    conn.close()

This is the complete function to update the plot but we’ll break it down below so that the code can be adapted to your purposes.

# Function to update ISS location plot
def update_iss_location(i):
    # Get the latest ISS location
    location_data = get_iss_location()
    purge_old_data()
    latitude = float(location_data['iss_position']['latitude'])
    longitude = float(location_data['iss_position']['longitude'])
    timestamp = location_data['timestamp']
    print(f"Latitude: {latitude}, Longitude: {longitude}, Timestamp: {timestamp}")

    # Save the location data to the database
    conn = sqlite3.connect('iss_location.db')
    c = conn.cursor()
    c.execute("INSERT INTO iss_location (timestamp, latitude, longitude) VALUES (?, ?, ?)", (timestamp, latitude, longitude))
    conn.commit()
    conn.close()

    # Retrieve all ISS location data from the database
    conn = sqlite3.connect('iss_location.db')
    df = pd.read_sql_query("SELECT * FROM iss_location ORDER BY timestamp DESC LIMIT 600", conn)
    conn.close()

    # convert timestamp to a human-readable format
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s')

    # Create a Plotly figure
    fig = make_subplots(rows=1, cols=1,
                        specs=[[{"type": "scattergeo"}]])

    # Add scatter plot for previous locations
    fig.add_trace(go.Scattergeo(
        lon=df['longitude'][1:],
        lat=df['latitude'][1:],
        mode='markers',
        marker=dict(
            size=3,
            color='plum',
            opacity=0.7,
            symbol='circle'
        ),
        name='Previous Positions'
    ))

    # Add scatter plot for the latest location
    fig.add_trace(go.Scattergeo(
        lon=[df['longitude'].iloc[0]],
        lat=[df['latitude'].iloc[0]],
        mode='markers',
        marker=dict(
            size=10,
            color='darkred',
            opacity=0.9,
            symbol='circle'
        ),
        name='Latest Position'
    ))

    # Update layout
    fig.update_geos(
        resolution=110,
        showframe=False,
        showcoastlines=True,
        coastlinecolor="black",
        showocean=True,
        oceancolor='lightsteelblue',
        showlakes=True,
        lakecolor='lightblue',
        showland=True,
        landcolor='antiquewhite',
        showcountries=True,
        countrycolor='gray'
    )

    fig.update_layout(
        title=f"ISS Location - Last Updated: {df['timestamp'].iloc[0]}"
    )

    # Add annotation for latitude and longitude
    fig.add_annotation(
        text=f"Latitude: {latitude}, Longitude: {longitude}",
        xref="paper", yref="paper",
        x=0.5, y=-0.1,
        showarrow=False,
        font=dict(
            size=12,
            color="black"
        )
    )

    fig.show()

You need to collect the data using the function that was built earlier.

def update_iss_location(i):
    # Get the latest ISS location
    location_data = get_iss_location()
    purge_old_data()
    latitude = float(location_data['iss_position']['latitude'])
    longitude = float(location_data['iss_position']['longitude'])
    timestamp = location_data['timestamp']
    print(f"Latitude: {latitude}, Longitude: {longitude}, Timestamp: {timestamp}")

writing the data to a database is optional, but gives a nice option to hold previous positions so that if the server is restarted it can still be plotted.

    # Save the location data to the database
    conn = sqlite3.connect('iss_location.db')
    c = conn.cursor()
    c.execute("INSERT INTO iss_location (timestamp, latitude, longitude) VALUES (?, ?, ?)", (timestamp, latitude, longitude))
    conn.commit()
    conn.close()

we want to retrieve the past 600 rows which should be approximately the past 100 minutes or a bit over a full rotation of the ISS.

    # Retrieve all ISS location data from the database
    conn = sqlite3.connect('iss_location.db')
    df = pd.read_sql_query("SELECT * FROM iss_location ORDER BY timestamp DESC LIMIT 600", conn)
    conn.close()

Convert the DateTime to a more human-readable format so that it can be used in the title later.

    # convert timestamp to a human-readable format
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s')

Now comes the time to initialise the plot

    # Create a Plotly figure
    fig = make_subplots(rows=1, cols=1,
                        specs=[[{"type": "scattergeo"}]])

Each plot is done separately and layered, essentially we’re going to be treating this as a scatter plot with a background of a map.

We’ll plot the previous positions by setting longitude and latitude with everything but the 1st number in the column.

    # Add scatter plot for previous locations
    fig.add_trace(go.Scattergeo(
        lon=df['longitude'][1:],
        lat=df['latitude'][1:],
        mode='markers',
        marker=dict(
            size=3,
            color='plum',
            opacity=0.7,
            symbol='circle'
        ),
        name='Previous Positions'
    ))

In order to highlight the latest position we’ll plot that a little larger and in a dark red color.

    # Add scatter plot for the latest location
    fig.add_trace(go.Scattergeo(
        lon=[df['longitude'].iloc[0]],
        lat=[df['latitude'].iloc[0]],
        mode='markers',
        marker=dict(
            size=10,
            color='darkred',
            opacity=0.9,
            symbol='circle'
        ),
        name='Latest Position'
    ))

This will add the map, this is also where we can adjust the colour and design of the map.

    # Update layout
    fig.update_geos(
        resolution=110,
        showframe=False,
        showcoastlines=True,
        coastlinecolor="black",
        showocean=True,
        oceancolor='lightsteelblue',
        showlakes=True,
        lakecolor='lightblue',
        showland=True,
        landcolor='antiquewhite',
        showcountries=True,
        countrycolor='gray'
    )

Place a title and subtitle to show the time of last updating and the last known longitude and latitude.

    fig.update_layout(
        title=f"ISS Location - Last Updated: {df['timestamp'].iloc[0]}"
    )

    # Add annotation for latitude and longitude
    fig.add_annotation(
        text=f"Latitude: {latitude}, Longitude: {longitude}",
        xref="paper", yref="paper",
        x=0.5, y=-0.1,
        showarrow=False,
        font=dict(
            size=12,
            color="black"
        )
    )

Finally, we show the figure

    fig.show()

This is then put in a loop so that it runs until interrupted.

# Update ISS location every 10 seconds
while True:
    update_iss_location(0)
    time.sleep(10)

Here are the final results.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *