Source code for dataFetcher

#
#  ISC License
#
#  Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#
#  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
#  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
#  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
#  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
#  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
#  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

from enum import Enum
from pathlib import Path
import functools
import requests
import pooch
import logging
from typing import Optional

from Basilisk.utilities.supportDataTools.registrySnippet import REGISTRY
from Basilisk import __version__

pooch_logger = pooch.utils.get_logger()
pooch_logger.setLevel(logging.INFO)

# Override URLs for large NAIF kernels (not in GitHub repo)
EXTERNAL_KERNEL_URLS = {
    "supportData/EphemerisData/de430.bsp": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de430.bsp",
    "supportData/EphemerisData/naif0012.tls": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/lsk/naif0012.tls",
    "supportData/EphemerisData/pck00010.tpc": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/pck00010.tpc",
    "supportData/EphemerisData/de-403-masses.tpc": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/de-403-masses.tpc",
    "supportData/EphemerisData/hst_edited.bsp": "https://naif.jpl.nasa.gov/pub/naif/HST/kernels/spk/hst_edited.bsp",
    "supportData/EphemerisData/nh_pred_od077.bsp": "https://naif.jpl.nasa.gov/pub/naif/pds/data/nh-j_p_ss-spice-6-v1.0/nhsp_1000/data/spk/nh_pred_od077.bsp",
}

# Do not set hashes for files fetched from external URLs such as JPL NAIF as
# they may change without notice.
for key in EXTERNAL_KERNEL_URLS:
    REGISTRY[key] = None

DATA_VERSION = f"v{__version__}"

ALBEDO_DATA_BASE_PATH = "AlbedoData/"
ATMOSPHERE_DATA_BASE_PATH = "AtmosphereData/"
DENTON_GEO_BASE_PATH = "DentonGEO/"
EPHEMERIS_DATA_BASE_PATH = "EphemerisData/"
LOCAL_GRAV_DATA_BASE_PATH = "LocalGravData/"
MAGNETIC_FIELD_BASE_PATH = "MagneticField/"


[docs] @functools.lru_cache(maxsize=32) def tag_exists(tag_url: str) -> bool: """Return True if the given GitHub tag URL exists. Cached so repeated calls never trigger additional network requests. """ try: r = requests.head(tag_url, timeout=1) return r.status_code == 200 except Exception: return False
[docs] def find_local_support_data() -> Optional[Path]: """ Return the path to the local ``supportData`` directory if running from a cloned repo in editable mode, otherwise return ``None``. Editable installs place modules under ``dist3/Basilisk/``, while the repo's ``supportData`` directory lives at the project root. """ module_path = Path(__file__).resolve() repo_root = module_path.parents[3] support_data = repo_root / "supportData" return support_data if support_data.is_dir() else None
# Compute the base GitHub URL once at import time. # With the caching on `tag_exists` this avoids repeated network calls during # file fetches. LOCAL_SUPPORT = find_local_support_data() BASE_URL = f"https://raw.githubusercontent.com/AVSLab/basilisk/{DATA_VERSION}/" POOCH = pooch.create( path=pooch.os_cache("bsk_support_data"), base_url=BASE_URL, registry=REGISTRY, urls=EXTERNAL_KERNEL_URLS, ) def _local_rel(rel: str) -> str: """Convert a registry-style key into a path relative to LOCAL_SUPPORT.""" prefix = "supportData/" return rel[len(prefix) :] if rel.startswith(prefix) else rel
[docs] def get_path(file_enum: Enum) -> Path: """ Return a filesystem path for the requested supportData file. If running from a local Basilisk repo, the file is returned directly from ``supportData/``. Otherwise, it is fetched or retrieved from the Pooch cache. """ rel = relpath(file_enum) if LOCAL_SUPPORT: local = LOCAL_SUPPORT / _local_rel(rel) if local.exists(): return local raise FileNotFoundError(f"Support data file not found: {local}") return Path(POOCH.fetch(rel))
class DataFile: class AlbedoData(Enum): Earth_ALB_2018_CERES_All_1x1 = "Earth_ALB_2018_CERES_All_1x1.csv" Earth_ALB_2018_CERES_All_5x5 = "Earth_ALB_2018_CERES_All_5x5.csv" Earth_ALB_2018_CERES_All_10x10 = "Earth_ALB_2018_CERES_All_10x10.csv" Earth_ALB_2018_CERES_Clear_1x1 = "Earth_ALB_2018_CERES_Clear_1x1.csv" Earth_ALB_2018_CERES_Clear_5x5 = "Earth_ALB_2018_CERES_Clear_5x5.csv" Earth_ALB_2018_CERES_Clear_10x10 = "Earth_ALB_2018_CERES_Clear_10x10.csv" earthReflectivityMean_1p25x1 = "earthReflectivityMean_1p25x1.dat" earthReflectivityMean_5x5 = "earthReflectivityMean_5x5.dat" earthReflectivityMean_10x10 = "earthReflectivityMean_10x10.dat" earthReflectivityStd_1p25x1 = "earthReflectivityStd_1p25x1.dat" earthReflectivityStd_5x5 = "earthReflectivityStd_5x5.dat" earthReflectivityStd_10x10 = "earthReflectivityStd_10x10.dat" Mars_ALB_TES_1x1 = "Mars_ALB_TES_1x1.csv" Mars_ALB_TES_5x5 = "Mars_ALB_TES_5x5.csv" Mars_ALB_TES_10x10 = "Mars_ALB_TES_10x10.csv" marsReflectivityMean_1p25x1 = "marsReflectivityMean_1p25x1.dat" marsReflectivityMean_5x5 = "marsReflectivityMean_5x5.dat" marsReflectivityMean_10x10 = "marsReflectivityMean_10x10.dat" class AtmosphereData(Enum): # You’re currently missing these three in the enum but they’re in REGISTRY: EarthGRAMNominal = "EarthGRAMNominal.txt" MarsGRAMNominal = "MarsGRAMNominal.txt" NRLMSISE00Nominal = "NRLMSISE00Nominal.txt" JupiterGRAMNominal = "JupiterGRAMNominal.csv" NeptuneGRAMNominal = "NeptuneGRAMNominal.csv" TitanGRAMNominal = "TitanGRAMNominal.csv" UranusGRAMNominal = "UranusGRAMNominal.csv" USStandardAtmosphere1976 = "USStandardAtmosphere1976.csv" VenusGRAMNominal = "VenusGRAMNominal.csv" class DentonGEOData(Enum): model_e_array_all = "model_e_array_all.txt" model_e_array_high = "model_e_array_high.txt" model_e_array_low = "model_e_array_low.txt" model_e_array_mid = "model_e_array_mid.txt" model_i_array_all = "model_i_array_all.txt" model_i_array_high = "model_i_array_high.txt" model_i_array_low = "model_i_array_low.txt" model_i_array_mid = "model_i_array_mid.txt" class EphemerisData(Enum): de_403_masses = "de-403-masses.tpc" de430 = "de430.bsp" hst_edited = "hst_edited.bsp" MVN_SCLKSCET_00000 = "MVN_SCLKSCET.00000.tsc" naif0011 = "naif0011.tls" naif0012 = "naif0012.tls" nh_pred_od077 = "nh_pred_od077.bsp" pck00010 = "pck00010.tpc" class LocalGravData(Enum): eros007790 = "eros007790.tab" GGM2BData = "GGM2BData.txt" GGM03S_J2_only = "GGM03S-J2-only.txt" GGM03S = "GGM03S.txt" VESTA20H = "VESTA20H.txt" class MagneticFieldData(Enum): WMM = "WMM.COF" CATEGORY_BASE_PATHS = { "AlbedoData": ALBEDO_DATA_BASE_PATH, "AtmosphereData": ATMOSPHERE_DATA_BASE_PATH, "DentonGEOData": DENTON_GEO_BASE_PATH, "EphemerisData": EPHEMERIS_DATA_BASE_PATH, "LocalGravData": LOCAL_GRAV_DATA_BASE_PATH, "MagneticFieldData": MAGNETIC_FIELD_BASE_PATH, } def relpath(file_enum: Enum) -> str: category_name = type(file_enum).__name__ try: base = CATEGORY_BASE_PATHS[category_name] rel = base + file_enum.value return "supportData/" + rel except KeyError: raise ValueError(f"Unknown supportData category: {category_name}")