Advertisement
Python253

nasa_epic_metadata

Jun 12th, 2024 (edited)
494
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.30 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Filename: nasa_epic_metadata.py
  4. # Version: 1.0.0
  5. # Author: Jeoi Reqi
  6.  
  7. """
  8. Description:
  9.    - This script fetches image metadata from NASA's EPIC (Earth Polychromatic Imaging Camera) API.
  10.    - EPIC is a camera onboard the NOAA's DSCOVR (Deep Space Climate Observatory) spacecraft,
  11.      positioned at the Lagrange point 1 (L1), approximately one million miles from Earth.
  12.    - The camera captures images of the entire sunlit side of Earth, providing a unique perspective on our planet.
  13.  
  14.    The EPIC images are available in four different types:
  15.        - Natural: True-color images that show Earth as it appears to the human eye.
  16.        - Enhanced: Images that are adjusted to enhance specific features of the atmosphere and surface.
  17.        - Aerosol: Images that highlight the distribution and concentration of aerosols in the atmosphere.
  18.        - Cloud: Images that provide detailed views of cloud formations and their dynamics.
  19.  
  20.    Users can specify a date range to fetch metadata for, and the metadata will be saved in the specified folder.
  21.    The metadata includes information such as:
  22.        - Identifier: A unique identifier for each image.
  23.        - Image: The name of the image file.
  24.        - Date: The date and time when the image was captured.
  25.        - Centroid Coordinates: The latitude and longitude coordinates of the image's center.
  26.        - DSCOVR J2000 Position: The position of the DSCOVR spacecraft in J2000 coordinates (X, Y, Z).
  27.        - Lunar J2000 Position: The position of the Moon in J2000 coordinates (X, Y, Z).
  28.        - Sun J2000 Position: The position of the Sun in J2000 coordinates (X, Y, Z).
  29.  
  30. Requirements:
  31.    - Python 3.x
  32.    - Required modules:
  33.        - requests
  34.        - datetime
  35.        - time
  36.        - dateutil
  37.        - colorama
  38.  
  39. Functions:
  40.    - make_api_request:
  41.        Makes an API request to the specified URL.
  42.    - check_rate_limit:
  43.        Checks and manages the API call rate limit.
  44.    - write_metadata_to_file:
  45.        Writes the fetched metadata to a file.
  46.  
  47. Classes:
  48.    - NASAEPICManager:
  49.        A class to manage NASA EPIC API requests and rate limits.
  50.  
  51. Usage:
  52.    1. Ensure there's a file named 'nasa_api.txt' in the current working directory containing your NASA API Key(s).
  53.       The API key file contents must be separated by a new line if you need to use multiple keys.
  54.    2. Run the script.
  55.    3. Follow the on-screen prompts to select an option and specify the date range.
  56.    4. The metadata will be fetched and saved in a text file.
  57.  
  58. Additional Notes:
  59.    - You need to obtain a NASA API key to use this script.
  60.        - You can get it by registering on the [NASA API portal](https://api.nasa.gov/index.html?apply-for-an-api-key).
  61.    - After obtaining your API key, create a text file named 'nasa_api.txt' in the same directory as this script.
  62.    - Save your API key(s) in 'nasa_api.txt', with each key on a new line if you have multiple keys. Example:
  63.        - EXAMPLE:
  64.            - YOUR_FIRST_API_KEY
  65.            - YOUR_SECOND_API_KEY
  66.    - To download the image files, you can go to URL: (https://pastebin.com/2KQKTxe2)
  67.        - Save the script as 'nasa_epic_images.py' in your current working directory.
  68.        - Follow the detailed prompting from the program
  69. """
  70.  
  71. # Get Essential Imports
  72. import time
  73. import requests
  74. from datetime import datetime, timedelta
  75. from dateutil import parser as date_parser
  76. from colorama import init, Fore, Style
  77.  
  78. # Initialize colorama
  79. init(autoreset=True)
  80.  
  81. class NASAEPICManager:
  82.     """
  83.    A class to manage NASA EPIC API requests and rate limits.
  84.  
  85.    Attributes:
  86.        api_keys (list): List of API keys obtained from a file.
  87.        current_key_index (int): Index of the current API key being used.
  88.        hourly_limit (int): Maximum number of API calls allowed per hour.
  89.        calls_count (int): Number of API calls made in the current hour.
  90.        last_reset_time (float): Timestamp of the last reset time for the hourly limit.
  91.    """
  92.  
  93.     def __init__(self, api_keys_file):
  94.         """
  95.        Initializes the NASAEPICManager with API keys and rate limit parameters.
  96.  
  97.        Args:
  98.            api_keys_file (str): Path to the file containing API keys.
  99.        """
  100.         with open(api_keys_file, 'r', encoding='utf-8') as file:
  101.             self.api_keys = file.read().splitlines()
  102.         self.current_key_index = 0
  103.         self.hourly_limit = 999
  104.         self.calls_count = 0
  105.         self.last_reset_time = time.time()
  106.  
  107.     def make_api_request(self, url, retries=3):
  108.         """
  109.        Makes an API request to the specified URL.
  110.  
  111.        Args:
  112.            url (str): The URL to make the API request to.
  113.            retries (int): Number of retries in case of failure. Default is 3.
  114.  
  115.        Returns:
  116.            dict: JSON response from the API if successful, otherwise None.
  117.        """
  118.         self.check_rate_limit()
  119.         current_api_key = self.api_keys[self.current_key_index]
  120.         params = {'api_key': current_api_key}
  121.  
  122.         for _ in range(retries):
  123.             try:
  124.                 response = requests.get(url, params=params)
  125.                 response.raise_for_status()
  126.                 self.calls_count += 1
  127.                 print(f"\nAPI Key: {current_api_key}, Remaining Requests: {response.headers.get('X-RateLimit-Remaining', 0)}\n")
  128.                 return response.json()
  129.             except requests.exceptions.RequestException:
  130.                 print(f"\nError: No image metadata found for {url}! Retrying...\n")
  131.                 time.sleep(3)  # Wait for 3 seconds before retrying
  132.  
  133.         print(f"Failed to fetch metadata after retries for {url}.")
  134.         return None
  135.  
  136.     def check_rate_limit(self):
  137.         """
  138.        Checks and manages the API call rate limit.
  139.        Resets counters if more than one hour has passed or switches to the next API key if the hourly limit is reached.
  140.        """
  141.         current_time = time.time()
  142.         time_since_reset = current_time - self.last_reset_time
  143.  
  144.         if time_since_reset >= 3600:
  145.             # Reset counters if more than one hour has passed
  146.             self.calls_count = 0
  147.             self.last_reset_time = current_time
  148.  
  149.         if self.calls_count >= self.hourly_limit:
  150.             # Switch to the next API key if the hourly limit is reached
  151.             self.current_key_index = (self.current_key_index + 1) % len(self.api_keys)
  152.             self.calls_count = 0
  153.  
  154. def write_metadata_to_file(metadata_list, filename_str):
  155.     """
  156.    Writes metadata to a file.
  157.  
  158.    Args:
  159.        metadata_list (list): A list of dictionaries containing metadata information.
  160.        filename_str (str): The name of the file to write the metadata to.
  161.  
  162.    Notes:
  163.        - Each entry in the metadata list should be a dictionary containing metadata information.
  164.        - The function writes metadata in a formatted way to the specified file.
  165.        - Metadata includes identifier, image name, date, centroid coordinates, and positions of DSCOVr, Lunar, and Sun.
  166.  
  167.    """
  168.     with open(filename_str, 'w', encoding='UTF-8') as file:
  169.         for entry in metadata_list:
  170.             file.write(f"Identifier: #{entry['identifier']}, \tImage: {entry['image']}, \tDate: {entry['date']}\n\n")
  171.             file.write(f"Centroid Coordinates: \tLatitude {entry['coords']['centroid_coordinates']['lat']}, \tLongitude {entry['coords']['centroid_coordinates']['lon']}\n\n")
  172.             file.write("\t\t\t\t[X]\t\t\t[Y]\t\t\t[Z]\n\n")
  173.             file.write(f"DSCOVr J2000 Position: \tx {entry['coords']['dscovr_j2000_position']['x']}, \ty {entry['coords']['dscovr_j2000_position']['y']}, \tz {entry['coords']['dscovr_j2000_position']['z']}\n")
  174.             file.write(f"Lunar J2000 Position: \tx {entry['coords']['lunar_j2000_position']['x']}, \ty {entry['coords']['lunar_j2000_position']['y']}, \tz {entry['coords']['lunar_j2000_position']['z']}\n")
  175.             file.write(f"Sun J2000 Position: \tx {entry['coords']['sun_j2000_position']['x']}, \ty {entry['coords']['sun_j2000_position']['y']}, \tz {entry['coords']['sun_j2000_position']['z']}\n\n")
  176.             file.write("-" * 50 + "\n\n")
  177.  
  178. # Define the header as a list of strings for easier manipulation
  179. header_lines = [
  180.     "                        ███    ██  █████  ███████  █████                            ",
  181.     "                        ████   ██ ██   ██ ██      ██   ██                           ",
  182.     "                        ██ ██  ██ ███████ ███████ ███████                           ",
  183.     "                        ██  ██ ██ ██   ██      ██ ██   ██                           ",
  184.     "                        ██   ████ ██   ██ ███████ ██   ██                           ",
  185.     "███████  █████         ██████  ███████        █████  ██████         ██████  ███████ ",
  186.     "     ██ ██   ██       ██       ██            ██   ██      ██       ██       ██      ",
  187.     "    ██   █████  █████ ███████  ███████ █████  █████   █████  █████ ███████  ███████ ",
  188.     "   ██   ██   ██       ██    ██      ██       ██   ██      ██       ██    ██      ██ ",
  189.     "   ██    █████         ██████  ███████        █████  ██████         ██████  ███████ ",
  190.     "                            ███████ ██████  ██  ██████                              ",
  191.     "                            ██      ██   ██ ██ ██                                   ",
  192.     "                            █████   ██████  ██ ██                                   ",
  193.     "                            ██      ██      ██ ██                                   ",
  194.     "                            ███████ ██      ██  ██████                   (METADATA) "
  195. ]
  196.  
  197. # Apply colors to the specified lines
  198. colored_header = []
  199. for i, line in enumerate(header_lines):
  200.     if 0 <= i < 5:  # NASA
  201.         colored_header.append(Fore.RED + line + Style.RESET_ALL)
  202.     elif 5 <= i < 10:  # 78-65-83-65 (ASCII CODE)
  203.         colored_header.append(Fore.WHITE + line + Style.RESET_ALL)
  204.     elif 10 <= i < 15:  # EPIC
  205.         colored_header.append(Fore.BLUE + line + Style.RESET_ALL)
  206.     else:
  207.         colored_header.append(line)
  208.  
  209. # Print the colored header
  210. for line in colored_header:
  211.     print(line)
  212.    
  213. if __name__ == "__main__":
  214.     nasa_manager = NASAEPICManager('nasa_api.txt')
  215.  
  216.     while True:  # Loop for the menu
  217.         # Display options for the user
  218.         print("_" * 83)
  219.         print("\nMetadata Options:\n")
  220.         print("1. Natural")
  221.         print("2. Enhanced")
  222.         print("3. Cloud")
  223.         print("4. Aerosol")
  224.         print("5. Exit")  # Option to exit
  225.  
  226.         # Get user's choice
  227.         option = input("\nSelect an option (1-5): ")
  228.  
  229.         if option == '5':  # Exit option
  230.             print("\nExiting Program...   GoodBye!\n")
  231.             break  # Exit the loop and end the program
  232.  
  233.         # Map the user's choice to the corresponding URL component
  234.         option_mapping = {
  235.             '1': 'natural',
  236.             '2': 'enhanced',
  237.             '3': 'cloud',
  238.             '4': 'aerosol'
  239.         }
  240.  
  241.         # Check if the user's choice is valid
  242.         if option in option_mapping:
  243.             selected_option = option_mapping[option]
  244.  
  245.             # Get current date to compare with user input
  246.             current_date = datetime.now().date()
  247.  
  248.             # Check if start date is before the epoch date (June 13, 2015)
  249.             epoch_dates = {
  250.                 'natural': '2015-06-13',
  251.                 'enhanced': '2015-08-07',
  252.                 'aerosol': '2020-09-04',
  253.                 'cloud': '2023-01-03'
  254.             }
  255.  
  256.             # Get start date from the user
  257.             print("_" * 83)
  258.             print("\n\t\t\t:: Start Date Format: (YYYY-MM-DD) ::\n")
  259.             start_date_str = input(f"Enter a 'Start Date' after {epoch_dates[selected_option]}:  ")
  260.             print("_" * 83)
  261.             start_date = date_parser.parse(start_date_str).date()
  262.  
  263.             # Check if start date is before the epoch date (June 13, 2015)
  264.             epoch_date = datetime(2015, 6, 13).date()
  265.             if start_date < epoch_date:
  266.                 print(Fore.RED + "\nError: Start date cannot be before June 13, 2015!\n" + Style.RESET_ALL)
  267.                 continue  # Restart the loop for a new input
  268.  
  269.             # Check if start date is in the future
  270.             if start_date > current_date:
  271.                 print(Fore.RED + "\nError: Start date cannot be in the future!\n" + Style.RESET_ALL)
  272.                 continue  # Restart the loop for a new input
  273.  
  274.             # Check if the user wants to log a single day or a range of dates
  275.             end_date_str = input("\nEnter the end date (YYYY-MM-DD) or press [ENTER] to log a single day: ")
  276.             if end_date_str:
  277.                 end_date = date_parser.parse(end_date_str).date()
  278.  
  279.                 # Check if end date is in the future
  280.                 if end_date > current_date:
  281.                     print(Fore.RED + "\nError: End date cannot be in the future!\n" + Style.RESET_ALL)
  282.                     continue  # Restart the loop for a new input
  283.  
  284.                 # Check if end date is before the epoch date (June 13, 2015)
  285.                 if end_date < epoch_date:
  286.                     print(Fore.RED + "\nError: End date cannot be before June 13, 2015!\n" + Style.RESET_ALL)
  287.                     continue  # Restart the loop for a new input
  288.             else:
  289.                 end_date = start_date
  290.  
  291.             # Specify the filename for the formatted metadata
  292.             filename = f'{start_date_str}_{end_date_str}_{selected_option}.txt'
  293.  
  294.             # Iterate through the date range and log data for each day
  295.             current_date = start_date
  296.             all_metadata = []  # List to store metadata for all dates
  297.  
  298.             while current_date <= end_date:
  299.                 date_str = current_date.strftime('%Y-%m-%d')
  300.                 metadata_url = f"https://api.nasa.gov/EPIC/api/{selected_option}/date/{date_str}?api_key=TEMPL4ujtA3LyN0qbFh4imFv7gxDfUG9WoU0eWOu"
  301.                 metadata = nasa_manager.make_api_request(metadata_url)
  302.  
  303.                 if metadata:
  304.                     print(f"Metadata for {date_str}:")
  305.                     print(metadata)
  306.                     all_metadata.extend(metadata)  # Append metadata to the list
  307.  
  308.                 current_date += timedelta(days=1)
  309.  
  310.             # Create the file if it doesn't exist
  311.             if all_metadata:
  312.                 write_metadata_to_file(all_metadata, filename)
  313.                 print(f"\nFormatted metadata has been written to {filename}\n")
  314.             else:
  315.                 print("\nNo valid entries found in the specified date range.\n")
  316.         else:
  317.             print("\nInvalid option! Please select a valid option (1-5).\n")  
  318.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement