gagely 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gagely/__init__.py +11 -0
- gagely/_dss_utils.py +106 -0
- gagely/_version.py +24 -0
- gagely/contrail/__init__.py +733 -0
- gagely/contrail/gage_lookup.py +262 -0
- gagely/usgs/__init__.py +42 -0
- gagely/usgs/_core.py +491 -0
- gagely/usgs/continuous.py +380 -0
- gagely/usgs/daily.py +432 -0
- gagely/usgs/metadata.py +134 -0
- gagely-0.1.0.dist-info/METADATA +184 -0
- gagely-0.1.0.dist-info/RECORD +15 -0
- gagely-0.1.0.dist-info/WHEEL +5 -0
- gagely-0.1.0.dist-info/licenses/LICENSE +21 -0
- gagely-0.1.0.dist-info/top_level.txt +1 -0
gagely/__init__.py
ADDED
gagely/_dss_utils.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Shared DSS interval-inference and gap-fill helpers.
|
|
2
|
+
|
|
3
|
+
Used by both ``gagely.usgs`` and ``gagely.contrail`` so that interval
|
|
4
|
+
detection lives in one place.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
from pydsstools.core import DssPathName, UNDEFINED
|
|
13
|
+
|
|
14
|
+
__all__: List[str] = []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _infer_interval_seconds(times: pd.Series) -> Optional[int]:
|
|
18
|
+
"""Return the modal sampling interval in seconds across all of *times*.
|
|
19
|
+
|
|
20
|
+
Computes consecutive diffs over the full sorted series and returns the
|
|
21
|
+
most common diff in seconds. Logs a warning if more than one unique
|
|
22
|
+
diff is found.
|
|
23
|
+
|
|
24
|
+
Returns ``None`` if fewer than 2 samples are available.
|
|
25
|
+
"""
|
|
26
|
+
if len(times) < 2:
|
|
27
|
+
return None
|
|
28
|
+
diffs = times.sort_values().diff().dropna()
|
|
29
|
+
if diffs.empty:
|
|
30
|
+
return None
|
|
31
|
+
counts = diffs.value_counts()
|
|
32
|
+
modal_diff = counts.index[0]
|
|
33
|
+
modal_seconds = int(modal_diff.total_seconds())
|
|
34
|
+
if len(counts) > 1:
|
|
35
|
+
anomalies = [
|
|
36
|
+
(int(d.total_seconds()), int(c))
|
|
37
|
+
for d, c in counts.iloc[1:4].items()
|
|
38
|
+
]
|
|
39
|
+
logging.warning(
|
|
40
|
+
"Non-uniform intervals: modal=%ds (%d/%d rows); top anomalies %s",
|
|
41
|
+
modal_seconds, int(counts.iloc[0]), int(counts.sum()), anomalies,
|
|
42
|
+
)
|
|
43
|
+
return modal_seconds
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _infer_interval_epart(times: pd.Series) -> Optional[str]:
|
|
47
|
+
"""Return the DSS E-part string for the modal interval in *times*, or ``None``."""
|
|
48
|
+
seconds = _infer_interval_seconds(times)
|
|
49
|
+
if seconds is None:
|
|
50
|
+
return None
|
|
51
|
+
return DssPathName.interval_to_epart(seconds)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_FILL_PROFILES = ("undefined", "ffill", "bfill", "interpolate")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _densify_timeseries(
|
|
58
|
+
times: List[datetime],
|
|
59
|
+
values: List[float],
|
|
60
|
+
interval_seconds: int,
|
|
61
|
+
profile: str = "undefined",
|
|
62
|
+
) -> Tuple[List[datetime], List[float]]:
|
|
63
|
+
"""Reindex a (times, values) pair onto a regular *interval_seconds* grid.
|
|
64
|
+
|
|
65
|
+
Gap slots are filled according to *profile*:
|
|
66
|
+
|
|
67
|
+
- ``"undefined"`` (default): stamp ``UNDEFINED`` (-9999.0).
|
|
68
|
+
- ``"ffill"``: forward-fill from the last known value; leading gaps → UNDEFINED.
|
|
69
|
+
- ``"bfill"``: backward-fill from the next known value; trailing gaps → UNDEFINED.
|
|
70
|
+
- ``"interpolate"``: linear interpolation between bounding values; edge gaps → UNDEFINED.
|
|
71
|
+
|
|
72
|
+
The grid is bounded by ``min(times)`` and ``max(times)``; the caller's
|
|
73
|
+
requested window is not padded.
|
|
74
|
+
|
|
75
|
+
Returns a new ``(times, values)`` pair sorted by time.
|
|
76
|
+
"""
|
|
77
|
+
if profile not in _FILL_PROFILES:
|
|
78
|
+
raise ValueError(f"profile must be one of {_FILL_PROFILES!r}, got {profile!r}")
|
|
79
|
+
|
|
80
|
+
s = pd.Series(values, index=pd.to_datetime(times), name="value").sort_index()
|
|
81
|
+
s = s[~s.index.duplicated(keep="first")]
|
|
82
|
+
|
|
83
|
+
diffs = s.index.to_series().diff().dropna()
|
|
84
|
+
if (diffs == pd.Timedelta(seconds=interval_seconds)).all():
|
|
85
|
+
return s.index.to_pydatetime().tolist(), s.tolist()
|
|
86
|
+
|
|
87
|
+
target = pd.date_range(
|
|
88
|
+
start=s.index.min(), end=s.index.max(), freq=f"{interval_seconds}s"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if profile == "undefined":
|
|
92
|
+
densified = s.reindex(target, fill_value=UNDEFINED)
|
|
93
|
+
elif profile == "ffill":
|
|
94
|
+
densified = s.reindex(target).ffill().fillna(UNDEFINED)
|
|
95
|
+
elif profile == "bfill":
|
|
96
|
+
densified = s.reindex(target).bfill().fillna(UNDEFINED)
|
|
97
|
+
else: # interpolate
|
|
98
|
+
densified = s.reindex(target).interpolate(method="time").fillna(UNDEFINED)
|
|
99
|
+
|
|
100
|
+
n_filled = len(densified) - len(s)
|
|
101
|
+
if n_filled > 0:
|
|
102
|
+
logging.debug(
|
|
103
|
+
"_densify_timeseries: filled %d / %d slots (profile=%r)",
|
|
104
|
+
n_filled, len(densified), profile,
|
|
105
|
+
)
|
|
106
|
+
return densified.index.to_pydatetime().tolist(), densified.tolist()
|
gagely/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|