From e255a2bab9a3bfb25a42f7f3304250824b70b742 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 01:54:16 +0000 Subject: [PATCH 01/28] Transfer some useful functions from auto_download_PPP script in Ginan repo --- gnssanalysis/gn_download.py | 454 ++++++++++++------------------------ gnssanalysis/gn_utils.py | 27 ++- 2 files changed, 176 insertions(+), 305 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index d44ce13..1307c9f 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -12,6 +12,7 @@ from itertools import repeat as _repeat import logging import os as _os +from copy import deepcopy as _deepcopy import random as _random import shutil import click as _click @@ -24,7 +25,7 @@ import ftplib as _ftplib from ftplib import FTP_TLS as _FTP_TLS from pathlib import Path as _Path -from typing import Optional as _Optional, Union as _Union, Tuple as _Tuple +from typing import Optional as _Optional, Union as _Union, Tuple as _Tuple, List as _List from urllib import request as _request from urllib.error import HTTPError as _HTTPError @@ -34,6 +35,7 @@ from boto3.s3.transfer import TransferConfig from .gn_datetime import GPSDate, dt2gpswk, gpswkD2dt +from .gn_utils import ensure_folders MB = 1024 * 1024 @@ -846,325 +848,169 @@ def download_multiple_files_from_cddis(files: [str], ftp_folder: str, output_fol list(executor.map(download_file_from_cddis, files, _repeat(ftp_folder), _repeat(output_folder))) -# TODO: Deprecate? Only supports legacy filenames -def download_prod( - dates, - dest, - ac="igs", - suff="", - f_type="sp3", - dwn_src="cddis", - ftps=False, - f_dict=False, - wkly_file=False, - repro3=False, -): +def download_product_from_cddis( + download_dir: _Path, + start_epoch: _datetime, + end_epoch: _datetime, + file_ext: str, + limit: int = None, + long_filename: bool = False, + analysis_center: str = "IGS", + solution_type: str = "ULT", + sampling_rate: str = "15M", + project_type: str = "OPS", + timespan: _datetime.timedelta = _datetime.timedelta(days=2), + if_file_present: str = "prompt_user", +) -> None: """ - Function used to get the product file/s from download server of choice, default: CDDIS - - Input: - dest - destination (str) - ac - Analysis Center / product of choice (e.g. igs, igr, cod, jpl, gfz, default = igs) - suff - optional suffix added to file name (e.g. _0 or _06 for ultra-rapid products) - f_type - file type to download (e.g. clk, cls, erp, sp3, sum, default = sp3) - dwn_src - Download Source (e.g. cddis, ga) - ftps - Optionally input active ftps connection object - wkly_file - optionally grab the weekly file rather than the daily - repro3 - option to download the REPRO3 version of the file - + Download the file/s from CDDIS based on start and end epoch, to the + provided the download directory (download_dir) """ + # DZ: Download the correct IGS FIN ERP files + if file_ext == "ERP" and analysis_center == "IGS" and solution_type == "FIN": # get the correct start_epoch + start_epoch = GPSDate(str(start_epoch)) + start_epoch = gpswkD2dt(f"{start_epoch.gpswk}0") + timespan = _datetime.timedelta(days=7) + # Details for debugging purposes: + logging.debug("Attempting CDDIS Product download/s") + logging.debug(f"Start Epoch - {start_epoch}") + logging.debug(f"End Epoch - {end_epoch}") + + reference_start = _deepcopy(start_epoch) + product_filename, gps_date, reference_start = generate_product_filename( + reference_start, + file_ext, + long_filename=long_filename, + AC=analysis_center, + timespan=timespan, + solution_type=solution_type, + sampling_rate=sampling_rate, + project=project_type, + ) + logging.debug( + f"Generated filename: {product_filename}, with GPS Date: {gps_date.gpswkD} and reference: {reference_start}" + ) + with ftp_tls("gdc.cddis.eosdis.nasa.gov") as ftps: + try: + ftps.cwd(f"gnss/products/{gps_date.gpswk}") + except _ftplib.all_errors as e: + logging.info(f"{reference_start} too recent") + logging.info(f"ftp_lib error: {e}") + product_filename, gps_date, reference_start = generate_product_filename( + reference_start, + file_ext, + shift=-6, + long_filename=long_filename, + AC=analysis_center, + timespan=timespan, + solution_type=solution_type, + sampling_rate=sampling_rate, + project=project_type, + ) + ftps.cwd(f"gnss/products/{gps_date.gpswk}") + + all_files = ftps.nlst() + if not (product_filename in all_files): + logging.info(f"{product_filename} not in gnss/products/{gps_date.gpswk} - too recent") + raise FileNotFoundError + + # reference_start will be changed in the first run through while loop below + reference_start -= _datetime.timedelta(hours=24) + count = 0 + remain = end_epoch - reference_start + while remain.total_seconds() > timespan.total_seconds(): + if count == limit: + remain = _datetime.timedelta(days=0) + else: + product_filename, gps_date, reference_start = generate_product_filename( + reference_start, + file_ext, + shift=24, # Shift at the start of the loop - speeds up total download time + long_filename=long_filename, + AC=analysis_center, + timespan=timespan, + solution_type=solution_type, + sampling_rate=sampling_rate, + project=project_type, + ) + download_filepath = check_whether_to_download( + filename=product_filename, download_dir=download_dir, if_file_present=if_file_present + ) + if download_filepath: + download_file_from_cddis( + filename=product_filename, + ftp_folder=f"gnss/products/{gps_date.gpswk}", + output_folder=download_dir, + if_file_present=if_file_present, + note_filetype=file_ext, + ) + count += 1 + remain = end_epoch - reference_start - # Convert input to list of datetime dates (if not already) - if (type(dates) == list) & (type(dates[0]) == _datetime.date): - dt_list = dates - else: - dt_list = dates_type_convert(dates) - - # File types should be converted to lists also, if not already so - if isinstance(f_type, list): - f_types = f_type - else: - f_types = [f_type] - - # Create directory if doesn't exist: - if not _Path(dest).is_dir(): - _Path(dest).mkdir(parents=True) - - # Create list to hold filenames that will be downloaded: - if f_dict: - f_dict = {f_typ: [] for f_typ in f_types} - # Connect to ftps if not already: - if not ftps: - # Connect to chosen server - if dwn_src == "cddis": - logging.info("\nGathering product files...") - ftps = connect_cddis(verbose=True) - p_gpswk = 0 - else: - p_gpswk = 0 - - for dt in dt_list: - for f_typ in f_types: - if dwn_src == "cddis": - if repro3: - f, gpswk = gen_prod_filename(dt, pref=ac, suff=suff, f_type=f_typ, repro3=True) - elif (ac == "igs") and (f_typ == "erp"): - f, gpswk = gen_prod_filename(dt, pref=ac, suff="7", f_type=f_typ, wkly_file=True) - elif f_typ == "snx": - mr_file, ftps, gpswk = find_mr_file(dt, f_typ, ac, ftps) - f = mr_file - elif wkly_file: - f, gpswk = gen_prod_filename(dt, pref=ac, suff=suff, f_type=f_typ, wkly_file=True) - else: - f, gpswk = gen_prod_filename(dt, pref=ac, suff=suff, f_type=f_typ) - - if not check_file_present(comp_filename=f, dwndir=dest): - # gpswk = dt2gpswk(dt) - if gpswk != p_gpswk: - ftps.cwd("/") - ftps.cwd(f"gnss/products/{gpswk}") - if repro3: - ftps.cwd(f"repro3") - - if f_typ == "rnx": - ftps.cwd("/") - ftps.cwd(f"gnss/data/daily/{dt.year}/brdc") - success = check_n_download( - f, dwndir=dest, ftps=ftps, uncomp=True, remove_crx=True, no_check=True - ) - ftps.cwd("/") - ftps.cwd(f"gnss/products/{gpswk}") - else: - success = check_n_download( - f, dwndir=dest, ftps=ftps, uncomp=True, remove_crx=True, no_check=True - ) - p_gpswk = gpswk - else: - success = True - if f_dict and success: - f_uncomp = gen_uncomp_filename(f) - if f_uncomp not in f_dict[f_typ]: - f_dict[f_typ].append(f_uncomp) - - else: - for dt in dt_list: - for f_typ in f_types: - f = gen_prod_filename(dt, pref=ac, suff=suff, f_type=f_typ) - success = check_n_download( - f, dwndir=dest, ftps=ftps, uncomp=True, remove_crx=True, no_check=True - ) - if f_dict and success: - f_uncomp = gen_uncomp_filename(f) - if f_uncomp not in f_dict[f_typ]: - f_dict[f_typ].append(f_uncomp) - if f_dict: - return f_dict - - -def download_pea_prods( - dest, - most_recent=True, - dates=None, - ac="igs", - out_dict=False, - trop_vmf3=False, - brd_typ="igs", - snx_typ="igs", - clk_sel="clk", - repro3=False, -): +def download_atx(download_dir: _Path, long_filename: bool = False, if_file_present: str = "prompt_user") -> None: """ - Download necessary pea product files for date/s provided + Download the ATX file necessary for running the PEA provided the download directory (download_dir) """ - if dest[-1] != "/": - dest += "/" - - if most_recent: - snx_vars_out = download_most_recent( - dest=dest, f_type="snx", ac=snx_typ, dwn_src="cddis", f_dict_out=True, gpswkD_out=True, ftps_out=True - ) - f_dict, gpswkD_out, ftps = snx_vars_out - - clk_vars_out = download_most_recent( - dest=dest, f_type=clk_sel, ac=ac, dwn_src="cddis", f_dict_out=True, gpswkD_out=True, ftps_out=True - ) - f_dict_update, gpswkD_out, ftps = clk_vars_out - f_dict.update(f_dict_update) - gpswkD = gpswkD_out["clk_gpswkD"][0] - if most_recent == True: - num = 1 - else: - num = most_recent - - dt0 = gpswkD2dt(gpswkD) - dtn = dt0 - _datetime.timedelta(days=num - 1) - - if dtn == dt0: - dt_list = [dt0] - else: - dates = _pd.date_range(start=str(dtn), end=str(dt0), freq="1D") - dates = list(dates) - dates.reverse() - dt_list = sorted(dates_type_convert(dates)) - else: - dt_list = sorted(dates_type_convert(dates)) - - dest_pth = _Path(dest) - # Output dict for the files that are downloaded - if not out_dict: - out_dict = {"dates": dt_list, "atxfiles": ["igs14.atx"], "blqfiles": ["OLOAD_GO.BLQ"]} - - # Get the ATX file if not present already: - if not (dest_pth / "igs14.atx").is_file(): - if not dest_pth.is_dir(): - dest_pth.mkdir(parents=True) - url = "https://files.igs.org/pub/station/general/igs14.atx" - check_n_download_url(url, dwndir=dest) - - # Get the BLQ file if not present already: - if not (dest_pth / "OLOAD_GO.BLQ").is_file(): - url = "https://peanpod.s3-ap-southeast-2.amazonaws.com/pea/examples/EX03/products/OLOAD_GO.BLQ" - check_n_download_url(url, dwndir=dest) - - # For the troposphere, have two options: gpt2 or vmf3. If flag is set to True, download 6-hourly trop files: - if trop_vmf3: - # If directory for the Tropospheric model files doesn't exist, create it: - if not (dest_pth / "grid5").is_dir(): - (dest_pth / "grid5").mkdir(parents=True) - for dt in dt_list: - year = dt.strftime("%Y") - # Create urls to the four 6-hourly files associated with the tropospheric model - begin_url = f"https://vmf.geo.tuwien.ac.at/trop_products/GRID/5x5/VMF3/VMF3_OP/{year}/" - f_begin = "VMF3_" + dt.strftime("%Y%m%d") + ".H" - urls = [begin_url + f_begin + en for en in ["00", "06", "12", "18"]] - urls.append(begin_url + "VMF3_" + (dt + _datetime.timedelta(days=1)).strftime("%Y%m%d") + ".H00") - # Run through model files, downloading if they are not in directory - for url in urls: - if not (dest_pth / f"grid5/{url[-17:]}").is_file(): - check_n_download_url(url, dwndir=str(dest_pth / "grid5")) + if long_filename: + atx_filename = "igs20.atx" else: - # Otherwise, check for GPT2 model file or download if necessary: - if not (dest_pth / "gpt_25.grd").is_file(): - url = "https://peanpod.s3-ap-southeast-2.amazonaws.com/pea/examples/EX03/products/gpt_25.grd" - check_n_download_url(url, dwndir=dest) - - if repro3: - snx_typ = ac - standards = ["sp3", "erp", clk_sel] - ac_typ_dict = {ac_sel: [] for ac_sel in [ac, brd_typ, snx_typ]} - for typ in standards: - ac_typ_dict[ac].append(typ) - ac_typ_dict[brd_typ].append("rnx") - - if not most_recent: - f_dict = {} - ac_typ_dict[snx_typ].append("snx") - - # Download product files of each type from CDDIS for the given dates: - for ac in ac_typ_dict: - if most_recent: - f_dict_update = download_prod( - dates=dt_list, dest=dest, ac=ac, f_type=ac_typ_dict[ac], dwn_src="cddis", f_dict=True, ftps=ftps - ) - elif repro3: - f_dict_update = download_prod( - dates=dt_list, dest=dest, ac=ac, f_type=ac_typ_dict[ac], dwn_src="cddis", f_dict=True, repro3=True - ) - else: - f_dict_update = download_prod( - dates=dt_list, dest=dest, ac=ac, f_type=ac_typ_dict[ac], dwn_src="cddis", f_dict=True - ) - f_dict.update(f_dict_update) - - f_types = [] - for el in list(ac_typ_dict.values()): - for typ in el: - f_types.append(typ) - if most_recent: - f_types.append("snx") - - # Prepare the output dictionary based on the downloaded files: - for f_type in f_types: - if f_type == "rnx": - out_dict[f"navfiles"] = sorted(f_dict[f_type]) - out_dict[f"{f_type}files"] = sorted(f_dict[f_type]) + atx_filename = "igs14.atx" + ensure_folders([download_dir]) + url = f"https://files.igs.org/pub/station/general/{atx_filename}" + attempt_url_download( + download_dir=download_dir, url=url, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present + ) - return out_dict +def download_satellite_metadata_snx(download_dir: _Path, if_file_present: str = "prompt_user") -> _Path: + """Download the most recent IGS satellite metadata file -def download_rinex3(dates, stations, dest, dwn_src="cddis", ftps=False, f_dict=False): - """ - Function used to get the RINEX3 observation file from download server of choice, default: CDDIS + :param _Path download_dir: Directory to store the file in + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" + :return _Path: Return the pathlib.Path of the downloaded file """ - if dest[-1] != "/": - dest += "/" - # Convert input to list of datetime dates (if not already) - dt_list = dates_type_convert(dates) + ensure_folders([download_dir]) + download_filepath = attempt_url_download( + download_dir=download_dir, + url="https://files.igs.org/pub/station/general/igs_satellite_metadata.snx", + filename="igs_satellite_metadata.snx", + type_of_file="IGS satellite metadata", + if_file_present=if_file_present, + ) + return download_filepath - if isinstance(stations, str): - stations = [stations] - # Create directory if doesn't exist: - if not _Path(dest).is_dir(): - _Path(dest).mkdir(parents=True) +def download_yaw_files(download_dir: _Path, if_file_present: str = "prompt_user") -> _List[_Path]: + """_summary_ - if f_dict: - f_dict = {"rnxfiles": []} + :param _Path download_dir: Directory to store the file in + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" + :return _List[_Path]: _description_ + """ + ensure_folders([download_dir]) + urls = [ + "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/tables/bds_yaw_modes.snx.gz", + "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/tables/qzss_yaw_modes.snx.gz", + "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/tables/sat_yaw_bias_rate.snx.gz", + ] + download_filepaths = [] + targets = ["bds_yaw_modes.snx.gz", "qzss_yaw_modes.snx.gz", "sat_yaw_bias_rate.snx.gz"] + + for url, target in zip(urls, targets): + + download_filepath = attempt_url_download( + download_dir=download_dir, + url=url, + filename=target, + type_of_file="Yaw Model SNX", + if_file_present=if_file_present, + ) + if download_filepath: + download_filepaths.append(decompress_file(download_filepath, delete_after_decompression=True)) - # Connect to ftps if not already: - if not ftps: - # Connect to chosen server - if dwn_src == "cddis": - logging.info("\nGathering RINEX files...") - ftps = connect_cddis(verbose=True) - p_date = 0 - - for dt in dt_list: - for station in stations: - f_pref = f"{station}_R_" - f_suff_crx = f"0000_01D_30S_MO.crx.gz" - f = f_pref + dt.strftime("%Y%j") + f_suff_crx - - if not check_file_present(comp_filename=f, dwndir=dest): - if p_date == dt: - try: - success = check_n_download( - f, dwndir=dest, ftps=ftps, uncomp=True, remove_crx=True, no_check=True - ) - except: - logging.error(f"Download of {f} failed - file not found") - success = False - else: - ftps.cwd("/") - ftps.cwd(f"gnss/data/daily{dt.strftime('/%Y/%j/%yd/')}") - try: - success = check_n_download( - f, dwndir=dest, ftps=ftps, uncomp=True, remove_crx=True, no_check=True - ) - except: - logging.error(f"Download of {f} failed - file not found") - success = False - p_date = dt - else: - success = True - if f_dict and success: - f_dict["rnxfiles"].append(gen_uncomp_filename(f)) - else: - for dt in dt_list: - for station in stations: - f_pref = f"{station}_R_" - f_suff_crx = f"0000_01D_30S_MO.crx.gz" - f = f_pref + dt.strftime("%Y%j") + f_suff_crx - if not check_file_present(comp_filename=f, dwndir=dest): - success = check_n_download(f, dwndir=dest, ftps=ftps, uncomp=True, remove_crx=True, no_check=True) - else: - success = True - if f_dict and success: - f_dict["rnxfiles"].append(gen_uncomp_filename(f)) - if f_dict: - return f_dict + return download_filepaths def get_vars_from_file(path): diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index dd7062b..98e3fb5 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -5,7 +5,7 @@ import click as _click -from typing import List +from typing import List as _List def diffutil_verify_input(input): @@ -52,6 +52,31 @@ def get_filetype(path): return suffix +def configure_logging(verbose: bool) -> None: + """Set up the logger object to use for encoding logging strings + + :param bool verbose: Flag to increase level of detail to print to screen - True: DEBUG, False: INFO + """ + if verbose: + logging_level = _logging.DEBUG + else: + logging_level = _logging.INFO + _logging.basicConfig(format="%(asctime)s [%(funcName)s] %(levelname)s: %(message)s") + _logging.getLogger().setLevel(logging_level) + + +def ensure_folders(paths: _List[_pathlib.Path]): + """Ensures the folders in the input list exist in the file system - if not, create them + + :param _List[_pathlib.Path] paths: list of pathlib.Path/s to check + """ + for path in paths: + if not isinstance(path, _pathlib.Path): + path = _pathlib.Path(path) + if not path.is_dir(): + path.mkdir(parents=True, exist_ok=True) + + @_click.group(invoke_without_command=True) @_click.option( "-i", From fc21dd46f7fa12395faaeee5cc065376f6dfc2d3 Mon Sep 17 00:00:00 2001 From: Ronald Maj <36747983+ronaldmaj@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:36:43 +1000 Subject: [PATCH 02/28] Update gnssanalysis/gn_download - Fix up input argument to analysis_center Co-authored-by: Nathan <95725385+treefern@users.noreply.github.com> --- gnssanalysis/gn_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 1307c9f..62bf48a 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -881,7 +881,7 @@ def download_product_from_cddis( reference_start, file_ext, long_filename=long_filename, - AC=analysis_center, + analysis_center=analysis_center, timespan=timespan, solution_type=solution_type, sampling_rate=sampling_rate, From 1deda8d9f1757883075231d0d8376eac2b540d54 Mon Sep 17 00:00:00 2001 From: Ronald Maj <36747983+ronaldmaj@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:37:09 +1000 Subject: [PATCH 03/28] Update gnssanalysis/gn_download- again Fix up input argument to analysis_center Co-authored-by: Nathan <95725385+treefern@users.noreply.github.com> --- gnssanalysis/gn_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 62bf48a..56768e8 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -927,7 +927,7 @@ def download_product_from_cddis( file_ext, shift=24, # Shift at the start of the loop - speeds up total download time long_filename=long_filename, - AC=analysis_center, + analysis_center=analysis_center, timespan=timespan, solution_type=solution_type, sampling_rate=sampling_rate, From 68955fbb1ec6ceb0787caca45e72d7d652672f24 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 02:52:56 +0000 Subject: [PATCH 04/28] Merged changes from PR into local branch --- gnssanalysis/gn_download.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 56768e8..61f4286 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -854,7 +854,7 @@ def download_product_from_cddis( end_epoch: _datetime, file_ext: str, limit: int = None, - long_filename: bool = False, + long_filename: bool = None, analysis_center: str = "IGS", solution_type: str = "ULT", sampling_rate: str = "15M", @@ -862,9 +862,21 @@ def download_product_from_cddis( timespan: _datetime.timedelta = _datetime.timedelta(days=2), if_file_present: str = "prompt_user", ) -> None: - """ - Download the file/s from CDDIS based on start and end epoch, to the - provided the download directory (download_dir) + """Download the file/s from CDDIS based on start and end epoch, to the download directory (download_dir) + + :param _Path download_dir: Where to download files + :param _datetime start_epoch: Start date/time of files to find and download + :param _datetime end_epoch: End date/time of files to find and download + :param str file_ext: Extension of files to download (e.g. SP3, CLK, ERP, etc) + :param int limit: Variable to limit the number of files downloaded, defaults to None + :param bool long_filename: Search for IGS long filename, if None use start_epoch to determine, defaults to None + :param str analysis_center: Which analysis center's files to download (e.g. COD, GFZ, WHU, etc), defaults to "IGS" + :param str solution_type: Which solution type to download (e.g. ULT, RAP, FIN), defaults to "ULT" + :param str sampling_rate: Sampling rate of file to download, defaults to "15M" + :param str project_type: Project type of file to download (e.g. ), defaults to "OPS" + :param _datetime.timedelta timespan: _description_, defaults to _datetime.timedelta(days=2) + :param str if_file_present: _description_, defaults to "prompt_user" + :raises FileNotFoundError: _description_ """ # DZ: Download the correct IGS FIN ERP files if file_ext == "ERP" and analysis_center == "IGS" and solution_type == "FIN": # get the correct start_epoch @@ -875,6 +887,8 @@ def download_product_from_cddis( logging.debug("Attempting CDDIS Product download/s") logging.debug(f"Start Epoch - {start_epoch}") logging.debug(f"End Epoch - {end_epoch}") + if long_filename == None: + long_filename = long_filename_cddis_cutoff(start_epoch) reference_start = _deepcopy(start_epoch) product_filename, gps_date, reference_start = generate_product_filename( @@ -901,7 +915,7 @@ def download_product_from_cddis( file_ext, shift=-6, long_filename=long_filename, - AC=analysis_center, + analysis_center=analysis_center, timespan=timespan, solution_type=solution_type, sampling_rate=sampling_rate, @@ -927,7 +941,7 @@ def download_product_from_cddis( file_ext, shift=24, # Shift at the start of the loop - speeds up total download time long_filename=long_filename, - analysis_center=analysis_center, + analysis_center=analysis_center, timespan=timespan, solution_type=solution_type, sampling_rate=sampling_rate, @@ -983,11 +997,11 @@ def download_satellite_metadata_snx(download_dir: _Path, if_file_present: str = def download_yaw_files(download_dir: _Path, if_file_present: str = "prompt_user") -> _List[_Path]: - """_summary_ + """Download yaw rate / bias files needed to for Ginan's PEA :param _Path download_dir: Directory to store the file in :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return _List[_Path]: _description_ + :return _List[_Path]: Return list of download files """ ensure_folders([download_dir]) urls = [ From 87109f9bb6d1a15bd566e40cb8aa8e88c2499f3a Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 02:59:50 +0000 Subject: [PATCH 05/28] doc: update docstrings for introduced functions --- gnssanalysis/gn_download.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 61f4286..8f21943 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -406,7 +406,7 @@ def check_whether_to_download( :param str filename: Filename of the downloaded file :param _Path download_dir: Path obj to download directory - :param str if_file_present: How to handle files that are already present ["replace","dont_replace","prompt_user"], defaults to "prompt_user" + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :return _Union[_Path, None]: Path obj to the downloaded file if file should be downloaded, otherwise returns None """ # Flag to determine whether to download: @@ -461,7 +461,7 @@ def attempt_ftps_download( :param _ftplib.FTP_TLS ftps: FTP_TLS client pointed at download source :param str filename: Filename to assign for the downloaded file :param str type_of_file: How to label the file for STDOUT messages, defaults to None - :param str if_file_present: How to handle files that are already present ["replace","dont_replace","prompt_user"], defaults to "prompt_user" + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :return _Path: Path obj to the downloaded file """ "" @@ -486,7 +486,7 @@ def attempt_url_download( :param str url: URL to download :param str filename: Filename to assign for the downloaded file, defaults to None :param str type_of_file: How to label the file for STDOUT messages, defaults to None - :param str if_file_present: How to handle files that are already present ["replace","dont_replace","prompt_user"], defaults to "prompt_user" + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :return Path: Path obj to the downloaded file """ # If the filename is not provided, use the filename from the URL @@ -874,9 +874,9 @@ def download_product_from_cddis( :param str solution_type: Which solution type to download (e.g. ULT, RAP, FIN), defaults to "ULT" :param str sampling_rate: Sampling rate of file to download, defaults to "15M" :param str project_type: Project type of file to download (e.g. ), defaults to "OPS" - :param _datetime.timedelta timespan: _description_, defaults to _datetime.timedelta(days=2) - :param str if_file_present: _description_, defaults to "prompt_user" - :raises FileNotFoundError: _description_ + :param _datetime.timedelta timespan: Timespan of the file/s to download, defaults to _datetime.timedelta(days=2) + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" + :raises FileNotFoundError: Raise error if the specified file cannot be found on CDDIS """ # DZ: Download the correct IGS FIN ERP files if file_ext == "ERP" and analysis_center == "IGS" and solution_type == "FIN": # get the correct start_epoch From a8d2a7925cbde63d31442cdf014097f3b321143f Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 03:25:17 +0000 Subject: [PATCH 06/28] refactor: Move URL bases up to top of gn_download --- gnssanalysis/gn_download.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 8f21943..6fb3e53 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -40,6 +40,8 @@ MB = 1024 * 1024 CDDIS_FTP = "gdc.cddis.eosdis.nasa.gov" +PRODUCT_BASE_URL = "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/" +IGS_FILES_URL = "https://files.igs.org/pub/" # s3client = boto3.client('s3', region_name='eu-central-1') @@ -793,14 +795,17 @@ def download_file_from_cddis( if_file_present: str = "prompt_user", note_filetype: str = None, ) -> _Path: - """Downloads a single file from the cddis ftp server. + """Downloads a single file from the CDDIS ftp server - :param filename: Name of the file to download - :ftp_folder: Folder where the file is stored on the remote - :output_folder: Folder to store the output file - :ftps: Optional active connection object which is reused - :max_retries: Number of retries before raising error - :uncomp: If true, uncompress files on download + :param str filename: Name of the file to download + :param str ftp_folder: Folder where the file is stored on the remote server + :param _Path output_folder: Local folder to store the output file + :param int max_retries: Number of retries before raising error, defaults to 3 + :param bool decompress: If true, decompresses files on download, defaults to True + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" + :param str note_filetype: How to label the file for STDOUT messages, defaults to None + :raises e: Raise any error that is run into by ftplib + :return _Path: Return the local Path of the file downloaded """ with ftp_tls("gdc.cddis.eosdis.nasa.gov") as ftps: ftps.cwd(ftp_folder) @@ -836,7 +841,7 @@ def download_file_from_cddis( return download_filepath -def download_multiple_files_from_cddis(files: [str], ftp_folder: str, output_folder: _Path) -> None: +def download_multiple_files_from_cddis(files: _List[str], ftp_folder: str, output_folder: _Path) -> None: """Downloads multiple files in a single folder from cddis in a thread pool. :param files: List of str filenames @@ -854,7 +859,7 @@ def download_product_from_cddis( end_epoch: _datetime, file_ext: str, limit: int = None, - long_filename: bool = None, + long_filename: Optional[bool] = None, analysis_center: str = "IGS", solution_type: str = "ULT", sampling_rate: str = "15M", @@ -878,7 +883,7 @@ def download_product_from_cddis( :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :raises FileNotFoundError: Raise error if the specified file cannot be found on CDDIS """ - # DZ: Download the correct IGS FIN ERP files + # Download the correct IGS FIN ERP files if file_ext == "ERP" and analysis_center == "IGS" and solution_type == "FIN": # get the correct start_epoch start_epoch = GPSDate(str(start_epoch)) start_epoch = gpswkD2dt(f"{start_epoch.gpswk}0") @@ -972,7 +977,7 @@ def download_atx(download_dir: _Path, long_filename: bool = False, if_file_prese else: atx_filename = "igs14.atx" ensure_folders([download_dir]) - url = f"https://files.igs.org/pub/station/general/{atx_filename}" + url = IGS_FILES_URL + f"station/general/{atx_filename}" attempt_url_download( download_dir=download_dir, url=url, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present ) @@ -988,7 +993,7 @@ def download_satellite_metadata_snx(download_dir: _Path, if_file_present: str = ensure_folders([download_dir]) download_filepath = attempt_url_download( download_dir=download_dir, - url="https://files.igs.org/pub/station/general/igs_satellite_metadata.snx", + url=IGS_FILES_URL + "station/general/igs_satellite_metadata.snx", filename="igs_satellite_metadata.snx", type_of_file="IGS satellite metadata", if_file_present=if_file_present, @@ -1004,13 +1009,9 @@ def download_yaw_files(download_dir: _Path, if_file_present: str = "prompt_user" :return _List[_Path]: Return list of download files """ ensure_folders([download_dir]) - urls = [ - "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/tables/bds_yaw_modes.snx.gz", - "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/tables/qzss_yaw_modes.snx.gz", - "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/tables/sat_yaw_bias_rate.snx.gz", - ] download_filepaths = [] targets = ["bds_yaw_modes.snx.gz", "qzss_yaw_modes.snx.gz", "sat_yaw_bias_rate.snx.gz"] + urls = [PRODUCT_BASE_URL + target for target in targets] for url, target in zip(urls, targets): From 5d067f0e58f2e09e5cb4a2229c84c6f612f3415b Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 03:27:31 +0000 Subject: [PATCH 07/28] doc: Fix type hint to download_product_from_cddis function --- gnssanalysis/gn_download.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 6fb3e53..3932d2b 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -859,7 +859,7 @@ def download_product_from_cddis( end_epoch: _datetime, file_ext: str, limit: int = None, - long_filename: Optional[bool] = None, + long_filename: _Optional[bool] = None, analysis_center: str = "IGS", solution_type: str = "ULT", sampling_rate: str = "15M", @@ -883,7 +883,7 @@ def download_product_from_cddis( :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :raises FileNotFoundError: Raise error if the specified file cannot be found on CDDIS """ - # Download the correct IGS FIN ERP files + # DZ: Download the correct IGS FIN ERP files if file_ext == "ERP" and analysis_center == "IGS" and solution_type == "FIN": # get the correct start_epoch start_epoch = GPSDate(str(start_epoch)) start_epoch = gpswkD2dt(f"{start_epoch.gpswk}0") From 9400daecf6fab39c03bdf68135dc017ffe75a880 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 03:30:13 +0000 Subject: [PATCH 08/28] doc: Remove initial DZ: from comment --- gnssanalysis/gn_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 3932d2b..b2c219b 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -883,7 +883,7 @@ def download_product_from_cddis( :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :raises FileNotFoundError: Raise error if the specified file cannot be found on CDDIS """ - # DZ: Download the correct IGS FIN ERP files + # Download the correct IGS FIN ERP files if file_ext == "ERP" and analysis_center == "IGS" and solution_type == "FIN": # get the correct start_epoch start_epoch = GPSDate(str(start_epoch)) start_epoch = gpswkD2dt(f"{start_epoch.gpswk}0") From a4c4510be88148ff503aee9eeac7400fde479412 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 03:43:49 +0000 Subject: [PATCH 09/28] refactor: Clean-up download_yaw_files function --- gnssanalysis/gn_download.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index b2c219b..762d1b6 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -1010,15 +1010,12 @@ def download_yaw_files(download_dir: _Path, if_file_present: str = "prompt_user" """ ensure_folders([download_dir]) download_filepaths = [] - targets = ["bds_yaw_modes.snx.gz", "qzss_yaw_modes.snx.gz", "sat_yaw_bias_rate.snx.gz"] - urls = [PRODUCT_BASE_URL + target for target in targets] - - for url, target in zip(urls, targets): - + files = ["bds_yaw_modes.snx.gz", "qzss_yaw_modes.snx.gz", "sat_yaw_bias_rate.snx.gz"] + for filename in files: download_filepath = attempt_url_download( download_dir=download_dir, - url=url, - filename=target, + url=PRODUCT_BASE_URL + filename, + filename=filename, type_of_file="Yaw Model SNX", if_file_present=if_file_present, ) From 0d30aa1b392ab4dc86e6a190fb80a256f8ee495a Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 03:55:36 +0000 Subject: [PATCH 10/28] refactor: use configure_logging function for all logging in clkq and orbq --- gnssanalysis/gn_utils.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 98e3fb5..4fad53c 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -52,17 +52,16 @@ def get_filetype(path): return suffix -def configure_logging(verbose: bool) -> None: - """Set up the logger object to use for encoding logging strings - - :param bool verbose: Flag to increase level of detail to print to screen - True: DEBUG, False: INFO - """ +def configure_logging(verbose: bool, output_logger: bool = False) -> None: if verbose: logging_level = _logging.DEBUG else: logging_level = _logging.INFO _logging.basicConfig(format="%(asctime)s [%(funcName)s] %(levelname)s: %(message)s") - _logging.getLogger().setLevel(logging_level) + if output_logger: + return _logging.getLogger().setLevel(logging_level) + else: + _logging.getLogger().setLevel(logging_level) def ensure_folders(paths: _List[_pathlib.Path]): @@ -602,12 +601,7 @@ def orbq( """ from gnssanalysis import gn_io, gn_aux, gn_diffaux - _logging.basicConfig(level="INFO") # seems that logging can only be configured before the first logging call - logger = _logging.getLogger() - # if verbose: - # logger.setLevel(_logging.INFO) - # else: - # _logging.disable() + logger = configure_logging(verbose=True, output_logger=True) sp3_a = gn_io.sp3.read_sp3(input[0], nodata_to_nan=nodata_to_nan) sp3_b = gn_io.sp3.read_sp3(input[1], nodata_to_nan=nodata_to_nan) @@ -795,12 +789,8 @@ def clkq( """ from gnssanalysis import gn_io, gn_aux, gn_diffaux, gn_const - _logging.basicConfig(level="INFO") # seems that logging can only be configured before the first logging call - logger = _logging.getLogger() - if verbose: - logger.setLevel(_logging.INFO) - else: - _logging.disable() + logger = configure_logging(verbose=verbose, output_logger=True) + clk_a, clk_b = gn_io.clk.read_clk(input_clk_paths[0]), gn_io.clk.read_clk(input_clk_paths[1]) if reject_re is not None: logger.log(msg=f"Excluding satellites based on regex expression: '{reject_re}'", level=_logging.INFO) From 28132d02fcd79e60fdf0efc107e0548f096ab8b0 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 04:02:59 +0000 Subject: [PATCH 11/28] docs: Update docstring for configure_logging --- gnssanalysis/gn_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 4fad53c..73a65e9 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -5,7 +5,7 @@ import click as _click -from typing import List as _List +from typing import List as _List, Union as _Union def diffutil_verify_input(input): @@ -52,7 +52,13 @@ def get_filetype(path): return suffix -def configure_logging(verbose: bool, output_logger: bool = False) -> None: +def configure_logging(verbose: bool, output_logger: bool = False) -> _Union[_logging.Logger, None]: + """_summary_ + + :param bool verbose: Set up the logger object to use for encoding logging strings + :param bool output_logger: Flag to indicate whether to output the Logger object, defaults to False + :return _Union[_logging.Logger, None]: Return the logger object or None (based on output_logger) + """ if verbose: logging_level = _logging.DEBUG else: From f46fae410b0ab9386baa6daa0d8716283bdc30ee Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 04:20:18 +0000 Subject: [PATCH 12/28] remove: Delete old code --- gnssanalysis/gn_download.py | 108 +----------------------------------- 1 file changed, 3 insertions(+), 105 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 762d1b6..3cbb3f9 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -660,7 +660,7 @@ def connect_cddis(verbose=False): if verbose: logging.info("\nConnecting to CDDIS server...") - ftps = _FTP_TLS("gdc.cddis.eosdis.nasa.gov") + ftps = _FTP_TLS(CDDIS_FTP) ftps.login() ftps.prot_p() @@ -684,108 +684,6 @@ def ftp_tls(url: str, **kwargs) -> None: ftps.quit() -@_contextmanager -def ftp_tls_cddis(connection: _FTP_TLS = None, **kwargs) -> None: - """Establish an ftp tls connection to CDDIS. Opens a new connection if one does not already exist. - - :param connection: Active connection which is passed through to allow reuse - """ - if connection is None: - with ftp_tls(CDDIS_FTP, **kwargs) as ftps: - yield ftps - else: - yield connection - - -def select_mr_file(mr_files, f_typ, ac): - """ - Given a list of most recent files, find files matching type and AC of interest - """ - if ac == "any": - search_str = f".{f_typ}.Z" - mr_typ_files = [f for f in mr_files if f.endswith(search_str)] - else: - search_str_end = f".{f_typ}.Z" - search_str_sta = f"{ac}" - mr_typ_files = [f for f in mr_files if ((f.startswith(search_str_sta)) & (f.endswith(search_str_end)))] - - return mr_typ_files - - -def find_mr_file(dt, f_typ, ac, ftps): - """Given connection to the ftps server, find the most recent file of type f_typ and analysis centre ac""" - c_gpswk = dt2gpswk(dt) - - ftps.cwd(f"gnss/products/{c_gpswk}") - mr_files = ftps.nlst() - mr_typ_files = select_mr_file(mr_files, f_typ, ac) - - if mr_typ_files == []: - while mr_typ_files == []: - logging.info(f"GPS Week {c_gpswk} too recent") - logging.info(f"No {ac} {f_typ} files found in GPS week {c_gpswk}") - logging.info(f"Moving to GPS week {int(c_gpswk) - 1}") - c_gpswk = str(int(c_gpswk) - 1) - ftps.cwd(f"../{c_gpswk}") - mr_files = ftps.nlst() - mr_typ_files = select_mr_file(mr_files, f_typ, ac) - mr_file = mr_typ_files[-1] - return mr_file, ftps, c_gpswk - - -def download_most_recent( - dest, f_type, ftps=None, ac="any", dwn_src="cddis", f_dict_out=False, gpswkD_out=False, ftps_out=False -): - """ - Download the most recent version of a product file - """ - # File types should be converted to lists if not already a list - if isinstance(f_type, list): - f_types = f_type - else: - f_types = [f_type] - - # Create directory if doesn't exist: - if not _Path(dest).is_dir(): - _Path(dest).mkdir(parents=True) - - # Create list to hold filenames that will be downloaded: - if f_dict_out: - f_dict = {f_typ: [] for f_typ in f_types} - if gpswkD_out: - gpswk_dict = {f_typ + "_gpswkD": [] for f_typ in f_types} - # Connect to ftps if not already: - if not ftps: - # Connect to chosen server - if dwn_src == "cddis": - ftps = connect_cddis() - - for f_typ in f_types: - logging.info(f"\nSearching for most recent {ac} {f_typ}...\n") - - dt = (_np.datetime64("today") - 1).astype(_datetime.datetime) - mr_file, ftps, c_gpswk = find_mr_file(dt, f_typ, ac, ftps) - check_n_download(mr_file, dwndir=dest, ftps=ftps, uncomp=True) - ftps.cwd(f"/") - if f_dict_out: - f_uncomp = gen_uncomp_filename(mr_file) - if f_uncomp not in f_dict[f_typ]: - f_dict[f_typ].append(f_uncomp) - c_gpswkD = mr_file[3:8] - if gpswkD_out: - gpswk_dict[f_typ + "_gpswkD"].append(c_gpswkD) - - ret_vars = [] - if f_dict_out: - ret_vars.append(f_dict) - if gpswkD_out: - ret_vars.append(gpswk_dict) - if ftps_out: - ret_vars.append(ftps) - - return ret_vars - - def download_file_from_cddis( filename: str, ftp_folder: str, @@ -807,7 +705,7 @@ def download_file_from_cddis( :raises e: Raise any error that is run into by ftplib :return _Path: Return the local Path of the file downloaded """ - with ftp_tls("gdc.cddis.eosdis.nasa.gov") as ftps: + with ftp_tls(CDDIS_FTP) as ftps: ftps.cwd(ftp_folder) retries = 0 download_done = False @@ -909,7 +807,7 @@ def download_product_from_cddis( logging.debug( f"Generated filename: {product_filename}, with GPS Date: {gps_date.gpswkD} and reference: {reference_start}" ) - with ftp_tls("gdc.cddis.eosdis.nasa.gov") as ftps: + with ftp_tls(CDDIS_FTP) as ftps: try: ftps.cwd(f"gnss/products/{gps_date.gpswk}") except _ftplib.all_errors as e: From f727313d3185b1bdd80d973a1737d42f102335e1 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Fri, 16 Aug 2024 05:57:23 +0000 Subject: [PATCH 13/28] fix: Return logger properly in configure_logging func --- gnssanalysis/gn_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 73a65e9..8b157cb 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -64,10 +64,11 @@ def configure_logging(verbose: bool, output_logger: bool = False) -> _Union[_log else: logging_level = _logging.INFO _logging.basicConfig(format="%(asctime)s [%(funcName)s] %(levelname)s: %(message)s") + _logging.getLogger().setLevel(logging_level) if output_logger: - return _logging.getLogger().setLevel(logging_level) + return _logging.getLogger() else: - _logging.getLogger().setLevel(logging_level) + return None def ensure_folders(paths: _List[_pathlib.Path]): From da58b841715f4574fc51d6be837cec9487287a47 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 00:12:29 +0000 Subject: [PATCH 14/28] doc: Make argument description consistent --- gnssanalysis/gn_download.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 3cbb3f9..106cb2f 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -407,7 +407,7 @@ def check_whether_to_download( already present and what action to take if it is (if_file_present) :param str filename: Filename of the downloaded file - :param _Path download_dir: Path obj to download directory + :param _Path download_dir: Where to download files (local directory) :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :return _Union[_Path, None]: Path obj to the downloaded file if file should be downloaded, otherwise returns None """ @@ -459,7 +459,7 @@ def attempt_ftps_download( ) -> _Path: """Attempt download of file (filename) given the ftps client object (ftps) to chosen location (download_dir) - :param _Path download_dir: Path obj to download directory + :param _Path download_dir: Where to download files (local directory) :param _ftplib.FTP_TLS ftps: FTP_TLS client pointed at download source :param str filename: Filename to assign for the downloaded file :param str type_of_file: How to label the file for STDOUT messages, defaults to None @@ -767,7 +767,7 @@ def download_product_from_cddis( ) -> None: """Download the file/s from CDDIS based on start and end epoch, to the download directory (download_dir) - :param _Path download_dir: Where to download files + :param _Path download_dir: Where to download files (local directory) :param _datetime start_epoch: Start date/time of files to find and download :param _datetime end_epoch: End date/time of files to find and download :param str file_ext: Extension of files to download (e.g. SP3, CLK, ERP, etc) @@ -865,26 +865,30 @@ def download_product_from_cddis( remain = end_epoch - reference_start -def download_atx(download_dir: _Path, long_filename: bool = False, if_file_present: str = "prompt_user") -> None: - """ - Download the ATX file necessary for running the PEA provided the download directory (download_dir) - """ +def download_atx(download_dir: _Path, long_filename: bool = False, if_file_present: str = "prompt_user") -> _Path: + """Download the ATX file necessary for running the PEA provided the download directory (download_dir) + :param _Path download_dir: Where to download files (local directory) + :param bool long_filename: _description_, defaults to False + :param str if_file_present: _description_, defaults to "prompt_user" + :return _Path: _description_ + """ if long_filename: atx_filename = "igs20.atx" else: atx_filename = "igs14.atx" ensure_folders([download_dir]) url = IGS_FILES_URL + f"station/general/{atx_filename}" - attempt_url_download( + download_filepath = attempt_url_download( download_dir=download_dir, url=url, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present ) + return download_filepath def download_satellite_metadata_snx(download_dir: _Path, if_file_present: str = "prompt_user") -> _Path: """Download the most recent IGS satellite metadata file - :param _Path download_dir: Directory to store the file in + :param _Path download_dir: Where to download files (local directory) :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :return _Path: Return the pathlib.Path of the downloaded file """ @@ -902,7 +906,7 @@ def download_satellite_metadata_snx(download_dir: _Path, if_file_present: str = def download_yaw_files(download_dir: _Path, if_file_present: str = "prompt_user") -> _List[_Path]: """Download yaw rate / bias files needed to for Ginan's PEA - :param _Path download_dir: Directory to store the file in + :param _Path download_dir: Where to download files (local directory) :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :return _List[_Path]: Return list of download files """ From 65a5f965a17e510903ee10eef9587cb0639e1ce6 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 00:34:43 +0000 Subject: [PATCH 15/28] fix: URL for yaw files download --- gnssanalysis/gn_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 106cb2f..9939d86 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -916,7 +916,7 @@ def download_yaw_files(download_dir: _Path, if_file_present: str = "prompt_user" for filename in files: download_filepath = attempt_url_download( download_dir=download_dir, - url=PRODUCT_BASE_URL + filename, + url=PRODUCT_BASE_URL + +"tables/" + filename, filename=filename, type_of_file="Yaw Model SNX", if_file_present=if_file_present, From 37e1dd67c7bab17a234f58a9eb65d1c07866cf05 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 00:35:40 +0000 Subject: [PATCH 16/28] fix: Dangling operator --- gnssanalysis/gn_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 9939d86..208f6ee 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -916,7 +916,7 @@ def download_yaw_files(download_dir: _Path, if_file_present: str = "prompt_user" for filename in files: download_filepath = attempt_url_download( download_dir=download_dir, - url=PRODUCT_BASE_URL + +"tables/" + filename, + url=PRODUCT_BASE_URL + "tables/" + filename, filename=filename, type_of_file="Yaw Model SNX", if_file_present=if_file_present, From 43a05eea7bf0d837fd94c4e39d095619fb0bdf2f Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 01:44:59 +0000 Subject: [PATCH 17/28] add: Unit tests for download functions --- gnssanalysis/gn_download.py | 10 +++---- gnssanalysis/gn_utils.py | 17 +++++++++++ tests/test_download.py | 57 +++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/test_download.py diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 208f6ee..f131ad6 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -464,7 +464,7 @@ def attempt_ftps_download( :param str filename: Filename to assign for the downloaded file :param str type_of_file: How to label the file for STDOUT messages, defaults to None :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return _Path: Path obj to the downloaded file + :return _Path: Return the pathlib.Path of the downloaded file """ "" logging.info(f"Attempting FTPS Download of {type_of_file} file - {filename} to {download_dir}") @@ -703,7 +703,7 @@ def download_file_from_cddis( :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :param str note_filetype: How to label the file for STDOUT messages, defaults to None :raises e: Raise any error that is run into by ftplib - :return _Path: Return the local Path of the file downloaded + :return _Path: Return the pathlib.Path of the downloaded file """ with ftp_tls(CDDIS_FTP) as ftps: ftps.cwd(ftp_folder) @@ -869,9 +869,9 @@ def download_atx(download_dir: _Path, long_filename: bool = False, if_file_prese """Download the ATX file necessary for running the PEA provided the download directory (download_dir) :param _Path download_dir: Where to download files (local directory) - :param bool long_filename: _description_, defaults to False - :param str if_file_present: _description_, defaults to "prompt_user" - :return _Path: _description_ + :param bool long_filename: Download ATX file relevant after 2022-11-27? True: igs20, False:igs14, defaults to False + :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" + :return _Path: Return the pathlib.Path of the downloaded file """ if long_filename: atx_filename = "igs20.atx" diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 8b157cb..2ba4be4 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -83,6 +83,23 @@ def ensure_folders(paths: _List[_pathlib.Path]): path.mkdir(parents=True, exist_ok=True) +def delete_entire_directory(directory: _pathlib.Path): + """Recursively delete a directory, including all subdirectories and files in subdirectories + + :param Path directory: Directory to recursively delete + """ + # First, iterate through all the files and subdirectories + for item in directory.iterdir(): + if item.is_dir(): + # Recursively delete subdirectories + delete_entire_directory(item) + else: + # Delete files + item.unlink() + # Finally, delete the empty directory itself + directory.rmdir() + + @_click.group(invoke_without_command=True) @_click.option( "-i", diff --git a/tests/test_download.py b/tests/test_download.py new file mode 100644 index 0000000..54a3fe6 --- /dev/null +++ b/tests/test_download.py @@ -0,0 +1,57 @@ +import unittest +from unittest.mock import patch, mock_open +from pyfakefs.fake_filesystem_unittest import TestCase +from pathlib import Path, PosixPath + +import numpy as np +import pandas as pd + +from gnssanalysis.gn_utils import delete_entire_directory +import gnssanalysis.gn_download as ga_download + + +class TestDownload(TestCase): + def setUp(self): + # Create directory to save files + self.test_dir = "download_test_dir" + Path.mkdir(self.test_dir, exist_ok=True) + # self.setUpPyfakefs() + + def tearDown(self): + # Clean up test directory after tests: + if Path(self.test_dir).is_dir(): + delete_entire_directory(Path(self.test_dir)) + + def test_download_atx_file(self): + + # Test download of yaw files + downloaded_file = ga_download.download_atx(download_dir=Path(self.test_dir), long_filename=True) + + # Verify + self.assertEqual(type(downloaded_file), PosixPath) + self.assertEqual(downloaded_file.name, "igs20.atx") + + # Re-try download - do not re-download + downloaded_file = ga_download.download_yaw_files( + download_dir=Path(self.test_dir), if_file_present="dont_replace" + ) + + # Verify + self.assertEqual(downloaded_file, None) + + def test_download_yaw_files(self): + + # Test download of yaw files + downloaded_files = ga_download.download_yaw_files(download_dir=Path(self.test_dir)) + + # Verify + self.assertEqual(len(downloaded_files), 3) + self.assertEqual(downloaded_files[0].name, "bds_yaw_modes.snx") + self.assertEqual(downloaded_files[1].name, "qzss_yaw_modes.snx") + self.assertEqual(downloaded_files[2].name, "sat_yaw_bias_rate.snx") + + # Re-try download - do re-download + downloaded_files = ga_download.download_yaw_files(download_dir=Path(self.test_dir), if_file_present="replace") + + # Verify + self.assertEqual(len(downloaded_files), 3) From 2ac391acdc090f03fbc280e5200434efc051bbd9 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 02:51:31 +0000 Subject: [PATCH 18/28] extend: unittests for new util functions and fix up docs + other issues --- gnssanalysis/gn_utils.py | 4 +-- tests/test_download.py | 40 +++++++++++++++++++++--- tests/test_utils.py | 66 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 tests/test_utils.py diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 2ba4be4..54999a2 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -53,9 +53,9 @@ def get_filetype(path): def configure_logging(verbose: bool, output_logger: bool = False) -> _Union[_logging.Logger, None]: - """_summary_ + """Configure the logger object with the level of verbosity requested and output if desired - :param bool verbose: Set up the logger object to use for encoding logging strings + :param bool verbose: Verbosity of logger object to use for encoding logging strings, True: DEBUG, False: INFO :param bool output_logger: Flag to indicate whether to output the Logger object, defaults to False :return _Union[_logging.Logger, None]: Return the logger object or None (based on output_logger) """ diff --git a/tests/test_download.py b/tests/test_download.py index 54a3fe6..084e768 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -2,6 +2,7 @@ from unittest.mock import patch, mock_open from pyfakefs.fake_filesystem_unittest import TestCase from pathlib import Path, PosixPath +from datetime import datetime import numpy as np import pandas as pd @@ -22,9 +23,23 @@ def tearDown(self): if Path(self.test_dir).is_dir(): delete_entire_directory(Path(self.test_dir)) - def test_download_atx_file(self): + def test_download_product_from_cddis(self): - # Test download of yaw files + # Test download of file from CDDIS (in this case an IGS ULT) + ga_download.download_product_from_cddis( + download_dir=Path(self.test_dir), + start_epoch=datetime(2024, 8, 10), + end_epoch=datetime(2024, 8, 12), + file_ext="SP3", + ) + + # Verify + self.assertEqual(type(next(Path(self.test_dir).glob("*.SP3"))), PosixPath) + self.assertEqual(next(Path(self.test_dir).glob("*.SP3")).name, "IGS0OPSULT_20242230000_02D_15M_ORB.SP3") + + def test_download_atx(self): + + # Test download of ATX file downloaded_file = ga_download.download_atx(download_dir=Path(self.test_dir), long_filename=True) # Verify @@ -32,7 +47,24 @@ def test_download_atx_file(self): self.assertEqual(downloaded_file.name, "igs20.atx") # Re-try download - do not re-download - downloaded_file = ga_download.download_yaw_files( + downloaded_file = ga_download.download_atx( + download_dir=Path(self.test_dir), long_filename=True, if_file_present="dont_replace" + ) + + # Verify + self.assertEqual(downloaded_file, None) + + def test_download_satellite_metadata_snx(self): + + # Test download of satellite metadata SNX file + downloaded_file = ga_download.download_satellite_metadata_snx(download_dir=Path(self.test_dir)) + + # Verify + self.assertEqual(type(downloaded_file), PosixPath) + self.assertEqual(downloaded_file.name, "igs_satellite_metadata.snx") + + # Re-try download - do not re-download + downloaded_file = ga_download.download_satellite_metadata_snx( download_dir=Path(self.test_dir), if_file_present="dont_replace" ) @@ -50,7 +82,7 @@ def test_download_yaw_files(self): self.assertEqual(downloaded_files[1].name, "qzss_yaw_modes.snx") self.assertEqual(downloaded_files[2].name, "sat_yaw_bias_rate.snx") - # Re-try download - do re-download + # Re-try download downloaded_files = ga_download.download_yaw_files(download_dir=Path(self.test_dir), if_file_present="replace") # Verify diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..f340e34 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,66 @@ +import unittest +import logging +from unittest.mock import patch, mock_open +from pyfakefs.fake_filesystem_unittest import TestCase +from pathlib import Path, PosixPath + +from gnssanalysis.gn_utils import delete_entire_directory +import gnssanalysis.gn_utils as ga_utils + + +class TestUtils(TestCase): + def setUp(self): + self.setUpPyfakefs() + # Create directory + self.test_dir_1 = "/test_dir_1" + self.test_dir_2 = "/test_dir_2/a/b/" + Path(self.test_dir_1).mkdir(exist_ok=True) + Path(self.test_dir_2).mkdir(exist_ok=True, parents=True) + + def tearDown(self): + # Clean up test directory after tests: + if Path(self.test_dir_1).is_dir(): + delete_entire_directory(Path(self.test_dir_1)) + if Path(self.test_dir_2).is_dir(): + delete_entire_directory(Path(self.test_dir_2)) + + def test_ensure_folders(self): + + # Verify directories that do and dont exist: + self.assertEqual(Path(self.test_dir_1).is_dir(), True) + self.assertEqual((Path(self.test_dir_1) / "a/").is_dir(), False) + self.assertEqual((Path(self.test_dir_1) / "a/b/").is_dir(), False) + self.assertEqual(Path(self.test_dir_2).is_dir(), True) + self.assertEqual((Path(self.test_dir_2) / "c/d/").is_dir(), False) + + # Use ensure_folders function to create various + ga_utils.ensure_folders([self.test_dir_1, self.test_dir_1 + "/a/b/", self.test_dir_2]) + + # Verify directories that do and dont exist: + self.assertEqual(Path(self.test_dir_1).is_dir(), True) + self.assertEqual((Path(self.test_dir_1) / "a/").is_dir(), True) + self.assertEqual((Path(self.test_dir_1) / "a/b/").is_dir(), True) + self.assertEqual(Path(self.test_dir_2).is_dir(), True) + self.assertEqual((Path(self.test_dir_2) / "c/d/").is_dir(), False) + + def test_configure_logging(self): + + # Set up verbose logger: + logger_verbose = ga_utils.configure_logging(verbose=True, output_logger=True) + + # Verify + self.assertEqual(type(logger_verbose), logging.RootLogger) + self.assertEqual(logger_verbose.level, 10) + + # Set up not verbose logger: + logger_not_verbose = ga_utils.configure_logging(verbose=False, output_logger=True) + + # Verify + self.assertEqual(type(logger_not_verbose), logging.RootLogger) + self.assertEqual(logger_not_verbose.level, 20) + + # Set up logger without output: + logger_not_output = ga_utils.configure_logging(verbose=True, output_logger=False) + + # Verify + self.assertEqual(logger_not_output, None) From f7b6578c19aafdf476687ef030635b415252bf08 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 02:58:24 +0000 Subject: [PATCH 19/28] delete: Unused function in gn_download - gen_prod_filename --- gnssanalysis/gn_download.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index f131ad6..0e87b56 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -184,38 +184,6 @@ def gen_uncomp_filename(comp_filename: str) -> str: return comp_filename -def gen_prod_filename(dt, pref, suff, f_type, wkly_file=False, repro3=False): - """ - Generate a product filename based on the inputs - """ - gpswk, gpswkD = dt2gpswk(dt, both=True) - - if repro3: - if f_type == "erp": - f = f'{pref.upper()}0R03FIN_{dt.year}{dt.strftime("%j")}0000_01D_01D_{f_type.upper()}.{f_type.upper()}.gz' - elif f_type == "clk": - f = f'{pref.upper()}0R03FIN_{dt.year}{dt.strftime("%j")}0000_01D_30S_{f_type.upper()}.{f_type.upper()}.gz' - elif f_type == "bia": - f = f'{pref.upper()}0R03FIN_{dt.year}{dt.strftime("%j")}0000_01D_01D_OSB.{f_type.upper()}.gz' - elif f_type == "sp3": - f = f'{pref.upper()}0R03FIN_{dt.year}{dt.strftime("%j")}0000_01D_05M_ORB.{f_type.upper()}.gz' - elif f_type == "snx": - f = f'{pref.upper()}0R03FIN_{dt.year}{dt.strftime("%j")}0000_01D_01D_SOL.{f_type.upper()}.gz' - elif f_type == "rnx": - f = f'BRDC00{pref.upper()}_R_{dt.year}{dt.strftime("%j")}0000_01D_MN.rnx.gz' - elif (pref == "igs") & (f_type == "snx") & wkly_file: - f = f"{pref}{str(dt.year)[2:]}P{gpswk}.{f_type}.Z" - elif (pref == "igs") & (f_type == "snx"): - f = f"{pref}{str(dt.year)[2:]}P{gpswkD}.{f_type}.Z" - elif f_type == "rnx": - f = f'BRDC00{pref.upper()}_R_{dt.year}{dt.strftime("%j")}0000_01D_MN.rnx.gz' - elif wkly_file: - f = f"{pref}{gpswk}{suff}.{f_type}.Z" - else: - f = f"{pref}{gpswkD}{suff}.{f_type}.Z" - return f, gpswk - - def generate_uncompressed_filename(filename: str) -> str: """Returns a string of the uncompressed filename given the [assumed compressed] filename From 1abfc3affa7d385bcef873f9a406579e261d2dae Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 03:57:41 +0000 Subject: [PATCH 20/28] docs: Remove redundant - return - in docstrings --- gnssanalysis/gn_download.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 0e87b56..a71d100 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -432,7 +432,7 @@ def attempt_ftps_download( :param str filename: Filename to assign for the downloaded file :param str type_of_file: How to label the file for STDOUT messages, defaults to None :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return _Path: Return the pathlib.Path of the downloaded file + :return _Path: The pathlib.Path of the downloaded file """ "" logging.info(f"Attempting FTPS Download of {type_of_file} file - {filename} to {download_dir}") @@ -671,7 +671,7 @@ def download_file_from_cddis( :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :param str note_filetype: How to label the file for STDOUT messages, defaults to None :raises e: Raise any error that is run into by ftplib - :return _Path: Return the pathlib.Path of the downloaded file + :return _Path: The pathlib.Path of the downloaded file """ with ftp_tls(CDDIS_FTP) as ftps: ftps.cwd(ftp_folder) @@ -839,7 +839,7 @@ def download_atx(download_dir: _Path, long_filename: bool = False, if_file_prese :param _Path download_dir: Where to download files (local directory) :param bool long_filename: Download ATX file relevant after 2022-11-27? True: igs20, False:igs14, defaults to False :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return _Path: Return the pathlib.Path of the downloaded file + :return _Path: The pathlib.Path of the downloaded file """ if long_filename: atx_filename = "igs20.atx" @@ -858,7 +858,7 @@ def download_satellite_metadata_snx(download_dir: _Path, if_file_present: str = :param _Path download_dir: Where to download files (local directory) :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return _Path: Return the pathlib.Path of the downloaded file + :return _Path: The pathlib.Path of the downloaded file """ ensure_folders([download_dir]) download_filepath = attempt_url_download( From df6c51a133092e9d09fff36bdc5d2095ad3534c7 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 04:11:48 +0000 Subject: [PATCH 21/28] refactor: Change argument name in download_atx to clear up confusion --- gnssanalysis/gn_download.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index a71d100..da813d5 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -833,18 +833,19 @@ def download_product_from_cddis( remain = end_epoch - reference_start -def download_atx(download_dir: _Path, long_filename: bool = False, if_file_present: str = "prompt_user") -> _Path: +def download_atx(download_dir: _Path, reference_frame: str = "IGS20", if_file_present: str = "prompt_user") -> _Path: """Download the ATX file necessary for running the PEA provided the download directory (download_dir) :param _Path download_dir: Where to download files (local directory) - :param bool long_filename: Download ATX file relevant after 2022-11-27? True: igs20, False:igs14, defaults to False + :param str reference_frame: Coordinate reference frame file to download, defaults to "IGS20" :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" :return _Path: The pathlib.Path of the downloaded file """ - if long_filename: - atx_filename = "igs20.atx" - else: - atx_filename = "igs14.atx" + match reference_frame: + case "IGS20": + atx_filename = "igs20.atx" + case "IGb14": + atx_filename = "igs14.atx" ensure_folders([download_dir]) url = IGS_FILES_URL + f"station/general/{atx_filename}" download_filepath = attempt_url_download( From a6730eb3df45e7b7b3eca8ae672c1f1b3366ec27 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Mon, 19 Aug 2024 04:15:36 +0000 Subject: [PATCH 22/28] fix: Change unittest for download_atx as well --- tests/test_download.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_download.py b/tests/test_download.py index 084e768..477138c 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -40,7 +40,7 @@ def test_download_product_from_cddis(self): def test_download_atx(self): # Test download of ATX file - downloaded_file = ga_download.download_atx(download_dir=Path(self.test_dir), long_filename=True) + downloaded_file = ga_download.download_atx(download_dir=Path(self.test_dir), reference_frame="IGS20") # Verify self.assertEqual(type(downloaded_file), PosixPath) @@ -48,7 +48,7 @@ def test_download_atx(self): # Re-try download - do not re-download downloaded_file = ga_download.download_atx( - download_dir=Path(self.test_dir), long_filename=True, if_file_present="dont_replace" + download_dir=Path(self.test_dir), reference_frame="IGS20", if_file_present="dont_replace" ) # Verify From 7f6e154bd168eb4d411bd7c336d3b7ecb57cbf13 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Tue, 20 Aug 2024 01:58:05 +0000 Subject: [PATCH 23/28] refactor: Based on PR comments - less likely to fail unexpectedly and increase clarity --- tests/test_download.py | 34 ++++++++++++++++++++++++---------- tests/test_utils.py | 20 ++++++++++---------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/tests/test_download.py b/tests/test_download.py index 477138c..0808a93 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -4,9 +4,6 @@ from pathlib import Path, PosixPath from datetime import datetime -import numpy as np -import pandas as pd - from gnssanalysis.gn_utils import delete_entire_directory import gnssanalysis.gn_download as ga_download @@ -28,14 +25,19 @@ def test_download_product_from_cddis(self): # Test download of file from CDDIS (in this case an IGS ULT) ga_download.download_product_from_cddis( download_dir=Path(self.test_dir), - start_epoch=datetime(2024, 8, 10), - end_epoch=datetime(2024, 8, 12), + start_epoch=datetime(2023, 8, 10), + end_epoch=datetime(2023, 8, 12), file_ext="SP3", ) + # Grab pathlib.Path + downloaded_file = next(Path(self.test_dir).glob("*.SP3")) + # Verify - self.assertEqual(type(next(Path(self.test_dir).glob("*.SP3"))), PosixPath) - self.assertEqual(next(Path(self.test_dir).glob("*.SP3")).name, "IGS0OPSULT_20242230000_02D_15M_ORB.SP3") + self.assertEqual(type(downloaded_file), PosixPath) + self.assertEqual(downloaded_file.name, "IGS0OPSULT_20232220000_02D_15M_ORB.SP3") + # Ensure file size is right: + self.assertEqual(downloaded_file.stat().st_size, 489602) def test_download_atx(self): @@ -46,6 +48,9 @@ def test_download_atx(self): self.assertEqual(type(downloaded_file), PosixPath) self.assertEqual(downloaded_file.name, "igs20.atx") + # Check not empty + self.assertGreater(downloaded_file.stat().st_size, 100) + # Re-try download - do not re-download downloaded_file = ga_download.download_atx( download_dir=Path(self.test_dir), reference_frame="IGS20", if_file_present="dont_replace" @@ -63,6 +68,9 @@ def test_download_satellite_metadata_snx(self): self.assertEqual(type(downloaded_file), PosixPath) self.assertEqual(downloaded_file.name, "igs_satellite_metadata.snx") + # Check not empty + self.assertGreater(downloaded_file.stat().st_size, 100) + # Re-try download - do not re-download downloaded_file = ga_download.download_satellite_metadata_snx( download_dir=Path(self.test_dir), if_file_present="dont_replace" @@ -76,11 +84,17 @@ def test_download_yaw_files(self): # Test download of yaw files downloaded_files = ga_download.download_yaw_files(download_dir=Path(self.test_dir)) + # Get filenames: + downloaded_filenames = [file.name for file in downloaded_files] + # Verify self.assertEqual(len(downloaded_files), 3) - self.assertEqual(downloaded_files[0].name, "bds_yaw_modes.snx") - self.assertEqual(downloaded_files[1].name, "qzss_yaw_modes.snx") - self.assertEqual(downloaded_files[2].name, "sat_yaw_bias_rate.snx") + self.assertIn("bds_yaw_modes.snx", downloaded_filenames) + self.assertIn("qzss_yaw_modes.snx", downloaded_filenames) + self.assertIn("sat_yaw_bias_rate.snx", downloaded_filenames) + # Check files aren't empty + for file in downloaded_files: + self.assertGreater(file.stat().st_size, 100) # Re-try download downloaded_files = ga_download.download_yaw_files(download_dir=Path(self.test_dir), if_file_present="replace") diff --git a/tests/test_utils.py b/tests/test_utils.py index f340e34..c1deead 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -27,21 +27,21 @@ def tearDown(self): def test_ensure_folders(self): # Verify directories that do and dont exist: - self.assertEqual(Path(self.test_dir_1).is_dir(), True) - self.assertEqual((Path(self.test_dir_1) / "a/").is_dir(), False) - self.assertEqual((Path(self.test_dir_1) / "a/b/").is_dir(), False) - self.assertEqual(Path(self.test_dir_2).is_dir(), True) - self.assertEqual((Path(self.test_dir_2) / "c/d/").is_dir(), False) + self.assertTrue(Path(self.test_dir_1).is_dir()) + self.assertFalse((Path(self.test_dir_1) / "a/").is_dir()) + self.assertFalse((Path(self.test_dir_1) / "a/b/").is_dir()) + self.assertTrue(Path(self.test_dir_2).is_dir()) + self.assertFalse((Path(self.test_dir_2) / "c/d/").is_dir()) # Use ensure_folders function to create various ga_utils.ensure_folders([self.test_dir_1, self.test_dir_1 + "/a/b/", self.test_dir_2]) # Verify directories that do and dont exist: - self.assertEqual(Path(self.test_dir_1).is_dir(), True) - self.assertEqual((Path(self.test_dir_1) / "a/").is_dir(), True) - self.assertEqual((Path(self.test_dir_1) / "a/b/").is_dir(), True) - self.assertEqual(Path(self.test_dir_2).is_dir(), True) - self.assertEqual((Path(self.test_dir_2) / "c/d/").is_dir(), False) + self.assertTrue(Path(self.test_dir_1).is_dir()) + self.assertTrue((Path(self.test_dir_1) / "a/").is_dir()) + self.assertTrue((Path(self.test_dir_1) / "a/b/").is_dir()) + self.assertTrue(Path(self.test_dir_2).is_dir()) + self.assertFalse((Path(self.test_dir_2) / "c/d/").is_dir()) def test_configure_logging(self): From 758e0cc510315c462abfb320288b5bb9375e7aa8 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Tue, 20 Aug 2024 05:00:50 +0000 Subject: [PATCH 24/28] add: Another download source for ATX file --- gnssanalysis/gn_download.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index da813d5..3019295 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -42,6 +42,7 @@ CDDIS_FTP = "gdc.cddis.eosdis.nasa.gov" PRODUCT_BASE_URL = "https://peanpod.s3.ap-southeast-2.amazonaws.com/aux/products/" IGS_FILES_URL = "https://files.igs.org/pub/" +BERN_URL = "http://ftp.aiub.unibe.ch/" # s3client = boto3.client('s3', region_name='eu-central-1') @@ -846,11 +847,20 @@ def download_atx(download_dir: _Path, reference_frame: str = "IGS20", if_file_pr atx_filename = "igs20.atx" case "IGb14": atx_filename = "igs14.atx" + ensure_folders([download_dir]) - url = IGS_FILES_URL + f"station/general/{atx_filename}" - download_filepath = attempt_url_download( - download_dir=download_dir, url=url, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present - ) + + url_igs = IGS_FILES_URL + f"station/general/{atx_filename}" + url_bern = BERN_URL + "BSWUSER54/REF/I20.ATX" + + try: + download_filepath = attempt_url_download( + download_dir=download_dir, url=url_igs, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present + ) + except: + download_filepath = attempt_url_download( + download_dir=download_dir, url=url_bern, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present + ) return download_filepath From 09c4e36d90cc7409624b5ac156ce3e6e674f00fa Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Tue, 20 Aug 2024 05:01:55 +0000 Subject: [PATCH 25/28] delete: test_download.py --- tests/test_download.py | 103 ----------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 tests/test_download.py diff --git a/tests/test_download.py b/tests/test_download.py deleted file mode 100644 index 0808a93..0000000 --- a/tests/test_download.py +++ /dev/null @@ -1,103 +0,0 @@ -import unittest -from unittest.mock import patch, mock_open -from pyfakefs.fake_filesystem_unittest import TestCase -from pathlib import Path, PosixPath -from datetime import datetime - -from gnssanalysis.gn_utils import delete_entire_directory -import gnssanalysis.gn_download as ga_download - - -class TestDownload(TestCase): - def setUp(self): - # Create directory to save files - self.test_dir = "download_test_dir" - Path.mkdir(self.test_dir, exist_ok=True) - # self.setUpPyfakefs() - - def tearDown(self): - # Clean up test directory after tests: - if Path(self.test_dir).is_dir(): - delete_entire_directory(Path(self.test_dir)) - - def test_download_product_from_cddis(self): - - # Test download of file from CDDIS (in this case an IGS ULT) - ga_download.download_product_from_cddis( - download_dir=Path(self.test_dir), - start_epoch=datetime(2023, 8, 10), - end_epoch=datetime(2023, 8, 12), - file_ext="SP3", - ) - - # Grab pathlib.Path - downloaded_file = next(Path(self.test_dir).glob("*.SP3")) - - # Verify - self.assertEqual(type(downloaded_file), PosixPath) - self.assertEqual(downloaded_file.name, "IGS0OPSULT_20232220000_02D_15M_ORB.SP3") - # Ensure file size is right: - self.assertEqual(downloaded_file.stat().st_size, 489602) - - def test_download_atx(self): - - # Test download of ATX file - downloaded_file = ga_download.download_atx(download_dir=Path(self.test_dir), reference_frame="IGS20") - - # Verify - self.assertEqual(type(downloaded_file), PosixPath) - self.assertEqual(downloaded_file.name, "igs20.atx") - - # Check not empty - self.assertGreater(downloaded_file.stat().st_size, 100) - - # Re-try download - do not re-download - downloaded_file = ga_download.download_atx( - download_dir=Path(self.test_dir), reference_frame="IGS20", if_file_present="dont_replace" - ) - - # Verify - self.assertEqual(downloaded_file, None) - - def test_download_satellite_metadata_snx(self): - - # Test download of satellite metadata SNX file - downloaded_file = ga_download.download_satellite_metadata_snx(download_dir=Path(self.test_dir)) - - # Verify - self.assertEqual(type(downloaded_file), PosixPath) - self.assertEqual(downloaded_file.name, "igs_satellite_metadata.snx") - - # Check not empty - self.assertGreater(downloaded_file.stat().st_size, 100) - - # Re-try download - do not re-download - downloaded_file = ga_download.download_satellite_metadata_snx( - download_dir=Path(self.test_dir), if_file_present="dont_replace" - ) - - # Verify - self.assertEqual(downloaded_file, None) - - def test_download_yaw_files(self): - - # Test download of yaw files - downloaded_files = ga_download.download_yaw_files(download_dir=Path(self.test_dir)) - - # Get filenames: - downloaded_filenames = [file.name for file in downloaded_files] - - # Verify - self.assertEqual(len(downloaded_files), 3) - self.assertIn("bds_yaw_modes.snx", downloaded_filenames) - self.assertIn("qzss_yaw_modes.snx", downloaded_filenames) - self.assertIn("sat_yaw_bias_rate.snx", downloaded_filenames) - # Check files aren't empty - for file in downloaded_files: - self.assertGreater(file.stat().st_size, 100) - - # Re-try download - downloaded_files = ga_download.download_yaw_files(download_dir=Path(self.test_dir), if_file_present="replace") - - # Verify - self.assertEqual(len(downloaded_files), 3) From f412f1bcea57b86803c2b30b06fa5e97337473f9 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Tue, 20 Aug 2024 06:30:08 +0000 Subject: [PATCH 26/28] extend: Add default case to download_atx function as per PR comment --- gnssanalysis/gn_download.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index 3019295..b2ca796 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -847,20 +847,30 @@ def download_atx(download_dir: _Path, reference_frame: str = "IGS20", if_file_pr atx_filename = "igs20.atx" case "IGb14": atx_filename = "igs14.atx" - + case _: + raise ValueError("Invalid value passed for reference_frame var. Must be either 'IGS20' or 'IGb14'") + ensure_folders([download_dir]) - + url_igs = IGS_FILES_URL + f"station/general/{atx_filename}" url_bern = BERN_URL + "BSWUSER54/REF/I20.ATX" - + try: download_filepath = attempt_url_download( - download_dir=download_dir, url=url_igs, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present + download_dir=download_dir, + url=url_igs, + filename=atx_filename, + type_of_file="ATX", + if_file_present=if_file_present, ) except: download_filepath = attempt_url_download( - download_dir=download_dir, url=url_bern, filename=atx_filename, type_of_file="ATX", if_file_present=if_file_present - ) + download_dir=download_dir, + url=url_bern, + filename=atx_filename, + type_of_file="ATX", + if_file_present=if_file_present, + ) return download_filepath From 35d49d6c92436fb7afebd0caa329d94abf1112c1 Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Tue, 20 Aug 2024 06:36:33 +0000 Subject: [PATCH 27/28] refactor: Replace Union with pipe as py v3.10 introduced that syntactic sugar - PR comment --- gnssanalysis/gn_download.py | 11 +++++------ gnssanalysis/gn_utils.py | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index b2ca796..d41ed77 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -25,7 +25,7 @@ import ftplib as _ftplib from ftplib import FTP_TLS as _FTP_TLS from pathlib import Path as _Path -from typing import Optional as _Optional, Union as _Union, Tuple as _Tuple, List as _List +from typing import Optional as _Optional, Tuple as _Tuple, List as _List from urllib import request as _request from urllib.error import HTTPError as _HTTPError @@ -147,7 +147,7 @@ def request_metadata(url: str, max_retries: int = 5, metadata_header: str = "x-a return None -def download_url(url: str, destfile: _Union[str, _os.PathLike], max_retries: int = 5) -> _Optional[_Path]: +def download_url(url: str, destfile: str | _os.PathLike, max_retries: int = 5) -> _Optional[_Path]: logging.info(f'requesting "{url}"') for retry in range(1, max_retries + 1): try: @@ -369,16 +369,14 @@ def generate_product_filename( return product_filename, gps_date, reference_start -def check_whether_to_download( - filename: str, download_dir: _Path, if_file_present: str = "prompt_user" -) -> _Union[_Path, None]: +def check_whether_to_download(filename: str, download_dir: _Path, if_file_present: str = "prompt_user") -> _Path | None: """Determine whether to download given file (filename) to the desired location (download_dir) based on whether it is already present and what action to take if it is (if_file_present) :param str filename: Filename of the downloaded file :param _Path download_dir: Where to download files (local directory) :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return _Union[_Path, None]: Path obj to the downloaded file if file should be downloaded, otherwise returns None + :return _Path | None: Path obj to the downloaded file if file should be downloaded, otherwise returns None """ # Flag to determine whether to download: download = None @@ -840,6 +838,7 @@ def download_atx(download_dir: _Path, reference_frame: str = "IGS20", if_file_pr :param _Path download_dir: Where to download files (local directory) :param str reference_frame: Coordinate reference frame file to download, defaults to "IGS20" :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" + :raises ValueError: If an invalid option is given for reference_frame variable :return _Path: The pathlib.Path of the downloaded file """ match reference_frame: diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 54999a2..6dbd974 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -5,7 +5,7 @@ import click as _click -from typing import List as _List, Union as _Union +from typing import List as _List def diffutil_verify_input(input): @@ -52,12 +52,12 @@ def get_filetype(path): return suffix -def configure_logging(verbose: bool, output_logger: bool = False) -> _Union[_logging.Logger, None]: +def configure_logging(verbose: bool, output_logger: bool = False) -> _logging.Logger | None: """Configure the logger object with the level of verbosity requested and output if desired :param bool verbose: Verbosity of logger object to use for encoding logging strings, True: DEBUG, False: INFO :param bool output_logger: Flag to indicate whether to output the Logger object, defaults to False - :return _Union[_logging.Logger, None]: Return the logger object or None (based on output_logger) + :return _logging.Logger | None: Return the logger object or None (based on output_logger) """ if verbose: logging_level = _logging.DEBUG From 3f5a3ba803254a97246f83b4b2a861bd82c4c1af Mon Sep 17 00:00:00 2001 From: Ronald Maj Date: Tue, 20 Aug 2024 06:42:55 +0000 Subject: [PATCH 28/28] doc: Make docs consistent and clear as per PR comments --- gnssanalysis/gn_download.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gnssanalysis/gn_download.py b/gnssanalysis/gn_download.py index d41ed77..37c5acd 100644 --- a/gnssanalysis/gn_download.py +++ b/gnssanalysis/gn_download.py @@ -376,7 +376,7 @@ def check_whether_to_download(filename: str, download_dir: _Path, if_file_presen :param str filename: Filename of the downloaded file :param _Path download_dir: Where to download files (local directory) :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return _Path | None: Path obj to the downloaded file if file should be downloaded, otherwise returns None + :return _Path | None: pathlib.Path to the downloaded file if file should be downloaded, otherwise returns None """ # Flag to determine whether to download: download = None @@ -451,12 +451,12 @@ def attempt_url_download( ) -> _Path: """Attempt download of file given URL (url) to chosen location (download_dir) - :param Path download_dir: Path obj to download directory + :param _Path download_dir: Where to download files (local directory) :param str url: URL to download :param str filename: Filename to assign for the downloaded file, defaults to None :param str type_of_file: How to label the file for STDOUT messages, defaults to None :param str if_file_present: What to do if file already present: "replace", "dont_replace", defaults to "prompt_user" - :return Path: Path obj to the downloaded file + :return _Path: The pathlib.Path of the downloaded file """ # If the filename is not provided, use the filename from the URL if not filename: