NASA EPIC and APOD APIs in Python#

by Sebastian Shirk

NASA EPIC API: https://epic.gsfc.nasa.gov/about/api

NASA APIs: https://api.nasa.gov/

NASA Images and Media Usage Guidelines: https://www.nasa.gov/nasa-brand-center/images-and-media/

The Earth Polychromatic Imaging Camera (EPIC) API provides the most recent images of Earth.

The Astronomy Picture of the Day (APOD) API provides the images of the universe taken by telescopes and other instruments.

These recipe examples were tested on August 27, 2024.

NOTE: This notebook automatically downloads the images returned by the APIs. This can download anywhere from 1 to 30 images per request depending on the number of images returned by the APIs.

Setup#

Import Libraries#

This tutorial will use the following libraries:

import requests
import os
from PIL import Image, ImageDraw, ImageFont

Create Images Folder#

All images will be saved in an images folder that will be created in the current working directory.

if not os.path.exists("images"):
        os.makedirs("images")

APOD API Key#

To access the APOD API, an API key is required. You can obtain one here.

from api_keys import nasa_key

1. Get the Latest Images of Earth (EPIC)#

This will get the latest images of Earth from the NASA EPIC API and download them as PNGs to your local directory.

Change the collection variable to see different collections of images.

# Offset is incremented to get the next image in the collection. It is necessary to get all the images taken on any day. 
offset = 0

# Collection options: natural, enhanced, cloud, aerosol
collection = "natural"
# Get the image from the API
def download_most_recent_images(offset):
    print("Getting images...")
    response = requests.get("https://epic.gsfc.nasa.gov/api/" + collection).json()
    size = len(response)
    images = []

    while offset < size:
        print(f"Image {offset+1} out of {size}")
        if offset >= len(response):
            offset = len(response) - 1
        date = response[offset]["date"]
        image = response[offset]["image"]

        # Parse to get the year, month, and day
        date_parts = date.split(" ")[0].split("-")
        time_parts = date.split(" ")[1].split(":")
        year = date_parts[0]
        month = date_parts[1]
        day = date_parts[2]
        hour = time_parts[0]
        minutes = time_parts[1]
        seconds = time_parts[2]
        date_text = f"{year}-{month}-{day}"
        time_text = f"{hour}:{minutes}:{seconds}"

        # Download the image
        image_path = f"images/Earth_Image{offset+1}_recent.png"
        with open(image_path, "wb") as f:
            file = requests.get(f"https://epic.gsfc.nasa.gov/archive/{collection}/{year}/{month}/{day}/png/{image}.png")
            f.write(file.content)
        
        # Open the image to add the date text
        img = Image.open(image_path)
        draw = ImageDraw.Draw(img)
        font = ImageFont.load_default(100)  # You can load a different font if needed
        date_position = (20, 10)  # Position of the text on the image
        time_position = (20, 100)
        draw.text(date_position, date_text, fill="white", font=font)
        draw.text(time_position, time_text, fill="white", font=font)
        img.save(image_path)  # Save the image with the date text

        # Append the image path to the list
        images.append(image_path)
        offset += 1
    print("Done.")
    return images

image_list = download_most_recent_images(offset)
Getting images...
Image 1 out of 15
Image 2 out of 15
Image 3 out of 15
Image 4 out of 15
Image 5 out of 15
Image 6 out of 15
Image 7 out of 15
Image 8 out of 15
Image 9 out of 15
Image 10 out of 15
Image 11 out of 15
Image 12 out of 15
Image 13 out of 15
Image 14 out of 15
Image 15 out of 15
Done.

Example Image

../../_images/Earth_Image1_recent.png

2. Get Earth Images from a Specific Date (EPIC)#

Use the get_valid_dates() function defined below to gather a list of all valid dates where images are available through the EPIC API.

Note that most dates from the launch of the API on June 13, 2015 are valid. However, there are several missing dates, as you can see below.

def get_valid_dates():
    dates = []
    response = requests.get("https://epic.gsfc.nasa.gov/api/" + collection + "/all").json()
    for entry in response:
        date = entry["date"]
        date_parts = date.split(" ")[0].split("-")
        year = date_parts[0]
        month = date_parts[1]
        day = date_parts[2]
        date_text = f"{year}-{month}-{day}"
        dates.append(date_text)
    return dates

# Create list of valid dates
dates = get_valid_dates()

# Print the last 10 elements in the list
dates[:-10:-1]
['2015-06-13',
 '2015-06-16',
 '2015-06-17',
 '2015-06-18',
 '2015-06-20',
 '2015-06-21',
 '2015-06-22',
 '2015-06-27',
 '2015-06-30']

Notice the gaps in the above results. Before we retrieve the images for a given date, let’s ensure that the date is available through the API:

# Note that this date is available
if '2016-05-15' in dates:
    print('2016-05-15 is valid')

# Note that this date is not available
if '2022-06-15' not in dates:
    print('2022-06-15 is not valid')
2016-05-15 is valid
2022-06-15 is not valid
def get_images_by_date(offset, year, month, day):
    print("Getting images...")
    response = requests.get("https://epic.gsfc.nasa.gov/api/" + collection + "/date/" + year + "-" + month + "-" + day).json()
    if not response:
        print("No images found for this date.")
        return []
    size = len(response)
    images = []

    while offset < size:
        print(f"Image {offset+1} out of {size}")
        if offset >= len(response):
            offset = len(response) - 1
        date = response[offset]["date"]
        image = response[offset]["image"]
        time_parts = date.split(" ")[1].split(":")
        hour = time_parts[0]
        minutes = time_parts[1]
        seconds = time_parts[2]

        # Download the image
        image_path = f"images/Earth_Image_By_Date{offset+1}.png"
        with open(image_path, "wb") as f:
            file = requests.get(f"https://epic.gsfc.nasa.gov/archive/{collection}/{year}/{month}/{day}/png/{image}.png")
            f.write(file.content)
        
        # Open the image to add the date text
        img = Image.open(image_path)
        draw = ImageDraw.Draw(img)
        font = ImageFont.load_default(100)  # You can load a different font if needed
        date_position = (20, 10)  # Position of the text on the image
        time_position = (20, 100)
        date_text = f"{year}-{month}-{day}"
        time_text = f"{hour}:{minutes}:{seconds}"
        draw.text(date_position, date_text, fill="white", font=font)
        draw.text(time_position, time_text, fill="white", font=font)
        img.save(image_path)  # Save the image with the date text

        # Append the image path to the list
        images.append(image_path)
        offset += 1
    print("Done.")
    return images

image_paths = get_images_by_date(offset, "2016", "05", "15")
Getting images...
Image 1 out of 7
Image 2 out of 7
Image 3 out of 7
Image 4 out of 7
Image 5 out of 7
Image 6 out of 7
Image 7 out of 7
Done.

Example Image

../../_images/Earth_Image_By_Date1.png

Stitch the Images Together#

This will stitch the images together to create one image containing all the images for easier viewing.

# Stitch the images together
def stitch_images(image_paths):
    print("Image Stitching...")
    images = [Image.open(image) for image in image_paths]
    
    # Split the images into two rows
    halfway = len(images) // 2
    first_row_images = images[:halfway]
    second_row_images = images[halfway:]
    
    # Get dimensions of the first image
    widths, heights = zip(*(i.size for i in images))
    
    total_width_first_row = sum(width.size[0] for width in first_row_images)
    total_width_second_row = sum(width.size[0] for width in second_row_images)
    max_width = max(total_width_first_row, total_width_second_row)
    max_height = max(heights)
    
    # Create a new blank image with the max width and twice the max height
    stitched_image = Image.new('RGB', (max_width, max_height * 2))
    
    # Paste each image into the blank image
    x_offset = 0
    for im in first_row_images:
        stitched_image.paste(im, (x_offset, 0))
        x_offset += im.size[0]
    
    x_offset = 0
    for im in second_row_images:
        stitched_image.paste(im, (x_offset, max_height))
        x_offset += im.size[0]
    stitched_image.save("images/Earth_Image_Stitched.png")
    print("Done.")

stitch_images(image_paths)
Image Stitching...
Done.
../../_images/Earth_Image_Stitched.png

3. Get the Astronomy Picture of the Day (APOD)#

This will get the Astronomy Picture of the Day from the NASA APOD API and download it as a PNG to your local directory.

You can get a random APOD image from their collection instead by uncommenting the two commented lines.

Note that the APOD API can only be called 30 times per IP address per hour and only 50 times per day.

def get_apod(date=None):
    print("Getting Astronomy Picture of the Day...")

    # Retrieves picture for the date specified, or the current date if unspecified
    if date:
        response = requests.get(f"https://api.nasa.gov/planetary/apod?date={date}&api_key={nasa_key}").json()
    else:
        response = requests.get(f"https://api.nasa.gov/planetary/apod?api_key={nasa_key}").json()
    image_url = response["url"]
    media_type = response["media_type"]
    
    # Uncomment the block below to get a random image from the collection
    # response = requests.get(f"https://api.nasa.gov/planetary/apod?api_key={nasa_key}&count=1").json()
    # image_url = response[0]["url"]
    # media_type = response[0]["media_type"]
    # print(f"Getting APOD for {response[0]['date']}")
    
    # Download the image
    if media_type == "image":
        image_path = f"images/APOD.png"
        with open(image_path, "wb") as f:
            file = requests.get(image_url)
            f.write(file.content)
        print("Done.")
    else:
        print("Media type is not an image.")
        print("You can follow this link to view the media: " + image_url)

get_apod('2024-08-27')
Getting Astronomy Picture of the Day...
Done.

Example Image

../../_images/APOD.png