def _fast_alloc(fn: T.TextIO | Path, Nl_sv: int) -> int: """ prescan first N lines of file to see if it truncates to less than 80 bytes Picking N: N > Nobs+4 or so. 100 seemed a good start. """ if isinstance(fn, Path): assert fn.is_file(), "need freshly opened file" elif isinstance(fn, io.StringIO): fn.seek(0) else: raise TypeError(f"Unknown filetype {type(fn)}") ln = "" # in case of truncated file, don't crash with opener(fn) as f: _skip_header(f) # %% find the first line with time (sometimes a blank line or two after header) for ln in f: try: t = _timeobs(ln) except ValueError: continue if isinstance(t, datetime): break try: _getsvind(f, ln) except ValueError as e: logging.debug(e) return 0 raw = [f.readline() for _ in range(Nl_sv)] lens = list(map(len, raw)) if max(lens) < 79: # oddly formatted file, no prediction return 0 shorts = sum(ln < 79 for ln in lens) return len(lens) - shorts
def rinexheader(fn: T.TextIO | Path) -> dict[str, T.Any]: """ retrieve RINEX 2/3 or CRINEX 1/3 header as unparsed dict() """ if isinstance(fn, (str, Path)): fn = Path(fn).expanduser() if isinstance(fn, Path) and fn.suffix == ".nc": return rinexinfo(fn) elif isinstance(fn, Path): with opener(fn, header=True) as f: return rinexheader(f) elif isinstance(fn, io.StringIO): fn.seek(0) elif isinstance(fn, io.TextIOWrapper): pass else: raise TypeError(f"unknown RINEX filetype {type(fn)}") info = rinexinfo(fn) if int(info["version"]) in (1, 2): if info["rinextype"] == "obs": hdr = obsheader2(fn) elif info["rinextype"] == "nav": hdr = navheader2(fn) else: raise ValueError(f"Unknown rinex type {info} in {fn}") elif int(info["version"]) == 3: if info["rinextype"] == "obs": hdr = obsheader3(fn) elif info["rinextype"] == "nav": hdr = navheader3(fn) else: raise ValueError(f"Unknown rinex type {info} in {fn}") else: raise ValueError(f"unknown RINEX {info} {fn}") return hdr
def opener(fn: T.TextIO | Path, header: bool = False) -> T.Iterator[T.TextIO]: """provides file handle for regular ASCII or gzip files transparently""" if isinstance(fn, str): fn = Path(fn).expanduser() if isinstance(fn, io.StringIO): fn.seek(0) yield fn elif isinstance(fn, Path): # need to have this check for Windows if not fn.is_file(): raise FileNotFoundError(fn) finf = fn.stat() if finf.st_size > 100e6: logging.info(f"opening {finf.st_size/1e6} MByte {fn.name}") suffix = fn.suffix.lower() if suffix == ".gz": with gzip.open(fn, "rt") as f: _, is_crinex = rinex_version(first_nonblank_line(f)) f.seek(0) if is_crinex and not header: # Conversion to string is necessary because of a quirk where gzip.open() # even with 'rt' doesn't decompress until read. f = io.StringIO(crx2rnx(f.read())) yield f elif suffix == ".bz2": # this is for plain bzip2 files, NOT tar.bz2, which requires f.seek(512) with bz2.open(fn, "rt") as f: _, is_crinex = rinex_version(first_nonblank_line(f)) f.seek(0) if is_crinex and not header: f = io.StringIO(crx2rnx(f.read())) yield f elif suffix == ".zip": with zipfile.ZipFile(fn, "r") as z: flist = z.namelist() for rinexfn in flist: with z.open(rinexfn, "r") as bf: f = io.StringIO( io.TextIOWrapper( bf, encoding="ascii", errors="ignore").read() # type: ignore ) yield f elif suffix == ".z": if unlzw is None: raise ImportError("pip install unlzw3") with fn.open("rb") as zu: with io.StringIO(unlzw(zu.read()).decode("ascii")) as f: yield f else: # assume not compressed (or Hatanaka) with fn.open("r", encoding="ascii", errors="ignore") as f: _, is_crinex = rinex_version(first_nonblank_line(f)) f.seek(0) if is_crinex and not header: f = io.StringIO(crx2rnx(f)) yield f else: raise OSError(f"Unsure what to do with input of type: {type(fn)}")
def rinexinfo(f: T.TextIO | Path) -> dict[str, T.Any]: """verify RINEX version""" if isinstance(f, (str, Path)): fn = Path(f).expanduser() if fn.suffix == ".nc": attrs: dict[str, T.Any] = {"rinextype": []} for g in ("OBS", "NAV"): try: dat = xarray.open_dataset(fn, group=g) attrs["rinextype"].append(g.lower()) except OSError: continue attrs.update(dat.attrs) return attrs with opener(fn, header=True) as f: return rinexinfo(f) f.seek(0) try: line = first_nonblank_line(f) # don't choke on binary files if line.startswith(("#a", "#c", "#d")): return {"version": line[1], "rinextype": "sp3"} version = rinex_version(line)[0] file_type = line[20] if int(version) == 2: if file_type == "N": system = "G" elif file_type == "G": system = "R" elif file_type == "E": system = "E" else: system = line[40] else: system = line[40] if line[20] in ("O", "C"): rinex_type = "obs" elif line[20] == "N" or "NAV" in line[20:40]: rinex_type = "nav" else: rinex_type = line[20] info = { "version": version, "filetype": file_type, "rinextype": rinex_type, "systems": system, } except (TypeError, AttributeError, ValueError) as e: # keep ValueError for consistent user error handling raise ValueError(f"not a known/valid RINEX file. {e}") return info
def obsheader2( f: T.TextIO | Path, useindicators: bool = False, meas: list[str] = None ) -> dict[str, T.Any]: """ End users should use rinexheader() """ if isinstance(f, (str, Path)): with opener(f, header=True) as h: return obsheader2(h, useindicators, meas) f.seek(0) # %% selection if isinstance(meas, str): meas = [meas] if not meas or not meas[0].strip(): meas = None hdr = rinexinfo(f) Nobs = 0 # not None due to type checking for ln in f: if "END OF HEADER" in ln: break hd = ln[60:80].strip() c = ln[:60] # %% measurement types if "# / TYPES OF OBSERV" in hd: if Nobs == 0: Nobs = int(c[:6]) hdr[hd] = c[6:].split() else: hdr[hd] += c[6:].split() elif hd not in hdr: # Header label hdr[hd] = c # string with info else: # concatenate hdr[hd] += " " + c # %% useful values try: hdr["systems"] = hdr["RINEX VERSION / TYPE"][40] except KeyError: pass hdr["Nobs"] = Nobs # 5 observations per line (incorporating LLI, SSI) hdr["Nl_sv"] = ceil(hdr["Nobs"] / 5) # %% list with receiver location in x,y,z cartesian ECEF (OPTIONAL) try: hdr["position"] = [float(j) for j in hdr["APPROX POSITION XYZ"].split()] if ecef2geodetic is not None: hdr["position_geodetic"] = ecef2geodetic(*hdr["position"]) except (KeyError, ValueError): pass # %% observation types try: hdr["fields"] = hdr["# / TYPES OF OBSERV"] if hdr["Nobs"] != len(hdr["fields"]): logging.error( f"{f.name} number of observations declared in header does not match fields" ) hdr["Nobs"] = len(hdr["fields"]) if isinstance(meas, (tuple, list, np.ndarray)): ind: T.Any = np.zeros(len(hdr["fields"]), dtype=bool) for m in meas: for i, field in enumerate(hdr["fields"]): if field.startswith(m): ind[i] = True hdr["fields_ind"] = np.nonzero(ind)[0] else: ind = np.s_[:] hdr["fields_ind"] = np.arange(hdr["Nobs"]) hdr["fields"] = np.array(hdr["fields"])[ind].tolist() except KeyError: pass hdr["Nobsused"] = hdr["Nobs"] if useindicators: hdr["Nobsused"] *= 3 # %% try: hdr["# OF SATELLITES"] = int(hdr["# OF SATELLITES"][:6]) except (KeyError, ValueError): pass # %% time try: hdr["t0"] = _timehdr(hdr["TIME OF FIRST OBS"]) except (KeyError, ValueError): pass try: hdr["t1"] = _timehdr(hdr["TIME OF LAST OBS"]) except (KeyError, ValueError): pass try: # This key is OPTIONAL hdr["interval"] = float(hdr["INTERVAL"][:10]) except (KeyError, ValueError): pass try: s = " " hdr["rxmodel"] = s.join(hdr["REC # / TYPE / VERS"].split()[1:-1]) except (KeyError, ValueError): pass return hdr