Advertisement
Python253

nasa_epic_images

Jun 12th, 2024
511
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 19.08 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Filename: nasa_epic_images.py
  4. # Version: 1.0.0
  5. # Author: Jeoi Reqi
  6.  
  7. """
  8. Description:
  9.    This script downloads images from NASA's EPIC (Earth Polychromatic Imaging Camera) satellite.
  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 download images for, and the images will be saved in the specified folder.
  21.  
  22. Requirements:
  23.    - Python 3.x
  24.    - Required modules:
  25.        - requests
  26.        - datetime
  27.        - concurrent.futures
  28.        - time
  29.        - colorama
  30. Functions:
  31.        - Main:
  32.            - download_images_for_date:
  33.                Checks if images are available for a specific date and option, and downloads them if available.
  34.            - download_image:
  35.                Downloads a single image from the specified source URL and saves it to the destination folder.
  36.            - get_valid_start_date:
  37.                Prompts the user to enter a valid start date after the epoch date for the selected option.
  38.            - get_valid_end_date:
  39.                Prompts the user to enter a valid end date before today or 'today'.
  40.  
  41.        - Header:
  42.            - generate_nasa_epic_header:
  43.                Generates the NASA EPIC header with color formatting.
  44.            - print_colored_header:
  45.                Prints the colored header with the NASA and EPIC logos.
  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 have multiple keys.
  54.    2. Run the script.
  55.    3. Follow the on-screen prompts to select an option, specify the date range, and enter the download location.
  56.    4. The images will be downloaded and saved in folders according to the selected option and date.
  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.    - Colorama is initialized to automatically reset colors after each use.
  67.    - To download the metadata, you can go to URL: (https://pastebin.com/zAB34Acu)
  68.        - Save the script as 'nasa_epic_metadata.py' in your current working directory.
  69.        - Follow the detailed prompting from the program
  70. """
  71.  
  72. # Get Essential Imports
  73. import os
  74. import requests
  75. from datetime import datetime, timedelta
  76. from concurrent.futures import ThreadPoolExecutor
  77. import time
  78. from colorama import init, Fore, Style
  79.  
  80. # Initialize colorama
  81. init(autoreset=True)
  82.  
  83. class NASAEPICManager:
  84.     """
  85.    A class to manage NASA EPIC API requests and rate limits.
  86.  
  87.    Attributes:
  88.        api_keys (list): List of API keys obtained from a file.
  89.        current_key_index (int): Index of the current API key being used.
  90.        hourly_limit (int): Maximum number of API calls allowed per hour.
  91.        calls_count (int): Number of API calls made in the current hour.
  92.        last_reset_time (float): Timestamp of the last reset time for the hourly limit.
  93.    """
  94.    
  95.     def __init__(self, api_keys_file):
  96.         """
  97.        Initializes the NASAEPICManager with API keys and rate limit parameters.
  98.  
  99.        Args:
  100.            api_keys_file (str): Path to the file containing API keys.
  101.        """
  102.         with open(api_keys_file, 'r', encoding='utf-8') as file:
  103.             self.api_keys = file.read().splitlines()
  104.         self.current_key_index = 0
  105.         self.hourly_limit = 1000  # Default value, will be updated based on the headers
  106.         self.calls_count = 0
  107.         self.last_reset_time = time.time()
  108.  
  109.     def make_api_request(self, url):
  110.         """
  111.        Makes an API request to the specified URL.
  112.  
  113.        Args:
  114.            url (str): The URL to make the API request to.
  115.  
  116.        Returns:
  117.            dict or None: JSON response from the API if successful, otherwise None.
  118.        """
  119.         self.check_rate_limit()
  120.         current_api_key = self.api_keys[self.current_key_index]
  121.         params = {'api_key': current_api_key}
  122.         response = requests.get(url, params=params)
  123.  
  124.         # Update rate limit counters based on response headers
  125.         self.calls_count += 1
  126.  
  127.         # Handle non-JSON response (e.g., HTML error pages)
  128.         try:
  129.             response_json = response.json()
  130.         except requests.exceptions.JSONDecodeError:
  131.             response_json = None
  132.  
  133.         remaining_requests = int(response.headers.get('X-RateLimit-Remaining', 0))
  134.         if self.calls_count == 1:
  135.             # Set the initial hourly limit based on the headers
  136.             self.hourly_limit = remaining_requests
  137.  
  138.         self.last_reset_time = int(response.headers.get('X-RateLimit-Reset', self.last_reset_time))
  139.  
  140.         print(f"API Key: {current_api_key}, \n\nRemaining Requests: {remaining_requests}")
  141.  
  142.         return response_json
  143.  
  144.     def check_rate_limit(self):
  145.         """
  146.        Checks and manages the API call rate limit.
  147.        Resets counters if more than one hour has passed or switches to the next API key if the hourly limit is reached.
  148.        """
  149.         current_time = time.time()
  150.         time_since_reset = current_time - self.last_reset_time
  151.  
  152.         if time_since_reset >= 3600:
  153.             # Reset counters if more than one hour has passed
  154.             self.calls_count = 0
  155.             self.last_reset_time = current_time
  156.  
  157.         if self.calls_count >= self.hourly_limit:
  158.             # Switch to the next API key if the hourly limit is reached
  159.             self.current_key_index = (self.current_key_index + 1) % len(self.api_keys)
  160.             self.calls_count = 0
  161.  
  162. # Set epoch dates for the 4 options
  163. epoch_dates = {
  164.     'natural': '2015-06-13',
  165.     'enhanced': '2015-08-07',
  166.     'aerosol': '2020-09-04',
  167.     'cloud': '2023-01-03'
  168. }
  169.  
  170. def get_valid_start_date(selected_option):
  171.     """
  172.    Prompt the user to enter a valid start date after the epoch date for the selected option.
  173.  
  174.    Args:
  175.        selected_option (str): The selected option ('natural', 'enhanced', 'aerosol', or 'cloud').
  176.  
  177.    Returns:
  178.        str: A valid start date in the format 'YYYY-MM-DD'.
  179.    """
  180.     while True:
  181.         start_date_input = input(f"\nEnter any start date after {epoch_dates[selected_option]} (YYYY-MM-DD) or type 'epoch': ")
  182.  
  183.         if start_date_input.lower() == 'epoch':
  184.             return epoch_dates[selected_option]
  185.  
  186.         try:
  187.             start_date = datetime.strptime(start_date_input, "%Y-%m-%d")
  188.             if start_date < datetime.strptime(epoch_dates[selected_option], "%Y-%m-%d"):
  189.                 print(f"\nStart date must be after {epoch_dates[selected_option]}! Please try again.\n")
  190.             elif start_date > datetime.utcnow():
  191.                 print("\nStart date cannot be in the future! Please try again.")
  192.             else:
  193.                 return start_date.strftime("%Y-%m-%d")
  194.         except ValueError:
  195.             print("\nInvalid date format! (Try: 'YYYY-MM-DD) Please try again.")
  196.  
  197. def get_valid_end_date():
  198.     """
  199.    Prompt the user to enter a valid end date before today or 'today'.
  200.  
  201.    Returns:
  202.        str: A valid end date in the format 'YYYY-MM-DD'.
  203.    """
  204.     while True:
  205.         end_date_input = input("\nEnter any end date before today (YYYY-MM-DD) or type 'today': ")
  206.  
  207.         if not end_date_input:
  208.             print("\nEnd date cannot be empty! Please enter a valid end date or type 'today' to use the current date.")
  209.             continue
  210.  
  211.         if end_date_input.lower() == 'today':
  212.             return datetime.utcnow().strftime("%Y-%m-%d")
  213.  
  214.         try:
  215.             end_date = datetime.strptime(end_date_input, "%Y-%m-%d")
  216.             if end_date > datetime.utcnow():
  217.                 print("\nEnd date cannot be in the future! Please try again.")
  218.             else:
  219.                 return end_date.strftime("%Y-%m-%d")
  220.         except ValueError:
  221.             print("\nInvalid date format! (Try: 'YYYY-MM-DD) Please try again.")
  222.  
  223. def download_images_for_date(date_str, nasa_manager, selected_option, download_location):
  224.     """
  225.    Download images for a specified date and option from the NASA EPIC API.
  226.  
  227.    Args:
  228.        date_str (str): The date in the format 'YYYY-MM-DD'.
  229.        nasa_manager (NASAEPICManager): An instance of the NASAEPICManager class.
  230.        selected_option (str): The selected option ('natural', 'enhanced', 'aerosol', or 'cloud').
  231.        download_location (str): The folder location where the images will be downloaded.
  232.  
  233.    Returns:
  234.        bool: True if images are downloaded successfully, False otherwise.
  235.    """
  236.     print(f"\nChecking if images are available for date {date_str} and option {selected_option}...\n")
  237.  
  238.     # Update the metadata URL based on the selected option
  239.     metadata_url = f"https://api.nasa.gov/EPIC/api/{selected_option}/date/{date_str}"
  240.     metadata = nasa_manager.make_api_request(metadata_url)
  241.  
  242.     # Check if imagery is available for the date
  243.     if isinstance(metadata, list) and metadata:
  244.         # Check if there are images available in the metadata
  245.         if "\nNo imagery available for the date!\n" in metadata[0].get("reason", ""):
  246.             print(f"No imagery available for the date: {date_str}")
  247.             return False
  248.  
  249.         # If imagery is available, create and download images to the folder
  250.         folder_name = f"{date_str[:4]}_{selected_option.upper()}"
  251.         folder_path = os.path.join(download_location, folder_name, date_str[5:7])
  252.         print("\nCreating folder and downloading images for date:", date_str)
  253.         os.makedirs(folder_path, exist_ok=True)
  254.  
  255.         with ThreadPoolExecutor() as executor:
  256.             futures = []
  257.  
  258.             for item in metadata:
  259.                 name = item["image"] + '.png'
  260.                 source = f"https://epic.gsfc.nasa.gov/archive/{selected_option}/{date_str[:4]}/{date_str[5:7]}/{date_str[8:10]}/png/{name}"
  261.                 destination = os.path.join(folder_path, name)
  262.  
  263.                 futures.append(executor.submit(download_image, source, destination, nasa_manager))
  264.  
  265.             for future in futures:
  266.                 future.result()
  267.  
  268.         print("\nImages downloaded successfully in folder:\n\n", folder_path)
  269.         return True
  270.     elif metadata is not None:
  271.         # Handle the case where the API request was successful but no metadata is available
  272.         print(f"\nNo metadata available for the date: {date_str}!\n")
  273.         return False
  274.     else:
  275.         # Handle the case where the API request failed (e.g., 503 error)
  276.         print(f"\nError fetching metadata for {date_str}! Please check if the date is valid or try again later.\n")
  277.         return False
  278.  
  279. def download_image(source, destination, nasa_manager, max_retries=3):
  280.     """
  281.    Download an image from the specified source URL to the destination path.
  282.  
  283.    Args:
  284.        source (str): The URL of the image to download.
  285.        destination (str): The local file path where the image will be saved.
  286.        nasa_manager (NASAEPICManager): An instance of the NASAEPICManager class for rate limit management.
  287.        max_retries (int): The maximum number of retry attempts in case of download failure. Default is 3.
  288.  
  289.    Returns:
  290.        bool: True if the image is downloaded successfully, False otherwise.
  291.    """
  292.     for attempt in range(max_retries):
  293.         try:
  294.             response = requests.get(source, timeout=5)  # (Default=5) Adjust the timeout value as needed (in seconds)
  295.             response.raise_for_status()  # Check for request success
  296.  
  297.             with open(destination, 'wb') as image_file:
  298.                 image_file.write(response.content)
  299.  
  300.             print("Downloaded:", os.path.basename(destination))
  301.             return True  # Image downloaded successfully
  302.  
  303.         except requests.exceptions.Timeout:
  304.             print(f"\nAttempt {attempt + 1}: Image download timed out! \n\nRetrying...\n")
  305.             continue  # Retry the download
  306.  
  307.         except requests.exceptions.RequestException as e:
  308.             print(f"\nError downloading image: {e}!\n")
  309.             if 'API Key' in str(e):  # Retry with a new API key if the error is related to the API key
  310.                 nasa_manager.check_rate_limit()
  311.                 continue
  312.             return False  # Failed to download image
  313.  
  314.     print(f"\nMax retries reached! Failed to download image: \n\n{os.path.basename(destination)}\n")
  315.     return False
  316.  
  317. # Define the header as a list of strings for easier manipulation
  318. header_lines = [
  319.     "                        ███    ██  █████  ███████  █████                            ",
  320.     "                        ████   ██ ██   ██ ██      ██   ██                           ",
  321.     "                        ██ ██  ██ ███████ ███████ ███████                           ",
  322.     "                        ██  ██ ██ ██   ██      ██ ██   ██                           ",
  323.     "                        ██   ████ ██   ██ ███████ ██   ██                           ",
  324.     "███████  █████         ██████  ███████        █████  ██████         ██████  ███████ ",
  325.     "     ██ ██   ██       ██       ██            ██   ██      ██       ██       ██      ",
  326.     "    ██   █████  █████ ███████  ███████ █████  █████   █████  █████ ███████  ███████ ",
  327.     "   ██   ██   ██       ██    ██      ██       ██   ██      ██       ██    ██      ██ ",
  328.     "   ██    █████         ██████  ███████        █████  ██████         ██████  ███████ ",
  329.     "                            ███████ ██████  ██  ██████                              ",
  330.     "                            ██      ██   ██ ██ ██                                   ",
  331.     "                            █████   ██████  ██ ██                                   ",
  332.     "                            ██      ██      ██ ██                                   ",
  333.     "                            ███████ ██      ██  ██████                     (IMAGES) "
  334. ]
  335.  
  336. # Apply colors to the specified lines
  337. colored_header = []
  338. for i, line in enumerate(header_lines):
  339.     if 0 <= i < 5:  # NASA
  340.         colored_header.append(Fore.RED + line + Style.RESET_ALL)
  341.     elif 5 <= i < 10:  # 78-65-83-65 (ASCII CODE)
  342.         colored_header.append(Fore.WHITE + line + Style.RESET_ALL)
  343.     elif 10 <= i < 15:  # EPIC
  344.         colored_header.append(Fore.BLUE + line + Style.RESET_ALL)
  345.     else:
  346.         colored_header.append(line)
  347.  
  348. # Print the colored header
  349. for line in colored_header:
  350.     print(line)
  351.    
  352. # Start menu
  353. while True:
  354.     """
  355.    Main loop to interact with the user, allowing them to select options for downloading EPIC images.
  356.  
  357.    The loop continuously prompts the user to select an option for EPIC imagery download, input start and end dates,
  358.    and specify the download location. It then iterates through the specified date range, attempting to download
  359.    EPIC images for each date within the range.
  360.  
  361.    The loop continues until the user decides to exit the program.
  362.  
  363.    """
  364.     nasa_manager_loop = NASAEPICManager('nasa_api.txt')
  365.  
  366.     # Display options for the user
  367.     print("_" * 83)
  368.     print("\nOptions:")
  369.     print("1. Natural")
  370.     print("2. Enhanced")
  371.     print("3. Cloud")
  372.     print("4. Aerosol")
  373.    
  374.     # Get user's choice
  375.     option_loop = input("\nSelect an option (1-4): ")
  376.    
  377.     # Map the user's choice to the corresponding URL component
  378.     options_mapping = {
  379.         '1': 'natural',
  380.         '2': 'enhanced',
  381.         '3': 'cloud',
  382.         '4': 'aerosol'
  383.     }
  384.  
  385.     selected_option_loop = options_mapping.get(option_loop)
  386.  
  387.     # Check if the selected option is valid
  388.     if not selected_option_loop:
  389.         # If the option is invalid, print an error message
  390.         print("\nInvalid option! Please enter a valid option number.")
  391.         # Since the option is invalid, continue to the next iteration of the loop
  392.         # to prompt the user again for a valid option
  393.         continue
  394.  
  395.     # Get the start date from the user
  396.     start_date_str_loop = get_valid_start_date(selected_option_loop)
  397.  
  398.     print(f"\nSelected Option: {selected_option_loop}, Start Date: {start_date_str_loop}, Epoch Date: {epoch_dates[selected_option_loop]}")
  399.  
  400.     # Get the end date from the user
  401.     end_date_str_loop = get_valid_end_date()
  402.  
  403.     # Get the download location from the user
  404.     download_location_loop = input("\nEnter the download location (e.g., 'EPIC_ARCHIVE'): ")
  405.     if not download_location_loop:
  406.         print("\nDownload location cannot be empty!\n")
  407.         continue
  408.  
  409.     # Convert the start and end dates to datetime objects
  410.     start_date_loop = datetime.strptime(start_date_str_loop, "%Y-%m-%d")
  411.     end_date_loop = datetime.strptime(end_date_str_loop, "%Y-%m-%d")
  412.  
  413.     # Iterate through the date range and download images for each date
  414.     current_date_loop = start_date_loop
  415.     while current_date_loop <= end_date_loop:
  416.         current_date_str_loop = current_date_loop.strftime("%Y-%m-%d")
  417.         if not download_images_for_date(current_date_str_loop, nasa_manager_loop, selected_option_loop, download_location_loop):
  418.             # If no images are available for the current date, move to the next date
  419.             current_date_loop += timedelta(days=1)
  420.             continue
  421.  
  422.         # Move to the next date
  423.         current_date_loop += timedelta(days=1)
  424.  
  425.     # Exit the loop if all dates have been processed
  426.     break
  427.  
  428. # Once the loop exits, print a farewell message
  429. print("\nExiting Program...   GoodBye!\n")
  430.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement